diff --git a/agent/src/disks.rs b/agent/src/disks.rs new file mode 100644 index 00000000..a4d01292 --- /dev/null +++ b/agent/src/disks.rs @@ -0,0 +1,197 @@ +use serde::Serialize; +use std::path::Path; +use sysinfo::Disks; +use tokio::process::Command; + +#[derive(Debug, Clone, Serialize)] +pub struct SmartInfo { + pub health: String, + pub power_on_hours: Option, + pub temperature_celsius: Option, + pub reallocated_sectors: Option, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DiskInfo { + pub device: String, + pub model: Option, + pub media_type: String, + pub total_bytes: u64, + pub used_bytes: u64, + pub mount_point: Option, + pub smart: Option, +} + +pub async fn collect_disks() -> Vec { + if std::env::var("CSFX_FAKE_DISKS").is_ok() { + return fake_disks(); + } + + let block_devices = list_block_devices(); + let mounts = Disks::new_with_refreshed_list(); + + let mut disks = Vec::with_capacity(block_devices.len()); + for device in block_devices { + let media_type = detect_media_type(&device); + let model = read_sys_string(&device, "device/model"); + let mount = mounts + .iter() + .find(|d| d.name().to_string_lossy().contains(&device)); + + let total_bytes = mount.map(|d| d.total_space()).unwrap_or(0); + let used_bytes = mount + .map(|d| d.total_space() - d.available_space()) + .unwrap_or(0); + let mount_point = mount.map(|d| d.mount_point().to_string_lossy().to_string()); + + disks.push(DiskInfo { + smart: collect_smart(&device).await, + device, + model, + media_type, + total_bytes, + used_bytes, + mount_point, + }); + } + + disks +} + +fn fake_disks() -> Vec { + vec![ + DiskInfo { + device: "nvme0n1".to_string(), + model: Some("Samsung SSD 990 PRO 2TB".to_string()), + media_type: "nvme".to_string(), + total_bytes: 2_000_398_934_016, + used_bytes: 812_000_000_000, + mount_point: Some("/".to_string()), + smart: Some(SmartInfo { + health: "healthy".to_string(), + power_on_hours: Some(3120), + temperature_celsius: Some(41.0), + reallocated_sectors: Some(0), + }), + }, + DiskInfo { + device: "sda".to_string(), + model: Some("Seagate IronWolf 8TB".to_string()), + media_type: "hdd".to_string(), + total_bytes: 8_001_563_222_016, + used_bytes: 5_200_000_000_000, + mount_point: Some("/mnt/csfx-volumes".to_string()), + smart: Some(SmartInfo { + health: "healthy".to_string(), + power_on_hours: Some(18544), + temperature_celsius: Some(34.0), + reallocated_sectors: Some(0), + }), + }, + DiskInfo { + device: "sdb".to_string(), + model: Some("Seagate IronWolf 8TB".to_string()), + media_type: "hdd".to_string(), + total_bytes: 8_001_563_222_016, + used_bytes: 7_600_000_000_000, + mount_point: None, + smart: Some(SmartInfo { + health: "failing".to_string(), + power_on_hours: Some(31022), + temperature_celsius: Some(52.0), + reallocated_sectors: Some(12), + }), + }, + ] +} + +fn list_block_devices() -> Vec { + let Ok(entries) = std::fs::read_dir("/sys/block") else { + return Vec::new(); + }; + + entries + .filter_map(|e| e.ok()) + .filter_map(|e| e.file_name().into_string().ok()) + .filter(|name| !name.starts_with("loop") && !name.starts_with("ram")) + .collect() +} + +fn read_sys_string(device: &str, relative_path: &str) -> Option { + let path = format!("/sys/block/{device}/{relative_path}"); + std::fs::read_to_string(path) + .ok() + .map(|v| v.trim().to_string()) + .filter(|v| !v.is_empty()) +} + +fn detect_media_type(device: &str) -> String { + if device.starts_with("nvme") { + return "nvme".to_string(); + } + match read_sys_string(device, "queue/rotational").as_deref() { + Some("0") => "ssd".to_string(), + Some("1") => "hdd".to_string(), + _ => "unknown".to_string(), + } +} + +async fn collect_smart(device: &str) -> Option { + if !Path::new("/dev").join(device).exists() { + return None; + } + + let output = Command::new("smartctl") + .args(["-j", "-a", &format!("/dev/{device}")]) + .output() + .await + .ok()?; + + let json: serde_json::Value = serde_json::from_slice(&output.stdout).ok()?; + parse_smart_json(&json) +} + +fn parse_smart_json(json: &serde_json::Value) -> Option { + let passed = json + .get("smart_status") + .and_then(|s| s.get("passed")) + .and_then(|p| p.as_bool()); + + let health = match passed { + Some(true) => "healthy", + Some(false) => "failing", + None => return None, + } + .to_string(); + + let power_on_hours = json + .get("power_on_time") + .and_then(|p| p.get("hours")) + .and_then(|h| h.as_u64()); + + let temperature_celsius = json + .get("temperature") + .and_then(|t| t.get("current")) + .and_then(|c| c.as_f64()) + .map(|c| c as f32); + + let reallocated_sectors = json + .get("ata_smart_attributes") + .and_then(|a| a.get("table")) + .and_then(|t| t.as_array()) + .and_then(|attrs| { + attrs + .iter() + .find(|a| a.get("id").and_then(|i| i.as_u64()) == Some(5)) + }) + .and_then(|a| a.get("raw")) + .and_then(|r| r.get("value")) + .and_then(|v| v.as_u64()); + + Some(SmartInfo { + health, + power_on_hours, + temperature_celsius, + reallocated_sectors, + }) +} diff --git a/agent/src/main.rs b/agent/src/main.rs index 47dd6e01..aaab1e33 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -1,6 +1,7 @@ mod agent_stream; mod client; mod config; +mod disks; mod firecracker; mod nftables; mod pki; diff --git a/agent/src/qemu/runtime.rs b/agent/src/qemu/runtime.rs index dfe69726..a8e2d45a 100644 --- a/agent/src/qemu/runtime.rs +++ b/agent/src/qemu/runtime.rs @@ -108,7 +108,10 @@ impl QemuRuntime { match crate::runtime::Runtime::list_managed_workloads(self).await { Ok(recovered) => { - info!(count = recovered.len(), "Reconciled running vms from disk state"); + info!( + count = recovered.len(), + "Reconciled running vms from disk state" + ); recovered } Err(e) => { diff --git a/agent/src/server.rs b/agent/src/server.rs index efd1f426..c17f2fca 100644 --- a/agent/src/server.rs +++ b/agent/src/server.rs @@ -26,6 +26,7 @@ use crate::system::LiveMetricsCollector; const TICKET_HEADER: &str = "X-Csfx-Proxy-Ticket"; pub const METRICS_TICKET_SCOPE: &str = "__node_metrics__"; pub const POWER_TICKET_SCOPE: &str = "__power__"; +pub const DISKS_TICKET_SCOPE: &str = "__disks__"; #[derive(Clone)] pub struct ServerState { @@ -42,6 +43,7 @@ pub async fn run(state: ServerState, port: u16) -> Result<()> { .route("/vnc/{workload_id}", get(vnc_handler)) .route("/metrics/stream", get(metrics_stream_handler)) .route("/power", post(power_handler)) + .route("/disks", get(disks_handler)) .with_state(state); let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port); @@ -445,6 +447,22 @@ impl PowerAction { } } +async fn disks_handler( + State(state): State, + ConnectInfo(addr): ConnectInfo, + headers: HeaderMap, +) -> Result>, (StatusCode, String)> { + if !is_internal_source(&addr) { + warn!(source = %addr, "rejected agent inbound request from non-internal source"); + return Err((StatusCode::FORBIDDEN, "source not allowed".to_string())); + } + + verify_ticket(&headers, DISKS_TICKET_SCOPE, &state.agent_id.to_string()) + .map_err(|e| (StatusCode::UNAUTHORIZED, e.to_string()))?; + + Ok(Json(crate::disks::collect_disks().await)) +} + async fn power_handler( State(state): State, ConnectInfo(addr): ConnectInfo, diff --git a/agent/src/system.rs b/agent/src/system.rs index 8e2fe2b1..9a4184d4 100644 --- a/agent/src/system.rs +++ b/agent/src/system.rs @@ -23,23 +23,27 @@ pub struct SystemMetrics { pub struct LiveMetricsCollector { sys: System, + simulator: Option, } impl LiveMetricsCollector { pub fn new() -> Self { let mut sys = System::new_all(); sys.refresh_cpu_usage(); - Self { sys } + let simulator = std::env::var("CSFX_SIMULATE_LOAD") + .ok() + .filter(|v| v == "1" || v.eq_ignore_ascii_case("true")) + .map(|_| LoadSimulator::new(sys.total_memory(), sys.cpus().len() as u32)); + Self { sys, simulator } } pub fn sample(&mut self) -> SystemMetrics { self.sys.refresh_cpu_usage(); self.sys.refresh_memory(); - let cpu_usage_percent = self.sys.global_cpu_usage(); - let cpu_cores = self.sys.cpus().len() as u32; - let memory_total_bytes = self.sys.total_memory(); - let memory_used_bytes = self.sys.used_memory(); + if let Some(simulator) = &mut self.simulator { + return simulator.sample(); + } let disks = Disks::new_with_refreshed_list(); let (disk_total_bytes, disk_used_bytes) = @@ -50,6 +54,11 @@ impl LiveMetricsCollector { ) }); + let cpu_usage_percent = self.sys.global_cpu_usage(); + let cpu_cores = self.sys.cpus().len() as u32; + let memory_total_bytes = self.sys.total_memory(); + let memory_used_bytes = self.sys.used_memory(); + let networks = Networks::new_with_refreshed_list(); let (network_rx_bytes, network_tx_bytes) = networks.iter().fold((0u64, 0u64), |(rx, tx), (_, data)| { @@ -70,6 +79,77 @@ impl LiveMetricsCollector { } } +struct LoadSimulator { + memory_total_bytes: u64, + disk_total_bytes: u64, + cpu_cores: u32, + cpu_usage_percent: f32, + memory_usage_percent: f32, + disk_usage_percent: f32, + network_rx_bytes: u64, + network_tx_bytes: u64, + seed: u64, +} + +impl LoadSimulator { + fn new(memory_total_bytes: u64, cpu_cores: u32) -> Self { + Self { + memory_total_bytes, + disk_total_bytes: 512 * 1_073_741_824, + cpu_cores: cpu_cores.max(1), + cpu_usage_percent: 25.0, + memory_usage_percent: 35.0, + disk_usage_percent: 42.0, + network_rx_bytes: 0, + network_tx_bytes: 0, + seed: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_nanos() as u64) + .unwrap_or(1), + } + } + + fn next_random(&mut self) -> f32 { + self.seed ^= self.seed << 13; + self.seed ^= self.seed >> 7; + self.seed ^= self.seed << 17; + (self.seed % 10_000) as f32 / 10_000.0 + } + + fn walk(&mut self, value: f32, max_step: f32, min: f32, max: f32) -> f32 { + let step = (self.next_random() - 0.5) * 2.0 * max_step; + (value + step).clamp(min, max) + } + + fn sample(&mut self) -> SystemMetrics { + self.cpu_usage_percent = self.walk(self.cpu_usage_percent, 8.0, 3.0, 95.0); + self.memory_usage_percent = self.walk(self.memory_usage_percent, 3.0, 10.0, 90.0); + self.disk_usage_percent = self.walk(self.disk_usage_percent, 0.2, 5.0, 85.0); + + let rx_rate_bytes = (self.walk(2.0, 1.5, 0.05, 12.0) * 1_048_576.0) as u64; + let tx_rate_bytes = (self.walk(0.8, 0.6, 0.02, 5.0) * 1_048_576.0) as u64; + self.network_rx_bytes += rx_rate_bytes; + self.network_tx_bytes += tx_rate_bytes; + + let memory_used_bytes = + ((self.memory_usage_percent / 100.0) as f64 * self.memory_total_bytes as f64) as u64; + let disk_used_bytes = + ((self.disk_usage_percent / 100.0) as f64 * self.disk_total_bytes as f64) as u64; + + SystemMetrics { + cpu_usage_percent: self.cpu_usage_percent, + cpu_cores: self.cpu_cores, + memory_total_bytes: self.memory_total_bytes, + memory_used_bytes, + disk_total_bytes: self.disk_total_bytes, + disk_used_bytes, + network_rx_bytes: self.network_rx_bytes, + network_tx_bytes: self.network_tx_bytes, + uptime_seconds: System::uptime(), + } + } +} + fn parse_os_release_field(content: &str, field: &str) -> Option { content .lines() diff --git a/app/.gitignore b/app/.gitignore index f8d7fc2f..13ddb275 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -24,3 +24,6 @@ vite.config.ts.timestamp-* .DS_Store .Thumbs.db + +*storybook.log +storybook-static diff --git a/app/.storybook/main.ts b/app/.storybook/main.ts new file mode 100644 index 00000000..8a498706 --- /dev/null +++ b/app/.storybook/main.ts @@ -0,0 +1,22 @@ +import type { StorybookConfig } from '@storybook/sveltekit'; +import tailwindcss from '@tailwindcss/vite'; + +const config: StorybookConfig = { + "stories": [ + "../src/**/*.stories.@(js|ts|svelte)" + ], + "addons": [ + "@storybook/addon-svelte-csf", + "@chromatic-com/storybook", + "@storybook/addon-vitest", + "@storybook/addon-a11y", + "@storybook/addon-docs" + ], + "framework": "@storybook/sveltekit", + async viteFinal(viteConfig) { + viteConfig.plugins ??= []; + viteConfig.plugins.push(tailwindcss()); + return viteConfig; + } +}; +export default config; \ No newline at end of file diff --git a/app/.storybook/preview.ts b/app/.storybook/preview.ts new file mode 100644 index 00000000..c69c04af --- /dev/null +++ b/app/.storybook/preview.ts @@ -0,0 +1,18 @@ +import type { Preview } from '@storybook/sveltekit' +import '../src/routes/layout.css' + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + a11y: { + test: 'todo' + } + }, +}; + +export default preview; \ No newline at end of file diff --git a/app/package-lock.json b/app/package-lock.json index f4ef194d..f1734c7d 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -8,26 +8,39 @@ "name": "app", "version": "0.0.1", "dependencies": { + "@blobatar/svelte": "^2.7.0", "@iconify/svelte": "^5.2.1", "@jis3r/icons": "^2.7.0", "@novnc/novnc": "^1.7.0", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", + "blobatar": "^2.7.0", "geist": "^1.7.0" }, "devDependencies": { + "@chromatic-com/storybook": "latest", "@fontsource-variable/inter": "^5.2.8", "@internationalized/date": "^3.12.1", "@lucide/svelte": "^1.23.0", + "@storybook/addon-a11y": "^10.6.0", + "@storybook/addon-docs": "^10.6.0", + "@storybook/addon-svelte-csf": "latest", + "@storybook/addon-vitest": "^10.6.0", + "@storybook/sveltekit": "^10.6.0", "@sveltejs/adapter-auto": "^7.0.1", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.57.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", "@tailwindcss/vite": "^4.2.2", + "@vitest/browser-playwright": "latest", + "@vitest/coverage-v8": "latest", "bits-ui": "^2.18.0", "clsx": "^2.1.1", + "layerchart": "^2.5.0", "mode-watcher": "^1.1.0", + "playwright": "latest", "shadcn-svelte": "^1.2.7", + "storybook": "^10.6.0", "svelte": "^5.55.2", "svelte-check": "^4.4.6", "svelte-sonner": "^1.1.1", @@ -36,9 +49,163 @@ "tailwindcss": "^4.2.2", "tw-animate-css": "^1.4.0", "typescript": "^6.0.2", - "vite": "^8.0.7" + "vite": "^8.0.7", + "vitest": "latest" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@blobatar/svelte": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@blobatar/svelte/-/svelte-2.7.0.tgz", + "integrity": "sha512-Pf/RE5ICEUatbWloaBa4vGLRx+HRsKNN/eNCCHp1Nksgs3+3VuutpZBB61Y169fa0uMgAqPl72fAr+GcmnmtHA==", + "license": "MIT", + "peerDependencies": { + "blobatar": "2.x", + "svelte": ">=5" + } + }, + "node_modules/@chromatic-com/storybook": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.3.1.tgz", + "integrity": "sha512-eUcbR9CddVmKAnjOiBCOz5rNdT096jiXIjR+qaWbUuTlgRdpc9yjTrk4/hiPAAykZrPYVNJN11p6rJgW9KNpKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@neoconfetti/react": "^1.0.0", + "chromatic": "^18.0.1", + "jsonfile": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20.0.0", + "yarn": ">=1.22.18" + }, + "peerDependencies": { + "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 || ^11.0.0-0" + } + }, + "node_modules/@dagrejs/dagre": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-2.0.4.tgz", + "integrity": "sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "3.0.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-3.0.4.tgz", + "integrity": "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg==", + "dev": true, + "license": "MIT" + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -72,1414 +239,1353 @@ "tslib": "^2.4.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.11" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@fontsource-variable/inter": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", - "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "OFL-1.1", - "funding": { - "url": "https://github.com/sponsors/ayuhito" - } - }, - "node_modules/@iconify/svelte": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@iconify/svelte/-/svelte-5.2.1.tgz", - "integrity": "sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==", "license": "MIT", - "dependencies": { - "@iconify/types": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/cyberalien" - }, - "peerDependencies": { - "svelte": ">5.0.0" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", "optional": true, - "peer": true, + "os": [ + "android" + ], "engines": { "node": ">=18" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "freebsd" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "darwin" + "freebsd" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ - "ppc64" + "ia32" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ - "riscv64" + "loong64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ - "s390x" + "mips64el" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ - "x64" + "ppc64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ - "arm64" + "riscv64" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ - "x64" + "s390x" ], - "license": "LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">=18" } }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ - "arm" + "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ - "ppc64" + "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "netbsd" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ - "riscv64" + "arm64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ - "s390x" + "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "openbsd" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ - "x64" + "arm64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "openharmony" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ - "arm64" + "x64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "sunos" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ - "x64" + "arm64" ], - "license": "Apache-2.0", + "dev": true, + "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "node": ">=18" } }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ - "wasm32" + "ia32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "dev": true, + "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, + "os": [ + "win32" + ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ - "arm64" + "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], - "peer": true, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" + "node": ">=18" } }, - "node_modules/@internationalized/date": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz", - "integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==", + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@jis3r/icons": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@jis3r/icons/-/icons-2.7.0.tgz", - "integrity": "sha512-RXee+W1AoWFHtFliLZ7SQxVopAezmCH0xU45HKTvvpDQ+6Y3g6tUHvKOK16J52TM8YsN4WDnsiOmcwau+bHmwg==", - "license": "MIT", - "peerDependencies": { - "svelte": "^5.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@floating-ui/utils": "^0.2.11" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@lucide/svelte": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.23.0.tgz", - "integrity": "sha512-3LQbKXx9vId6Nx4E2Nu2qwgJfdmr5+CVeVJbxe5cy+HcnCRd9QVVtZXqvgBYAV1OJrPmQAf9/3gJWLCpASC/Ng==", + "node_modules/@fontsource-variable/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", "dev": true, - "license": "ISC", - "peerDependencies": { - "svelte": "^5" + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", - "dev": true, + "node_modules/@iconify/svelte": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@iconify/svelte/-/svelte-5.2.1.tgz", + "integrity": "sha512-zHmsIPmnIhGd5gc95bNN5FL+GifwMZv7M2rlZEpa7IXYGFJm/XGHdWf6PWQa6OBoC+R69WyiPO9NAj5wjfjbow==", "license": "MIT", - "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@iconify/types": "^2.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "url": "https://github.com/sponsors/cyberalien" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "svelte": ">5.0.0" } }, - "node_modules/@next/env": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", - "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "license": "MIT", - "peer": true + "optional": true, + "peer": true, + "engines": { + "node": ">=18" + } }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", - "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "peer": true, "engines": { - "node": ">= 10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", - "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "peer": true, "engines": { - "node": ">= 10" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", - "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "linux" + "darwin" ], "peer": true, - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", - "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ - "arm64" + "x64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "linux" + "darwin" ], "peer": true, - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", - "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ - "x64" + "arm" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "peer": true, - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", - "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ - "x64" + "arm64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "peer": true, - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", - "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ - "arm64" + "ppc64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], "peer": true, - "engines": { - "node": ">= 10" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", - "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ - "x64" + "riscv64" ], - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@novnc/novnc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.7.0.tgz", - "integrity": "sha512-ucEJOx4T2avIRCleodk7YobZj5O2Ga2AeLfQ69A/yjG9HHba2+PDgwSkN3FttrmG+70ZGx21sElNFouK13RzyA==", - "license": "MPL-2.0" - }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ - "arm64" + "s390x" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "android" + "linux" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ - "arm64" + "x64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "darwin" + "linux" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ - "x64" + "arm64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "darwin" + "linux" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "freebsd" + "linux" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" } }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ - "arm64" + "ppc64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ - "ppc64" + "riscv64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ - "x64" + "arm64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ - "arm64" + "x64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "openharmony" + "linux" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, + "peer": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/runtime": "^1.7.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], + "peer": true, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "node_modules/@internationalized/date": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.1.tgz", + "integrity": "sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, + "node_modules/@jis3r/icons": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@jis3r/icons/-/icons-2.7.0.tgz", + "integrity": "sha512-RXee+W1AoWFHtFliLZ7SQxVopAezmCH0xU45HKTvvpDQ+6Y3g6tUHvKOK16J52TM8YsN4WDnsiOmcwau+bHmwg==", + "license": "MIT", + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "license": "MIT" }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", - "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@sveltejs/adapter-auto": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz", - "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==", + "node_modules/@layerstack/svelte-actions": { + "version": "1.0.1-next.20", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-actions/-/svelte-actions-1.0.1-next.20.tgz", + "integrity": "sha512-OtFteDyvTUR6F7dSKPkpD/S77K7vbCHStApSlx8djJBxDKJHU3zxA4gYP6Z71Nc6HB/wLCfjZopkvxnMblbRRw==", "dev": true, "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" + "dependencies": { + "@floating-ui/dom": "^1.7.0", + "@layerstack/utils": "2.0.0-next.19", + "d3-scale": "^4.0.2" } }, - "node_modules/@sveltejs/adapter-static": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", - "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "node_modules/@layerstack/svelte-state": { + "version": "0.1.0-next.25", + "resolved": "https://registry.npmjs.org/@layerstack/svelte-state/-/svelte-state-0.1.0-next.25.tgz", + "integrity": "sha512-K+SO4dYd2D9OSCnj92Ugo6i5OfVKZoHHA1DA7gs2ynigylRKInicniGI/OmRpp+002n3mhE/i2r3Kfctj8eAdw==", "dev": true, "license": "MIT", - "peerDependencies": { - "@sveltejs/kit": "^2.0.0" + "dependencies": { + "@layerstack/utils": "2.0.0-next.19" } }, - "node_modules/@sveltejs/kit": { - "version": "2.58.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.58.0.tgz", - "integrity": "sha512-kT9GCN8yJTkCK1W+Gi/bvGooWAM7y7WXP+yd+rf6QOIjyoK1ERPrMwSufXJUNu2pMWIqruhFvmz+LbOqsEmKmA==", + "node_modules/@layerstack/tailwind": { + "version": "2.0.0-next.21", + "resolved": "https://registry.npmjs.org/@layerstack/tailwind/-/tailwind-2.0.0-next.21.tgz", + "integrity": "sha512-Qgp2EpmEHmjtura8MQzWicR6ztBRSsRvddakFtx9ShrLMz6jWzd6bCMVVRu44Q3ZOrtXmSu4QxjCZWu1ytvuPg==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/cookie": "^0.6.0", - "acorn": "^8.14.1", - "cookie": "^0.6.0", - "devalue": "^5.6.4", - "esm-env": "^1.2.2", - "kleur": "^4.1.5", - "magic-string": "^0.30.5", - "mrmime": "^2.0.0", - "set-cookie-parser": "^3.0.0", - "sirv": "^3.0.0" - }, - "bin": { - "svelte-kit": "svelte-kit.js" - }, - "engines": { - "node": ">=18.13" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0", - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.3.3 || ^6.0.0", - "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "typescript": { - "optional": true - } + "@layerstack/utils": "^2.0.0-next.18", + "clsx": "^2.1.1", + "d3-array": "^3.2.4", + "tailwind-merge": "^3.2.0" } }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.0.0.tgz", - "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", + "node_modules/@layerstack/utils": { + "version": "2.0.0-next.19", + "resolved": "https://registry.npmjs.org/@layerstack/utils/-/utils-2.0.0-next.19.tgz", + "integrity": "sha512-HyXCEz9pge4rFWklrSgFr3YIwdE1gP8WM6GPetmnKPDfYLF1/oetRFKI9FqiE+dw4brnULGPKJsVhjM3vQfD4A==", "dev": true, "license": "MIT", "dependencies": { - "deepmerge": "^4.3.1", - "magic-string": "^0.30.21", - "obug": "^2.1.0", - "vitefu": "^1.1.2" - }, - "engines": { - "node": "^20.19 || ^22.12 || >=24" - }, - "peerDependencies": { - "svelte": "^5.46.4", - "vite": "^8.0.0-beta.7 || ^8.0.0" + "d3-array": "^3.2.4", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0" } }, - "node_modules/@swc/helpers": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", - "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "node_modules/@lucide/svelte": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@lucide/svelte/-/svelte-1.23.0.tgz", + "integrity": "sha512-3LQbKXx9vId6Nx4E2Nu2qwgJfdmr5+CVeVJbxe5cy+HcnCRd9QVVtZXqvgBYAV1OJrPmQAf9/3gJWLCpASC/Ng==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" + "license": "ISC", + "peerDependencies": { + "svelte": "^5" } }, - "node_modules/@tailwindcss/node": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", - "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.4" + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" } }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", - "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", "dev": true, "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, "engines": { - "node": ">= 20" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-x64": "4.2.4", - "@tailwindcss/oxide-freebsd-x64": "4.2.4", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-x64-musl": "4.2.4", - "@tailwindcss/oxide-wasm32-wasi": "4.2.4", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", - "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", + "node_modules/@neoconfetti/react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz", + "integrity": "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@next/env": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", + "license": "MIT", + "peer": true + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "darwin" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", - "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", - "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", - "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", "cpu": [ - "x64" + "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "freebsd" + "linux" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", - "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", "cpu": [ - "arm" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", - "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", "cpu": [ - "arm64" + "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", - "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", - "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], + "peer": true, "engines": { - "node": ">= 20" + "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", - "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", + "node_modules/@novnc/novnc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.7.0.tgz", + "integrity": "sha512-ucEJOx4T2avIRCleodk7YobZj5O2Ga2AeLfQ69A/yjG9HHba2+PDgwSkN3FttrmG+70ZGx21sElNFouK13RzyA==", + "license": "MPL-2.0" + }, + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.127.0.tgz", + "integrity": "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", - "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.127.0.tgz", + "integrity": "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==", "cpu": [ - "wasm32" + "arm64" ], "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, + "os": [ + "android" + ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", - "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.127.0.tgz", + "integrity": "sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==", "cpu": [ "arm64" ], @@ -1487,16 +1593,16 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", - "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.127.0.tgz", + "integrity": "sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==", "cpu": [ "x64" ], @@ -1504,221 +1610,2795 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { - "node": ">= 20" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.127.0.tgz", + "integrity": "sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.127.0.tgz", + "integrity": "sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.127.0.tgz", + "integrity": "sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.127.0.tgz", + "integrity": "sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.127.0.tgz", + "integrity": "sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.127.0.tgz", + "integrity": "sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.127.0.tgz", + "integrity": "sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.127.0.tgz", + "integrity": "sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.127.0.tgz", + "integrity": "sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.127.0.tgz", + "integrity": "sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.127.0.tgz", + "integrity": "sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.127.0.tgz", + "integrity": "sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.127.0.tgz", + "integrity": "sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.127.0.tgz", + "integrity": "sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.127.0.tgz", + "integrity": "sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.127.0.tgz", + "integrity": "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", + "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.2.tgz", + "integrity": "sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.2.tgz", + "integrity": "sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.2.tgz", + "integrity": "sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.2.tgz", + "integrity": "sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.2.tgz", + "integrity": "sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.2.tgz", + "integrity": "sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.2.tgz", + "integrity": "sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.2.tgz", + "integrity": "sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.2.tgz", + "integrity": "sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.2.tgz", + "integrity": "sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.2.tgz", + "integrity": "sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.2.tgz", + "integrity": "sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.2.tgz", + "integrity": "sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.2.tgz", + "integrity": "sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.2.tgz", + "integrity": "sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.2.tgz", + "integrity": "sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.2.tgz", + "integrity": "sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.0", + "@emnapi/runtime": "1.11.0", + "@napi-rs/wasm-runtime": "^1.1.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", + "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.2.tgz", + "integrity": "sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.2.tgz", + "integrity": "sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", + "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", + "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", + "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", + "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", + "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", + "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", + "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.17", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", + "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/addon-a11y": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.6.0.tgz", + "integrity": "sha512-Z/CZor/F8H74/+IoeLroEQXQoVxvmjVtUgATJ95KPBolHxMaHLXLsYU0wosIiiORxmfRCnpen5pQl1n4+pBWQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "axe-core": "^4.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.6.0" + } + }, + "node_modules/@storybook/addon-docs": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.6.0.tgz", + "integrity": "sha512-Fk3rUQWeGyCfL170OkmRp6Ruyv7MHPb/xrKa6YNk7FlGmlVLi5tKzh2T/0J13vWEy8wiu2MV3IBu8iRsaUR+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mdx-js/react": "^3.0.0", + "@storybook/icons": "^2.0.2", + "@storybook/react-dom-shim": "10.6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "ts-dedent": "^2.0.0", + "unplugin": "^2.3.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.6.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-svelte-csf": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@storybook/addon-svelte-csf/-/addon-svelte-csf-5.1.3.tgz", + "integrity": "sha512-WD9bVaimpxwRE+f89vBMvw3pWKe8xDUW4jHeZbVPB0zSpH+7Ad/rFQ1oiAklswiVYwF/NXvnC7SZ4V+UXhngLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/csf": "^0.1.13", + "dedent": "^1.5.3", + "es-toolkit": "^1.26.1", + "esrap": "^1.2.2", + "magic-string": "^0.30.12", + "svelte-ast-print": "^0.4.0", + "zimmerframe": "^1.1.2" + }, + "peerDependencies": { + "@storybook/svelte": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 || ^11.0.0-0", + "@sveltejs/vite-plugin-svelte": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "storybook": "^0.0.0-0 || ^8.2.0 || ^9.0.0 || ^9.1.0-0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 || ^11.0.0-0", + "svelte": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/addon-svelte-csf/node_modules/esrap": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.9.tgz", + "integrity": "sha512-3OMlcd0a03UGuZpPeUC1HxR3nA23l+HEyCiZw3b3FumJIN9KphoGzDJKMXI1S72jVS1dsenDyQC0kJlO1U9E1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/@storybook/addon-vitest": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.6.0.tgz", + "integrity": "sha512-GY2SwSXLNgyMqjQ59J7YTkEQTHdEbBMggOvbbuhuPUBhWo97OGSDCmn1UW3nXDZvkoWyWhF9T9628m1uei7FbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@vitest/browser": "^3.0.0 || ^4.0.0", + "@vitest/browser-playwright": "^4.0.0", + "@vitest/runner": "^3.0.0 || ^4.0.0", + "storybook": "^10.6.0", + "vitest": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/runner": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-vite": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.6.0.tgz", + "integrity": "sha512-7zt8tYB6dnSO8fbjUz91MHexGDpf9HrLlA3FAmlVb+2ZL3eWUs499L3I6BDUymcWu7oW5WCBJlB8ERj8ZLLzig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.6.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/csf": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.13.tgz", + "integrity": "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/icons": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.1.0.tgz", + "integrity": "sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.6.0.tgz", + "integrity": "sha512-ZE0l4xL6d00rKFeJhRDuh0t3r+/2ffhx47lIyaSWrTD5kuM3nDjKDxCIWwX4E3gLV+oNX8E1prXRMuEWJUJwJg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.6.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/svelte": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/svelte/-/svelte-10.6.0.tgz", + "integrity": "sha512-H7zdGvuVx/Z4C9LlfwdhVuVPdNlMwVZJ5IV6DZW3huVOZNiZcWMnftOaLGIxiLI2TZmuYJV50cBwjed5RLImLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ts-dedent": "^2.0.0", + "type-fest": "^5.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.6.0", + "svelte": "^5.0.0" + } + }, + "node_modules/@storybook/svelte-vite": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/svelte-vite/-/svelte-vite-10.6.0.tgz", + "integrity": "sha512-j1X/AOn0Z5dxcmtbox6z1tvz7erTuD55kR1tW3OGJpbV5mjdSuvc9aEqz+s3hgxTJgC500327K7z55Co7pG2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/builder-vite": "10.6.0", + "@storybook/svelte": "10.6.0", + "magic-string": "^1.1.0", + "svelte2tsx": "^0.7.55", + "typescript": "^4.9.4 || ^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "storybook": "^10.6.0", + "svelte": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@storybook/svelte-vite/node_modules/magic-string": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz", + "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/@storybook/svelte-vite/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@storybook/svelte/node_modules/type-fest": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/sveltekit": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/@storybook/sveltekit/-/sveltekit-10.6.0.tgz", + "integrity": "sha512-4WIa6xzrFMlGfoI0r9irJrNKYl8faPLxfFitHKmDcZ3FAaY9wLOlkAn3mLb/EXfzgz3nftK7ZYMlWkU3rsCXzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/builder-vite": "10.6.0", + "@storybook/svelte": "10.6.0", + "@storybook/svelte-vite": "10.6.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^10.6.0", + "svelte": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz", + "integrity": "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz", + "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.58.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.58.0.tgz", + "integrity": "sha512-kT9GCN8yJTkCK1W+Gi/bvGooWAM7y7WXP+yd+rf6QOIjyoK1ERPrMwSufXJUNu2pMWIqruhFvmz+LbOqsEmKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.5", + "@types/cookie": "^0.6.0", + "acorn": "^8.14.1", + "cookie": "^0.6.0", + "devalue": "^5.6.4", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.0.0.tgz", + "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", + "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.4" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", + "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-arm64": "4.2.4", + "@tailwindcss/oxide-darwin-x64": "4.2.4", + "@tailwindcss/oxide-freebsd-x64": "4.2.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", + "@tailwindcss/oxide-linux-x64-musl": "4.2.4", + "@tailwindcss/oxide-wasm32-wasi": "4.2.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", + "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", + "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", + "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", + "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", + "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", + "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", + "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", + "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", + "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", + "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", + "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", + "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz", + "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.4", + "@tailwindcss/oxide": "4.2.4", + "tailwindcss": "4.2.4" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz", + "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.7", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.7.tgz", + "integrity": "sha512-MPCpX8bxe8zS+JmmTwLp8jd0dy1rAm60Te/SL8JrQM3qvQJcBOs1d7IefJMyZzqM3EWBrDn/LWDt1BCGu4ASfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@vitest/browser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.10.tgz", + "integrity": "sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.10" + } + }, + "node_modules/@vitest/browser-playwright": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.10.tgz", + "integrity": "sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/browser": "4.1.10", + "@vitest/mocker": "4.1.10", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webcontainer/env": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@webcontainer/env/-/env-1.1.1.tgz", + "integrity": "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==", + "dev": true, + "license": "MIT" + }, + "node_modules/@xterm/addon-fit": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" + }, + "node_modules/@xterm/xterm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", + "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", + "license": "MIT", + "workspaces": [ + "addons/*" + ] + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.3.tgz", + "integrity": "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz", + "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/axe-core": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bits-ui": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.18.0.tgz", + "integrity": "sha512-GLOBZRVy3hxNHIQ2MpD/+5aK9KcBFZRhUJtZ1UDABXdlVR4K6zFpgt4T+Rwuhf2sQzlc6yK1q/DprHPjwT4Pjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.35.1", + "svelte-toolbelt": "^0.10.6", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/blobatar": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/blobatar/-/blobatar-2.7.0.tgz", + "integrity": "sha512-tL1p2J3dXE/Z9U/ITwHqU7HBHmTH+Am+hif1J1hWAhvFT1VYC2HG+eXtN7081yuY+L2lrp9bs/VaF7dVH3S2jw==", + "license": "MIT", + "peerDependencies": { + "react": ">=18", + "vue": ">=3" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chromatic": { + "version": "18.9.2", + "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-18.9.2.tgz", + "integrity": "sha512-RHsDLRTHPMIonELl+x3WPtMUBV/fMfC+G1LuHUvhtIWapF4eze7xhm5Mrd92+Ti9m1F+rv0fBKJ+spWZhvE7zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "bin": { + "chroma": "dist/bin.cjs", + "chromatic": "dist/bin.cjs", + "chromatic-cli": "dist/bin.cjs" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@chromatic-com/cypress": "^0.*.* || ^1.0.0", + "@chromatic-com/playwright": "^0.*.* || ^1.0.0", + "@chromatic-com/vitest": "^0.*.* || ^1.0.0" + }, + "peerDependenciesMeta": { + "@chromatic-com/cypress": { + "optional": true + }, + "@chromatic-com/playwright": { + "optional": true + }, + "@chromatic-com/vitest": { + "optional": true + } + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT", + "peer": true + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@tailwindcss/vite": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz", - "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==", + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "dev": true, - "license": "MIT", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", "dependencies": { - "@tailwindcss/node": "4.2.4", - "@tailwindcss/oxide": "4.2.4", - "tailwindcss": "4.2.4" + "d3-array": "2.5.0 - 3" }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" + "engines": { + "node": ">=12" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "node_modules/d3-geo-voronoi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/d3-geo-voronoi/-/d3-geo-voronoi-2.1.0.tgz", + "integrity": "sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", "dependencies": { - "tslib": "^2.4.0" + "d3-array": "3", + "d3-delaunay": "6", + "d3-geo": "3", + "d3-tricontour": "1" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "license": "MIT" + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" + "node_modules/d3-interpolate-path": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-interpolate-path/-/d3-interpolate-path-2.3.0.tgz", + "integrity": "sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==", + "dev": true, + "license": "BSD-3-Clause" }, - "node_modules/@xterm/addon-fit": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", - "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", - "license": "MIT" + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/@xterm/xterm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", - "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", - "license": "MIT", - "workspaces": [ - "addons/*" - ] + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/aria-query": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", - "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", - "license": "Apache-2.0", + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "license": "Apache-2.0", + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, "engines": { - "node": ">= 0.4" + "node": ">=12" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", - "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" }, "engines": { - "node": ">=6.0.0" + "node": ">=12" } }, - "node_modules/bits-ui": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.18.0.tgz", - "integrity": "sha512-GLOBZRVy3hxNHIQ2MpD/+5aK9KcBFZRhUJtZ1UDABXdlVR4K6zFpgt4T+Rwuhf2sQzlc6yK1q/DprHPjwT4Pjw==", + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@floating-ui/core": "^1.7.1", - "@floating-ui/dom": "^1.7.1", - "esm-env": "^1.1.2", - "runed": "^0.35.1", - "svelte-toolbelt": "^0.10.6", - "tabbable": "^6.2.0" + "d3-path": "^3.1.0" }, "engines": { - "node": ">=20" + "node": ">=12" + } + }, + "node_modules/d3-tile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d3-tile/-/d3-tile-1.0.0.tgz", + "integrity": "sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" }, - "funding": { - "url": "https://github.com/sponsors/huntabyte" + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" }, - "peerDependencies": { - "@internationalized/date": "^3.8.1", - "svelte": "^5.33.0" + "engines": { + "node": ">=12" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0", - "peer": true + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/d3-tricontour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-tricontour/-/d3-tricontour-1.1.0.tgz", + "integrity": "sha512-G7gHKj89n2owmkGb6WX6ixcnQ0Kf/0wpa9VIh9DGdbHu8wdrlaHU4ir3/bFNERl8N8nn4G7e7qbtBG8N9caihQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "readdirp": "^4.0.1" + "d3-delaunay": "6", + "d3-scale": "4" }, "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "node": ">=12" } }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, "license": "MIT", - "peer": true + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/dedent-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz", + "integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", "dev": true, "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, "engines": { - "node": ">=20" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" } }, "node_modules/dequal": { @@ -1747,6 +4427,13 @@ "integrity": "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.21.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", @@ -1761,12 +4448,88 @@ "node": ">=10.13.0" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, "node_modules/esm-env": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esrap": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.5.tgz", @@ -1784,6 +4547,26 @@ } } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1833,6 +4616,46 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -1840,6 +4663,51 @@ "dev": true, "license": "MIT" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-reference": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", @@ -1849,6 +4717,61 @@ "@types/estree": "^1.0.6" } }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -1859,6 +4782,33 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -1869,6 +4819,77 @@ "node": ">=6" } }, + "node_modules/layerchart": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/layerchart/-/layerchart-2.5.0.tgz", + "integrity": "sha512-9xOVLKXeurBiFduMFKx7g/rU9/IyGYb2pTFRNJmabXEg/QRoY+ILwOBcWH5dGBEpFWiFlzVfcHDtbcbaymaETw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dagrejs/dagre": "^2.0.4", + "@layerstack/svelte-actions": "1.0.1-next.20", + "@layerstack/svelte-state": "0.1.0-next.25", + "@layerstack/tailwind": "2.0.0-next.21", + "@layerstack/utils": "2.0.0-next.19", + "@types/d3-contour": "^3.0.6", + "d3-array": "^3.2.4", + "d3-chord": "^3.0.1", + "d3-color": "^3.1.0", + "d3-contour": "^4.0.2", + "d3-delaunay": "^6.0.4", + "d3-dsv": "^3.0.1", + "d3-force": "^3.0.0", + "d3-geo": "^3.1.1", + "d3-geo-voronoi": "^2.1.0", + "d3-hierarchy": "^3.1.2", + "d3-interpolate": "^3.0.1", + "d3-interpolate-path": "^2.3.0", + "d3-path": "^3.1.0", + "d3-polygon": "^3.0.1", + "d3-quadtree": "^3.0.1", + "d3-random": "^3.0.1", + "d3-sankey": "^0.12.3", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", + "d3-shape": "^3.2.0", + "d3-tile": "^1.0.0", + "d3-time": "^3.1.0", + "memoize": "^10.2.0", + "runed": "^0.37.1" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/layerchart/node_modules/runed": { + "version": "0.37.1", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.37.1.tgz", + "integrity": "sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0", + "zod": "^4.1.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", @@ -2126,33 +5147,107 @@ "node": ">= 12.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.5.tgz", + "integrity": "sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT" + "node_modules/memoize": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.2.0.tgz", + "integrity": "sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "engines": { + "node": ">=4" } }, "node_modules/mode-watcher": { @@ -2371,6 +5466,111 @@ ], "license": "MIT" }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oxc-parser": { + "version": "0.127.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.127.0.tgz", + "integrity": "sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.127.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.127.0", + "@oxc-parser/binding-android-arm64": "0.127.0", + "@oxc-parser/binding-darwin-arm64": "0.127.0", + "@oxc-parser/binding-darwin-x64": "0.127.0", + "@oxc-parser/binding-freebsd-x64": "0.127.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.127.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.127.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.127.0", + "@oxc-parser/binding-linux-arm64-musl": "0.127.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.127.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.127.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.127.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.127.0", + "@oxc-parser/binding-linux-x64-gnu": "0.127.0", + "@oxc-parser/binding-linux-x64-musl": "0.127.0", + "@oxc-parser/binding-openharmony-arm64": "0.127.0", + "@oxc-parser/binding-wasm32-wasi": "0.127.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.127.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.127.0", + "@oxc-parser/binding-win32-x64-msvc": "0.127.0" + } + }, + "node_modules/oxc-resolver": { + "version": "11.21.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.2.tgz", + "integrity": "sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.21.2", + "@oxc-resolver/binding-android-arm64": "11.21.2", + "@oxc-resolver/binding-darwin-arm64": "11.21.2", + "@oxc-resolver/binding-darwin-x64": "11.21.2", + "@oxc-resolver/binding-freebsd-x64": "11.21.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.21.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.21.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.21.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.21.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.21.2", + "@oxc-resolver/binding-linux-x64-musl": "11.21.2", + "@oxc-resolver/binding-openharmony-arm64": "11.21.2", + "@oxc-resolver/binding-wasm32-wasi": "11.21.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.21.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.21.2" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2390,6 +5590,45 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postcss": { "version": "8.5.12", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", @@ -2419,12 +5658,26 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/react": { "version": "19.2.5", "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2434,7 +5687,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2442,6 +5694,13 @@ "react": "^19.2.5" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -2456,6 +5715,44 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/recast": { + "version": "0.23.21", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.21.tgz", + "integrity": "sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/rolldown": { "version": "1.0.0-rc.17", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", @@ -2490,6 +5787,19 @@ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" } }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/runed": { "version": "0.35.1", "resolved": "https://registry.npmjs.org/runed/-/runed-0.35.1.tgz", @@ -2515,6 +5825,13 @@ } } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -2528,20 +5845,32 @@ "node": ">=6" } }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "devOptional": true, "license": "ISC", - "optional": true, - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -2584,65 +5913,200 @@ "optional": true, "peer": true, "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/storybook": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.6.0.tgz", + "integrity": "sha512-H48X6AURAFTpfN2zEux6vNesee5Oq8Gxj1IanYmblPZ218sfKSeyyerF1NC7GD7BrruoC8L4/cU0zII4y+d6zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/icons": "^2.0.2", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/user-event": "^14.6.3", + "@vitest/expect": "3.2.4", + "@vitest/spy": "3.2.4", + "@webcontainer/env": "^1.1.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", + "jsonc-parser": "^3.3.1", + "open": "^10.2.0", + "oxc-parser": "^0.127.0", + "oxc-resolver": "11.21.2", + "recast": "^0.23.5", + "semver": "^7.7.3", + "use-sync-external-store": "^1.5.0", + "ws": "^8.21.1" + }, + "bin": { + "storybook": "dist/bin/dispatcher.js" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "prettier": "^2 || ^3", + "vite-plus": "^0.1.15 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "prettier": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/storybook/node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/sirv": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", - "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, "engines": { - "node": ">=18" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/style-to-object": { @@ -2679,6 +6143,19 @@ } } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/svelte": { "version": "5.55.5", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.5.tgz", @@ -2706,6 +6183,51 @@ "node": ">=18" } }, + "node_modules/svelte-ast-print": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/svelte-ast-print/-/svelte-ast-print-0.4.2.tgz", + "integrity": "sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/xeho91" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/xeho91" + } + ], + "license": "MIT", + "dependencies": { + "esrap": "1.2.2", + "zimmerframe": "1.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-ast-print/node_modules/esrap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, + "node_modules/svelte-ast-print/node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true, + "license": "MIT" + }, "node_modules/svelte-check": { "version": "4.4.6", "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.6.tgz", @@ -2781,6 +6303,21 @@ "svelte": "^5.30.2" } }, + "node_modules/svelte2tsx": { + "version": "0.7.61", + "resolved": "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.61.tgz", + "integrity": "sha512-EpQ/+UHITBULeUojx/LLD3uTYasZXcX1BrqlrzfLUnT9vdUhaOWK59a3ryWcFU8L0sY1SP+gRhJ2Beiis98+qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dedent-js": "^1.0.1", + "scule": "^1.3.0" + }, + "peerDependencies": { + "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", + "typescript": "^4.9.4 || ^5.0.0 || ^6.0.0" + } + }, "node_modules/tabbable": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", @@ -2788,6 +6325,19 @@ "dev": true, "license": "MIT" }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tailwind-merge": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", @@ -2840,6 +6390,30 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -2857,6 +6431,26 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -2867,6 +6461,16 @@ "node": ">=6" } }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2883,6 +6487,19 @@ "url": "https://github.com/sponsors/Wombosvideo" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -2897,6 +6514,42 @@ "node": ">=14.17" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "8.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", @@ -2995,6 +6648,186 @@ } } }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zimmerframe": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", diff --git a/app/package.json b/app/package.json index ef6d0723..7f04790c 100644 --- a/app/package.json +++ b/app/package.json @@ -9,21 +9,34 @@ "preview": "vite preview", "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "devDependencies": { + "@chromatic-com/storybook": "latest", "@fontsource-variable/inter": "^5.2.8", "@internationalized/date": "^3.12.1", "@lucide/svelte": "^1.23.0", + "@storybook/addon-a11y": "^10.6.0", + "@storybook/addon-docs": "^10.6.0", + "@storybook/addon-svelte-csf": "latest", + "@storybook/addon-vitest": "^10.6.0", + "@storybook/sveltekit": "^10.6.0", "@sveltejs/adapter-auto": "^7.0.1", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.57.0", "@sveltejs/vite-plugin-svelte": "^7.0.0", "@tailwindcss/vite": "^4.2.2", + "@vitest/browser-playwright": "latest", + "@vitest/coverage-v8": "latest", "bits-ui": "^2.18.0", "clsx": "^2.1.1", + "layerchart": "^2.5.0", "mode-watcher": "^1.1.0", + "playwright": "latest", "shadcn-svelte": "^1.2.7", + "storybook": "^10.6.0", "svelte": "^5.55.2", "svelte-check": "^4.4.6", "svelte-sonner": "^1.1.1", @@ -32,14 +45,17 @@ "tailwindcss": "^4.2.2", "tw-animate-css": "^1.4.0", "typescript": "^6.0.2", - "vite": "^8.0.7" + "vite": "^8.0.7", + "vitest": "latest" }, "dependencies": { + "@blobatar/svelte": "^2.7.0", "@iconify/svelte": "^5.2.1", "@jis3r/icons": "^2.7.0", "@novnc/novnc": "^1.7.0", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", + "blobatar": "^2.7.0", "geist": "^1.7.0" } } diff --git a/app/src/lib/api/events.ts b/app/src/lib/api/events.ts new file mode 100644 index 00000000..bc4a912f --- /dev/null +++ b/app/src/lib/api/events.ts @@ -0,0 +1,54 @@ +import { authedFetch } from './http'; + +const API_BASE = '/api'; + +export interface AlertEvent { + id: string; + agent_id: string | null; + event_type: string; + affected_workloads: string[] | null; + duration_ms: number | null; + created_at: string; + severity: string; + status: string; + resolved_at: string | null; + message: string | null; +} + +export async function listEvents( + token: string, + filters?: { status?: string; agentId?: string }, +): Promise { + const params = new URLSearchParams(); + if (filters?.status) params.set('status', filters.status); + if (filters?.agentId) params.set('agent_id', filters.agentId); + const query = params.toString(); + const res = await authedFetch(`${API_BASE}/events${query ? `?${query}` : ''}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error(`events fetch failed: ${res.status}`); + return res.json(); +} + +export async function setMaintenance(token: string, agentId: string, minutes: number): Promise { + const res = await authedFetch(`${API_BASE}/agents/${agentId}/maintenance`, { + method: 'POST', + headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ minutes }), + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.status })); + throw new Error(err.error ?? `Failed to set maintenance: ${res.status}`); + } +} + +export async function clearMaintenance(token: string, agentId: string): Promise { + const res = await authedFetch(`${API_BASE}/agents/${agentId}/maintenance`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.status })); + throw new Error(err.error ?? `Failed to clear maintenance: ${res.status}`); + } +} diff --git a/app/src/lib/api/nodes.ts b/app/src/lib/api/nodes.ts index 1689748a..f5f49dff 100644 --- a/app/src/lib/api/nodes.ts +++ b/app/src/lib/api/nodes.ts @@ -15,6 +15,7 @@ export interface Node { last_heartbeat: string | null; registered_at: string; cordoned: boolean; + maintenance_until: string | null; } export interface NodeMetrics { @@ -123,12 +124,37 @@ export async function openNodeMetricsSocket(token: string, agentId: string): Pro return new WebSocket(url); } +export interface SmartInfo { + health: string; + power_on_hours: number | null; + temperature_celsius: number | null; + reallocated_sectors: number | null; +} + +export interface DiskInfo { + device: string; + model: string | null; + media_type: string; + total_bytes: number; + used_bytes: number; + mount_point: string | null; + smart: SmartInfo | null; +} + +export async function getNodeDisks(token: string, id: string): Promise { + const res = await authedFetch(`${API_BASE}/agents/${id}/disks`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error(`disks fetch failed: ${res.status}`); + return res.json(); +} + export interface HealthHistoryPoint { bucket: string; online_count: number; } -export async function getHealthHistory(token: string, range: '1h' | '7d' | '30d'): Promise { +export async function getHealthHistory(token: string, range: '1h' | '24h' | '7d' | '30d'): Promise { const res = await authedFetch(`${API_BASE}/system/stats/history?range=${range}`, { headers: { Authorization: `Bearer ${token}` }, }); diff --git a/app/src/lib/api/resource-groups.ts b/app/src/lib/api/resource-groups.ts index 186ca2ab..b6d3730a 100644 --- a/app/src/lib/api/resource-groups.ts +++ b/app/src/lib/api/resource-groups.ts @@ -12,6 +12,8 @@ export interface ResourceGroup { icon: string; color: string; pinned: boolean; + has_icon_image: boolean; + preview_url: string | null; created_at: string; updated_at: string | null; } @@ -30,6 +32,7 @@ export interface UpdateResourceGroupRequest { icon?: string; color?: string; pinned?: boolean; + preview_url?: string; } export interface PortMapping { @@ -234,6 +237,42 @@ export async function updateResourceGroup( return res.json(); } +export function resourceGroupIconImageUrl(id: string): string { + return `${API_BASE}/resource-groups/${id}/icon-image`; +} + +export async function uploadResourceGroupIconImage( + token: string, + id: string, + file: File, +): Promise { + const res = await authedFetch(`${API_BASE}/resource-groups/${id}/icon-image`, { + method: 'PUT', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': file.type, + }, + body: file, + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.status })); + throw new Error(err.error ?? `Failed to upload icon image: ${res.status}`); + } + return res.json(); +} + +export async function deleteResourceGroupIconImage(token: string, id: string): Promise { + const res = await authedFetch(`${API_BASE}/resource-groups/${id}/icon-image`, { + method: 'DELETE', + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) { + const err = await res.json().catch(() => ({ error: res.status })); + throw new Error(err.error ?? `Failed to remove icon image: ${res.status}`); + } + return res.json(); +} + export async function deleteResourceGroup(token: string, id: string): Promise { const res = await authedFetch(`${API_BASE}/resource-groups/${id}`, { method: 'DELETE', @@ -250,6 +289,14 @@ export async function listResourceGroupWorkloads(token: string, rgId: string): P return res.json(); } +export async function listWorkloads(token: string): Promise { + const res = await authedFetch(`${API_BASE}/workloads`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error(`Failed to list workloads: ${res.status}`); + return res.json(); +} + export async function createWorkload(token: string, req: CreateWorkloadRequest): Promise<{ workload_id: string; status: string; assigned_agent_id: string | null; message: string }> { const res = await authedFetch(`${API_BASE}/workloads`, { method: 'POST', @@ -443,6 +490,14 @@ export async function listResourceGroupVolumes(token: string, rgId: string): Pro return res.json(); } +export async function listVolumes(token: string): Promise { + const res = await authedFetch(`${API_BASE}/volumes`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error(`Failed to list volumes: ${res.status}`); + return res.json(); +} + export async function createVolume(token: string, req: CreateVolumeRequest): Promise { const res = await authedFetch(`${API_BASE}/volumes`, { method: 'POST', diff --git a/app/src/lib/auth/api.ts b/app/src/lib/auth/api.ts index 7b333602..8287b3e7 100644 --- a/app/src/lib/auth/api.ts +++ b/app/src/lib/auth/api.ts @@ -186,6 +186,29 @@ export async function gravatarUrl(email: string, size = 80): Promise { return `https://www.gravatar.com/avatar/${hash}?s=${size}&d=404`; } +export type AvatarFallbackStyle = 'initials' | 'blobatar'; + +export async function getAvatarFallbackStyle(token: string): Promise { + const res = await fetch(`${API_BASE}/settings/avatar-fallback`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error(`get avatar fallback failed: ${res.status}`); + const data = await res.json(); + return data.style === 'blobatar' ? 'blobatar' : 'initials'; +} + +export async function setAvatarFallbackStyle(token: string, style: AvatarFallbackStyle): Promise { + const res = await fetch(`${API_BASE}/settings/avatar-fallback`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ style }), + }); + if (!res.ok) throw new Error(`set avatar fallback failed: ${res.status}`); +} + export interface Setup2FAResponse { secret: string; qr_code: string; diff --git a/app/src/lib/auth/pending-login.ts b/app/src/lib/auth/pending-login.ts new file mode 100644 index 00000000..cbeb939a --- /dev/null +++ b/app/src/lib/auth/pending-login.ts @@ -0,0 +1,18 @@ +interface PendingCredentials { + username: string; + password: string; +} + +let pending: PendingCredentials | null = null; + +export const pendingLogin = { + set(username: string, password: string) { + pending = { username, password }; + }, + get(): PendingCredentials | null { + return pending; + }, + clear() { + pending = null; + }, +}; diff --git a/app/src/lib/auth/right-panel.svelte b/app/src/lib/auth/right-panel.svelte deleted file mode 100644 index b49014e9..00000000 --- a/app/src/lib/auth/right-panel.svelte +++ /dev/null @@ -1,116 +0,0 @@ - - - diff --git a/app/src/lib/components/command-palette/command-palette-store.svelte.ts b/app/src/lib/components/command-palette/command-palette-store.svelte.ts new file mode 100644 index 00000000..ab2658ac --- /dev/null +++ b/app/src/lib/components/command-palette/command-palette-store.svelte.ts @@ -0,0 +1,19 @@ +let open = $state(false); + +export const commandPalette = { + get open() { + return open; + }, + show() { + open = true; + }, + hide() { + open = false; + }, + toggle() { + open = !open; + }, + set(value: boolean) { + open = value; + }, +}; diff --git a/app/src/lib/components/command-palette/command-palette.svelte b/app/src/lib/components/command-palette/command-palette.svelte new file mode 100644 index 00000000..19832ea6 --- /dev/null +++ b/app/src/lib/components/command-palette/command-palette.svelte @@ -0,0 +1,214 @@ + + + + + commandPalette.set(value)}> + + + + + + {loading ? 'Loading...' : 'No results found.'} + + {#each visibleResults as [groupName, items] (groupName)} + {#if items.length > 0} + + + {groupName} + + + {#each items as item (item.id)} + handleSelect(item)} + class="flex items-center gap-2.5 px-2.5 py-2 rounded-md text-sm cursor-pointer data-selected:bg-muted" + > + + {item.label} + {#if item.sublabel} + {item.sublabel} + {/if} + + {/each} + + + {/if} + {/each} + + + + diff --git a/app/src/lib/components/dashboard/dashboard-info-panel.svelte b/app/src/lib/components/dashboard/dashboard-info-panel.svelte new file mode 100644 index 00000000..61655a85 --- /dev/null +++ b/app/src/lib/components/dashboard/dashboard-info-panel.svelte @@ -0,0 +1,30 @@ + + +
+ Dashboard + +
+ Uptime (7d) + + {uptimePercent !== null ? `${uptimePercent.toFixed(2)}%` : "-"} + +
+ +
+ Live usage + +
+
diff --git a/app/src/lib/components/dashboard/donut-chart.svelte b/app/src/lib/components/dashboard/donut-chart.svelte new file mode 100644 index 00000000..0d9a9c22 --- /dev/null +++ b/app/src/lib/components/dashboard/donut-chart.svelte @@ -0,0 +1,37 @@ + + +
+ + + {#snippet tooltip()} + {#if !empty} + + {/if} + {/snippet} + + +
+ {total} + {label} +
+
diff --git a/app/src/lib/components/dashboard/segment-card.svelte b/app/src/lib/components/dashboard/segment-card.svelte new file mode 100644 index 00000000..ead00dbf --- /dev/null +++ b/app/src/lib/components/dashboard/segment-card.svelte @@ -0,0 +1,29 @@ + + +
+ +
+

{title}

+
+ {#each segments as segment (segment.label)} +
+ + {segment.label} + {loading ? "-" : segment.value} +
+ {/each} +
+
+
diff --git a/app/src/lib/components/dashboard/usage-chart.svelte b/app/src/lib/components/dashboard/usage-chart.svelte new file mode 100644 index 00000000..2c64dc72 --- /dev/null +++ b/app/src/lib/components/dashboard/usage-chart.svelte @@ -0,0 +1,67 @@ + + +
+
+ {#each SERIES as series (series.key)} + + + {series.label} + {latest ? `${latest[series.key].toFixed(0)}%` : "-"} + + {/each} +
+ + + {#each SERIES as series (series.key)} + + + + + {/each} + + {#each paths as path (path.key)} + {#if path.line} + + + {/if} + {/each} + +
diff --git a/app/src/lib/components/gauge-card.svelte b/app/src/lib/components/gauge-card.svelte new file mode 100644 index 00000000..45a737ab --- /dev/null +++ b/app/src/lib/components/gauge-card.svelte @@ -0,0 +1,47 @@ + + +
+
+ + + + +
+ {clamped.toFixed(0)}% +
+
+
+ {label} + {detail} +
+
diff --git a/app/src/lib/components/icon-picker.stories.svelte b/app/src/lib/components/icon-picker.stories.svelte new file mode 100644 index 00000000..4f77f0bb --- /dev/null +++ b/app/src/lib/components/icon-picker.stories.svelte @@ -0,0 +1,26 @@ + + + + {#snippet template(args)} +
+ +
+ {/snippet} +
diff --git a/app/src/lib/components/icon-picker.svelte b/app/src/lib/components/icon-picker.svelte index 168549c0..324d2c09 100644 --- a/app/src/lib/components/icon-picker.svelte +++ b/app/src/lib/components/icon-picker.svelte @@ -1,7 +1,31 @@ - -{#snippet gaugeCard(value: number, label: string, detail: string)} - {#if true} - {@const arc = gaugeArc(value, 20)} -
-
- - - - - {value.toFixed(0)}% -
-
-

{label}

-

{detail}

-
-
- {/if} -{/snippet} - - { if (!v) onClose(); }} -> - - {#if node} - -
-
- - - - -
-
- {node.hostname} - {node.ip_address ?? 'no ip'} -
-
- {#if node.cordoned} - cordoned - {/if} - - {node.status.toLowerCase()} -
-
- -
- - - {#if node.cordoned} - - {:else} - - {/if} - -
- - {#if powerActionError} -

{powerActionError}

- {/if} - -
- {#each tabs as tab} - - {/each} -
-
- -
- {#if activeTab === 'summary'} -
-
-
- Live Load - {#if metricsLoading} - Loading... - {:else if metricsLive} - - - live - - {:else if metrics?.timestamp} - - {refreshedLabel(metrics.timestamp)} - - {/if} -
- - {#if metricsError} -

{metricsError}

- {:else} -
- {#if true} - {@const cpuVal = clampPct(metrics?.cpu_usage_percent ?? null)} - {@const cpuDetail = metrics?.cpu_model ?? ((metrics?.cpu_cores ?? '-') + ' cores')} - {@render gaugeCard(cpuVal, 'CPU', cpuDetail)} - {/if} - {#if true} - {@const memVal = clampPct(memPct())} - {@const memDetail = bytesToGb(metrics?.memory_total_bytes ?? null)} - {@render gaugeCard(memVal, 'Memory', memDetail)} - {/if} - {#if true} - {@const diskVal = clampPct(diskPct())} - {@const diskDetail = bytesToGb(metrics?.disk_total_bytes ?? null)} - {@render gaugeCard(diskVal, 'Disk', diskDetail)} - {/if} - {#if true} - {@const rxPct = netRxPct()} - {@const txPct = netTxPct()} - {@const outerR = 20} - {@const innerR = 13} - {@const outerC = 2 * Math.PI * outerR} - {@const innerC = 2 * Math.PI * innerR} -
-
- - - - - - -
-
-

Network

-

- RX {formatBytes(metrics?.network_rx_bytes ?? null)} -

-

- TX {formatBytes(metrics?.network_tx_bytes ?? null)} -

-
-
- {/if} -
- {/if} -
- -
- -
- Host & Lifecycle -
-
- Hostname - {node.hostname} -
-
- IP address - {node.ip_address ?? '-'} -
-
- OS - {node.os_type} {node.os_version} -
- {#if metrics?.kernel_version} -
- Kernel - {metrics.kernel_version} -
- {/if} -
- Architecture - {node.architecture} -
-
- Agent version - {node.agent_version} -
-
- Uptime - {formatUptime(metrics?.uptime_seconds ?? null)} -
-
- Registered - {node.registered_at.slice(0, 16)} -
-
- Last heartbeat - {node.last_heartbeat ? node.last_heartbeat.slice(0, 16) : 'never'} -
-
-
-
- - {:else if activeTab === 'hardware'} -
- {#if metricsLoading} -

Loading...

- {:else if metrics} -
- CPU -
-
- Model - {metrics.cpu_model ?? '-'} -
-
- Cores - {metrics.cpu_cores ?? '-'} -
-
- Threads - {metrics.cpu_threads ?? '-'} -
-
- Usage - {pctStr(metrics.cpu_usage_percent)} -
-
-
-
- Memory -
-
- Total - {bytesToGb(metrics.memory_total_bytes)} -
-
- Used - {bytesToGb(metrics.memory_used_bytes)} -
-
- Usage - {pctStr(memPct())} -
-
-
-
- Storage -
-
- Total - {bytesToGb(metrics.disk_total_bytes)} -
-
- Used - {bytesToGb(metrics.disk_used_bytes)} -
-
- Usage - {pctStr(diskPct())} -
-
-
- {:else} -

{metricsError ?? 'No data'}

- {/if} -
- - {:else if activeTab === 'workloads'} -
-

Workload scheduling coming soon.

-
- - {:else if activeTab === 'network'} -
- {#if metrics} -
- Interface -
-
- IP address - {node?.ip_address ?? '-'} -
-
- RX - {metrics.network_rx_bytes != null ? (metrics.network_rx_bytes / 1_048_576).toFixed(2) + ' MB/s' : '-'} -
-
- TX - {metrics.network_tx_bytes != null ? (metrics.network_tx_bytes / 1_048_576).toFixed(2) + ' MB/s' : '-'} -
-
-
- {:else} -

{metricsError ?? 'No data'}

- {/if} -
- - {:else if activeTab === 'tasks'} -
-

No active tasks.

-
- {/if} -
- {/if} -
-
diff --git a/app/src/lib/components/nodes/nodes-info-panel.svelte b/app/src/lib/components/nodes/nodes-info-panel.svelte new file mode 100644 index 00000000..c64223f9 --- /dev/null +++ b/app/src/lib/components/nodes/nodes-info-panel.svelte @@ -0,0 +1,52 @@ + + +{#snippet statRow(label: string, value: string, hint?: string)} +
+ {label} + + {value} + {#if hint} + {hint} + {/if} + +
+{/snippet} + +
+
+ +
+ Cluster overview +
+ +
+ {#if stats} + {@render statRow("Total nodes", String(stats.node_count))} + {@render statRow("Online", `${stats.online_count} / ${stats.node_count}`)} + {@render statRow("vCPU capacity", String(stats.total_cpu_cores), `${cpuPercent.toFixed(1)}% used`)} + {@render statRow("Memory", formatBytes(stats.used_memory_bytes), `/ ${formatBytes(stats.total_memory_bytes)}`)} + {@render statRow("Disk", formatBytes(stats.used_disk_bytes), `/ ${formatBytes(stats.total_disk_bytes)}`)} + {/if} +
+
diff --git a/app/src/lib/components/resource-group/appearance-dialog.svelte b/app/src/lib/components/resource-group/appearance-dialog.svelte new file mode 100644 index 00000000..ca6d4bce --- /dev/null +++ b/app/src/lib/components/resource-group/appearance-dialog.svelte @@ -0,0 +1,89 @@ + + + (error = null)}> + + {#if error} +

{error}

+ {/if} +
+ + +
+
diff --git a/app/src/lib/components/resource-group/bucket-detail-dialog.svelte b/app/src/lib/components/resource-group/bucket-detail-dialog.svelte new file mode 100644 index 00000000..0e93f657 --- /dev/null +++ b/app/src/lib/components/resource-group/bucket-detail-dialog.svelte @@ -0,0 +1,119 @@ + + + { + bucket = null; + createdSecret = null; + }} +> +
+

Access Keys

+ {#if keys.length === 0} +

no access keys yet

+ {:else} +
+ {#each keys as key (key.id)} +
+
+

{key.name}

+

{key.garage_key_id}

+
+ {key.permissions} +
+ {/each} +
+ {/if} +
+ + {#if createdSecret} +
+

Secret access key created

+

{createdSecret}

+

this will not be shown again, copy it now

+ +
+ {/if} + + {#if error} +

{error}

+ {/if} + +
+ + +
+
diff --git a/app/src/lib/components/resource-group/bucket-row.svelte b/app/src/lib/components/resource-group/bucket-row.svelte new file mode 100644 index 00000000..44fcb237 --- /dev/null +++ b/app/src/lib/components/resource-group/bucket-row.svelte @@ -0,0 +1,57 @@ + + + onOpen()} +> + +
+ +
+

{b.name}

+

{b.global_alias}

+
+
+ + + Bucket + + + {b.quota_max_size ? fmtBytes(b.quota_max_size) : "unlimited"} + + + {b.exposure} + + + + + +
+ + +
+ + diff --git a/app/src/lib/components/resource-group/compose-dialog.svelte b/app/src/lib/components/resource-group/compose-dialog.svelte new file mode 100644 index 00000000..f228059e --- /dev/null +++ b/app/src/lib/components/resource-group/compose-dialog.svelte @@ -0,0 +1,195 @@ + + + resetComposeForm()} +> +
+
+

{editingStackId ? "Edit Compose Stack" : "Deploy Docker Compose Stack"}

+ +
+
+ + +
+
+
+ +
+ {#if composePreview.services.length === 0 && composePreview.volumes.length === 0} +

No services detected yet.

+ {:else} + {#each composePreview.services as service (service.serviceName)} +
+ +
+

{service.serviceName}

+

{service.image ?? "no image"}

+ {#if service.ports.length > 0} +

{service.ports.join(", ")}

+ {/if} +
+
+ {/each} + {#if composePreview.volumes.length > 0} +

Volumes

+ {#each composePreview.volumes as volumeName (volumeName)} +
+ +

{volumeName}

+
+ {/each} + {/if} + {/if} +
+
+
+
+ + +
+
+
+ {#each Array(composeLineCount) as _, i} +
{i + 1}
+ {/each} +
+
{@html composeHighlighted}
+ +
+
+
+ {#if composeError} +

{composeError}

+ {/if} +
+ + +
+
+
diff --git a/app/src/lib/components/resource-group/container-dialog.svelte b/app/src/lib/components/resource-group/container-dialog.svelte new file mode 100644 index 00000000..1690c07a --- /dev/null +++ b/app/src/lib/components/resource-group/container-dialog.svelte @@ -0,0 +1,171 @@ + + + (activeId = null)} +> + {#if container} +
+
+
+ +
+

{container.service_name ?? container.name}

+

{container.image}

+
+ +
+
+ + + +
+ +
+
+ {#if actionError} +

{actionError}

+ {/if} +
+
+ {#each tabs as [key, label]} + + {/each} +
+
+
+ {#if tab === "logs"} + + {:else if tab === "shell" && isVm} + {#if container.status !== "running" || !auth.token} +

Console is only available while the vm is running.

+ {:else} + {#key container.id} + + {/key} + {/if} + {:else if tab === "shell"} + + {:else if tab === "insights"} + + {:else if tab === "network"} + + {:else if !container.stack_id} + + {/if} +
+
+ {/if} +
diff --git a/app/src/lib/components/resource-group/container-insights-tab.svelte b/app/src/lib/components/resource-group/container-insights-tab.svelte new file mode 100644 index 00000000..f984c355 --- /dev/null +++ b/app/src/lib/components/resource-group/container-insights-tab.svelte @@ -0,0 +1,51 @@ + + +
+
+ {#each stats as stat} +
+

{stat.label}

+

{stat.value}

+ {#if stat.note} +

{stat.note}

+ {/if} +
+ {/each} +
+

+ {workload.stats_updated_at ? `Last updated ${new Date(workload.stats_updated_at).toLocaleTimeString()}` : "No stats reported yet."} +

+ {#if workload.restart_count > 0} +

+ Restarted {workload.restart_count} time{workload.restart_count === 1 ? "" : "s"}{workload.max_restarts !== null ? ` (max ${workload.max_restarts})` : ""} +

+ {/if} +
diff --git a/app/src/lib/components/resource-group/container-logs-tab.svelte b/app/src/lib/components/resource-group/container-logs-tab.svelte new file mode 100644 index 00000000..bfe4fd5d --- /dev/null +++ b/app/src/lib/components/resource-group/container-logs-tab.svelte @@ -0,0 +1,42 @@ + + +
+ {#if error} +

{error}

+ {/if} + {#each lines as line} +

{line}

+ {/each} +
diff --git a/app/src/lib/components/resource-group/container-network-tab.svelte b/app/src/lib/components/resource-group/container-network-tab.svelte new file mode 100644 index 00000000..32a6f1c5 --- /dev/null +++ b/app/src/lib/components/resource-group/container-network-tab.svelte @@ -0,0 +1,74 @@ + + +
+ {#if rows.length > 0} +
+

Ports

+
+ {#each rows as row (row.port)} +
+ {#each row.addresses as address (address.key)} +
+
+

{address.label}

+

{address.value}

+
+ +
+ {/each} +
+ {/each} +
+
+

+ Reachable over VPN via the RG-internal address, or externally via the node IP if a node port is set. Use "Connect VPN" to resolve RG-internal hostnames. +

+ {:else} +

No ports configured for this container.

+ {/if} +
diff --git a/app/src/lib/components/resource-group/container-settings-tab.svelte b/app/src/lib/components/resource-group/container-settings-tab.svelte new file mode 100644 index 00000000..1c99f8da --- /dev/null +++ b/app/src/lib/components/resource-group/container-settings-tab.svelte @@ -0,0 +1,97 @@ + + +
+
+ + +
+
+ + +

One KEY=VALUE per line.

+
+
+
+ + +
+
+ + +
+
+

+ Redeploying applies the new configuration by recreating the container, pulling the image again if changed. +

+ {#if error} +

{error}

+ {/if} +
+ +
+
diff --git a/app/src/lib/components/resource-group/container-shell-tab.svelte b/app/src/lib/components/resource-group/container-shell-tab.svelte new file mode 100644 index 00000000..0811dedb --- /dev/null +++ b/app/src/lib/components/resource-group/container-shell-tab.svelte @@ -0,0 +1,62 @@ + + +
+ {#if !running} +

Shell is only available while the container is running.

+ {:else} + {#if error} +

{error}

+ {/if} +
+ {/if} +
diff --git a/app/src/lib/components/resource-group/create-bucket-dialog.svelte b/app/src/lib/components/resource-group/create-bucket-dialog.svelte new file mode 100644 index 00000000..97630a4b --- /dev/null +++ b/app/src/lib/components/resource-group/create-bucket-dialog.svelte @@ -0,0 +1,60 @@ + + + (error = null)}> +
+
+ + +
+
+ + +
+
+ {#if error} +

{error}

+ {/if} +
+ + +
+
diff --git a/app/src/lib/components/resource-group/create-volume-dialog.svelte b/app/src/lib/components/resource-group/create-volume-dialog.svelte new file mode 100644 index 00000000..53992583 --- /dev/null +++ b/app/src/lib/components/resource-group/create-volume-dialog.svelte @@ -0,0 +1,57 @@ + + + (error = null)}> +
+
+ + +
+
+ + +
+
+ {#if error} +

{error}

+ {/if} +
+ + +
+
diff --git a/app/src/lib/components/resource-group/deploy-container-dialog.svelte b/app/src/lib/components/resource-group/deploy-container-dialog.svelte new file mode 100644 index 00000000..0c2ae96d --- /dev/null +++ b/app/src/lib/components/resource-group/deploy-container-dialog.svelte @@ -0,0 +1,279 @@ + + + resetDeployForm()} +> +
+
+

Deploy Container

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ Ports + +
+ {#if formPortRows.length === 0} +

No ports exposed. Container is only reachable via RG-internal DNS on its default port.

+ {:else} +
+ Container port + + RG port + + Node port +
+
+ {#each formPortRows as row, i} + {@const cPort = String(row.containerPort).trim()} + {@const rPort = String(row.rgPort).trim()} + {@const nPort = String(row.nodePort).trim()} +
+ + + + + + + +
+ {#if cPort} +

+ RG mesh: {rPort || cPort}/{row.protocol} → container:{cPort} + {#if nPort} +  Â·  external: node-ip:{nPort} → container:{cPort} + {/if} +

+ {/if} + {/each} +
+

+ RG port maps to the container port inside the RG mesh (optional, defaults to container port). Node port separately exposes it externally on the node's IP, like a Kubernetes NodePort. +

+ {/if} +
+
+ {#if volumes.length > 0} +
+ + +

Available: {volumes.map(v => v.name).join(", ")}

+
+ {/if} +
+ + +
+ {#if deployError} +

{deployError}

+ {/if} +
+ + +
+
+
diff --git a/app/src/lib/components/resource-group/deploy-vm-dialog.svelte b/app/src/lib/components/resource-group/deploy-vm-dialog.svelte new file mode 100644 index 00000000..c7cb7c4c --- /dev/null +++ b/app/src/lib/components/resource-group/deploy-vm-dialog.svelte @@ -0,0 +1,268 @@ + + + resetVmForm()} +> +
+
+

Deploy Virtual Machine

+ +
+
+
+ + +
+
+
+ ISO Image + {#if vmExistingIsos.length > 0} +
+ + +
+ {/if} +
+ {#if vmExistingIsosLoading} +

Loading isos...

+ {:else if vmIsoMode === "existing"} + + {:else} + + {#if vmDeploying && vmFormIsoFile} +
+
+
+ {/if} + {/if} +
+ {#if vmFormAdvanced} +
+ + +
+
+ + +
+
+ + +
+ {:else} +
+ + +
+
+ + +
+
+ + +
+ {/if} +
+ +
+
+

+ The ISO boots on first start. Open the console after deploying to complete the installation. +

+ {#if vmError} +

{vmError}

+ {/if} +
+ + +
+
+
diff --git a/app/src/lib/components/resource-group/modal-dialog.svelte b/app/src/lib/components/resource-group/modal-dialog.svelte new file mode 100644 index 00000000..c3aee68f --- /dev/null +++ b/app/src/lib/components/resource-group/modal-dialog.svelte @@ -0,0 +1,54 @@ + + + +
+
+
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+ +
+ {@render children()} +
+
diff --git a/app/src/lib/components/resource-group/resource-group-preview.svelte b/app/src/lib/components/resource-group/resource-group-preview.svelte new file mode 100644 index 00000000..4dd2d5eb --- /dev/null +++ b/app/src/lib/components/resource-group/resource-group-preview.svelte @@ -0,0 +1,89 @@ + + +
+
+ {#if group.preview_url} + + {#if !editing} + + {/if} + {:else} + + {/if} +
+ {#if editing} + + {/if} +
diff --git a/app/src/lib/components/resource-group/resource-group-sidebar.svelte b/app/src/lib/components/resource-group/resource-group-sidebar.svelte new file mode 100644 index 00000000..913f4f95 --- /dev/null +++ b/app/src/lib/components/resource-group/resource-group-sidebar.svelte @@ -0,0 +1,191 @@ + + +
+ + {#if editingField === "name"} + + {:else} + + {/if} +
+ {#if editingField === "description"} + + {:else} + + {/if} +
+ patch({ preview_url: url })} /> +
+
+ + +
+ +
+
+ + + Status + + +
+
+ + + Created + + {group.created_at.slice(0, 10)} +
+
+ + + Network + + {group.internal_cidr} +
+ +
+ +
+

Resources

+ {#each resources as row} +
+
+ {row.label} + {row.running}/{row.total} +
+
+
+
+
+ {/each} +
+ + diff --git a/app/src/lib/components/resource-group/resource-picker-dialog.svelte b/app/src/lib/components/resource-group/resource-picker-dialog.svelte new file mode 100644 index 00000000..d911cda2 --- /dev/null +++ b/app/src/lib/components/resource-group/resource-picker-dialog.svelte @@ -0,0 +1,48 @@ + + + + + +
+ {#each RESOURCE_TYPES as type} + + {/each} +
+
diff --git a/app/src/lib/components/resource-group/resource-table.svelte b/app/src/lib/components/resource-group/resource-table.svelte new file mode 100644 index 00000000..778760f9 --- /dev/null +++ b/app/src/lib/components/resource-group/resource-table.svelte @@ -0,0 +1,158 @@ + + +{#snippet workloadRow(w: Workload, indented: boolean)} + onOpenWorkload(w)} + onRestart={() => run(() => restartWorkload(token(), w.id), "Failed to restart container")} + onStop={() => run(() => stopWorkload(token(), w.id), "Failed to stop container")} + onDelete={() => run(() => deleteWorkload(token(), w.id), "Failed to delete container")} + /> +{/snippet} + +
+
+
+ {#each [["all", `All ${allResources.length}`], ["container", `Container ${workloads.filter((w) => w.runtime_class !== "vm").length}`], ["vm", `VM ${workloads.filter((w) => w.runtime_class === "vm").length}`], ["volume", `Volume ${volumes.length}`], ["bucket", `Bucket ${buckets.length}`]] as [tab, label]} + + {/each} +
+
+ + +
+
+ + + + + + + + + + + + + + {#if filteredResources.length === 0} + + + + {:else} + {#each filteredResources as item (item.kind + (item.kind === "stack" ? item.data.stack_id : item.data.id))} + {#if item.kind === "container" || item.kind === "vm"} + {@render workloadRow(item.data, false)} + {:else if item.kind === "stack"} + {@const stack = item.data} + {@const expanded = expandedStacks.has(stack.stack_id)} + onOpenStack(stack.stack_id)} + onToggle={() => toggleStack(stack.stack_id)} + onRestart={() => run(() => restartStack(token(), stack.stack_id), "Failed to restart stack")} + onStop={() => run(() => stopStack(token(), stack.stack_id), "Failed to stop stack")} + onDelete={() => run(() => deleteStack(token(), stack.stack_id), "Failed to delete stack")} + /> + {#if expanded} + {#each stack.children as child (child.id)} + {@render workloadRow(child, true)} + {/each} + {/if} + {:else if item.kind === "volume"} + run(() => deleteVolume(token(), item.data.id), "Failed to delete volume")} + /> + {:else if item.kind === "bucket"} + onOpenBucket(item.data)} + onDelete={() => run(() => deleteBucket(token(), item.data.id), "Failed to delete bucket")} + /> + {/if} + {/each} + {/if} + +
ResourceKindHost / SizeLoadStatus
+ {allResources.length === 0 ? "No resources yet. Deploy a container or create a volume." : "No resources match filter."} +
+
diff --git a/app/src/lib/components/resource-group/stack-row.svelte b/app/src/lib/components/resource-group/stack-row.svelte new file mode 100644 index 00000000..f2c21972 --- /dev/null +++ b/app/src/lib/components/resource-group/stack-row.svelte @@ -0,0 +1,89 @@ + + + onOpen()} +> + +
+ +
+ +
+
+

{stack.stack_name}

+

{stack.children.length} services

+
+
+ + + Compose Stack + + + {stack.children[0]?.assigned_agent_id ? stack.children[0].assigned_agent_id!.slice(0, 8) : "-"} + + - + + + + +
+ + + +
+ + diff --git a/app/src/lib/components/resource-group/volume-row.svelte b/app/src/lib/components/resource-group/volume-row.svelte new file mode 100644 index 00000000..6cdbb50b --- /dev/null +++ b/app/src/lib/components/resource-group/volume-row.svelte @@ -0,0 +1,49 @@ + + + + +
+
+ +
+
+

{v.name}

+

{v.size_gb} GB · {v.pool}

+
+
+ + + Volume + + + {v.size_gb} GB + + +
+ {v.size_gb}G +
+
+
+
+ + + + + + + + diff --git a/app/src/lib/components/resource-group/workload-row.svelte b/app/src/lib/components/resource-group/workload-row.svelte new file mode 100644 index 00000000..dcaf515d --- /dev/null +++ b/app/src/lib/components/resource-group/workload-row.svelte @@ -0,0 +1,92 @@ + + + + +
+
+ +
+

{w.service_name ?? w.name}

+
+ + + {w.runtime_class === "vm" ? "VM" : "Container"} + + + {w.assigned_agent_id ? w.assigned_agent_id.slice(0, 8) : "-"} + + +
+ {w.cpu_millicores}m +
+
+
+
+ + +
+ + {#if w.restart_count > 0} + + ↻ {w.restart_count} + + {/if} +
+ + +
+ + + +
+ + diff --git a/app/src/lib/components/rg-icon.svelte b/app/src/lib/components/rg-icon.svelte new file mode 100644 index 00000000..56bbbd21 --- /dev/null +++ b/app/src/lib/components/rg-icon.svelte @@ -0,0 +1,29 @@ + + +{#if hasIconImage} + +{:else} + +{/if} diff --git a/app/src/lib/components/search-header.svelte b/app/src/lib/components/search-header.svelte new file mode 100644 index 00000000..39d1d54b --- /dev/null +++ b/app/src/lib/components/search-header.svelte @@ -0,0 +1,25 @@ + + +
+
+ +
+
+ +
+
diff --git a/app/src/lib/components/settings/account-settings.stories.svelte b/app/src/lib/components/settings/account-settings.stories.svelte new file mode 100644 index 00000000..19f95c70 --- /dev/null +++ b/app/src/lib/components/settings/account-settings.stories.svelte @@ -0,0 +1,16 @@ + + + +
+ +
+
diff --git a/app/src/lib/components/settings/general-settings.stories.svelte b/app/src/lib/components/settings/general-settings.stories.svelte new file mode 100644 index 00000000..a56960a1 --- /dev/null +++ b/app/src/lib/components/settings/general-settings.stories.svelte @@ -0,0 +1,16 @@ + + + +
+ +
+
diff --git a/app/src/lib/components/settings/general-settings.svelte b/app/src/lib/components/settings/general-settings.svelte index bbe69da5..efb5bb23 100644 --- a/app/src/lib/components/settings/general-settings.svelte +++ b/app/src/lib/components/settings/general-settings.svelte @@ -1 +1,96 @@ -

No general settings configured.

+ + +{#if error} +
+ {error} +
+{/if} + +
+ + + Avatar fallback + + How users without a profile image are shown across the app. + + + + {#if loading} +
+ {:else} +
+
+ + +
+
+ {#if style === "blobatar"} + + {:else} + {initials(auth.user?.username ?? "??")} + {/if} +
+
+ {/if} +
+
+
diff --git a/app/src/lib/components/settings/logs-settings.stories.svelte b/app/src/lib/components/settings/logs-settings.stories.svelte new file mode 100644 index 00000000..dfb94df5 --- /dev/null +++ b/app/src/lib/components/settings/logs-settings.stories.svelte @@ -0,0 +1,16 @@ + + + +
+ +
+
diff --git a/app/src/lib/components/settings/settings-dialog.stories.svelte b/app/src/lib/components/settings/settings-dialog.stories.svelte new file mode 100644 index 00000000..19b81216 --- /dev/null +++ b/app/src/lib/components/settings/settings-dialog.stories.svelte @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/lib/components/settings/update-settings.stories.svelte b/app/src/lib/components/settings/update-settings.stories.svelte new file mode 100644 index 00000000..a06e13c6 --- /dev/null +++ b/app/src/lib/components/settings/update-settings.stories.svelte @@ -0,0 +1,16 @@ + + + +
+ +
+
diff --git a/app/src/lib/components/sidebar/app-sidebar.stories.svelte b/app/src/lib/components/sidebar/app-sidebar.stories.svelte new file mode 100644 index 00000000..58043027 --- /dev/null +++ b/app/src/lib/components/sidebar/app-sidebar.stories.svelte @@ -0,0 +1,19 @@ + + + +
+ + + +
+
diff --git a/app/src/lib/components/sidebar/nav-main.stories.svelte b/app/src/lib/components/sidebar/nav-main.stories.svelte new file mode 100644 index 00000000..48e84e69 --- /dev/null +++ b/app/src/lib/components/sidebar/nav-main.stories.svelte @@ -0,0 +1,26 @@ + + + + +
+ +
+
+
diff --git a/app/src/lib/components/sidebar/nav-projects.stories.svelte b/app/src/lib/components/sidebar/nav-projects.stories.svelte new file mode 100644 index 00000000..82106376 --- /dev/null +++ b/app/src/lib/components/sidebar/nav-projects.stories.svelte @@ -0,0 +1,26 @@ + + + + +
+ +
+
+
diff --git a/app/src/lib/components/sidebar/nav-user.stories.svelte b/app/src/lib/components/sidebar/nav-user.stories.svelte new file mode 100644 index 00000000..360614f2 --- /dev/null +++ b/app/src/lib/components/sidebar/nav-user.stories.svelte @@ -0,0 +1,19 @@ + + + + +
+ +
+
+
diff --git a/app/src/lib/components/sidebar/nav-user.svelte b/app/src/lib/components/sidebar/nav-user.svelte index 2eb5f235..b8ce7f4f 100644 --- a/app/src/lib/components/sidebar/nav-user.svelte +++ b/app/src/lib/components/sidebar/nav-user.svelte @@ -7,13 +7,18 @@ import LogOutIcon from "@lucide/svelte/icons/log-out"; import UserIcon from "@lucide/svelte/icons/user"; import { auth } from "$lib/auth/store.svelte"; - import { gravatarUrl } from "$lib/auth/api"; + import { gravatarUrl, getAvatarFallbackStyle, type AvatarFallbackStyle } from "$lib/auth/api"; + import { Blobatar } from "@blobatar/svelte"; + import { gaze } from "@blobatar/svelte/gaze"; + import "blobatar/motion.css"; + import "blobatar/gaze.css"; import { settingsDialog } from "$lib/components/settings/settings-store.svelte.js"; import { goto } from "$app/navigation"; const sidebar = useSidebar(); let avatarUrl = $state(null); + let fallbackStyle = $state("initials"); $effect(() => { const source = auth.user?.gravatar_email; @@ -24,10 +29,20 @@ gravatarUrl(source, 64).then((url) => (avatarUrl = url)); }); + $effect(() => { + if (!auth.token) return; + getAvatarFallbackStyle(auth.token) + .then((style) => (fallbackStyle = style)) + .catch(() => {}); + }); + function initials(name: string): string { return name.slice(0, 2).toUpperCase(); } + const triggerEyes = gaze({ travel: 3, target: "pointer" }); + const menuEyes = gaze({ travel: 3, target: "pointer" }); + function logout() { auth.clearSession(); goto("/login"); @@ -44,12 +59,16 @@ class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" {...props} > - + {#if avatarUrl} {/if} - - {auth.user ? initials(auth.user.username) : "??"} + + {#if fallbackStyle === "blobatar" && auth.user} + + {:else} + {auth.user ? initials(auth.user.username) : "??"} + {/if}
@@ -68,12 +87,16 @@ >
- + {#if avatarUrl} {/if} - - {auth.user ? initials(auth.user.username) : "??"} + + {#if fallbackStyle === "blobatar" && auth.user} + + {:else} + {auth.user ? initials(auth.user.username) : "??"} + {/if}
diff --git a/app/src/lib/components/sidebar/team-switcher.stories.svelte b/app/src/lib/components/sidebar/team-switcher.stories.svelte new file mode 100644 index 00000000..dac9ee46 --- /dev/null +++ b/app/src/lib/components/sidebar/team-switcher.stories.svelte @@ -0,0 +1,22 @@ + + + + +
+ +
+
+
diff --git a/app/src/lib/components/sidebar/update-card.stories.svelte b/app/src/lib/components/sidebar/update-card.stories.svelte new file mode 100644 index 00000000..9e1ac394 --- /dev/null +++ b/app/src/lib/components/sidebar/update-card.stories.svelte @@ -0,0 +1,19 @@ + + + + +
+ +
+
+
diff --git a/app/src/lib/components/status-badge.stories.svelte b/app/src/lib/components/status-badge.stories.svelte new file mode 100644 index 00000000..862b4408 --- /dev/null +++ b/app/src/lib/components/status-badge.stories.svelte @@ -0,0 +1,36 @@ + + + + + +
+ + + + + + + + + + +
+
+ + diff --git a/app/src/lib/components/status-badge.svelte b/app/src/lib/components/status-badge.svelte index aaff01d0..ca3c5d07 100644 --- a/app/src/lib/components/status-badge.svelte +++ b/app/src/lib/components/status-badge.svelte @@ -6,11 +6,12 @@ status, label: labelOverride, class: className = "", - }: { status: string; label?: string; class?: string } = $props(); + title, + }: { status: string; label?: string; class?: string; title?: string } = $props(); const variant = $derived(statusVariant(status)); const label = $derived(labelOverride ?? statusLabel(status)); const loading = $derived(isTransientStatus(status)); -{label} +{label} diff --git a/app/src/lib/components/ui/avatar/avatar.stories.svelte b/app/src/lib/components/ui/avatar/avatar.stories.svelte new file mode 100644 index 00000000..0b0a558f --- /dev/null +++ b/app/src/lib/components/ui/avatar/avatar.stories.svelte @@ -0,0 +1,53 @@ + + + + + + CS + + + + + + + CS + + + + +
+ + SM + + + MD + + + LG + +
+
+ + + + + AA + + + BB + + + CC + + +3 + + diff --git a/app/src/lib/components/ui/avatar/avatar.svelte b/app/src/lib/components/ui/avatar/avatar.svelte index ea6fde56..f88fe456 100644 --- a/app/src/lib/components/ui/avatar/avatar.svelte +++ b/app/src/lib/components/ui/avatar/avatar.svelte @@ -19,7 +19,7 @@ data-slot="avatar" data-size={size} class={cn( - "size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten", + "size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none overflow-hidden after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten", className )} {...restProps} diff --git a/app/src/lib/components/ui/badge/badge.stories.svelte b/app/src/lib/components/ui/badge/badge.stories.svelte new file mode 100644 index 00000000..b256abc4 --- /dev/null +++ b/app/src/lib/components/ui/badge/badge.stories.svelte @@ -0,0 +1,45 @@ + + + + {#snippet template(args)} + Badge + {/snippet} + + + +
+ Default + Secondary + Outline + Success + Warning + Destructive + Pending +
+
+ + + {#snippet template(args)} + Loading + {/snippet} + diff --git a/app/src/lib/components/ui/breadcrumb/breadcrumb.stories.svelte b/app/src/lib/components/ui/breadcrumb/breadcrumb.stories.svelte new file mode 100644 index 00000000..38c30627 --- /dev/null +++ b/app/src/lib/components/ui/breadcrumb/breadcrumb.stories.svelte @@ -0,0 +1,54 @@ + + + + + + + Resource groups + + + + production + + + + csfx-node-01 + + + + + + + + + + Home + + + + + + + + Current + + + + diff --git a/app/src/lib/components/ui/button/button.stories.svelte b/app/src/lib/components/ui/button/button.stories.svelte new file mode 100644 index 00000000..16a049a3 --- /dev/null +++ b/app/src/lib/components/ui/button/button.stories.svelte @@ -0,0 +1,58 @@ + + + + {#snippet template(args)} + + {/snippet} + + + +
+ + + + + + +
+
+ + +
+ + + + +
+
+ + + {#snippet template(args)} + + {/snippet} + diff --git a/app/src/lib/components/ui/card/card.stories.svelte b/app/src/lib/components/ui/card/card.stories.svelte new file mode 100644 index 00000000..0b00969f --- /dev/null +++ b/app/src/lib/components/ui/card/card.stories.svelte @@ -0,0 +1,37 @@ + + + + + + Node metrics + Last 24 hours + + + + + +

CPU 42%, Memory 61%

+
+ + + +
+
diff --git a/app/src/lib/components/ui/chart/chart-container.svelte b/app/src/lib/components/ui/chart/chart-container.svelte new file mode 100644 index 00000000..3687ea06 --- /dev/null +++ b/app/src/lib/components/ui/chart/chart-container.svelte @@ -0,0 +1,80 @@ + + +
+ + {@render children?.()} +
diff --git a/app/src/lib/components/ui/chart/chart-style.svelte b/app/src/lib/components/ui/chart/chart-style.svelte new file mode 100644 index 00000000..74c2b8a3 --- /dev/null +++ b/app/src/lib/components/ui/chart/chart-style.svelte @@ -0,0 +1,37 @@ + + +{#if themeContents} + {#key id} + + {themeContents} + + {/key} +{/if} diff --git a/app/src/lib/components/ui/chart/chart-tooltip.svelte b/app/src/lib/components/ui/chart/chart-tooltip.svelte new file mode 100644 index 00000000..88114368 --- /dev/null +++ b/app/src/lib/components/ui/chart/chart-tooltip.svelte @@ -0,0 +1,180 @@ + + +{#snippet TooltipLabel()} + {#if formattedLabel} +
+ {#if typeof formattedLabel === "function"} + {@render formattedLabel()} + {:else} + {formattedLabel} + {/if} +
+ {/if} +{/snippet} + + +
+ {#if !nestLabel} + {@render TooltipLabel()} + {/if} +
+ {#each visibleSeries as item, i (item.key + i)} + {@const key = `${nameKey || item.key || item.label || "value"}`} + {@const itemConfig = getPayloadConfigFromPayload( + chart.config, + item, + key, + chartCtx.tooltip.data + )} + {@const indicatorColor = color || item.config?.color || item.color} +
svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground", + indicator === "dot" && "items-center" + )} + > + {#if formatter && item.value !== undefined && item.label} + {@render formatter({ + value: item.value, + name: item.label, + item, + index: i, + payload: visibleSeries, + })} + {:else} + {#if itemConfig?.icon} + + {:else if !hideIndicator} +
+ {/if} +
+
+ {#if nestLabel} + {@render TooltipLabel()} + {/if} + + {itemConfig?.label || item.label} + +
+ {#if item.value !== undefined} + + {item.value.toLocaleString()} + + {/if} +
+ {/if} +
+ {/each} +
+
+
diff --git a/app/src/lib/components/ui/chart/chart-utils.ts b/app/src/lib/components/ui/chart/chart-utils.ts new file mode 100644 index 00000000..6e1f0000 --- /dev/null +++ b/app/src/lib/components/ui/chart/chart-utils.ts @@ -0,0 +1,68 @@ +import { getContext, setContext, type Component, type Snippet } from "svelte"; +import type { Tooltip } from "layerchart"; + +export const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: string; + icon?: Component; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +export type ExtractSnippetParams = T extends Snippet<[infer P]> ? P : never; + +export type TooltipPayload = Tooltip.TooltipSeries; + +// Helper to extract item config from a payload. +export function getPayloadConfigFromPayload( + config: ChartConfig, + payload: TooltipPayload, + key: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data?: Record | null +) { + if (typeof payload !== "object" || payload === null) return undefined; + + const payloadConfig = + "config" in payload && typeof payload.config === "object" && payload.config !== null + ? payload.config + : undefined; + + let configLabelKey: string = key; + + if (payload.key === key) { + configLabelKey = payload.key; + } else if (payload.label === key) { + configLabelKey = payload.label; + } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") { + configLabelKey = payload[key as keyof typeof payload] as string; + } else if ( + payloadConfig !== undefined && + key in payloadConfig && + typeof payloadConfig[key as keyof typeof payloadConfig] === "string" + ) { + configLabelKey = payloadConfig[key as keyof typeof payloadConfig] as string; + } else if (data != null && key in data && typeof data[key] === "string") { + configLabelKey = data[key] as string; + } + + return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config]; +} + +type ChartContextValue = { + config: ChartConfig; +}; + +const chartContextKey = Symbol("chart-context"); + +export function setChartContext(value: ChartContextValue) { + return setContext(chartContextKey, value); +} + +export function useChart() { + return getContext(chartContextKey); +} diff --git a/app/src/lib/components/ui/chart/index.ts b/app/src/lib/components/ui/chart/index.ts new file mode 100644 index 00000000..f22375e0 --- /dev/null +++ b/app/src/lib/components/ui/chart/index.ts @@ -0,0 +1,6 @@ +import ChartContainer from "./chart-container.svelte"; +import ChartTooltip from "./chart-tooltip.svelte"; + +export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js"; + +export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip }; diff --git a/app/src/lib/components/ui/checkbox/checkbox.stories.svelte b/app/src/lib/components/ui/checkbox/checkbox.stories.svelte new file mode 100644 index 00000000..8c0835f7 --- /dev/null +++ b/app/src/lib/components/ui/checkbox/checkbox.stories.svelte @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/lib/components/ui/collapsible/collapsible.stories.svelte b/app/src/lib/components/ui/collapsible/collapsible.stories.svelte new file mode 100644 index 00000000..7089a011 --- /dev/null +++ b/app/src/lib/components/ui/collapsible/collapsible.stories.svelte @@ -0,0 +1,24 @@ + + + + + + {#snippet child({ props })} + + {/snippet} + + + Additional node details rendered here. + + + diff --git a/app/src/lib/components/ui/dialog/dialog.stories.svelte b/app/src/lib/components/ui/dialog/dialog.stories.svelte new file mode 100644 index 00000000..6c2dba99 --- /dev/null +++ b/app/src/lib/components/ui/dialog/dialog.stories.svelte @@ -0,0 +1,33 @@ + + + + + + {#snippet child({ props })} + + {/snippet} + + + Delete node + + This permanently removes the node from the cluster. This action cannot be undone. + + + + diff --git a/app/src/lib/components/ui/dropdown-menu/dropdown-menu.stories.svelte b/app/src/lib/components/ui/dropdown-menu/dropdown-menu.stories.svelte new file mode 100644 index 00000000..0370c90b --- /dev/null +++ b/app/src/lib/components/ui/dropdown-menu/dropdown-menu.stories.svelte @@ -0,0 +1,35 @@ + + + + + + {#snippet child({ props })} + + {/snippet} + + + Node + + Restart + Drain + Delete + + + diff --git a/app/src/lib/components/ui/input-otp/input-otp.stories.svelte b/app/src/lib/components/ui/input-otp/input-otp.stories.svelte new file mode 100644 index 00000000..06b5eadd --- /dev/null +++ b/app/src/lib/components/ui/input-otp/input-otp.stories.svelte @@ -0,0 +1,28 @@ + + + + + {#snippet children({ cells })} + + {#each cells.slice(0, 3) as cell (cell)} + + {/each} + + + + {#each cells.slice(3, 6) as cell (cell)} + + {/each} + + {/snippet} + + diff --git a/app/src/lib/components/ui/input/input.stories.svelte b/app/src/lib/components/ui/input/input.stories.svelte new file mode 100644 index 00000000..fdd5f938 --- /dev/null +++ b/app/src/lib/components/ui/input/input.stories.svelte @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/lib/components/ui/label/label.stories.svelte b/app/src/lib/components/ui/label/label.stories.svelte new file mode 100644 index 00000000..f7a6772a --- /dev/null +++ b/app/src/lib/components/ui/label/label.stories.svelte @@ -0,0 +1,22 @@ + + + + + + + +
+ + +
+
diff --git a/app/src/lib/components/ui/separator/separator.stories.svelte b/app/src/lib/components/ui/separator/separator.stories.svelte new file mode 100644 index 00000000..33c61ea6 --- /dev/null +++ b/app/src/lib/components/ui/separator/separator.stories.svelte @@ -0,0 +1,26 @@ + + + +
+

Above

+ +

Below

+
+
+ + +
+ Left + + Right +
+
diff --git a/app/src/lib/components/ui/sheet/sheet.stories.svelte b/app/src/lib/components/ui/sheet/sheet.stories.svelte new file mode 100644 index 00000000..a32da347 --- /dev/null +++ b/app/src/lib/components/ui/sheet/sheet.stories.svelte @@ -0,0 +1,38 @@ + + + + + + {#snippet child({ props })} + + {/snippet} + + + + csfx-node-01 + Node details and metrics + + + + + + + diff --git a/app/src/lib/components/ui/sidebar/sidebar.stories.svelte b/app/src/lib/components/ui/sidebar/sidebar.stories.svelte new file mode 100644 index 00000000..456d9509 --- /dev/null +++ b/app/src/lib/components/ui/sidebar/sidebar.stories.svelte @@ -0,0 +1,66 @@ + + + +
+ + + + CSFX + + + + Platform + + + + Nodes + + + Buckets + + + Resource groups + + + Logs + + + + + + + cedric.stillecke + + + +
+ + Page content +
+
+
+
+
diff --git a/app/src/lib/components/ui/skeleton/skeleton.stories.svelte b/app/src/lib/components/ui/skeleton/skeleton.stories.svelte new file mode 100644 index 00000000..264c1d8f --- /dev/null +++ b/app/src/lib/components/ui/skeleton/skeleton.stories.svelte @@ -0,0 +1,26 @@ + + + + + + + +
+ + + +
+
+ + + + diff --git a/app/src/lib/components/ui/sonner/sonner.stories.svelte b/app/src/lib/components/ui/sonner/sonner.stories.svelte new file mode 100644 index 00000000..094f20a0 --- /dev/null +++ b/app/src/lib/components/ui/sonner/sonner.stories.svelte @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/lib/components/ui/spinner/spinner.stories.svelte b/app/src/lib/components/ui/spinner/spinner.stories.svelte new file mode 100644 index 00000000..39868a01 --- /dev/null +++ b/app/src/lib/components/ui/spinner/spinner.stories.svelte @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/lib/components/ui/switch/switch.stories.svelte b/app/src/lib/components/ui/switch/switch.stories.svelte new file mode 100644 index 00000000..d469969f --- /dev/null +++ b/app/src/lib/components/ui/switch/switch.stories.svelte @@ -0,0 +1,24 @@ + + + + + + + diff --git a/app/src/lib/components/ui/tooltip/tooltip.stories.svelte b/app/src/lib/components/ui/tooltip/tooltip.stories.svelte new file mode 100644 index 00000000..8fbc90b0 --- /dev/null +++ b/app/src/lib/components/ui/tooltip/tooltip.stories.svelte @@ -0,0 +1,24 @@ + + + + + + + {#snippet child({ props })} + + {/snippet} + + Node is running + + + diff --git a/app/src/lib/components/vm/vm-console-preview.svelte b/app/src/lib/components/vm/vm-console-preview.svelte new file mode 100644 index 00000000..7b6ee7e6 --- /dev/null +++ b/app/src/lib/components/vm/vm-console-preview.svelte @@ -0,0 +1,31 @@ + + + diff --git a/app/src/lib/components/vm/vm-details-tab.svelte b/app/src/lib/components/vm/vm-details-tab.svelte new file mode 100644 index 00000000..96a4650d --- /dev/null +++ b/app/src/lib/components/vm/vm-details-tab.svelte @@ -0,0 +1,29 @@ + + +
+
+ {#each rows as row (row.label)} +
+ {row.label} + {row.value} +
+ {/each} +
+
diff --git a/app/src/lib/components/vm/vm-summary-tab.svelte b/app/src/lib/components/vm/vm-summary-tab.svelte new file mode 100644 index 00000000..fd261142 --- /dev/null +++ b/app/src/lib/components/vm/vm-summary-tab.svelte @@ -0,0 +1,40 @@ + + +
+
+ + + +
+ Network + RX {workload.network_rx_bytes !== null ? fmtBytes(workload.network_rx_bytes) : "-"} + TX {workload.network_tx_bytes !== null ? fmtBytes(workload.network_tx_bytes) : "-"} +
+
+

{lastUpdated}

+ {#if workload.restart_count > 0} +

+ Restarted {workload.restart_count} time{workload.restart_count === 1 ? "" : "s"} +

+ {/if} +
diff --git a/app/src/lib/components/vnc-console.stories.svelte b/app/src/lib/components/vnc-console.stories.svelte new file mode 100644 index 00000000..e897ff01 --- /dev/null +++ b/app/src/lib/components/vnc-console.stories.svelte @@ -0,0 +1,18 @@ + + + + {#snippet template(args)} +
+ +
+ {/snippet} +
diff --git a/app/src/lib/components/vnc-console.svelte b/app/src/lib/components/vnc-console.svelte index d2d9b41e..1b6200e9 100644 --- a/app/src/lib/components/vnc-console.svelte +++ b/app/src/lib/components/vnc-console.svelte @@ -2,7 +2,11 @@ import { onDestroy, onMount } from "svelte"; import { getWorkloadVncUrl } from "$lib/api/resource-groups"; - let { token, workloadId }: { token: string; workloadId: string } = $props(); + let { + token, + workloadId, + viewOnly = false, + }: { token: string; workloadId: string; viewOnly?: boolean } = $props(); let container = $state(null); let error = $state(null); @@ -15,6 +19,7 @@ const { default: RFB } = await import("@novnc/novnc"); rfb = new RFB(container, url); rfb.scaleViewport = true; + rfb.viewOnly = viewOnly; rfb.addEventListener("disconnect", () => { error = "Console disconnected"; }); diff --git a/app/src/lib/utils/clipboard.svelte.ts b/app/src/lib/utils/clipboard.svelte.ts new file mode 100644 index 00000000..a1d7d785 --- /dev/null +++ b/app/src/lib/utils/clipboard.svelte.ts @@ -0,0 +1,24 @@ +const RESET_DELAY_MS = 1500; + +export function createClipboard() { + let copiedKey = $state(null); + + async function copy(value: string, key: string) { + try { + await navigator.clipboard.writeText(value); + copiedKey = key; + setTimeout(() => { + if (copiedKey === key) copiedKey = null; + }, RESET_DELAY_MS); + } catch { + copiedKey = null; + } + } + + return { + get copiedKey() { + return copiedKey; + }, + copy, + }; +} diff --git a/app/src/lib/utils/cluster-metrics.ts b/app/src/lib/utils/cluster-metrics.ts new file mode 100644 index 00000000..9bb24de8 --- /dev/null +++ b/app/src/lib/utils/cluster-metrics.ts @@ -0,0 +1,40 @@ +import type { ClusterStats, HealthHistoryPoint } from "$lib/api/nodes"; + +export interface UsageSample { + cpu: number; + memory: number; + disk: number; +} + +const HEALTHY_UPTIME = 99.9; +const DEGRADED_UPTIME = 99; +const BUCKET_SECONDS = 3600; +const MS_PER_SECOND = 1000; + +function percent(used: number, total: number): number { + return total > 0 ? Math.min(100, (used / total) * 100) : 0; +} + +export function uptimeColor(uptimePercent: number | null): string { + if (uptimePercent === null) return "currentColor"; + if (uptimePercent >= HEALTHY_UPTIME) return "#22c55e"; + if (uptimePercent >= DEGRADED_UPTIME) return "#eab308"; + return "#ef4444"; +} + +export function toUsageSample(stats: ClusterStats): UsageSample { + return { + cpu: Math.min(100, stats.avg_cpu_usage_percent), + memory: percent(stats.used_memory_bytes, stats.total_memory_bytes), + disk: percent(stats.used_disk_bytes, stats.total_disk_bytes), + }; +} + +export function clusterUptimePercent(points: HealthHistoryPoint[], nodeCount: number): number | null { + if (points.length === 0 || nodeCount === 0) return null; + const first = new Date(`${points[0].bucket}Z`).getTime(); + const last = new Date(`${points[points.length - 1].bucket}Z`).getTime(); + const expectedBuckets = Math.round((last - first) / (BUCKET_SECONDS * MS_PER_SECOND)) + 1; + const available = points.reduce((sum, point) => sum + Math.min(1, point.online_count / nodeCount), 0); + return Math.min(100, (available / expectedBuckets) * 100); +} diff --git a/app/src/lib/utils/dashboard.ts b/app/src/lib/utils/dashboard.ts new file mode 100644 index 00000000..3087e95f --- /dev/null +++ b/app/src/lib/utils/dashboard.ts @@ -0,0 +1,39 @@ +import type { Node } from "$lib/api/nodes"; +import type { Bucket, Volume, Workload } from "$lib/api/resource-groups"; + +export interface Segment { + label: string; + value: number; + color: string; +} + +const COLORS = { + healthy: "#22c55e", + problem: "#eab308", + offline: "#ef4444", + vm: "#6366f1", + container: "#38bdf8", + volume: "#a855f7", + bucket: "#f59e0b", +}; + +export function summarizeNodes(nodes: Node[]): Segment[] { + const count = (status: string) => nodes.filter((n) => n.status.toLowerCase() === status).length; + const online = count("online"); + const offline = count("offline"); + return [ + { label: "Online", value: online, color: COLORS.healthy }, + { label: "Problems", value: nodes.length - online - offline, color: COLORS.problem }, + { label: "Offline", value: offline, color: COLORS.offline }, + ]; +} + +export function summarizeServices(workloads: Workload[], volumes: Volume[], buckets: Bucket[]): Segment[] { + const vms = workloads.filter((w) => w.runtime_class === "vm").length; + return [ + { label: "VMs", value: vms, color: COLORS.vm }, + { label: "Containers", value: workloads.length - vms, color: COLORS.container }, + { label: "Volumes", value: volumes.length, color: COLORS.volume }, + { label: "Buckets", value: buckets.length, color: COLORS.bucket }, + ]; +} diff --git a/app/src/lib/utils/format.ts b/app/src/lib/utils/format.ts new file mode 100644 index 00000000..f38f07e3 --- /dev/null +++ b/app/src/lib/utils/format.ts @@ -0,0 +1,5 @@ +export function fmtBytes(bytes: number): string { + if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`; + if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(0)} MB`; + return `${bytes} B`; +} diff --git a/app/src/lib/utils/resource-items.ts b/app/src/lib/utils/resource-items.ts new file mode 100644 index 00000000..ba961f21 --- /dev/null +++ b/app/src/lib/utils/resource-items.ts @@ -0,0 +1,78 @@ +import type { Bucket, Volume, Workload } from "$lib/api/resource-groups"; + +export type ResourceTab = "all" | "container" | "vm" | "volume" | "bucket"; + +export interface WorkloadStack { + stack_id: string; + stack_name: string; + children: Workload[]; +} + +export type ResourceItem = + | { kind: "container"; data: Workload } + | { kind: "vm"; data: Workload } + | { kind: "stack"; data: WorkloadStack } + | { kind: "volume"; data: Volume } + | { kind: "bucket"; data: Bucket }; + +export function groupWorkloadsByStack(items: Workload[]): ResourceItem[] { + const standalone: Workload[] = []; + const stacks = new Map(); + + for (const workload of items) { + if (!workload.stack_id) { + standalone.push(workload); + continue; + } + const children = stacks.get(workload.stack_id) ?? []; + children.push(workload); + stacks.set(workload.stack_id, children); + } + + const stackItems = Array.from(stacks.entries()).map( + ([stackId, children]): ResourceItem => ({ + kind: "stack", + data: { stack_id: stackId, stack_name: `Stack ${stackId.slice(0, 8)}`, children }, + }), + ); + + return [ + ...standalone.map((w): ResourceItem => ({ kind: w.runtime_class === "vm" ? "vm" : "container", data: w })), + ...stackItems, + ]; +} + +export function buildResourceItems(workloads: Workload[], volumes: Volume[], buckets: Bucket[]): ResourceItem[] { + return [ + ...groupWorkloadsByStack(workloads), + ...volumes.map((data): ResourceItem => ({ kind: "volume", data })), + ...buckets.map((data): ResourceItem => ({ kind: "bucket", data })), + ]; +} + +function matchesTab(item: ResourceItem, tab: ResourceTab): boolean { + if (tab === "all") return true; + if (tab === "container") return item.kind === "container" || item.kind === "stack"; + return item.kind === tab; +} + +function matchesText(item: ResourceItem, query: string): boolean { + const includes = (value: string) => value.toLowerCase().includes(query); + if (item.kind === "container" || item.kind === "vm") return includes(item.data.name) || includes(item.data.image); + if (item.kind === "stack") { + return includes(item.data.stack_name) || item.data.children.some((c) => includes(c.name) || includes(c.image)); + } + return includes(item.data.name); +} + +export function filterResources(items: ResourceItem[], tab: ResourceTab, text: string): ResourceItem[] { + const query = text.toLowerCase(); + return items.filter((item) => matchesTab(item, tab) && (!query || matchesText(item, query))); +} + +export function stackStatus(children: Workload[]): string { + if (children.every((c) => c.status === "running")) return "running"; + if (children.some((c) => c.status === "failed" || c.status === "error")) return "failed"; + if (children.every((c) => c.status === "stopped")) return "stopped"; + return "pending"; +} diff --git a/app/src/lib/utils/status.ts b/app/src/lib/utils/status.ts index 0e8047d2..03d1a316 100644 --- a/app/src/lib/utils/status.ts +++ b/app/src/lib/utils/status.ts @@ -16,8 +16,11 @@ const VARIANT_BY_STATUS: Record = { active: "success", available: "success", in_use: "success", + online: "success", failed: "destructive", error: "destructive", + offline: "destructive", + degraded: "warning", scheduled: "pending", attaching: "pending", detaching: "pending", diff --git a/app/src/novnc.d.ts b/app/src/novnc.d.ts index 5e0ae5ac..e241dfb5 100644 --- a/app/src/novnc.d.ts +++ b/app/src/novnc.d.ts @@ -3,6 +3,7 @@ declare module "@novnc/novnc" { constructor(target: HTMLElement, urlOrChannel: string, options?: Record); scaleViewport: boolean; resizeSession: boolean; + viewOnly: boolean; disconnect(): void; } } diff --git a/app/src/routes/(app)/+layout.svelte b/app/src/routes/(app)/+layout.svelte index 7900ef5a..4d23469f 100644 --- a/app/src/routes/(app)/+layout.svelte +++ b/app/src/routes/(app)/+layout.svelte @@ -1,6 +1,7 @@ - + {@render children()} + diff --git a/app/src/routes/(app)/+page.svelte b/app/src/routes/(app)/+page.svelte index 497f86ca..30466772 100644 --- a/app/src/routes/(app)/+page.svelte +++ b/app/src/routes/(app)/+page.svelte @@ -1,11 +1,127 @@ -
- -
+
+ + +
+ -
-

Dashboard

+
+
+

+ Welcome back{#if auth.user}, {auth.user.username}{/if} +

+

+ Global uptime at + {dailyUptimePercent !== null ? `${dailyUptimePercent.toFixed(2)}%` : "-"} + in the last 24h. +

+
+ {#if error} +

{error}

+ {/if} +
+ + +
+
+
diff --git a/app/src/routes/(app)/nodes/+page.svelte b/app/src/routes/(app)/nodes/+page.svelte index ab6a4794..c45cc92a 100644 --- a/app/src/routes/(app)/nodes/+page.svelte +++ b/app/src/routes/(app)/nodes/+page.svelte @@ -1,66 +1,112 @@ -
- - / - Nodes -
+{#snippet metricBars(percent: number)} +
+ {#each [0.4, 0.7, 0.55, 1, 0.85] as heightRatio, i (i)} + + {/each} +
+{/snippet} + +
+ + +
+
+
+ +
+
+ +
+
-
+

Nodes

Manage and monitor your cluster nodes

-
- {#if stats} -
-
-

Total Nodes

-

{stats.node_count}

+
+
+
+ + Node Summary
- -
-
-

Healthy

-
- {#each (['1h', '7d', '30d'] as HealthRange[]) as r} - +
+ + + {#snippet child({ props })} + + {/snippet} + + + {#each [...new Set(nodes.map((n) => n.status))] as status (status)} + (statusFilter = toggleSet(statusFilter, status))} + > + {status} + {/each} -
-
-
-
- -

{stats.online_count}

-
- {#if sparklineValues().length >= 2} - - - - {/if} -
-
- -
-

vCPU Capacity

-
-
-

{stats.total_cpu_cores}

-

{cpuPct().toFixed(1)}% used

-
- {#if cpuHistory.length >= 2} - - - - {/if} -
-
- -
-

Memory

-
-
-

{bytesToGb(stats.used_memory_bytes)}

-

/ {bytesToGb(stats.total_memory_bytes)}

-
- {#if memHistory.length >= 2} - - - - {/if} -
+ + + + + + {#snippet child({ props })} + + {/snippet} + + + (sortColumn = value as SortColumn)}> + {#each Object.entries(SORT_LABELS) as [value, label] (value)} + {label} + {/each} + + + (sortAscending = !checked)} + > + Descending + + + + + +
- {/if} - -
- + - - + + + + {#if loading} - + {:else if error} - + {:else if nodes.length === 0} - + + + {:else if filteredNodes.length === 0} + + {:else} - {#each nodes as node (node.id)} + {#each filteredNodes as node (node.id)} + {@const metrics = nodeMetrics(node)} + {@const cpu = metrics?.cpu_usage_percent ?? 0} + {@const memory = metricRatio(metrics?.memory_used_bytes ?? null, metrics?.memory_total_bytes ?? null)} + {@const disk = metricRatio(metrics?.disk_used_bytes ?? null, metrics?.disk_total_bytes ?? null)} openNode(node)} @@ -343,9 +441,42 @@ - + + +
IDHostname + + IP OS ArchVersionStatus + + + + + + + + Heartbeat
Loading...Loading...
{error}{error}
No nodes registeredNo nodes registered
No nodes match the current filters
{node.ip_address ?? "-"} {node.os_type} {node.os_version} {node.architecture}{node.agent_version} - {node.status} + {#if node.maintenance_until && new Date(node.maintenance_until).getTime() > Date.now()} + + {:else} + + {/if} + + {#if metrics} +
+ {@render metricBars(cpu)} + {cpu.toFixed(0)}% +
+ {:else} + - + {/if} +
+ {#if metrics} +
+ {@render metricBars(memory)} + {memory.toFixed(0)}% +
+ {:else} + - + {/if} +
+ {#if metrics} +
+ {@render metricBars(disk)} + {disk.toFixed(0)}% +
+ {:else} + - + {/if}
{node.last_heartbeat ? node.last_heartbeat.slice(0, 16) : "never"} @@ -356,6 +487,6 @@
+
+
- - diff --git a/app/src/routes/(app)/nodes/[id]/+page.svelte b/app/src/routes/(app)/nodes/[id]/+page.svelte new file mode 100644 index 00000000..c2eead31 --- /dev/null +++ b/app/src/routes/(app)/nodes/[id]/+page.svelte @@ -0,0 +1,1233 @@ + + +{#snippet gaugeCard(value: number, label: string, detail: string)} + {#if true} + {@const arc = gaugeArc(value, 44)} +
+
+ + + + +
+ {value.toFixed(0)}% +
+
+
+ {label} + {detail} +
+
+ {/if} +{/snippet} + +{#snippet historyChart(label: string, currentValue: number, currentLabel: string, path: string, values: number[], color: string, maxValue: number)} + {@const fillPath = sparklineFillPath(path, values, 440, 120)} + {@const gradientId = 'fill-' + label.toLowerCase().replace(/[^a-z0-9]+/g, '-')} +
+
+

{label}

+

{currentLabel}

+
+
+ + + + + + + + + + +
+ {maxValue.toFixed(0)}% + 0% +
+
+
+{/snippet} + +{#snippet networkHistoryChart(points: { rxBps: number; txBps: number }[], max: number)} + {@const rxValues = points.map((p) => p.rxBps)} + {@const txValues = points.map((p) => p.txBps)} + {@const rxPath = sparklinePath(rxValues, 440, 120, max)} + {@const txPath = sparklinePath(txValues, 440, 120, max)} + {@const rxFillPath = sparklineFillPath(rxPath, rxValues, 440, 120)} + {@const lastRx = points[points.length - 1]?.rxBps ?? 0} + {@const lastTx = points[points.length - 1]?.txBps ?? 0} +
+
+

Network

+

+ RX {formatBandwidth(lastRx)} + / + TX {formatBandwidth(lastTx)} +

+
+
+ + + + + + + + + + + +
+ {formatBandwidth(max)} + 0 B/s +
+
+
+{/snippet} + +
+ + {#if node} +
+ +
+
+
+ {node.hostname} + + {#if node.cordoned} + cordoned + {/if} + {#if isInMaintenance} + + maintenance{maintenanceRemaining ? ` · ${maintenanceRemaining}` : ''} + + {/if} +
+
+ + + {node.ip_address ?? 'no ip'} + + + + v{node.agent_version} + + {#if metrics?.uptime_seconds != null} + + + up {formatUptime(metrics.uptime_seconds)} + + {/if} +
+
+ +
+ {#if isInMaintenance} + + {:else} + + {/if} + + {#if node.cordoned} + + {:else} + + {/if} + +
+ {/if} +
+ +{#if node && powerActionError} +

{powerActionError}

+{/if} + +
+ {#if loading} +

Loading...

+ {:else if error} +

{error}

+ {:else if node} +
+
+ {#each tabs as tab} + + {/each} +
+
+ +
+ {#if activeTab === 'summary'} +
+
+ {#if metricsError} +

{metricsError}

+ {:else} +
+ {#if true} + {@const cpuVal = clampPct(metrics?.cpu_usage_percent ?? null)} + {@const cpuDetail = metrics?.cpu_model ?? ((metrics?.cpu_cores ?? '-') + ' cores')} + {@render gaugeCard(cpuVal, 'CPU', cpuDetail)} + {/if} + {#if true} + {@const memVal = clampPct(memPct())} + {@const memDetail = bytesToGb(metrics?.memory_total_bytes ?? null)} + {@render gaugeCard(memVal, 'Memory', memDetail)} + {/if} + {#if true} + {@const diskVal = clampPct(diskPct())} + {@const diskDetail = bytesToGb(metrics?.disk_total_bytes ?? null)} + {@render gaugeCard(diskVal, 'Disk', diskDetail)} + {/if} + {#if true} + {@const rxPct = netRxPct()} + {@const txPct = netTxPct()} + {@const outerR = 44} + {@const innerR = 33} + {@const outerC = 2 * Math.PI * outerR} + {@const innerC = 2 * Math.PI * innerR} + {@const outerArc = outerC * GAUGE_ARC_FRACTION} + {@const innerArc = innerC * GAUGE_ARC_FRACTION} +
+
+ + + + + + +
+ Network +
+
+
+ RX {formatBytes(metrics?.network_rx_bytes ?? null)} + TX {formatBytes(metrics?.network_tx_bytes ?? null)} +
+
+ {/if} +
+ {/if} +
+ +
+ +
+
+ History +
+ {#if history.length < 2} +
+ {#each [0, 1] as i (i)} +
+
+
+
+
+
+
+ {/each} +
+
+
+
+
+
+
+
+ {:else} + {@const cpuValues = history.map((h) => h.cpu)} + {@const memValues = history.map((h) => h.memory)} + {@const cpuMax = Math.max(20, ...cpuValues)} + {@const memMax = Math.max(20, ...memValues)} + {@const netMax = Math.max(1_048_576, ...history.map((h) => Math.max(h.rxBps, h.txBps)))} +
+ {@render historyChart('CPU usage', cpuValues[cpuValues.length - 1], pctStr(cpuValues[cpuValues.length - 1]), sparklinePath(cpuValues, 440, 120, cpuMax), cpuValues, '#3b82f6', cpuMax)} + {@render historyChart('Memory usage', memValues[memValues.length - 1], pctStr(memValues[memValues.length - 1]), sparklinePath(memValues, 440, 120, memMax), memValues, '#a855f7', memMax)} +
+
+ {@render networkHistoryChart(history, netMax)} +
+ {/if} +
+
+ +
+
+ Alerts + {#if openAlerts.length > 0} + + {/if} +
+
+ {#if alertsLoading} +
+ {#each [0, 1, 2] as i (i)} +
+
+
+
+
+
+
+ {/each} +
+ {:else if openAlerts.length === 0} +
+ +

No open alerts

+
+ {:else} +
+ {#each openAlerts.slice(0, 5) as alert (alert.id)} +
+ +
+ {alert.message ?? alert.event_type} +

{new Date(alert.created_at).toLocaleString()}

+
+
+ {/each} +
+ {/if} +
+
+
+
+ + {:else if activeTab === 'hardware'} +
+ {#if metricsLoading} +

Loading...

+ {:else if metrics} +
+ Host +
+
+ Hostname + {node.hostname} +
+
+ OS + {node.os_type} {node.os_version} +
+ {#if metrics.kernel_version} +
+ Kernel + {metrics.kernel_version} +
+ {/if} +
+ Architecture + {node.architecture} +
+
+ Uptime + {formatUptime(metrics.uptime_seconds)} +
+
+ Registered + {node.registered_at.slice(0, 16)} +
+
+ Last heartbeat + {node.last_heartbeat ? node.last_heartbeat.slice(0, 16) : 'never'} +
+
+
+
+ CPU +
+
+ Model + {metrics.cpu_model ?? '-'} +
+
+ Cores + {metrics.cpu_cores ?? '-'} +
+
+ Threads + {metrics.cpu_threads ?? '-'} +
+
+ Usage + {pctStr(metrics.cpu_usage_percent)} +
+
+
+
+ Memory +
+
+ Total + {bytesToGb(metrics.memory_total_bytes)} +
+
+ Used + {bytesToGb(metrics.memory_used_bytes)} +
+
+ Usage + {pctStr(memPct())} +
+
+
+
+ Storage +
+
+ Total + {bytesToGb(metrics.disk_total_bytes)} +
+
+ Used + {bytesToGb(metrics.disk_used_bytes)} +
+
+ Usage + {pctStr(diskPct())} +
+
+
+ {:else} +

{metricsError ?? 'No data'}

+ {/if} +
+ + {:else if activeTab === 'storage'} +
+ {#if disksLoading} +
+ {#each [0, 1, 2] as i (i)} +
+
+
+
+
+
+
+
+
+
+ {/each} +
+ {:else if disksError} +

{disksError}

+ {:else if disks.length === 0} +
+ +

No disks detected on this node

+
+ {:else} +
+ {#each disks as disk (disk.device)} + {@const DiskIcon = diskIcon(disk.media_type)} + {@const usagePct = diskUsagePct(disk)} +
+
+
+ +
+
+ {disk.model ?? disk.device} + + {disk.device}{disk.mount_point ? ` · ${disk.mount_point}` : ''} + +
+ {#if disk.smart} + {@const SmartIcon = smartIcon(disk.smart.health)} + + + {disk.smart.health} + + {:else} + + + no smart data + + {/if} +
+ +
+
+
+
+
+ {formatBytes(disk.used_bytes)} used + {bytesToGb(disk.total_bytes)} +
+
+ + {#if disk.smart && (disk.smart.power_on_hours != null || disk.smart.temperature_celsius != null)} +
+ {#if disk.smart.power_on_hours != null} + + + {disk.smart.power_on_hours.toLocaleString()}h + + {/if} + {#if disk.smart.temperature_celsius != null} + + + {disk.smart.temperature_celsius}°C + + {/if} +
+ {/if} +
+ {/each} +
+ {/if} +
+ + {:else if activeTab === 'workloads'} +
+ {#if workloadsLoading} +
+ + + + + + + + + + + + {#each [0, 1, 2] as i (i)} + + + + + + + + {/each} + +
NameImageStatusCPUMemory
+
+ {:else if workloadsError} +

{workloadsError}

+ {:else if workloads.length === 0} +
+ +

No workloads scheduled on this node

+
+ {:else} +
+ + + + + + + + + + + + {#each workloads as workload (workload.id)} + + + + + + + + {/each} + +
NameImageStatusCPUMemory
{workload.name}{workload.image}{workload.cpu_usage_percent != null ? `${workload.cpu_usage_percent.toFixed(0)}%` : '-'}{formatBytes(workload.memory_usage_bytes)}
+
+ {/if} +
+ + {:else if activeTab === 'network'} +
+ {#if metrics} +
+ Interface +
+
+ IP address + {node?.ip_address ?? '-'} +
+
+ RX + {metrics.network_rx_bytes != null ? formatBandwidth(history[history.length - 1]?.rxBps ?? 0) : '-'} +
+
+ TX + {metrics.network_tx_bytes != null ? formatBandwidth(history[history.length - 1]?.txBps ?? 0) : '-'} +
+
+
+ {:else} +

{metricsError ?? 'No data'}

+ {/if} +
+ + {:else if activeTab === 'alerts'} +
+ {#if alertsLoading} +
+ {#each [0, 1, 2, 3] as i (i)} +
+
+
+
+
+
+
+ {/each} +
+ {:else if alertsError} +

{alertsError}

+ {:else if alerts.length === 0} +

No alerts for this node.

+ {:else} +
+ {#each alerts as alert (alert.id)} +
+ +
+
+ {alert.message ?? alert.event_type} + + {alert.status} + +
+

+ {new Date(alert.created_at).toLocaleString()} + {#if alert.resolved_at} +  · resolved {new Date(alert.resolved_at).toLocaleString()} + {/if} +

+
+
+ {/each} +
+ {/if} +
+ + {:else if activeTab === 'tasks'} +
+

No active tasks.

+
+ {/if} +
+ {/if} +
+ + +
+

Start maintenance

+

Hardware alerts for this node are suppressed until maintenance ends.

+
+ + +
+ {#if maintenanceError} +

{maintenanceError}

+ {/if} +
+ + +
+
+
diff --git a/app/src/routes/(app)/resource-groups/+page.svelte b/app/src/routes/(app)/resource-groups/+page.svelte index da5ea67f..e31ebe12 100644 --- a/app/src/routes/(app)/resource-groups/+page.svelte +++ b/app/src/routes/(app)/resource-groups/+page.svelte @@ -12,6 +12,7 @@ import { Button } from "$lib/components/ui/button/index.js"; import Icon from "@iconify/svelte"; import IconPicker from "$lib/components/icon-picker.svelte"; + import RgIcon from "$lib/components/rg-icon.svelte"; let groups = $state([]); let loading = $state(true); @@ -20,6 +21,14 @@ let createDialog = $state(null); let searchText = $state(""); let pinnedScroller = $state(null); + let viewMode = $state<"list" | "grid">( + (typeof localStorage !== "undefined" && localStorage.getItem("rg-view-mode") as "list" | "grid") || "list", + ); + + function setViewMode(mode: "list" | "grid") { + viewMode = mode; + localStorage.setItem("rg-view-mode", mode); + } function scrollPinned(direction: -1 | 1) { pinnedScroller?.scrollBy({ left: direction * 280, behavior: "smooth" }); @@ -213,10 +222,10 @@ onclick={() => goto(`/resource-groups/${group.id}`)} >
- +
@@ -249,11 +258,67 @@
{/if} -
- - +
+
+ + +
+
+ + +
+ {#if viewMode === "grid"} +
+ {#if loading} +

Loading...

+ {:else if filteredGroups.length === 0} +

+ {groups.length === 0 ? "No resource groups. Create one to get started." : "No resource groups match search."} +

+ {:else} + {#each filteredGroups as group (group.id)} + + {/each} + {/if} +
+ {:else}
@@ -285,10 +350,10 @@
- +
{group.name}
@@ -305,4 +370,5 @@
+ {/if}
diff --git a/app/src/routes/(app)/resource-groups/[id]/+page.svelte b/app/src/routes/(app)/resource-groups/[id]/+page.svelte index 2f6d4210..9f3faeda 100644 --- a/app/src/routes/(app)/resource-groups/[id]/+page.svelte +++ b/app/src/routes/(app)/resource-groups/[id]/+page.svelte @@ -1,61 +1,37 @@ - resetDeployForm()} -> -
-
-

Deploy Container

- -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- Ports - -
- {#if formPortRows.length === 0} -

No ports exposed. Container is only reachable via RG-internal DNS on its default port.

- {:else} -
- Container port - - RG port - - Node port -
-
- {#each formPortRows as row, i} - {@const cPort = String(row.containerPort).trim()} - {@const rPort = String(row.rgPort).trim()} - {@const nPort = String(row.nodePort).trim()} -
- - - - - - - -
- {#if cPort} -

- RG mesh: {rPort || cPort}/{row.protocol} → container:{cPort} - {#if nPort} -  Â·  external: node-ip:{nPort} → container:{cPort} - {/if} -

- {/if} - {/each} -
-

- RG port maps to the container port inside the RG mesh (optional, defaults to container port). Node port separately exposes it externally on the node's IP, like a Kubernetes NodePort. -

- {/if} -
-
- {#if volumes.length > 0} -
- - -

Available: {volumes.map(v => v.name).join(", ")}

-
+ + + + + + + + (group = updated)} /> + + +
+ -
- resetVmForm()} -> -
-
-

Deploy Virtual Machine

- -
-
-
- - -
-
-
- ISO Image - {#if vmExistingIsos.length > 0} -
- - -
- {/if} -
- {#if vmExistingIsosLoading} -

Loading isos...

- {:else if vmIsoMode === "existing"} - - {:else} - - {#if vmDeploying && vmFormIsoFile} -
-
-
- {/if} - {/if} -
- {#if vmFormAdvanced} -
- - -
-
- - -
-
- - -
- {:else} -
- - -
-
- - -
-
- - -
- {/if} -
- -
-
-

- The ISO boots on first start. Open the console after deploying to complete the installation. -

- {#if vmError} -

{vmError}

- {/if} -
- - -
-
-
- - -
-
-

Add Resource

- -
-
- {#each RESOURCE_TYPES as type} - - {/each} -
-
-
- - resetComposeForm()} -> -
-
-

{editingStackId ? "Edit Compose Stack" : "Deploy Docker Compose Stack"}

- -
-
- - -
-
-
- -
- {#if composePreview.services.length === 0 && composePreview.volumes.length === 0} -

No services detected yet.

- {:else} - {#each composePreview.services as service (service.serviceName)} -
- -
-

{service.serviceName}

-

{service.image ?? "no image"}

- {#if service.ports.length > 0} -

{service.ports.join(", ")}

- {/if} -
-
- {/each} - {#if composePreview.volumes.length > 0} -

Volumes

- {#each composePreview.volumes as volumeName (volumeName)} -
- -

{volumeName}

-
- {/each} - {/if} - {/if} -
-
-
-
- - -
-
-
- {#each Array(composeLineCount) as _, i} -
{i + 1}
- {/each} -
-
{@html composeHighlighted}
- -
-
-
- {#if composeError} -

{composeError}

- {/if} -
- - -
-
-
- - { appearanceError = null; }} -> -
-
-

Edit Appearance

- -
- - {#if appearanceError} -

{appearanceError}

- {/if} -
- - -
-
-
- - { rgSettingsError = null; }} -> -
-
-

Resource Group Settings

- -
-
-
- - -
-
- - -
-
- {#if rgSettingsError} -

{rgSettingsError}

- {/if} -
- - -
-
-
- - { volumeError = null; }} -> -
-
-

Create Volume

- -
-
-
- - -
-
- - -
-
- {#if volumeError} -

{volumeError}

- {/if} -
- - -
-
-
- - { bucketError = null; }} -> -
-
-

Create Bucket

- -
-
-
- - -
-
- - -
-
- {#if bucketError} -

{bucketError}

- {/if} -
- - -
-
-
- - { activeBucket = null; createdKeySecret = null; }} -> - {#if activeBucket} -
-
-
-

{activeBucket.name}

-

{bucketEndpointUrl(activeBucket)}

-
+
+
+ -
-

Access Keys

- {#if bucketKeys.length === 0} -

no access keys yet

- {:else} -
- {#each bucketKeys as key (key.id)} -
-
-

{key.name}

-

{key.garage_key_id}

-
- {key.permissions} -
- {/each} -
- {/if} -
- - {#if createdKeySecret} -
-

Secret access key created

-

{createdKeySecret}

-

this will not be shown again, copy it now

- -
- {/if} - - {#if bucketKeysError} -

{bucketKeysError}

- {/if} - -
- - -
-
- {/if} -
- - closeContainer()} -> - {#if activeContainer} -
-
-
- -
-

{activeContainer.service_name ?? activeContainer.name}

-

{activeContainer.image}

-
- -
-
- - - -
- -
-
- {#if containerActionError} -

{containerActionError}

- {/if} -
-
- {#each [...(activeContainer.runtime_class === "vm" ? [] : [["logs", "Logs"]]), ["shell", activeContainer.runtime_class === "vm" ? "Console" : "Shell"], ["insights", "Performance"], ["network", "Network"], ...(activeContainer.stack_id ? [] : [["settings", "Settings"]])] as [tab, label]} - - {/each} -
-
-
- {#if containerDialogTab === "logs"} -
- {#if logsError} -

{logsError}

- {/if} - {#each logsLines as line} -

{line}

- {/each} -
- {:else if containerDialogTab === "shell" && activeContainer.runtime_class === "vm"} - {#if activeContainer.status !== "running" || !auth.token} -

Console is only available while the vm is running.

- {:else} - {#key activeContainer.id} - - {/key} - {/if} - {:else if containerDialogTab === "shell"} -
- {#if activeContainer.status !== "running"} -

Shell is only available while the container is running.

- {:else} - {#if execError} -

{execError}

- {/if} -
- {/if} -
- {:else if containerDialogTab === "insights"} -
-
-
-

CPU Usage

-

- {activeContainer.cpu_usage_percent !== null ? `${activeContainer.cpu_usage_percent.toFixed(1)}%` : "-"} -

-

{activeContainer.cpu_millicores}m requested

-
-
-

Memory Usage

-

- {activeContainer.memory_usage_bytes !== null ? fmtBytes(activeContainer.memory_usage_bytes) : "-"} -

-

{fmtBytes(activeContainer.memory_bytes)} requested

-
-
-

Network RX

-

- {activeContainer.network_rx_bytes !== null ? fmtBytes(activeContainer.network_rx_bytes) : "-"} -

-
-
-

Network TX

-

- {activeContainer.network_tx_bytes !== null ? fmtBytes(activeContainer.network_tx_bytes) : "-"} -

-
-
-

- {activeContainer.stats_updated_at ? `Last updated ${new Date(activeContainer.stats_updated_at).toLocaleTimeString()}` : "No stats reported yet."} -

- {#if activeContainer.restart_count > 0} -

- Restarted {activeContainer.restart_count} time{activeContainer.restart_count === 1 ? "" : "s"}{activeContainer.max_restarts !== null ? ` (max ${activeContainer.max_restarts})` : ""} -

- {/if} -
- {:else if containerDialogTab === "network"} -
- {#if activeContainer.ports && activeContainer.ports.length > 0} -
-

Ports

-
- {#each activeContainer.ports as port} - {@const rgHost = `${activeContainer.service_name ?? activeContainer.name}.svc.${rgId}.internal`} - {@const rgAddress = `${rgHost}:${port.container_port}`} - {@const rgPortAddress = port.rg_port ? `rg-gateway:${port.rg_port}` : null} - {@const nodeIp = nodeIpCache[activeContainer.assigned_agent_id ?? ""]} - {@const extAddress = port.node_port ? `${nodeIp ?? "node-ip"}:${port.node_port}` : null} -
-
-
-

RG-internal (DNS)

-

{rgAddress}

-
- -
- {#if rgPortAddress} -
-
-

RG port (mesh gateway)

-

{rgPortAddress}

-
- -
- {/if} - {#if extAddress} -
-
-

External

-

{extAddress}

-
- -
- {/if} -
- {/each} -
-
-

- Reachable over VPN via the RG-internal address, or externally via the node IP if a node port is set. Use "Connect VPN" to resolve RG-internal hostnames. -

- {:else} -

No ports configured for this container.

- {/if} -
- {:else if !activeContainer.stack_id} -
-
- - -
-
- - -

One KEY=VALUE per line.

-
-
-
- - -
-
- - -
-
-

- Redeploying applies the new configuration by recreating the container, pulling the image again if changed. -

- {#if settingsError} -

{settingsError}

- {/if} -
- -
-
- {/if} -
-
- {/if} -
- -
- - / - - / - {group?.name ?? rgId.slice(0, 8)} -
- -
+
{#if loading}

Loading...

{:else if error && !group}

{error}

{:else if group} -
- -
-
-

{group.name}

- -
- {#if group.description} -

{group.description}

- {/if} -

{group.internal_cidr}

-
+
- - - - -
-
-
-

Containers

-

{workloads.length}

-

{workloads.filter(w => w.status === 'running').length} running

-
-
-

Volumes

-

{volumes.length}

-

{totalDisk} GB total

-
-
-

Buckets

-

{buckets.length}

-

{buckets.filter(b => b.exposure === 'external').length} external

-
-
-

CPU Requested

-

{(totalCpu / 1000).toFixed(1)}

-

vCPU

-
-
-

Memory Requested

-

{fmtBytes(totalMem)}

-

across containers

-
-
- {#if error}

{error}

{/if} -
-
-
- {#each [["all", `All ${allResources.length}`], ["container", `Container ${workloads.filter((w) => w.runtime_class !== "vm").length}`], ["vm", `VM ${workloads.filter((w) => w.runtime_class === "vm").length}`], ["volume", `Volume ${volumes.length}`], ["bucket", `Bucket ${buckets.length}`]] as [tab, label]} - - {/each} -
-
- - -
-
- - - - - - - - - - - - - - {#if filteredResources.length === 0} - - - - {:else} - {#snippet workloadRow(w: Workload, indented: boolean)} - w.runtime_class === "vm" ? goto(`/resource-groups/${rgId}/vm/${w.id}`) : openContainer(w)} - > - - - - - - - - {/snippet} - {#each filteredResources as item (item.kind + (item.kind === "stack" ? item.data.stack_id : item.data.id))} - {#if item.kind === "container" || item.kind === "vm"} - {@render workloadRow(item.data, false)} - {:else if item.kind === "stack"} - {@const stack = item.data} - {@const expanded = expandedStacks.has(stack.stack_id)} - openStackEditor(stack.stack_id)} - > - - - - - - - - {#if expanded} - {#each stack.children as child (child.id)} - {@render workloadRow(child, true)} - {/each} - {/if} - {:else if item.kind === "volume"} - {@const v = item.data} - - - - - - - - - {:else if item.kind === "bucket"} - {@const b = item.data} - openBucketDetail(b)} - > - - - - - - - - {/if} - {/each} - {/if} - -
ResourceKindHost / SizeLoadStatus
- {allResources.length === 0 ? "No resources yet. Deploy a container or create a volume." : "No resources match filter."} -
-
-
- -
-

{w.service_name ?? w.name}

-
-
- {w.runtime_class === "vm" ? "VM" : "Container"} - - {w.assigned_agent_id ? w.assigned_agent_id.slice(0, 8) : "-"} - -
- {w.cpu_millicores}m -
-
-
-
-
-
- - {#if w.restart_count > 0} - - ↻ {w.restart_count} - - {/if} -
-
-
- - - -
-
-
- -
- -
-
-

{stack.stack_name}

-

{stack.children.length} services

-
-
-
- Compose Stack - - {stack.children[0]?.assigned_agent_id ? stack.children[0].assigned_agent_id!.slice(0, 8) : "-"} - - - - -
- - - -
-
-
-
- -
-
-

{v.name}

-

{v.size_gb} GB · {v.pool}

-
-
-
- Volume - - {v.size_gb} GB - -
- {v.size_gb}G -
-
-
-
-
- - - -
-
- -
-

{b.name}

-

{b.global_alias}

-
-
-
- Bucket - - {b.quota_max_size ? fmtBytes(b.quota_max_size) : "unlimited"} - - {b.exposure} - - - -
- - -
-
-
+ composeDialog?.open(stackId)} + onOpenBucket={(bucket) => bucketDetailDialog?.open(bucket)} + onChanged={load} + onError={(message) => (error = message)} + /> {/if} +
+
diff --git a/app/src/routes/(app)/resource-groups/[id]/vm/[workloadId]/+page.svelte b/app/src/routes/(app)/resource-groups/[id]/vm/[workloadId]/+page.svelte index bb5d208c..da9cbe8d 100644 --- a/app/src/routes/(app)/resource-groups/[id]/vm/[workloadId]/+page.svelte +++ b/app/src/routes/(app)/resource-groups/[id]/vm/[workloadId]/+page.svelte @@ -11,10 +11,35 @@ type Workload, } from "$lib/api/resource-groups"; import { getNode } from "$lib/api/nodes"; - import VncConsole from "$lib/components/vnc-console.svelte"; - import Icon from "@iconify/svelte"; + import { fmtBytes } from "$lib/utils/format"; import { Button } from "$lib/components/ui/button/index.js"; - import StatusBadge from "$lib/components/status-badge.svelte"; + import VncConsole from "$lib/components/vnc-console.svelte"; + import VmConsolePreview from "$lib/components/vm/vm-console-preview.svelte"; + import VmDetailsTab from "$lib/components/vm/vm-details-tab.svelte"; + import VmSummaryTab from "$lib/components/vm/vm-summary-tab.svelte"; + import ArrowLeftIcon from "@lucide/svelte/icons/arrow-left"; + import CpuIcon from "@lucide/svelte/icons/cpu"; + import GlobeIcon from "@lucide/svelte/icons/globe"; + import MemoryStickIcon from "@lucide/svelte/icons/memory-stick"; + import RotateCwIcon from "@lucide/svelte/icons/rotate-cw"; + import SquareIcon from "@lucide/svelte/icons/square"; + import Trash2Icon from "@lucide/svelte/icons/trash-2"; + + type Tab = "summary" | "console" | "details"; + + const POLL_INTERVAL_MS = 5000; + const TABS: { id: Tab; label: string }[] = [ + { id: "summary", label: "Summary" }, + { id: "console", label: "Console" }, + { id: "details", label: "Details" }, + ]; + const STATUS_DOT: Record = { + running: "bg-green-500", + failed: "bg-red-500", + error: "bg-red-500", + pending: "bg-yellow-500", + starting: "bg-yellow-500", + }; const rgId: string = $page.params.id; const workloadId: string = $page.params.workloadId; @@ -25,21 +50,25 @@ let actionError = $state(null); let actionBusy = $state(false); let nodeIp = $state(null); + let activeTab = $state("summary"); + + const statusDot = $derived(STATUS_DOT[workload?.status.toLowerCase() ?? ""] ?? "bg-muted-foreground"); + + async function loadNodeIp(agentId: string | null) { + if (!agentId || !auth.token) return; + try { + nodeIp = (await getNode(auth.token, agentId)).ip_address; + } catch { + nodeIp = null; + } + } async function load() { if (!auth.token) return; try { const workloads = await listResourceGroupWorkloads(auth.token, rgId); - const found = workloads.find((w) => w.id === workloadId) ?? null; - workload = found; - if (found?.assigned_agent_id) { - try { - const node = await getNode(auth.token, found.assigned_agent_id); - nodeIp = node.ip_address; - } catch { - nodeIp = null; - } - } + workload = workloads.find((w) => w.id === workloadId) ?? null; + await loadNodeIp(workload?.assigned_agent_id ?? null); } catch (e) { error = e instanceof Error ? e.message : "Failed to load vm"; } finally { @@ -47,35 +76,15 @@ } } - function fmtBytes(bytes: number): string { - if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`; - if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(0)} MB`; - return `${bytes} B`; - } - - async function handleStop() { - if (!auth.token || !workload) return; - actionBusy = true; - actionError = null; - try { - await stopWorkload(auth.token, workload.id); - await load(); - } catch (e) { - actionError = e instanceof Error ? e.message : "Failed to stop vm"; - } finally { - actionBusy = false; - } - } - - async function handleRestart() { + async function runAction(action: (token: string, id: string) => Promise, failure: string) { if (!auth.token || !workload) return; actionBusy = true; actionError = null; try { - await restartWorkload(auth.token, workload.id); + await action(auth.token, workload.id); await load(); } catch (e) { - actionError = e instanceof Error ? e.message : "Failed to restart vm"; + actionError = e instanceof Error ? e.message : failure; } finally { actionBusy = false; } @@ -106,9 +115,7 @@ let pollInterval: ReturnType | null = null; $effect(() => { - if (workload && !pollInterval) { - pollInterval = setInterval(load, 5000); - } + if (workload && !pollInterval) pollInterval = setInterval(load, POLL_INTERVAL_MS); }); onDestroy(() => { @@ -116,140 +123,94 @@ }); -
- - / - - / - - / - {workload?.service_name ?? workload?.name ?? workloadId.slice(0, 8)} +
+ + {#if workload} + (activeTab = "console")} /> +
+
+ {workload.service_name ?? workload.name} + + {#if workload.restart_count > 0} + + ↻ {workload.restart_count} + + {/if} +
+
+ + + {nodeIp ?? "no node"} + + + + {workload.cpu_millicores / 1000} vCPU + + + + {fmtBytes(workload.memory_bytes)} + +
+
+
+ + + +
+ {/if}
-
+{#if actionError} +

{actionError}

+{/if} + +
{#if loading} -

Loading...

+

Loading...

{:else if error} -

{error}

+

{error}

{:else if !workload} -

VM not found.

+

VM not found.

{:else} -
-
-
- -
-
-
-

{workload.service_name ?? workload.name}

- - {#if workload.restart_count > 0} - - ↻ {workload.restart_count} - - {/if} -
-

- {workload.cpu_millicores}m vCPU · {fmtBytes(workload.memory_bytes)} RAM · {fmtBytes(workload.disk_bytes)} disk - {#if workload.assigned_agent_id} - · node {nodeIp ?? workload.assigned_agent_id.slice(0, 8)} - {/if} -

-
-
-
- - - +
+
+ {#each TABS as tab (tab.id)} + + {/each}
- {#if actionError} -

{actionError}

- {/if} - -
-
-

CPU Usage

-

- {workload.cpu_usage_percent !== null ? `${workload.cpu_usage_percent.toFixed(1)}%` : "-"} -

-

{workload.cpu_millicores}m requested

-
-
-

Memory Usage

-

- {workload.memory_usage_bytes !== null ? fmtBytes(workload.memory_usage_bytes) : "-"} -

-

{fmtBytes(workload.memory_bytes)} requested

-
-
-

Network RX

-

- {workload.network_rx_bytes !== null ? fmtBytes(workload.network_rx_bytes) : "-"} -

-
-
-

Network TX

-

- {workload.network_tx_bytes !== null ? fmtBytes(workload.network_tx_bytes) : "-"} -

-
-
-

- {workload.stats_updated_at ? `Last updated ${new Date(workload.stats_updated_at).toLocaleTimeString()}` : "No stats reported yet."} -

- -
-
- -

Console

-
-
- {#if workload.status !== "running" || !auth.token} -
-

Console is only available while the vm is running.

-
- {:else} - {#key workload.id} - - {/key} - {/if} + {#if activeTab === "summary"} + + {:else if activeTab === "console"} +
+
+ {#if workload.status !== "running" || !auth.token} +
+

Console is only available while the vm is running.

+
+ {:else} + {#key workload.id} + + {/key} + {/if} +
-
+ {:else} + + {/if} {/if}
diff --git a/app/src/routes/(auth)/+layout.svelte b/app/src/routes/(auth)/+layout.svelte index 2c56f278..c8e52242 100644 --- a/app/src/routes/(auth)/+layout.svelte +++ b/app/src/routes/(auth)/+layout.svelte @@ -1,15 +1,28 @@ -
-
+
+ + + +
{@render children()}
-
diff --git a/app/src/routes/(auth)/login/+page.svelte b/app/src/routes/(auth)/login/+page.svelte index 0e769e58..0d93d9be 100644 --- a/app/src/routes/(auth)/login/+page.svelte +++ b/app/src/routes/(auth)/login/+page.svelte @@ -5,6 +5,7 @@ import { Lock, User, CircleCheck } from "@lucide/svelte"; import { goto } from "$app/navigation"; import { login, TwoFactorRequiredError } from "$lib/auth/api"; + import { pendingLogin } from "$lib/auth/pending-login"; import { auth } from "$lib/auth/store.svelte"; import Spinner from "$lib/components/ui/spinner/spinner.svelte"; import { toast } from "svelte-sonner"; @@ -26,9 +27,8 @@ } catch (err) { status = "idle"; if (err instanceof TwoFactorRequiredError) { - goto( - `/otp?username=${encodeURIComponent(err.username)}&password=${encodeURIComponent(err.password)}`, - ); + pendingLogin.set(err.username, err.password); + goto("/otp"); } else { toast.error("Invalid credentials", { description: diff --git a/app/src/routes/(auth)/otp/+page.svelte b/app/src/routes/(auth)/otp/+page.svelte index 98fcaab1..7da3e0d2 100644 --- a/app/src/routes/(auth)/otp/+page.svelte +++ b/app/src/routes/(auth)/otp/+page.svelte @@ -3,26 +3,32 @@ import { Label } from "$lib/components/ui/label/index.js"; import { CircleCheck } from "@lucide/svelte"; import { goto } from "$app/navigation"; - import { page } from "$app/stores"; + import { onDestroy, onMount } from "svelte"; import * as InputOTP from "$lib/components/ui/input-otp/index.js"; import { login } from "$lib/auth/api"; + import { pendingLogin } from "$lib/auth/pending-login"; import { auth } from "$lib/auth/store.svelte"; import Spinner from "$lib/components/ui/spinner/spinner.svelte"; import { toast } from "svelte-sonner"; type Status = "idle" | "loading" | "success"; - const username = $derived($page.url.searchParams.get("username") ?? ""); - const password = $derived($page.url.searchParams.get("password") ?? ""); - let otpValue = $state(""); let status = $state("idle"); + onMount(() => { + if (!pendingLogin.get()) goto("/login"); + }); + + onDestroy(() => pendingLogin.clear()); + async function handleVerify() { - if (otpValue.length !== 6) return; + const credentials = pendingLogin.get(); + if (otpValue.length !== 6 || !credentials) return; status = "loading"; try { - const response = await login(username, password, otpValue); + const response = await login(credentials.username, credentials.password, otpValue); + pendingLogin.clear(); auth.setSession(response); status = "success"; const target = response.force_password_change ? "/pw_change" : "/"; diff --git a/app/src/routes/layout.css b/app/src/routes/layout.css index 704e1533..f54c4af1 100644 --- a/app/src/routes/layout.css +++ b/app/src/routes/layout.css @@ -85,6 +85,13 @@ @theme inline { --font-sans: "Geist", sans-serif; + --text-label: 0.75rem; + --text-label--line-height: 1rem; + --text-label--letter-spacing: 0.02em; + --text-label--font-weight: 500; + --text-stat: 1.875rem; + --text-stat--line-height: 2.25rem; + --text-stat--font-weight: 600; --color-panel-foreground: var(--panel-foreground); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); diff --git a/app/static/login/login_dark.avif b/app/static/login/login_dark.avif new file mode 100644 index 00000000..4aca2c91 Binary files /dev/null and b/app/static/login/login_dark.avif differ diff --git a/app/static/login/login_light.avif b/app/static/login/login_light.avif new file mode 100644 index 00000000..eded4fdc Binary files /dev/null and b/app/static/login/login_light.avif differ diff --git a/app/static/node_icon/csfx_node_server.avif b/app/static/node_icon/csfx_node_server.avif new file mode 100644 index 00000000..d0c42f35 Binary files /dev/null and b/app/static/node_icon/csfx_node_server.avif differ diff --git a/app/static/server/server_rack_3x.png b/app/static/server/server_rack_3x.png new file mode 100644 index 00000000..9ae9139b Binary files /dev/null and b/app/static/server/server_rack_3x.png differ diff --git a/app/vite.config.ts b/app/vite.config.ts index 6042d847..fe3395f6 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -1,23 +1,52 @@ +/// import tailwindcss from '@tailwindcss/vite'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +import { playwright } from '@vitest/browser-playwright'; +const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url)); +// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon export default defineConfig({ - plugins: [tailwindcss(), sveltekit()], - optimizeDeps: { - include: ['@xterm/xterm', '@xterm/addon-fit'], - }, - server: { - watch: { - usePolling: true, - interval: 1000, - }, - proxy: { - '/api': { - target: process.env.VITE_API_URL || 'https://localhost:8000', - changeOrigin: true, - secure: false, - }, - }, - }, -}); + plugins: [tailwindcss(), sveltekit()], + optimizeDeps: { + include: ['@xterm/xterm', '@xterm/addon-fit'] + }, + server: { + watch: { + usePolling: true, + interval: 1000 + }, + proxy: { + '/api': { + target: process.env.VITE_API_URL || 'https://localhost:8000', + changeOrigin: true, + secure: false + } + } + }, + test: { + projects: [{ + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, '.storybook') + })], + test: { + name: 'storybook', + browser: { + enabled: true, + headless: true, + provider: playwright({}), + instances: [{ + browser: 'chromium' + }] + } + } + }] + } +}); \ No newline at end of file diff --git a/app/vitest.shims.d.ts b/app/vitest.shims.d.ts new file mode 100644 index 00000000..7782f28d --- /dev/null +++ b/app/vitest.shims.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/control-plane/api-gateway/src/auth/rbac.rs b/control-plane/api-gateway/src/auth/rbac.rs index e61373f0..afce9255 100644 --- a/control-plane/api-gateway/src/auth/rbac.rs +++ b/control-plane/api-gateway/src/auth/rbac.rs @@ -26,6 +26,18 @@ pub struct CanViewLogs(pub Claims); pub struct CanManageLogs(pub Claims); pub struct CanViewBuckets(pub Claims); pub struct CanManageBuckets(pub Claims); +pub struct AuthenticatedUser(pub Claims); + +impl FromRequestParts for AuthenticatedUser { + type Rejection = StatusCode; + + async fn from_request_parts( + parts: &mut Parts, + state: &AppState, + ) -> Result { + extract_claims(parts, state).await.map(Self) + } +} async fn extract_claims(parts: &mut Parts, state: &AppState) -> Result { let token = parts diff --git a/control-plane/api-gateway/src/routes/agent_proxy.rs b/control-plane/api-gateway/src/routes/agent_proxy.rs index 62331c15..7f00fe48 100644 --- a/control-plane/api-gateway/src/routes/agent_proxy.rs +++ b/control-plane/api-gateway/src/routes/agent_proxy.rs @@ -562,6 +562,56 @@ pub async fn power_agent( Ok(StatusCode::NO_CONTENT) } +const DISKS_TICKET_SCOPE: &str = "__disks__"; + +pub async fn get_agent_disks( + CanViewAgents(_claims): CanViewAgents, + State(state): State, + Path(agent_id): Path, +) -> Result)> { + let tunnel_ip = resolve_agent_tunnel_ip(&state, agent_id).await?; + let proxy_ticket = fetch_proxy_ticket(&state, agent_id, DISKS_TICKET_SCOPE).await?; + + let agent_port: u16 = std::env::var(CSFX_AGENT_PORT_ENV) + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(7443); + + let url = format!("http://{}:{}/disks", tunnel_ip, agent_port); + + let client = reqwest::Client::new(); + let resp = client + .get(&url) + .header("X-Csfx-Proxy-Ticket", proxy_ticket) + .send() + .await + .map_err(|e| { + ( + StatusCode::BAD_GATEWAY, + Json(json!({ "error": format!("failed to reach agent: {}", e) })), + ) + })?; + + if !resp.status().is_success() { + let agent_status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + tracing::warn!(agent_id = %agent_id, agent_status = %agent_status, body = %body, "agent refused disks request"); + return Err(( + StatusCode::BAD_GATEWAY, + Json(json!({ "error": "agent refused disks request", "detail": body })), + )); + } + + let body: serde_json::Value = resp.json().await.map_err(|e| { + ( + StatusCode::BAD_GATEWAY, + Json(json!({ "error": format!("invalid agent response: {}", e) })), + ) + })?; + + Ok(Json(body)) +} + pub fn agent_proxy_routes() -> Router { Router::new() .route("/workloads/{id}/logs", get(stream_workload_logs)) @@ -581,4 +631,5 @@ pub fn agent_proxy_routes() -> Router { ) .route("/agents/{id}/metrics/stream", get(stream_node_metrics)) .route("/agents/{id}/power", axum::routing::post(power_agent)) + .route("/agents/{id}/disks", get(get_agent_disks)) } diff --git a/control-plane/api-gateway/src/routes/agents.rs b/control-plane/api-gateway/src/routes/agents.rs index eabe6093..4367858d 100644 --- a/control-plane/api-gateway/src/routes/agents.rs +++ b/control-plane/api-gateway/src/routes/agents.rs @@ -63,6 +63,7 @@ pub struct AgentResponse { pub last_heartbeat: Option, pub registered_at: String, pub cordoned: bool, + pub maintenance_until: Option, } impl From for AgentResponse { @@ -80,6 +81,7 @@ impl From for AgentResponse { last_heartbeat: model.last_heartbeat.map(|dt| dt.to_string()), registered_at: model.registered_at.to_string(), cordoned: model.cordoned, + maintenance_until: model.maintenance_until.map(|dt| dt.and_utc().to_rfc3339()), } } } diff --git a/control-plane/api-gateway/src/routes/events.rs b/control-plane/api-gateway/src/routes/events.rs index c6cc8d7a..514a2b7e 100644 --- a/control-plane/api-gateway/src/routes/events.rs +++ b/control-plane/api-gateway/src/routes/events.rs @@ -1,12 +1,13 @@ use axum::{ body::Body, - extract::State, + extract::{Path, RawQuery, State}, http::{HeaderMap, StatusCode}, response::{IntoResponse, Json}, - routing::get, + routing::{delete, get, post}, Router, }; use serde_json::json; +use uuid::Uuid; use crate::{auth::middleware::AuthenticatedUser, AppState}; @@ -48,6 +49,45 @@ pub async fn list_events( AuthenticatedUser(_claims): AuthenticatedUser, State(state): State, headers: HeaderMap, + RawQuery(query): RawQuery, +) -> Result)> { + let header_map = headers + .iter() + .filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string()))) + .collect(); + let path = match query { + Some(q) => format!("/events?{}", q), + None => "/events".to_string(), + }; + proxy_to_failover(&state, reqwest::Method::GET, &path, None, Some(header_map)).await +} + +pub async fn set_maintenance( + AuthenticatedUser(_claims): AuthenticatedUser, + State(state): State, + Path(agent_id): Path, + headers: HeaderMap, + Json(body): Json, +) -> Result)> { + let header_map = headers + .iter() + .filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), val.to_string()))) + .collect(); + proxy_to_failover( + &state, + reqwest::Method::POST, + &format!("/agents/{}/maintenance", agent_id), + Some(body), + Some(header_map), + ) + .await +} + +pub async fn clear_maintenance( + AuthenticatedUser(_claims): AuthenticatedUser, + State(state): State, + Path(agent_id): Path, + headers: HeaderMap, ) -> Result)> { let header_map = headers .iter() @@ -55,8 +95,8 @@ pub async fn list_events( .collect(); proxy_to_failover( &state, - reqwest::Method::GET, - "/events", + reqwest::Method::DELETE, + &format!("/agents/{}/maintenance", agent_id), None, Some(header_map), ) @@ -64,5 +104,8 @@ pub async fn list_events( } pub fn events_routes() -> Router { - Router::new().route("/events", get(list_events)) + Router::new() + .route("/events", get(list_events)) + .route("/agents/{agent_id}/maintenance", post(set_maintenance)) + .route("/agents/{agent_id}/maintenance", delete(clear_maintenance)) } diff --git a/control-plane/api-gateway/src/routes/mod.rs b/control-plane/api-gateway/src/routes/mod.rs index bd123b0d..6ab7bdbf 100644 --- a/control-plane/api-gateway/src/routes/mod.rs +++ b/control-plane/api-gateway/src/routes/mod.rs @@ -9,6 +9,7 @@ use axum::http::{HeaderValue, Request, Response}; use axum::routing::get; use axum::Router; use std::sync::Arc; +use std::time::Duration; use tower_governor::{ governor::GovernorConfigBuilder, key_extractor::PeerIpKeyExtractor, GovernorLayer, }; @@ -37,6 +38,10 @@ pub mod users; pub mod volumes; pub mod workloads; +fn replenish_period(requests_per_second: u64) -> Duration { + Duration::from_secs_f64(1.0 / requests_per_second.max(1) as f64) +} + /// Creates the main application router and logs all registered routes. pub fn create_router() -> Router { let rate_limit_per_second: u64 = std::env::var("RATE_LIMIT_PER_SECOND") @@ -52,7 +57,7 @@ pub fn create_router() -> Router { let governor_config = Arc::new( GovernorConfigBuilder::default() .key_extractor(JwtOrIpKeyExtractor::new()) - .per_second(rate_limit_per_second) + .period(replenish_period(rate_limit_per_second)) .burst_size(burst_size) .finish() .expect("invalid rate limit configuration"), @@ -71,7 +76,7 @@ pub fn create_router() -> Router { let login_governor_config = Arc::new( GovernorConfigBuilder::default() .key_extractor(PeerIpKeyExtractor) - .per_second(login_rate_limit_per_second) + .period(replenish_period(login_rate_limit_per_second)) .burst_size(login_burst_size) .finish() .expect("invalid login rate limit configuration"), diff --git a/control-plane/api-gateway/src/routes/resource_groups.rs b/control-plane/api-gateway/src/routes/resource_groups.rs index e85e8db7..540793b0 100644 --- a/control-plane/api-gateway/src/routes/resource_groups.rs +++ b/control-plane/api-gateway/src/routes/resource_groups.rs @@ -1,6 +1,7 @@ use axum::{ + body::Bytes, extract::{Path, State}, - http::{header, StatusCode}, + http::{header, HeaderMap, StatusCode}, response::{IntoResponse, Json, Response}, routing::get, Router, @@ -29,6 +30,8 @@ use crate::{ const DEFAULT_ICON: &str = "mdi:cube-outline"; const DEFAULT_COLOR: &str = "#6366f1"; +const MAX_ICON_IMAGE_BYTES: usize = 512 * 1024; +const ALLOWED_ICON_IMAGE_TYPES: [&str; 3] = ["image/png", "image/jpeg", "image/svg+xml"]; #[derive(Debug, Deserialize)] pub struct CreateResourceGroupRequest { @@ -46,6 +49,7 @@ pub struct UpdateResourceGroupRequest { pub icon: Option, pub color: Option, pub pinned: Option, + pub preview_url: Option, } #[derive(Debug, Serialize)] @@ -59,6 +63,8 @@ pub struct ResourceGroupResponse { pub icon: String, pub color: String, pub pinned: bool, + pub has_icon_image: bool, + pub preview_url: Option, pub created_at: chrono::NaiveDateTime, pub updated_at: Option, } @@ -75,12 +81,30 @@ impl From for ResourceGroupResponse { icon: m.icon, color: m.color, pinned: m.pinned, + has_icon_image: m.icon_image.is_some(), + preview_url: m.preview_url, created_at: m.created_at, updated_at: m.updated_at, } } } +fn normalize_preview_url( + raw: String, +) -> Result, (StatusCode, Json)> { + let trimmed = raw.trim(); + if trimmed.is_empty() { + return Ok(None); + } + if trimmed.starts_with("http://") || trimmed.starts_with("https://") { + return Ok(Some(trimmed.to_string())); + } + Err(( + StatusCode::BAD_REQUEST, + Json(json!({ "error": "preview_url must start with http:// or https://" })), + )) +} + fn get_org_id(state: &AppState) -> Uuid { state .default_org_id @@ -172,6 +196,9 @@ pub async fn create_resource_group( icon: Set(req.icon.unwrap_or_else(|| DEFAULT_ICON.to_string())), color: Set(req.color.unwrap_or_else(|| DEFAULT_COLOR.to_string())), pinned: Set(false), + icon_image: Set(None), + icon_image_mime: Set(None), + preview_url: Set(None), created_at: Set(now), updated_at: Set(None), }; @@ -312,6 +339,8 @@ pub async fn update_resource_group( } if let Some(icon) = req.icon { active.icon = Set(icon); + active.icon_image = Set(None); + active.icon_image_mime = Set(None); } if let Some(color) = req.color { active.color = Set(color); @@ -319,6 +348,9 @@ pub async fn update_resource_group( if let Some(pinned) = req.pinned { active.pinned = Set(pinned); } + if let Some(preview_url) = req.preview_url { + active.preview_url = Set(normalize_preview_url(preview_url)?); + } active.updated_at = Set(Some(Utc::now().naive_utc())); let updated = active.update(&state.db_conn).await.map_err(|e| { @@ -335,6 +367,152 @@ pub async fn update_resource_group( )) } +pub async fn upload_resource_group_icon_image( + CanManageResourceGroups(_claims): CanManageResourceGroups, + State(state): State, + Path(id): Path, + headers: HeaderMap, + body: Bytes, +) -> Result)> { + let org_id = get_org_id(&state); + + let content_type = headers + .get(header::CONTENT_TYPE) + .and_then(|v| v.to_str().ok()) + .unwrap_or(""); + + if !ALLOWED_ICON_IMAGE_TYPES.contains(&content_type) { + return Err(( + StatusCode::UNSUPPORTED_MEDIA_TYPE, + Json(json!({ "error": "icon image must be png, jpeg or svg" })), + )); + } + + if body.len() > MAX_ICON_IMAGE_BYTES { + return Err(( + StatusCode::PAYLOAD_TOO_LARGE, + Json( + json!({ "error": format!("icon image must be under {} bytes", MAX_ICON_IMAGE_BYTES) }), + ), + )); + } + + let group = ResourceGroups::find_by_id(id) + .filter(resource_groups::Column::OrganizationId.eq(org_id)) + .one(&state.db_conn) + .await + .map_err(|e| { + tracing::error!(error = %e, "failed to find resource group"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })? + .ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + Json(json!({ "error": "resource group not found" })), + ) + })?; + + let mut active: resource_groups::ActiveModel = group.into(); + active.icon_image = Set(Some(body.to_vec())); + active.icon_image_mime = Set(Some(content_type.to_string())); + active.updated_at = Set(Some(Utc::now().naive_utc())); + + let updated = active.update(&state.db_conn).await.map_err(|e| { + tracing::error!(error = %e, "failed to store resource group icon image"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + + Ok(( + StatusCode::OK, + Json(json!(ResourceGroupResponse::from(updated))), + )) +} + +pub async fn get_resource_group_icon_image( + CanViewResourceGroups(_claims): CanViewResourceGroups, + State(state): State, + Path(id): Path, +) -> Result)> { + let org_id = get_org_id(&state); + + let group = ResourceGroups::find_by_id(id) + .filter(resource_groups::Column::OrganizationId.eq(org_id)) + .one(&state.db_conn) + .await + .map_err(|e| { + tracing::error!(error = %e, "failed to find resource group"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })? + .ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + Json(json!({ "error": "resource group not found" })), + ) + })?; + + let (Some(image), Some(mime)) = (group.icon_image, group.icon_image_mime) else { + return Err(( + StatusCode::NOT_FOUND, + Json(json!({ "error": "resource group has no icon image" })), + )); + }; + + Ok((StatusCode::OK, [(header::CONTENT_TYPE, mime)], image).into_response()) +} + +pub async fn delete_resource_group_icon_image( + CanManageResourceGroups(_claims): CanManageResourceGroups, + State(state): State, + Path(id): Path, +) -> Result)> { + let org_id = get_org_id(&state); + + let group = ResourceGroups::find_by_id(id) + .filter(resource_groups::Column::OrganizationId.eq(org_id)) + .one(&state.db_conn) + .await + .map_err(|e| { + tracing::error!(error = %e, "failed to find resource group"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })? + .ok_or_else(|| { + ( + StatusCode::NOT_FOUND, + Json(json!({ "error": "resource group not found" })), + ) + })?; + + let mut active: resource_groups::ActiveModel = group.into(); + active.icon_image = Set(None); + active.icon_image_mime = Set(None); + active.updated_at = Set(Some(Utc::now().naive_utc())); + + let updated = active.update(&state.db_conn).await.map_err(|e| { + tracing::error!(error = %e, "failed to clear resource group icon image"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + + Ok(( + StatusCode::OK, + Json(json!(ResourceGroupResponse::from(updated))), + )) +} + pub async fn delete_resource_group( CanManageResourceGroups(_claims): CanManageResourceGroups, State(state): State, @@ -984,6 +1162,12 @@ pub fn resource_groups_routes() -> Router { .patch(update_resource_group) .delete(delete_resource_group), ) + .route( + "/resource-groups/{id}/icon-image", + get(get_resource_group_icon_image) + .put(upload_resource_group_icon_image) + .delete(delete_resource_group_icon_image), + ) .route( "/resource-groups/{id}/workloads", get(list_resource_group_workloads), diff --git a/control-plane/api-gateway/src/routes/settings.rs b/control-plane/api-gateway/src/routes/settings.rs index 5598b4bf..8b2f1f23 100644 --- a/control-plane/api-gateway/src/routes/settings.rs +++ b/control-plane/api-gateway/src/routes/settings.rs @@ -10,12 +10,18 @@ use sea_orm::{ActiveModelTrait, ActiveValue::Set, EntityTrait}; use serde::{Deserialize, Serialize}; use serde_json::json; -use crate::{auth::rbac::CanManageLogs, AppState}; +use crate::{ + auth::rbac::{AuthenticatedUser, CanManageLogs, CanManageSystem}, + AppState, +}; const RETENTION_KEY: &str = "logs.retention_days"; const MIN_RETENTION_DAYS: i64 = 1; const MAX_RETENTION_DAYS: i64 = 365; +const AVATAR_FALLBACK_KEY: &str = "avatar.fallback_style"; +const AVATAR_FALLBACK_STYLES: [&str; 2] = ["initials", "blobatar"]; + #[derive(Debug, Serialize)] pub struct LogsRetentionResponse { pub retention_days: i64, @@ -64,27 +70,34 @@ pub async fn update_logs_retention( })?; let now = chrono::Utc::now().into(); - let model = match existing { + match existing { Some(setting) => { let mut active: system_settings::ActiveModel = setting.into(); active.value = Set(json!(req.retention_days)); active.updated_at = Set(now); - active + active.update(&state.db_conn).await.map_err(|e| { + tracing::error!(error = %e, "failed to update logs retention setting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; } - None => system_settings::ActiveModel { - key: Set(RETENTION_KEY.to_string()), - value: Set(json!(req.retention_days)), - updated_at: Set(now), - }, - }; - - model.save(&state.db_conn).await.map_err(|e| { - tracing::error!(error = %e, "failed to save logs retention setting"); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(json!({ "error": "database error" })), - ) - })?; + None => { + let active = system_settings::ActiveModel { + key: Set(RETENTION_KEY.to_string()), + value: Set(json!(req.retention_days)), + updated_at: Set(now), + }; + active.insert(&state.db_conn).await.map_err(|e| { + tracing::error!(error = %e, "failed to insert logs retention setting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + } + } Ok(( StatusCode::OK, @@ -113,9 +126,111 @@ pub async fn load_retention_days( .unwrap_or(MAX_RETENTION_DAYS.min(30))) } +#[derive(Debug, Serialize)] +pub struct AvatarFallbackResponse { + pub style: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateAvatarFallbackRequest { + pub style: String, +} + +pub async fn get_avatar_fallback( + AuthenticatedUser(_claims): AuthenticatedUser, + State(state): State, +) -> Result)> { + let setting = SystemSettings::find_by_id(AVATAR_FALLBACK_KEY) + .one(&state.db_conn) + .await + .map_err(|e| { + tracing::error!(error = %e, "failed to load avatar fallback setting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + + let style = setting + .and_then(|s| s.value.as_str().map(str::to_string)) + .unwrap_or_else(|| "initials".to_string()); + + Ok(( + StatusCode::OK, + Json(json!(AvatarFallbackResponse { style })), + )) +} + +pub async fn update_avatar_fallback( + CanManageSystem(_claims): CanManageSystem, + State(state): State, + Json(req): Json, +) -> Result)> { + if !AVATAR_FALLBACK_STYLES.contains(&req.style.as_str()) { + return Err(( + StatusCode::BAD_REQUEST, + Json(json!({ "error": format!( + "style must be one of {}", + AVATAR_FALLBACK_STYLES.join(", ") + ) })), + )); + } + + let existing = SystemSettings::find_by_id(AVATAR_FALLBACK_KEY) + .one(&state.db_conn) + .await + .map_err(|e| { + tracing::error!(error = %e, "failed to load avatar fallback setting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + + let now = chrono::Utc::now().into(); + match existing { + Some(setting) => { + let mut active: system_settings::ActiveModel = setting.into(); + active.value = Set(json!(req.style)); + active.updated_at = Set(now); + active.update(&state.db_conn).await.map_err(|e| { + tracing::error!(error = %e, "failed to update avatar fallback setting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + } + None => { + let active = system_settings::ActiveModel { + key: Set(AVATAR_FALLBACK_KEY.to_string()), + value: Set(json!(req.style)), + updated_at: Set(now), + }; + active.insert(&state.db_conn).await.map_err(|e| { + tracing::error!(error = %e, "failed to insert avatar fallback setting"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ "error": "database error" })), + ) + })?; + } + } + + Ok(( + StatusCode::OK, + Json(json!(AvatarFallbackResponse { style: req.style })), + )) +} + pub fn settings_routes() -> Router { - Router::new().route( - "/admin/settings/logs-retention", - get(get_logs_retention).put(update_logs_retention), - ) + Router::new() + .route( + "/admin/settings/logs-retention", + get(get_logs_retention).put(update_logs_retention), + ) + .route( + "/settings/avatar-fallback", + get(get_avatar_fallback).put(update_avatar_fallback), + ) } diff --git a/control-plane/api-gateway/src/routes/system.rs b/control-plane/api-gateway/src/routes/system.rs index 070ba9a9..491e454c 100644 --- a/control-plane/api-gateway/src/routes/system.rs +++ b/control-plane/api-gateway/src/routes/system.rs @@ -232,6 +232,7 @@ async fn get_health_history( let range = params.range.as_deref().unwrap_or("1h"); let (interval_str, bucket_seconds, limit_n) = match range { + "24h" => ("24 hours", 3600i64, 24usize), "7d" => ("7 days", 3600i64, 168usize), "30d" => ("30 days", 21600i64, 120usize), _ => ("1 hour", 300i64, 12usize), diff --git a/control-plane/api-gateway/src/self_monitor.rs b/control-plane/api-gateway/src/self_monitor.rs index 8eebe988..5bfeb7dc 100644 --- a/control-plane/api-gateway/src/self_monitor.rs +++ b/control-plane/api-gateway/src/self_monitor.rs @@ -95,6 +95,7 @@ impl SelfMonitor { wg_tunnel_ip: ActiveValue::Set(None), kvm_capable: ActiveValue::Set(false), cordoned: ActiveValue::Set(false), + maintenance_until: ActiveValue::Set(None), }; let agent = new_agent.insert(db_conn.as_ref()).await?; diff --git a/control-plane/failover-controller/src/db/agents.rs b/control-plane/failover-controller/src/db/agents.rs index 0a9720a5..f057550b 100644 --- a/control-plane/failover-controller/src/db/agents.rs +++ b/control-plane/failover-controller/src/db/agents.rs @@ -18,6 +18,38 @@ pub async fn set_agent_status(db: &DatabaseConnection, agent_id: Uuid, status: & Ok(()) } +pub async fn set_maintenance( + db: &DatabaseConnection, + agent_id: Uuid, + until: Option, +) -> Result<()> { + let agent = agents::Entity::find_by_id(agent_id) + .one(db) + .await? + .ok_or_else(|| anyhow::anyhow!("Agent not found agent_id={}", agent_id))?; + + let mut active: agents::ActiveModel = agent.into(); + active.maintenance_until = Set(until); + active.update(db).await?; + + Ok(()) +} + +pub async fn get_agents_for_alerting(db: &DatabaseConnection) -> Result> { + let now = Utc::now().naive_utc(); + + let agents = agents::Entity::find() + .filter( + agents::Column::MaintenanceUntil + .is_null() + .or(agents::Column::MaintenanceUntil.lt(now)), + ) + .all(db) + .await?; + + Ok(agents) +} + pub async fn get_stale_agents( db: &DatabaseConnection, soft_threshold_secs: i64, @@ -40,6 +72,10 @@ pub async fn get_stale_agents( let mut offline = Vec::new(); for agent in all { + if agent.maintenance_until.is_some_and(|until| until > now) { + continue; + } + let last_hb = match agent.last_heartbeat { Some(ts) => ts, None => continue, diff --git a/control-plane/failover-controller/src/db/events.rs b/control-plane/failover-controller/src/db/events.rs index 16c1be5c..0970748f 100644 --- a/control-plane/failover-controller/src/db/events.rs +++ b/control-plane/failover-controller/src/db/events.rs @@ -1,7 +1,10 @@ use anyhow::Result; use chrono::Utc; use entity::entities::failover_events; -use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryOrder, Set}; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter, + QueryOrder, Set, +}; use uuid::Uuid; use crate::models::event::FailoverEvent; @@ -12,6 +15,27 @@ pub async fn insert( event_type: &str, affected_workloads: Option>, duration_ms: Option, +) -> Result { + insert_with_severity( + db, + agent_id, + event_type, + "warning", + None, + affected_workloads, + duration_ms, + ) + .await +} + +pub async fn insert_with_severity( + db: &DatabaseConnection, + agent_id: Option, + event_type: &str, + severity: &str, + message: Option, + affected_workloads: Option>, + duration_ms: Option, ) -> Result { let workloads_json = affected_workloads.map(|ids| serde_json::to_value(ids).unwrap_or(serde_json::Value::Null)); @@ -23,11 +47,45 @@ pub async fn insert( affected_workloads: Set(workloads_json), duration_ms: Set(duration_ms), created_at: Set(Utc::now().naive_utc()), + severity: Set(severity.to_string()), + status: Set("open".to_string()), + resolved_at: Set(None), + message: Set(message), }; Ok(model.insert(db).await?) } +pub async fn find_open( + db: &DatabaseConnection, + agent_id: Uuid, + event_type: &str, +) -> Result> { + let row = failover_events::Entity::find() + .filter(failover_events::Column::AgentId.eq(agent_id)) + .filter(failover_events::Column::EventType.eq(event_type)) + .filter(failover_events::Column::Status.eq("open")) + .order_by_desc(failover_events::Column::CreatedAt) + .one(db) + .await?; + + Ok(row) +} + +pub async fn resolve(db: &DatabaseConnection, event_id: Uuid) -> Result<()> { + let event = failover_events::Entity::find_by_id(event_id) + .one(db) + .await? + .ok_or_else(|| anyhow::anyhow!("Event not found event_id={}", event_id))?; + + let mut active: failover_events::ActiveModel = event.into(); + active.status = Set("resolved".to_string()); + active.resolved_at = Set(Some(Utc::now().naive_utc())); + active.update(db).await?; + + Ok(()) +} + pub async fn get_recent(db: &DatabaseConnection, limit: u64) -> Result> { let rows: Vec = failover_events::Entity::find() .order_by_desc(failover_events::Column::CreatedAt) @@ -48,5 +106,9 @@ fn into_event(m: failover_events::Model) -> FailoverEvent { .and_then(|v| serde_json::from_value(v).ok()), duration_ms: m.duration_ms, created_at: m.created_at.and_utc(), + severity: m.severity, + status: m.status, + resolved_at: m.resolved_at.map(|t| t.and_utc()), + message: m.message, } } diff --git a/control-plane/failover-controller/src/handlers/events.rs b/control-plane/failover-controller/src/handlers/events.rs index dd4ae31e..b9e1a05c 100644 --- a/control-plane/failover-controller/src/handlers/events.rs +++ b/control-plane/failover-controller/src/handlers/events.rs @@ -1,10 +1,73 @@ -use axum::{extract::State, http::StatusCode, response::IntoResponse, Json}; +use axum::{ + extract::{Path, Query, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use serde::Deserialize; +use uuid::Uuid; use crate::server::AppState; -pub async fn list_events(State(state): State) -> impl IntoResponse { +#[derive(Debug, Deserialize)] +pub struct ListEventsQuery { + status: Option, + agent_id: Option, +} + +pub async fn list_events( + State(state): State, + Query(query): Query, +) -> impl IntoResponse { match crate::db::events::get_recent(&state.db, 100).await { - Ok(events) => (StatusCode::OK, Json(serde_json::json!(events))).into_response(), + Ok(events) => { + let filtered: Vec<_> = events + .into_iter() + .filter(|e| query.status.as_deref().is_none_or(|s| e.status == s)) + .filter(|e| query.agent_id.is_none_or(|id| e.agent_id == Some(id))) + .collect(); + (StatusCode::OK, Json(serde_json::json!(filtered))).into_response() + } + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({ "error": e.to_string() })), + ) + .into_response(), + } +} + +#[derive(Debug, Deserialize)] +pub struct SetMaintenanceRequest { + minutes: i64, +} + +pub async fn set_maintenance( + State(state): State, + Path(agent_id): Path, + Json(body): Json, +) -> impl IntoResponse { + let until = chrono::Utc::now().naive_utc() + chrono::Duration::minutes(body.minutes.max(1)); + + match crate::db::agents::set_maintenance(&state.db, agent_id, Some(until)).await { + Ok(()) => ( + StatusCode::OK, + Json(serde_json::json!({ "maintenance_until": until })), + ) + .into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({ "error": e.to_string() })), + ) + .into_response(), + } +} + +pub async fn clear_maintenance( + State(state): State, + Path(agent_id): Path, +) -> impl IntoResponse { + match crate::db::agents::set_maintenance(&state.db, agent_id, None).await { + Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(e) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e.to_string() })), diff --git a/control-plane/failover-controller/src/models/event.rs b/control-plane/failover-controller/src/models/event.rs index c6d6d96b..96c04bf6 100644 --- a/control-plane/failover-controller/src/models/event.rs +++ b/control-plane/failover-controller/src/models/event.rs @@ -10,4 +10,8 @@ pub struct FailoverEvent { pub affected_workloads: Option>, pub duration_ms: Option, pub created_at: DateTime, + pub severity: String, + pub status: String, + pub resolved_at: Option>, + pub message: Option, } diff --git a/control-plane/failover-controller/src/server.rs b/control-plane/failover-controller/src/server.rs index 877335f0..ff29c83c 100644 --- a/control-plane/failover-controller/src/server.rs +++ b/control-plane/failover-controller/src/server.rs @@ -1,4 +1,9 @@ -use axum::{http::StatusCode, response::IntoResponse, routing::get, Router}; +use axum::{ + http::StatusCode, + response::IntoResponse, + routing::{delete, get, post}, + Router, +}; use sea_orm::DatabaseConnection; use crate::{handlers::events, metrics}; @@ -17,5 +22,13 @@ pub fn create_router(state: AppState) -> Router { .route("/health", get(health_check)) .route("/metrics", get(metrics::metrics_handler)) .route("/events", get(events::list_events)) + .route( + "/agents/{agent_id}/maintenance", + post(events::set_maintenance), + ) + .route( + "/agents/{agent_id}/maintenance", + delete(events::clear_maintenance), + ) .with_state(state) } diff --git a/control-plane/failover-controller/src/services/hardware_alerts.rs b/control-plane/failover-controller/src/services/hardware_alerts.rs new file mode 100644 index 00000000..3c57d800 --- /dev/null +++ b/control-plane/failover-controller/src/services/hardware_alerts.rs @@ -0,0 +1,161 @@ +use chrono::Utc; +use entity::entities::{agent_metrics, agents}; +use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder, QuerySelect}; +use uuid::Uuid; + +use crate::db::events; + +const SUSTAINED_WINDOW_SECS: i64 = 300; +const CPU_THRESHOLD_PERCENT: f32 = 90.0; +const MEMORY_THRESHOLD_PERCENT: f32 = 90.0; +const DISK_THRESHOLD_PERCENT: f32 = 85.0; + +struct ThresholdRule { + event_type: &'static str, + severity: &'static str, + threshold: f32, + label: &'static str, +} + +const RULES: &[ThresholdRule] = &[ + ThresholdRule { + event_type: "high_cpu", + severity: "warning", + threshold: CPU_THRESHOLD_PERCENT, + label: "CPU usage", + }, + ThresholdRule { + event_type: "high_memory", + severity: "warning", + threshold: MEMORY_THRESHOLD_PERCENT, + label: "Memory usage", + }, + ThresholdRule { + event_type: "high_disk", + severity: "warning", + threshold: DISK_THRESHOLD_PERCENT, + label: "Disk usage", + }, +]; + +fn sample_value(sample: &agent_metrics::Model, event_type: &str) -> Option { + match event_type { + "high_cpu" => sample.cpu_usage_percent, + "high_memory" => sample.memory_usage_percent, + "high_disk" => sample.disk_usage_percent, + _ => None, + } +} + +async fn is_sustained_above( + db: &DatabaseConnection, + agent_id: Uuid, + event_type: &str, + threshold: f32, +) -> anyhow::Result { + let cutoff = Utc::now().naive_utc() - chrono::Duration::seconds(SUSTAINED_WINDOW_SECS); + + let samples = agent_metrics::Entity::find() + .filter(agent_metrics::Column::AgentId.eq(agent_id)) + .filter(agent_metrics::Column::Timestamp.gt(cutoff)) + .order_by_desc(agent_metrics::Column::Timestamp) + .limit(20) + .all(db) + .await?; + + if samples.is_empty() { + return Ok(false); + } + + Ok(samples + .iter() + .all(|s| sample_value(s, event_type).is_some_and(|v| v > threshold))) +} + +pub async fn check_agent(db: &DatabaseConnection, agent: &agents::Model) { + for rule in RULES { + let sustained = + match is_sustained_above(db, agent.id, rule.event_type, rule.threshold).await { + Ok(v) => v, + Err(e) => { + crate::log_error!( + "hardware_alerts", + &format!( + "Failed to evaluate rule agent_id={} rule={} err={}", + agent.id, rule.event_type, e + ) + ); + continue; + } + }; + + let existing = match events::find_open(db, agent.id, rule.event_type).await { + Ok(v) => v, + Err(e) => { + crate::log_error!( + "hardware_alerts", + &format!( + "Failed to query open event agent_id={} rule={} err={}", + agent.id, rule.event_type, e + ) + ); + continue; + } + }; + + match (sustained, existing) { + (true, None) => { + let message = format!( + "{} above {:.0}% for {}m", + rule.label, + rule.threshold, + SUSTAINED_WINDOW_SECS / 60 + ); + if let Err(e) = events::insert_with_severity( + db, + Some(agent.id), + rule.event_type, + rule.severity, + Some(message.clone()), + None, + None, + ) + .await + { + crate::log_error!( + "hardware_alerts", + &format!( + "Failed to insert alert agent_id={} rule={} err={}", + agent.id, rule.event_type, e + ) + ); + continue; + } + crate::log_warn!( + "hardware_alerts", + &format!( + "agent_id={} alert={} message={}", + agent.id, rule.event_type, message + ) + ); + } + (false, Some(open_event)) => { + if let Err(e) = events::resolve(db, open_event.id).await { + crate::log_error!( + "hardware_alerts", + &format!( + "Failed to resolve alert agent_id={} rule={} err={}", + agent.id, rule.event_type, e + ) + ); + continue; + } + crate::log_info!( + "hardware_alerts", + &format!("agent_id={} alert={} resolved", agent.id, rule.event_type) + ); + } + _ => {} + } + } +} diff --git a/control-plane/failover-controller/src/services/mod.rs b/control-plane/failover-controller/src/services/mod.rs index 60f85493..b2a9c0a6 100644 --- a/control-plane/failover-controller/src/services/mod.rs +++ b/control-plane/failover-controller/src/services/mod.rs @@ -1,2 +1,3 @@ pub mod failover; +pub mod hardware_alerts; pub mod monitor; diff --git a/control-plane/failover-controller/src/services/monitor.rs b/control-plane/failover-controller/src/services/monitor.rs index a35eda63..a11f2afb 100644 --- a/control-plane/failover-controller/src/services/monitor.rs +++ b/control-plane/failover-controller/src/services/monitor.rs @@ -5,6 +5,7 @@ use tokio::time::{interval, Duration}; use crate::db::agents as agent_db; use crate::db::events; use crate::services::failover::FailoverService; +use crate::services::hardware_alerts; const POLL_INTERVAL_SECS: u64 = 30; const SOFT_THRESHOLD_SECS: i64 = 120; @@ -17,6 +18,20 @@ pub async fn run(db: DatabaseConnection) { loop { tick.tick().await; + match agent_db::get_agents_for_alerting(&db).await { + Ok(agents) => { + for agent in &agents { + hardware_alerts::check_agent(&db, agent).await; + } + } + Err(e) => { + crate::log_error!( + "monitor", + &format!("Failed to query agents for alerting err={}", e) + ); + } + } + let (degraded, offline) = match agent_db::get_stale_agents(&db, SOFT_THRESHOLD_SECS, HARD_THRESHOLD_SECS).await { Ok(result) => result, diff --git a/control-plane/registry/src/db/agents.rs b/control-plane/registry/src/db/agents.rs index 18fcd454..c1c8237c 100644 --- a/control-plane/registry/src/db/agents.rs +++ b/control-plane/registry/src/db/agents.rs @@ -84,6 +84,7 @@ pub async fn create( wg_tunnel_ip: Set(wg_tunnel_ip), kvm_capable: Set(false), cordoned: Set(false), + maintenance_until: Set(None), }; Ok(model.insert(db).await?) diff --git a/control-plane/shared/entity/src/entities/agents.rs b/control-plane/shared/entity/src/entities/agents.rs index 884c774d..9f9f5894 100644 --- a/control-plane/shared/entity/src/entities/agents.rs +++ b/control-plane/shared/entity/src/entities/agents.rs @@ -26,6 +26,7 @@ pub struct Model { pub wg_tunnel_ip: Option, pub kvm_capable: bool, pub cordoned: bool, + pub maintenance_until: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/control-plane/shared/entity/src/entities/failover_events.rs b/control-plane/shared/entity/src/entities/failover_events.rs index f933c291..24c51305 100644 --- a/control-plane/shared/entity/src/entities/failover_events.rs +++ b/control-plane/shared/entity/src/entities/failover_events.rs @@ -11,6 +11,10 @@ pub struct Model { pub affected_workloads: Option, pub duration_ms: Option, pub created_at: DateTime, + pub severity: String, + pub status: String, + pub resolved_at: Option, + pub message: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/control-plane/shared/entity/src/entities/resource_groups.rs b/control-plane/shared/entity/src/entities/resource_groups.rs index d3e0c157..afbec2dd 100644 --- a/control-plane/shared/entity/src/entities/resource_groups.rs +++ b/control-plane/shared/entity/src/entities/resource_groups.rs @@ -14,6 +14,10 @@ pub struct Model { pub icon: String, pub color: String, pub pinned: bool, + #[serde(skip_serializing)] + pub icon_image: Option>, + pub icon_image_mime: Option, + pub preview_url: Option, pub created_at: chrono::NaiveDateTime, pub updated_at: Option, } diff --git a/control-plane/shared/migration/src/lib.rs b/control-plane/shared/migration/src/lib.rs index 72ffb996..18fbd702 100644 --- a/control-plane/shared/migration/src/lib.rs +++ b/control-plane/shared/migration/src/lib.rs @@ -35,7 +35,9 @@ mod m20260809_000000_add_user_gravatar_email; mod m20260816_000000_add_object_storage; mod m20260816_010000_add_bucket_master_key; mod m20260816_020000_garage_nodes_agent_id_nullable; - +mod m20260918_000000_add_alerts_and_maintenance; +mod m20260919_000000_add_resource_group_icon_image; +mod m20260920_000000_add_resource_group_preview_url; pub struct Migrator; #[async_trait::async_trait] @@ -77,6 +79,9 @@ impl MigratorTrait for Migrator { Box::new(m20260816_000000_add_object_storage::Migration), Box::new(m20260816_010000_add_bucket_master_key::Migration), Box::new(m20260816_020000_garage_nodes_agent_id_nullable::Migration), + Box::new(m20260918_000000_add_alerts_and_maintenance::Migration), + Box::new(m20260919_000000_add_resource_group_icon_image::Migration), + Box::new(m20260920_000000_add_resource_group_preview_url::Migration), ] } } diff --git a/control-plane/shared/migration/src/m20260918_000000_add_alerts_and_maintenance.rs b/control-plane/shared/migration/src/m20260918_000000_add_alerts_and_maintenance.rs new file mode 100644 index 00000000..507c1cd3 --- /dev/null +++ b/control-plane/shared/migration/src/m20260918_000000_add_alerts_and_maintenance.rs @@ -0,0 +1,76 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Alias::new("failover_events")) + .add_column_if_not_exists( + ColumnDef::new(Alias::new("severity")) + .string() + .not_null() + .default("info"), + ) + .add_column_if_not_exists( + ColumnDef::new(Alias::new("status")) + .string() + .not_null() + .default("open"), + ) + .add_column_if_not_exists(ColumnDef::new(Alias::new("resolved_at")).date_time()) + .add_column_if_not_exists(ColumnDef::new(Alias::new("message")).string()) + .to_owned(), + ) + .await?; + + manager + .create_index( + Index::create() + .if_not_exists() + .name("idx_failover_events_status") + .table(Alias::new("failover_events")) + .col(Alias::new("status")) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(Alias::new("agents")) + .add_column_if_not_exists( + ColumnDef::new(Alias::new("maintenance_until")).date_time(), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Alias::new("agents")) + .drop_column(Alias::new("maintenance_until")) + .to_owned(), + ) + .await?; + + manager + .alter_table( + Table::alter() + .table(Alias::new("failover_events")) + .drop_column(Alias::new("severity")) + .drop_column(Alias::new("status")) + .drop_column(Alias::new("resolved_at")) + .drop_column(Alias::new("message")) + .to_owned(), + ) + .await + } +} diff --git a/control-plane/shared/migration/src/m20260919_000000_add_resource_group_icon_image.rs b/control-plane/shared/migration/src/m20260919_000000_add_resource_group_icon_image.rs new file mode 100644 index 00000000..91cdc18e --- /dev/null +++ b/control-plane/shared/migration/src/m20260919_000000_add_resource_group_icon_image.rs @@ -0,0 +1,33 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Alias::new("resource_groups")) + .add_column_if_not_exists(ColumnDef::new(Alias::new("icon_image")).binary()) + .add_column_if_not_exists( + ColumnDef::new(Alias::new("icon_image_mime")).string(), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Alias::new("resource_groups")) + .drop_column(Alias::new("icon_image")) + .drop_column(Alias::new("icon_image_mime")) + .to_owned(), + ) + .await + } +} diff --git a/control-plane/shared/migration/src/m20260920_000000_add_resource_group_preview_url.rs b/control-plane/shared/migration/src/m20260920_000000_add_resource_group_preview_url.rs new file mode 100644 index 00000000..ba72a94d --- /dev/null +++ b/control-plane/shared/migration/src/m20260920_000000_add_resource_group_preview_url.rs @@ -0,0 +1,31 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Alias::new("resource_groups")) + .add_column_if_not_exists( + ColumnDef::new(Alias::new("preview_url")).string().null(), + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Alias::new("resource_groups")) + .drop_column(Alias::new("preview_url")) + .to_owned(), + ) + .await + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 220dd760..9d035c3e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -306,6 +306,9 @@ services: environment: CSFX_GATEWAY_URL: http://api-gateway:8000 CSFX_HEARTBEAT_INTERVAL: "15" + CSFX_SIMULATE_LOAD: "1" + CSFX_FAKE_DISKS: "1" + CSFX_AGENT_PORT: "7443" RUST_LOG: ${RUST_LOG:-debug} depends_on: api-gateway: @@ -326,6 +329,9 @@ services: environment: CSFX_GATEWAY_URL: http://api-gateway:8000 CSFX_HEARTBEAT_INTERVAL: "15" + CSFX_SIMULATE_LOAD: "1" + CSFX_FAKE_DISKS: "1" + CSFX_AGENT_PORT: "7443" RUST_LOG: ${RUST_LOG:-debug} depends_on: api-gateway: @@ -346,6 +352,9 @@ services: environment: CSFX_GATEWAY_URL: http://api-gateway:8000 CSFX_HEARTBEAT_INTERVAL: "15" + CSFX_SIMULATE_LOAD: "1" + CSFX_FAKE_DISKS: "1" + CSFX_AGENT_PORT: "7443" RUST_LOG: ${RUST_LOG:-debug} depends_on: api-gateway: