183
frontend/src/routes/migration/mappings/+page.svelte
Normal file
183
frontend/src/routes/migration/mappings/+page.svelte
Normal file
@@ -0,0 +1,183 @@
|
||||
<!-- [DEF:MappingManagement:Component] -->
|
||||
<!--
|
||||
@SEMANTICS: mapping, management, database, fuzzy-matching
|
||||
@PURPOSE: Page for managing database mappings between environments.
|
||||
@LAYER: Page
|
||||
@RELATION: USES -> EnvSelector
|
||||
@RELATION: USES -> MappingTable
|
||||
|
||||
@INVARIANT: Mappings are saved to the backend for persistence.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
// [SECTION: IMPORTS]
|
||||
import { onMount } from 'svelte';
|
||||
import EnvSelector from '../../../components/EnvSelector.svelte';
|
||||
import MappingTable from '../../../components/MappingTable.svelte';
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: STATE]
|
||||
let environments = [];
|
||||
let sourceEnvId = "";
|
||||
let targetEnvId = "";
|
||||
let sourceDatabases = [];
|
||||
let targetDatabases = [];
|
||||
let mappings = [];
|
||||
let suggestions = [];
|
||||
let loading = true;
|
||||
let fetchingDbs = false;
|
||||
let error = "";
|
||||
let success = "";
|
||||
// [/SECTION]
|
||||
|
||||
async function fetchEnvironments() {
|
||||
try {
|
||||
const response = await fetch('/api/environments');
|
||||
if (!response.ok) throw new Error('Failed to fetch environments');
|
||||
environments = await response.json();
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(fetchEnvironments);
|
||||
|
||||
// [DEF:fetchDatabases:Function]
|
||||
/**
|
||||
* @purpose Fetches databases from both environments and gets suggestions.
|
||||
*/
|
||||
async function fetchDatabases() {
|
||||
if (!sourceEnvId || !targetEnvId) return;
|
||||
fetchingDbs = true;
|
||||
error = "";
|
||||
success = "";
|
||||
|
||||
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:handleUpdate:Function]
|
||||
/**
|
||||
* @purpose Saves a mapping to the backend.
|
||||
*/
|
||||
async function handleUpdate(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];
|
||||
success = "Mapping saved successfully";
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
// [/DEF:handleUpdate]
|
||||
</script>
|
||||
|
||||
<!-- [SECTION: TEMPLATE] -->
|
||||
<div class="max-w-6xl mx-auto p-6">
|
||||
<h1 class="text-2xl font-bold mb-6">Database Mapping Management</h1>
|
||||
|
||||
{#if loading}
|
||||
<p>Loading environments...</p>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||
<EnvSelector
|
||||
label="Source Environment"
|
||||
bind:selectedId={sourceEnvId}
|
||||
{environments}
|
||||
on:change={() => { sourceDatabases = []; mappings = []; suggestions = []; }}
|
||||
/>
|
||||
<EnvSelector
|
||||
label="Target Environment"
|
||||
bind:selectedId={targetEnvId}
|
||||
{environments}
|
||||
on:change={() => { targetDatabases = []; mappings = []; suggestions = []; }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-8">
|
||||
<button
|
||||
on:click={fetchDatabases}
|
||||
disabled={!sourceEnvId || !targetEnvId || sourceEnvId === targetEnvId || fetchingDbs}
|
||||
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"
|
||||
>
|
||||
{fetchingDbs ? 'Fetching...' : 'Fetch Databases & Suggestions'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if success}
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||
{success}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if sourceDatabases.length > 0}
|
||||
<MappingTable
|
||||
{sourceDatabases}
|
||||
{targetDatabases}
|
||||
{mappings}
|
||||
{suggestions}
|
||||
on:update={handleUpdate}
|
||||
/>
|
||||
{:else if !fetchingDbs && sourceEnvId && targetEnvId}
|
||||
<p class="text-gray-500 italic">Select environments and click "Fetch Databases" to start mapping.</p>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<!-- [/SECTION] -->
|
||||
|
||||
<style>
|
||||
/* Page specific styles */
|
||||
</style>
|
||||
|
||||
<!-- [/DEF:MappingManagement] -->
|
||||
Reference in New Issue
Block a user