Compare commits

..

91 Commits

Author SHA1 Message Date
11e8c8e132 Finalize task-020 reports navigation and stability fixes 2026-02-23 13:28:30 +03:00
40c2e2414d semantic update 2026-02-23 13:15:48 +03:00
066ef5eab5 таски готовы 2026-02-23 10:18:56 +03:00
2946ee9b42 Fix task API stability and Playwright runtime in Docker 2026-02-21 23:43:46 +03:00
5f70a239a7 feat: restore legacy data and add typed task result views 2026-02-21 23:17:56 +03:00
d67d24e7e6 db + docker 2026-02-20 20:47:39 +03:00
01efc9dae1 semantic update 2026-02-20 10:41:15 +03:00
43814511ee few shots update 2026-02-20 10:26:01 +03:00
db47e4ce55 css refactor 2026-02-19 18:24:36 +03:00
d5a5c3b902 +Svelte specific 2026-02-19 17:47:24 +03:00
066c37087d ai base 2026-02-19 17:43:45 +03:00
b40649b9ed fix tax log 2026-02-19 16:05:59 +03:00
197647d97a tests ready 2026-02-19 13:33:20 +03:00
e9e529e322 Coder + fix workflow 2026-02-19 13:33:10 +03:00
bc3ff29d2f Test logic update 2026-02-19 12:44:31 +03:00
eb8ed5da59 task panel 2026-02-19 09:43:01 +03:00
b6ae41d576 docs: amend constitution to v2.3.0 (tailwind css first principle) 2026-02-18 18:29:52 +03:00
cf42de3060 refactor 2026-02-18 17:29:46 +03:00
6062712a92 fix 2026-02-15 11:11:30 +03:00
7790a2dc51 измененные спеки таски 2026-02-10 15:53:38 +03:00
a58bef5c73 updated tasks 2026-02-10 15:04:43 +03:00
232dd947d8 linter + новые таски 2026-02-10 12:53:01 +03:00
33966548d7 Таски готовы 2026-02-09 12:35:27 +03:00
cad6e97464 semantic update 2026-02-08 22:53:54 +03:00
47a3213fb9 таски готовы 2026-02-07 12:42:32 +03:00
303d7272f8 Похоже работает 2026-02-07 11:26:06 +03:00
0711ded532 feat(llm-plugin): switch to environment API for log retrieval
- Replace local backend.log reading with Superset API /log/ fetch
- Update DashboardValidationPlugin to use SupersetClient
- Filter logs by dashboard_id and last 24 hours
- Update spec FR-006 to reflect API usage
2026-02-06 17:57:25 +03:00
495857bbee Semantic protocol update - add UX 2026-01-30 18:53:52 +03:00
df7582a8db tasks ux-reference 2026-01-30 13:35:03 +03:00
3802b0af8c feat(speckit): integrate ux reference into workflows
Introduce a UX reference stage to ensure technical plans align with
user experience goals. Adds a new template, a generation step in the
specification workflow, and mandatory validation checks during
planning to prevent technical compromises from degrading the defined
user experience.
2026-01-30 12:31:19 +03:00
1702f3a5e9 Вроде работает 2026-01-30 11:10:16 +03:00
83c24d4b85 tasks and workflow updated 2026-01-29 10:06:28 +03:00
dd596698e5 docs: amend constitution to v2.0.0 (delegate semantics to protocol + add async/testability principles) 2026-01-28 18:48:43 +03:00
0fee26a846 tasks ready 2026-01-28 18:30:23 +03:00
35096b5e23 semantic update 2026-01-28 16:57:19 +03:00
0299728d72 semantic protocol condense + script update 2026-01-28 15:49:39 +03:00
de6ff0d41b tested 2026-01-27 23:49:19 +03:00
260a90aac5 Передаем на тест 2026-01-27 16:32:08 +03:00
56a1508b38 tasks ready 2026-01-27 13:26:06 +03:00
7c0a601499 Обновил gitignore - убрал логи 2026-01-26 22:15:17 +03:00
a5b1bba226 Закончили редизайн, обновили интерфейс бэкапа 2026-01-26 22:12:35 +03:00
8f13ed3031 Выполнено, передано на тестирование 2026-01-26 21:17:05 +03:00
305b07bf8b tasks ready 2026-01-26 20:58:38 +03:00
4e1992f489 semantic update 2026-01-26 11:57:36 +03:00
ac7a6cfadc Файловое хранилище готово 2026-01-26 11:08:18 +03:00
29daebd628 Передаем на тест 2026-01-25 18:33:00 +03:00
71873b7bb3 tasks ready 2026-01-24 16:21:43 +03:00
68b25c90a8 Update .gitignore 2026-01-24 11:26:19 +03:00
e9b8794f1a Update backup scheduler task status 2026-01-24 11:26:05 +03:00
6d94d26e40 semantic cleanup 2026-01-23 21:58:32 +03:00
598dd50d1d Мультиязночность + причесывание css 2026-01-23 17:53:46 +03:00
eacb88a0e3 tasks ready 2026-01-23 14:56:05 +03:00
10676b7029 Работает создание коммитов и перенос в новый enviroment 2026-01-23 13:57:44 +03:00
2023f6c211 tasks ready 2026-01-22 23:59:16 +03:00
2111c12d0a +gitignore 2026-01-22 23:25:29 +03:00
b46133e4c1 fix error 2026-01-22 23:18:48 +03:00
6cc2fb4c9b refactor complete 2026-01-22 17:37:17 +03:00
c406f71988 ашч 2026-01-21 14:00:48 +03:00
55bdd981b1 fix(backend): standardize superset client init and auth
- Update plugins (debug, mapper, search) to explicitly map environment config to SupersetConfig
- Add authenticate method to SupersetClient for explicit session management
- Add get_environment method to ConfigManager
- Fix navbar dropdown hover stability in frontend with invisible bridge
2026-01-20 19:31:17 +03:00
15843a4607 TaskLog fix 2026-01-19 17:10:43 +03:00
8b81bb9f1f bug fixs 2026-01-19 00:07:06 +03:00
7f244a8252 bug fixes 2026-01-18 23:21:00 +03:00
c0505b4d4f semantic markup update 2026-01-18 21:29:54 +03:00
1b863bea1b semantic checker script update 2026-01-13 17:33:57 +03:00
7c6c959774 constitution update 2026-01-13 15:29:42 +03:00
554e1128b8 semantics update 2026-01-13 09:11:27 +03:00
55ca476972 tasks.md status 2026-01-12 12:35:45 +03:00
4b4d23e671 1st iter 2026-01-12 12:33:51 +03:00
e80369c8b5 tasks ready 2026-01-07 18:59:49 +03:00
ffe942c9dd docs: amend constitution to v1.6.0 (add 'Everything is a Plugin' principle) and refactor 010 plan 2026-01-07 18:36:38 +03:00
19744796e4 Product Manager role 2026-01-07 11:39:44 +03:00
a6bebe295c project map script | semantic parcer 2026-01-01 16:58:21 +03:00
e2ce346b7b backup worked 2025-12-30 22:02:51 +03:00
789e5a90e3 docs ready 2025-12-30 21:30:37 +03:00
163d03e6f5 +api rework 2025-12-30 20:08:48 +03:00
169237b31b cleaned 2025-12-30 18:20:40 +03:00
45bb8c5429 Password promt 2025-12-30 17:21:12 +03:00
17c28433bd TaskManager refactor 2025-12-29 10:13:37 +03:00
077daa0245 mappings+migrate 2025-12-27 10:16:41 +03:00
d38cda09dd tech_lead / coder 2roles 2025-12-27 08:02:59 +03:00
1a893c0bc0 semantic add 2025-12-27 07:14:08 +03:00
40ed375aa4 new loggers logic in constitution 2025-12-27 06:51:28 +03:00
5fdc92fcdf tasks ready 2025-12-27 06:37:03 +03:00
e83328b4ff Merge branch '001-migration-ui-redesign' into master 2025-12-27 05:58:35 +03:00
687f4ce565 superset_tool logger rework 2025-12-27 05:53:30 +03:00
dc9e9e0588 feat(logging): implement configurable belief state logging
- Add LoggingConfig model and logging field to GlobalSettings
- Implement belief_scope context manager for structured logging
- Add configure_logger for dynamic level and file rotation settings
- Add logging configuration UI to Settings page
- Update ConfigManager to apply logging settings on initialization and updates
2025-12-27 05:39:33 +03:00
2de3e53ab2 006 plan ready 2025-12-26 19:36:49 +03:00
40ea0580d9 001-migration-ui-redesign (#3)
Reviewed-on: #3
2025-12-26 18:17:58 +03:00
8da906738b Merge branch 'migration' into 001-migration-ui-redesign 2025-12-26 18:16:24 +03:00
d5a1c0e091 spec rules 2025-12-25 22:28:42 +03:00
ef7a0fcf92 feat(migration): implement interactive mapping resolution workflow
- Add SQLite database integration for environments and mappings
- Update TaskManager to support pausing tasks (AWAITING_MAPPING)
- Modify MigrationPlugin to detect missing mappings and wait for resolution
- Add frontend UI for handling missing mappings interactively
- Create dedicated migration routes and API endpoints
- Update .gitignore and project documentation
2025-12-25 22:27:29 +03:00
14 changed files with 276 additions and 362 deletions

Binary file not shown.

View File

@@ -24,7 +24,7 @@ class EncryptionManager:
# @PRE: ENCRYPTION_KEY env var must be set or use default dev key.
# @POST: Fernet instance ready for encryption/decryption.
def __init__(self):
self.key = os.getenv("ENCRYPTION_KEY", "ZcytYzi0iHIl4Ttr-GdAEk117aGRogkGvN3wiTxrPpE=").encode()
self.key = os.getenv("ENCRYPTION_KEY", "REMOVED_HISTORICAL_SECRET_DO_NOT_USE").encode()
self.fernet = Fernet(self.key)
# [/DEF:EncryptionManager.__init__:Function]

Binary file not shown.

View File

@@ -1,76 +0,0 @@
#!/usr/bin/env python3
"""Debug script to test Superset API authentication"""
from pprint import pprint
from src.core.superset_client import SupersetClient
from src.core.config_manager import ConfigManager
def main():
print("Debugging Superset API authentication...")
config = ConfigManager()
# Select first available environment
environments = config.get_environments()
if not environments:
print("No environments configured")
return
env = environments[0]
print(f"\nTesting environment: {env.name}")
print(f"URL: {env.url}")
try:
# Test API client authentication
print("\n--- Testing API Authentication ---")
client = SupersetClient(env)
tokens = client.authenticate()
print("\nAPI Auth Success!")
print(f"Access Token: {tokens.get('access_token', 'N/A')}")
print(f"CSRF Token: {tokens.get('csrf_token', 'N/A')}")
# Debug cookies from session
print("\n--- Session Cookies ---")
for cookie in client.network.session.cookies:
print(f"{cookie.name}={cookie.value}")
# Test accessing UI via requests
print("\n--- Testing UI Access ---")
ui_url = env.url.rstrip('/').replace('/api/v1', '')
print(f"UI URL: {ui_url}")
# Try to access UI home page
ui_response = client.network.session.get(ui_url, timeout=30, allow_redirects=True)
print(f"Status Code: {ui_response.status_code}")
print(f"URL: {ui_response.url}")
# Check response headers
print("\n--- Response Headers ---")
pprint(dict(ui_response.headers))
print("\n--- Response Content Preview (200 chars) ---")
print(repr(ui_response.text[:200]))
if ui_response.status_code == 200:
print("\nUI Access: Success")
# Try to access a dashboard
# For testing, just use the home page
print("\n--- Checking if login is required ---")
if "login" in ui_response.url.lower() or "login" in ui_response.text.lower():
print("❌ Not logged in to UI")
else:
print("✅ Logged in to UI")
except Exception as e:
print(f"\n❌ Error: {type(e).__name__}: {e}")
import traceback
print("\nStack Trace:")
print(traceback.format_exc())
if __name__ == "__main__":
main()

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env python3
"""Test script to debug API key decryption issue."""
from src.core.database import SessionLocal
from src.models.llm import LLMProvider
from cryptography.fernet import Fernet
import os
# Get the encryption key
key = os.getenv("ENCRYPTION_KEY", "ZcytYzi0iHIl4Ttr-GdAEk117aGRogkGvN3wiTxrPpE=").encode()
print(f"Encryption key (first 20 chars): {key[:20]}")
print(f"Encryption key length: {len(key)}")
# Create Fernet instance
fernet = Fernet(key)
# Get provider from database
db = SessionLocal()
provider = db.query(LLMProvider).filter(LLMProvider.id == '6c899741-4108-4196-aea4-f38ad2f0150e').first()
if provider:
print("\nProvider found:")
print(f" ID: {provider.id}")
print(f" Name: {provider.name}")
print(f" Encrypted API Key (first 50 chars): {provider.api_key[:50]}")
print(f" Encrypted API Key Length: {len(provider.api_key)}")
# Test decryption
print("\nAttempting decryption...")
try:
decrypted = fernet.decrypt(provider.api_key.encode()).decode()
print("Decryption successful!")
print(f" Decrypted key length: {len(decrypted)}")
print(f" Decrypted key (first 8 chars): {decrypted[:8]}")
print(f" Decrypted key is empty: {len(decrypted) == 0}")
except Exception as e:
print(f"Decryption failed with error: {e}")
print(f"Error type: {type(e).__name__}")
import traceback
traceback.print_exc()
else:
print("Provider not found")
db.close()

View File

@@ -1 +0,0 @@
[{"key[": 20, ")\n\n# Create Fernet instance\nfernet = Fernet(key)\n\n# Test encrypting an empty string\nempty_encrypted = fernet.encrypt(b\"": ".", "print(f": "nEncrypted empty string: {empty_encrypted"}, {"test-api-key-12345\"\ntest_encrypted = fernet.encrypt(test_key.encode()).decode()\nprint(f": "nEncrypted test key: {test_encrypted"}, {"gAAAAABphhwSZie0OwXjJ78Fk-c4Uo6doNJXipX49AX7Bypzp4ohiRX3hXPXKb45R1vhNUOqbm6Ke3-eRwu_KdWMZ9chFBKmqw==\"\nprint(f": "nStored encrypted key: {stored_key"}, {"len(stored_key)}": "Check if stored key matches empty string encryption\nif stored_key == empty_encrypted:\n print(", "string!": "else:\n print(", "print(f": "mpty string encryption: {empty_encrypted"}, {"stored_key}": "Try to decrypt the stored key\ntry:\n decrypted = fernet.decrypt(stored_key.encode()).decode()\n print(f", "print(f": "ecrypted key length: {len(decrypted)"}, {")\nexcept Exception as e:\n print(f": "nDecryption failed with error: {e"}]

View File

@@ -2,7 +2,8 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
<link rel="alternate icon" type="image/png" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>

View File

@@ -14,6 +14,7 @@
import { page } from "$app/stores";
import { t, _ } from "$lib/i18n";
import Icon from "$lib/ui/Icon.svelte";
let { maxVisible = 3 } = $props();
@@ -82,30 +83,103 @@
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
function getCrumbMeta(item) {
if (item.path === "/") {
return {
icon: "home",
tone: "from-sky-100 to-cyan-100 text-sky-700 ring-sky-200",
};
}
const segment = item.path.split("/").filter(Boolean).at(-1) || "";
const map = {
dashboards: {
icon: "dashboard",
tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200",
},
datasets: {
icon: "database",
tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200",
},
storage: {
icon: "storage",
tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200",
},
reports: {
icon: "reports",
tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200",
},
admin: {
icon: "admin",
tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200",
},
settings: {
icon: "settings",
tone: "from-slate-100 to-slate-200 text-slate-700 ring-slate-200",
},
git: {
icon: "storage",
tone: "from-orange-100 to-orange-200 text-orange-700 ring-orange-200",
},
};
return (
map[segment] || {
icon: "layers",
tone: "from-slate-100 to-slate-200 text-slate-600 ring-slate-200",
}
);
}
</script>
<nav
class="flex items-center space-x-2 text-sm text-gray-600"
class="mx-4 md:mx-6"
aria-label="Breadcrumb navigation"
>
{#each breadcrumbItems as item, index}
<div class="flex items-center">
{#if item.isEllipsis}
<span class="text-gray-400">...</span>
{:else if item.isLast}
<span class="text-gray-900 font-medium">{item.label}</span>
{:else}
<a
href={item.path}
class="hover:text-primary hover:underline cursor-pointer transition-colors"
>{item.label}</a
>
<div class="inline-flex max-w-full items-center gap-1.5 rounded-xl border border-slate-200/80 bg-white/85 px-2 py-1.5 shadow-sm backdrop-blur">
{#each breadcrumbItems as item, index}
<div class="flex min-w-0 items-center gap-1.5">
{#if item.isEllipsis}
<span class="px-2 py-1 text-xs font-semibold tracking-wide text-slate-400"
>...</span
>
{:else}
{@const meta = getCrumbMeta(item)}
{#if item.isLast}
<span
class="inline-flex min-w-0 items-center gap-2 rounded-lg bg-slate-900 px-2.5 py-1.5 text-sm font-medium text-white"
>
<span
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-white/10"
>
<Icon name={meta.icon} size={12} strokeWidth={2.1} />
</span>
<span class="truncate">{item.label}</span>
</span>
{:else}
<a
href={item.path}
class="inline-flex min-w-0 items-center gap-2 rounded-lg px-2.5 py-1.5 text-sm text-slate-700 ring-1 ring-transparent transition-all hover:bg-slate-50 hover:ring-slate-200"
>
<span
class="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md bg-gradient-to-br ring-1 {meta.tone}"
>
<Icon name={meta.icon} size={12} strokeWidth={2.1} />
</span>
<span class="truncate">{item.label}</span>
</a>
{/if}
{/if}
</div>
{#if index < breadcrumbItems.length - 1}
<span class="text-slate-300">
<Icon name="chevronRight" size={14} strokeWidth={2.1} />
</span>
{/if}
</div>
{#if index < breadcrumbItems.length - 1}
<span class="text-gray-400">/</span>
{/if}
{/each}
{/each}
</div>
</nav>
<!-- [/DEF:Breadcrumbs:Component] -->

View File

@@ -24,59 +24,68 @@
} from "$lib/stores/sidebar.js";
import { t } from "$lib/i18n";
import { browser } from "$app/environment";
import Icon from "$lib/ui/Icon.svelte";
// Sidebar categories with sub-items matching Superset-style navigation
let categories = [
{
id: "dashboards",
label: $t.nav?.dashboards || "DASHBOARDS",
icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z",
path: "/dashboards",
subItems: [
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
],
},
{
id: "datasets",
label: $t.nav?.datasets || "DATASETS",
icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z",
path: "/datasets",
subItems: [
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
],
},
{
id: "storage",
label: $t.nav?.storage || "STORAGE",
icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z",
path: "/storage",
subItems: [
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
{
label: $t.nav?.repositories || "Repositories",
path: "/storage/repos",
},
],
},
{
id: "reports",
label: $t.nav?.reports || "REPORTS",
icon: "M4 5h16M4 12h16M4 19h10",
path: "/reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
},
{
id: "admin",
label: $t.nav?.admin || "ADMIN",
icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z",
path: "/admin",
subItems: [
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
{ label: $t.nav?.settings || "Settings", path: "/settings" },
],
},
];
function buildCategories() {
return [
{
id: "dashboards",
label: $t.nav?.dashboards || "DASHBOARDS",
icon: "dashboard",
tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200",
path: "/dashboards",
subItems: [
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
],
},
{
id: "datasets",
label: $t.nav?.datasets || "DATASETS",
icon: "database",
tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200",
path: "/datasets",
subItems: [
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
],
},
{
id: "storage",
label: $t.nav?.storage || "STORAGE",
icon: "storage",
tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200",
path: "/storage",
subItems: [
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
{
label: $t.nav?.repositories || "Repositories",
path: "/storage/repos",
},
],
},
{
id: "reports",
label: $t.nav?.reports || "REPORTS",
icon: "reports",
tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200",
path: "/reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
},
{
id: "admin",
label: $t.nav?.admin || "ADMIN",
icon: "admin",
tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200",
path: "/admin",
subItems: [
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
{ label: $t.nav?.settings || "Settings", path: "/settings" },
],
},
];
}
let categories = buildCategories();
let isExpanded = true;
let activeCategory = "dashboards";
@@ -93,57 +102,7 @@
}
// Reactive categories to update translations
$: categories = [
{
id: "dashboards",
label: $t.nav?.dashboards || "DASHBOARDS",
icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z",
path: "/dashboards",
subItems: [
{ label: $t.nav?.overview || "Overview", path: "/dashboards" },
],
},
{
id: "datasets",
label: $t.nav?.datasets || "DATASETS",
icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z",
path: "/datasets",
subItems: [
{ label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" },
],
},
{
id: "storage",
label: $t.nav?.storage || "STORAGE",
icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z",
path: "/storage",
subItems: [
{ label: $t.nav?.backups || "Backups", path: "/storage/backups" },
{
label: $t.nav?.repositories || "Repositories",
path: "/storage/repos",
},
],
},
{
id: "reports",
label: $t.nav?.reports || "REPORTS",
icon: "M4 5h16M4 12h16M4 19h10",
path: "/reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
},
{
id: "admin",
label: $t.nav?.admin || "ADMIN",
icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z",
path: "/admin",
subItems: [
{ label: $t.nav?.admin_users || "Users", path: "/admin/users" },
{ label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" },
{ label: $t.nav?.settings || "Settings", path: "/settings" },
],
},
];
$: categories = buildCategories();
// Update active item when page changes
$: if ($page && $page.url.pathname !== activeItem) {
@@ -238,7 +197,12 @@
: 'justify-center'}"
>
{#if isExpanded}
<span class="font-semibold text-gray-800">Menu</span>
<span class="font-semibold text-gray-800 flex items-center gap-2">
<span class="inline-flex h-6 w-6 items-center justify-center rounded-md bg-gradient-to-br from-slate-100 to-slate-200 text-slate-700 ring-1 ring-slate-200">
<Icon name="layers" size={14} />
</span>
Menu
</span>
{:else}
<span class="text-xs text-gray-500">M</span>
{/if}
@@ -264,16 +228,9 @@
aria-expanded={expandedCategories.has(category.id)}
>
<div class="flex items-center">
<svg
class="w-5 h-5 shrink-0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d={category.icon} />
</svg>
<span class="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br ring-1 transition-all {category.tone}">
<Icon name={category.icon} size={16} strokeWidth={2} />
</span>
{#if isExpanded}
<span class="ml-3 text-sm font-medium truncate"
>{category.label}</span
@@ -281,22 +238,15 @@
{/if}
</div>
{#if isExpanded}
<svg
<Icon
name="chevronDown"
size={16}
class="text-gray-400 transition-transform duration-200 {expandedCategories.has(
category.id,
)
? 'rotate-180'
: ''}"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M6 9l6 6 6-6" />
</svg>
/>
{/if}
</div>
@@ -332,18 +282,9 @@
class="flex items-center justify-center w-full px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
on:click={handleToggleClick}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
class="mr-2"
>
<path d="M15 18l-6-6 6-6" />
</svg>
<span class="mr-2 inline-flex h-6 w-6 items-center justify-center rounded-md bg-slate-100 text-slate-600">
<Icon name="chevronLeft" size={14} />
</span>
Collapse
</button>
</div>
@@ -354,17 +295,7 @@
on:click={handleToggleClick}
aria-label="Expand sidebar"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M9 18l6-6-6-6" />
</svg>
<Icon name="chevronRight" size={16} />
<span class="ml-2">Expand</span>
</button>
</div>

View File

@@ -24,6 +24,7 @@
import PasswordPrompt from "../../../components/PasswordPrompt.svelte";
import { t } from "$lib/i18n";
import { api } from "$lib/api.js";
import Icon from "$lib/ui/Icon.svelte";
let isOpen = false;
let activeTaskId = null;
@@ -209,9 +210,7 @@
<div class="flex items-center gap-2.5">
{#if !activeTaskId && recentTasks.length > 0}
<span class="flex items-center justify-center p-1.5 mr-1 text-cyan-400">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>
</svg>
<Icon name="list" size={16} strokeWidth={2} />
</span>
{:else if activeTaskId}
<button
@@ -219,17 +218,7 @@
on:click={goBackToList}
aria-label="Back to task list"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M19 12H5M12 19l-7-7 7-7" />
</svg>
<Icon name="back" size={16} strokeWidth={2} />
</button>
{/if}
<h2 class="text-sm font-semibold text-slate-100 tracking-tight">
@@ -256,17 +245,7 @@
on:click={handleClose}
aria-label="Close drawer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
<Icon name="close" size={18} strokeWidth={2} />
</button>
</div>
</div>
@@ -301,18 +280,12 @@
</div>
{:else}
<div class="flex flex-col items-center justify-center h-full text-slate-500">
<svg
class="w-12 h-12 mb-3 text-slate-700"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
/>
</svg>
<Icon
name="clipboard"
size={48}
strokeWidth={1.6}
className="mb-3 text-slate-700"
/>
<p>{$t.tasks?.select_task || 'No recent tasks'}</p>
</div>
{/if}
@@ -330,4 +303,3 @@
{/if}
<!-- [/DEF:TaskDrawer:Component] -->

View File

@@ -25,6 +25,7 @@
import { sidebarStore, toggleMobileSidebar } from "$lib/stores/sidebar.js";
import { t } from "$lib/i18n";
import { auth } from "$lib/auth/store.js";
import Icon from "$lib/ui/Icon.svelte";
const dispatch = createEventDispatcher();
@@ -99,19 +100,7 @@
on:click={handleHamburgerClick}
aria-label="Toggle menu"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<Icon name="menu" size={22} />
</button>
<!-- Logo/Brand -->
@@ -119,14 +108,9 @@
href="/"
class="flex items-center text-xl font-bold text-gray-800 hover:text-primary transition-colors"
>
<svg
class="w-8 h-8 mr-2 text-primary"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<span class="mr-2 inline-flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-sky-500 via-cyan-500 to-indigo-600 text-white shadow-sm">
<Icon name="layers" size={18} strokeWidth={2.1} />
</span>
<span>Superset Tools</span>
</a>
</div>
@@ -147,7 +131,7 @@
<div class="flex items-center space-x-4">
<!-- Activity Indicator -->
<div
class="relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors"
class="relative cursor-pointer p-2 rounded-lg hover:bg-gray-100 transition-colors text-slate-600"
on:click={handleActivityClick}
on:keydown={(e) =>
(e.key === "Enter" || e.key === " ") && handleActivityClick()}
@@ -155,18 +139,7 @@
tabindex="0"
aria-label="Activity"
>
<svg
class="w-6 h-6 text-gray-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"
/>
</svg>
<Icon name="activity" size={22} />
{#if activeCount > 0}
<span
class="absolute -top-1 -right-1 bg-destructive text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center"

View File

@@ -0,0 +1,66 @@
<script>
export let name = "circle";
export let size = 20;
export let className = "";
export let strokeWidth = 1.9;
const iconPaths = {
home: ["M3 11l9-7 9 7", "M5 10v9h14v-9", "M10 19v-5h4v5"],
dashboard: ["M4 4h16v16H4z", "M4 10h16", "M10 4v16"],
database: [
"M4 7c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z",
"M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3",
"M4 17c0 1.7 3.6 3 8 3s8-1.3 8-3",
"M4 7v10",
"M20 7v10",
],
storage: [
"M3 8l9-4 9 4-9 4-9-4z",
"M3 13l9 4 9-4",
"M3 17l9 4 9-4",
],
reports: ["M5 5h14v14H5z", "M8 9h8", "M8 13h8", "M8 17h5"],
admin: ["M12 3l8 4v5c0 5.2-3.4 8.6-8 9.9C7.4 20.6 4 17.2 4 12V7l8-4z", "M9 12l2 2 4-4"],
chevronDown: ["M6 9l6 6 6-6"],
chevronLeft: ["M15 6l-6 6 6 6"],
chevronRight: ["M9 6l6 6-6 6"],
menu: ["M4 7h16", "M4 12h16", "M4 17h16"],
activity: [
"M12 3v3",
"M12 18v3",
"M4.9 4.9l2.1 2.1",
"M17 17l2.1 2.1",
"M3 12h3",
"M18 12h3",
"M4.9 19.1L7 17",
"M17 7l2.1-2.1",
"M12 15a3 3 0 100-6 3 3 0 000 6z",
],
layers: ["M12 4l8 4-8 4-8-4 8-4z", "M4 12l8 4 8-4", "M4 16l8 4 8-4"],
back: ["M19 12H5", "M12 5l-7 7 7 7"],
close: ["M18 6L6 18", "M6 6l12 12"],
list: ["M8 7h12", "M8 12h12", "M8 17h12", "M4 7h.01", "M4 12h.01", "M4 17h.01"],
clipboard: ["M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2", "M9 5a2 2 0 002 2h2a2 2 0 002-2", "M9 5a2 2 0 012-2h2a2 2 0 012 2"],
settings: ["M12 8.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z", "M19.4 15a1 1 0 00.2 1.1l.1.1a1 1 0 010 1.4l-1.1 1.1a1 1 0 01-1.4 0l-.1-.1a1 1 0 00-1.1-.2 1 1 0 00-.6.9V20a1 1 0 01-1 1h-1.6a1 1 0 01-1-1v-.2a1 1 0 00-.6-.9 1 1 0 00-1.1.2l-.1.1a1 1 0 01-1.4 0l-1.1-1.1a1 1 0 010-1.4l.1-.1a1 1 0 00.2-1.1 1 1 0 00-.9-.6H4a1 1 0 01-1-1v-1.6a1 1 0 011-1h.2a1 1 0 00.9-.6 1 1 0 00-.2-1.1l-.1-.1a1 1 0 010-1.4l1.1-1.1a1 1 0 011.4 0l.1.1a1 1 0 001.1.2 1 1 0 00.6-.9V4a1 1 0 011-1h1.6a1 1 0 011 1v.2a1 1 0 00.6.9 1 1 0 001.1-.2l.1-.1a1 1 0 011.4 0l1.1 1.1a1 1 0 010 1.4l-.1.1a1 1 0 00-.2 1.1 1 1 0 00.9.6H20a1 1 0 011 1v1.6a1 1 0 01-1 1h-.2a1 1 0 00-.9.6z"],
};
$: paths = iconPaths[name] || iconPaths.dashboard;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width={strokeWidth}
stroke-linecap="round"
stroke-linejoin="round"
class={className}
aria-hidden="true"
>
{#each paths as d}
<path d={d} />
{/each}
</svg>

View File

@@ -56,7 +56,7 @@
<!-- Top Navigation Bar -->
<TopNavbar />
<!-- Breadcrumbs -->
<div class="mt-16">
<div class="mt-16 pt-3">
<Breadcrumbs />
</div>

View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<linearGradient id="bg" x1="10%" y1="0%" x2="90%" y2="100%">
<stop offset="0%" stop-color="#0EA5E9" />
<stop offset="45%" stop-color="#06B6D4" />
<stop offset="100%" stop-color="#2563EB" />
</linearGradient>
<linearGradient id="stack" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF" />
<stop offset="100%" stop-color="#E2E8F0" />
</linearGradient>
</defs>
<rect x="4" y="4" width="56" height="56" rx="16" fill="url(#bg)" />
<path d="M32 16 14 24l18 8 18-8-18-8Z" fill="url(#stack)" opacity="0.98" />
<path d="m14 33 18 8 18-8" fill="none" stroke="#F8FAFC" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
<path d="m14 42 18 8 18-8" fill="none" stroke="#F8FAFC" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" opacity="0.92" />
<path d="M49 14v6M46 17h6" stroke="#F8FAFC" stroke-width="2.2" stroke-linecap="round" />
</svg>

After

Width:  |  Height:  |  Size: 1013 B