diff --git a/web/src/lib/components/Breadcrumb.svelte b/web/src/lib/components/Breadcrumb.svelte new file mode 100644 index 0000000..c2c1276 --- /dev/null +++ b/web/src/lib/components/Breadcrumb.svelte @@ -0,0 +1,27 @@ + + + diff --git a/web/src/lib/components/icons/IconArrowLeft.svelte b/web/src/lib/components/icons/IconArrowLeft.svelte new file mode 100644 index 0000000..5da1936 --- /dev/null +++ b/web/src/lib/components/icons/IconArrowLeft.svelte @@ -0,0 +1,7 @@ + + diff --git a/web/src/lib/components/icons/IconChevronDown.svelte b/web/src/lib/components/icons/IconChevronDown.svelte new file mode 100644 index 0000000..cbb52a8 --- /dev/null +++ b/web/src/lib/components/icons/IconChevronDown.svelte @@ -0,0 +1,7 @@ + + diff --git a/web/src/lib/components/icons/index.ts b/web/src/lib/components/icons/index.ts index 607706c..0bde807 100644 --- a/web/src/lib/components/icons/index.ts +++ b/web/src/lib/components/icons/index.ts @@ -48,3 +48,5 @@ export { default as IconRefresh } from './IconRefresh.svelte'; export { default as IconProxies } from './IconProxies.svelte'; export { default as IconEvents } from './IconEvents.svelte'; export { default as IconLogout } from './IconLogout.svelte'; +export { default as IconArrowLeft } from './IconArrowLeft.svelte'; +export { default as IconChevronDown } from './IconChevronDown.svelte'; diff --git a/web/src/lib/i18n/en.json b/web/src/lib/i18n/en.json index 9fcbafe..f52815e 100644 --- a/web/src/lib/i18n/en.json +++ b/web/src/lib/i18n/en.json @@ -58,7 +58,9 @@ "imageLoadFailed": "Failed to load images", "alreadyAdded": "Already added", "portHelpText": "Auto-detected from EXPOSE if empty", - "healthcheckHelpText": "Auto-detected from image if empty" + "healthcheckHelpText": "Auto-detected from image if empty", + "searchPlaceholder": "Search projects by name, image, or registry...", + "noMatchingProjects": "No projects match your search." }, "projectDetail": { "deleteProject": "Delete Project", @@ -162,6 +164,7 @@ "save": "Save", "add": "Add", "adding": "Adding...", + "scopeGuide": "Volume Scopes", "noVolumes": "No volumes configured yet. Add one above.", "volumeAdded": "Volume added", "volumeUpdated": "Volume updated", @@ -258,6 +261,11 @@ "dockerNetworkHelp": "Docker network for deployed containers", "subdomainPattern": "Subdomain Pattern", "subdomainPatternHelp": "Pattern for auto-generated subdomains", + "subdomainVarsTitle": "Available variables", + "varProject": "Project name", + "varStage": "Stage name", + "varTag": "Image tag", + "varPort": "Container port", "pollingInterval": "Polling Interval (seconds)", "pollingIntervalHelp": "How often to check registries for new tags (10-86400)", "notificationUrl": "Notification URL", diff --git a/web/src/lib/i18n/ru.json b/web/src/lib/i18n/ru.json index 153d2e2..0f0cadf 100644 --- a/web/src/lib/i18n/ru.json +++ b/web/src/lib/i18n/ru.json @@ -58,7 +58,9 @@ "imageLoadFailed": "Не удалось загрузить образы", "alreadyAdded": "Уже добавлен", "portHelpText": "Автоопределение из EXPOSE, если пусто", - "healthcheckHelpText": "Автоопределение из образа, если пусто" + "healthcheckHelpText": "Автоопределение из образа, если пусто", + "searchPlaceholder": "Поиск по имени, образу или реестру...", + "noMatchingProjects": "Проекты не найдены." }, "projectDetail": { "deleteProject": "Удалить проект", @@ -162,6 +164,7 @@ "save": "Сохранить", "add": "Добавить", "adding": "Добавление...", + "scopeGuide": "Области видимости томов", "noVolumes": "Тома ещё не настроены. Добавьте один выше.", "volumeAdded": "Том добавлен", "volumeUpdated": "Том обновлён", @@ -258,6 +261,11 @@ "dockerNetworkHelp": "Docker-сеть для развёрнутых контейнеров", "subdomainPattern": "Шаблон поддомена", "subdomainPatternHelp": "Шаблон для автоматически генерируемых поддоменов", + "subdomainVarsTitle": "Доступные переменные", + "varProject": "Имя проекта", + "varStage": "Имя стадии", + "varTag": "Тег образа", + "varPort": "Порт контейнера", "pollingInterval": "Интервал опроса (секунды)", "pollingIntervalHelp": "Как часто проверять реестры на новые теги (10-86400)", "notificationUrl": "URL уведомлений", diff --git a/web/src/lib/styles/tokens.css b/web/src/lib/styles/tokens.css index 229b666..3922ee1 100644 --- a/web/src/lib/styles/tokens.css +++ b/web/src/lib/styles/tokens.css @@ -210,6 +210,13 @@ animation: button-press 150ms ease-in-out; } +/* ── Disabled Buttons ────────────────────────────────────────────── */ + +button:disabled, +a[aria-disabled="true"] { + cursor: not-allowed; +} + /* ── Skeleton Loader ──────────────────────────────────────────────── */ .skeleton { @@ -226,6 +233,32 @@ /* ── Toggle Switch ────────────────────────────────────────────────── */ +/* ── Badge Tokens ────────────────────────────────────────────────── */ + +.badge-success { background: #ecfdf5; color: #047857; } +.badge-warning { background: #fffbeb; color: #b45309; } +.badge-danger { background: #fef2f2; color: #dc2626; } +.badge-info { background: #eff6ff; color: #2563eb; } +.badge-purple { background: #faf5ff; color: #7c3aed; } +.badge-cyan { background: #ecfeff; color: #0e7490; } +.badge-gray { background: #f3f4f6; color: #4b5563; } +.badge-amber { background: #fffbeb; color: #b45309; } +.badge-indigo { background: #eef2ff; color: #4f46e5; } +.badge-rose { background: #fff1f2; color: #e11d48; } + +[data-theme="dark"] .badge-success { background: rgba(6, 78, 59, 0.3); color: #34d399; } +[data-theme="dark"] .badge-warning { background: rgba(120, 53, 15, 0.3); color: #fbbf24; } +[data-theme="dark"] .badge-danger { background: rgba(127, 29, 29, 0.3); color: #f87171; } +[data-theme="dark"] .badge-info { background: rgba(30, 58, 138, 0.3); color: #60a5fa; } +[data-theme="dark"] .badge-purple { background: rgba(76, 29, 149, 0.3); color: #a78bfa; } +[data-theme="dark"] .badge-cyan { background: rgba(14, 116, 144, 0.3); color: #22d3ee; } +[data-theme="dark"] .badge-gray { background: rgba(55, 65, 81, 0.5); color: #9ca3af; } +[data-theme="dark"] .badge-amber { background: rgba(120, 53, 15, 0.3); color: #fbbf24; } +[data-theme="dark"] .badge-indigo { background: rgba(67, 56, 202, 0.3); color: #818cf8; } +[data-theme="dark"] .badge-rose { background: rgba(159, 18, 57, 0.3); color: #fb7185; } + +/* ── Toggle Switch ────────────────────────────────────────────────── */ + .toggle-switch { position: relative; width: 2.75rem; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 40ab99b..89c681a 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -6,7 +6,7 @@ import Toast from '$lib/components/Toast.svelte'; import ThemeToggle from '$lib/components/ThemeToggle.svelte'; import LocaleSwitcher from '$lib/components/LocaleSwitcher.svelte'; - import { IconDashboard, IconProjects, IconDeploy, IconSettings, IconMenu, IconX, IconLogout } from '$lib/components/icons'; + import { IconDashboard, IconProjects, IconDeploy, IconSettings, IconMenu, IconX, IconLogout, IconChevronDown } from '$lib/components/icons'; import { goto } from '$app/navigation'; import { connectGlobalEvents, type SSEConnection } from '$lib/sse'; import { instanceStatusStore } from '$lib/stores/instance-status'; @@ -142,24 +142,9 @@ {$t('app.name')} - - - {#if !dockerConnected && hintsExpanded && dockerHealth?.error} @@ -240,6 +225,19 @@
{$t('app.name')} {$t('app.version')}
diff --git a/web/src/routes/dns/+page.svelte b/web/src/routes/dns/+page.svelte index 35fa96c..92760bd 100644 --- a/web/src/routes/dns/+page.svelte +++ b/web/src/routes/dns/+page.svelte @@ -77,11 +77,11 @@ function statusColor(status: string): string { switch (status) { - case 'synced': return 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400'; - case 'missing': return 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'; - case 'orphaned': return 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400'; - case 'wildcard': return 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'; - default: return 'bg-gray-100 text-gray-700'; + case 'synced': return 'badge-success'; + case 'missing': return 'badge-danger'; + case 'orphaned': return 'badge-warning'; + case 'wildcard': return 'badge-info'; + default: return 'badge-gray'; } } diff --git a/web/src/routes/events/+page.svelte b/web/src/routes/events/+page.svelte index 0173c3c..4ed797d 100644 --- a/web/src/routes/events/+page.svelte +++ b/web/src/routes/events/+page.svelte @@ -11,6 +11,7 @@ import EventLogEntryComponent from '$lib/components/EventLogEntry.svelte'; import EventLogFilter from '$lib/components/EventLogFilter.svelte'; import EmptyState from '$lib/components/EmptyState.svelte'; + import { IconLoader } from '$lib/components/icons'; // ── State ───────────────────────────────────────────────────── @@ -244,10 +245,7 @@ {#if loading}{$t('projects.noMatchingProjects')}
+| @@ -262,5 +290,6 @@ |
{$t('settingsGeneral.sslCertificateHelp')}
+