181 lines
5.1 KiB
Svelte
181 lines
5.1 KiB
Svelte
<!-- [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 { api } from '../../../lib/api.js';
|
|
import EnvSelector from '../../../components/EnvSelector.svelte';
|
|
import MappingTable from '../../../components/MappingTable.svelte';
|
|
import { t } from '$lib/i18n';
|
|
import { Button, PageHeader } from '$lib/ui';
|
|
// [/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]
|
|
|
|
// [DEF:fetchEnvironments:Function]
|
|
// @PURPOSE: Fetches the list of environments.
|
|
// @PRE: None.
|
|
// @POST: environments array is populated.
|
|
async function fetchEnvironments() {
|
|
try {
|
|
environments = await api.getEnvironmentsList();
|
|
} catch (e) {
|
|
error = e.message;
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
// [/DEF:fetchEnvironments:Function]
|
|
|
|
onMount(fetchEnvironments);
|
|
|
|
// [DEF:fetchDatabases:Function]
|
|
/**
|
|
* @purpose Fetches databases from both environments and gets suggestions.
|
|
* @pre sourceEnvId and targetEnvId must be set.
|
|
* @post sourceDatabases, targetDatabases, mappings, and suggestions are updated.
|
|
*/
|
|
async function fetchDatabases() {
|
|
if (!sourceEnvId || !targetEnvId) return;
|
|
fetchingDbs = true;
|
|
error = "";
|
|
success = "";
|
|
|
|
try {
|
|
const [src, tgt, maps, sugs] = await Promise.all([
|
|
api.requestApi(`/environments/${sourceEnvId}/databases`),
|
|
api.requestApi(`/environments/${targetEnvId}/databases`),
|
|
api.requestApi(`/mappings?source_env_id=${sourceEnvId}&target_env_id=${targetEnvId}`),
|
|
api.postApi(`/mappings/suggest`, { source_env_id: sourceEnvId, target_env_id: targetEnvId })
|
|
]);
|
|
|
|
sourceDatabases = src;
|
|
targetDatabases = tgt;
|
|
mappings = maps;
|
|
suggestions = sugs;
|
|
} catch (e) {
|
|
error = e.message;
|
|
} finally {
|
|
fetchingDbs = false;
|
|
}
|
|
}
|
|
// [/DEF:fetchDatabases:Function]
|
|
|
|
// [DEF:handleUpdate:Function]
|
|
/**
|
|
* @purpose Saves a mapping to the backend.
|
|
* @pre event.detail contains sourceUuid and targetUuid.
|
|
* @post Mapping is saved and local mappings list is updated.
|
|
*/
|
|
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 savedMapping = await api.postApi('/mappings', {
|
|
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
|
|
});
|
|
|
|
mappings = [...mappings.filter(m => m.source_db_uuid !== sourceUuid), savedMapping];
|
|
success = "Mapping saved successfully";
|
|
} catch (e) {
|
|
error = e.message;
|
|
}
|
|
}
|
|
// [/DEF:handleUpdate:Function]
|
|
</script>
|
|
|
|
<!-- [SECTION: TEMPLATE] -->
|
|
<div class="max-w-6xl mx-auto p-6">
|
|
<PageHeader title="Database Mapping Management" />
|
|
|
|
{#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}
|
|
isLoading={fetchingDbs}
|
|
>
|
|
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:Component] -->
|