mappings+migrate
This commit is contained in:
@@ -12,16 +12,26 @@
|
||||
// [SECTION: IMPORTS]
|
||||
import { onMount } from 'svelte';
|
||||
import EnvSelector from '../../components/EnvSelector.svelte';
|
||||
import DashboardGrid from '../../components/DashboardGrid.svelte';
|
||||
import MappingTable from '../../components/MappingTable.svelte';
|
||||
import MissingMappingModal from '../../components/MissingMappingModal.svelte';
|
||||
import type { DashboardMetadata, DashboardSelection } from '../../types/dashboard';
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let environments = [];
|
||||
let environments: any[] = [];
|
||||
let sourceEnvId = "";
|
||||
let targetEnvId = "";
|
||||
let dashboardRegex = ".*";
|
||||
let replaceDb = false;
|
||||
let loading = true;
|
||||
let error = "";
|
||||
let dashboards: DashboardMetadata[] = [];
|
||||
let selectedDashboardIds: number[] = [];
|
||||
let sourceDatabases: any[] = [];
|
||||
let targetDatabases: any[] = [];
|
||||
let mappings: any[] = [];
|
||||
let suggestions: any[] = [];
|
||||
let fetchingDbs = false;
|
||||
// [/SECTION]
|
||||
|
||||
// [DEF:fetchEnvironments:Function]
|
||||
@@ -42,8 +52,100 @@
|
||||
}
|
||||
// [/DEF:fetchEnvironments]
|
||||
|
||||
// [DEF:fetchDashboards:Function]
|
||||
/**
|
||||
* @purpose Fetches dashboards for the selected source environment.
|
||||
* @param envId The environment ID.
|
||||
* @post dashboards state is updated.
|
||||
*/
|
||||
async function fetchDashboards(envId: string) {
|
||||
try {
|
||||
const response = await fetch(`/api/environments/${envId}/dashboards`);
|
||||
if (!response.ok) throw new Error('Failed to fetch dashboards');
|
||||
dashboards = await response.json();
|
||||
selectedDashboardIds = []; // Reset selection when env changes
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
dashboards = [];
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchDashboards]
|
||||
|
||||
onMount(fetchEnvironments);
|
||||
|
||||
// Reactive: fetch dashboards when source env changes
|
||||
$: if (sourceEnvId) fetchDashboards(sourceEnvId);
|
||||
|
||||
// [DEF:fetchDatabases:Function]
|
||||
/**
|
||||
* @purpose Fetches databases from both environments and gets suggestions.
|
||||
*/
|
||||
async function fetchDatabases() {
|
||||
if (!sourceEnvId || !targetEnvId) return;
|
||||
fetchingDbs = true;
|
||||
error = "";
|
||||
|
||||
try {
|
||||
const [srcRes, tgtRes, mapRes, sugRes] = await Promise.all([
|
||||
fetch(`/api/environments/${sourceEnvId}/databases`),
|
||||
fetch(`/api/environments/${targetEnvId}/databases`),
|
||||
fetch(`/api/mappings?source_env_id=${sourceEnvId}&target_env_id=${targetEnvId}`),
|
||||
fetch(`/api/mappings/suggest`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ source_env_id: sourceEnvId, target_env_id: targetEnvId })
|
||||
})
|
||||
]);
|
||||
|
||||
if (!srcRes.ok || !tgtRes.ok) throw new Error('Failed to fetch databases from environments');
|
||||
|
||||
sourceDatabases = await srcRes.json();
|
||||
targetDatabases = await tgtRes.json();
|
||||
mappings = await mapRes.json();
|
||||
suggestions = await sugRes.json();
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
} finally {
|
||||
fetchingDbs = false;
|
||||
}
|
||||
}
|
||||
// [/DEF:fetchDatabases]
|
||||
|
||||
// [DEF:handleMappingUpdate:Function]
|
||||
/**
|
||||
* @purpose Saves a mapping to the backend.
|
||||
*/
|
||||
async function handleMappingUpdate(event: CustomEvent) {
|
||||
const { sourceUuid, targetUuid } = event.detail;
|
||||
const sDb = sourceDatabases.find(d => d.uuid === sourceUuid);
|
||||
const tDb = targetDatabases.find(d => d.uuid === targetUuid);
|
||||
|
||||
if (!sDb || !tDb) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/mappings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
source_env_id: sourceEnvId,
|
||||
target_env_id: targetEnvId,
|
||||
source_db_uuid: sourceUuid,
|
||||
target_db_uuid: targetUuid,
|
||||
source_db_name: sDb.database_name,
|
||||
target_db_name: tDb.database_name
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to save mapping');
|
||||
|
||||
const savedMapping = await response.json();
|
||||
mappings = [...mappings.filter(m => m.source_db_uuid !== sourceUuid), savedMapping];
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
// [/DEF:handleMappingUpdate]
|
||||
|
||||
// [DEF:startMigration:Function]
|
||||
/**
|
||||
* @purpose Starts the migration process.
|
||||
@@ -58,10 +160,33 @@
|
||||
error = "Source and target environments must be different.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedDashboardIds.length === 0) {
|
||||
error = "Please select at least one dashboard to migrate.";
|
||||
return;
|
||||
}
|
||||
|
||||
error = "";
|
||||
console.log(`[MigrationDashboard][Action] Starting migration from ${sourceEnvId} to ${targetEnvId} (Replace DB: ${replaceDb})`);
|
||||
// TODO: Implement actual migration trigger in US3
|
||||
try {
|
||||
const selection: DashboardSelection = {
|
||||
selected_ids: selectedDashboardIds,
|
||||
source_env_id: sourceEnvId,
|
||||
target_env_id: targetEnvId
|
||||
};
|
||||
console.log(`[MigrationDashboard][Action] Starting migration with selection:`, selection);
|
||||
const response = await fetch('/api/migration/execute', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(selection)
|
||||
});
|
||||
console.log(`[MigrationDashboard][Action] API response status: ${response.status}`);
|
||||
if (!response.ok) throw new Error(`Failed to start migration: ${response.status} ${response.statusText}`);
|
||||
const result = await response.json();
|
||||
console.log(`[MigrationDashboard][Action] Migration started: ${result.task_id} - ${result.message}`);
|
||||
// TODO: Show success message or redirect to task status
|
||||
} catch (e) {
|
||||
console.error(`[MigrationDashboard][Failure] Migration failed:`, e);
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
// [/DEF:startMigration]
|
||||
</script>
|
||||
@@ -91,23 +216,28 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- [DEF:DashboardSelectionSection] -->
|
||||
<div class="mb-8">
|
||||
<label for="dashboard-regex" class="block text-sm font-medium text-gray-700 mb-1">Dashboard Regex</label>
|
||||
<input
|
||||
id="dashboard-regex"
|
||||
type="text"
|
||||
bind:value={dashboardRegex}
|
||||
placeholder="e.g. ^Finance Dashboard$"
|
||||
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
<p class="mt-1 text-sm text-gray-500">Regular expression to filter dashboards to migrate.</p>
|
||||
<h2 class="text-lg font-medium mb-4">Select Dashboards</h2>
|
||||
|
||||
{#if sourceEnvId}
|
||||
<DashboardGrid
|
||||
{dashboards}
|
||||
bind:selectedIds={selectedDashboardIds}
|
||||
/>
|
||||
{:else}
|
||||
<p class="text-gray-500 italic">Select a source environment to view dashboards.</p>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- [/DEF:DashboardSelectionSection] -->
|
||||
|
||||
<div class="flex items-center mb-8">
|
||||
|
||||
<div class="flex items-center mb-4">
|
||||
<input
|
||||
id="replace-db"
|
||||
type="checkbox"
|
||||
bind:checked={replaceDb}
|
||||
on:change={() => { if (replaceDb && sourceDatabases.length === 0) fetchDatabases(); }}
|
||||
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
||||
/>
|
||||
<label for="replace-db" class="ml-2 block text-sm text-gray-900">
|
||||
@@ -115,9 +245,33 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if replaceDb}
|
||||
<div class="mb-8 p-4 border rounded-md bg-gray-50">
|
||||
<h3 class="text-md font-medium mb-4">Database Mappings</h3>
|
||||
{#if fetchingDbs}
|
||||
<p>Loading databases and suggestions...</p>
|
||||
{:else if sourceDatabases.length > 0}
|
||||
<MappingTable
|
||||
{sourceDatabases}
|
||||
{targetDatabases}
|
||||
{mappings}
|
||||
{suggestions}
|
||||
on:update={handleMappingUpdate}
|
||||
/>
|
||||
{:else if sourceEnvId && targetEnvId}
|
||||
<button
|
||||
on:click={fetchDatabases}
|
||||
class="text-indigo-600 hover:text-indigo-500 text-sm font-medium"
|
||||
>
|
||||
Refresh Databases & Suggestions
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
on:click={startMigration}
|
||||
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId}
|
||||
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId || selectedDashboardIds.length === 0}
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:bg-gray-400"
|
||||
>
|
||||
Start Migration
|
||||
|
||||
Reference in New Issue
Block a user