first commit
This commit is contained in:
8
app/Http/Controllers/Controller.php
Normal file
8
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
40
app/Http/Controllers/DashboardController.php
Normal file
40
app/Http/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SourceDatabase;
|
||||
use App\Models\TargetDatabase;
|
||||
use App\Models\Table;
|
||||
use App\Models\MigrationSchedule;
|
||||
use App\Models\MigrationRun;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$stats = [
|
||||
'source_databases' => SourceDatabase::count(),
|
||||
'target_databases' => TargetDatabase::count(),
|
||||
'tables' => Table::count(),
|
||||
'schedules' => MigrationSchedule::where('is_active', true)->count(),
|
||||
];
|
||||
|
||||
$recentMigrations = MigrationRun::with('schedule')
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(10)
|
||||
->get()
|
||||
->map(fn($run) => [
|
||||
'id' => $run->id,
|
||||
'schedule_name' => $run->schedule->name ?? 'N/A',
|
||||
'status' => $run->status,
|
||||
'started_at' => $run->started_at?->format('Y-m-d H:i:s'),
|
||||
'completed_at' => $run->completed_at?->format('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
return Inertia::render('Dashboard', [
|
||||
'stats' => $stats,
|
||||
'recentMigrations' => $recentMigrations,
|
||||
]);
|
||||
}
|
||||
}
|
||||
231
app/Http/Controllers/MigrationScheduleController.php
Normal file
231
app/Http/Controllers/MigrationScheduleController.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\MigrationSchedule;
|
||||
use App\Models\SourceDatabase;
|
||||
use App\Models\TargetDatabase;
|
||||
use App\Models\Table;
|
||||
use App\Models\MigrationRun;
|
||||
use App\Jobs\RunMigrationJob;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class MigrationScheduleController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$schedules = MigrationSchedule::with(['sourceDatabase', 'targetDatabase'])
|
||||
->withCount('migrationRuns')
|
||||
->get();
|
||||
|
||||
return Inertia::render('Migrations/Index', [
|
||||
'schedules' => $schedules,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$sourceDatabases = SourceDatabase::where('is_active', true)->get();
|
||||
$targetDatabases = TargetDatabase::where('is_active', true)->get();
|
||||
$tables = Table::with('sourceDatabase')->get()->groupBy('source_database_id');
|
||||
|
||||
return Inertia::render('Migrations/ScheduleForm', [
|
||||
'schedule' => null,
|
||||
'sourceDatabases' => $sourceDatabases,
|
||||
'targetDatabases' => $targetDatabases,
|
||||
'tables' => $tables,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'source_database_id' => 'required|exists:source_databases,id',
|
||||
'target_database_id' => 'required|exists:target_databases,id',
|
||||
'tables' => 'required|array|min:1',
|
||||
'tables.*' => 'exists:tables,id',
|
||||
'cron_expression' => 'required|string|max:100',
|
||||
'timezone' => 'nullable|string|max:50',
|
||||
'is_active' => 'boolean',
|
||||
'run_in_parallel' => 'boolean',
|
||||
'batch_size' => 'integer|min:1',
|
||||
'truncate_before_migration' => 'boolean',
|
||||
'create_indexes_after' => 'boolean',
|
||||
'python_script_path' => 'nullable|string',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['timezone'] = $validated['timezone'] ?? 'UTC';
|
||||
$validated['is_active'] = $validated['is_active'] ?? true;
|
||||
$validated['is_incremental'] = $validated['is_incremental'] ?? false;
|
||||
$validated['incremental_column'] = $validated['is_incremental'] ? ($validated['incremental_column'] ?? 'updated_at') : null;
|
||||
$validated['use_life_table'] = $validated['use_life_table'] ?? false;
|
||||
|
||||
if ($validated['use_life_table']) {
|
||||
$validated['life_table_name'] = $validated['life_table_name'] ?? null;
|
||||
$validated['life_id_column'] = $validated['life_id_column'] ?? null;
|
||||
$validated['base_id_column'] = $validated['base_id_column'] ?? null;
|
||||
$validated['operation_column'] = $validated['operation_column'] ?? 'x_Operation';
|
||||
$validated['datetime_column'] = $validated['datetime_column'] ?? 'x_DateTime';
|
||||
}
|
||||
|
||||
$validated['run_in_parallel'] = $validated['run_in_parallel'] ?? false;
|
||||
$validated['batch_size'] = $validated['batch_size'] ?? 1000;
|
||||
$validated['truncate_before_migration'] = $validated['truncate_before_migration'] ?? false;
|
||||
$validated['create_indexes_after'] = $validated['create_indexes_after'] ?? true;
|
||||
|
||||
$validated['python_script_args'] = [
|
||||
'incremental' => $validated['is_incremental'],
|
||||
'incremental_column' => $validated['incremental_column'],
|
||||
'use_life_table' => $validated['use_life_table'],
|
||||
'life_table_name' => $validated['life_table_name'],
|
||||
'life_id_column' => $validated['life_id_column'],
|
||||
'base_id_column' => $validated['base_id_column'],
|
||||
'operation_column' => $validated['operation_column'],
|
||||
'datetime_column' => $validated['datetime_column'],
|
||||
];
|
||||
|
||||
$schedule = MigrationSchedule::create($validated);
|
||||
|
||||
// Attach tables to schedule
|
||||
$schedule->scheduledTables()->sync($validated['tables']);
|
||||
|
||||
return redirect()->route('migrations.index')
|
||||
->with('success', 'Migration schedule created successfully.');
|
||||
}
|
||||
|
||||
public function edit(MigrationSchedule $schedule)
|
||||
{
|
||||
$schedule->load('scheduledTables');
|
||||
|
||||
$sourceDatabases = SourceDatabase::where('is_active', true)->get();
|
||||
$targetDatabases = TargetDatabase::where('is_active', true)->get();
|
||||
$tables = Table::with('sourceDatabase')
|
||||
->where('source_database_id', $schedule->source_database_id)
|
||||
->get();
|
||||
|
||||
return Inertia::render('Migrations/ScheduleForm', [
|
||||
'schedule' => $schedule,
|
||||
'sourceDatabases' => $sourceDatabases,
|
||||
'targetDatabases' => $targetDatabases,
|
||||
'tables' => $tables,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, MigrationSchedule $schedule)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'source_database_id' => 'required|exists:source_databases,id',
|
||||
'target_database_id' => 'required|exists:target_databases,id',
|
||||
'tables' => 'required|array|min:1',
|
||||
'tables.*' => 'exists:tables,id',
|
||||
'cron_expression' => 'required|string|max:100',
|
||||
'timezone' => 'nullable|string|max:50',
|
||||
'is_active' => 'boolean',
|
||||
'is_incremental' => 'boolean',
|
||||
'incremental_column' => 'nullable|string|max:100',
|
||||
'use_life_table' => 'boolean',
|
||||
'life_table_name' => 'nullable|string|max:255',
|
||||
'life_id_column' => 'nullable|string|max:100',
|
||||
'base_id_column' => 'nullable|string|max:100',
|
||||
'operation_column' => 'nullable|string|max:100',
|
||||
'datetime_column' => 'nullable|string|max:100',
|
||||
'run_in_parallel' => 'boolean',
|
||||
'batch_size' => 'integer|min:1',
|
||||
'truncate_before_migration' => 'boolean',
|
||||
'create_indexes_after' => 'boolean',
|
||||
'python_script_path' => 'nullable|string',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['is_incremental'] = $validated['is_incremental'] ?? false;
|
||||
$validated['incremental_column'] = $validated['is_incremental'] ? ($validated['incremental_column'] ?? 'updated_at') : null;
|
||||
$validated['use_life_table'] = $validated['use_life_table'] ?? false;
|
||||
|
||||
if ($validated['use_life_table']) {
|
||||
$validated['life_table_name'] = $validated['life_table_name'] ?? null;
|
||||
$validated['life_id_column'] = $validated['life_id_column'] ?? null;
|
||||
$validated['base_id_column'] = $validated['base_id_column'] ?? null;
|
||||
$validated['operation_column'] = $validated['operation_column'] ?? 'x_Operation';
|
||||
$validated['datetime_column'] = $validated['datetime_column'] ?? 'x_DateTime';
|
||||
}
|
||||
|
||||
$validated['python_script_args'] = [
|
||||
'incremental' => $validated['is_incremental'],
|
||||
'incremental_column' => $validated['incremental_column'],
|
||||
'use_life_table' => $validated['use_life_table'],
|
||||
'life_table_name' => $validated['life_table_name'],
|
||||
'life_id_column' => $validated['life_id_column'],
|
||||
'base_id_column' => $validated['base_id_column'],
|
||||
'operation_column' => $validated['operation_column'],
|
||||
'datetime_column' => $validated['datetime_column'],
|
||||
];
|
||||
|
||||
$schedule->update($validated);
|
||||
|
||||
// Sync tables
|
||||
$schedule->scheduledTables()->sync($validated['tables']);
|
||||
|
||||
return redirect()->route('migrations.index')
|
||||
->with('success', 'Migration schedule updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy(MigrationSchedule $schedule)
|
||||
{
|
||||
$schedule->delete();
|
||||
|
||||
return redirect()->route('migrations.index')
|
||||
->with('success', 'Migration schedule deleted successfully.');
|
||||
}
|
||||
|
||||
public function runNow(MigrationSchedule $schedule)
|
||||
{
|
||||
// Check if there's already a running or pending migration for this schedule
|
||||
$existingRun = MigrationRun::where('schedule_id', $schedule->id)
|
||||
->whereIn('status', ['pending', 'running'])
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($existingRun) {
|
||||
return redirect()->back()
|
||||
->with('warning', 'Миграция уже выполняется или ожидает в очереди.');
|
||||
}
|
||||
|
||||
$migrationRun = MigrationRun::create([
|
||||
'schedule_id' => $schedule->id,
|
||||
'status' => 'pending',
|
||||
'total_tables' => count($schedule->tables),
|
||||
]);
|
||||
|
||||
RunMigrationJob::dispatch($migrationRun)->onQueue('default');
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Миграция запущена.');
|
||||
}
|
||||
|
||||
public function toggle(MigrationSchedule $schedule)
|
||||
{
|
||||
$schedule->update(['is_active' => !$schedule->is_active]);
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Schedule ' . ($schedule->is_active ? 'activated' : 'deactivated') . '.');
|
||||
}
|
||||
|
||||
public function show(MigrationSchedule $schedule)
|
||||
{
|
||||
$schedule->load(['sourceDatabase', 'targetDatabase', 'scheduledTables']);
|
||||
|
||||
$runs = MigrationRun::where('schedule_id', $schedule->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(50)
|
||||
->get();
|
||||
|
||||
return Inertia::render('Migrations/Show', [
|
||||
'schedule' => $schedule,
|
||||
'runs' => $runs,
|
||||
]);
|
||||
}
|
||||
}
|
||||
100
app/Http/Controllers/SchemaController.php
Normal file
100
app/Http/Controllers/SchemaController.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SourceDatabase;
|
||||
use App\Models\Table;
|
||||
use App\Models\Column;
|
||||
use App\Services\SchemaExtractionService;
|
||||
use App\Services\SchemaComparisonService;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SchemaController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private SchemaExtractionService $extractionService,
|
||||
private SchemaComparisonService $comparisonService
|
||||
) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$databases = SourceDatabase::with('tables')->get();
|
||||
|
||||
return Inertia::render('Schemas/Index', [
|
||||
'databases' => $databases,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(SourceDatabase $database)
|
||||
{
|
||||
$database->load(['tables.columns', 'tables.indexes', 'tables.foreignKeys']);
|
||||
|
||||
return Inertia::render('Schemas/Show', [
|
||||
'database' => $database,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sync(SourceDatabase $database)
|
||||
{
|
||||
try {
|
||||
$this->extractionService->saveSchema($database);
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Schema synchronized successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()
|
||||
->with('error', 'Failed to sync schema: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function checkChanges(SourceDatabase $database)
|
||||
{
|
||||
$changes = $this->comparisonService->checkForChanges($database);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'changes' => $changes,
|
||||
]);
|
||||
}
|
||||
|
||||
public function tableDetail(Table $table)
|
||||
{
|
||||
$table->load(['columns', 'indexes', 'foreignKeys', 'sourceDatabase']);
|
||||
|
||||
return Inertia::render('Schemas/TableDetail', [
|
||||
'table' => $table,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateColumn(Request $request, Column $column)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'target_column_name' => 'nullable|string|max:255',
|
||||
'target_data_type' => 'nullable|string|max:100',
|
||||
'exclude_from_migration' => 'boolean',
|
||||
]);
|
||||
|
||||
$column->update($validated);
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Column settings updated successfully.');
|
||||
}
|
||||
|
||||
public function pendingChanges()
|
||||
{
|
||||
$changes = $this->comparisonService->getPendingChanges();
|
||||
|
||||
return Inertia::render('Schemas/PendingChanges', [
|
||||
'changes' => $changes,
|
||||
]);
|
||||
}
|
||||
|
||||
public function applyChange(int $changeId)
|
||||
{
|
||||
$this->comparisonService->markAsApplied($changeId);
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Schema change marked as applied.');
|
||||
}
|
||||
}
|
||||
112
app/Http/Controllers/SourceDatabaseController.php
Normal file
112
app/Http/Controllers/SourceDatabaseController.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\SourceDatabase;
|
||||
use App\Services\DatabaseConnectionService;
|
||||
use App\Services\SchemaExtractionService;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class SourceDatabaseController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private DatabaseConnectionService $connectionService,
|
||||
private SchemaExtractionService $extractionService
|
||||
) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$databases = SourceDatabase::withCount('tables')->get();
|
||||
|
||||
return Inertia::render('Databases/SourceDatabases', [
|
||||
'databases' => $databases,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Databases/SourceDatabaseForm', [
|
||||
'database' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'host' => 'required|string|max:255',
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'database' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'required|string',
|
||||
'driver' => 'nullable|string|max:50',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['driver'] = $validated['driver'] ?? 'pgsql';
|
||||
|
||||
$database = SourceDatabase::create($validated);
|
||||
|
||||
return redirect()->route('databases.source.index')
|
||||
->with('success', 'Source database created successfully.');
|
||||
}
|
||||
|
||||
public function edit(SourceDatabase $database)
|
||||
{
|
||||
return Inertia::render('Databases/SourceDatabaseForm', [
|
||||
'database' => $database,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, SourceDatabase $database)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'host' => 'required|string|max:255',
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'database' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'nullable|string',
|
||||
'driver' => 'nullable|string|max:50',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if (empty($validated['password'])) {
|
||||
unset($validated['password']);
|
||||
}
|
||||
|
||||
$database->update($validated);
|
||||
|
||||
return redirect()->route('databases.source.index')
|
||||
->with('success', 'Source database updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy(SourceDatabase $database)
|
||||
{
|
||||
$database->delete();
|
||||
|
||||
return redirect()->route('databases.source.index')
|
||||
->with('success', 'Source database deleted successfully.');
|
||||
}
|
||||
|
||||
public function testConnection(SourceDatabase $database)
|
||||
{
|
||||
$result = $this->connectionService->testSourceConnection($database);
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function sync(SourceDatabase $database)
|
||||
{
|
||||
try {
|
||||
$this->extractionService->saveSchema($database);
|
||||
|
||||
return redirect()->back()
|
||||
->with('success', 'Schema synchronized successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return redirect()->back()
|
||||
->with('error', 'Failed to sync schema: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
97
app/Http/Controllers/TargetDatabaseController.php
Normal file
97
app/Http/Controllers/TargetDatabaseController.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\TargetDatabase;
|
||||
use App\Services\DatabaseConnectionService;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class TargetDatabaseController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private DatabaseConnectionService $connectionService
|
||||
) {}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$databases = TargetDatabase::withCount('migrationSchedules')->get();
|
||||
|
||||
return Inertia::render('Databases/TargetDatabases', [
|
||||
'databases' => $databases,
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return Inertia::render('Databases/TargetDatabaseForm', [
|
||||
'database' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'host' => 'required|string|max:255',
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'database' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'required|string',
|
||||
'driver' => 'nullable|string|max:50',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['driver'] = $validated['driver'] ?? 'pgsql';
|
||||
|
||||
$database = TargetDatabase::create($validated);
|
||||
|
||||
return redirect()->route('databases.target.index')
|
||||
->with('success', 'Target database created successfully.');
|
||||
}
|
||||
|
||||
public function edit(TargetDatabase $database)
|
||||
{
|
||||
return Inertia::render('Databases/TargetDatabaseForm', [
|
||||
'database' => $database,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, TargetDatabase $database)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'host' => 'required|string|max:255',
|
||||
'port' => 'required|integer|between:1,65535',
|
||||
'database' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255',
|
||||
'password' => 'nullable|string',
|
||||
'driver' => 'nullable|string|max:50',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if (empty($validated['password'])) {
|
||||
unset($validated['password']);
|
||||
}
|
||||
|
||||
$database->update($validated);
|
||||
|
||||
return redirect()->route('databases.target.index')
|
||||
->with('success', 'Target database updated successfully.');
|
||||
}
|
||||
|
||||
public function destroy(TargetDatabase $database)
|
||||
{
|
||||
$database->delete();
|
||||
|
||||
return redirect()->route('databases.target.index')
|
||||
->with('success', 'Target database deleted successfully.');
|
||||
}
|
||||
|
||||
public function testConnection(TargetDatabase $database)
|
||||
{
|
||||
$result = $this->connectionService->testTargetConnection($database);
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user