i18 cleanup
This commit is contained in:
@@ -159,7 +159,7 @@
|
||||
// Update selection state
|
||||
updateSelectionState();
|
||||
} catch (err) {
|
||||
error = err.message || "Failed to load dashboards";
|
||||
error = err.message || $t.dashboard?.load_failed ;
|
||||
console.error("[DashboardHub][Coherence:Failed]", err);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
@@ -318,7 +318,11 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[DashboardHub][Coherence:Failed] Validation failed:", err);
|
||||
alert("Failed to start validation: " + (err.message || "Unknown error"));
|
||||
alert(
|
||||
($t.dashboard?.validation_start_failed ) +
|
||||
": " +
|
||||
(err.message || $t.dashboard?.unknown_error ),
|
||||
);
|
||||
} finally {
|
||||
validatingIds.delete(dashboard.id);
|
||||
validatingIds = new Set(validatingIds);
|
||||
@@ -399,7 +403,9 @@
|
||||
if (isSubmittingMigrate) return;
|
||||
if (selectedIds.size === 0) return;
|
||||
if (!targetEnvId) {
|
||||
alert("Please select a target environment");
|
||||
alert(
|
||||
$t.dashboard?.target_env_required ,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -432,7 +438,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[DashboardHub][Coherence:Failed]", err);
|
||||
alert("Failed to create migration task");
|
||||
alert($t.dashboard?.migration_task_failed );
|
||||
} finally {
|
||||
isSubmittingMigrate = false;
|
||||
}
|
||||
@@ -469,7 +475,7 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[DashboardHub][Coherence:Failed]", err);
|
||||
alert("Failed to create backup task");
|
||||
alert($t.dashboard?.backup_task_failed );
|
||||
} finally {
|
||||
isSubmittingBackup = false;
|
||||
}
|
||||
@@ -568,7 +574,7 @@
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold text-gray-900">
|
||||
{$t.nav?.dashboard || "Dashboards"}
|
||||
{$t.nav?.dashboards }
|
||||
</h1>
|
||||
<div class="flex items-center space-x-4">
|
||||
<select
|
||||
@@ -584,7 +590,7 @@
|
||||
class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"
|
||||
on:click={loadDashboards}
|
||||
>
|
||||
{$t.common?.refresh || "Refresh"}
|
||||
{$t.common?.refresh }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -599,7 +605,7 @@
|
||||
class="px-4 py-2 bg-destructive text-white rounded hover:bg-destructive-hover transition-colors"
|
||||
on:click={loadDashboards}
|
||||
>
|
||||
{$t.common?.retry || "Retry"}
|
||||
{$t.common?.retry }
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -641,7 +647,7 @@
|
||||
>
|
||||
<path d="M3 3h18v18H3V3zm16 16V5H5v14h14z" />
|
||||
</svg>
|
||||
<p>{$t.dashboards?.empty || "No dashboards found"}</p>
|
||||
<p>{$t.dashboard?.empty }</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Toolbar -->
|
||||
@@ -652,18 +658,24 @@
|
||||
on:click={handleSelectAll}
|
||||
disabled={total === 0}
|
||||
>
|
||||
{isAllSelected ? "Deselect All" : "Select All"}
|
||||
{isAllSelected
|
||||
? $t.dashboard?.deselect_all
|
||||
: $t.dashboard?.select_all }
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
|
||||
on:click={handleSelectVisible}
|
||||
disabled={dashboards.length === 0}
|
||||
>
|
||||
{isAllVisibleSelected ? "Deselect Visible" : "Select Visible"}
|
||||
{isAllVisibleSelected
|
||||
? $t.dashboard?.deselect_visible
|
||||
: $t.dashboard?.select_visible }
|
||||
</button>
|
||||
{#if selectedIds.size > 0}
|
||||
<span class="text-sm text-gray-600">
|
||||
{selectedIds.size} selected
|
||||
{(
|
||||
$t.dashboard?.selected_count
|
||||
).replace("{count}", String(selectedIds.size))}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -671,7 +683,7 @@
|
||||
<input
|
||||
type="text"
|
||||
class="px-4 py-2 border border-gray-300 rounded-lg bg-white focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Search dashboards..."
|
||||
placeholder={$t.dashboard?.search }
|
||||
on:input={handleSearch}
|
||||
value={searchQuery}
|
||||
/>
|
||||
@@ -686,14 +698,14 @@
|
||||
>
|
||||
<div class="col-span-1"></div>
|
||||
<div class="col-span-3 font-medium text-gray-900">
|
||||
{$t.dashboards?.title || "Title"}
|
||||
{$t.dashboard?.title }
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{$t.dashboards?.git_status || "Git Status"}
|
||||
{$t.dashboard?.git_status }
|
||||
</div>
|
||||
<div class="col-span-3">{$t.dashboards?.last_task || "Last Task"}</div>
|
||||
<div class="col-span-3">{$t.dashboard?.last_task }</div>
|
||||
<div class="col-span-3 flex items-center">
|
||||
{$t.dashboards?.actions || "Actions"}
|
||||
{$t.dashboard?.actions }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -716,7 +728,7 @@
|
||||
<button
|
||||
class="text-left text-blue-700 hover:text-blue-900 hover:underline transition-colors"
|
||||
on:click={() => navigateToDashboardDetail(dashboard.id)}
|
||||
title="Open dashboard overview"
|
||||
title={$t.dashboard?.open_overview }
|
||||
>
|
||||
{dashboard.title}
|
||||
</button>
|
||||
@@ -729,9 +741,9 @@
|
||||
class="status-badge {getStatusBadgeClass(dashboard.gitStatus)}"
|
||||
>
|
||||
{#if dashboard.gitStatus.toLowerCase() === "ok"}
|
||||
✓ {$t.dashboards?.status_synced || "Synced"}
|
||||
✓ {$t.dashboard?.status_synced }
|
||||
{:else if dashboard.gitStatus.toLowerCase() === "diff"}
|
||||
! {$t.dashboards?.status_diff || "Diff"}
|
||||
! {$t.dashboard?.status_diff }
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
@@ -750,18 +762,18 @@
|
||||
handleTaskStatusClick(dashboard)}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label={$t.dashboards?.view_task || "View task"}
|
||||
aria-label={$t.dashboard?.view_task }
|
||||
>
|
||||
{@html getTaskStatusIcon(dashboard.lastTask.status)}
|
||||
<span class="ml-1">
|
||||
{#if dashboard.lastTask.status.toLowerCase() === "running"}
|
||||
{$t.dashboards?.task_running || "Running..."}
|
||||
{$t.dashboard?.task_running }
|
||||
{:else if dashboard.lastTask.status.toLowerCase() === "success"}
|
||||
{$t.dashboards?.task_done || "Done"}
|
||||
{$t.dashboard?.task_done }
|
||||
{:else if dashboard.lastTask.status.toLowerCase() === "error"}
|
||||
{$t.dashboards?.task_failed || "Failed"}
|
||||
{$t.dashboard?.task_failed }
|
||||
{:else if dashboard.lastTask.status.toLowerCase() === "waiting_input"}
|
||||
{$t.dashboards?.task_waiting || "Waiting"}
|
||||
{$t.dashboard?.task_waiting }
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
@@ -776,7 +788,7 @@
|
||||
<button
|
||||
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleAction(dashboard, "migrate")}
|
||||
title={$t.dashboards?.action_migrate || "Migrate"}
|
||||
title={$t.dashboard?.action_migrate }
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
@@ -793,7 +805,7 @@
|
||||
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleValidate(dashboard)}
|
||||
disabled={validatingIds.has(dashboard.id)}
|
||||
title={$t.dashboards?.action_validate || "Validate"}
|
||||
title={$t.dashboard?.action_validate }
|
||||
>
|
||||
{#if validatingIds.has(dashboard.id)}
|
||||
<svg
|
||||
@@ -830,7 +842,7 @@
|
||||
<button
|
||||
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={() => handleAction(dashboard, "backup")}
|
||||
title={$t.dashboards?.action_backup || "Backup"}
|
||||
title={$t.dashboard?.action_backup }
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
@@ -857,10 +869,10 @@
|
||||
class="flex items-center justify-between px-4 py-3 bg-gray-50 border-t border-gray-200"
|
||||
>
|
||||
<div class="text-sm text-gray-600">
|
||||
Showing {(currentPage - 1) * pageSize + 1}-{Math.min(
|
||||
currentPage * pageSize,
|
||||
total,
|
||||
)} of {total}
|
||||
{($t.dashboard?.showing )
|
||||
.replace("{start}", String((currentPage - 1) * pageSize + 1))
|
||||
.replace("{end}", String(Math.min(currentPage * pageSize, total)))
|
||||
.replace("{total}", String(total))}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
@@ -868,14 +880,14 @@
|
||||
on:click={() => handlePageChange(1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
First
|
||||
{$t.common?.first }
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
|
||||
on:click={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
Previous
|
||||
{$t.dashboard?.previous }
|
||||
</button>
|
||||
{#each getPaginationRange(currentPage, totalPages) as pageNum}
|
||||
{#if pageNum === "..."}
|
||||
@@ -894,14 +906,14 @@
|
||||
on:click={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Next
|
||||
{$t.dashboard?.next }
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed min-w-[32px]"
|
||||
on:click={() => handlePageChange(totalPages)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
Last
|
||||
{$t.common?.last }
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@@ -910,11 +922,36 @@
|
||||
value={pageSize}
|
||||
on:change={handlePageSizeChange}
|
||||
>
|
||||
<option value={5}>5 per page</option>
|
||||
<option value={10}>10 per page</option>
|
||||
<option value={25}>25 per page</option>
|
||||
<option value={50}>50 per page</option>
|
||||
<option value={100}>100 per page</option>
|
||||
<option value={5}>
|
||||
{($t.dashboard?.per_page_option ).replace(
|
||||
"{count}",
|
||||
"5",
|
||||
)}
|
||||
</option>
|
||||
<option value={10}>
|
||||
{($t.dashboard?.per_page_option ).replace(
|
||||
"{count}",
|
||||
"10",
|
||||
)}
|
||||
</option>
|
||||
<option value={25}>
|
||||
{($t.dashboard?.per_page_option ).replace(
|
||||
"{count}",
|
||||
"25",
|
||||
)}
|
||||
</option>
|
||||
<option value={50}>
|
||||
{($t.dashboard?.per_page_option ).replace(
|
||||
"{count}",
|
||||
"50",
|
||||
)}
|
||||
</option>
|
||||
<option value={100}>
|
||||
{($t.dashboard?.per_page_option ).replace(
|
||||
"{count}",
|
||||
"100",
|
||||
)}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -928,7 +965,9 @@
|
||||
<div class="flex items-center justify-between max-w-7xl mx-auto">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-medium">
|
||||
✓ {selectedIds.size} selected
|
||||
✓ {(
|
||||
$t.dashboard?.selected_count
|
||||
).replace("{count}", String(selectedIds.size))}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
@@ -944,19 +983,19 @@
|
||||
isEditingMappings = false;
|
||||
}}
|
||||
>
|
||||
Migrate
|
||||
{$t.dashboard?.bulk_migrate }
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors"
|
||||
on:click={() => (showBackupModal = true)}
|
||||
>
|
||||
Backup
|
||||
{$t.dashboard?.bulk_backup }
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
|
||||
on:click={() => selectedIds.clear()}
|
||||
>
|
||||
Cancel
|
||||
{$t.common?.cancel }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -985,12 +1024,14 @@
|
||||
class="px-6 py-4 border-b border-gray-200 flex items-center justify-between relative"
|
||||
>
|
||||
<h2 id="migrate-modal-title" class="text-xl font-bold">
|
||||
Migrate {selectedIds.size} Dashboards
|
||||
{(
|
||||
$t.dashboard?.migrate_modal_title
|
||||
).replace("{count}", String(selectedIds.size))}
|
||||
</h2>
|
||||
<button
|
||||
on:click={() => (showMigrateModal = false)}
|
||||
class="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-all"
|
||||
aria-label="Close modal"
|
||||
aria-label={$t.common?.close_modal }
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -1015,14 +1056,14 @@
|
||||
<label
|
||||
for="source-env"
|
||||
class="block text-sm font-medium text-gray-500 mb-1"
|
||||
>Source Environment</label
|
||||
>{$t.migration?.source_env }</label
|
||||
>
|
||||
<div
|
||||
id="source-env"
|
||||
class="px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-gray-700 font-medium"
|
||||
>
|
||||
{environments.find((e) => e.id === selectedEnv)?.name ||
|
||||
selectedEnv} (read-only)
|
||||
selectedEnv} {$t.dashboard?.read_only }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1031,7 +1072,7 @@
|
||||
<label
|
||||
for="target-env"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>Target Environment</label
|
||||
>{$t.migration?.target_env }</label
|
||||
>
|
||||
<select
|
||||
id="target-env"
|
||||
@@ -1039,7 +1080,10 @@
|
||||
bind:value={targetEnvId}
|
||||
on:change={handleTargetEnvChange}
|
||||
>
|
||||
<option value="">Select target environment...</option>
|
||||
<option value="">
|
||||
{$t.dashboard?.target_env_placeholder ||
|
||||
"Select target environment..."}
|
||||
</option>
|
||||
{#each environments.filter((e) => e.id !== selectedEnv) as env}
|
||||
<option value={env.id}>{env.name}</option>
|
||||
{/each}
|
||||
@@ -1052,7 +1096,7 @@
|
||||
<label
|
||||
for="use-db-mappings"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>Database Mappings</label
|
||||
>{$t.migration?.database_mappings }</label
|
||||
>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
@@ -1065,7 +1109,9 @@
|
||||
class="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"
|
||||
></div>
|
||||
<span class="ml-2 text-xs text-gray-500"
|
||||
>{useDbMappings ? "On" : "Off"}</span
|
||||
>{useDbMappings
|
||||
? $t.common?.on
|
||||
: $t.common?.off }</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
@@ -1077,7 +1123,9 @@
|
||||
class="text-sm text-blue-600 hover:underline"
|
||||
on:click={() => (isEditingMappings = !isEditingMappings)}
|
||||
>
|
||||
{isEditingMappings ? "View Summary" : "Edit Mappings"}
|
||||
{isEditingMappings
|
||||
? $t.dashboard?.view_summary
|
||||
: $t.dashboard?.edit_mappings }
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1105,13 +1153,13 @@
|
||||
<thead class="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th class="px-4 py-2 font-semibold text-gray-700"
|
||||
>Source Database</th
|
||||
>{$t.dashboard?.source_database }</th
|
||||
>
|
||||
<th class="px-4 py-2 font-semibold text-gray-700"
|
||||
>Target Database</th
|
||||
>{$t.dashboard?.target_database }</th
|
||||
>
|
||||
<th class="px-4 py-2 font-semibold text-gray-700"
|
||||
>Match %</th
|
||||
>{$t.dashboard?.match_percent }</th
|
||||
>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1130,7 +1178,9 @@
|
||||
dbMappings[mapping.source_db_uuid],
|
||||
)?.database_name || mapping.target_db}
|
||||
{:else}
|
||||
<span class="text-red-500">Not mapped</span>
|
||||
<span class="text-red-500"
|
||||
>{$t.dashboard?.not_mapped }</span
|
||||
>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
@@ -1152,8 +1202,10 @@
|
||||
class="px-4 py-4 text-center text-gray-500 italic"
|
||||
>
|
||||
{targetEnvId
|
||||
? "No databases found to map"
|
||||
: "Select target environment to see mappings"}
|
||||
? $t.dashboard?.no_databases_to_map ||
|
||||
"No databases found to map"
|
||||
: $t.dashboard?.select_target_for_mappings ||
|
||||
"Select target environment to see mappings"}
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
@@ -1163,8 +1215,8 @@
|
||||
{/if}
|
||||
{:else}
|
||||
<p class="text-xs text-gray-400 italic">
|
||||
Database mapping is disabled. Dashboards will be imported with
|
||||
original database references.
|
||||
{$t.dashboard?.mapping_disabled_hint ||
|
||||
"Database mapping is disabled. Dashboards will be imported with original database references."}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1179,12 +1231,12 @@
|
||||
/>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-900"
|
||||
>Исправить связи кросс-фильтрации</span
|
||||
>{$t.dashboard?.fix_cross_filters_title ||
|
||||
"Fix cross-filter bindings"}</span
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-0.5">
|
||||
Автоматически перепривязать ID чартов и датасетов в
|
||||
кросс-фильтрах к ID целевого окружения. Рекомендуется при
|
||||
миграции дашбордов с кросс-фильтрами.
|
||||
{$t.dashboard?.fix_cross_filters_hint ||
|
||||
"Automatically remap chart and dataset IDs in cross-filters to target environment IDs. Recommended when migrating dashboards with cross-filters."}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
@@ -1193,7 +1245,7 @@
|
||||
<!-- Selected Dashboards List -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>Selected Dashboards</label
|
||||
>{$t.dashboard?.selected_dashboards }</label
|
||||
>
|
||||
<div
|
||||
class="max-h-40 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 p-2"
|
||||
@@ -1216,7 +1268,8 @@
|
||||
<button
|
||||
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
|
||||
on:click={() => (showMigrateModal = false)}
|
||||
disabled={isSubmittingMigrate}>Cancel</button
|
||||
disabled={isSubmittingMigrate}
|
||||
>{$t.common?.cancel }</button
|
||||
>
|
||||
<button
|
||||
class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
@@ -1225,7 +1278,9 @@
|
||||
selectedIds.size === 0 ||
|
||||
isSubmittingMigrate}
|
||||
>
|
||||
{isSubmittingMigrate ? "Starting..." : "Start Migration"}
|
||||
{isSubmittingMigrate
|
||||
? $t.dashboard?.starting
|
||||
: $t.migration?.start }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1253,12 +1308,14 @@
|
||||
class="px-6 py-4 border-b border-gray-200 flex items-center justify-between relative"
|
||||
>
|
||||
<h2 id="backup-modal-title" class="text-xl font-bold">
|
||||
Backup {selectedIds.size} Dashboards
|
||||
{(
|
||||
$t.dashboard?.backup_modal_title
|
||||
).replace("{count}", String(selectedIds.size))}
|
||||
</h2>
|
||||
<button
|
||||
on:click={() => (showBackupModal = false)}
|
||||
class="absolute top-4 right-4 p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-all"
|
||||
aria-label="Close modal"
|
||||
aria-label={$t.common?.close_modal }
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -1283,21 +1340,21 @@
|
||||
<label
|
||||
for="backup-env"
|
||||
class="block text-sm font-medium text-gray-500 mb-1"
|
||||
>Environment</label
|
||||
>{$t.dashboard?.environment }</label
|
||||
>
|
||||
<div
|
||||
id="backup-env"
|
||||
class="px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-gray-700 font-medium"
|
||||
>
|
||||
{environments.find((e) => e.id === selectedEnv)?.name ||
|
||||
selectedEnv} (read-only)
|
||||
selectedEnv} {$t.dashboard?.read_only }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Dashboards List -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>Selected Dashboards</label
|
||||
>{$t.dashboard?.selected_dashboards }</label
|
||||
>
|
||||
<div
|
||||
class="max-h-40 overflow-y-auto border border-gray-200 rounded-lg bg-gray-50 p-2"
|
||||
@@ -1320,7 +1377,7 @@
|
||||
<label
|
||||
id="backup-schedule-label"
|
||||
class="block text-sm font-medium text-gray-700 mb-2"
|
||||
>Schedule</label
|
||||
>{$t.dashboard?.schedule }</label
|
||||
>
|
||||
<div class="space-y-3" aria-labelledby="backup-schedule-label">
|
||||
<label class="flex items-center">
|
||||
@@ -1331,7 +1388,9 @@
|
||||
bind:group={backupSchedule}
|
||||
class="mr-2"
|
||||
/>
|
||||
<span class="text-sm">One-time backup</span>
|
||||
<span class="text-sm"
|
||||
>{$t.dashboard?.one_time_backup }</span
|
||||
>
|
||||
</label>
|
||||
<label class="flex items-center">
|
||||
<input
|
||||
@@ -1341,24 +1400,27 @@
|
||||
bind:group={backupSchedule}
|
||||
class="mr-2"
|
||||
/>
|
||||
<span class="text-sm">Schedule backup</span>
|
||||
<span class="text-sm"
|
||||
>{$t.dashboard?.schedule_backup }</span
|
||||
>
|
||||
</label>
|
||||
{#if backupSchedule !== ""}
|
||||
<div class="ml-6">
|
||||
<label
|
||||
for="cron-expression"
|
||||
class="block text-xs text-gray-500 mb-1"
|
||||
>Cron expression</label
|
||||
>{$t.dashboard?.cron_expression }</label
|
||||
>
|
||||
<input
|
||||
id="cron-expression"
|
||||
type="text"
|
||||
class="search-input w-full text-sm"
|
||||
placeholder="0 2 * * * (daily at 2 AM)"
|
||||
placeholder={$t.dashboard?.cron_placeholder ||
|
||||
"0 2 * * * (daily at 2 AM)"}
|
||||
bind:value={backupSchedule}
|
||||
/>
|
||||
<button class="text-xs text-blue-600 hover:underline mt-1"
|
||||
>Help with cron syntax</button
|
||||
>{$t.dashboard?.cron_help }</button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1370,14 +1432,17 @@
|
||||
<button
|
||||
class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-100 transition-colors"
|
||||
on:click={() => (showBackupModal = false)}
|
||||
disabled={isSubmittingBackup}>Cancel</button
|
||||
disabled={isSubmittingBackup}
|
||||
>{$t.common?.cancel }</button
|
||||
>
|
||||
<button
|
||||
class="px-3 py-1 text-sm bg-primary text-white border-primary rounded hover:bg-primary-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
on:click={handleBulkBackup}
|
||||
disabled={selectedIds.size === 0 || isSubmittingBackup}
|
||||
>
|
||||
{isSubmittingBackup ? "Starting..." : "Start Backup"}
|
||||
{isSubmittingBackup
|
||||
? $t.dashboard?.starting
|
||||
: $t.dashboard?.start_backup }
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
async function loadDashboardDetail() {
|
||||
if (!dashboardId || !envId) {
|
||||
error = "Missing dashboard ID or environment ID";
|
||||
error = $t.dashboard?.missing_context ;
|
||||
isLoading = false;
|
||||
return;
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
try {
|
||||
dashboard = await api.getDashboardDetail(envId, dashboardId);
|
||||
} catch (err) {
|
||||
error = err.message || "Failed to load dashboard details";
|
||||
error = err.message || $t.dashboard?.load_detail_failed ;
|
||||
console.error("[DashboardDetail][Coherence:Failed]", err);
|
||||
} finally {
|
||||
isLoading = false;
|
||||
@@ -69,20 +69,20 @@
|
||||
on:click={goBack}
|
||||
>
|
||||
<Icon name="chevronLeft" size={16} />
|
||||
{$t.common?.back || "Back to Dashboards"}
|
||||
{$t.common?.back }
|
||||
</button>
|
||||
<h1 class="mt-2 text-2xl font-bold text-slate-900">
|
||||
{dashboard?.title || "Dashboard Overview"}
|
||||
{dashboard?.title || ($t.dashboard?.overview )}
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
ID: {dashboardId}{#if dashboard?.slug} • {dashboard.slug}{/if}
|
||||
{$t.common?.id }: {dashboardId}{#if dashboard?.slug} • {dashboard.slug}{/if}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"
|
||||
on:click={loadDashboardDetail}
|
||||
>
|
||||
{$t.common?.refresh || "Refresh"}
|
||||
{$t.common?.refresh }
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<div class="flex items-center justify-between rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-red-700">
|
||||
<span>{error}</span>
|
||||
<button class="rounded bg-red-600 px-3 py-1.5 text-white hover:bg-red-700" on:click={loadDashboardDetail}>
|
||||
{$t.common?.retry || "Retry"}
|
||||
{$t.common?.retry }
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -105,39 +105,39 @@
|
||||
{:else if dashboard}
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Last modified</p>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.dashboard?.last_modified }</p>
|
||||
<p class="mt-2 text-lg font-semibold text-slate-900">{formatDate(dashboard.last_modified)}</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Charts</p>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.dashboard?.charts }</p>
|
||||
<p class="mt-2 text-lg font-semibold text-slate-900">{dashboard.chart_count || 0}</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-4">
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Datasets</p>
|
||||
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.nav?.datasets }</p>
|
||||
<p class="mt-2 text-lg font-semibold text-slate-900">{dashboard.dataset_count || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if dashboard.description}
|
||||
<div class="rounded-xl border border-slate-200 bg-white p-4">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">Overview</h2>
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wide text-slate-500">{$t.dashboard?.overview }</h2>
|
||||
<p class="mt-2 text-sm text-slate-700">{dashboard.description}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<div class="border-b border-slate-200 px-4 py-3">
|
||||
<h2 class="text-lg font-semibold text-slate-900">Charts</h2>
|
||||
<h2 class="text-lg font-semibold text-slate-900">{$t.dashboard?.charts }</h2>
|
||||
</div>
|
||||
{#if dashboard.charts && dashboard.charts.length > 0}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">Chart</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">Dataset</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">Overview</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">Last modified</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.settings?.type_chart }</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.nav?.datasets }</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.dashboard?.overview }</th>
|
||||
<th class="px-4 py-2 text-left font-semibold text-slate-600">{$t.dashboard?.last_modified }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
@@ -152,9 +152,9 @@
|
||||
<button
|
||||
class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-blue-700 hover:bg-blue-50 hover:text-blue-800"
|
||||
on:click={() => openDataset(chart.dataset_id)}
|
||||
title={`Open dataset ${chart.dataset_id}`}
|
||||
title={`${$t.datasets?.table_name } ${chart.dataset_id}`}
|
||||
>
|
||||
Dataset {chart.dataset_id}
|
||||
{$t.nav?.datasets } {chart.dataset_id}
|
||||
<Icon name="chevronRight" size={12} className="text-blue-500" />
|
||||
</button>
|
||||
{:else}
|
||||
@@ -169,13 +169,13 @@
|
||||
</table>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="px-4 py-8 text-sm text-slate-500">No charts found for this dashboard.</div>
|
||||
<div class="px-4 py-8 text-sm text-slate-500">{$t.dashboard?.no_charts }</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||||
<div class="border-b border-slate-200 px-4 py-3">
|
||||
<h2 class="text-lg font-semibold text-slate-900">Datasets</h2>
|
||||
<h2 class="text-lg font-semibold text-slate-900">{$t.nav?.datasets }</h2>
|
||||
</div>
|
||||
{#if dashboard.datasets && dashboard.datasets.length > 0}
|
||||
<div class="divide-y divide-slate-100">
|
||||
@@ -199,7 +199,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="px-4 py-8 text-sm text-slate-500">No datasets found for this dashboard.</div>
|
||||
<div class="px-4 py-8 text-sm text-slate-500">{$t.dashboard?.no_datasets }</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user