Files
2026-03-23 00:51:38 +09:00

246 lines
14 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>