refactor(frontend): migrate Svelte stores from .ts to .svelte.ts runes
- Delete legacy .ts stores (auth, activity, assistantChat, datasetReview, environmentContext, health, sidebar, taskDrawer, translationRun) - Create new .svelte.ts runes-based stores using reactive primitives - Migrate i18n: /i18n → /i18n/index.svelte.js - Migrate toasts: /toasts → /toasts.svelte.js - Update all component imports across 180+ files: components, pages, routes, lib - Remove fromStore() wrappers — use store.value directly with Svelte 5 runes - Update test mocks for new import paths - Add @DEPRECATED annotation to legacy alias
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
@RELATION DEPENDS_ON ->[TaskLogViewer]
|
||||
@RELATION DEPENDS_ON ->[PasswordPrompt]
|
||||
@INVARIANT: This component contains NO business logic — it only renders model state and calls model.action().
|
||||
@RATIONALE First page fully migrated to semantic design tokens (surface-*, border-*, text-*, primary, destructive, success, warning, info). All raw buttons replaced with $lib/ui Button. Serves as the canonical visual reference for all ss-tools pages.
|
||||
@UX_STATE: [idle] -> Skeleton loader while environments load.
|
||||
@UX_STATE: [ready] -> Step wizard with env selection, dashboards, options.
|
||||
@UX_STATE: [loading] -> Skeleton/spinner during API calls (dry-run, DB fetch).
|
||||
@@ -34,7 +35,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { fromStore } from "svelte/store";
|
||||
import EnvSelector from "../../components/EnvSelector.svelte";
|
||||
import DashboardGrid from "../../components/DashboardGrid.svelte";
|
||||
import MappingTable from "../../components/MappingTable.svelte";
|
||||
@@ -48,7 +48,7 @@
|
||||
DashboardSelection,
|
||||
MigrationDryRunResult,
|
||||
} from "../../types/dashboard";
|
||||
import { t } from "$lib/i18n";
|
||||
import { t } from "$lib/i18n/index.svelte.js";
|
||||
import { Button, Card, PageHeader } from "$lib/ui";
|
||||
|
||||
// ── Model: single source of truth ───────────────────────────────
|
||||
@@ -56,7 +56,11 @@
|
||||
|
||||
// Store bindings (reactive, read from stores for TaskRunner + PasswordPrompt)
|
||||
const selectedTaskStore = model.selectedTaskStore;
|
||||
const taskSnap = fromStore(selectedTaskStore);
|
||||
let activeTask = $state(null);
|
||||
$effect(() => {
|
||||
const unsub = selectedTaskStore.subscribe(v => { activeTask = v; });
|
||||
return unsub;
|
||||
});
|
||||
|
||||
// ── Lifecycle ──────────────────────────────────────────────────
|
||||
onMount(() => model.loadEnvironments());
|
||||
@@ -82,9 +86,9 @@
|
||||
<!-- #region MigrationHeader -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<PageHeader title={$t.nav.migration} />
|
||||
<button
|
||||
<Button
|
||||
variant="ghost"
|
||||
onclick={() => model.toggleTaskHistory()}
|
||||
class="inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:text-gray-900 transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
||||
@@ -93,15 +97,15 @@
|
||||
<svg class="w-4 h-4 transition-transform" class:rotate-180={model.showTaskHistory} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<!-- #endregion MigrationHeader -->
|
||||
|
||||
{#if taskSnap.current}
|
||||
{#if activeTask}
|
||||
<!-- #region ActiveTaskSection -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-900">
|
||||
<h2 class="text-lg font-semibold text-text">
|
||||
{$t.tasks?.task_label || "Task"}
|
||||
</h2>
|
||||
<Button variant="secondary" onclick={() => selectedTaskStore.set(null)}>
|
||||
@@ -129,6 +133,7 @@
|
||||
{ step: 3, label: $t.tasks?.summary_report || "Review" },
|
||||
{ step: 4, label: $t.migration?.start || "Migrate" },
|
||||
] as s}
|
||||
<!-- Step indicator buttons — custom styling for step wizard (not standard Button) -->
|
||||
<button
|
||||
onclick={() => model.goToStep(s.step)}
|
||||
disabled={s.step > model.currentStep && !((s.step === 2 && model.stepReady[1]) || (s.step === 3 && model.stepReady[1] && model.stepReady[2]) || (s.step === 4 && model.dryRunResult))}
|
||||
@@ -137,12 +142,12 @@
|
||||
<div class={`
|
||||
w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold border-2 transition-all
|
||||
${model.currentStep > s.step
|
||||
? 'bg-indigo-600 border-indigo-600 text-white'
|
||||
? 'bg-primary border-primary text-white'
|
||||
: model.currentStep === s.step
|
||||
? 'bg-indigo-50 border-indigo-500 text-indigo-700'
|
||||
: 'bg-white border-gray-300 text-gray-400'
|
||||
? 'bg-primary-light border-primary-ring text-primary'
|
||||
: 'bg-surface-card border-border-strong text-text-subtle'
|
||||
}
|
||||
${(s.step <= model.currentStep || (s.step === 2 && model.stepReady[1]) || (s.step === 3 && model.stepReady[1] && model.stepReady[2])) ? 'cursor-pointer hover:border-indigo-400' : 'cursor-not-allowed'}
|
||||
${(s.step <= model.currentStep || (s.step === 2 && model.stepReady[1]) || (s.step === 3 && model.stepReady[1] && model.stepReady[2])) ? 'cursor-pointer hover:border-primary-ring' : 'cursor-not-allowed'}
|
||||
`}>
|
||||
{#if model.currentStep > s.step}
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -153,13 +158,13 @@
|
||||
{/if}
|
||||
</div>
|
||||
<span class="mt-2 text-xs font-medium hidden sm:block
|
||||
${model.currentStep === s.step ? 'text-indigo-700' : 'text-gray-500'}
|
||||
${model.currentStep === s.step ? 'text-primary' : 'text-text-muted'}
|
||||
">{s.label}</span>
|
||||
</button>
|
||||
{#if s.step < 4}
|
||||
<div class="flex-1 mx-2 mb-6">
|
||||
<div class="h-0.5 bg-gray-200 rounded">
|
||||
<div class="h-0.5 bg-indigo-500 rounded transition-all duration-500" style="width: {model.currentStep > s.step ? '100%' : '0%'}"></div>
|
||||
<div class="h-0.5 bg-surface-muted rounded">
|
||||
<div class="h-0.5 bg-primary rounded transition-all duration-500" style="width: {model.currentStep > s.step ? '100%' : '0%'}"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -172,31 +177,31 @@
|
||||
{#if model.loading}
|
||||
<div class="space-y-6">
|
||||
<div class="animate-pulse">
|
||||
<div class="h-8 bg-gray-200 rounded w-1/3 mb-4"></div>
|
||||
<div class="h-8 bg-surface-muted rounded w-1/3 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="h-24 bg-gray-100 rounded-lg"></div>
|
||||
<div class="h-24 bg-gray-100 rounded-lg"></div>
|
||||
<div class="h-24 bg-surface-muted rounded-lg"></div>
|
||||
<div class="h-24 bg-surface-muted rounded-lg"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
{:else if model.error}
|
||||
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl">
|
||||
<div class="mb-6 p-4 bg-destructive-light border border-destructive-ring rounded-xl">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0 w-5 h-5 text-red-500 mt-0.5">
|
||||
<div class="flex-shrink-0 w-5 h-5 text-destructive mt-0.5">
|
||||
<svg viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-medium text-red-800">{model.error}</p>
|
||||
<p class="text-sm font-medium text-destructive">{model.error}</p>
|
||||
</div>
|
||||
<button onclick={() => model.clearError()} class="text-red-400 hover:text-red-600" aria-label={$t.common?.close || "Close"}>
|
||||
<Button variant="ghost" size="sm" onclick={() => model.clearError()} aria-label={$t.common?.close || "Close"}>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -206,14 +211,14 @@
|
||||
<Card padding="none" class="mb-6">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 text-sm font-bold">1</span>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{$t.migration?.source_env || "Source Environment"} → {$t.migration?.target_env || "Target Environment"}</h3>
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-primary-light text-primary text-sm font-bold">1</span>
|
||||
<h3 class="text-lg font-semibold text-text">{$t.migration?.source_env || "Source Environment"} → {$t.migration?.target_env || "Target Environment"}</h3>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="p-4 border border-gray-200 rounded-xl bg-white hover:border-indigo-300 transition-colors">
|
||||
<div class="p-4 border border-border rounded-xl bg-surface-card hover:border-primary-ring transition-colors">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-2 h-2 rounded-full bg-emerald-500"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">{$t.migration?.source_env || "Source"}</h3>
|
||||
<div class="w-2 h-2 rounded-full bg-success"></div>
|
||||
<h3 class="text-sm font-semibold text-text uppercase tracking-wide">{$t.migration?.source_env || "Source"}</h3>
|
||||
</div>
|
||||
<EnvSelector
|
||||
label={$t.migration?.source_env || "Source Environment"}
|
||||
@@ -222,10 +227,10 @@
|
||||
onchange={({ id }) => model.selectSourceEnv(id)}
|
||||
/>
|
||||
</div>
|
||||
<div class="p-4 border border-gray-200 rounded-xl bg-white hover:border-indigo-300 transition-colors">
|
||||
<div class="p-4 border border-border rounded-xl bg-surface-card hover:border-primary-ring transition-colors">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-2 h-2 rounded-full bg-violet-500"></div>
|
||||
<h3 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">{$t.migration?.target_env || "Target"}</h3>
|
||||
<div class="w-2 h-2 rounded-full bg-primary"></div>
|
||||
<h3 class="text-sm font-semibold text-text uppercase tracking-wide">{$t.migration?.target_env || "Target"}</h3>
|
||||
</div>
|
||||
<EnvSelector
|
||||
label={$t.migration?.target_env || "Target Environment"}
|
||||
@@ -236,8 +241,8 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if model.sourceEnvId && model.targetEnvId && model.sourceEnvId === model.targetEnvId}
|
||||
<div class="mt-4 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
||||
<p class="text-sm text-amber-700 flex items-center gap-2">
|
||||
<div class="mt-4 p-3 bg-warning-light border border-warning-ring rounded-lg">
|
||||
<p class="text-sm text-warning flex items-center gap-2">
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
||||
</svg>
|
||||
@@ -260,8 +265,8 @@
|
||||
<Card padding="none" class="mb-6">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 text-sm font-bold">2</span>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{$t.migration?.select_dashboards_title || "Select Dashboards"}</h3>
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-primary-light text-primary text-sm font-bold">2</span>
|
||||
<h3 class="text-lg font-semibold text-text">{$t.migration?.select_dashboards_title || "Select Dashboards"}</h3>
|
||||
</div>
|
||||
|
||||
{#if model.sourceEnvId}
|
||||
@@ -273,38 +278,38 @@
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-gray-500 italic">
|
||||
<p class="text-text-muted italic">
|
||||
{$t.dashboard?.select_source || "Select a source environment to view dashboards."}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<!-- Migration Options -->
|
||||
<div class="border-t border-gray-100 pt-4 mt-2">
|
||||
<h4 class="text-sm font-semibold text-gray-700 mb-3">{$t.dashboard?.settings || "Options"}</h4>
|
||||
<div class="border-t border-border pt-4 mt-2">
|
||||
<h4 class="text-sm font-semibold text-text mb-3">{$t.dashboard?.settings || "Options"}</h4>
|
||||
<div class="space-y-3">
|
||||
<label class="flex items-start gap-3 p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<label class="flex items-start gap-3 p-3 border border-border rounded-lg hover:bg-surface-muted cursor-pointer transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={model.fixCrossFilters}
|
||||
onchange={(e) => model.setFixCrossFilters(e.currentTarget.checked)}
|
||||
class="mt-0.5 h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
||||
class="mt-0.5 h-4 w-4 text-primary focus:ring-primary border-border-strong rounded"
|
||||
/>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-900">{$t.migration?.fix_cross_filters || "Fix Cross-Filters"}</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">{$t.dashboard?.fix_cross_filters_hint || "Automatically remap chart and dataset IDs in cross-filters to target environment IDs."}</p>
|
||||
<span class="text-sm font-medium text-text">{$t.migration?.fix_cross_filters || "Fix Cross-Filters"}</span>
|
||||
<p class="text-xs text-text-muted mt-0.5">{$t.dashboard?.fix_cross_filters_hint || "Automatically remap chart and dataset IDs in cross-filters to target environment IDs."}</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-start gap-3 p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer transition-colors">
|
||||
<label class="flex items-start gap-3 p-3 border border-border rounded-lg hover:bg-surface-muted cursor-pointer transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={model.replaceDb}
|
||||
onchange={(e) => model.setReplaceDb(e.currentTarget.checked)}
|
||||
class="mt-0.5 h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
|
||||
class="mt-0.5 h-4 w-4 text-primary focus:ring-primary border-border-strong rounded"
|
||||
/>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-900">{$t.migration?.replace_db || "Replace Database"}</span>
|
||||
<p class="text-xs text-gray-500 mt-0.5">{$t.dashboard?.mapping_disabled_hint || "Apply database mappings during migration."}</p>
|
||||
<span class="text-sm font-medium text-text">{$t.migration?.replace_db || "Replace Database"}</span>
|
||||
<p class="text-xs text-text-muted mt-0.5">{$t.dashboard?.mapping_disabled_hint || "Apply database mappings during migration."}</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -312,22 +317,23 @@
|
||||
|
||||
<!-- Database Mapping Section -->
|
||||
{#if model.replaceDb}
|
||||
<div class="mt-4 p-4 bg-gray-50 border border-gray-200 rounded-xl">
|
||||
<div class="mt-4 p-4 bg-surface-muted border border-border rounded-xl">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h4 class="text-sm font-semibold text-gray-700">{$t.migration?.database_mappings || "Database Mappings"}</h4>
|
||||
<button
|
||||
<h4 class="text-sm font-semibold text-text">{$t.migration?.database_mappings || "Database Mappings"}</h4>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onclick={() => model.fetchDatabases()}
|
||||
disabled={model.fetchingDbs || !model.sourceEnvId || !model.targetEnvId}
|
||||
class="text-xs font-medium text-indigo-600 hover:text-indigo-800 disabled:text-gray-400 disabled:cursor-not-allowed flex items-center gap-1"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" class:animate-spin={model.fetchingDbs} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
{$t.migration?.fetch_dbs || "Fetch Databases"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
{#if model.fetchingDbs}
|
||||
<div class="flex items-center gap-2 text-sm text-gray-500">
|
||||
<div class="flex items-center gap-2 text-sm text-text-muted">
|
||||
<svg class="w-4 h-4 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
@@ -342,7 +348,7 @@
|
||||
onupdate={(e) => model.saveMapping(e.detail.sourceUuid, e.detail.targetUuid)}
|
||||
/>
|
||||
{:else if model.sourceEnvId && model.targetEnvId}
|
||||
<p class="text-sm text-gray-500">{$t.migration?.mapping_hint || "Select environments and click \"Fetch Databases\" to start mapping."}</p>
|
||||
<p class="text-sm text-text-muted">{$t.migration?.mapping_hint || "Select environments and click \"Fetch Databases\" to start mapping."}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -374,22 +380,22 @@
|
||||
<Card padding="none" class="mb-6">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-indigo-100 text-indigo-700 text-sm font-bold">3</span>
|
||||
<h3 class="text-lg font-semibold text-gray-900">{$t.tasks?.summary_report || "Review & Validate"}</h3>
|
||||
<span class="flex items-center justify-center w-8 h-8 rounded-full bg-primary-light text-primary text-sm font-bold">3</span>
|
||||
<h3 class="text-lg font-semibold text-text">{$t.tasks?.summary_report || "Review & Validate"}</h3>
|
||||
</div>
|
||||
|
||||
{#if model.dryRunResult}
|
||||
<!-- Selection Summary -->
|
||||
<div class="mb-6 p-4 bg-indigo-50 border border-indigo-100 rounded-xl">
|
||||
<div class="mb-6 p-4 bg-info-light border border-info-ring rounded-xl">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-info" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<h3 class="text-sm font-semibold text-indigo-800">{$t.dashboard?.selected_dashboards || "Selected Dashboards"}</h3>
|
||||
<h3 class="text-sm font-semibold text-info-hover">{$t.dashboard?.selected_dashboards || "Selected Dashboards"}</h3>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each model.dryRunResult.selected_dashboard_titles || [] as title}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-700">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-info-light text-info">
|
||||
{title}
|
||||
</span>
|
||||
{/each}
|
||||
@@ -399,110 +405,110 @@
|
||||
<!-- Diff Summary Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<!-- Dashboards -->
|
||||
<div class="p-4 border border-gray-200 rounded-xl bg-white">
|
||||
<div class="p-4 border border-border rounded-xl bg-surface-card">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="w-8 h-8 rounded-lg bg-info-light flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-info" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="text-sm font-semibold text-gray-700">{$t.dashboard?.title || "Dashboards"}</h4>
|
||||
<h4 class="text-sm font-semibold text-text">{$t.dashboard?.title || "Dashboards"}</h4>
|
||||
</div>
|
||||
<div class="space-y-1.5 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-emerald-600 font-medium">Create</span>
|
||||
<span class="font-semibold text-emerald-600">{model.dryRunResult.summary.dashboards.create}</span>
|
||||
<span class="text-success font-medium">Create</span>
|
||||
<span class="font-semibold text-success">{model.dryRunResult.summary.dashboards.create}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-amber-600 font-medium">Update</span>
|
||||
<span class="font-semibold text-amber-600">{model.dryRunResult.summary.dashboards.update}</span>
|
||||
<span class="text-warning font-medium">Update</span>
|
||||
<span class="font-semibold text-warning">{model.dryRunResult.summary.dashboards.update}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-red-600 font-medium">Delete</span>
|
||||
<span class="font-semibold text-red-600">{model.dryRunResult.summary.dashboards.delete}</span>
|
||||
<span class="text-destructive font-medium">Delete</span>
|
||||
<span class="font-semibold text-destructive">{model.dryRunResult.summary.dashboards.delete}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts -->
|
||||
<div class="p-4 border border-gray-200 rounded-xl bg-white">
|
||||
<div class="p-4 border border-border rounded-xl bg-surface-card">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-violet-100 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-violet-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="w-8 h-8 rounded-lg bg-primary-light flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="text-sm font-semibold text-gray-700">{$t.dashboard?.charts || "Charts"}</h4>
|
||||
<h4 class="text-sm font-semibold text-text">{$t.dashboard?.charts || "Charts"}</h4>
|
||||
</div>
|
||||
<div class="space-y-1.5 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-emerald-600 font-medium">Create</span>
|
||||
<span class="font-semibold text-emerald-600">{model.dryRunResult.summary.charts.create}</span>
|
||||
<span class="text-success font-medium">Create</span>
|
||||
<span class="font-semibold text-success">{model.dryRunResult.summary.charts.create}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-amber-600 font-medium">Update</span>
|
||||
<span class="font-semibold text-amber-600">{model.dryRunResult.summary.charts.update}</span>
|
||||
<span class="text-warning font-medium">Update</span>
|
||||
<span class="font-semibold text-warning">{model.dryRunResult.summary.charts.update}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-red-600 font-medium">Delete</span>
|
||||
<span class="font-semibold text-red-600">{model.dryRunResult.summary.charts.delete}</span>
|
||||
<span class="text-destructive font-medium">Delete</span>
|
||||
<span class="font-semibold text-destructive">{model.dryRunResult.summary.charts.delete}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Datasets -->
|
||||
<div class="p-4 border border-gray-200 rounded-xl bg-white">
|
||||
<div class="p-4 border border-border rounded-xl bg-surface-card">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-emerald-100 flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-emerald-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<div class="w-8 h-8 rounded-lg bg-success-light flex items-center justify-center">
|
||||
<svg class="w-4 h-4 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="text-sm font-semibold text-gray-700">{$t.dashboard?.linked_resources || "Datasets"}</h4>
|
||||
<h4 class="text-sm font-semibold text-text">{$t.dashboard?.linked_resources || "Datasets"}</h4>
|
||||
</div>
|
||||
<div class="space-y-1.5 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-emerald-600 font-medium">Create</span>
|
||||
<span class="font-semibold text-emerald-600">{model.dryRunResult.summary.datasets.create}</span>
|
||||
<span class="text-success font-medium">Create</span>
|
||||
<span class="font-semibold text-success">{model.dryRunResult.summary.datasets.create}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-amber-600 font-medium">Update</span>
|
||||
<span class="font-semibold text-amber-600">{model.dryRunResult.summary.datasets.update}</span>
|
||||
<span class="text-warning font-medium">Update</span>
|
||||
<span class="font-semibold text-warning">{model.dryRunResult.summary.datasets.update}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-red-600 font-medium">Delete</span>
|
||||
<span class="font-semibold text-red-600">{model.dryRunResult.summary.datasets.delete}</span>
|
||||
<span class="text-destructive font-medium">Delete</span>
|
||||
<span class="font-semibold text-destructive">{model.dryRunResult.summary.datasets.delete}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Risk Assessment -->
|
||||
<div class="mb-6 p-4 border border-gray-200 rounded-xl bg-white">
|
||||
<div class="mb-6 p-4 border border-border rounded-xl bg-surface-card">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01" />
|
||||
</svg>
|
||||
<h3 class="text-sm font-semibold text-gray-700">{$t.tasks?.issues || "Risk Assessment"}</h3>
|
||||
<h3 class="text-sm font-semibold text-text">{$t.tasks?.issues || "Risk Assessment"}</h3>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-500">Score:</span>
|
||||
<span class="text-xs text-text-muted">Score:</span>
|
||||
<div class={`
|
||||
px-2.5 py-1 rounded-full text-xs font-bold
|
||||
${model.dryRunResult.risk.score <= 20 ? 'bg-emerald-100 text-emerald-700' :
|
||||
model.dryRunResult.risk.score <= 50 ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'}
|
||||
${model.dryRunResult.risk.score <= 20 ? 'bg-success-light text-success' :
|
||||
model.dryRunResult.risk.score <= 50 ? 'bg-warning-light text-warning' :
|
||||
'bg-destructive-light text-destructive'}
|
||||
`}>
|
||||
{model.dryRunResult.risk.score}/100
|
||||
</div>
|
||||
</div>
|
||||
<div class={`
|
||||
px-2.5 py-1 rounded-full text-xs font-bold
|
||||
${model.dryRunResult.risk.level === 'low' ? 'bg-emerald-100 text-emerald-700' :
|
||||
model.dryRunResult.risk.level === 'medium' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'}
|
||||
${model.dryRunResult.risk.level === 'low' ? 'bg-success-light text-success' :
|
||||
model.dryRunResult.risk.level === 'medium' ? 'bg-warning-light text-warning' :
|
||||
'bg-destructive-light text-destructive'}
|
||||
`}>
|
||||
{model.dryRunResult.risk.level.toUpperCase()}
|
||||
</div>
|
||||
@@ -510,12 +516,12 @@
|
||||
</div>
|
||||
|
||||
<!-- Risk Progress Bar -->
|
||||
<div class="w-full h-2 bg-gray-100 rounded-full mb-4 overflow-hidden">
|
||||
<div class="w-full h-2 bg-surface-muted rounded-full mb-4 overflow-hidden">
|
||||
<div
|
||||
class="h-full rounded-full transition-all duration-700"
|
||||
class:bg-emerald-500={model.dryRunResult.risk.score <= 20}
|
||||
class:bg-amber-500={model.dryRunResult.risk.score > 20 && model.dryRunResult.risk.score <= 50}
|
||||
class:bg-red-500={model.dryRunResult.risk.score > 50}
|
||||
class:bg-success={model.dryRunResult.risk.score <= 20}
|
||||
class:bg-warning={model.dryRunResult.risk.score > 20 && model.dryRunResult.risk.score <= 50}
|
||||
class:bg-destructive={model.dryRunResult.risk.score > 50}
|
||||
style="width: {model.dryRunResult.risk.score}%"
|
||||
></div>
|
||||
</div>
|
||||
@@ -525,38 +531,38 @@
|
||||
<div class="space-y-2">
|
||||
{#each model.dryRunResult.risk.items as item}
|
||||
<div class="flex items-start gap-3 p-2.5 rounded-lg text-sm
|
||||
${item.severity === 'high' ? 'bg-red-50' :
|
||||
item.severity === 'medium' ? 'bg-amber-50' : 'bg-blue-50'}
|
||||
${item.severity === 'high' ? 'bg-destructive-light' :
|
||||
item.severity === 'medium' ? 'bg-warning-light' : 'bg-info-light'}
|
||||
">
|
||||
<div class={`
|
||||
flex-shrink-0 w-2 h-2 rounded-full mt-1.5
|
||||
${item.severity === 'high' ? 'bg-red-500' :
|
||||
item.severity === 'medium' ? 'bg-amber-500' : 'bg-blue-500'}
|
||||
${item.severity === 'high' ? 'bg-destructive' :
|
||||
item.severity === 'medium' ? 'bg-warning' : 'bg-info'}
|
||||
`}></div>
|
||||
<div>
|
||||
<p class="font-medium text-gray-800">{item.message}</p>
|
||||
<p class="text-xs text-gray-500 mt-0.5">{item.object_type}: {item.object_uuid}</p>
|
||||
<p class="font-medium text-text">{item.message}</p>
|
||||
<p class="text-xs text-text-muted mt-0.5">{item.object_type}: {item.object_uuid}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-sm text-gray-500">No issues detected.</p>
|
||||
<p class="text-sm text-text-muted">No issues detected.</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Raw JSON Details -->
|
||||
<details class="border border-gray-200 rounded-xl overflow-hidden">
|
||||
<summary class="px-4 py-3 bg-gray-50 cursor-pointer text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors flex items-center gap-2">
|
||||
<details class="border border-border rounded-xl overflow-hidden">
|
||||
<summary class="px-4 py-3 bg-surface-muted cursor-pointer text-sm font-medium text-text hover:bg-surface-muted transition-colors flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
{$t.tasks?.diff_preview || "Diff JSON"}
|
||||
</summary>
|
||||
<pre class="p-4 max-h-72 overflow-auto text-xs bg-gray-50 text-gray-700">{JSON.stringify(model.dryRunResult, null, 2)}</pre>
|
||||
<pre class="p-4 max-h-72 overflow-auto text-xs bg-surface-muted text-text">{JSON.stringify(model.dryRunResult, null, 2)}</pre>
|
||||
</details>
|
||||
{:else}
|
||||
<div class="flex flex-col items-center justify-center py-8 text-gray-400">
|
||||
<div class="flex flex-col items-center justify-center py-8 text-text-subtle">
|
||||
<svg class="w-12 h-12 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
@@ -564,7 +570,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mt-6 flex items-center justify-between border-t border-gray-100 pt-6">
|
||||
<div class="mt-6 flex items-center justify-between border-t border-border pt-6">
|
||||
<Button variant="secondary" onclick={() => model.goToStep(2)}>
|
||||
← {$t.common.back}
|
||||
</Button>
|
||||
@@ -584,14 +590,14 @@
|
||||
<!-- #endregion Step3: Review & Dry-Run Results -->
|
||||
|
||||
<!-- #region Step4: Migration in Progress (handled by ActiveTaskSection above) -->
|
||||
{#if model.currentStep === 4 && !taskSnap.current}
|
||||
{#if model.currentStep === 4 && !activeTask}
|
||||
<Card padding="none" class="mb-6">
|
||||
<div class="flex flex-col items-center justify-center p-12">
|
||||
<svg class="w-16 h-16 text-indigo-400 mb-4 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-16 h-16 text-info mb-4 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-semibold text-gray-700 mb-2">{$t.migration?.starting || "Starting Migration..."}</h3>
|
||||
<p class="text-sm text-gray-500">{$t.common.loading || "Please wait..."}</p>
|
||||
<h3 class="text-lg font-semibold text-text mb-2">{$t.migration?.starting || "Starting Migration..."}</h3>
|
||||
<p class="text-sm text-text-muted">{$t.common.loading || "Please wait..."}</p>
|
||||
<Button variant="secondary" class="mt-6" onclick={() => model.goToStep(3)}>
|
||||
← {$t.common.back}
|
||||
</Button>
|
||||
@@ -603,14 +609,14 @@
|
||||
<!-- Quick Actions when no step is active -->
|
||||
{#if !model.currentStep || model.currentStep === 0}
|
||||
<Card class="mb-6">
|
||||
<div class="flex flex-col items-center justify-center py-12 text-gray-400">
|
||||
<div class="flex flex-col items-center justify-center py-12 text-text-subtle">
|
||||
<svg class="w-16 h-16 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
||||
</svg>
|
||||
<p class="text-sm">{$t.dashboard?.migrate_modal_title || "Select environments to start"}</p>
|
||||
<button onclick={() => model.goToStep(1)} class="mt-4 text-indigo-600 hover:text-indigo-800 text-sm font-medium">
|
||||
<Button variant="ghost" size="sm" class="mt-4" onclick={() => model.goToStep(1)}>
|
||||
{$t.migration?.source_env || "Get Started"} →
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
@@ -634,4 +640,4 @@
|
||||
/>
|
||||
<!-- #endregion MigrationModals -->
|
||||
|
||||
<!-- #endregion MigrationPage -->
|
||||
<!-- #endregion MigrationPage -->
|
||||
Reference in New Issue
Block a user