first commit
This commit is contained in:
245
resources/js/Pages/Migrations/ScheduleForm.vue
Normal file
245
resources/js/Pages/Migrations/ScheduleForm.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user