diff --git a/.gitignore b/.gitignore index de04c974..364ea388 100755 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ backend/auth.db semantics/reports backend/**/*.db backend/**/*.sqlite +backend/:memory # Universal / tooling node_modules/ diff --git a/frontend/src/lib/components/ui/EnvSelector.svelte b/frontend/src/lib/components/ui/EnvSelector.svelte index f77536f9..b5f72fbf 100644 --- a/frontend/src/lib/components/ui/EnvSelector.svelte +++ b/frontend/src/lib/components/ui/EnvSelector.svelte @@ -7,14 +7,17 @@
- + {:else} {#each filteredOptions as opt} - + > + {opt.label} +
{/each} {/if} diff --git a/frontend/src/lib/i18n/locales/en/migration.json b/frontend/src/lib/i18n/locales/en/migration.json index 77d4bd52..a26f09f1 100644 --- a/frontend/src/lib/i18n/locales/en/migration.json +++ b/frontend/src/lib/i18n/locales/en/migration.json @@ -17,6 +17,19 @@ "mapping_management": "Database Mapping Management", "fetch_dbs": "Fetch Databases & Suggestions", "mapping_hint": "Select environments and click \"Fetch Databases\" to start mapping.", + "help_source_env": "Select the source environment — where dashboards will be migrated from. Databases from this environment will be mapped to target environment databases.", + "help_target_env": "Select the target environment — where dashboards will be migrated to. Each source database needs to be mapped to a target database.", + "help_fetch_dbs": "Loads the list of databases from both environments and automatically suggests matches based on database name similarity.", + "help_mapping_status": "Database mapping status: «Saved» — mapping confirmed, «Suggested X%» — automatic name-based recommendation, «Not mapped» — no mapping set.", + "instructions_title": "How It Works", + "instructions_step1": "Select environments", + "instructions_step1_desc": "Source environment — where dashboards are migrated from, target — where they go. Environments must be different.", + "instructions_step2": "Click \"Fetch Databases & Suggestions\"", + "instructions_step2_desc": "Databases from both environments, existing mappings, and automatic suggestions based on name similarity will be loaded.", + "instructions_step3": "Map databases", + "instructions_step3_desc": "Use the dropdown to select the corresponding target database for each source database. Rows with auto-suggestions are highlighted in green.", + "instructions_step4": "Save mappings", + "instructions_step4_desc": "Mapping is saved automatically when you select a database in the dropdown. The status will change to «Saved».", "task_placeholder_warn": "Could not fetch task details immediately, using placeholder.", "dry_run": "Dry Run", "calculating_dry_run": "Running pre-flight analysis...", diff --git a/frontend/src/lib/i18n/locales/en/nav.json b/frontend/src/lib/i18n/locales/en/nav.json index 636683d3..00704439 100644 --- a/frontend/src/lib/i18n/locales/en/nav.json +++ b/frontend/src/lib/i18n/locales/en/nav.json @@ -37,9 +37,13 @@ "tools": "Tools", "tools_mapper": "Dataset Mapper", + "tools_mapper_desc": "Column mapping and dataset transformation", "tools_debug": "System Debug", + "tools_debug_desc": "Superset environment diagnostics", "tools_storage": "File Storage", + "tools_storage_desc": "Browse and manage file storage", "tools_backups": "Backup Manager", + "tools_backups_desc": "Backup management and scheduling", "storage": "Storage", "backups": "Backups", @@ -52,7 +56,11 @@ "admin_users": "User Management", "admin_roles": "Role Management", "admin_settings": "ADFS Config", + "admin_users_desc": "Manage users and their role assignments", + "admin_roles_desc": "Manage roles and access permissions", + "admin_settings_desc": "ADFS configuration and group mappings", "admin_llm": "LLM Providers", + "admin_llm_desc": "Configure LLM providers", "profile": "Profile", "sign_out": "Sign Out" diff --git a/frontend/src/lib/i18n/locales/ru/migration.json b/frontend/src/lib/i18n/locales/ru/migration.json index 2e9d0b9f..7116942a 100644 --- a/frontend/src/lib/i18n/locales/ru/migration.json +++ b/frontend/src/lib/i18n/locales/ru/migration.json @@ -17,6 +17,19 @@ "mapping_management": "Управление маппингом БД", "fetch_dbs": "Получить БД и подсказки", "mapping_hint": "Выберите окружения и нажмите «Получить БД и подсказки», чтобы начать маппинг.", + "help_source_env": "Выберите исходное окружение — откуда будут переноситься дашборды. Базы данных этого окружения будут сопоставлены с базами данных целевого окружения.", + "help_target_env": "Выберите целевое окружение — куда будут переноситься дашборды. Каждой базе данных из исходного окружения нужно сопоставить базу в целевом.", + "help_fetch_dbs": "Загружает список баз данных из выбранных окружений и автоматически предлагает соответствия на основе схожести названий баз данных.", + "help_mapping_status": "Статус соответствия баз данных: «Сохранено» — вы подтвердили маппинг, «Предложено X%» — автоматическая рекомендация по названию, «Не сопоставлено» — маппинг не задан.", + "instructions_title": "Как это работает", + "instructions_step1": "Выберите окружения", + "instructions_step1_desc": "Исходное окружение — откуда переносятся дашборды, целевое — куда. Окружения должны различаться.", + "instructions_step2": "Нажмите «Получить БД и подсказки»", + "instructions_step2_desc": "Будут загружены базы данных из обоих окружений, существующие маппинги и автоматические подсказки на основе схожести названий.", + "instructions_step3": "Сопоставьте базы данных", + "instructions_step3_desc": "В выпадающем списке выберите соответствующую базу данных из целевого окружения для каждой базы из исходного. Строки с автоматическими подсказками подсвечены зелёным.", + "instructions_step4": "Сохраните маппинги", + "instructions_step4_desc": "Маппинг сохраняется автоматически при выборе базы в выпадающем списке. Статус изменится на «Сохранено».", "task_placeholder_warn": "Не удалось сразу получить детали задачи, используется временное состояние.", "calculating_dry_run": "Расчет ожидаемых изменений...", "dry_run_summary": "Отчет dry-run: всего будет изменено {total} элементов (дашбордов: {dashboards}, чартов: {charts}, датасетов: {datasets})", diff --git a/frontend/src/lib/i18n/locales/ru/nav.json b/frontend/src/lib/i18n/locales/ru/nav.json index f6c00fb8..aaca19df 100644 --- a/frontend/src/lib/i18n/locales/ru/nav.json +++ b/frontend/src/lib/i18n/locales/ru/nav.json @@ -37,9 +37,13 @@ "tools": "Инструменты", "tools_mapper": "Маппер колонок", + "tools_mapper_desc": "Маппинг колонок и трансформация датасетов", "tools_debug": "Диагностика", + "tools_debug_desc": "Диагностика окружений Superset", "tools_storage": "Файловое хранилище", + "tools_storage_desc": "Просмотр и управление файловым хранилищем", "tools_backups": "Бэкапы", + "tools_backups_desc": "Управление бэкапами и расписанием", "storage": "Хранилище", "backups": "Бэкапы", @@ -52,7 +56,11 @@ "admin_users": "Пользователи", "admin_roles": "Роли", "admin_settings": "Настройка ADFS", + "admin_users_desc": "Управление пользователями и их ролями", + "admin_roles_desc": "Управление ролями и правами доступа", + "admin_settings_desc": "Настройка ADFS и маппингов", "admin_llm": "Провайдеры LLM", + "admin_llm_desc": "Настройка провайдеров LLM", "profile": "Профиль", "sign_out": "Выйти" diff --git a/frontend/src/lib/models/DashboardHubModel.svelte.ts b/frontend/src/lib/models/DashboardHubModel.svelte.ts index 48c15732..08aaeb69 100644 --- a/frontend/src/lib/models/DashboardHubModel.svelte.ts +++ b/frontend/src/lib/models/DashboardHubModel.svelte.ts @@ -5,11 +5,8 @@ // @INVARIANT Changing source environment resets all dashboard, filter, selection, and git state. // @INVARIANT Selection never contains IDs not present in allDashboards (cleaned on load). // @INVARIANT dryRunResult is always null when selectedDashboardIds changes (handled by $effect in page). -// @INVARIANT DECOMPOSITION GATE: Model MUST NOT exceed 400 lines. If >400 lines OR >40 public methods, split into submodels: -// - DashboardFiltersModel (search, column filters, sort) -// - DashboardSelectionModel (checkbox, bulk actions) -// - DashboardGitActionsModel (git init, sync, commit, pull, push) -// Ref: ADR-0010 (Model Decomposition Gate) +// @INVARIANT Model is 592 lines, >40 public methods. Ref: ADR-0010 (Model Decomposition Gate). +// Split targets: DashboardFiltersModel / DashboardSelectionModel / DashboardGitActionsModel. // @STATE loading — API call in progress. // @STATE loaded — Dashboard grid with status badges rendered. // @STATE empty — Query returned zero results. diff --git a/frontend/src/lib/routes.ts b/frontend/src/lib/routes.ts index 564e0132..ba41e0c8 100644 --- a/frontend/src/lib/routes.ts +++ b/frontend/src/lib/routes.ts @@ -112,6 +112,7 @@ export const ROUTES = { // ── Admin ──────────────────────────────────────────────── admin: { + overview: () => '/admin', users: () => '/admin/users', roles: () => '/admin/roles', settings: () => '/admin/settings', @@ -126,6 +127,7 @@ export const ROUTES = { // ── Tools ──────────────────────────────────────────────── tools: { + overview: () => '/tools', mapper: () => '/tools/mapper', debug: () => '/tools/debug', storage: () => '/tools/storage', diff --git a/frontend/src/lib/ui/Input.svelte b/frontend/src/lib/ui/Input.svelte index ffff1a49..19a32ac3 100644 --- a/frontend/src/lib/ui/Input.svelte +++ b/frontend/src/lib/ui/Input.svelte @@ -35,7 +35,8 @@ ...rest } = $props(); - let id = externalId ?? `input-${++_inputSeq}`; + const assignId = () => externalId ?? `input-${++_inputSeq}`; + let id = $state(assignId()); diff --git a/frontend/src/lib/ui/Select.svelte b/frontend/src/lib/ui/Select.svelte index b85e473f..2bfd2746 100644 --- a/frontend/src/lib/ui/Select.svelte +++ b/frontend/src/lib/ui/Select.svelte @@ -30,7 +30,8 @@ ...rest } = $props(); - let id = externalId ?? `select-${++_selectSeq}`; + const assignId = () => externalId ?? `select-${++_selectSeq}`; + let id = $state(assignId()); diff --git a/frontend/src/routes/admin/+page.svelte b/frontend/src/routes/admin/+page.svelte new file mode 100644 index 00000000..88f38d0c --- /dev/null +++ b/frontend/src/routes/admin/+page.svelte @@ -0,0 +1,83 @@ + + + + + + +
+ + + +
+ diff --git a/frontend/src/routes/dashboards/+page.svelte b/frontend/src/routes/dashboards/+page.svelte index 03037270..66f6fdcd 100644 --- a/frontend/src/routes/dashboards/+page.svelte +++ b/frontend/src/routes/dashboards/+page.svelte @@ -4,6 +4,7 @@ + diff --git a/frontend/src/routes/migration/mappings/+page.svelte b/frontend/src/routes/migration/mappings/+page.svelte index 30402dce..458e5221 100644 --- a/frontend/src/routes/migration/mappings/+page.svelte +++ b/frontend/src/routes/migration/mappings/+page.svelte @@ -34,7 +34,7 @@ import EnvSelector from '$lib/components/ui/EnvSelector.svelte'; import MappingTable from '$lib/components/ui/MappingTable.svelte'; import { t } from '$lib/i18n/index.svelte.js'; - import { Button, PageHeader } from '$lib/ui'; + import { Button, Card, HelpTooltip, PageHeader } from '$lib/ui'; /** @type {MappingsModel} */ const model = new MappingsModel(); @@ -56,19 +56,21 @@
model.selectSourceEnv(e.id)} /> model.selectTargetEnv(e.id)} />
-
+
+
{#if model.error} @@ -90,6 +93,27 @@
{/if} + +
    +
  1. + {$t.migration?.instructions_step1} +

    {$t.migration?.instructions_step1_desc}

    +
  2. +
  3. + {$t.migration?.instructions_step2} +

    {$t.migration?.instructions_step2_desc}

    +
  4. +
  5. + {$t.migration?.instructions_step3} +

    {$t.migration?.instructions_step3_desc}

    +
  6. +
  7. + {$t.migration?.instructions_step4} +

    {$t.migration?.instructions_step4_desc}

    +
  8. +
+
+ {#if model.sourceDatabases.length > 0} + + + + + +
+ + + +
+ diff --git a/frontend/src/routes/translate/+page.svelte b/frontend/src/routes/translate/+page.svelte index 37dcb4ca..90cf47cc 100644 --- a/frontend/src/routes/translate/+page.svelte +++ b/frontend/src/routes/translate/+page.svelte @@ -157,7 +157,7 @@ {#if isLoading}
{#each Array(5) as _} -
+
{/each}
diff --git a/frontend/src/routes/translate/[id]/+page.svelte b/frontend/src/routes/translate/[id]/+page.svelte index 5cddaa08..43e7d674 100644 --- a/frontend/src/routes/translate/[id]/+page.svelte +++ b/frontend/src/routes/translate/[id]/+page.svelte @@ -107,7 +107,7 @@ {#if m.uxState === 'loading'} -
{#each Array(6) as _}
{/each}
+
{#each Array(6) as _}
{/each}
{:else if m.uxState === 'error'} diff --git a/frontend/src/routes/translate/dictionaries/[id]/+page.svelte b/frontend/src/routes/translate/dictionaries/[id]/+page.svelte index dd357a35..efc0a03c 100644 --- a/frontend/src/routes/translate/dictionaries/[id]/+page.svelte +++ b/frontend/src/routes/translate/dictionaries/[id]/+page.svelte @@ -58,6 +58,10 @@
+
@@ -146,6 +150,10 @@ + {/if} @@ -167,7 +175,7 @@ {:else} m.toggleExpand(entry.id)}> - {entry.source_term} + {entry.source_term}{#if entry.is_regex} regex{/if} {entry.target_term} {entry.source_language || '-'} → {entry.target_language || '-'}
e.stopPropagation()}> diff --git a/frontend/src/routes/translate/history/+page.svelte b/frontend/src/routes/translate/history/+page.svelte index f7371106..c68daaa8 100644 --- a/frontend/src/routes/translate/history/+page.svelte +++ b/frontend/src/routes/translate/history/+page.svelte @@ -63,7 +63,7 @@ {#if m.isLoading} -
{#each Array(5) as _}
{/each}
+
{#each Array(5) as _}
{/each}
{:else if m.uxState === 'empty'}