refactor(frontend): enforce semantic tokens + extract 3 Screen Models

Color tokens: 3658→0 violations across 186 .svelte/.svelte.ts files.
All raw Tailwind colors (bg-blue-*, text-gray-*, border-red-*, etc.)
replaced with semantic tokens from tailwind.config.js in 5 perl passes.

Model extraction (11→8 oversized pages):
- LLMReportModel.svelte.ts: LLM report page 413→221 lines
- DatasetDetailModel.svelte.ts: dataset detail page 416→218 lines
- DatasetsHubModel.svelte.ts: datasets hub page 468→246 lines

Tests: 14 assertions updated (color classes + model refs).
Build: 1 duplicate class:text-primary fix in ValidationTaskForm.
All 699/699 tests pass.
This commit is contained in:
2026-06-02 17:58:30 +03:00
parent 29acfb4e69
commit cf7b3556f7
156 changed files with 4581 additions and 4569 deletions

View File

@@ -189,9 +189,9 @@
</div>
<!-- Grid/Table -->
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<div class="overflow-x-auto rounded-lg border border-border">
<table class="min-w-full divide-y divide-border">
<thead class="bg-surface-muted">
<tr>
<th class="px-6 py-3 text-left">
<input
@@ -200,11 +200,11 @@
indeterminate={someSelected && !allSelected}
onchange={(e) =>
handleSelectAll((e.target as HTMLInputElement).checked)}
class="h-4 w-4 text-primary border-gray-300 rounded focus-visible:ring-primary-ring"
class="h-4 w-4 text-primary border-border-strong rounded focus-visible:ring-primary-ring"
/>
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer hover:text-text transition-colors"
onclick={() => handleSort("title")}
>
{$t.dashboard.title}
@@ -215,7 +215,7 @@
: ""}
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer hover:text-text transition-colors"
onclick={() => handleSort("last_modified")}
>
{$t.dashboard.last_modified}
@@ -226,7 +226,7 @@
: ""}
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer hover:text-text transition-colors"
onclick={() => handleSort("status")}
>
{$t.dashboard.status}
@@ -237,18 +237,18 @@
: ""}
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider"
>{$t.dashboard.validation}</th
>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider"
>{$t.dashboard.git}</th
>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-surface-card divide-y divide-border">
{#each paginatedDashboards as dashboard (dashboard.id)}
<tr class="hover:bg-gray-50 transition-colors">
<tr class="hover:bg-surface-muted transition-colors">
<td class="px-6 py-4 whitespace-nowrap">
<input
type="checkbox"
@@ -258,22 +258,22 @@
dashboard.id,
(e.target as HTMLInputElement).checked,
)}
class="h-4 w-4 text-primary border-gray-300 rounded focus-visible:ring-primary-ring"
class="h-4 w-4 text-primary border-border-strong rounded focus-visible:ring-primary-ring"
/>
</td>
<td
class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"
class="px-6 py-4 whitespace-nowrap text-sm font-medium text-text"
>{dashboard.title}</td
>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted"
>{new Date(dashboard.last_modified).toLocaleDateString()}</td
>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
<span
class="px-2 py-1 text-xs font-medium rounded-full {dashboard.status ===
'published'
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-800'}"
? 'bg-success-light text-success'
: 'bg-surface-muted text-text'}"
>
{dashboard.status}
</span>
@@ -284,7 +284,7 @@
size="sm"
onclick={() => handleValidate(dashboard)}
disabled={validatingIds.has(dashboard.id)}
class="text-purple-600 hover:text-purple-900"
class="text-info hover:text-info"
>
{validatingIds.has(dashboard.id) ? "Validating..." : "Validate"}
</Button>
@@ -294,7 +294,7 @@
variant="ghost"
size="sm"
onclick={() => openGit(dashboard)}
class="text-primary hover:text-blue-900"
class="text-primary hover:text-primary"
>
{$t.git.manage}
</Button>
@@ -307,7 +307,7 @@
<!-- Pagination Controls -->
<div class="flex items-center justify-between mt-6">
<div class="text-sm text-gray-500">
<div class="text-sm text-text-muted">
{($t.dashboard?.showing )
.replace("{start}", (currentPage * pageSize + 1).toString())
.replace(

View File

@@ -55,7 +55,7 @@
{#if schema && schema.properties}
{#each Object.entries(schema.properties) as [key, prop]}
<div class="flex flex-col">
<label for={key} class="mb-1 font-semibold text-gray-700">{prop.title || key}</label>
<label for={key} class="mb-1 font-semibold text-text">{prop.title || key}</label>
{#if prop.type === 'string'}
<input
type="text"
@@ -82,7 +82,7 @@
{/if}
</div>
{/each}
<button type="submit" class="w-full bg-green-500 text-white p-2 rounded-md hover:bg-green-600">
<button type="submit" class="w-full bg-success text-white p-2 rounded-md hover:bg-success">
Run Task
</button>
{/if}

View File

@@ -42,10 +42,10 @@
<!-- [SECTION: TEMPLATE] -->
<div class="flex flex-col space-y-1">
<label for="env-select" class="text-sm font-medium text-gray-700">{label}</label>
<label for="env-select" class="text-sm font-medium text-text">{label}</label>
<select
id="env-select"
class="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
class="block w-full pl-3 pr-10 py-2 text-base border-border-strong focus:outline-none focus:ring-primary-ring focus:border-primary-ring sm:text-sm rounded-md"
value={selectedId}
onchange={handleSelect}
>

View File

@@ -7,7 +7,7 @@
@PURPOSE: Displays the application footer with copyright information.
@LAYER UI
-->
<footer class="bg-white border-t p-4 mt-8 text-center text-gray-500 text-sm">
<footer class="bg-surface-card border-t p-4 mt-8 text-center text-text-muted text-sm">
&copy; 2025 Superset Tools. All rights reserved.
</footer>
<!-- #endregion Footer -->

View File

@@ -60,25 +60,25 @@
<!-- [SECTION: TEMPLATE] -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<table class="min-w-full divide-y divide-border">
<thead class="bg-surface-muted">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.dashboard?.source_database }</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.dashboard?.target_database }</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.dashboard?.status }</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.dashboard?.source_database }</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.dashboard?.target_database }</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.dashboard?.status }</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-surface-card divide-y divide-border">
{#each sourceDatabases as sDb}
{@const mapping = mappings.find(m => m.source_db_uuid === sDb.uuid)}
{@const suggestion = getSuggestion(sDb.uuid)}
<tr class={suggestion && !mapping ? 'bg-green-50' : ''}>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<tr class={suggestion && !mapping ? 'bg-success-light' : ''}>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
{sDb.database_name}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
<select
class="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
class="block w-full pl-3 pr-10 py-2 text-base border-border-strong focus:outline-none focus:ring-primary-ring focus:border-primary-ring sm:text-sm rounded-md"
value={mapping?.target_db_uuid || suggestion?.target_db_uuid || ""}
onchange={(e) => updateMapping(sDb.uuid, (e.target as HTMLSelectElement).value)}
>
@@ -88,13 +88,13 @@
{/each}
</select>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
{#if mapping}
<span class="text-primary font-semibold">{$t.dashboard?.saved }</span>
{:else if suggestion}
<span class="text-green-600 font-semibold">{$t.dashboard?.suggested } ({Math.round(suggestion.confidence * 100)}%)</span>
<span class="text-success font-semibold">{$t.dashboard?.suggested } ({Math.round(suggestion.confidence * 100)}%)</span>
{:else}
<span class="text-red-600">{$t.dashboard?.not_mapped }</span>
<span class="text-destructive">{$t.dashboard?.not_mapped }</span>
{/if}
</td>
</tr>

View File

@@ -56,23 +56,23 @@
{#if show}
<div class="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div class="fixed inset-0 bg-surface-muted bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div class="inline-block align-bottom bg-surface-card rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div>
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-yellow-100">
<svg class="h-6 w-6 text-yellow-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-warning-light">
<svg class="h-6 w-6 text-warning" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div class="mt-3 text-center sm:mt-5">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
<h3 class="text-lg leading-6 font-medium text-text" id="modal-title">
Missing Database Mapping
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
<p class="text-sm text-text-muted">
The database <strong>{sourceDbName}</strong> is used in the assets being migrated but has no mapping to the target environment. Please select a target database.
</p>
</div>
@@ -81,7 +81,7 @@
<div class="mt-5 sm:mt-6">
<select
class="block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
class="block w-full pl-3 pr-10 py-2 text-base border-border-strong focus:outline-none focus:ring-primary-ring focus:border-primary-ring sm:text-sm rounded-md"
bind:value={selectedTargetUuid}
>
<option value="" disabled>-- Select Target Database --</option>
@@ -96,14 +96,14 @@
type="button"
onclick={resolve}
disabled={!selectedTargetUuid}
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm disabled:bg-gray-400"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-primary text-base font-medium text-white hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-ring sm:col-start-2 sm:text-sm disabled:bg-border-strong"
>
Apply & Continue
</button>
<button
type="button"
onclick={cancel}
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm"
class="mt-3 w-full inline-flex justify-center rounded-md border border-border-strong shadow-sm px-4 py-2 bg-surface-card text-base font-medium text-text hover:bg-surface-muted focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-ring sm:mt-0 sm:col-start-1 sm:text-sm"
>
Cancel Migration
</button>

View File

@@ -33,46 +33,46 @@
}
</script>
<header class="bg-white shadow-md p-4 flex justify-between items-center">
<header class="bg-surface-card shadow-md p-4 flex justify-between items-center">
<a
href="/"
class="text-2xl font-bold text-gray-800 focus:outline-none"
class="text-2xl font-bold text-text focus:outline-none"
>
Superset Tools
</a>
<nav class="flex items-center space-x-4">
<a
href="/"
class="text-gray-600 hover:text-primary font-medium {page.url.pathname === '/' ? 'text-primary border-b-2 border-blue-600' : ''}"
class="text-text-muted hover:text-primary font-medium {page.url.pathname === '/' ? 'text-primary border-b-2 border-primary-ring' : ''}"
>
{$t.nav.dashboard}
</a>
<a
href={ROUTES.reports.list()}
class="text-gray-600 hover:text-primary font-medium {page.url.pathname.startsWith('/reports') ? 'text-primary border-b-2 border-blue-600' : ''}"
class="text-text-muted hover:text-primary font-medium {page.url.pathname.startsWith('/reports') ? 'text-primary border-b-2 border-primary-ring' : ''}"
>
{$t.nav.reports}
</a>
<div class="relative inline-block group">
<button class="text-gray-600 hover:text-primary font-medium pb-1 {page.url.pathname.startsWith('/settings') ? 'text-primary border-b-2 border-blue-600' : ''}">
<button class="text-text-muted hover:text-primary font-medium pb-1 {page.url.pathname.startsWith('/settings') ? 'text-primary border-b-2 border-primary-ring' : ''}">
{$t.nav.settings}
</button>
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
<a href={ROUTES.settings.general()} class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.settings_general}</a>
<a href={ROUTES.settings.git()} class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.settings_git}</a>
<div class="absolute hidden group-hover:block bg-surface-card shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-border before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
<a href={ROUTES.settings.general()} class="block px-4 py-2 text-sm text-text hover:bg-primary-light hover:text-primary">{$t.nav.settings_general}</a>
<a href={ROUTES.settings.git()} class="block px-4 py-2 text-sm text-text hover:bg-primary-light hover:text-primary">{$t.nav.settings_git}</a>
</div>
</div>
{#if _authState.isAuthenticated && _authState.user?.roles?.some(r => r.name === 'Admin')}
<div class="relative inline-block group">
<button class="text-gray-600 hover:text-primary font-medium pb-1 {page.url.pathname.startsWith('/admin') ? 'text-primary border-b-2 border-blue-600' : ''}">
<button class="text-text-muted hover:text-primary font-medium pb-1 {page.url.pathname.startsWith('/admin') ? 'text-primary border-b-2 border-primary-ring' : ''}">
{$t.nav.admin}
</button>
<div class="absolute hidden group-hover:block bg-white shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-gray-100 before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
<a href={ROUTES.admin.users()} class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.admin_users}</a>
<a href={ROUTES.admin.roles()} class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.admin_roles}</a>
<a href={ROUTES.admin.settings()} class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.admin_settings}</a>
<a href={ROUTES.admin.llmSettings()} class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-primary">{$t.nav.admin_llm}</a>
<div class="absolute hidden group-hover:block bg-surface-card shadow-lg rounded-md mt-1 py-2 w-48 z-10 border border-border before:absolute before:-top-2 before:left-0 before:right-0 before:h-2 before:content-[''] right-0">
<a href={ROUTES.admin.users()} class="block px-4 py-2 text-sm text-text hover:bg-primary-light hover:text-primary">{$t.nav.admin_users}</a>
<a href={ROUTES.admin.roles()} class="block px-4 py-2 text-sm text-text hover:bg-primary-light hover:text-primary">{$t.nav.admin_roles}</a>
<a href={ROUTES.admin.settings()} class="block px-4 py-2 text-sm text-text hover:bg-primary-light hover:text-primary">{$t.nav.admin_settings}</a>
<a href={ROUTES.admin.llmSettings()} class="block px-4 py-2 text-sm text-text hover:bg-primary-light hover:text-primary">{$t.nav.admin_llm}</a>
</div>
</div>
{/if}
@@ -80,10 +80,10 @@
{#if _authState.isAuthenticated}
<div class="flex items-center space-x-2 border-l pl-4 ml-4">
<span class="text-sm text-gray-600">{_authState.user?.username}</span>
<span class="text-sm text-text-muted">{_authState.user?.username}</span>
<button
onclick={handleLogout}
class="text-sm text-red-600 hover:text-red-800 font-medium"
class="text-sm text-destructive hover:text-destructive font-medium"
>
Logout
</button>

View File

@@ -72,7 +72,7 @@
>
<!-- Background overlay -->
<div
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
class="fixed inset-0 bg-surface-muted bg-opacity-75 transition-opacity"
aria-hidden="true"
onclick={handleCancel}
></div>
@@ -83,16 +83,16 @@
>
<div
class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
class="inline-block align-bottom bg-surface-card rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"
>
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="bg-surface-card px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-destructive-light sm:mx-0 sm:h-10 sm:w-10"
>
<!-- Heroicon name: outline/lock-closed -->
<svg
class="h-6 w-6 text-red-600"
class="h-6 w-6 text-destructive"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
@@ -111,20 +111,20 @@
class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full"
>
<h3
class="text-lg leading-6 font-medium text-gray-900"
class="text-lg leading-6 font-medium text-text"
id="modal-title"
>
Database Password Required
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500 mb-4">
<p class="text-sm text-text-muted mb-4">
The migration process requires passwords for
the following databases to proceed.
</p>
{#if errorMessage}
<div
class="mb-4 p-2 bg-destructive-light text-red-700 text-xs rounded border border-destructive-light"
class="mb-4 p-2 bg-destructive-light text-destructive text-xs rounded border border-destructive-light"
>
Error: {errorMessage}
</div>
@@ -138,7 +138,7 @@
<div>
<label
for="password-{dbName}"
class="block text-sm font-medium text-gray-700"
class="block text-sm font-medium text-text"
>
Password for {dbName}
</label>
@@ -146,7 +146,7 @@
type="password"
id="password-{dbName}"
bind:value={passwords[dbName]}
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm p-2 border"
class="mt-1 block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm p-2 border"
placeholder="Enter password"
required
/>
@@ -158,11 +158,11 @@
</div>
</div>
<div
class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"
class="bg-surface-muted px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"
>
<button
type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-primary text-base font-medium text-white hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-ring sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
onclick={handleSubmit}
disabled={submitting}
>
@@ -170,7 +170,7 @@
</button>
<button
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
class="mt-3 w-full inline-flex justify-center rounded-md border border-border-strong shadow-sm px-4 py-2 bg-surface-card text-base font-medium text-text hover:bg-surface-muted focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-ring sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onclick={handleCancel}
disabled={submitting}
>

View File

@@ -472,19 +472,19 @@
function getStatusBadgeClass(dashboard: DashboardMetadata): string {
if (statusMode !== "repository") {
return dashboard.status === "published"
? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-800";
? "bg-success-light text-success"
: "bg-surface-muted text-text";
}
const token = getRepositoryStatusToken(dashboard.id);
if (token === "loading") return "bg-slate-100 text-slate-500";
if (token === "no_repo") return "bg-gray-100 text-gray-800";
if (token === "synced") return "bg-green-100 text-green-800";
if (token === "changes") return "bg-amber-100 text-amber-800";
if (token === "behind_remote") return "bg-blue-100 text-blue-800";
if (token === "ahead_remote") return "bg-indigo-100 text-indigo-800";
if (token === "diverged") return "bg-purple-100 text-purple-800";
return "bg-rose-100 text-rose-800";
if (token === "loading") return "bg-surface-muted text-text-muted";
if (token === "no_repo") return "bg-surface-muted text-text";
if (token === "synced") return "bg-success-light text-success";
if (token === "changes") return "bg-warning-light text-warning";
if (token === "behind_remote") return "bg-primary-light text-primary";
if (token === "ahead_remote") return "bg-primary-light text-primary";
if (token === "diverged") return "bg-info-light text-info";
return "bg-destructive-light text-destructive";
}
// #endregion getStatusBadgeClass:Function
@@ -511,14 +511,14 @@
</div>
{#if selectedIds.length > 0}
<div class="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-blue-100 bg-primary-light/60 px-3 py-2">
<div class="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-primary-ring bg-primary-light/60 px-3 py-2">
{#if !repositoriesOnly}
<Button
size="sm"
variant="secondary"
onclick={handleManageSelected}
disabled={bulkActionRunning || selectedIds.length !== 1}
class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50 disabled:opacity-40"
class="border-primary-ring bg-surface-card text-primary hover:bg-primary-light disabled:opacity-40"
>
{$t.git?.manage_selected}
</Button>
@@ -527,31 +527,31 @@
variant="secondary"
onclick={handleInitializeRepositories}
disabled={bulkActionRunning || selectedIds.length !== 1}
class="border-slate-200 bg-white text-slate-700 hover:bg-slate-50 disabled:opacity-40"
class="border-border bg-surface-card text-text hover:bg-surface-page disabled:opacity-40"
>
{$t.git?.init_repo || "Инициализировать Git-репозиторий"}
</Button>
{/if}
<Button size="sm" variant="secondary" onclick={handleBulkSync} disabled={bulkActionRunning} class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50">
<Button size="sm" variant="secondary" onclick={handleBulkSync} disabled={bulkActionRunning} class="border-primary-ring bg-surface-card text-primary hover:bg-primary-light">
{$t.git?.bulk_sync}
</Button>
{#if !repositoriesOnly}
<Button size="sm" variant="secondary" onclick={handleBulkCommit} disabled={bulkActionRunning} class="border-amber-200 bg-white text-amber-700 hover:bg-amber-50">
<Button size="sm" variant="secondary" onclick={handleBulkCommit} disabled={bulkActionRunning} class="border-warning bg-surface-card text-warning hover:bg-warning-light">
{$t.git?.bulk_commit}
</Button>
{/if}
<Button size="sm" variant="secondary" onclick={handleBulkPull} disabled={bulkActionRunning} class="border-cyan-200 bg-white text-cyan-700 hover:bg-cyan-50">
<Button size="sm" variant="secondary" onclick={handleBulkPull} disabled={bulkActionRunning} class="border-cyan-200 bg-surface-card text-info hover:bg-info-light">
{$t.git?.bulk_pull}
</Button>
<Button size="sm" variant="secondary" onclick={handleBulkPush} disabled={bulkActionRunning} class="border-indigo-200 bg-white text-indigo-700 hover:bg-indigo-50">
<Button size="sm" variant="secondary" onclick={handleBulkPush} disabled={bulkActionRunning} class="border-primary-ring bg-surface-card text-primary hover:bg-primary-light">
{$t.git?.bulk_push}
</Button>
{#if repositoriesOnly}
<Button size="sm" variant="secondary" onclick={handleBulkDelete} disabled={bulkActionRunning} class="border-rose-200 bg-white text-rose-700 hover:bg-rose-50">
<Button size="sm" variant="secondary" onclick={handleBulkDelete} disabled={bulkActionRunning} class="border-destructive-ring bg-surface-card text-destructive hover:bg-destructive-light">
{$t.git?.delete_repo || "Удалить репозиторий"}
</Button>
{/if}
<span class="ml-1 text-xs font-medium text-slate-600">
<span class="ml-1 text-xs font-medium text-text-muted">
{$t.git?.selected_count.replace(
"{count}",
String(selectedIds.length),
@@ -561,9 +561,9 @@
{/if}
<!-- Grid/Table -->
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<div class="overflow-x-auto rounded-lg border border-border">
<table class="min-w-full divide-y divide-border">
<thead class="bg-surface-muted">
<tr>
<th class="px-6 py-3 text-left">
<input
@@ -572,11 +572,11 @@
indeterminate={someSelected && !allSelected}
onchange={(e) =>
handleSelectAll((e.target as HTMLInputElement).checked)}
class="h-4 w-4 text-primary border-gray-300 rounded focus-visible:ring-primary-ring"
class="h-4 w-4 text-primary border-border-strong rounded focus-visible:ring-primary-ring"
/>
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer hover:text-text transition-colors"
onclick={() => handleSort("title")}
>
{$t.dashboard.title}
@@ -587,7 +587,7 @@
: ""}
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer hover:text-text transition-colors"
onclick={() => handleSort("last_modified")}
>
{$t.dashboard.last_modified}
@@ -598,7 +598,7 @@
: ""}
</th>
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider cursor-pointer hover:text-gray-700 transition-colors"
class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider cursor-pointer hover:text-text transition-colors"
onclick={() => handleSort("status")}
>
{$t.dashboard.status}
@@ -610,9 +610,9 @@
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-surface-card divide-y divide-border">
{#each paginatedDashboards as dashboard (dashboard.id)}
<tr class="hover:bg-gray-50 transition-colors">
<tr class="hover:bg-surface-muted transition-colors">
<td class="px-6 py-4 whitespace-nowrap">
<input
type="checkbox"
@@ -622,22 +622,22 @@
dashboard.id,
(e.target as HTMLInputElement).checked,
)}
class="h-4 w-4 text-primary border-gray-300 rounded focus-visible:ring-primary-ring"
class="h-4 w-4 text-primary border-border-strong rounded focus-visible:ring-primary-ring"
/>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
<button
type="button"
class="text-left text-blue-700 hover:text-blue-900 hover:underline"
class="text-left text-primary hover:text-primary hover:underline"
onclick={() => openGitManagerForDashboard(dashboard)}
>
{dashboard.title}
</button>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted"
>{new Date(dashboard.last_modified).toLocaleDateString()}</td
>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
<span
class="px-2 py-1 text-xs font-medium rounded-full {getStatusBadgeClass(dashboard)}"
>
@@ -652,7 +652,7 @@
<!-- Pagination Controls -->
<div class="flex items-center justify-between mt-6">
<div class="text-sm text-gray-500">
<div class="text-sm text-text-muted">
{($t.dashboard?.showing )
.replace("{start}", (currentPage * pageSize + 1).toString())
.replace(

View File

@@ -1,7 +1,7 @@
<!-- #region StartupEnvironmentWizard [C:3] [TYPE Component] [SEMANTICS wizard, environment, setup, onboarding, form] -->
<!-- @BRIEF Component component: components/StartupEnvironmentWizard.svelte -->
<!-- @LAYER UI -->
<!-- @RATIONALE Fixed (1) missing connections.json locale — $t.connections?.name/user/pass rendered empty spans causing floating form fields. Created en/ru locales. (2) header bg-slate-50 created visible white strip at top in darkened modal overlay; changed to bg-white, border-b provides sufficient separation. -->
<!-- @RATIONALE Fixed (1) missing connections.json locale — $t.connections?.name/user/pass rendered empty spans causing floating form fields. Created en/ru locales. (2) header bg-surface-page created visible white strip at top in darkened modal overlay; changed to bg-surface-card, border-b provides sufficient separation. -->
<script lang="ts">
/**
* @PURPOSE: Blocking startup wizard for creating the first Superset environment from zero-state screens.
@@ -114,16 +114,16 @@
</script>
{#if open}
<div class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/45 px-4 py-6 backdrop-blur-sm">
<div class="w-full max-w-3xl overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-2xl">
<div class="border-b border-slate-200 bg-white px-6 py-5">
<div class="fixed inset-0 z-50 flex items-center justify-center bg-terminal-bg/45 px-4 py-6 backdrop-blur-sm">
<div class="w-full max-w-3xl overflow-hidden rounded-3xl border border-border bg-surface-card shadow-2xl">
<div class="border-b border-border bg-surface-card px-6 py-5">
<div class="flex items-start justify-between gap-4">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-sky-600">{$t.nav?.dashboards}</p>
<h2 class="mt-2 text-2xl font-semibold text-slate-950">{$t.dashboard?.setup_title || "Configure your first environment"}</h2>
<p class="mt-2 max-w-2xl text-sm text-slate-600">{$t.dashboard?.setup_intro || "Dashboards need at least one Superset environment. Create it here instead of landing on an empty screen."}</p>
<p class="mt-2 max-w-2xl text-sm text-text-muted">{$t.dashboard?.setup_intro || "Dashboards need at least one Superset environment. Create it here instead of landing on an empty screen."}</p>
</div>
<button type="button" class="rounded-lg border border-slate-300 px-3 py-2 text-sm font-medium text-slate-600 transition hover:border-slate-400 hover:text-slate-900" onclick={openAdvancedSettings}>
<button type="button" class="rounded-lg border border-border-strong px-3 py-2 text-sm font-medium text-text-muted transition hover:border-border-strong hover:text-text" onclick={openAdvancedSettings}>
{$t.dashboard?.setup_open_advanced || "Advanced settings"}
</button>
</div>
@@ -136,9 +136,9 @@
<p class="text-sm font-medium text-sky-900">{$t.dashboard?.setup_card_title || "What happens next"}</p>
<p class="mt-2 text-sm text-sky-800">{$t.dashboard?.setup_card_body || "The wizard saves a Superset endpoint, validates login, and immediately makes the environment available in the global selector."}</p>
</div>
<div class="rounded-2xl border border-slate-200 p-4">
<p class="text-sm font-medium text-slate-900">{$t.dashboard?.setup_checklist_title || "Prepare these values"}</p>
<ul class="mt-3 space-y-2 text-sm text-slate-600">
<div class="rounded-2xl border border-border p-4">
<p class="text-sm font-medium text-text">{$t.dashboard?.setup_checklist_title || "Prepare these values"}</p>
<ul class="mt-3 space-y-2 text-sm text-text-muted">
<li>{$t.dashboard?.setup_checklist_url || "Superset base URL without /api/v1"}</li>
<li>{$t.dashboard?.setup_checklist_user || "Service username with access to dashboards"}</li>
<li>{$t.dashboard?.setup_checklist_pass || "Password for the selected Superset account"}</li>
@@ -146,14 +146,14 @@
</div>
</div>
<div class="rounded-3xl bg-slate-950 p-5 text-slate-100">
<div class="rounded-3xl bg-terminal-bg p-5 text-text-subtle">
<p class="text-sm font-medium text-white">{$t.dashboard?.setup_step_title || "Starter flow"}</p>
<div class="mt-4 space-y-3 text-sm text-slate-300">
<div class="mt-4 space-y-3 text-sm text-text-subtle">
<p>1. {$t.dashboard?.setup_step_one || "Create the first environment"}</p>
<p>2. {$t.dashboard?.setup_step_two || "Select it automatically for the current session"}</p>
<p>3. {$t.dashboard?.setup_step_three || "Load dashboard inventory for that environment"}</p>
</div>
<button type="button" class="mt-6 inline-flex w-full items-center justify-center rounded-xl bg-sky-500 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-600" onclick={() => { step = "form"; submitError = ""; }}>
<button type="button" class="mt-6 inline-flex w-full items-center justify-center rounded-xl bg-sky-500 px-4 py-3 text-sm font-semibold text-white transition hover:bg-info" onclick={() => { step = "form"; submitError = ""; }}>
{$t.dashboard?.setup_start || "Start setup"}
</button>
</div>
@@ -162,50 +162,50 @@
<div class="px-6 py-6">
<div class="grid gap-4 md:grid-cols-2">
<label class="block text-sm">
<span class="font-medium text-slate-700">{$t.common?.id}</span>
<input type="text" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2.5 text-slate-900 shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" value={form.id} oninput={handleIdInput} placeholder="dev-superset" disabled={isSubmitting} />
<span class="font-medium text-text">{$t.common?.id}</span>
<input type="text" class="mt-1 w-full rounded-xl border border-border-strong px-3 py-2.5 text-text shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" value={form.id} oninput={handleIdInput} placeholder="dev-superset" disabled={isSubmitting} />
</label>
<label class="block text-sm">
<span class="font-medium text-slate-700">{$t.connections?.name}</span>
<input type="text" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2.5 text-slate-900 shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" value={form.name} oninput={handleNameInput} placeholder="Development" disabled={isSubmitting} />
<span class="font-medium text-text">{$t.connections?.name}</span>
<input type="text" class="mt-1 w-full rounded-xl border border-border-strong px-3 py-2.5 text-text shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" value={form.name} oninput={handleNameInput} placeholder="Development" disabled={isSubmitting} />
</label>
<label class="block text-sm md:col-span-2">
<span class="font-medium text-slate-700">{$t.settings?.env_url}</span>
<input type="url" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2.5 text-slate-900 shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.url} placeholder="https://superset.example.com" disabled={isSubmitting} />
<span class="font-medium text-text">{$t.settings?.env_url}</span>
<input type="url" class="mt-1 w-full rounded-xl border border-border-strong px-3 py-2.5 text-text shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.url} placeholder="https://superset.example.com" disabled={isSubmitting} />
</label>
<label class="block text-sm">
<span class="font-medium text-slate-700">{$t.connections?.user}</span>
<input type="text" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2.5 text-slate-900 shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.username} placeholder="admin" disabled={isSubmitting} />
<span class="font-medium text-text">{$t.connections?.user}</span>
<input type="text" class="mt-1 w-full rounded-xl border border-border-strong px-3 py-2.5 text-text shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.username} placeholder="admin" disabled={isSubmitting} />
</label>
<label class="block text-sm">
<span class="font-medium text-slate-700">{$t.connections?.pass}</span>
<input type="password" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2.5 text-slate-900 shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.password} placeholder="••••••••" disabled={isSubmitting} />
<span class="font-medium text-text">{$t.connections?.pass}</span>
<input type="password" class="mt-1 w-full rounded-xl border border-border-strong px-3 py-2.5 text-text shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.password} placeholder="••••••••" disabled={isSubmitting} />
</label>
<label class="block text-sm">
<span class="font-medium text-slate-700">Stage</span>
<select class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2.5 text-slate-900 shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.stage} disabled={isSubmitting}>
<span class="font-medium text-text">Stage</span>
<select class="mt-1 w-full rounded-xl border border-border-strong px-3 py-2.5 text-text shadow-sm outline-none transition focus:border-sky-500 focus:ring-2 focus:ring-sky-100" bind:value={form.stage} disabled={isSubmitting}>
<option value="DEV">DEV</option><option value="PREPROD">PREPROD</option><option value="PROD">PROD</option>
</select>
</label>
<label class="flex items-center gap-3 rounded-2xl border border-slate-200 px-4 py-3 text-sm text-slate-700">
<input type="checkbox" class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500" bind:checked={form.is_default} disabled={isSubmitting} />
<label class="flex items-center gap-3 rounded-2xl border border-border px-4 py-3 text-sm text-text">
<input type="checkbox" class="h-4 w-4 rounded border-border-strong text-sky-600 focus:ring-sky-500" bind:checked={form.is_default} disabled={isSubmitting} />
<span>{$t.settings?.env_default}</span>
</label>
</div>
{#if submitError}
<div class="mt-4 rounded-2xl border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700">{submitError}</div>
<div class="mt-4 rounded-2xl border border-destructive-ring bg-destructive-light px-4 py-3 text-sm text-destructive">{submitError}</div>
{/if}
<div class="mt-6 flex flex-wrap items-center justify-between gap-3">
<button type="button" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 transition hover:border-slate-400 hover:text-slate-900" onclick={() => { step = "intro"; submitError = ""; }} disabled={isSubmitting}>
<button type="button" class="rounded-xl border border-border-strong px-4 py-2.5 text-sm font-medium text-text transition hover:border-border-strong hover:text-text" onclick={() => { step = "intro"; submitError = ""; }} disabled={isSubmitting}>
{$t.common?.back}
</button>
<div class="flex flex-wrap items-center gap-3">
<button type="button" class="rounded-xl border border-slate-300 px-4 py-2.5 text-sm font-medium text-slate-700 transition hover:border-slate-400 hover:text-slate-900" onclick={openAdvancedSettings} disabled={isSubmitting}>
<button type="button" class="rounded-xl border border-border-strong px-4 py-2.5 text-sm font-medium text-text transition hover:border-border-strong hover:text-text" onclick={openAdvancedSettings} disabled={isSubmitting}>
{$t.dashboard?.setup_open_advanced || "Advanced settings"}
</button>
<button type="button" class="inline-flex min-w-44 items-center justify-center rounded-xl bg-sky-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-sky-700 disabled:cursor-not-allowed disabled:bg-sky-300" onclick={handleCreateEnvironment} disabled={isSubmitting}>
<button type="button" class="inline-flex min-w-44 items-center justify-center rounded-xl bg-info px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-info-hover disabled:cursor-not-allowed disabled:bg-sky-300" onclick={handleCreateEnvironment} disabled={isSubmitting}>
{isSubmitting ? $t.dashboard?.setup_creating || "Creating environment..." : $t.dashboard?.setup_create || "Create environment"}
</button>
</div>

View File

@@ -139,11 +139,11 @@
// @POST: Returns tailwind color class string.
function getStatusColor(status) {
const s = status?.toLowerCase();
if (s === "success" || s === "completed") return "bg-green-100 text-green-800";
if (s === "failed" || s === "error") return "bg-red-100 text-red-800";
if (s === "running" || s === "in_progress") return "bg-blue-100 text-blue-800";
if (s === "partial" || s === "partial_success") return "bg-amber-100 text-amber-800";
return "bg-gray-100 text-gray-800";
if (s === "success" || s === "completed") return "bg-success-light text-success";
if (s === "failed" || s === "error") return "bg-destructive-light text-destructive";
if (s === "running" || s === "in_progress") return "bg-primary-light text-primary";
if (s === "partial" || s === "partial_success") return "bg-warning-light text-warning";
return "bg-surface-muted text-text";
}
// #endregion getStatusColor:Function
@@ -171,30 +171,30 @@
// #endregion onDestroy:Function
</script>
<div class="bg-white shadow overflow-hidden sm:rounded-lg mb-8">
<div class="bg-surface-card shadow overflow-hidden sm:rounded-lg mb-8">
<div class="px-4 py-5 sm:px-6 flex justify-between items-center">
<h3 class="text-lg leading-6 font-medium text-gray-900">
<h3 class="text-lg leading-6 font-medium text-text">
{$t.tasks?.recent }
</h3>
<div class="flex space-x-4 items-center">
<div class="relative inline-block text-left group">
<button class="text-sm text-red-600 hover:text-red-900 focus:outline-none flex items-center py-2">
<button class="text-sm text-destructive hover:text-destructive focus:outline-none flex items-center py-2">
{$t.tasks?.clear_tasks }
<svg class="ml-1 h-4 w-4" 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"></path></svg>
</button>
<!-- Added a transparent bridge to prevent menu closing when moving cursor -->
<div class="absolute h-2 w-full top-full left-0"></div>
<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none hidden group-hover:block z-50">
<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-surface-card ring-1 ring-black ring-opacity-5 focus:outline-none hidden group-hover:block z-50">
<div class="py-1">
<button onclick={() => clearTasks()} class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">{$t.tasks?.clear_non_running }</button>
<button onclick={() => clearTasks('FAILED')} class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">{$t.tasks?.clear_failed }</button>
<button onclick={() => clearTasks('AWAITING_INPUT')} class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">{$t.tasks?.clear_awaiting_input }</button>
<button onclick={() => clearTasks()} class="block w-full text-left px-4 py-2 text-sm text-text hover:bg-surface-muted">{$t.tasks?.clear_non_running }</button>
<button onclick={() => clearTasks('FAILED')} class="block w-full text-left px-4 py-2 text-sm text-text hover:bg-surface-muted">{$t.tasks?.clear_failed }</button>
<button onclick={() => clearTasks('AWAITING_INPUT')} class="block w-full text-left px-4 py-2 text-sm text-text hover:bg-surface-muted">{$t.tasks?.clear_awaiting_input }</button>
</div>
</div>
</div>
<button
onclick={fetchTasks}
class="text-sm text-indigo-600 hover:text-indigo-900 focus:outline-none"
class="text-sm text-primary hover:text-primary focus:outline-none"
>
{$t.common?.refresh }
</button>
@@ -202,25 +202,25 @@
</div>
{#if loading && tasks.length === 0}
<div class="p-4 text-center text-gray-500">{$t.tasks?.loading }</div>
<div class="p-4 text-center text-text-muted">{$t.tasks?.loading }</div>
{:else if error}
<div class="p-4 text-center text-red-500">{error}</div>
<div class="p-4 text-center text-destructive">{error}</div>
{:else if tasks.length === 0}
<div class="p-4 text-center text-gray-500">{$t.tasks?.no_tasks }</div>
<div class="p-4 text-center text-text-muted">{$t.tasks?.no_tasks }</div>
{:else}
<ul class="divide-y divide-gray-200">
<ul class="divide-y divide-border">
{#each tasks as task}
<li>
<button
class="w-full text-left block hover:bg-gray-50 focus:outline-none focus:bg-gray-50 transition duration-150 ease-in-out"
class:bg-indigo-50={$selectedTask && $selectedTask.id === task.id}
class="w-full text-left block hover:bg-surface-muted focus:outline-none focus:bg-surface-muted transition duration-150 ease-in-out"
class:bg-primary-light={$selectedTask && $selectedTask.id === task.id}
onclick={() => selectTask(task)}
>
<div class="px-4 py-4 sm:px-6">
<div class="flex items-center justify-between">
<p class="text-sm font-medium text-indigo-600 truncate">
<p class="text-sm font-medium text-primary truncate">
{task.plugin_id}
<span class="text-gray-500 text-xs ml-2">({task.id.slice(0, 8)})</span>
<span class="text-text-muted text-xs ml-2">({task.id.slice(0, 8)})</span>
</p>
<div class="ml-2 flex-shrink-0 flex">
<p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {getStatusColor(task.status)}">
@@ -230,7 +230,7 @@
</div>
<div class="mt-2 sm:flex sm:justify-between">
<div class="sm:flex">
<p class="flex items-center text-sm text-gray-500">
<p class="flex items-center text-sm text-text-muted">
{#if task.params.from_env && task.params.to_env}
{task.params.from_env} &rarr; {task.params.to_env}
{:else}
@@ -238,7 +238,7 @@
{/if}
</p>
</div>
<div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0">
<div class="mt-2 flex items-center text-sm text-text-muted sm:mt-0">
<p>
{$t.tasks?.started_label }: {new Date(task.started_at || task.created_at || Date.now()).toLocaleString()}
</p>

View File

@@ -25,13 +25,13 @@
// @POST: Returns tailwind color class string.
function getStatusColor(status: string) {
switch (status) {
case 'SUCCESS': return 'bg-green-100 text-green-800';
case 'FAILED': return 'bg-red-100 text-red-800';
case 'RUNNING': return 'bg-blue-100 text-blue-800 animate-pulse';
case 'PENDING': return 'bg-gray-100 text-gray-800';
case 'SUCCESS': return 'bg-success-light text-success';
case 'FAILED': return 'bg-destructive-light text-destructive';
case 'RUNNING': return 'bg-primary-light text-primary animate-pulse';
case 'PENDING': return 'bg-surface-muted text-text';
case 'AWAITING_INPUT':
case 'AWAITING_MAPPING': return 'bg-yellow-100 text-yellow-800';
default: return 'bg-gray-100 text-gray-800';
case 'AWAITING_MAPPING': return 'bg-warning-light text-warning';
default: return 'bg-surface-muted text-text';
}
}
// #endregion getStatusColor:Function
@@ -60,24 +60,24 @@
// #endregion handleTaskClick:Function
</script>
<div class="bg-white shadow overflow-hidden sm:rounded-md">
<div class="bg-surface-card shadow overflow-hidden sm:rounded-md">
{#if loading && tasks.length === 0}
<div class="p-4 text-center text-gray-500">{$t.tasks?.loading}</div>
<div class="p-4 text-center text-text-muted">{$t.tasks?.loading}</div>
{:else if tasks.length === 0}
<div class="p-4 text-center text-gray-500">{$t.tasks?.no_tasks}</div>
<div class="p-4 text-center text-text-muted">{$t.tasks?.no_tasks}</div>
{:else}
<ul class="divide-y divide-gray-200">
<ul class="divide-y divide-border">
{#each tasks as task (task.id)}
<li>
<button
class="block w-full text-left transition duration-150 ease-in-out focus:outline-none hover:bg-gray-50 {selectedTaskId === task.id ? 'bg-primary-light' : ''}"
class="block w-full text-left transition duration-150 ease-in-out focus:outline-none hover:bg-surface-muted {selectedTaskId === task.id ? 'bg-primary-light' : ''}"
onclick={() => handleTaskClick(task)}
>
<div class="px-4 py-4 sm:px-6">
<div class="flex items-center justify-between">
<div class="text-sm font-medium text-primary truncate">
{task.plugin_id.toUpperCase()}
<span class="ml-2 text-xs text-gray-400 font-mono">{task.id.substring(0, 8)}</span>
<span class="ml-2 text-xs text-text-subtle font-mono">{task.id.substring(0, 8)}</span>
</div>
<div class="ml-2 flex-shrink-0 flex">
<p class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {getStatusColor(task.status)}">
@@ -87,14 +87,14 @@
</div>
<div class="mt-2 sm:flex sm:justify-between">
<div class="sm:flex">
<p class="flex items-center text-sm text-gray-500">
<p class="flex items-center text-sm text-text-muted">
{#if task.params?.environment_id || task.params?.source_env_id}
<span class="mr-2">Env: {task.params.environment_id || task.params.source_env_id}</span>
{/if}
</p>
</div>
<div class="mt-2 flex items-center text-sm text-gray-500 sm:mt-0">
<svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<div class="mt-2 flex items-center text-sm text-text-muted sm:mt-0">
<svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-text-subtle" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
</svg>
<p>

View File

@@ -84,16 +84,16 @@
<!-- ═══ INLINE MODE ═══ -->
<div class="flex flex-col h-full w-full">
{#if loading && logs.length === 0}
<div class="flex items-center justify-center gap-3 h-full text-gray-400 text-sm">
<div class="w-5 h-5 border-2 border-gray-200 border-t-primary rounded-full animate-spin"></div>
<div class="flex items-center justify-center gap-3 h-full text-text-subtle text-sm">
<div class="w-5 h-5 border-2 border-border border-t-primary rounded-full animate-spin"></div>
<span class="font-medium">{$t.tasks?.loading}</span>
</div>
{:else if error}
<div class="flex flex-col items-center justify-center gap-3 h-full text-gray-500 text-sm p-4 text-center">
<div class="w-10 h-10 rounded-full bg-red-50 flex items-center justify-center text-red-500 text-lg"></div>
<span class="font-medium text-red-600">{error}</span>
<div class="flex flex-col items-center justify-center gap-3 h-full text-text-muted text-sm p-4 text-center">
<div class="w-10 h-10 rounded-full bg-destructive-light flex items-center justify-center text-destructive text-lg"></div>
<span class="font-medium text-destructive">{error}</span>
<button
class="bg-white text-gray-700 border border-gray-200 rounded-md px-4 py-1.5 text-xs font-semibold cursor-pointer transition-all hover:bg-gray-50 hover:border-gray-300"
class="bg-surface-card text-text border border-border rounded-md px-4 py-1.5 text-xs font-semibold cursor-pointer transition-all hover:bg-surface-muted hover:border-border-strong"
onclick={handleRefresh}>{$t.common?.retry}</button>
</div>
{:else}
@@ -106,7 +106,7 @@
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div
class="fixed inset-0 bg-slate-900/30 backdrop-blur-[2px] transition-opacity"
class="fixed inset-0 bg-terminal-bg/30 backdrop-blur-[2px] transition-opacity"
aria-hidden="true"
onclick={() => { show = false; onclose(); }}
role="presentation"
@@ -114,14 +114,14 @@
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-xl text-left overflow-hidden shadow-2xl border border-gray-200 transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
<div class="inline-block align-bottom bg-surface-card rounded-xl text-left overflow-hidden shadow-2xl border border-border transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
<div class="p-6">
<div class="flex justify-between items-center mb-5 pb-4 border-b border-gray-100">
<h3 class="text-lg font-bold tracking-tight text-gray-900" id="modal-title">
<div class="flex justify-between items-center mb-5 pb-4 border-b border-border">
<h3 class="text-lg font-bold tracking-tight text-text" id="modal-title">
{$t.tasks?.logs_title}
</h3>
<button
class="p-1.5 rounded-md text-gray-400 bg-transparent border-none cursor-pointer transition-all hover:text-gray-900 hover:bg-gray-100"
class="p-1.5 rounded-md text-text-subtle bg-transparent border-none cursor-pointer transition-all hover:text-text hover:bg-surface-muted"
onclick={() => { show = false; onclose(); }}
aria-label={$t.common?.close}
>
@@ -130,18 +130,18 @@
</div>
<div class="h-[550px] overflow-hidden">
{#if loading && logs.length === 0}
<div class="flex flex-col items-center justify-center h-full space-y-4 text-gray-400">
<div class="w-10 h-10 border-4 border-gray-200 border-t-primary rounded-full animate-spin"></div>
<div class="flex flex-col items-center justify-center h-full space-y-4 text-text-subtle">
<div class="w-10 h-10 border-4 border-border border-t-primary rounded-full animate-spin"></div>
<p class="text-sm font-semibold tracking-wide uppercase">{$t.tasks?.loading}</p>
</div>
{:else if error}
<div class="flex flex-col items-center justify-center h-full p-8 text-center space-y-4">
<div class="w-14 h-14 rounded-full bg-red-50 flex items-center justify-center text-red-500 shadow-inner">
<div class="w-14 h-14 rounded-full bg-destructive-light flex items-center justify-center text-destructive shadow-inner">
<span class="text-3xl"></span>
</div>
<p class="text-red-600 font-semibold text-lg">{error}</p>
<p class="text-destructive font-semibold text-lg">{error}</p>
<button
class="mt-2 rounded-lg border border-gray-200 bg-white px-5 py-2.5 text-sm font-bold text-gray-700 hover:bg-gray-50 hover:border-gray-300 transition-all shadow-sm"
class="mt-2 rounded-lg border border-border bg-surface-card px-5 py-2.5 text-sm font-bold text-text hover:bg-surface-muted hover:border-border-strong transition-all shadow-sm"
onclick={handleRefresh}>
{$t.common?.retry}
</button>

View File

@@ -322,20 +322,20 @@
</script>
<!-- [SECTION: TEMPLATE] -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<div class="bg-surface-card rounded-xl border border-border shadow-sm overflow-hidden">
<!-- Card Header -->
<div class="px-6 py-4 bg-gradient-to-r from-gray-50 to-slate-50 border-b border-gray-100">
<div class="px-6 py-4 bg-gradient-to-r from-gray-50 to-slate-50 border-b border-border">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-gray-100 text-gray-600">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-surface-muted text-text-muted">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9"></path>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
</svg>
</div>
<div>
<h2 class="text-lg font-semibold text-gray-900">{$t.tasks?.task_label}</h2>
<p class="text-sm text-gray-500">Мониторинг выполнения задач</p>
<h2 class="text-lg font-semibold text-text">{$t.tasks?.task_label}</h2>
<p class="text-sm text-text-muted">Мониторинг выполнения задач</p>
</div>
</div>
@@ -343,25 +343,25 @@
<div class="flex items-center gap-2">
{#if connectionStatus === 'connecting'}
<span class="flex h-3 w-3 relative">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-yellow-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-yellow-500"></span>
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-warning opacity-75"></span>
<span class="relative inline-flex rounded-full h-3 w-3 bg-warning"></span>
</span>
<span class="text-xs font-medium text-yellow-600 bg-yellow-50 px-2.5 py-1 rounded-full">{$t.tasks?.connecting}</span>
<span class="text-xs font-medium text-warning bg-warning-light px-2.5 py-1 rounded-full">{$t.tasks?.connecting}</span>
{:else if connectionStatus === 'connected'}
<span class="h-3 w-3 rounded-full bg-green-500"></span>
<span class="text-xs font-medium text-green-600 bg-green-50 px-2.5 py-1 rounded-full">{$t.tasks?.live}</span>
<span class="h-3 w-3 rounded-full bg-success"></span>
<span class="text-xs font-medium text-success bg-success-light px-2.5 py-1 rounded-full">{$t.tasks?.live}</span>
{:else if connectionStatus === 'completed'}
<span class="h-3 w-3 rounded-full bg-blue-500"></span>
<span class="text-xs font-medium text-blue-600 bg-blue-50 px-2.5 py-1 rounded-full">{$t.tasks?.completed}</span>
<span class="h-3 w-3 rounded-full bg-primary-ring"></span>
<span class="text-xs font-medium text-primary bg-primary-light px-2.5 py-1 rounded-full">{$t.tasks?.completed}</span>
{:else if connectionStatus === 'awaiting_mapping'}
<span class="h-3 w-3 rounded-full bg-orange-500 animate-pulse"></span>
<span class="text-xs font-medium text-orange-600 bg-orange-50 px-2.5 py-1 rounded-full">{$t.tasks?.awaiting_mapping}</span>
<span class="h-3 w-3 rounded-full bg-warning-light animate-pulse"></span>
<span class="text-xs font-medium text-warning bg-warning-light px-2.5 py-1 rounded-full">{$t.tasks?.awaiting_mapping}</span>
{:else if connectionStatus === 'awaiting_input'}
<span class="h-3 w-3 rounded-full bg-orange-500 animate-pulse"></span>
<span class="text-xs font-medium text-orange-600 bg-orange-50 px-2.5 py-1 rounded-full">{$t.tasks?.awaiting_input}</span>
<span class="h-3 w-3 rounded-full bg-warning-light animate-pulse"></span>
<span class="text-xs font-medium text-warning bg-warning-light px-2.5 py-1 rounded-full">{$t.tasks?.awaiting_input}</span>
{:else}
<span class="h-3 w-3 rounded-full bg-red-500"></span>
<span class="text-xs font-medium text-red-600 bg-red-50 px-2.5 py-1 rounded-full">{$t.tasks?.disconnected}</span>
<span class="h-3 w-3 rounded-full bg-destructive"></span>
<span class="text-xs font-medium text-destructive bg-destructive-light px-2.5 py-1 rounded-full">{$t.tasks?.disconnected}</span>
{/if}
</div>
{/if}
@@ -372,31 +372,31 @@
<div class="p-6">
{#if $selectedTask}
<!-- Task Info Section -->
<div class="mb-4 bg-gray-50 rounded-lg text-sm border border-gray-200 overflow-hidden">
<div class="mb-4 bg-surface-muted rounded-lg text-sm border border-border overflow-hidden">
<details open>
<summary class="cursor-pointer font-medium text-gray-700 focus:outline-none hover:text-indigo-600 px-4 py-3 hover:bg-gray-100 transition-colors">{$t.tasks?.details_parameters}</summary>
<div class="px-4 pb-4 pt-2 border-t border-gray-200">
<summary class="cursor-pointer font-medium text-text focus:outline-none hover:text-primary px-4 py-3 hover:bg-surface-muted transition-colors">{$t.tasks?.details_parameters}</summary>
<div class="px-4 pb-4 pt-2 border-t border-border">
<div class="grid grid-cols-2 gap-3 mb-3">
<div class="flex items-center gap-2">
<span class="font-semibold text-gray-600">{$t.common?.id}:</span>
<span class="text-gray-900 font-mono text-xs bg-white px-2 py-0.5 rounded border border-gray-200">{$selectedTask.id}</span>
<span class="font-semibold text-text-muted">{$t.common?.id}:</span>
<span class="text-text font-mono text-xs bg-surface-card px-2 py-0.5 rounded border border-border">{$selectedTask.id}</span>
</div>
<div class="flex items-center gap-2">
<span class="font-semibold text-gray-600">{$t.dashboard?.status}:</span>
<span class="text-gray-900">{$selectedTask.status}</span>
<span class="font-semibold text-text-muted">{$t.dashboard?.status}:</span>
<span class="text-text">{$selectedTask.status}</span>
</div>
<div class="flex items-center gap-2">
<span class="font-semibold text-gray-600">{$t.tasks?.started_label}:</span>
<span class="text-gray-900">{new Date($selectedTask.started_at || $selectedTask.created_at || Date.now()).toLocaleString()}</span>
<span class="font-semibold text-text-muted">{$t.tasks?.started_label}:</span>
<span class="text-text">{new Date($selectedTask.started_at || $selectedTask.created_at || Date.now()).toLocaleString()}</span>
</div>
<div class="flex items-center gap-2">
<span class="font-semibold text-gray-600">{$t.tasks?.plugin}:</span>
<span class="text-gray-900">{$selectedTask.plugin_id}</span>
<span class="font-semibold text-text-muted">{$t.tasks?.plugin}:</span>
<span class="text-text">{$selectedTask.plugin_id}</span>
</div>
</div>
<div class="mt-3">
<span class="font-semibold text-gray-600">{$t.tasks?.parameters}:</span>
<pre class="text-xs bg-white p-3 rounded-lg mt-2 overflow-x-auto border border-gray-200 shadow-inner font-mono">{JSON.stringify($selectedTask.params, null, 2)}</pre>
<span class="font-semibold text-text-muted">{$t.tasks?.parameters}:</span>
<pre class="text-xs bg-surface-card p-3 rounded-lg mt-2 overflow-x-auto border border-border shadow-inner font-mono">{JSON.stringify($selectedTask.params, null, 2)}</pre>
</div>
</div>
</details>
@@ -412,7 +412,7 @@
/>
{#if waitingForData && connectionStatus === 'connected'}
<div class="flex items-center gap-2 text-gray-500 italic mt-3 animate-pulse text-xs">
<div class="flex items-center gap-2 text-text-muted italic mt-3 animate-pulse text-xs">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
@@ -424,8 +424,8 @@
{:else}
<!-- Empty State -->
<div class="flex flex-col items-center justify-center py-12 px-6 text-center">
<div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="text-gray-400">
<div class="w-16 h-16 rounded-full bg-surface-muted flex items-center justify-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="text-text-subtle">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
@@ -433,8 +433,8 @@
<polyline points="10 9 9 9 8 9"></polyline>
</svg>
</div>
<h3 class="text-lg font-medium text-gray-900 mb-2">{$t.tasks?.select_task}</h3>
<p class="text-sm text-gray-500 max-w-xs">Запустите задачу маппинга, чтобы увидеть логи и детали выполнения</p>
<h3 class="text-lg font-medium text-text mb-2">{$t.tasks?.select_task}</h3>
<p class="text-sm text-text-muted max-w-xs">Запустите задачу маппинга, чтобы увидеть логи и детали выполнения</p>
</div>
{/if}
</div>

View File

@@ -23,23 +23,23 @@
{#each $toasts.filter(t => t.persistent) as toast (toast.id)}
<div
class="pointer-events-auto mx-4 flex w-full max-w-lg items-start gap-3 rounded-lg border p-4 shadow-2xl
{toast.type === 'warning' ? 'border-amber-300 bg-amber-50 text-amber-900' : ''}
{toast.type === 'error' ? 'border-red-300 bg-red-50 text-red-900' : ''}
{toast.type === 'info' ? 'border-blue-200 bg-blue-50 text-blue-900' : ''}
{toast.type === 'success' ? 'border-green-200 bg-green-50 text-green-900' : ''}
{toast.type === 'warning' ? 'border-warning bg-warning-light text-amber-900' : ''}
{toast.type === 'error' ? 'border-destructive-ring bg-destructive-light text-destructive' : ''}
{toast.type === 'info' ? 'border-primary-ring bg-primary-light text-primary' : ''}
{toast.type === 'success' ? 'border-success bg-success-light text-green-900' : ''}
"
role="alert"
aria-live="assertive"
>
<div class="mt-0.5 flex-shrink-0">
{#if toast.type === 'warning'}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-amber-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-warning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
{:else if toast.type === 'error'}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-destructive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6"/></svg>
{:else if toast.type === 'success'}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-success" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-blue-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01"/></svg>
{/if}
</div>
<div class="flex-1 text-sm">
@@ -49,10 +49,10 @@
type="button"
onclick={() => handleDismiss(toast.id)}
class="flex-shrink-0 rounded p-1 transition-colors
{toast.type === 'warning' ? 'hover:bg-amber-100 text-amber-400' : ''}
{toast.type === 'error' ? 'hover:bg-red-100 text-red-400' : ''}
{toast.type === 'info' ? 'hover:bg-blue-100 text-blue-400' : ''}
{toast.type === 'success' ? 'hover:bg-green-100 text-green-400' : ''}
{toast.type === 'warning' ? 'hover:bg-warning-light text-warning' : ''}
{toast.type === 'error' ? 'hover:bg-destructive-light text-destructive' : ''}
{toast.type === 'info' ? 'hover:bg-primary-light text-primary' : ''}
{toast.type === 'success' ? 'hover:bg-success-light text-success' : ''}
"
aria-label="Закрыть"
>
@@ -66,10 +66,10 @@
<div class="pointer-events-none fixed bottom-0 right-0 z-[60] p-4">
{#each $toasts.filter(t => !t.persistent) as toast (toast.id)}
<div class="pointer-events-auto mb-2 rounded-lg px-4 py-3 text-sm text-white shadow-lg
{toast.type === 'info' && 'bg-blue-600'}
{toast.type === 'success' && 'bg-green-600'}
{toast.type === 'error' && 'bg-red-600'}
{toast.type === 'warning' && 'bg-amber-600'}
{toast.type === 'info' && 'bg-primary'}
{toast.type === 'success' && 'bg-success'}
{toast.type === 'error' && 'bg-destructive'}
{toast.type === 'warning' && 'bg-warning'}
">
{toast.message}
</div>

View File

@@ -136,7 +136,7 @@
{#if $auth.loading || isCheckingAccess}
<div class="flex items-center justify-center min-h-screen">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-ring"></div>
</div>
{:else if $auth.isAuthenticated && hasRouteAccess}
{@render children?.()}

View File

@@ -49,10 +49,10 @@
</script>
<!-- [SECTION: TEMPLATE] -->
<div class="mb-3 flex flex-wrap items-center gap-2 text-sm text-gray-600">
<div class="mb-3 flex flex-wrap items-center gap-2 text-sm text-text-muted">
<button
onclick={() => onNavigate('backups')}
class="hover:text-indigo-600"
class="hover:text-primary"
>
{$t.storage.root}
</button>
@@ -60,7 +60,7 @@
<span>/</span>
<button
onclick={() => navigateToPathIndex(index + 1)}
class="hover:text-indigo-600"
class="hover:text-primary"
>
{part}
</button>
@@ -78,32 +78,32 @@
</Button>
</div>
<div class="overflow-x-auto rounded-lg border border-gray-200">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<div class="overflow-x-auto rounded-lg border border-border">
<table class="min-w-full divide-y divide-border">
<thead class="bg-surface-muted">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
{$t.storage.table.name}
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
{$t.tasks.target_env}
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">
{$t.storage.table.created_at}
</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="px-6 py-3 text-right text-xs font-medium text-text-muted uppercase tracking-wider">
{$t.storage.table.actions}
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<tbody class="bg-surface-card divide-y divide-border">
{#each backups as backup}
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<tr class="hover:bg-surface-muted transition-colors">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
{#if isDirectory(backup)}
<button
onclick={() => onNavigate(backup.path)}
class="text-indigo-600 hover:text-indigo-900"
class="text-primary hover:text-primary"
>
{backup.name}
</button>
@@ -111,10 +111,10 @@
{backup.name}
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
{backup.environment}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
{formatCreatedAt(backup)}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
@@ -122,7 +122,7 @@
<Button
variant="ghost"
size="sm"
class="text-primary hover:text-blue-900"
class="text-primary hover:text-primary"
onclick={() => onNavigate(backup.path)}
>
{$t.storage.table.go_to_storage}
@@ -132,7 +132,7 @@
</tr>
{:else}
<tr>
<td colspan="4" class="px-6 py-10 text-center text-gray-500">
<td colspan="4" class="px-6 py-10 text-center text-text-muted">
{$t.storage.no_files}
</td>
</tr>

View File

@@ -216,22 +216,22 @@
</div>
{#if selectedEnvId}
<div class="pt-6 border-t border-gray-100 mt-4">
<h3 class="text-sm font-semibold text-gray-800 mb-4 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div class="pt-6 border-t border-border mt-4">
<h3 class="text-sm font-semibold text-text mb-4 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{$t.tasks.backup_schedule}
</h3>
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
<div class="bg-surface-muted rounded-lg p-4 border border-border">
<div class="flex flex-col md:flex-row md:items-start gap-6">
<div class="pt-8">
<label class="flex items-center gap-3 cursor-pointer group">
<div class="relative inline-flex items-center">
<input type="checkbox" bind:checked={scheduleEnabled} class="sr-only peer" />
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-ring 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-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
<span class="ml-3 text-sm font-medium text-gray-700 group-hover:text-gray-900 transition-colors">{$t.tasks.schedule_enabled}</span>
<div class="w-11 h-6 bg-surface-muted peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-ring 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-surface-card after:border-border-strong after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
<span class="ml-3 text-sm font-medium text-text group-hover:text-text transition-colors">{$t.tasks.schedule_enabled}</span>
</div>
</label>
</div>
@@ -243,7 +243,7 @@
bind:value={cronExpression}
disabled={!scheduleEnabled}
/>
<p class="text-xs text-gray-500 italic">{$t.tasks.cron_hint}</p>
<p class="text-xs text-text-muted italic">{$t.tasks.cron_hint}</p>
</div>
<div class="pt-8">
@@ -274,9 +274,9 @@
</Card>
<div class="space-y-3">
<h2 class="text-lg font-semibold text-gray-700">{$t.storage.backups}</h2>
<h2 class="text-lg font-semibold text-text">{$t.storage.backups}</h2>
{#if loading}
<div class="py-10 text-center text-gray-500">{$t.common.loading}</div>
<div class="py-10 text-center text-text-muted">{$t.common.loading}</div>
{:else}
<BackupList
{backups}

View File

@@ -69,20 +69,20 @@
</div>
{#if model.branchError}
<div class="max-h-32 overflow-y-auto rounded-lg border border-red-300 bg-red-50 p-3 text-sm text-red-800" role="alert">
<div class="max-h-32 overflow-y-auto rounded-lg border border-destructive-ring bg-destructive-light p-3 text-sm text-destructive" role="alert">
<div class="flex items-start gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="mt-0.5 h-4 w-4 flex-shrink-0 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="mt-0.5 h-4 w-4 flex-shrink-0 text-destructive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6"/></svg>
<div class="flex-1 min-w-0">
<p class="truncate">{model.branchError.message}</p>
{#if model.branchError.next_steps?.length}
<ul class="mt-1 list-disc pl-4 text-xs text-red-700">
<ul class="mt-1 list-disc pl-4 text-xs text-destructive">
{#each model.branchError.next_steps as step}
<li>{step}</li>
{/each}
</ul>
{/if}
</div>
<button type="button" onclick={() => model.branchError = null} class="flex-shrink-0 rounded p-0.5 hover:bg-red-100" aria-label="Закрыть">
<button type="button" onclick={() => model.branchError = null} class="flex-shrink-0 rounded p-0.5 hover:bg-destructive-light" aria-label="Закрыть">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
</button>
</div>
@@ -90,7 +90,7 @@
{/if}
{#if model.showCreate}
<div class="flex items-end gap-2 bg-gray-50 p-3 rounded-lg border border-dashed border-gray-200">
<div class="flex items-end gap-2 bg-surface-muted p-3 rounded-lg border border-dashed border-border">
<div class="flex-grow">
<Input
bind:value={model.newBranchName}
@@ -104,7 +104,7 @@
onclick={() => model.handleCreate()}
disabled={model.loading || !model.newBranchName}
isLoading={model.loading}
class="bg-green-600 hover:bg-green-700"
class="bg-success hover:bg-success"
>
{$t.git.create}
</Button>

View File

@@ -66,7 +66,7 @@
<!-- [SECTION: TEMPLATE] -->
<div class="mt-2">
<div class="flex justify-between items-center mb-6">
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider">
<h3 class="text-sm font-semibold text-text-subtle uppercase tracking-wider">
{$t.git.history}
</h3>
<Button variant="ghost" size="sm" onclick={loadHistory} class="text-primary">
@@ -76,19 +76,19 @@
{#if loading}
<div class="flex items-center justify-center py-12">
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-primary-ring"></div>
</div>
{:else if history.length === 0}
<p class="text-gray-500 italic text-center py-12">{$t.git.no_commits}</p>
<p class="text-text-muted italic text-center py-12">{$t.git.no_commits}</p>
{:else}
<div class="space-y-3 max-h-96 overflow-y-auto pr-2">
{#each history as commit}
<div class="border-l-2 border-blue-500 pl-4 py-1">
<div class="border-l-2 border-primary-ring pl-4 py-1">
<div class="flex justify-between items-start">
<span class="font-medium text-sm">{commit.message}</span>
<span class="text-xs text-gray-400 font-mono">{commit.hash.substring(0, 7)}</span>
<span class="text-xs text-text-subtle font-mono">{commit.hash.substring(0, 7)}</span>
</div>
<div class="text-xs text-gray-500 mt-1">
<div class="text-xs text-text-muted mt-1">
{commit.author}{new Date(commit.timestamp).toLocaleString()}
</div>
</div>

View File

@@ -39,7 +39,7 @@
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
>
<div
class="bg-white p-6 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col"
class="bg-surface-card p-6 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col"
>
<h2 class="text-xl font-bold mb-4">{$t.git?.commit}</h2>
@@ -48,13 +48,13 @@
<div class="w-full md:w-1/3 flex flex-col">
<div class="mb-4">
<div class="flex justify-between items-center mb-1">
<label class="block text-sm font-medium text-gray-700"
<label class="block text-sm font-medium text-text"
>{$t.git?.commit_message}</label
>
<button
onclick={() => model.handleGenerateMessage()}
disabled={model.generatingMessage || model.loading}
class="text-xs text-primary hover:text-blue-800 disabled:opacity-50 flex items-center"
class="text-xs text-primary hover:text-primary disabled:opacity-50 flex items-center"
>
{#if model.generatingMessage}
<span class="animate-spin mr-1"></span> {$t.mapper?.generating}
@@ -72,13 +72,13 @@
{#if model.status}
<div class="flex-1 overflow-y-auto">
<h3 class="text-sm font-bold text-gray-500 uppercase mb-2">
<h3 class="text-sm font-bold text-text-muted uppercase mb-2">
{$t.git?.changed_files}
</h3>
<ul class="text-xs space-y-1">
{#each model.status.staged_files as file}
<li
class="text-green-600 flex items-center font-semibold"
class="text-success flex items-center font-semibold"
title={$t.git?.staged}
>
<span class="mr-2">S</span>
@@ -87,7 +87,7 @@
{/each}
{#each model.status.modified_files as file}
<li
class="text-yellow-600 flex items-center"
class="text-warning flex items-center"
title={$t.git?.modified_unstaged}
>
<span class="mr-2">M</span>
@@ -110,20 +110,20 @@
<!-- Right: Diff Viewer -->
<div
class="w-full md:w-2/3 flex flex-col overflow-hidden border rounded bg-gray-50"
class="w-full md:w-2/3 flex flex-col overflow-hidden border rounded bg-surface-muted"
>
<div class="bg-gray-200 px-3 py-1 text-xs font-bold text-gray-600 border-b">
<div class="bg-surface-muted px-3 py-1 text-xs font-bold text-text-muted border-b">
{$t.git?.changes_preview}
</div>
<div class="flex-1 overflow-auto p-2">
{#if model.loading}
<div class="flex items-center justify-center h-full text-gray-500">
<div class="flex items-center justify-center h-full text-text-muted">
{$t.git?.loading_diff}
</div>
{:else if model.diff}
<pre class="text-xs font-mono whitespace-pre-wrap">{model.diff}</pre>
{:else}
<div class="flex items-center justify-center h-full text-gray-500 italic">
<div class="flex items-center justify-center h-full text-text-muted italic">
{$t.git?.no_changes}
</div>
{/if}
@@ -134,7 +134,7 @@
<div class="flex justify-end space-x-3 mt-6 pt-4 border-t">
<button
onclick={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
class="px-4 py-2 text-text-muted hover:bg-surface-muted rounded"
>
{$t.common?.cancel}
</button>

View File

@@ -82,12 +82,12 @@
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
>
<div
class="bg-white p-6 rounded-lg shadow-xl w-full max-w-5xl max-h-[90vh] flex flex-col"
class="bg-surface-card p-6 rounded-lg shadow-xl w-full max-w-5xl max-h-[90vh] flex flex-col"
>
<h2 class="text-xl font-bold mb-4 text-red-600">
<h2 class="text-xl font-bold mb-4 text-destructive">
Merge Conflicts Detected
</h2>
<p class="text-gray-600 mb-4">
<p class="text-text-muted mb-4">
The following files have conflicts. Please choose how to resolve
them.
</p>
@@ -96,12 +96,12 @@
{#each conflicts as conflict}
<div class="border rounded-lg overflow-hidden">
<div
class="bg-gray-100 px-4 py-2 font-medium border-b flex justify-between items-center"
class="bg-surface-muted px-4 py-2 font-medium border-b flex justify-between items-center"
>
<span>{conflict.file_path}</span>
{#if resolutions[conflict.file_path]}
<span
class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full uppercase font-bold"
class="text-xs bg-primary-light text-primary px-2 py-0.5 rounded-full uppercase font-bold"
>
Resolved: {resolutions[conflict.file_path]}
</span>
@@ -116,7 +116,7 @@
>
Your Changes (Mine)
</div>
<div class="p-4 bg-white flex-1 overflow-auto">
<div class="p-4 bg-surface-card flex-1 overflow-auto">
<pre
class="text-xs font-mono whitespace-pre">{conflict.mine}</pre>
</div>
@@ -125,7 +125,7 @@
conflict.file_path
] === 'mine'
? 'bg-primary text-white'
: 'bg-gray-50 hover:bg-blue-50 text-primary'}"
: 'bg-surface-muted hover:bg-primary-light text-primary'}"
onclick={() =>
resolve(conflict.file_path, "mine")}
>
@@ -134,11 +134,11 @@
</div>
<div class="p-0 flex flex-col">
<div
class="bg-green-50 px-4 py-1 text-[10px] font-bold text-green-600 uppercase border-b"
class="bg-success-light px-4 py-1 text-[10px] font-bold text-success uppercase border-b"
>
Remote Changes (Theirs)
</div>
<div class="p-4 bg-white flex-1 overflow-auto">
<div class="p-4 bg-surface-card flex-1 overflow-auto">
<pre
class="text-xs font-mono whitespace-pre">{conflict.theirs}</pre>
</div>
@@ -146,8 +146,8 @@
class="w-full py-2 text-sm font-medium border-t transition-colors {resolutions[
conflict.file_path
] === 'theirs'
? 'bg-green-600 text-white'
: 'bg-gray-50 hover:bg-green-50 text-green-600'}"
? 'bg-success text-white'
: 'bg-surface-muted hover:bg-success-light text-success'}"
onclick={() =>
resolve(conflict.file_path, "theirs")}
>
@@ -162,7 +162,7 @@
<div class="flex justify-end space-x-3 pt-4 border-t">
<button
onclick={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded transition-colors"
class="px-4 py-2 text-text-muted hover:bg-surface-muted rounded transition-colors"
>
Cancel
</button>

View File

@@ -43,37 +43,37 @@
{#if show}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white p-6 rounded-lg shadow-xl w-96">
<div class="bg-surface-card p-6 rounded-lg shadow-xl w-96">
<h2 class="text-xl font-bold mb-4">{$t.git?.deploy}</h2>
{#if model.loading}
<p class="text-gray-500">{$t.migration?.loading_envs}</p>
<p class="text-text-muted">{$t.migration?.loading_envs}</p>
{:else if model.deploymentCandidates.length === 0}
<p class="text-red-500 mb-4">
<p class="text-destructive mb-4">
{$t.git?.no_deploy_envs}
</p>
<div class="flex justify-end">
<button
onclick={() => (show = false)}
class="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
class="px-4 py-2 bg-surface-muted text-text rounded hover:bg-surface-muted"
>
{$t.common?.close}
</button>
</div>
{:else}
{#if model.normalizedPreferredStage}
<p class="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
<p class="mb-3 rounded border border-warning bg-warning-light px-3 py-2 text-xs text-warning">
GitFlow target stage: {model.normalizedPreferredStage}
</p>
{/if}
<div class="mb-6">
<label for="deployment-target-env" class="block text-sm font-medium text-gray-700 mb-2"
<label for="deployment-target-env" class="block text-sm font-medium text-text mb-2"
>{$t.migration?.target_env}</label
>
<select
id="deployment-target-env"
bind:value={model.selectedEnv}
class="w-full border rounded p-2 focus-visible:ring-2 focus-visible:ring-primary-ring outline-none bg-white"
class="w-full border rounded p-2 focus-visible:ring-2 focus-visible:ring-primary-ring outline-none bg-surface-card"
>
{#each model.deploymentCandidates as env}
<option value={env.id}
@@ -83,7 +83,7 @@
</select>
</div>
{#if model.isProdTarget}
<p class="mb-3 rounded border border-destructive-light bg-destructive-light px-3 py-2 text-xs text-red-800">
<p class="mb-3 rounded border border-destructive-light bg-destructive-light px-3 py-2 text-xs text-destructive">
Внимание: выбрано PROD окружение. Потребуется подтверждение перед deploy.
</p>
{/if}
@@ -91,14 +91,14 @@
<div class="flex justify-end space-x-3">
<button
onclick={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
class="px-4 py-2 text-text-muted hover:bg-surface-muted rounded"
>
{$t.common?.cancel}
</button>
<button
onclick={() => model.handleDeploy()}
disabled={model.deploying || !model.selectedEnv}
class={`px-4 py-2 text-white rounded disabled:opacity-50 flex items-center ${model.isProdTarget ? 'bg-destructive hover:bg-destructive-hover' : 'bg-green-600 hover:bg-green-700'}`}
class={`px-4 py-2 text-white rounded disabled:opacity-50 flex items-center ${model.isProdTarget ? 'bg-destructive hover:bg-destructive-hover' : 'bg-success hover:bg-success'}`}
>
{#if model.deploying}
<svg

View File

@@ -22,7 +22,7 @@
<div class="mx-auto max-w-md py-8">
<Card>
<p class="mb-6 text-sm text-gray-600">
<p class="mb-6 text-sm text-text-muted">
{$t.git?.not_linked || 'Этот дашборд еще не привязан к Git-репозиторию.'}
</p>
@@ -33,7 +33,7 @@
options={configs.map(c => ({ value: c.id, label: `${c.name} (${c.provider})` }))}
/>
{#if configs.length === 0}
<p class="-mt-4 text-xs text-red-500">{$t.git?.no_servers_configured || 'Нет сконфигурированных Git серверов'}</p>
<p class="-mt-4 text-xs text-destructive">{$t.git?.no_servers_configured || 'Нет сконфигурированных Git серверов'}</p>
{/if}
<Input

View File

@@ -59,19 +59,19 @@
{#if show}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/60 pt-6 backdrop-blur-sm" onclick={handleBackdropClick} onkeydown={(e) => { if (e.key === 'Escape') closeModal(); }} role="dialog" tabindex="-1" aria-label={`${$t.git?.management || 'Управление Git'}: ${dashboardTitle}`}>
<div class="relative w-[95vw] max-w-[1600px] overflow-hidden rounded-xl bg-white shadow-2xl" role="document" aria-label={`${$t.git?.management || 'Управление Git'}: ${dashboardTitle}`}>
<div class="relative w-[95vw] max-w-[1600px] overflow-hidden rounded-xl bg-surface-card shadow-2xl" role="document" aria-label={`${$t.git?.management || 'Управление Git'}: ${dashboardTitle}`}>
<!-- Header -->
<div class="flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-6 py-4">
<div class="flex items-center justify-between border-b border-border bg-gradient-to-r from-slate-50 to-white px-6 py-4">
<div class="flex items-center gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-600 text-white shadow-sm">
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-primary text-white shadow-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>
</div>
<div>
<h2 class="text-lg font-bold text-slate-900">{$t.git?.management || 'Управление Git'}</h2>
<p class="text-sm text-slate-500">{dashboardTitle} <span class="text-slate-300">·</span> slug: {dashboardId}</p>
<h2 class="text-lg font-bold text-text">{$t.git?.management || 'Управление Git'}</h2>
<p class="text-sm text-text-muted">{dashboardTitle} <span class="text-text-subtle">·</span> slug: {dashboardId}</p>
</div>
</div>
<button type="button" onclick={closeModal} class="flex h-9 w-9 items-center justify-center rounded-lg text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-700" aria-label={$t.common?.close || 'Close'}>
<button type="button" onclick={closeModal} class="flex h-9 w-9 items-center justify-center rounded-lg text-text-subtle transition-colors hover:bg-surface-muted hover:text-text" aria-label={$t.common?.close || 'Close'}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
@@ -79,25 +79,25 @@
<!-- Git Error Banner -->
{#if model.gitError}
<div
class="mx-6 mt-4 flex items-start gap-3 rounded-lg border p-4 text-sm shadow-sm {model.gitErrorType === 'warning' ? 'border-amber-300 bg-amber-50 text-amber-900' : 'border-red-300 bg-red-50 text-red-900'}"
class="mx-6 mt-4 flex items-start gap-3 rounded-lg border p-4 text-sm shadow-sm {model.gitErrorType === 'warning' ? 'border-warning bg-warning-light text-amber-900' : 'border-destructive-ring bg-destructive-light text-destructive'}"
role="alert"
aria-live="polite"
>
<div class="mt-0.5 flex-shrink-0">
{#if model.gitErrorType === 'warning'}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-amber-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-warning" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
{:else}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-destructive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6"/></svg>
{/if}
</div>
<div class="flex-1 space-y-2">
<p class="font-medium">{model.gitError.message || model.gitError}</p>
{#if model.gitError.files?.length}
<details class="text-xs text-red-700/80">
<details class="text-xs text-destructive/80">
<summary class="cursor-pointer font-medium">Файлы, которые будут перезаписаны ({model.gitError.files.length})</summary>
<ul class="mt-1 list-disc space-y-0.5 pl-5">
{#each model.gitError.files as file}
<li><code class="rounded bg-red-100/50 px-1">{file}</code></li>
<li><code class="rounded bg-destructive-light/50 px-1">{file}</code></li>
{/each}
</ul>
</details>
@@ -116,7 +116,7 @@
<button
type="button"
onclick={() => model.clearGitError()}
class="flex-shrink-0 rounded p-1 transition-colors {model.gitErrorType === 'warning' ? 'hover:bg-amber-100' : 'hover:bg-red-100'}"
class="flex-shrink-0 rounded p-1 transition-colors {model.gitErrorType === 'warning' ? 'hover:bg-warning-light' : 'hover:bg-destructive-light'}"
aria-label="Закрыть"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
@@ -128,27 +128,27 @@
<div class="flex flex-col p-6" style="height: calc(92vh - 80px);">
{#if model.checkingStatus}
<div class="flex justify-center py-12"><div class="h-8 w-8 animate-spin rounded-full border-b-2 border-blue-600"></div></div>
<div class="flex justify-center py-12"><div class="h-8 w-8 animate-spin rounded-full border-b-2 border-primary-ring"></div></div>
{:else if !model.initialized}
<GitInitPanel configs={model.configs} bind:selectedConfigId={model.selectedConfigId} bind:remoteUrl={model.remoteUrl} creatingRemoteRepo={model.creatingRemoteRepo} loading={model.loading} onCreateRemoteRepo={() => model.handleCreateRemoteRepo()} onInit={() => model.handleInit()} />
{:else}
<div class="flex min-h-0 flex-1 flex-col gap-4">
{#if model.hasOriginConfigMismatch}
<div class="rounded-lg border border-amber-300 bg-amber-50 p-3 text-sm text-amber-800"><div class="font-semibold">Git server mismatch detected</div><div class="mt-1">Configured: <code>{model.configHost}</code>, origin: <code>{model.originHost}</code>.</div></div>
<div class="rounded-lg border border-warning bg-warning-light p-3 text-sm text-warning"><div class="font-semibold">Git server mismatch detected</div><div class="mt-1">Configured: <code>{model.configHost}</code>, origin: <code>{model.originHost}</code>.</div></div>
{/if}
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-slate-200 bg-white px-4 py-3 shadow-sm">
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border bg-surface-card px-4 py-3 shadow-sm">
<div class="flex items-center gap-3">
<span class={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-bold uppercase tracking-wider ${stageBadgeClass(model.currentEnvStage)}`}>
<span class="h-1.5 w-1.5 rounded-full bg-current opacity-60"></span>
{model.currentEnvStage || 'DEV'}
</span>
<span class="hidden text-xs text-slate-500 sm:inline">·</span>
<span class="flex items-center gap-1.5 text-sm text-slate-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>
<span class="hidden sm:inline">Ветка:</span> <strong class="font-mono text-slate-800">{model.currentBranch}</strong>
<span class="hidden text-xs text-text-muted sm:inline">·</span>
<span class="flex items-center gap-1.5 text-sm text-text-muted">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-text-subtle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>
<span class="hidden sm:inline">Ветка:</span> <strong class="font-mono text-text">{model.currentBranch}</strong>
</span>
{#if model.changedFilesCount > 0}
<span class="inline-flex items-center gap-1 rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-700 ring-1 ring-inset ring-amber-200">
<span class="inline-flex items-center gap-1 rounded-full bg-warning-light px-2.5 py-0.5 text-xs font-medium text-warning ring-1 ring-inset ring-warning-ring">
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{model.changedFilesCount} изменений
</span>
@@ -156,18 +156,18 @@
</div>
<div class="flex w-full items-center gap-1 sm:w-64"><BranchSelector {dashboardId} envId={model.resolvedEnvId} bind:currentBranch={model.currentBranch} /><HelpTooltip text={$t.git?.hint_branch_selector || ''} /></div>
</div>
<div class="flex flex-wrap items-center gap-1 border-b border-slate-200 pb-0">
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'workspace' ? 'border border-b-white bg-white text-blue-700 shadow-sm' : 'border border-transparent text-slate-500 hover:bg-slate-100 hover:text-slate-700'}`} onclick={() => (model.activeTab = 'workspace')}>
<div class="flex flex-wrap items-center gap-1 border-b border-border pb-0">
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'workspace' ? 'border border-b-white bg-surface-card text-primary shadow-sm' : 'border border-transparent text-text-muted hover:bg-surface-muted hover:text-text'}`} onclick={() => (model.activeTab = 'workspace')}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
Фиксация изменений
<HelpTooltip text={$t.git?.hint_workspace || ''} />
</button>
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'release' ? 'border border-b-white bg-white text-blue-700 shadow-sm' : 'border border-transparent text-slate-500 hover:bg-slate-100 hover:text-slate-700'}`} onclick={() => (model.activeTab = 'release')}>
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'release' ? 'border border-b-white bg-surface-card text-primary shadow-sm' : 'border border-transparent text-text-muted hover:bg-surface-muted hover:text-text'}`} onclick={() => (model.activeTab = 'release')}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
Релиз
<HelpTooltip text={$t.git?.hint_release || ''} />
</button>
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'operations' ? 'border border-b-white bg-white text-blue-700 shadow-sm' : 'border border-transparent text-slate-500 hover:bg-slate-100 hover:text-slate-700'}`} onclick={() => (model.activeTab = 'operations')}>
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'operations' ? 'border border-b-white bg-surface-card text-primary shadow-sm' : 'border border-transparent text-text-muted hover:bg-surface-muted hover:text-text'}`} onclick={() => (model.activeTab = 'operations')}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
Серверные операции
<HelpTooltip text={$t.git?.hint_operations || ''} />

View File

@@ -33,37 +33,37 @@
aria-modal="true"
aria-label={$t.git?.unfinished_merge?.title || 'Repository has an unfinished merge'}
>
<div class="max-h-[85vh] w-full max-w-2xl overflow-y-auto rounded-lg border border-amber-300 bg-white p-5 shadow-2xl">
<div class="max-h-[85vh] w-full max-w-2xl overflow-y-auto rounded-lg border border-warning bg-surface-card p-5 shadow-2xl">
<div class="mb-3 text-lg font-semibold text-amber-900">
{$t.git?.unfinished_merge?.title || 'Repository has an unfinished merge'}
</div>
<p class="mb-4 text-sm text-slate-700">
<p class="mb-4 text-sm text-text">
{unfinishedMergeContext.message || ($t.git?.unfinished_merge?.default_message || 'An unfinished merge was detected in this repository.')}
</p>
<div class="grid grid-cols-1 gap-2 text-sm md:grid-cols-2">
<div class="rounded border border-slate-200 bg-slate-50 p-2">
<div class="text-xs text-slate-500">{$t.git?.unfinished_merge?.repository_path || 'Repository path'}</div>
<div class="break-all font-mono text-slate-800">{unfinishedMergeContext.repositoryPath || '—'}</div>
<div class="rounded border border-border bg-surface-page p-2">
<div class="text-xs text-text-muted">{$t.git?.unfinished_merge?.repository_path || 'Repository path'}</div>
<div class="break-all font-mono text-text">{unfinishedMergeContext.repositoryPath || '—'}</div>
</div>
<div class="rounded border border-slate-200 bg-slate-50 p-2">
<div class="text-xs text-slate-500">{$t.git?.unfinished_merge?.branch || 'Current branch'}</div>
<div class="font-mono text-slate-800">{unfinishedMergeContext.currentBranch || '—'}</div>
<div class="rounded border border-border bg-surface-page p-2">
<div class="text-xs text-text-muted">{$t.git?.unfinished_merge?.branch || 'Current branch'}</div>
<div class="font-mono text-text">{unfinishedMergeContext.currentBranch || '—'}</div>
</div>
{#if unfinishedMergeContext.mergeHead}
<div class="rounded border border-slate-200 bg-slate-50 p-2 md:col-span-2">
<div class="text-xs text-slate-500">MERGE_HEAD</div>
<div class="break-all font-mono text-slate-800">{unfinishedMergeContext.mergeHead}</div>
<div class="rounded border border-border bg-surface-page p-2 md:col-span-2">
<div class="text-xs text-text-muted">MERGE_HEAD</div>
<div class="break-all font-mono text-text">{unfinishedMergeContext.mergeHead}</div>
</div>
{/if}
</div>
{#if unfinishedMergeContext.nextSteps?.length}
<div class="mt-4">
<div class="mb-1 text-sm font-semibold text-slate-700">
<div class="mb-1 text-sm font-semibold text-text">
{$t.git?.unfinished_merge?.next_steps || 'Recommended steps'}
</div>
<ol class="list-decimal space-y-1 pl-5 text-sm text-slate-700">
<ol class="list-decimal space-y-1 pl-5 text-sm text-text">
{#each unfinishedMergeContext.nextSteps as step}
<li>{step}</li>
{/each}
@@ -73,10 +73,10 @@
{#if unfinishedMergeContext.commands?.length}
<div class="mt-4">
<div class="mb-1 text-sm font-semibold text-slate-700">
<div class="mb-1 text-sm font-semibold text-text">
{$t.git?.unfinished_merge?.manual_commands || 'Manual recovery commands'}
</div>
<pre class="overflow-auto rounded border border-slate-200 bg-slate-50 p-3 text-xs text-slate-800">{unfinishedMergeContext.commands.join('\n')}</pre>
<pre class="overflow-auto rounded border border-border bg-surface-page p-3 text-xs text-text">{unfinishedMergeContext.commands.join('\n')}</pre>
</div>
{/if}
@@ -110,7 +110,7 @@
onclick={onAbortMerge}
disabled={mergeAbortInProgress}
isLoading={mergeAbortInProgress}
class="border border-destructive-ring text-red-700 hover:bg-red-50"
class="border border-destructive-ring text-destructive hover:bg-destructive-light"
>
{$t.git?.unfinished_merge?.abort_merge || 'Abort merge'}
</Button>

View File

@@ -22,7 +22,7 @@
onclick={onPull}
disabled={isPulling}
isLoading={isPulling}
class="border border-gray-200"
class="border border-border"
>
⬇ Pull
</Button>
@@ -31,14 +31,14 @@
onclick={onPush}
disabled={isPushing}
isLoading={isPushing}
class="border border-gray-200"
class="border border-border"
>
⬆ Push{#if workspaceStatus?.ahead_count > 0} ({workspaceStatus.ahead_count}){/if}
</Button>
<Button
variant="primary"
onclick={onDeploy}
class={`w-full ${currentEnvStage === 'PROD' ? 'bg-destructive hover:bg-destructive-hover focus-visible:ring-red-500' : 'bg-green-600 hover:bg-green-700 focus-visible:ring-green-500'}`}
class={`w-full ${currentEnvStage === 'PROD' ? 'bg-destructive hover:bg-destructive-hover focus-visible:ring-destructive-ring' : 'bg-success hover:bg-success focus-visible:ring-success-ring'}`}
>
🚀 Deploy
</Button>

View File

@@ -24,17 +24,17 @@
</script>
<div class="space-y-4">
<div class="rounded-lg border border-slate-200 bg-slate-50 p-4">
<div class="mb-3 text-sm font-semibold text-slate-700">Текущий статус пайплайна</div>
<div class="rounded-lg border border-border bg-surface-page p-4">
<div class="mb-3 text-sm font-semibold text-text">Текущий статус пайплайна</div>
<div class="flex flex-wrap items-center gap-2 text-sm">
<span class={`rounded-full border px-3 py-1 font-semibold ${stageBadgeClass('DEV')}`}>DEV (dev)</span>
<span class="text-slate-400"></span>
<span class="text-text-subtle"></span>
<span class={`rounded-full border px-3 py-1 font-semibold ${stageBadgeClass('PREPROD')}`}>PREPROD (preprod)</span>
<span class="text-slate-400"></span>
<span class="text-text-subtle"></span>
<span class={`rounded-full border px-3 py-1 font-semibold ${stageBadgeClass('PROD')}`}>PROD (main)</span>
</div>
{#if preferredDeployTargetStage}
<p class="mt-2 text-xs text-slate-600">
<p class="mt-2 text-xs text-text-muted">
Следующий шаг по GitFlow: <strong>{promoteFromBranch}{promoteToBranch}</strong>
</p>
{/if}
@@ -44,7 +44,7 @@
onclick={onPromote}
disabled={promoting || !promoteFromBranch || !promoteToBranch || promoteFromBranch === promoteToBranch}
isLoading={promoting}
class={`w-full ${promoteMode === 'direct' ? 'bg-destructive hover:bg-destructive-hover focus-visible:ring-red-500' : ''}`}
class={`w-full ${promoteMode === 'direct' ? 'bg-destructive hover:bg-destructive-hover focus-visible:ring-destructive-ring' : ''}`}
>
{promoteMode === 'direct'
? `Прямой перенос ${promoteFromBranch} ${promoteToBranch} (unsafe)`
@@ -52,14 +52,14 @@
</Button>
<button
class="text-sm text-slate-600 hover:text-slate-900"
class="text-sm text-text-muted hover:text-text"
onclick={() => (showAdvancedPromote = !showAdvancedPromote)}
>
{showAdvancedPromote ? '▴ Скрыть advanced settings' : '▾ Advanced settings'}
</button>
{#if showAdvancedPromote}
<div class="space-y-3 rounded-lg border border-slate-200 p-3">
<div class="space-y-3 rounded-lg border border-border p-3">
<div class="grid grid-cols-1 gap-2 md:grid-cols-2">
<Input
label="From branch"
@@ -81,7 +81,7 @@
]}
/>
{#if promoteMode === 'direct'}
<div class="rounded-lg border border-destructive-ring bg-destructive-light p-3 text-sm text-red-800">
<div class="rounded-lg border border-destructive-ring bg-destructive-light p-3 text-sm text-destructive">
<div class="font-semibold">Внимание: прямой перенос без MR</div>
<div class="mt-1">Это обходит процесс аппрува и записывается в audit лог.</div>
</div>

View File

@@ -110,7 +110,7 @@
highlight: true,
});
} catch (_e) {
return '<div class="p-4 text-sm text-red-500">Failed to render diff</div>';
return '<div class="p-4 text-sm text-destructive">Failed to render diff</div>';
}
});
@@ -122,7 +122,7 @@
<!-- Left sidebar: commit controls (sticky, no scroll) -->
<div class="w-full shrink-0 space-y-4 lg:w-80 xl:w-96">
<!-- Sync button -->
<div class="rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<div class="rounded-lg border border-border bg-surface-card p-4 shadow-sm">
<Button
variant="secondary"
onclick={onSync}
@@ -136,11 +136,11 @@
</div>
<!-- Commit message -->
<div class="rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<div class="rounded-lg border border-border bg-surface-card p-4 shadow-sm">
<div class="mb-3 flex items-center justify-between">
<h3 class="text-sm font-semibold text-slate-700">Сообщение коммита</h3>
<h3 class="text-sm font-semibold text-text">Сообщение коммита</h3>
<button
class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-50 disabled:opacity-50"
class="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-primary transition-colors hover:bg-primary-light disabled:opacity-50"
onclick={onGenerateMessage}
disabled={generatingMessage || workspaceLoading}
>
@@ -150,16 +150,16 @@
</div>
<textarea
bind:value={commitMessage}
class={`h-32 w-full resize-none rounded-lg border border-slate-200 p-3 text-sm outline-none transition-colors focus:border-blue-400 focus:ring-2 focus:ring-blue-100 ${generatingMessage ? 'animate-pulse bg-slate-50' : 'bg-white'}`}
class={`h-32 w-full resize-none rounded-lg border border-border p-3 text-sm outline-none transition-colors focus:border-primary-ring focus:ring-2 focus:ring-primary-ring ${generatingMessage ? 'animate-pulse bg-surface-page' : 'bg-surface-card'}`}
placeholder="Опишите изменения..."
></textarea>
</div>
<!-- Commit action -->
<div class="rounded-lg border border-slate-200 bg-white p-4 shadow-sm">
<div class="mb-3 flex items-center gap-2 rounded-lg bg-slate-50 px-3 py-2 text-sm text-slate-600">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-slate-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Файлов с изменениями: <strong class="text-slate-800">{changedFilesCount}</strong>
<div class="rounded-lg border border-border bg-surface-card p-4 shadow-sm">
<div class="mb-3 flex items-center gap-2 rounded-lg bg-surface-page px-3 py-2 text-sm text-text-muted">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-text-subtle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
Файлов с изменениями: <strong class="text-text">{changedFilesCount}</strong>
</div>
<Button
@@ -172,38 +172,38 @@
Зафиксировать (Commit)
</Button>
<label class="mt-3 flex items-center gap-2.5 rounded-md border border-slate-100 bg-slate-50 px-3 py-2 text-xs text-slate-600 transition-colors hover:bg-slate-100">
<input type="checkbox" bind:checked={autoPushAfterCommit} class="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" />
<span>{$t.git?.auto_push_after_commit || 'Сделать push после commit в'} <strong class="font-medium text-slate-700">{pushProviderLabel}</strong></span>
<label class="mt-3 flex items-center gap-2.5 rounded-md border border-border bg-surface-page px-3 py-2 text-xs text-text-muted transition-colors hover:bg-surface-muted">
<input type="checkbox" bind:checked={autoPushAfterCommit} class="h-4 w-4 rounded border-border-strong text-primary focus:ring-primary-ring" />
<span>{$t.git?.auto_push_after_commit || 'Сделать push после commit в'} <strong class="font-medium text-text">{pushProviderLabel}</strong></span>
</label>
</div>
</div>
<!-- Right area: diff preview — lazy chunked rendering -->
<div class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm">
<div class="flex items-center justify-between border-b border-slate-200 bg-slate-50 px-4 py-3">
<div class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-border bg-surface-card shadow-sm">
<div class="flex items-center justify-between border-b border-border bg-surface-page px-4 py-3">
<div class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-slate-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/></svg>
<span class="text-sm font-semibold text-slate-700">Diff (изменения)</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-text-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/></svg>
<span class="text-sm font-semibold text-text">Diff (изменения)</span>
</div>
{#if hasWorkspaceChanges}
<span class="inline-flex items-center gap-1 rounded-full bg-blue-50 px-2.5 py-0.5 text-xs font-medium text-blue-700 ring-1 ring-inset ring-blue-200">
<span class="inline-flex items-center gap-1 rounded-full bg-primary-light px-2.5 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary-ring">
{changedFilesCount} файлов
{#if totalChunks > 0}
<span class="text-blue-400">· {renderedChunkCount}/{totalChunks}</span>
<span class="text-primary">· {renderedChunkCount}/{totalChunks}</span>
{/if}
</span>
{/if}
</div>
<div class="flex-1 overflow-auto bg-white p-4" id="diff-scroll-container">
<div class="flex-1 overflow-auto bg-surface-card p-4" id="diff-scroll-container">
{#if workspaceLoading}
<div class="space-y-3">
{#each Array(8) as _}
<div class="h-4 animate-pulse rounded bg-slate-100"></div>
<div class="h-4 animate-pulse rounded bg-surface-muted"></div>
{/each}
<div class="mt-6 grid grid-cols-2 gap-3">
{#each Array(6) as _}
<div class="h-6 animate-pulse rounded bg-slate-50"></div>
<div class="h-6 animate-pulse rounded bg-surface-page"></div>
{/each}
</div>
</div>
@@ -213,17 +213,17 @@
{#if hasMoreChunks}
<div
bind:this={sentinelEl}
class="flex items-center justify-center py-6 text-sm text-slate-400"
class="flex items-center justify-center py-6 text-sm text-text-subtle"
role="status"
aria-live="polite"
>
{#if chunkLoading}
<div class="flex items-center gap-2">
<div class="h-4 w-4 animate-spin rounded-full border-b-2 border-blue-500"></div>
<div class="h-4 w-4 animate-spin rounded-full border-b-2 border-primary-ring"></div>
<span>{$t.git?.diff_loading || 'Загрузка изменений...'}</span>
</div>
{:else}
<span class="cursor-pointer text-blue-500 hover:text-blue-700" onclick={() => {
<span class="cursor-pointer text-primary hover:text-primary" onclick={() => {
renderedChunkCount = Math.min(renderedChunkCount + CHUNKS_PER_PAGE, totalChunks);
}}>
{$t.git?.diff_show_more?.replace('{count}', totalChunks - renderedChunkCount) || `Показать ещё (${totalChunks - renderedChunkCount} файлов)`}
@@ -231,20 +231,20 @@
{/if}
</div>
{:else if totalChunks > 0}
<div class="flex items-center justify-center py-4 text-xs text-slate-400">
<div class="flex items-center justify-center py-4 text-xs text-text-subtle">
{$t.git?.diff_all_shown?.replace('{count}', totalChunks) || `Показаны все изменения (${totalChunks} файлов)`}
</div>
{/if}
{:else if hasWorkspaceChanges}
<div class="flex h-full flex-col items-center justify-center gap-3 text-sm text-slate-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-slate-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/></svg>
<div class="flex h-full flex-col items-center justify-center gap-3 text-sm text-text-subtle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-text-subtle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"/></svg>
<span>Загрузка diff... (нажмите «Синхронизировать»)</span>
</div>
{:else}
<div class="flex h-full flex-col items-center justify-center gap-3 text-sm text-slate-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-slate-200" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z"/></svg>
<div class="flex h-full flex-col items-center justify-center gap-3 text-sm text-text-subtle">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 text-text-subtle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z"/></svg>
<span>Нет изменений для коммита</span>
<span class="text-xs text-slate-300">Синхронизируйте дашборд, чтобы увидеть изменения</span>
<span class="text-xs text-text-subtle">Синхронизируйте дашборд, чтобы увидеть изменения</span>
</div>
{/if}
</div>

View File

@@ -40,26 +40,26 @@
{#if previewDoc}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] flex flex-col">
<div class="bg-surface-card p-6 rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] flex flex-col">
<h3 class="text-lg font-semibold mb-4">{$t.llm.doc_preview_title}</h3>
<div class="flex-1 overflow-y-auto mb-6 prose prose-sm max-w-none border rounded p-4 bg-gray-50">
<h4 class="text-md font-bold text-gray-800 mb-2">{$t.llm.dataset_desc}</h4>
<p class="text-gray-700 mb-4 whitespace-pre-wrap">{previewDoc.description || 'No description generated.'}</p>
<div class="flex-1 overflow-y-auto mb-6 prose prose-sm max-w-none border rounded p-4 bg-surface-muted">
<h4 class="text-md font-bold text-text mb-2">{$t.llm.dataset_desc}</h4>
<p class="text-text mb-4 whitespace-pre-wrap">{previewDoc.description || 'No description generated.'}</p>
<h4 class="text-md font-bold text-gray-800 mb-2">{$t.llm.column_doc}</h4>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-100">
<h4 class="text-md font-bold text-text mb-2">{$t.llm.column_doc}</h4>
<table class="min-w-full divide-y divide-border">
<thead class="bg-surface-muted">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Column</th>
<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Description</th>
<th class="px-3 py-2 text-left text-xs font-medium text-text-muted uppercase">Column</th>
<th class="px-3 py-2 text-left text-xs font-medium text-text-muted uppercase">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tbody class="divide-y divide-border">
{#each Object.entries(previewDoc.columns || {}) as [name, desc]}
<tr>
<td class="px-3 py-2 text-sm font-mono text-gray-900">{name}</td>
<td class="px-3 py-2 text-sm text-gray-700">{desc}</td>
<td class="px-3 py-2 text-sm font-mono text-text">{name}</td>
<td class="px-3 py-2 text-sm text-text">{desc}</td>
</tr>
{/each}
</tbody>
@@ -68,7 +68,7 @@
<div class="flex justify-end gap-3">
<button
class="px-4 py-2 border rounded hover:bg-gray-50"
class="px-4 py-2 border rounded hover:bg-surface-muted"
onclick={onCancel}
disabled={isSaving}
>

View File

@@ -364,7 +364,7 @@
role="dialog"
aria-modal="true"
>
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
<div class="bg-surface-card p-6 rounded-lg shadow-xl w-full max-w-md">
<h3 class="text-lg font-semibold mb-4">
{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}
</h3>
@@ -373,7 +373,7 @@
<div>
<label
for="provider-name"
class="block text-sm font-medium text-gray-700"
class="block text-sm font-medium text-text"
>{$t.llm.name}</label
>
<input
@@ -388,7 +388,7 @@
<div>
<label
for="provider-type"
class="block text-sm font-medium text-gray-700"
class="block text-sm font-medium text-text"
>{$t.llm.type}</label
>
<select
@@ -407,7 +407,7 @@
<div>
<label
for="provider-base-url"
class="block text-sm font-medium text-gray-700"
class="block text-sm font-medium text-text"
>{$t.llm.base_url}</label
>
<div class="flex gap-2">
@@ -420,7 +420,7 @@
/>
<button
type="button"
class="mt-1 px-3 py-2 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50"
class="mt-1 px-3 py-2 text-sm border rounded-md hover:bg-surface-muted disabled:opacity-50"
title={$t.llm.refresh_models}
onclick={fetchModels}
disabled={isLoadingModels || !isBaseUrlValid(formData.base_url)}
@@ -433,12 +433,12 @@
<div>
<label
for="provider-api-key"
class="block text-sm font-medium text-gray-700"
class="block text-sm font-medium text-text"
>{$t.llm.api_key}</label
>
{#if editingProvider && formData.api_key && isMaskedKey(formData.api_key)}
<div class="mt-1 flex items-center gap-2">
<code class="block w-full rounded-md border border-gray-300 bg-gray-50 p-2 font-mono text-sm text-gray-700 select-all">
<code class="block w-full rounded-md border border-border-strong bg-surface-muted p-2 font-mono text-sm text-text select-all">
{formData.api_key}
</code>
<button
@@ -449,7 +449,7 @@
{$t.llm?.change_key || "Change"}
</button>
</div>
<p class="mt-1 text-xs text-gray-400">
<p class="mt-1 text-xs text-text-subtle">
{$t.llm?.key_masked_hint || "Masked for security. Click \"Change\" to enter a new key."}
</p>
{:else}
@@ -467,17 +467,17 @@
<div>
<label
for="provider-default-model"
class="block text-sm font-medium text-gray-700"
class="block text-sm font-medium text-text"
>{$t.llm.default_model}</label
>
{#if isLoadingModels}
<div class="mt-1 text-sm text-gray-500 italic flex items-center gap-2">
<span class="inline-block w-3 h-3 border-2 border-gray-400 border-t-transparent rounded-full animate-spin"></span>
<div class="mt-1 text-sm text-text-muted italic flex items-center gap-2">
<span class="inline-block w-3 h-3 border-2 border-border-strong border-t-transparent rounded-full animate-spin"></span>
{$t.llm.loading_models}
</div>
{:else if modelsLoadError}
<div class="mt-1 text-xs mb-1 {modelsLoadError.startsWith('Enter') ? 'text-amber-600' : 'text-red-600'}">{modelsLoadError}</div>
<div class="mt-1 text-xs mb-1 {modelsLoadError.startsWith('Enter') ? 'text-warning' : 'text-destructive'}">{modelsLoadError}</div>
{/if}
{#if availableModels.length > 0 && !isLoadingModels}
@@ -490,7 +490,7 @@
<option value={model}>{model}</option>
{/each}
</select>
<div class="mt-1 text-xs text-gray-400">
<div class="mt-1 text-xs text-text-subtle">
{($t.llm.models_loaded || "{count} models loaded").replace("{count}", availableModels.length)}
</div>
{:else}
@@ -513,7 +513,7 @@
/>
<label
for="provider-active"
class="text-sm font-medium text-gray-700">{$t.llm.active}</label
class="text-sm font-medium text-text">{$t.llm.active}</label
>
</div>
@@ -526,7 +526,7 @@
/>
<label
for="provider-multimodal"
class="text-sm font-medium text-gray-700"
class="text-sm font-medium text-text"
>
{$t.llm.multimodal_checkbox}
</label>
@@ -535,29 +535,29 @@
<div class="border-t pt-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-1">
<span class="text-sm font-medium text-gray-700">
<span class="text-sm font-medium text-text">
{$t.llm.max_images_label}
</span>
<HelpTooltip text={$t.llm.help_max_images} />
{#if formData.max_images != null}
<span class="ml-1 text-sm font-mono text-gray-600 bg-gray-100 px-2 py-0.5 rounded">
<span class="ml-1 text-sm font-mono text-text-muted bg-surface-muted px-2 py-0.5 rounded">
{formData.max_images}
</span>
{:else}
<span class="ml-1 text-xs text-gray-400">
<span class="ml-1 text-xs text-text-subtle">
{$t.llm.not_detected}
</span>
{/if}
</div>
<button
type="button"
class="text-xs px-3 py-1.5 border rounded-md hover:bg-gray-50 disabled:opacity-50"
class="text-xs px-3 py-1.5 border rounded-md hover:bg-surface-muted disabled:opacity-50"
onclick={detectMaxImages}
disabled={isProbing || !editingProvider}
title={editingProvider ? $t.llm.detect_max_images : $t.llm.save_before_probe}
>
{#if isProbing}
<span class="inline-block w-3 h-3 border-2 border-gray-400 border-t-transparent rounded-full animate-spin mr-1"></span>
<span class="inline-block w-3 h-3 border-2 border-border-strong border-t-transparent rounded-full animate-spin mr-1"></span>
{$t.llm.detecting}
{:else}
{$t.llm.detect_max_images}
@@ -565,10 +565,10 @@
</button>
</div>
{#if probeError}
<div class="mt-1 text-xs text-red-600">{probeError}</div>
<div class="mt-1 text-xs text-destructive">{probeError}</div>
{/if}
{#if probeResult && probeResult.max_images != null}
<div class="mt-1 text-xs text-green-600">
<div class="mt-1 text-xs text-success">
{$t.llm.detected_via.replace("{method}", probeResult.method === "parse_error" ? $t.llm.detection_method_error : $t.llm.detection_method_binary)}
</div>
{/if}
@@ -577,7 +577,7 @@
{#if testStatus.message}
<div
class={`mt-4 p-2 rounded text-sm ${testStatus.type === "success" ? "bg-green-100 text-green-800" : testStatus.type === "error" ? "bg-red-100 text-red-800" : "bg-blue-100 text-blue-800"}`}
class={`mt-4 p-2 rounded text-sm ${testStatus.type === "success" ? "bg-success-light text-success" : testStatus.type === "error" ? "bg-destructive-light text-destructive" : "bg-primary-light text-primary"}`}
>
{testStatus.message}
</div>
@@ -586,7 +586,7 @@
<div class="mt-6 flex justify-between gap-2">
<button
type="button"
class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
class="px-4 py-2 border rounded hover:bg-surface-muted flex-1"
onclick={() => {
showForm = false;
}}
@@ -595,7 +595,7 @@
</button>
<button
type="button"
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
class="px-4 py-2 bg-surface-muted text-white rounded hover:bg-terminal-bg flex-1"
disabled={isTesting}
onclick={testConnection}
>
@@ -616,25 +616,25 @@
<div class="grid gap-4">
{#each providers as provider}
<div
class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm"
class="border rounded-lg p-4 flex justify-between items-center bg-surface-card shadow-sm"
>
<div>
<div class="font-bold flex items-center gap-2">
{provider.name}
<span
class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}
class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? "bg-success-light text-success" : "bg-surface-muted text-text"}`}
>
{provider.is_active ? $t.llm.active : "Inactive"}
</span>
<span
class={`text-xs px-2 py-0.5 rounded-full ${provider.is_multimodal ? "bg-sky-100 text-sky-800" : "bg-amber-100 text-amber-800"}`}
class={`text-xs px-2 py-0.5 rounded-full ${provider.is_multimodal ? "bg-sky-100 text-sky-800" : "bg-warning-light text-warning"}`}
>
{provider.is_multimodal
? ($t.llm?.multimodal )
: ($t.llm?.text_only )}
</span>
</div>
<div class="text-sm text-gray-500">
<div class="text-sm text-text-muted">
{provider.provider_type} {provider.default_model}
</div>
</div>
@@ -648,7 +648,7 @@
</button>
<button
type="button"
class="text-sm text-red-600 hover:underline"
class="text-sm text-destructive hover:underline"
onclick={() => handleDelete(provider)}
disabled={deletingProviderIds.has(provider.id)}
>
@@ -660,7 +660,7 @@
</button>
<button
type="button"
class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
class={`text-sm ${provider.is_active ? "text-warning" : "text-success"} hover:underline`}
onclick={() => toggleActive(provider)}
disabled={togglingProviderIds.has(provider.id) || deletingProviderIds.has(provider.id)}
>
@@ -674,7 +674,7 @@
</div>
{:else}
<div
class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg"
class="text-center py-8 text-text-muted border-2 border-dashed rounded-lg"
>
{$t.llm.no_providers}
</div>

View File

@@ -14,18 +14,18 @@
function getStatusColor(status) {
switch (status) {
case 'PASS': return 'text-green-600 bg-green-100';
case 'WARN': return 'text-yellow-600 bg-yellow-100';
case 'FAIL': return 'text-red-600 bg-red-100';
default: return 'text-gray-600 bg-gray-100';
case 'PASS': return 'text-success bg-success-light';
case 'WARN': return 'text-warning bg-warning-light';
case 'FAIL': return 'text-destructive bg-destructive-light';
default: return 'text-text-muted bg-surface-muted';
}
}
</script>
{#if result}
<div class="bg-white shadow rounded-lg p-6 space-y-6">
<div class="bg-surface-card shadow rounded-lg p-6 space-y-6">
<div class="flex items-center justify-between">
<h2 class="text-xl font-bold text-gray-900">{$t.llm?.validation_report_title}</h2>
<h2 class="text-xl font-bold text-text">{$t.llm?.validation_report_title}</h2>
<span class={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(result.status)}`}>
{result.status}
</span>
@@ -41,14 +41,14 @@
<h3 class="text-lg font-semibold">{$t.tasks?.issues}</h3>
<div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<thead class="bg-surface-muted">
<tr>
<th class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900">{$t.reports?.severity}</th>
<th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{$t.reports?.message}</th>
<th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">{$t.tasks?.location}</th>
<th class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-text">{$t.reports?.severity}</th>
<th class="px-3 py-3.5 text-left text-sm font-semibold text-text">{$t.reports?.message}</th>
<th class="px-3 py-3.5 text-left text-sm font-semibold text-text">{$t.tasks?.location}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<tbody class="divide-y divide-border bg-surface-card">
{#each result.issues as issue}
<tr>
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm">
@@ -56,8 +56,8 @@
{issue.severity}
</span>
</td>
<td class="px-3 py-4 text-sm text-gray-500">{issue.message}</td>
<td class="px-3 py-4 text-sm text-gray-500">{issue.location || $t.common?.not_available}</td>
<td class="px-3 py-4 text-sm text-text-muted">{issue.message}</td>
<td class="px-3 py-4 text-sm text-text-muted">{issue.location || $t.common?.not_available}</td>
</tr>
{/each}
</tbody>
@@ -69,13 +69,13 @@
{#if result.screenshot_path}
<div class="space-y-4">
<h3 class="text-lg font-semibold">{$t.dashboard?.screenshot_strategy}</h3>
<img src={`/api/storage/file?path=${encodeURIComponent(result.screenshot_path)}`} alt={$t.dashboard?.dashboard_validation} class="rounded-lg border border-gray-200 shadow-sm max-w-full" />
<img src={`/api/storage/file?path=${encodeURIComponent(result.screenshot_path)}`} alt={$t.dashboard?.dashboard_validation} class="rounded-lg border border-border shadow-sm max-w-full" />
</div>
{/if}
</div>
{:else}
<div class="text-center py-12 bg-gray-50 rounded-lg border-2 border-dashed border-gray-300">
<p class="text-gray-500">{$t.llm?.no_validation_result}</p>
<div class="text-center py-12 bg-surface-muted rounded-lg border-2 border-dashed border-border-strong">
<p class="text-text-muted">{$t.llm?.no_validation_result}</p>
</div>
{/if}

View File

@@ -90,24 +90,24 @@
<!-- [SECTION: TEMPLATE] -->
<div class="overflow-x-auto">
<table class="min-w-full bg-white border border-gray-200">
<thead class="bg-gray-50">
<table class="min-w-full bg-surface-card border border-border">
<thead class="bg-surface-muted">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.name}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.category}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.size}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.created_at}</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.storage.table.actions}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.storage.table.name}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.storage.table.category}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.storage.table.size}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-text-muted uppercase tracking-wider">{$t.storage.table.created_at}</th>
<th class="px-6 py-3 text-right text-xs font-medium text-text-muted uppercase tracking-wider">{$t.storage.table.actions}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tbody class="divide-y divide-border">
{#each files as file}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<tr class="hover:bg-surface-muted">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-text">
{#if isDirectory(file)}
<button
onclick={() => onnavigate(file.path)}
class="flex items-center text-indigo-600 hover:text-indigo-900"
class="flex items-center text-primary hover:text-primary"
>
<svg class="h-5 w-5 mr-2 text-yellow-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" />
@@ -116,31 +116,31 @@
</button>
{:else}
<div class="flex items-center">
<svg class="h-5 w-5 mr-2 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="h-5 w-5 mr-2 text-text-subtle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
{file.name}
</div>
{/if}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 capitalize">{file.category}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted capitalize">{file.category}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">
{isDirectory(file) ? '--' : formatSize(file.size)}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{formatDate(file.created_at)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-text-muted">{formatDate(file.created_at)}</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{#if !isDirectory(file)}
<button
type="button"
onclick={() => handleDownload(file)}
class="text-indigo-600 hover:text-indigo-900 mr-4"
class="text-primary hover:text-primary mr-4"
>
{$t.storage.table.download}
</button>
{/if}
<button
onclick={() => ondelete({ category: file.category, path: file.path, name: file.name })}
class="text-red-600 hover:text-red-900"
class="text-destructive hover:text-destructive"
>
{$t.storage.table.delete}
</button>
@@ -148,7 +148,7 @@
</tr>
{:else}
<tr>
<td colspan="5" class="px-6 py-10 text-center text-sm text-gray-500">
<td colspan="5" class="px-6 py-10 text-center text-sm text-text-muted">
{$t.storage.no_files}
</td>
</tr>

View File

@@ -78,16 +78,16 @@
</script>
<!-- [SECTION: TEMPLATE] -->
<div class="bg-white p-6 rounded-lg border border-gray-200 shadow-sm">
<div class="bg-surface-card p-6 rounded-lg border border-border shadow-sm">
<h2 class="text-lg font-semibold mb-4">{$t.storage.upload_title}</h2>
<div class="space-y-4">
<div>
<label for="storage-upload-category" class="block text-sm font-medium text-gray-700 mb-1">{$t.storage.target_category}</label>
<label for="storage-upload-category" class="block text-sm font-medium text-text mb-1">{$t.storage.target_category}</label>
<select
id="storage-upload-category"
bind:value={category}
class="block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
class="block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm"
>
<option value="backups">{$t.storage.backups}</option>
<option value="repositorys">{$t.storage.repositories}</option>
@@ -96,7 +96,7 @@
<div
class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-dashed rounded-md transition-colors
{dragOver ? 'border-indigo-500 bg-indigo-50' : 'border-gray-300'}"
{dragOver ? 'border-primary-ring bg-primary-light' : 'border-border-strong'}"
role="region"
aria-label={$t.storage.upload_title}
ondragover={(event) => { event.preventDefault(); dragOver = true; }}
@@ -104,11 +104,11 @@
ondrop={(event) => { event.preventDefault(); handleDrop(event); }}
>
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<svg class="mx-auto h-12 w-12 text-text-subtle" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
<div class="flex text-sm text-gray-600">
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<div class="flex text-sm text-text-muted">
<label for="file-upload" class="relative cursor-pointer bg-surface-card rounded-md font-medium text-primary hover:text-primary focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-primary-ring">
<span>{$t.storage.upload_button}</span>
<input
id="file-upload"
@@ -122,13 +122,13 @@
</label>
<p class="pl-1">{$t.storage.drag_drop}</p>
</div>
<p class="text-xs text-gray-500">{$t.storage.supported_formats}</p>
<p class="text-xs text-text-muted">{$t.storage.supported_formats}</p>
</div>
</div>
{#if isUploading}
<div class="flex items-center justify-center space-x-2 text-indigo-600">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-indigo-600"></div>
<div class="flex items-center justify-center space-x-2 text-primary">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-primary-ring"></div>
<span class="text-sm font-medium">{$t.storage.uploading}</span>
</div>
{/if}

View File

@@ -49,17 +49,17 @@
);
let sourceClass = $derived(
sourceStyles[log.source?.toLowerCase()] ||
"bg-gray-100 text-gray-500",
"bg-surface-muted text-text-muted",
);
let hasProgress = $derived(log.metadata?.progress !== undefined);
let progressPercent = $derived(log.metadata?.progress || 0);
</script>
<div class="py-2.5 px-4 border-b border-gray-100 transition-colors hover:bg-gray-50">
<div class="py-2.5 px-4 border-b border-border transition-colors hover:bg-surface-muted">
<!-- Meta line: time + level + source -->
<div class="flex items-center gap-2 mb-1">
<span class="font-mono text-xs text-gray-400 shrink-0"
<span class="font-mono text-xs text-text-subtle shrink-0"
>{formattedTime}</span
>
<span
@@ -76,7 +76,7 @@
<!-- Message -->
<div
class="font-mono text-sm leading-relaxed text-gray-700 break-words whitespace-pre-wrap"
class="font-mono text-sm leading-relaxed text-text break-words whitespace-pre-wrap"
>
{log.message}
</div>
@@ -84,13 +84,13 @@
<!-- Progress bar (if applicable) -->
{#if hasProgress}
<div class="flex items-center gap-2 mt-1.5">
<div class="flex-1 h-1.5 bg-gray-100 rounded-full overflow-hidden">
<div class="flex-1 h-1.5 bg-surface-muted rounded-full overflow-hidden">
<div
class="h-full bg-gradient-to-r from-primary to-purple-500 rounded-full transition-[width] duration-300 ease-out"
style="width: {progressPercent}%"
></div>
</div>
<span class="font-mono text-[0.625rem] text-gray-400 shrink-0"
<span class="font-mono text-[0.625rem] text-text-subtle shrink-0"
>{progressPercent.toFixed(0)}%</span
>
</div>

View File

@@ -66,11 +66,11 @@
);
</script>
<div class="flex items-center gap-2 px-3 py-2 bg-white border-b border-gray-200">
<div class="flex items-center gap-2 px-3 py-2 bg-surface-card border-b border-border">
<div class="flex items-center gap-2 flex-1 min-w-0">
<!-- Level filter -->
<select
class="bg-white text-gray-600 border border-gray-300 rounded-md px-2 py-1.5 text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:ring-2 focus:ring-primary-ring focus:border-primary-ring"
class="bg-surface-card text-text-muted border border-border-strong rounded-md px-2 py-1.5 text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:ring-2 focus:ring-primary-ring focus:border-primary-ring"
style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;"
value={selectedLevel}
onchange={handleLevelChange}
@@ -83,7 +83,7 @@
<!-- Source filter -->
<select
class="bg-white text-gray-600 border border-gray-300 rounded-md px-2 py-1.5 text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:ring-2 focus:ring-primary-ring focus:border-primary-ring"
class="bg-surface-card text-text-muted border border-border-strong rounded-md px-2 py-1.5 text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:ring-2 focus:ring-primary-ring focus:border-primary-ring"
style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;"
value={selectedSource}
onchange={handleSourceChange}
@@ -98,7 +98,7 @@
<!-- Search input -->
<div class="relative flex-1 min-w-0">
<svg
class="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"
class="absolute left-2 top-1/2 -translate-y-1/2 text-text-subtle pointer-events-none"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
@@ -112,7 +112,7 @@
</svg>
<input
type="text"
class="w-full bg-white text-gray-900 border border-gray-300 rounded-md py-1.5 px-2 pl-7 text-xs placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-ring focus:border-primary-ring"
class="w-full bg-surface-card text-text border border-border-strong rounded-md py-1.5 px-2 pl-7 text-xs placeholder:text-text-subtle focus:outline-none focus:ring-2 focus:ring-primary-ring focus:border-primary-ring"
placeholder="Search..."
value={searchText}
oninput={handleSearchChange}
@@ -123,7 +123,7 @@
{#if hasActiveFilters}
<button
class="flex items-center justify-center p-1.5 bg-white border border-gray-300 rounded-md text-gray-400 shrink-0 cursor-pointer transition-all hover:text-red-500 hover:border-red-300 hover:bg-red-50"
class="flex items-center justify-center p-1.5 bg-surface-card border border-border-strong rounded-md text-text-subtle shrink-0 cursor-pointer transition-all hover:text-destructive hover:border-destructive-ring hover:bg-destructive-light"
onclick={clearFilters}
aria-label="Clear filters"
>

View File

@@ -85,7 +85,7 @@
});
</script>
<div class="flex flex-col h-full bg-white overflow-hidden rounded-lg border border-gray-200 shadow-sm">
<div class="flex flex-col h-full bg-surface-card overflow-hidden rounded-lg border border-border shadow-sm">
<!-- Filter Bar -->
<LogFilterBar {availableSources} onfilterchange={handleFilterChange} />
@@ -96,7 +96,7 @@
>
{#if filteredLogs.length === 0}
<div
class="flex flex-col items-center justify-center py-12 px-4 text-gray-300 gap-3"
class="flex flex-col items-center justify-center py-12 px-4 text-text-subtle gap-3"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -113,7 +113,7 @@
<line x1="16" y1="17" x2="8" y2="17" />
<polyline points="10 9 9 9 8 9" />
</svg>
<span class="text-sm text-gray-400">No logs available</span>
<span class="text-sm text-text-subtle">No logs available</span>
</div>
{:else}
{#each filteredLogs as log}
@@ -124,16 +124,16 @@
<!-- Footer Stats -->
<div
class="flex items-center justify-between py-1.5 px-4 border-t border-gray-200 bg-gray-50"
class="flex items-center justify-between py-1.5 px-4 border-t border-border bg-surface-muted"
>
<span class="font-mono text-xs text-gray-400">
<span class="font-mono text-xs text-text-subtle">
{filteredLogs.length}{filteredLogs.length !== logs.length
? ` / ${logs.length}`
: ""} entries
</span>
<button
class="flex items-center gap-1.5 bg-transparent border-none text-xs cursor-pointer py-1 px-1.5 rounded transition-all hover:bg-gray-200 hover:text-gray-700
{autoScroll ? 'text-primary font-medium' : 'text-gray-400'}"
class="flex items-center gap-1.5 bg-transparent border-none text-xs cursor-pointer py-1 px-1.5 rounded transition-all hover:bg-surface-muted hover:text-text
{autoScroll ? 'text-primary font-medium' : 'text-text-subtle'}"
onclick={toggleAutoScroll}
aria-label="Toggle auto-scroll"
>

View File

@@ -15,46 +15,46 @@
switch (status) {
case 'PASS':
case 'SUCCESS':
return 'bg-green-100 text-green-700';
return 'bg-success-light text-success';
case 'WARN':
case 'PARTIAL_SUCCESS':
return 'bg-yellow-100 text-yellow-700';
return 'bg-warning-light text-warning';
case 'FAIL':
case 'FAILED':
return 'bg-red-100 text-red-700';
return 'bg-destructive-light text-destructive';
default:
return 'bg-slate-100 text-slate-700';
return 'bg-surface-muted text-text';
}
}
</script>
{#if !task}
<div class="rounded-lg border border-dashed border-slate-200 bg-slate-50 p-6 text-sm text-slate-500">
<div class="rounded-lg border border-dashed border-border bg-surface-page p-6 text-sm text-text-muted">
{$t.tasks?.result_select_task}
</div>
{:else if !result}
<div class="rounded-lg border border-slate-200 bg-white p-4">
<p class="text-sm text-slate-700">{$t.tasks?.result_not_structured}</p>
<div class="rounded-lg border border-border bg-surface-card p-4">
<p class="text-sm text-text">{$t.tasks?.result_not_structured}</p>
</div>
{:else if pluginId === 'llm_dashboard_validation'}
<div class="space-y-4 rounded-lg border border-slate-200 bg-white p-4">
<div class="space-y-4 rounded-lg border border-border bg-surface-card p-4">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-slate-900">{$t.tasks?.result_llm_validation}</h3>
<h3 class="text-sm font-semibold text-text">{$t.tasks?.result_llm_validation}</h3>
<span class={`rounded-full px-2 py-1 text-xs font-semibold ${statusColor(result.status)}`}>{result.status || 'UNKNOWN'}</span>
</div>
<p class="text-sm text-slate-700">{result.summary || $t.tasks?.result_no_summary}</p>
<p class="text-sm text-text">{result.summary || $t.tasks?.result_no_summary}</p>
{#if result.issues?.length}
<div>
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.tasks?.issues} ({result.issues.length})</p>
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted">{$t.tasks?.issues} ({result.issues.length})</p>
<ul class="space-y-2">
{#each result.issues as issue}
<li class="rounded-md border border-slate-200 bg-slate-50 p-2 text-sm">
<li class="rounded-md border border-border bg-surface-page p-2 text-sm">
<div class="flex items-center gap-2">
<span class={`rounded px-2 py-0.5 text-xs font-semibold ${statusColor(issue.severity)}`}>{issue.severity}</span>
<span class="text-slate-700">{issue.message}</span>
<span class="text-text">{issue.message}</span>
</div>
{#if issue.location}
<p class="mt-1 text-xs text-slate-500">{$t.tasks?.location}: {issue.location}</p>
<p class="mt-1 text-xs text-text-muted">{$t.tasks?.location}: {issue.location}</p>
{/if}
</li>
{/each}
@@ -63,12 +63,12 @@
{/if}
</div>
{:else if pluginId === 'superset-backup'}
<div class="space-y-4 rounded-lg border border-slate-200 bg-white p-4">
<div class="space-y-4 rounded-lg border border-border bg-surface-card p-4">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-slate-900">{$t.tasks?.backup_result}</h3>
<h3 class="text-sm font-semibold text-text">{$t.tasks?.backup_result}</h3>
<span class={`rounded-full px-2 py-1 text-xs font-semibold ${statusColor(result.status)}`}>{result.status || 'UNKNOWN'}</span>
</div>
<div class="grid grid-cols-2 gap-2 text-sm text-slate-700">
<div class="grid grid-cols-2 gap-2 text-sm text-text">
<p>{$t.dashboard?.environment}: {result.environment || $t.common?.not_available}</p>
<p>{$t.tasks?.total}: {result.total_dashboards ?? 0}</p>
<p>{$t.tasks?.successful}: {result.backed_up_dashboards ?? 0}</p>
@@ -76,10 +76,10 @@
</div>
{#if result.failures?.length}
<div>
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.tasks?.errors}</p>
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted">{$t.tasks?.errors}</p>
<ul class="space-y-2">
{#each result.failures as failure}
<li class="rounded-md border border-destructive-light bg-destructive-light p-2 text-sm text-red-700">
<li class="rounded-md border border-destructive-light bg-destructive-light p-2 text-sm text-destructive">
{failure.title || failure.id}: {failure.error}
</li>
{/each}
@@ -88,12 +88,12 @@
{/if}
</div>
{:else if pluginId === 'superset-migration'}
<div class="space-y-4 rounded-lg border border-slate-200 bg-white p-4">
<div class="space-y-4 rounded-lg border border-border bg-surface-card p-4">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-slate-900">{$t.tasks?.migration_result}</h3>
<h3 class="text-sm font-semibold text-text">{$t.tasks?.migration_result}</h3>
<span class={`rounded-full px-2 py-1 text-xs font-semibold ${statusColor(result.status)}`}>{result.status || 'UNKNOWN'}</span>
</div>
<div class="grid grid-cols-2 gap-2 text-sm text-slate-700">
<div class="grid grid-cols-2 gap-2 text-sm text-text">
<p>{$t.migration?.source_env}: {result.source_environment || $t.common?.not_available}</p>
<p>{$t.migration?.target_env}: {result.target_environment || $t.common?.not_available}</p>
<p>{$t.tasks?.selected}: {result.selected_dashboards ?? 0}</p>
@@ -103,10 +103,10 @@
</div>
{#if result.failed_dashboards?.length}
<div>
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-500">{$t.tasks?.migration_errors}</p>
<p class="mb-2 text-xs font-semibold uppercase tracking-wide text-text-muted">{$t.tasks?.migration_errors}</p>
<ul class="space-y-2">
{#each result.failed_dashboards as failed}
<li class="rounded-md border border-destructive-light bg-destructive-light p-2 text-sm text-red-700">
<li class="rounded-md border border-destructive-light bg-destructive-light p-2 text-sm text-destructive">
{failed.title || failed.id}: {failed.error}
</li>
{/each}
@@ -115,8 +115,8 @@
{/if}
</div>
{:else}
<div class="rounded-lg border border-slate-200 bg-white p-4">
<pre class="overflow-auto rounded bg-slate-50 p-3 text-xs text-slate-700">{JSON.stringify(result, null, 2)}</pre>
<div class="rounded-lg border border-border bg-surface-card p-4">
<pre class="overflow-auto rounded bg-surface-page p-3 text-xs text-text">{JSON.stringify(result, null, 2)}</pre>
</div>
{/if}

View File

@@ -129,12 +129,12 @@
</script>
<div class="space-y-6">
<div class="bg-white p-6 rounded-lg shadow-sm border border-gray-200">
<h3 class="text-lg font-medium text-gray-900 mb-4">{$t.debug?.title}</h3>
<div class="bg-surface-card p-6 rounded-lg shadow-sm border border-border">
<h3 class="text-lg font-medium text-text mb-4">{$t.debug?.title}</h3>
<div class="mb-4">
<label for="debug-action" class="block text-sm font-medium text-gray-700">{$t.debug?.action}</label>
<select id="debug-action" bind:value={action} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<label for="debug-action" class="block text-sm font-medium text-text">{$t.debug?.action}</label>
<select id="debug-action" bind:value={action} class="mt-1 block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm">
<option value="test-db-api">{$t.debug?.test_db_api}</option>
<option value="get-dataset-structure">{$t.debug?.get_dataset_structure}</option>
</select>
@@ -143,8 +143,8 @@
{#if action === 'test-db-api'}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="src-env" class="block text-sm font-medium text-gray-700">{$t.migration?.source_env}</label>
<select id="src-env" bind:value={sourceEnv} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<label for="src-env" class="block text-sm font-medium text-text">{$t.migration?.source_env}</label>
<select id="src-env" bind:value={sourceEnv} class="mt-1 block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm">
<option value="" disabled>{$t.debug?.select_source}</option>
{#each envs as env}
<option value={env.id}>{env.name}</option>
@@ -152,8 +152,8 @@
</select>
</div>
<div>
<label for="tgt-env" class="block text-sm font-medium text-gray-700">{$t.migration?.target_env}</label>
<select id="tgt-env" bind:value={targetEnv} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<label for="tgt-env" class="block text-sm font-medium text-text">{$t.migration?.target_env}</label>
<select id="tgt-env" bind:value={targetEnv} class="mt-1 block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm">
<option value="" disabled>{$t.debug?.select_target}</option>
{#each envs as env}
<option value={env.id}>{env.name}</option>
@@ -164,8 +164,8 @@
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="debug-env" class="block text-sm font-medium text-gray-700">{$t.dashboard?.environment}</label>
<select id="debug-env" bind:value={selectedEnv} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
<label for="debug-env" class="block text-sm font-medium text-text">{$t.dashboard?.environment}</label>
<select id="debug-env" bind:value={selectedEnv} class="mt-1 block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm">
<option value="" disabled>{$t.common?.choose_environment}</option>
{#each envs as env}
<option value={env.id}>{env.name}</option>
@@ -173,26 +173,26 @@
</select>
</div>
<div>
<label for="debug-ds-id" class="block text-sm font-medium text-gray-700">{$t.mapper?.dataset_id}</label>
<input type="number" id="debug-ds-id" bind:value={datasetId} class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
<label for="debug-ds-id" class="block text-sm font-medium text-text">{$t.mapper?.dataset_id}</label>
<input type="number" id="debug-ds-id" bind:value={datasetId} class="mt-1 block w-full border-border-strong rounded-md shadow-sm focus:ring-primary-ring focus:border-primary-ring sm:text-sm" />
</div>
</div>
{/if}
<div class="mt-4 flex justify-end">
<button onclick={handleRunDebug} disabled={isRunning} 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:opacity-50">
<button onclick={handleRunDebug} disabled={isRunning} class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-primary hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-ring disabled:opacity-50">
{isRunning ? $t.dashboard?.running : $t.debug?.run}
</button>
</div>
</div>
{#if results}
<div class="bg-white shadow overflow-hidden sm:rounded-md border border-gray-200">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h3 class="text-lg leading-6 font-medium text-gray-900">{$t.debug?.output}</h3>
<div class="bg-surface-card shadow overflow-hidden sm:rounded-md border border-border">
<div class="px-4 py-5 sm:px-6 bg-surface-muted border-b border-border">
<h3 class="text-lg leading-6 font-medium text-text">{$t.debug?.output}</h3>
</div>
<div class="p-4">
<pre class="text-xs text-gray-600 bg-gray-900 text-green-400 p-4 rounded-md overflow-x-auto h-96">{JSON.stringify(results, null, 2)}</pre>
<pre class="text-xs text-text-muted bg-terminal-bg text-success p-4 rounded-md overflow-x-auto h-96">{JSON.stringify(results, null, 2)}</pre>
</div>
</div>
{/if}

View File

@@ -150,16 +150,16 @@
<!-- [SECTION: TEMPLATE] -->
<div class="space-y-6">
<!-- Main Configuration Card -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
<div class="bg-surface-card rounded-xl border border-border shadow-sm overflow-hidden">
<!-- Card Header -->
<div class="px-6 py-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<div class="px-6 py-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-border">
<div class="flex items-center gap-3">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-blue-100 text-blue-600">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary-light text-primary">
<Icon name="database" size={20} />
</div>
<div>
<h2 class="text-lg font-semibold text-gray-900">{$t.mapper.title}</h2>
<p class="text-sm text-gray-500">Настройте параметры маппинга колонок</p>
<h2 class="text-lg font-semibold text-text">{$t.mapper.title}</h2>
<p class="text-sm text-text-muted">Настройте параметры маппинга колонок</p>
</div>
</div>
</div>
@@ -169,10 +169,10 @@
<!-- Environment & Dataset ID Row -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div class="space-y-2">
<label class="flex items-center gap-2 text-sm font-medium text-gray-700">
<Icon name="settings" size={16} className="text-gray-400" />
<label class="flex items-center gap-2 text-sm font-medium text-text">
<Icon name="settings" size={16} className="text-text-subtle" />
{$t.mapper.environment}
<span class="text-red-500">*</span>
<span class="text-destructive">*</span>
</label>
<Select
bind:value={selectedEnv}
@@ -183,10 +183,10 @@
/>
</div>
<div class="space-y-2">
<label class="flex items-center gap-2 text-sm font-medium text-gray-700">
<Icon name="clipboard" size={16} className="text-gray-400" />
<label class="flex items-center gap-2 text-sm font-medium text-text">
<Icon name="clipboard" size={16} className="text-text-subtle" />
{$t.mapper.dataset_id}
<span class="text-red-500">*</span>
<span class="text-destructive">*</span>
</label>
<Input
type="number"
@@ -198,8 +198,8 @@
<!-- Source Selection -->
<div class="mb-6">
<label class="flex items-center gap-2 text-sm font-medium text-gray-700 mb-3">
<Icon name="layers" size={16} className="text-gray-400" />
<label class="flex items-center gap-2 text-sm font-medium text-text mb-3">
<Icon name="layers" size={16} className="text-text-subtle" />
{$t.mapper.source}
</label>
<div class="flex gap-4">
@@ -210,16 +210,16 @@
value="sqllab"
class="peer sr-only"
/>
<div class="flex items-center gap-3 p-4 rounded-lg border-2 border-gray-200 bg-gray-50
hover:border-blue-300 hover:bg-blue-50
peer-checked:border-blue-500 peer-checked:bg-blue-50 peer-checked:ring-2 peer-checked:ring-blue-500/20
<div class="flex items-center gap-3 p-4 rounded-lg border-2 border-border bg-surface-muted
hover:border-primary-ring hover:bg-primary-light
peer-checked:border-primary-ring peer-checked:bg-primary-light peer-checked:ring-2 peer-checked:ring-primary-ring/20
transition-all duration-200">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-blue-100 text-blue-600 peer-checked:bg-blue-600 peer-checked:text-white">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary-light text-primary peer-checked:bg-primary peer-checked:text-white">
<Icon name="database" size={20} />
</div>
<div>
<div class="font-medium text-gray-900">{$t.mapper.source_sqllab}</div>
<div class="text-xs text-gray-500">Подключение через SQL Lab</div>
<div class="font-medium text-text">{$t.mapper.source_sqllab}</div>
<div class="text-xs text-text-muted">Подключение через SQL Lab</div>
</div>
</div>
</label>
@@ -230,16 +230,16 @@
value="excel"
class="peer sr-only"
/>
<div class="flex items-center gap-3 p-4 rounded-lg border-2 border-gray-200 bg-gray-50
hover:border-green-300 hover:bg-green-50
peer-checked:border-green-500 peer-checked:bg-green-50 peer-checked:ring-2 peer-checked:ring-green-500/20
<div class="flex items-center gap-3 p-4 rounded-lg border-2 border-border bg-surface-muted
hover:border-success hover:bg-success-light
peer-checked:border-success peer-checked:bg-success-light peer-checked:ring-2 peer-checked:ring-success-ring/20
transition-all duration-200">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-green-100 text-green-600 peer-checked:bg-green-600 peer-checked:text-white">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-success-light text-success peer-checked:bg-success peer-checked:text-white">
<Icon name="clipboard" size={20} />
</div>
<div>
<div class="font-medium text-gray-900">{$t.mapper.source_excel}</div>
<div class="text-xs text-gray-500">Импорт из Excel файла</div>
<div class="font-medium text-text">{$t.mapper.source_excel}</div>
<div class="text-xs text-text-muted">Импорт из Excel файла</div>
</div>
</div>
</label>
@@ -247,18 +247,18 @@
</div>
<!-- Source-specific Configuration -->
<div class="rounded-xl border border-gray-200 bg-gray-50 p-5 mb-6">
<div class="rounded-xl border border-border bg-surface-muted p-5 mb-6">
{#if source === 'sqllab'}
<div class="space-y-4">
<div class="flex items-center gap-2 mb-4">
<Icon name="database" size={18} className="text-blue-600" />
<h3 class="text-sm font-semibold text-gray-900">Настройки SQL Lab</h3>
<Icon name="database" size={18} className="text-primary" />
<h3 class="text-sm font-semibold text-text">Настройки SQL Lab</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700">
<label class="text-sm font-medium text-text">
{$t.mapper.database_id}
<span class="text-red-500">*</span>
<span class="text-destructive">*</span>
</label>
<Input
type="number"
@@ -267,7 +267,7 @@
/>
</div>
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700">
<label class="text-sm font-medium text-text">
{$t.mapper.sql_query_label}
</label>
<Input
@@ -281,13 +281,13 @@
{:else}
<div class="space-y-4">
<div class="flex items-center gap-2 mb-4">
<Icon name="clipboard" size={18} className="text-green-600" />
<h3 class="text-sm font-semibold text-gray-900">Настройки Excel</h3>
<Icon name="clipboard" size={18} className="text-success" />
<h3 class="text-sm font-semibold text-text">Настройки Excel</h3>
</div>
<div class="space-y-2">
<label class="text-sm font-medium text-gray-700">
<label class="text-sm font-medium text-text">
{$t.mapper.excel_path}
<span class="text-red-500">*</span>
<span class="text-destructive">*</span>
</label>
<Input
type="text"
@@ -300,7 +300,7 @@
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row justify-end gap-3 pt-4 border-t border-gray-100">
<div class="flex flex-col sm:flex-row justify-end gap-3 pt-4 border-t border-border">
<Button
variant="secondary"
onclick={handleGenerateDocs}