246 lines
14 KiB
Vue
246 lines
14 KiB
Vue
<template>
|
||
<AppLayout>
|
||
<template #header>
|
||
<h2 class="text-xl font-bold">
|
||
{{ schedule?.id ? 'Редактирование' : 'Создание' }} расписания миграции
|
||
</h2>
|
||
</template>
|
||
|
||
<Card>
|
||
<template #content>
|
||
<form @submit.prevent="submit" class="flex flex-col gap-4">
|
||
<div>
|
||
<label for="name" class="block text-sm font-medium mb-1">Название *</label>
|
||
<InputText id="name" v-model="form.name" class="w-full" :class="{ 'p-invalid': form.errors.name }" />
|
||
<small class="p-error">{{ form.errors.name }}</small>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label for="source_database_id" class="block text-sm font-medium mb-1">Исходная БД *</label>
|
||
<Dropdown id="source_database_id" v-model="form.source_database_id"
|
||
:options="sourceDatabases" optionLabel="name" optionValue="id"
|
||
class="w-full" :class="{ 'p-invalid': form.errors.source_database_id }"
|
||
placeholder="Выберите исходную БД" />
|
||
<small class="p-error">{{ form.errors.source_database_id }}</small>
|
||
</div>
|
||
<div>
|
||
<label for="target_database_id" class="block text-sm font-medium mb-1">Целевая БД *</label>
|
||
<Dropdown id="target_database_id" v-model="form.target_database_id"
|
||
:options="targetDatabases" optionLabel="name" optionValue="id"
|
||
class="w-full" :class="{ 'p-invalid': form.errors.target_database_id }"
|
||
placeholder="Выберите целевую БД" />
|
||
<small class="p-error">{{ form.errors.target_database_id }}</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium mb-1">Таблицы *</label>
|
||
<Listbox v-model="form.tables" :options="availableTables" optionLabel="table_name" optionValue="id"
|
||
multiple class="w-full h-64" :class="{ 'p-invalid': form.errors.tables }" />
|
||
<small class="p-error">{{ form.errors.tables }}</small>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label for="cron_expression" class="block text-sm font-medium mb-1">Cron выражение *</label>
|
||
<InputText id="cron_expression" v-model="form.cron_expression" class="w-full"
|
||
placeholder="0 * * * *" :class="{ 'p-invalid': form.errors.cron_expression }" />
|
||
<small class="text-muted-color">Например: "0 * * * *" - каждый час, "0 0 * * *" - ежедневно</small>
|
||
<small class="p-error">{{ form.errors.cron_expression }}</small>
|
||
</div>
|
||
<div>
|
||
<label for="timezone" class="block text-sm font-medium mb-1">Часовой пояс</label>
|
||
<InputText id="timezone" v-model="form.timezone" class="w-full" placeholder="UTC" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label for="batch_size" class="block text-sm font-medium mb-1">Размер пакета</label>
|
||
<InputNumber id="batch_size" v-model="form.batch_size" class="w-full" />
|
||
</div>
|
||
<div>
|
||
<label for="python_script_path" class="block text-sm font-medium mb-1">Путь к скрипту Python</label>
|
||
<InputText id="python_script_path" v-model="form.python_script_path" class="w-full"
|
||
placeholder="/path/to/migration_script.py" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div class="flex items-center gap-2">
|
||
<Checkbox id="is_active" v-model="form.is_active" :binary="true" />
|
||
<label for="is_active" class="text-sm">Активно</label>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<Checkbox id="is_incremental" v-model="form.is_incremental" :binary="true" />
|
||
<label for="is_incremental" class="text-sm">Инкрементальная миграция</label>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<Checkbox id="run_in_parallel" v-model="form.run_in_parallel" :binary="true" />
|
||
<label for="run_in_parallel" class="text-sm">Параллельное выполнение</label>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<Checkbox id="truncate_before_migration" v-model="form.truncate_before_migration" :binary="true" />
|
||
<label for="truncate_before_migration" class="text-sm">Очищать таблицы перед миграцией</label>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<Checkbox id="create_indexes_after" v-model="form.create_indexes_after" :binary="true" />
|
||
<label for="create_indexes_after" class="text-sm">Создавать индексы после миграции</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="form.is_incremental" class="p-4 bg-surface-50 dark:bg-surface-900 rounded-lg">
|
||
<label for="incremental_column" class="block text-sm font-medium mb-1">Колонка для инкрементальной миграции</label>
|
||
<InputText id="incremental_column" v-model="form.incremental_column" class="w-full"
|
||
placeholder="updated_at" />
|
||
<small class="text-muted-color">Данные будут мигрированы только с момента последнего запуска (по этой колонке)</small>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div class="flex items-center gap-2">
|
||
<Checkbox id="use_life_table" v-model="form.use_life_table" :binary="true" />
|
||
<label for="use_life_table" class="text-sm">Использовать Life-таблицы (MSSQL)</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="form.use_life_table" class="p-4 bg-surface-50 dark:bg-surface-900 rounded-lg space-y-4">
|
||
<h4 class="font-bold text-sm">Настройки Life-таблиц</h4>
|
||
|
||
<div>
|
||
<label for="life_table_name" class="block text-sm font-medium mb-1">Life-таблица</label>
|
||
<InputText id="life_table_name" v-model="form.life_table_name" class="w-full"
|
||
placeholder="Life_oms_kl_VisitResult" />
|
||
<small class="text-muted-color">Например: Life_oms_kl_VisitResult</small>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label for="life_id_column" class="block text-sm font-medium mb-1">LifeID колонка</label>
|
||
<InputText id="life_id_column" v-model="form.life_id_column" class="w-full"
|
||
placeholder="kl_VisitResultLifeID" />
|
||
</div>
|
||
<div>
|
||
<label for="base_id_column" class="block text-sm font-medium mb-1">Base ID колонка</label>
|
||
<InputText id="base_id_column" v-model="form.base_id_column" class="w-full"
|
||
placeholder="kl_VisitResultID" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label for="operation_column" class="block text-sm font-medium mb-1">Колонка операции</label>
|
||
<InputText id="operation_column" v-model="form.operation_column" class="w-full"
|
||
placeholder="x_Operation" />
|
||
<small class="text-muted-color">x_Operation (i/u/d)</small>
|
||
</div>
|
||
<div>
|
||
<label for="datetime_column" class="block text-sm font-medium mb-1">Колонка времени</label>
|
||
<InputText id="datetime_column" v-model="form.datetime_column" class="w-full"
|
||
placeholder="x_DateTime" />
|
||
<small class="text-muted-color">x_DateTime</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label for="description" class="block text-sm font-medium mb-1">Описание</label>
|
||
<Textarea id="description" v-model="form.description" class="w-full" rows="3" />
|
||
</div>
|
||
|
||
<div class="flex gap-2 pt-4">
|
||
<Button type="submit" label="Сохранить" icon="pi pi-check" :loading="form.processing" />
|
||
<Link :href="route('migrations.index')">
|
||
<Button type="button" label="Отмена" severity="secondary" />
|
||
</Link>
|
||
</div>
|
||
</form>
|
||
</template>
|
||
</Card>
|
||
</AppLayout>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, watch } from 'vue';
|
||
import { useForm, Link } from '@inertiajs/vue3';
|
||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||
import Card from 'primevue/card';
|
||
import InputText from 'primevue/inputtext';
|
||
import Textarea from 'primevue/textarea';
|
||
import Button from 'primevue/button';
|
||
import Dropdown from 'primevue/dropdown';
|
||
import Listbox from 'primevue/listbox';
|
||
import Checkbox from 'primevue/checkbox';
|
||
import InputNumber from 'primevue/inputnumber';
|
||
|
||
const props = defineProps({
|
||
schedule: Object,
|
||
sourceDatabases: Array,
|
||
targetDatabases: Array,
|
||
tables: Object,
|
||
});
|
||
|
||
const availableTables = computed(() => {
|
||
if (!props.tables) return [];
|
||
|
||
// Если tables это объект с ключами source_database_id
|
||
// if (form.source_database_id && props.tables[form.source_database_id]) {
|
||
// const tables = props.tables[form.source_database_id];
|
||
// console.log(props.tables)
|
||
// return Array.isArray(tables) ? tables : [];
|
||
// }
|
||
|
||
|
||
|
||
// Если tables это плоский массив
|
||
if (Array.isArray(props.tables)) {
|
||
return props.tables;
|
||
}
|
||
|
||
// Если tables это объект, преобразуем в массив
|
||
if (typeof props.tables === 'object') {
|
||
return Object.values(props.tables).flat();
|
||
}
|
||
|
||
return [];
|
||
});
|
||
|
||
const form = useForm({
|
||
name: props.schedule?.name || '',
|
||
source_database_id: props.schedule?.source_database_id || null,
|
||
target_database_id: props.schedule?.target_database_id || null,
|
||
tables: props.schedule?.scheduled_tables?.map(t => t.id) || [],
|
||
cron_expression: props.schedule?.cron_expression || '',
|
||
timezone: props.schedule?.timezone || 'UTC',
|
||
is_active: props.schedule?.is_active ?? true,
|
||
is_incremental: props.schedule?.is_incremental ?? false,
|
||
incremental_column: props.schedule?.incremental_column || 'updated_at',
|
||
use_life_table: props.schedule?.use_life_table ?? false,
|
||
life_table_name: props.schedule?.life_table_name || '',
|
||
life_id_column: props.schedule?.life_id_column || '',
|
||
base_id_column: props.schedule?.base_id_column || '',
|
||
operation_column: props.schedule?.operation_column || 'x_Operation',
|
||
datetime_column: props.schedule?.datetime_column || 'x_DateTime',
|
||
run_in_parallel: props.schedule?.run_in_parallel ?? false,
|
||
batch_size: props.schedule?.batch_size || 1000,
|
||
truncate_before_migration: props.schedule?.truncate_before_migration ?? false,
|
||
create_indexes_after: props.schedule?.create_indexes_after ?? true,
|
||
python_script_path: props.schedule?.python_script_path || '',
|
||
description: props.schedule?.description || '',
|
||
});
|
||
|
||
watch(() => form.source_database_id, (newVal) => {
|
||
if (newVal) {
|
||
form.tables = [];
|
||
}
|
||
});
|
||
|
||
const submit = () => {
|
||
if (props.schedule?.id) {
|
||
form.put(route('migrations.update', props.schedule.id));
|
||
} else {
|
||
form.post(route('migrations.store'));
|
||
}
|
||
};
|
||
</script>
|