fix: address code review findings for DNS management

- CRITICAL: Change DNS zones endpoint from GET to POST to avoid
  leaking API token in URL query parameters
- HIGH: Add sync.RWMutex to protect dnsProvider field in Server,
  Deployer, and proxy Manager against concurrent read/write races
- HIGH: Capture old DNS provider reference synchronously before
  launching background cleanup goroutine
- HIGH: Use getDNS()/getDNSProviderLocked() accessors instead of
  direct field reads in all DNS operations
This commit is contained in:
2026-04-02 14:54:15 +03:00
parent c730cfaa45
commit 670948f113
243 changed files with 15971 additions and 535 deletions
@@ -0,0 +1,294 @@
import { a as attr, b as attr_class, c as clsx, f as ensure_array_like, s as store_get, g as stringify, e as escape_html, u as unsubscribe_stores, i as derived } from "../../chunks/index.js";
import { o as onDestroy } from "../../chunks/index-server.js";
import { p as page } from "../../chunks/stores.js";
import { t as toasts } from "../../chunks/toast.js";
import { I as IconCheck } from "../../chunks/IconCheck.js";
import { I as IconX } from "../../chunks/IconX.js";
import { I as IconAlert } from "../../chunks/IconAlert.js";
import { t as themeMode } from "../../chunks/theme.js";
import { t, a as availableLocales, l as locale } from "../../chunks/index2.js";
import { I as IconGlobe } from "../../chunks/IconGlobe.js";
import { I as IconDeploy } from "../../chunks/IconDeploy.js";
import { I as IconSettings } from "../../chunks/IconSettings.js";
import { w as writable, g as get } from "../../chunks/index3.js";
function IconDashboard($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><rect width="7" height="9" x="3" y="3" rx="1"></rect><rect width="7" height="5" x="14" y="3" rx="1"></rect><rect width="7" height="9" x="14" y="12" rx="1"></rect><rect width="7" height="5" x="3" y="16" rx="1"></rect></svg>`);
}
function IconProjects($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path></svg>`);
}
function IconInfo($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><path d="M12 8h.01"></path></svg>`);
}
function IconSun($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></svg>`);
}
function IconMoon($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></svg>`);
}
function IconMonitor($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><rect width="20" height="14" x="2" y="3" rx="2"></rect><line x1="8" x2="16" y1="21" y2="21"></line><line x1="12" x2="12" y1="17" y2="21"></line></svg>`);
}
function IconMenu($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg>`);
}
function IconProxies($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"></path><path d="M2 12h20"></path></svg>`);
}
function IconEvents($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><path d="M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z"></path><path d="M15 3v4a2 2 0 0 0 2 2h4"></path><path d="M8 13h.01"></path><path d="M8 17h.01"></path><path d="M12 13h4"></path><path d="M12 17h4"></path></svg>`);
}
function IconLogout($$renderer, $$props) {
const { size = 24, class: className = "" } = $$props;
$$renderer.push(`<svg${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(className))}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>`);
}
function Toast($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const bgMap = {
success: "bg-[var(--color-success)]",
error: "bg-[var(--color-danger)]",
warning: "bg-[var(--color-warning)]",
info: "bg-[var(--color-info)]"
};
$$renderer2.push(`<div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none"><!--[-->`);
const each_array = ensure_array_like(store_get($$store_subs ??= {}, "$toasts", toasts));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let toast = each_array[$$index];
$$renderer2.push(`<div${attr_class(`pointer-events-auto flex items-center gap-3 rounded-xl px-4 py-3 text-white shadow-lg animate-slide-in ${stringify(bgMap[toast.type])}`)} role="alert"><div class="flex h-5 w-5 flex-shrink-0 items-center justify-center">`);
if (toast.type === "success") {
$$renderer2.push("<!--[0-->");
IconCheck($$renderer2, { size: 18 });
} else if (toast.type === "error") {
$$renderer2.push("<!--[1-->");
IconX($$renderer2, { size: 18 });
} else if (toast.type === "warning") {
$$renderer2.push("<!--[2-->");
IconAlert($$renderer2, { size: 18 });
} else {
$$renderer2.push("<!--[-1-->");
IconInfo($$renderer2, { size: 18 });
}
$$renderer2.push(`<!--]--></div> <span class="flex-1 text-sm font-medium">${escape_html(toast.message)}</span> <button class="ml-2 rounded-md p-0.5 text-white/70 hover:text-white transition-colors" aria-label="Dismiss notification">`);
IconX($$renderer2, { size: 16 });
$$renderer2.push(`<!----></button></div>`);
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function ThemeToggle($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const modes = ["light", "dark", "system"];
$$renderer2.push(`<div class="flex items-center gap-0.5 rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] p-0.5"><!--[-->`);
const each_array = ensure_array_like(modes);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let mode = each_array[$$index];
$$renderer2.push(`<button type="button"${attr_class(`flex items-center justify-center rounded-md p-1.5 transition-all duration-150 ${stringify(store_get($$store_subs ??= {}, "$themeMode", themeMode) === mode ? "bg-[var(--color-brand-100)] text-[var(--color-brand-700)] shadow-sm" : "text-[var(--text-tertiary)] hover:text-[var(--text-secondary)]")}`)}${attr("title", store_get($$store_subs ??= {}, "$t", t)(`theme.${mode}`))}>`);
if (mode === "light") {
$$renderer2.push("<!--[0-->");
IconSun($$renderer2, { size: 14 });
} else if (mode === "dark") {
$$renderer2.push("<!--[1-->");
IconMoon($$renderer2, { size: 14 });
} else {
$$renderer2.push("<!--[-1-->");
IconMonitor($$renderer2, { size: 14 });
}
$$renderer2.push(`<!--]--></button>`);
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function LocaleSwitcher($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
$$renderer2.push(`<div class="flex items-center gap-1.5">`);
IconGlobe($$renderer2, { size: 14, class: "text-[var(--text-tertiary)]" });
$$renderer2.push(`<!----> <div class="flex items-center gap-0.5 rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] p-0.5"><!--[-->`);
const each_array = ensure_array_like(availableLocales);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let loc = each_array[$$index];
$$renderer2.push(`<button type="button"${attr_class(`rounded-md px-2 py-0.5 text-xs font-medium transition-all duration-150 ${stringify(store_get($$store_subs ??= {}, "$locale", locale) === loc ? "bg-[var(--color-brand-100)] text-[var(--color-brand-700)] shadow-sm" : "text-[var(--text-tertiary)] hover:text-[var(--text-secondary)]")}`)}>${escape_html(loc.toUpperCase())}</button>`);
}
$$renderer2.push(`<!--]--></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function createInstanceStatusStore() {
const { subscribe, set, update: storeUpdate } = writable({
statuses: {},
lastUpdate: 0,
deployStatuses: {}
});
return {
subscribe,
/** Update an instance's status from an SSE event. */
update(payload) {
storeUpdate((state) => ({
...state,
statuses: {
...state.statuses,
[payload.instance_id]: payload.status
},
lastUpdate: Date.now()
}));
},
/** Record a deploy status change from an SSE event. */
notifyDeploy(payload) {
storeUpdate((state) => ({
...state,
deployStatuses: {
...state.deployStatuses,
[payload.deploy_id]: payload
},
lastUpdate: Date.now()
}));
},
/** Get the current status of an instance, or undefined if not tracked. */
getStatus(instanceId) {
return get({ subscribe }).statuses[instanceId];
},
/** Reset the store (e.g., on disconnect). */
reset() {
set({
statuses: {},
lastUpdate: 0,
deployStatuses: {}
});
}
};
}
createInstanceStatusStore();
function _layout($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const { children } = $$props;
const navItems = [
{ href: "/", labelKey: "nav.dashboard", icon: "dashboard" },
{
href: "/projects",
labelKey: "nav.projects",
icon: "projects"
},
{ href: "/deploy", labelKey: "nav.deploy", icon: "deploy" },
{ href: "/proxies", labelKey: "nav.proxies", icon: "proxies" },
{ href: "/events", labelKey: "nav.events", icon: "events" },
{
href: "/settings",
labelKey: "nav.settings",
icon: "settings"
}
];
function isActive(href, pathname) {
if (href === "/") return pathname === "/";
return pathname.startsWith(href);
}
let sseConnection = null;
const isLoginPage = derived(() => store_get($$store_subs ??= {}, "$page", page).url.pathname === "/login");
onDestroy(() => {
sseConnection?.close();
sseConnection = null;
});
if (isLoginPage()) {
$$renderer2.push("<!--[0-->");
children($$renderer2);
$$renderer2.push(`<!---->`);
} else {
$$renderer2.push("<!--[-1-->");
$$renderer2.push(`<div class="flex h-screen overflow-hidden bg-[var(--surface-page)]">`);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> <aside${attr_class(`fixed inset-y-0 left-0 z-50 flex w-64 flex-col border-r border-[var(--border-primary)] bg-[var(--surface-sidebar)] transition-transform duration-300 lg:static lg:translate-x-0 ${stringify("-translate-x-full")}`)}><div class="flex h-16 items-center gap-2.5 border-b border-[var(--border-primary)] px-5"><div class="flex h-8 w-8 items-center justify-center rounded-lg bg-[var(--color-brand-600)]"><svg class="h-4.5 w-4.5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25"></path></svg></div> <span class="text-base font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</span> <button class="ml-auto rounded-md p-1 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] lg:hidden" aria-label="Close sidebar">`);
IconX($$renderer2, { size: 20 });
$$renderer2.push(`<!----></button></div> <nav class="flex-1 space-y-0.5 px-3 py-3"><!--[-->`);
const each_array = ensure_array_like(navItems);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
const active = isActive(item.href, store_get($$store_subs ??= {}, "$page", page).url.pathname);
$$renderer2.push(`<a${attr("href", item.href)}${attr_class(`group flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-150 ${stringify(active ? "bg-[var(--color-brand-50)] text-[var(--color-brand-700)] shadow-sm" : "text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)]")}`)}>`);
if (item.icon === "dashboard") {
$$renderer2.push("<!--[0-->");
IconDashboard($$renderer2, {
size: 18,
class: `${stringify(active ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]")} transition-colors duration-150`
});
} else if (item.icon === "projects") {
$$renderer2.push("<!--[1-->");
IconProjects($$renderer2, {
size: 18,
class: `${stringify(active ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]")} transition-colors duration-150`
});
} else if (item.icon === "deploy") {
$$renderer2.push("<!--[2-->");
IconDeploy($$renderer2, {
size: 18,
class: `${stringify(active ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]")} transition-colors duration-150`
});
} else if (item.icon === "proxies") {
$$renderer2.push("<!--[3-->");
IconProxies($$renderer2, {
size: 18,
class: `${stringify(active ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]")} transition-colors duration-150`
});
} else if (item.icon === "events") {
$$renderer2.push("<!--[4-->");
IconEvents($$renderer2, {
size: 18,
class: `${stringify(active ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]")} transition-colors duration-150`
});
} else if (item.icon === "settings") {
$$renderer2.push("<!--[5-->");
IconSettings($$renderer2, {
size: 18,
class: `${stringify(active ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]")} transition-colors duration-150`
});
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> ${escape_html(store_get($$store_subs ??= {}, "$t", t)(item.labelKey))} `);
if (active) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="ml-auto h-1.5 w-1.5 rounded-full bg-[var(--color-brand-600)]"></div>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></a>`);
}
$$renderer2.push(`<!--]--></nav> <div class="space-y-3 border-t border-[var(--border-primary)] px-4 py-3">`);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> <div class="flex items-center justify-between">`);
ThemeToggle($$renderer2);
$$renderer2.push(`<!----> `);
LocaleSwitcher($$renderer2);
$$renderer2.push(`<!----></div> <div class="flex items-center justify-between"><p class="text-xs text-[var(--text-tertiary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))} ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.version"))}</p> <button class="inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-[var(--text-secondary)] transition-colors hover:bg-[var(--surface-card-hover)] hover:text-[var(--color-danger)]"${attr("title", store_get($$store_subs ??= {}, "$t", t)("nav.logout"))}>`);
IconLogout($$renderer2, { size: 14 });
$$renderer2.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("nav.logout"))}</button></div></div></aside> <div class="flex flex-1 flex-col overflow-hidden"><header class="flex h-14 items-center gap-3 border-b border-[var(--border-primary)] bg-[var(--surface-sidebar)] px-4 lg:hidden"><button class="rounded-md p-1.5 text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)] transition-colors" aria-label="Open sidebar">`);
IconMenu($$renderer2, { size: 22 });
$$renderer2.push(`<!----></button> <div class="flex h-7 w-7 items-center justify-center rounded-lg bg-[var(--color-brand-600)]"><svg class="h-3.5 w-3.5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25"></path></svg></div> <span class="text-sm font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</span></header> <main class="flex-1 overflow-y-auto"><div class="mx-auto max-w-7xl px-4 py-6 sm:px-6 sm:py-8">`);
children($$renderer2);
$$renderer2.push(`<!----></div></main></div></div>`);
}
$$renderer2.push(`<!--]--> `);
Toast($$renderer2);
$$renderer2.push(`<!---->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_layout as default
};
@@ -0,0 +1,6 @@
const ssr = false;
const prerender = false;
export {
prerender,
ssr
};
@@ -0,0 +1,71 @@
import { a as attr, b as attr_class, c as clsx, h as head, e as escape_html, s as store_get, g as stringify, f as ensure_array_like, u as unsubscribe_stores, i as derived } from "../../chunks/index.js";
import { t } from "../../chunks/index2.js";
import { I as IconDeploy } from "../../chunks/IconDeploy.js";
import { I as IconAlert } from "../../chunks/IconAlert.js";
import { S as SkeletonCard } from "../../chunks/SkeletonCard.js";
import "clsx";
function IconServer($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect><rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect><line x1="6" x2="6.01" y1="6" y2="6"></line><line x1="6" x2="6.01" y1="18" y2="18"></line></svg>`);
}
function IconBox($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"></path><path d="m3.3 7 8.7 5 8.7-5"></path><path d="M12 22V12"></path></svg>`);
}
function IconClock($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>`);
}
function SystemHealthCard($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]-->`);
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let projects = [];
let instancesByProject = {};
let staleContainers = [];
const totalProjects = derived(() => projects.length);
const totalRunning = derived(() => Object.values(instancesByProject).flat().filter((i) => i.status === "running").length);
const totalFailed = derived(() => Object.values(instancesByProject).flat().filter((i) => i.status === "failed").length);
const totalStale = derived(() => staleContainers.length);
head("1uha8ag", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="space-y-6"><div class="flex items-center justify-between"><h1 class="text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.title"))}</h1> <a href="/deploy" class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-[var(--color-brand-700)] active:animate-press">`);
IconDeploy($$renderer2, { size: 16 });
$$renderer2.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.quickDeploy"))}</a></div> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"><div class="flex items-center gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)]"><div class="flex h-12 w-12 items-center justify-center rounded-xl bg-[var(--color-brand-50)] text-[var(--color-brand-600)]">`);
IconBox($$renderer2, { size: 24 });
$$renderer2.push(`<!----></div> <div><p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.totalProjects"))}</p> <p class="mt-0.5 text-2xl font-bold text-[var(--text-primary)]">${escape_html(totalProjects())}</p></div></div> <div class="flex items-center gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)]"><div class="flex h-12 w-12 items-center justify-center rounded-xl bg-emerald-50 text-emerald-600">`);
IconServer($$renderer2, { size: 24 });
$$renderer2.push(`<!----></div> <div><p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.runningInstances"))}</p> <p class="mt-0.5 text-2xl font-bold text-emerald-600">${escape_html(totalRunning())}</p></div></div> <div class="flex items-center gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)]"><div${attr_class(`flex h-12 w-12 items-center justify-center rounded-xl ${stringify(totalFailed() > 0 ? "bg-red-50 text-red-600" : "bg-gray-50 text-gray-400")}`)}>`);
IconAlert($$renderer2, { size: 24 });
$$renderer2.push(`<!----></div> <div><p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.failedInstances"))}</p> <p${attr_class(`mt-0.5 text-2xl font-bold ${stringify(totalFailed() > 0 ? "text-red-600" : "text-[var(--text-primary)]")}`)}>${escape_html(totalFailed())}</p></div></div> <a href="/containers/stale" class="flex items-center gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)] transition-colors hover:bg-[var(--surface-card-hover)]"><div${attr_class(`flex h-12 w-12 items-center justify-center rounded-xl ${stringify(totalStale() > 0 ? "bg-amber-50 text-amber-600" : "bg-gray-50 text-gray-400")}`)}>`);
IconClock($$renderer2, { size: 24 });
$$renderer2.push(`<!----></div> <div><p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.staleContainers"))}</p> <p${attr_class(`mt-0.5 text-2xl font-bold ${stringify(totalStale() > 0 ? "text-amber-600" : "text-[var(--text-primary)]")}`)}>${escape_html(totalStale())}</p></div></a></div> `);
SystemHealthCard($$renderer2);
$$renderer2.push(`<!----> <div><h2 class="text-lg font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("dashboard.projects"))}</h2> `);
{
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"><!--[-->`);
const each_array = ensure_array_like(Array(3));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
SkeletonCard($$renderer2);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,135 @@
import { b as attr_class, g as stringify, e as escape_html, s as store_get, u as unsubscribe_stores, i as derived, h as head, a as attr, f as ensure_array_like } from "../../../../chunks/index.js";
import { c as cleanupStaleContainer, b as bulkCleanupStaleContainers } from "../../../../chunks/api.js";
import { I as IconTrash } from "../../../../chunks/IconTrash.js";
import { I as IconLoader } from "../../../../chunks/IconLoader.js";
import { t } from "../../../../chunks/index2.js";
import { I as IconAlert } from "../../../../chunks/IconAlert.js";
import { S as SkeletonCard } from "../../../../chunks/SkeletonCard.js";
import { t as toasts } from "../../../../chunks/toast.js";
function ConfirmDialog($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const {
open,
title,
message,
confirmLabel = "Confirm",
confirmVariant = "primary",
onconfirm,
oncancel
} = $$props;
const confirmClass = derived(() => confirmVariant === "danger" ? "bg-[var(--color-danger)] hover:bg-[var(--color-danger-dark)] focus-visible:outline-[var(--color-danger)]" : "bg-[var(--color-brand-600)] hover:bg-[var(--color-brand-700)] focus-visible:outline-[var(--color-brand-600)]");
const iconBgClass = derived(() => confirmVariant === "danger" ? "bg-[var(--color-danger-light)]" : "bg-[var(--color-brand-50)]");
const iconColorClass = derived(() => confirmVariant === "danger" ? "text-[var(--color-danger)]" : "text-[var(--color-brand-600)]");
if (open) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="fixed inset-0 z-40 bg-[var(--surface-overlay)] animate-fade-in" role="presentation"></div> <div class="fixed inset-0 z-50 flex items-center justify-center p-4"><div class="w-full max-w-md rounded-2xl bg-[var(--surface-card)] p-6 shadow-xl animate-scale-in"><div class="flex items-start gap-4"><div${attr_class(`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full ${stringify(iconBgClass())}`)}>`);
IconAlert($$renderer2, { size: 20, class: iconColorClass() });
$$renderer2.push(`<!----></div> <div class="flex-1"><h3 class="text-lg font-semibold text-[var(--text-primary)]">${escape_html(title)}</h3> <p class="mt-2 text-sm text-[var(--text-secondary)] leading-relaxed">${escape_html(message)}</p></div></div> <div class="mt-6 flex justify-end gap-3"><button type="button" class="rounded-lg px-4 py-2 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] transition-colors active:animate-press">${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.cancel"))}</button> <button type="button"${attr_class(`rounded-lg px-4 py-2 text-sm font-medium text-white ${stringify(confirmClass())} shadow-sm transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 active:animate-press`)}>${escape_html(confirmLabel)}</button></div></div></div>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]-->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let containers = [];
let confirmSingleId = "";
let confirmBulk = false;
let cleaningIds = /* @__PURE__ */ new Set();
let bulkCleaning = false;
async function handleConfirmCleanup() {
const id = confirmSingleId;
confirmSingleId = "";
cleaningIds = /* @__PURE__ */ new Set([...cleaningIds, id]);
try {
await cleanupStaleContainer(id);
containers = containers.filter((c) => c.id !== id);
toasts.success(store_get($$store_subs ??= {}, "$t", t)("stale.cleanedUp"));
} catch (e) {
toasts.error(e instanceof Error ? e.message : store_get($$store_subs ??= {}, "$t", t)("stale.cleanupFailed"));
} finally {
const next = new Set(cleaningIds);
next.delete(id);
cleaningIds = next;
}
}
async function handleConfirmBulkCleanup() {
confirmBulk = false;
bulkCleaning = true;
try {
const result = await bulkCleanupStaleContainers();
containers = [];
toasts.success(store_get($$store_subs ??= {}, "$t", t)("stale.bulkCleanedUp", { count: String(result.deleted) }));
} catch (e) {
toasts.error(e instanceof Error ? e.message : store_get($$store_subs ??= {}, "$t", t)("stale.cleanupFailed"));
} finally {
bulkCleaning = false;
}
}
head("r4vrn", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("stale.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="space-y-6"><div class="flex items-center justify-between"><h1 class="text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("stale.title"))}</h1> `);
if (containers.length > 0) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<button type="button"${attr("disabled", bulkCleaning, true)} class="inline-flex items-center gap-2 rounded-lg border border-[var(--color-danger)] px-4 py-2.5 text-sm font-medium text-[var(--color-danger)] transition-colors hover:bg-[var(--color-danger-light)] disabled:opacity-50 active:animate-press">`);
if (bulkCleaning) {
$$renderer2.push("<!--[0-->");
IconLoader($$renderer2, { size: 16 });
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> `);
IconTrash($$renderer2, { size: 16 });
$$renderer2.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("stale.cleanupAll"))}</button>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></div> `);
{
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"><!--[-->`);
const each_array = ensure_array_like(Array(3));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
SkeletonCard($$renderer2);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div> `);
ConfirmDialog($$renderer2, {
open: confirmSingleId !== "",
title: store_get($$store_subs ??= {}, "$t", t)("stale.cleanup"),
message: store_get($$store_subs ??= {}, "$t", t)("stale.confirmCleanup"),
confirmLabel: store_get($$store_subs ??= {}, "$t", t)("stale.cleanup"),
confirmVariant: "danger",
onconfirm: handleConfirmCleanup,
oncancel: () => {
confirmSingleId = "";
}
});
$$renderer2.push(`<!----> `);
ConfirmDialog($$renderer2, {
open: confirmBulk,
title: store_get($$store_subs ??= {}, "$t", t)("stale.cleanupAll"),
message: store_get($$store_subs ??= {}, "$t", t)("stale.confirmBulkCleanup"),
confirmLabel: store_get($$store_subs ??= {}, "$t", t)("stale.cleanupAll"),
confirmVariant: "danger",
onconfirm: handleConfirmBulkCleanup,
oncancel: () => {
confirmBulk = false;
}
});
$$renderer2.push(`<!---->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,4 @@
const ssr = false;
export {
ssr
};
@@ -0,0 +1,89 @@
import { h as head, e as escape_html, s as store_get, a as attr, u as unsubscribe_stores } from "../../../chunks/index.js";
import { F as FormField } from "../../../chunks/FormField.js";
import { I as IconSearch, E as EntityPicker } from "../../../chunks/EntityPicker.js";
import "../../../chunks/toast.js";
import { t } from "../../../chunks/index2.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let imageUrl = "";
let inspecting = false;
let errors = {};
let showImagePicker = false;
let imagePickerItems = [];
function selectPickedImage(value) {
imageUrl = value;
showImagePicker = false;
}
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("2t7dow", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("quickDeploy.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer3.push(`<div class="mx-auto max-w-2xl space-y-6"><div><h1 class="text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("quickDeploy.title"))}</h1> <p class="mt-1 text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("quickDeploy.description"))}</p></div> <div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]"><h2 class="mb-4 text-base font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("quickDeploy.step1"))}</h2> <div class="flex gap-3"><div class="flex-1">`);
FormField($$renderer3, {
label: store_get($$store_subs ??= {}, "$t", t)("quickDeploy.imageUrl"),
name: "imageUrl",
placeholder: "registry.example.com/org/app:tag",
required: true,
error: errors.imageUrl ?? "",
helpText: store_get($$store_subs ??= {}, "$t", t)("quickDeploy.imageUrlHelp"),
disabled: inspecting,
get value() {
return imageUrl;
},
set value($$value) {
imageUrl = $$value;
$$settled = false;
}
});
$$renderer3.push(`<!----></div> <div class="flex items-start gap-2 pt-[26px]"><button type="button"${attr("title", store_get($$store_subs ??= {}, "$t", t)("quickDeploy.browseImages"))}${attr("aria-label", store_get($$store_subs ??= {}, "$t", t)("quickDeploy.browseImages"))} class="rounded-lg border border-[var(--border-primary)] p-2 text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)] transition-colors">`);
{
$$renderer3.push("<!--[-1-->");
IconSearch($$renderer3, { size: 16 });
}
$$renderer3.push(`<!--]--></button> <button${attr("disabled", !imageUrl.trim(), true)} class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-info)] px-4 py-2 text-sm font-medium text-white transition-all duration-150 hover:bg-[var(--color-info-dark)] disabled:cursor-not-allowed disabled:opacity-50 active:animate-press">`);
{
$$renderer3.push("<!--[-1-->");
IconSearch($$renderer3, { size: 16 });
$$renderer3.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("quickDeploy.inspect"))}`);
}
$$renderer3.push(`<!--]--></button></div></div> `);
EntityPicker($$renderer3, {
items: imagePickerItems,
current: imageUrl,
title: store_get($$store_subs ??= {}, "$t", t)("quickDeploy.selectImage"),
placeholder: store_get($$store_subs ??= {}, "$t", t)("entityPicker.search"),
onselect: selectPickedImage,
onclose: () => {
showImagePicker = false;
},
get open() {
return showImagePicker;
},
set open($$value) {
showImagePicker = $$value;
$$settled = false;
}
});
$$renderer3.push(`<!----></div> `);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--></div>`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,126 @@
import { f as ensure_array_like, b as attr_class, g as stringify, e as escape_html, s as store_get, a as attr, u as unsubscribe_stores, i as derived } from "../../../chunks/index.js";
import { o as onDestroy } from "../../../chunks/index-server.js";
import { t } from "../../../chunks/index2.js";
function EventLogFilter($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const {
severities,
sources,
dateRange,
searchText,
stats
} = $$props;
const allSeverities = ["info", "warn", "error"];
const allSources = ["deploy", "container", "proxy", "system"];
const dateRangeOptions = [
{ value: "1h", labelKey: "events.filter.lastHour" },
{ value: "24h", labelKey: "events.filter.last24h" },
{ value: "7d", labelKey: "events.filter.last7d" },
{ value: "all", labelKey: "events.filter.allTime" }
];
const severityStyles = {
info: {
active: "bg-blue-100 text-blue-700 ring-1 ring-blue-300 dark:bg-blue-900/40 dark:text-blue-300 dark:ring-blue-700",
inactive: "bg-[var(--surface-card-hover)] text-[var(--text-tertiary)] line-through decoration-1",
dot: "bg-blue-500"
},
warn: {
active: "bg-amber-100 text-amber-700 ring-1 ring-amber-300 dark:bg-amber-900/40 dark:text-amber-300 dark:ring-amber-700",
inactive: "bg-[var(--surface-card-hover)] text-[var(--text-tertiary)] line-through decoration-1",
dot: "bg-amber-500"
},
error: {
active: "bg-red-100 text-red-700 ring-1 ring-red-300 dark:bg-red-900/40 dark:text-red-300 dark:ring-red-700",
inactive: "bg-[var(--surface-card-hover)] text-[var(--text-tertiary)] line-through decoration-1",
dot: "bg-red-500"
}
};
const activeFilterCount = derived(() => (severities.length < allSeverities.length ? 1 : 0) + (sources.length < allSources.length ? 1 : 0) + 0 + (searchText.trim() !== "" ? 1 : 0));
$$renderer2.push(`<div class="space-y-3"><div class="flex items-center gap-2 flex-wrap"><!--[-->`);
const each_array = ensure_array_like(allSeverities);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let sev = each_array[$$index];
const active = severities.includes(sev);
const style = severityStyles[sev];
$$renderer2.push(`<button type="button"${attr_class(`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-all duration-150 select-none cursor-pointer ${stringify(active ? style.active : style.inactive)}`)}><span${attr_class(`h-1.5 w-1.5 rounded-full ${stringify(active ? style.dot : "bg-[var(--text-tertiary)] opacity-40")}`)}></span> ${escape_html(store_get($$store_subs ??= {}, "$t", t)(`events.severity.${sev}`))} `);
if (stats) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<span${attr_class(`tabular-nums font-semibold ${stringify(active ? "" : "opacity-50")}`)}>${escape_html(stats[sev] ?? 0)}</span>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></button>`);
}
$$renderer2.push(`<!--]--> <div class="h-4 w-px bg-[var(--border-primary)] mx-0.5 hidden sm:block"></div> <!--[-->`);
const each_array_1 = ensure_array_like(allSources);
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
let src = each_array_1[$$index_1];
const active = sources.includes(src);
$$renderer2.push(`<button type="button"${attr_class(`inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-medium transition-all duration-150 select-none cursor-pointer ${stringify(active ? "bg-[var(--surface-card)] text-[var(--text-primary)] ring-1 ring-[var(--border-primary)] shadow-[var(--shadow-sm)]" : "bg-[var(--surface-card-hover)] text-[var(--text-tertiary)] line-through decoration-1")}`)}>${escape_html(store_get($$store_subs ??= {}, "$t", t)(`events.source.${src}`))}</button>`);
}
$$renderer2.push(`<!--]--> <div class="flex-1"></div> `);
if (activeFilterCount() > 0) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<button type="button" class="inline-flex items-center gap-1 rounded-full px-2 py-1 text-xs font-medium text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--surface-card-hover)] transition-colors"><svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("events.filter.clear"))}</button>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></div> <div class="flex items-center gap-2 flex-wrap"><div class="inline-flex items-center rounded-lg bg-[var(--surface-card-hover)] p-0.5"><!--[-->`);
const each_array_2 = ensure_array_like(dateRangeOptions);
for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
let opt = each_array_2[$$index_2];
$$renderer2.push(`<button type="button"${attr_class(`rounded-md px-2.5 py-1 text-xs font-medium transition-all duration-150 ${stringify(dateRange === opt.value ? "bg-[var(--surface-card)] text-[var(--text-primary)] shadow-[var(--shadow-sm)]" : "text-[var(--text-tertiary)] hover:text-[var(--text-secondary)]")}`)}>${escape_html(store_get($$store_subs ??= {}, "$t", t)(opt.labelKey))}</button>`);
}
$$renderer2.push(`<!--]--></div> <div class="relative flex-1 min-w-[180px] max-w-sm"><svg class="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--text-tertiary)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg> <input type="text"${attr("placeholder", store_get($$store_subs ??= {}, "$t", t)("events.filter.search"))}${attr("value", searchText)} class="w-full rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] py-1.5 pl-8 pr-3 text-xs text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-400)] focus:outline-none focus:ring-1 focus:ring-[var(--color-brand-400)] transition-colors"/></div></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let stats = { info: 0, warn: 0, error: 0, total: 0 };
let pendingNewEvents = [];
let severities = ["info", "warn", "error"];
let sources = ["deploy", "container", "proxy", "system"];
let dateRange = "all";
let searchText = "";
let sseConnection = null;
onDestroy(() => {
sseConnection?.close();
sseConnection = null;
});
$$renderer2.push(`<div class="space-y-4"><div class="flex items-center justify-between"><h1 class="text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("events.title"))}</h1> `);
if (stats.total > 0) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<span class="text-xs text-[var(--text-tertiary)] tabular-nums">${escape_html(stats.total)} total</span>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></div> `);
EventLogFilter($$renderer2, {
severities,
sources,
dateRange,
searchText,
stats
});
$$renderer2.push(`<!----> `);
if (pendingNewEvents.length > 0) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<button type="button" class="w-full rounded-lg bg-[var(--color-brand-600)] px-4 py-2 text-sm font-medium text-white shadow-sm transition-all hover:bg-[var(--color-brand-700)] animate-fade-in">${escape_html(pendingNewEvents.length)} ${escape_html(store_get($$store_subs ??= {}, "$t", t)("events.newEvents"))}</button>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> `);
{
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="flex items-center justify-center py-16"><svg class="h-5 w-5 animate-spin text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg></div>`);
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,4 @@
const ssr = false;
export {
ssr
};
@@ -0,0 +1,31 @@
import { e as escape_html, s as store_get, a as attr, u as unsubscribe_stores } from "../../../chunks/index.js";
import "@sveltejs/kit/internal";
import "../../../chunks/exports.js";
import "../../../chunks/utils.js";
import "@sveltejs/kit/internal/server";
import "../../../chunks/root.js";
import "../../../chunks/state.svelte.js";
import { t } from "../../../chunks/index2.js";
import "../../../chunks/theme.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let username = "";
let password = "";
let loading = false;
$$renderer2.push(`<div class="flex min-h-screen items-center justify-center bg-[var(--surface-page)] px-4"><div class="w-full max-w-sm"><div class="rounded-2xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-8 shadow-[var(--shadow-lg)]"><div class="mb-6 text-center"><div class="mx-auto flex h-12 w-12 items-center justify-center rounded-xl bg-[var(--color-brand-600)] shadow-md"><svg class="h-6 w-6 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25"></path></svg></div> <h1 class="mt-4 text-xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.title"))}</h1> <p class="mt-1 text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.subtitle"))}</p></div> `);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> <form class="space-y-4"><div class="flex flex-col gap-1.5"><label for="username" class="text-sm font-medium text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.username"))}</label> <input id="username" type="text"${attr("value", username)} required="" autocomplete="username" class="w-full rounded-lg border border-[var(--border-input)] bg-[var(--surface-input)] px-3 py-2.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)]"/></div> <div class="flex flex-col gap-1.5"><label for="password" class="text-sm font-medium text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.password"))}</label> <input id="password" type="password"${attr("value", password)} required="" autocomplete="current-password" class="w-full rounded-lg border border-[var(--border-input)] bg-[var(--surface-input)] px-3 py-2.5 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)]"/></div> <button type="submit"${attr("disabled", loading, true)} class="w-full inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-[var(--color-brand-700)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)] focus:ring-offset-2 disabled:opacity-50 active:animate-press">`);
{
$$renderer2.push("<!--[-1-->");
$$renderer2.push(`${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.signIn"))}`);
}
$$renderer2.push(`<!--]--></button></form> <div class="mt-5"><div class="relative"><div class="absolute inset-0 flex items-center"><div class="w-full border-t border-[var(--border-primary)]"></div></div> <div class="relative flex justify-center text-xs"><span class="bg-[var(--surface-card)] px-3 text-[var(--text-tertiary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.or"))}</span></div></div> <button class="mt-4 w-full rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] px-4 py-2.5 text-sm font-medium text-[var(--text-secondary)] shadow-sm transition-all duration-150 hover:bg-[var(--surface-card-hover)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)] focus:ring-offset-2">${escape_html(store_get($$store_subs ??= {}, "$t", t)("login.ssoButton"))}</button></div></div></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,51 @@
import { h as head, e as escape_html, s as store_get, f as ensure_array_like, u as unsubscribe_stores } from "../../../../chunks/index.js";
import "@sveltejs/kit/internal";
import "../../../../chunks/exports.js";
import "../../../../chunks/utils.js";
import "@sveltejs/kit/internal/server";
import "../../../../chunks/root.js";
import "../../../../chunks/state.svelte.js";
import { t } from "../../../../chunks/index2.js";
import { S as Skeleton } from "../../../../chunks/Skeleton.js";
import "../../../../chunks/toast.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("ffmenf", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.project"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
{
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<div class="space-y-6"><div class="flex items-start justify-between"><div class="space-y-2">`);
Skeleton($$renderer3, { width: "4rem", height: "0.875rem" });
$$renderer3.push(`<!----> `);
Skeleton($$renderer3, { width: "12rem", height: "1.75rem" });
$$renderer3.push(`<!----> `);
Skeleton($$renderer3, { width: "16rem", height: "0.875rem" });
$$renderer3.push(`<!----></div></div> <div class="grid grid-cols-4 gap-4"><!--[-->`);
const each_array = ensure_array_like(Array(4));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
Skeleton($$renderer3, { height: "3rem" });
}
$$renderer3.push(`<!--]--></div></div>`);
}
$$renderer3.push(`<!--]-->`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,43 @@
import { h as head, e as escape_html, s as store_get, a as attr, g as stringify, u as unsubscribe_stores, i as derived } from "../../../../../chunks/index.js";
import { p as page } from "../../../../../chunks/stores.js";
import "../../../../../chunks/toast.js";
import { t } from "../../../../../chunks/index2.js";
import { I as IconChevronRight } from "../../../../../chunks/IconChevronRight.js";
import { S as Skeleton } from "../../../../../chunks/Skeleton.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const projectId = derived(() => store_get($$store_subs ??= {}, "$page", page).params.id);
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("1fdiw6z", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("envEditor.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer3.push(`<div class="space-y-6"><div><div class="flex items-center gap-1.5 text-sm text-[var(--text-tertiary)]"><a${attr("href", `/projects/${stringify(projectId())}`)} class="hover:text-[var(--text-link)] transition-colors">${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.project"))}</a> `);
IconChevronRight($$renderer3, { size: 14 });
$$renderer3.push(`<!----></div> <h1 class="mt-1 text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("envEditor.title"))}</h1> <p class="mt-1 text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("envEditor.description"))}</p></div> `);
{
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<div class="space-y-4">`);
Skeleton($$renderer3, { width: "16rem", height: "2.5rem" });
$$renderer3.push(`<!----> `);
Skeleton($$renderer3, { height: "12rem" });
$$renderer3.push(`<!----></div>`);
}
$$renderer3.push(`<!--]--></div>`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,80 @@
import { h as head, a as attr, g as stringify, e as escape_html, s as store_get, f as ensure_array_like, b as attr_class, u as unsubscribe_stores, i as derived } from "../../../../../chunks/index.js";
import { p as page } from "../../../../../chunks/stores.js";
import "../../../../../chunks/toast.js";
import { t } from "../../../../../chunks/index2.js";
import { I as IconChevronRight } from "../../../../../chunks/IconChevronRight.js";
import { S as Skeleton } from "../../../../../chunks/Skeleton.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let scopeInfos = [];
const projectId = derived(() => store_get($$store_subs ??= {}, "$page", page).params.id ?? "");
const scopeColors = {
instance: {
bg: "bg-amber-50 dark:bg-amber-900/30",
text: "text-amber-700 dark:text-amber-400"
},
stage: {
bg: "bg-blue-50 dark:bg-blue-900/30",
text: "text-blue-700 dark:text-blue-400"
},
project: {
bg: "bg-emerald-50 dark:bg-emerald-900/30",
text: "text-emerald-700 dark:text-emerald-400"
},
project_named: {
bg: "bg-violet-50 dark:bg-violet-900/30",
text: "text-violet-700 dark:text-violet-400"
},
named: {
bg: "bg-purple-50 dark:bg-purple-900/30",
text: "text-purple-700 dark:text-purple-400"
},
ephemeral: {
bg: "bg-gray-100 dark:bg-gray-800",
text: "text-gray-600 dark:text-gray-400"
}
};
function scopeColor(scope) {
return scopeColors[scope] ?? scopeColors.project;
}
head("15zmna7", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeEditor.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="space-y-6"><div><div class="flex items-center gap-1.5 text-sm text-[var(--text-tertiary)]"><a${attr("href", `/projects/${stringify(projectId())}`)} class="hover:text-[var(--text-link)] transition-colors">${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.project"))}</a> `);
IconChevronRight($$renderer2, { size: 14 });
$$renderer2.push(`<!----></div> <h1 class="mt-1 text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeEditor.title"))}</h1> <p class="mt-1 text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeEditor.description"))}</p></div> `);
if (scopeInfos.length > 0) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3"><!--[-->`);
const each_array = ensure_array_like(scopeInfos);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let info = each_array[$$index];
const colors = scopeColor(info.scope);
$$renderer2.push(`<div${attr_class(`rounded-lg border border-[var(--border-primary)] p-3 ${stringify(colors.bg)}`)}><div class="flex items-center gap-2"><span${attr_class(`rounded-full px-2 py-0.5 text-xs font-semibold ${stringify(colors.text)} ${stringify(colors.bg)}`)}>${escape_html(info.scope)}</span> `);
if (info.needs_name) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<span class="text-[10px] text-[var(--text-tertiary)]">(${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeEditor.requiresName"))})</span>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></div> <p class="mt-1.5 text-xs text-[var(--text-secondary)]">${escape_html(info.description)}</p> <code class="mt-1 block text-[10px] text-[var(--text-tertiary)]">${escape_html(info.path_example)}</code></div>`);
}
$$renderer2.push(`<!--]--></div>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> `);
{
$$renderer2.push("<!--[0-->");
Skeleton($$renderer2, { height: "12rem" });
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,45 @@
import { h as head, a as attr, g as stringify, e as escape_html, s as store_get, b as attr_class, f as ensure_array_like, u as unsubscribe_stores, i as derived } from "../../../../../../../chunks/index.js";
import { p as page } from "../../../../../../../chunks/stores.js";
import "../../../../../../../chunks/toast.js";
import { t } from "../../../../../../../chunks/index2.js";
import { I as IconChevronRight } from "../../../../../../../chunks/IconChevronRight.js";
import { S as Skeleton } from "../../../../../../../chunks/Skeleton.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const projectId = derived(() => store_get($$store_subs ??= {}, "$page", page).params.id ?? "");
const breadcrumbs = derived(() => () => {
return [];
});
head("8w09c1", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeBrowser.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="space-y-4"><div><div class="flex items-center gap-1.5 text-sm text-[var(--text-tertiary)]"><a${attr("href", `/projects/${stringify(projectId())}`)} class="hover:text-[var(--text-link)] transition-colors">${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.project"))}</a> `);
IconChevronRight($$renderer2, { size: 14 });
$$renderer2.push(`<!----> <a${attr("href", `/projects/${stringify(projectId())}/volumes`)} class="hover:text-[var(--text-link)] transition-colors">${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeEditor.title"))}</a> `);
IconChevronRight($$renderer2, { size: 14 });
$$renderer2.push(`<!----></div> <div class="mt-1 flex items-center justify-between"><h1 class="text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeBrowser.title"))}</h1> <div class="flex items-center gap-2"><button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-[var(--border-primary)] px-3 py-2 text-xs font-medium text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] transition-colors">📦 ${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeBrowser.downloadAll"))}</button> <label${attr_class(`inline-flex cursor-pointer items-center gap-1.5 rounded-lg bg-[var(--color-brand-600)] px-3 py-2 text-xs font-medium text-white hover:bg-[var(--color-brand-700)] transition-colors ${stringify("")}`)}>`);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("volumeBrowser.upload"))} <input type="file" multiple="" class="hidden"/></label></div></div></div> <nav class="flex items-center gap-1 text-sm"><button type="button"${attr_class(`rounded px-1.5 py-0.5 text-[var(--text-link)] hover:bg-[var(--surface-card-hover)] transition-colors ${stringify("font-semibold")}`)}>/</button> <!--[-->`);
const each_array = ensure_array_like(breadcrumbs()());
for (let i = 0, $$length = each_array.length; i < $$length; i++) {
let segment = each_array[i];
IconChevronRight($$renderer2, { size: 12, class: "text-[var(--text-tertiary)]" });
$$renderer2.push(`<!----> <button type="button"${attr_class(`rounded px-1.5 py-0.5 text-[var(--text-link)] hover:bg-[var(--surface-card-hover)] transition-colors ${stringify(i === breadcrumbs()().length - 1 ? "font-semibold text-[var(--text-primary)]" : "")}`)}>${escape_html(segment)}</button>`);
}
$$renderer2.push(`<!--]--></nav> `);
{
$$renderer2.push("<!--[0-->");
Skeleton($$renderer2, { height: "16rem" });
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,4 @@
const ssr = false;
export {
ssr
};
@@ -0,0 +1,66 @@
import { f as ensure_array_like, h as head, e as escape_html, s as store_get, b as attr_class, u as unsubscribe_stores, g as stringify } from "../../../chunks/index.js";
import { t } from "../../../chunks/index2.js";
import { I as IconPlus } from "../../../chunks/IconPlus.js";
import { S as Skeleton } from "../../../chunks/Skeleton.js";
/* empty css */
function SkeletonTable($$renderer, $$props) {
const { rows = 5, cols = 4 } = $$props;
$$renderer.push(`<div class="overflow-hidden rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] shadow-[var(--shadow-sm)]"><div class="border-b border-[var(--border-primary)] bg-[var(--surface-card-hover)] px-6 py-3"><div class="flex gap-6"><!--[-->`);
const each_array = ensure_array_like(Array(cols));
for (let i = 0, $$length = each_array.length; i < $$length; i++) {
each_array[i];
Skeleton($$renderer, { width: i === 0 ? "6rem" : "5rem", height: "0.75rem" });
}
$$renderer.push(`<!--]--></div></div> <!--[-->`);
const each_array_1 = ensure_array_like(Array(rows));
for (let i = 0, $$length = each_array_1.length; i < $$length; i++) {
each_array_1[i];
$$renderer.push(`<div class="flex gap-6 border-b border-[var(--border-secondary)] px-6 py-4 last:border-b-0"><!--[-->`);
const each_array_2 = ensure_array_like(Array(cols));
for (let j = 0, $$length2 = each_array_2.length; j < $$length2; j++) {
each_array_2[j];
Skeleton($$renderer, { width: j === 0 ? "40%" : "20%", height: "0.875rem" });
}
$$renderer.push(`<!--]--></div>`);
}
$$renderer.push(`<!--]--></div>`);
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("rqn88j", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("projects.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer3.push(`<div class="space-y-6"><div class="flex items-center justify-between"><h1 class="text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("projects.title"))}</h1> <button type="button"${attr_class(`inline-flex items-center gap-2 rounded-lg ${stringify("bg-[var(--color-brand-600)] text-white shadow-sm hover:bg-[var(--color-brand-700)]")} px-4 py-2.5 text-sm font-medium transition-all duration-150 active:animate-press`)}>`);
{
$$renderer3.push("<!--[0-->");
IconPlus($$renderer3, { size: 16 });
}
$$renderer3.push(`<!--]--> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("projects.addProject"))}</button></div> `);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--> `);
{
$$renderer3.push("<!--[0-->");
SkeletonTable($$renderer3, { rows: 4, cols: 5 });
}
$$renderer3.push(`<!--]--></div>`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,34 @@
import { h as head, e as escape_html, s as store_get, u as unsubscribe_stores } from "../../../../../chunks/index.js";
import "@sveltejs/kit/internal";
import "../../../../../chunks/exports.js";
import "../../../../../chunks/utils.js";
import "@sveltejs/kit/internal/server";
import "../../../../../chunks/root.js";
import "../../../../../chunks/state.svelte.js";
import { t } from "../../../../../chunks/index2.js";
import { I as IconGlobe } from "../../../../../chunks/IconGlobe.js";
import { I as IconLoader } from "../../../../../chunks/IconLoader.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
head("57j31g", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.editTitle"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="mb-6"><a href="/proxies" class="inline-flex items-center gap-1 text-sm text-[var(--text-secondary)] hover:text-[var(--color-brand-600)] transition-colors"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"></path><path d="m12 19-7-7 7-7"></path></svg> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.back"))}</a></div> <div class="mb-6 flex items-center gap-3"><div class="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-brand-50)] text-[var(--color-brand-600)]">`);
IconGlobe($$renderer2, { size: 22 });
$$renderer2.push(`<!----></div> <h1 class="text-xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.editTitle"))}</h1></div> `);
{
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="flex items-center justify-center py-20">`);
IconLoader($$renderer2, { size: 24, class: "text-[var(--color-brand-500)]" });
$$renderer2.push(`<!----> <span class="ml-2 text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.loading"))}</span></div>`);
}
$$renderer2.push(`<!--]-->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,35 @@
import { h as head, e as escape_html, s as store_get, u as unsubscribe_stores } from "../../../chunks/index.js";
import { t } from "../../../chunks/index2.js";
import { I as IconGlobe } from "../../../chunks/IconGlobe.js";
import { I as IconLoader } from "../../../chunks/IconLoader.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
head("88k8ar", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"><div class="flex items-center gap-3"><div class="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-brand-50)] text-[var(--color-brand-600)]">`);
IconGlobe($$renderer2, { size: 22 });
$$renderer2.push(`<!----></div> <div><h1 class="text-xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.title"))}</h1> `);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></div></div> <a href="/proxies/create" class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-[var(--color-brand-700)] active:animate-press"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path><path d="M12 5v14"></path></svg> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.create"))}</a></div> `);
{
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="flex items-center justify-center py-20">`);
IconLoader($$renderer2, {
size: 24,
class: "animate-spin text-[var(--color-brand-500)]"
});
$$renderer2.push(`<!----> <span class="ml-2 text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.loading"))}</span></div>`);
}
$$renderer2.push(`<!--]-->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,272 @@
import { e as escape_html, s as store_get, u as unsubscribe_stores, f as ensure_array_like, a as attr, i as derived, h as head } from "../../../../chunks/index.js";
import "@sveltejs/kit/internal";
import "../../../../chunks/exports.js";
import "../../../../chunks/utils.js";
import "@sveltejs/kit/internal/server";
import "../../../../chunks/root.js";
import "../../../../chunks/state.svelte.js";
import { t } from "../../../../chunks/index2.js";
import { v as validateProxy } from "../../../../chunks/api.js";
import { F as FormField } from "../../../../chunks/FormField.js";
import { I as IconCheck } from "../../../../chunks/IconCheck.js";
import { I as IconX } from "../../../../chunks/IconX.js";
import { I as IconLoader } from "../../../../chunks/IconLoader.js";
import { I as IconGlobe } from "../../../../chunks/IconGlobe.js";
function ValidationChecklist($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const { result, loading = false } = $$props;
const stepLabelKeys = {
syntax: "proxies.validation.syntax",
dns: "proxies.validation.dns",
tcp: "proxies.validation.tcp",
http: "proxies.validation.http"
};
function getStepLabel(name) {
const key = stepLabelKeys[name];
return key ? store_get($$store_subs ??= {}, "$t", t)(key) : name;
}
if (loading || result) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="rounded-lg border border-[var(--border-primary)] bg-[var(--surface-card)] p-4"><h4 class="text-sm font-medium text-[var(--text-primary)] mb-3">${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.validation.title"))}</h4> `);
if (loading && !result) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="flex items-center gap-2 text-sm text-[var(--text-secondary)]">`);
IconLoader($$renderer2, { size: 16 });
$$renderer2.push(`<!----> <span>${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.validation.checking"))}</span></div>`);
} else if (result) {
$$renderer2.push("<!--[1-->");
$$renderer2.push(`<ul class="space-y-2"><!--[-->`);
const each_array = ensure_array_like(result.steps);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let step = each_array[$$index];
$$renderer2.push(`<li><div class="flex items-center gap-2">`);
if (step.passed) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<span class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-emerald-100 dark:bg-emerald-950">`);
IconCheck($$renderer2, { size: 14, class: "text-emerald-600 dark:text-emerald-400" });
$$renderer2.push(`<!----></span> <span class="text-sm text-[var(--text-primary)]">${escape_html(getStepLabel(step.name))}</span> `);
if (step.message) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<span class="text-xs text-[var(--text-tertiary)]">— ${escape_html(step.message)}</span>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]-->`);
} else {
$$renderer2.push("<!--[-1-->");
$$renderer2.push(`<span class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-red-100 dark:bg-red-950">`);
IconX($$renderer2, { size: 14, class: "text-red-600 dark:text-red-400" });
$$renderer2.push(`<!----></span> <span class="text-sm text-[var(--text-primary)]">${escape_html(getStepLabel(step.name))}</span> `);
if (step.message) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<span class="text-xs text-[var(--text-tertiary)]">— ${escape_html(step.message)}</span>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]-->`);
}
$$renderer2.push(`<!--]--></div> `);
if (!step.passed && step.hint) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<p class="ml-7 mt-1 text-xs text-amber-600 dark:text-amber-400">${escape_html(step.hint)}</p>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></li>`);
}
$$renderer2.push(`<!--]--></ul>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--></div>`);
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]-->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function ProxyForm($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
const { proxy } = $$props;
let destinationUrl = proxy?.destination_url ?? "";
let port = proxy?.destination_port?.toString() ?? "";
let domain = proxy?.domain ?? "";
let validationResult = null;
let validating = false;
let validationTimer = null;
const portNum = derived(() => parseInt(port, 10));
const portValid = derived(() => !isNaN(portNum()) && portNum() >= 1 && portNum() <= 65535);
const canSubmit = derived(() => destinationUrl.trim().length > 0 && port.trim().length > 0 && portValid() && domain.trim().length > 0 && true);
const title = derived(
() => store_get($$store_subs ??= {}, "$t", t)("proxies.form.title")
);
const submitLabel = derived(
() => store_get($$store_subs ??= {}, "$t", t)("proxies.form.create")
);
function suggestDomain(dest) {
if (!dest) return "";
try {
const withScheme = dest.includes("://") ? dest : `http://${dest}`;
const url = new URL(withScheme);
const host = url.hostname;
const cleaned = host.replace(/^(www|api|app)\./, "").replace(/\.\w+$/, "").replace(/[^a-z0-9.-]/gi, "-").toLowerCase();
return cleaned || "";
} catch {
return dest.replace(/[^a-z0-9.-]/gi, "-").toLowerCase();
}
}
function scheduleValidation() {
if (validationTimer !== null) {
clearTimeout(validationTimer);
}
validationResult = null;
if (!destinationUrl.trim() || !port.trim() || !portValid()) {
return;
}
validationTimer = setTimeout(
() => {
runValidation();
},
300
);
}
async function runValidation() {
if (!destinationUrl.trim() || !portValid()) return;
validating = true;
try {
validationResult = await validateProxy(destinationUrl.trim(), portNum());
} catch {
validationResult = null;
} finally {
validating = false;
}
}
function handleDestinationInput() {
{
const suggested = suggestDomain(destinationUrl);
if (!domain || domain === suggestDomain(destinationUrl.slice(0, -1))) {
domain = suggested;
}
}
scheduleValidation();
}
function handlePortInput() {
scheduleValidation();
}
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
$$renderer3.push(`<div class="space-y-6"><h3 class="text-lg font-semibold text-[var(--text-primary)]">${escape_html(title())}</h3> <form class="space-y-4">`);
FormField($$renderer3, {
label: store_get($$store_subs ??= {}, "$t", t)("proxies.form.destination"),
name: "destination_url",
placeholder: "192.168.1.100 or http://my-service",
required: true,
oninput: handleDestinationInput,
get value() {
return destinationUrl;
},
set value($$value) {
destinationUrl = $$value;
$$settled = false;
}
});
$$renderer3.push(`<!----> `);
FormField($$renderer3, {
label: store_get($$store_subs ??= {}, "$t", t)("proxies.form.port"),
name: "destination_port",
type: "number",
placeholder: "8080",
required: true,
error: port && !portValid() ? store_get($$store_subs ??= {}, "$t", t)("validation.invalidPort") : "",
oninput: handlePortInput,
get value() {
return port;
},
set value($$value) {
port = $$value;
$$settled = false;
}
});
$$renderer3.push(`<!----> `);
FormField($$renderer3, {
label: store_get($$store_subs ??= {}, "$t", t)("proxies.form.domain"),
name: "domain",
placeholder: "my-service.example.com",
required: true,
helpText: store_get($$store_subs ??= {}, "$t", t)("proxies.form.domainHelp"),
get value() {
return domain;
},
set value($$value) {
domain = $$value;
$$settled = false;
}
});
$$renderer3.push(`<!----> <div class="space-y-2">`);
ValidationChecklist($$renderer3, { result: validationResult, loading: validating });
$$renderer3.push(`<!----> <button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-[var(--border-primary)] px-3 py-1.5 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"${attr("disabled", !destinationUrl.trim() || !portValid() || validating, true)}>`);
if (validating) {
$$renderer3.push("<!--[0-->");
IconLoader($$renderer3, { size: 14 });
$$renderer3.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.validating"))}`);
} else {
$$renderer3.push("<!--[-1-->");
$$renderer3.push(`${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.validate"))}`);
}
$$renderer3.push(`<!--]--></button></div> `);
if (validationResult && !validationResult.valid) {
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<p class="text-xs text-amber-600 dark:text-amber-400">Validation reported issues but you can still create the proxy.</p>`);
} else {
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--> `);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--> <div class="flex items-center justify-between pt-2"><div>`);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--></div> <div class="flex items-center gap-3"><button type="button" class="rounded-lg px-4 py-2 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] transition-colors">${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.cancel"))}</button> <button type="submit" class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-[var(--color-brand-700)] transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-brand-600)] disabled:opacity-50 disabled:cursor-not-allowed"${attr("disabled", !canSubmit(), true)}>`);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--> ${escape_html(submitLabel())}</button></div></div></form></div> `);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]-->`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
head("1eyu80y", $$renderer2, ($$renderer3) => {
$$renderer3.title(($$renderer4) => {
$$renderer4.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer2.push(`<div class="mb-6"><a href="/proxies" class="inline-flex items-center gap-1 text-sm text-[var(--text-secondary)] hover:text-[var(--color-brand-600)] transition-colors"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5"></path><path d="m12 19-7-7 7-7"></path></svg> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("common.back"))}</a></div> <div class="mb-6 flex items-center gap-3"><div class="flex h-10 w-10 items-center justify-center rounded-xl bg-[var(--color-brand-50)] text-[var(--color-brand-600)]">`);
IconGlobe($$renderer2, { size: 22 });
$$renderer2.push(`<!----></div> <h1 class="text-xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("proxies.form.title"))}</h1></div> <div class="mx-auto max-w-2xl rounded-2xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]">`);
ProxyForm($$renderer2, {});
$$renderer2.push(`<!----></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,90 @@
import { a as attr, b as attr_class, c as clsx, e as escape_html, s as store_get, f as ensure_array_like, g as stringify, u as unsubscribe_stores, i as derived } from "../../../chunks/index.js";
import { p as page } from "../../../chunks/stores.js";
import { t } from "../../../chunks/index2.js";
import { I as IconSettings } from "../../../chunks/IconSettings.js";
function IconKey($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><circle cx="7.5" cy="15.5" r="5.5"></circle><path d="m21 2-9.3 9.3"></path><path d="M18.5 5.5 21 3"></path><path d="m15 8 2.5 2.5"></path></svg>`);
}
function IconShield($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"></path></svg>`);
}
function IconDatabase($$renderer, $$props) {
const { size = 20, class: c = "" } = $$props;
$$renderer.push(`<svg xmlns="http://www.w3.org/2000/svg"${attr("width", size)}${attr("height", size)} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"${attr_class(clsx(c))} aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14a9 3 0 0 0 18 0V5"></path><path d="M3 12a9 3 0 0 0 18 0"></path></svg>`);
}
function _layout($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let { children } = $$props;
const navItems = [
{
href: "/settings",
labelKey: "settings.general",
icon: "general"
},
{
href: "/settings/registries",
labelKey: "settings.registries",
icon: "registries"
},
{
href: "/settings/credentials",
labelKey: "settings.credentials",
icon: "credentials"
},
{
href: "/settings/auth",
labelKey: "settings.authentication",
icon: "auth"
}
];
let currentPath = derived(() => store_get($$store_subs ??= {}, "$page", page).url.pathname);
function isActive(href) {
if (href === "/settings") return currentPath() === "/settings";
return currentPath().startsWith(href);
}
$$renderer2.push(`<div class="mx-auto max-w-4xl"><h1 class="mb-6 text-2xl font-bold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settings.title"))}</h1> <div class="flex flex-col gap-6 sm:flex-row"><nav class="w-full flex-shrink-0 sm:w-48"><ul class="flex gap-1 overflow-x-auto sm:flex-col sm:space-y-0.5"><!--[-->`);
const each_array = ensure_array_like(navItems);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
$$renderer2.push(`<li><a${attr("href", item.href)}${attr_class(`flex items-center gap-2.5 whitespace-nowrap rounded-lg px-3 py-2 text-sm font-medium transition-all duration-150 ${stringify(isActive(item.href) ? "bg-[var(--color-brand-50)] text-[var(--color-brand-700)] shadow-sm" : "text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)]")}`)}>`);
if (item.icon === "general") {
$$renderer2.push("<!--[0-->");
IconSettings($$renderer2, {
size: 16,
class: isActive(item.href) ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)]"
});
} else if (item.icon === "registries") {
$$renderer2.push("<!--[1-->");
IconDatabase($$renderer2, {
size: 16,
class: isActive(item.href) ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)]"
});
} else if (item.icon === "credentials") {
$$renderer2.push("<!--[2-->");
IconKey($$renderer2, {
size: 16,
class: isActive(item.href) ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)]"
});
} else if (item.icon === "auth") {
$$renderer2.push("<!--[3-->");
IconShield($$renderer2, {
size: 16,
class: isActive(item.href) ? "text-[var(--color-brand-600)]" : "text-[var(--text-tertiary)]"
});
} else {
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> ${escape_html(store_get($$store_subs ??= {}, "$t", t)(item.labelKey))}</a></li>`);
}
$$renderer2.push(`<!--]--></ul></nav> <div class="flex-1 min-w-0">`);
children($$renderer2);
$$renderer2.push(`<!----></div></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_layout as default
};
@@ -0,0 +1,70 @@
import { h as head, e as escape_html, s as store_get, f as ensure_array_like, u as unsubscribe_stores } from "../../../chunks/index.js";
import { E as EntityPicker } from "../../../chunks/EntityPicker.js";
import "../../../chunks/toast.js";
import { t } from "../../../chunks/index2.js";
import { S as Skeleton } from "../../../chunks/Skeleton.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let sslCertificateId = 0;
let certPickerOpen = false;
let certPickerItems = [];
function handleCertSelect(value) {
const id = parseInt(value, 10);
sslCertificateId = id;
const item = certPickerItems.find((i) => i.value === value);
item?.label ?? "";
certPickerOpen = false;
}
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("1i19ct2", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsGeneral.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer3.push(`<div class="space-y-6">`);
{
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<div class="space-y-4">`);
Skeleton($$renderer3, { height: "2rem", width: "12rem" });
$$renderer3.push(`<!----> <div class="grid grid-cols-2 gap-4"><!--[-->`);
const each_array = ensure_array_like(Array(6));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
Skeleton($$renderer3, { height: "4rem" });
}
$$renderer3.push(`<!--]--></div></div>`);
}
$$renderer3.push(`<!--]--></div> `);
EntityPicker($$renderer3, {
items: certPickerItems,
current: String(sslCertificateId),
title: store_get($$store_subs ??= {}, "$t", t)("settingsGeneral.selectCertificate"),
onselect: handleCertSelect,
onclose: () => {
certPickerOpen = false;
},
get open() {
return certPickerOpen;
},
set open($$value) {
certPickerOpen = $$value;
$$settled = false;
}
});
$$renderer3.push(`<!---->`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,134 @@
import { e as escape_html, a as attr, s as store_get, f as ensure_array_like, b as attr_class, g as stringify, u as unsubscribe_stores } from "../../../../chunks/index.js";
import { t } from "../../../../chunks/index2.js";
import { I as IconTrash } from "../../../../chunks/IconTrash.js";
import { I as IconPlus } from "../../../../chunks/IconPlus.js";
function EmptyState($$renderer, $$props) {
const {
title,
description = "",
actionLabel = "",
actionHref = "",
onaction,
icon = "projects"
} = $$props;
$$renderer.push(`<div class="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-[var(--border-primary)] px-6 py-16 text-center animate-fade-in"><div class="mb-4 flex h-16 w-16 items-center justify-center rounded-2xl bg-[var(--color-brand-50)]">`);
if (icon === "projects") {
$$renderer.push("<!--[0-->");
$$renderer.push(`<svg class="h-8 w-8 text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path><path d="M12 10v6"></path><path d="M9 13h6"></path></svg>`);
} else if (icon === "instances") {
$$renderer.push("<!--[1-->");
$$renderer.push(`<svg class="h-8 w-8 text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect><rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect><line x1="6" x2="6.01" y1="6" y2="6"></line><line x1="6" x2="6.01" y1="18" y2="18"></line></svg>`);
} else if (icon === "deploys") {
$$renderer.push("<!--[2-->");
$$renderer.push(`<svg class="h-8 w-8 text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15.59 14.37a6 6 0 0 1-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 0 0 6.16-12.12A14.98 14.98 0 0 0 9.631 8.41m5.96 5.96a14.926 14.926 0 0 1-5.841 2.58M16.5 9a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"></path></svg>`);
} else if (icon === "registries") {
$$renderer.push("<!--[3-->");
$$renderer.push(`<svg class="h-8 w-8 text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14a9 3 0 0 0 18 0V5"></path><path d="M3 12a9 3 0 0 0 18 0"></path></svg>`);
} else if (icon === "volumes") {
$$renderer.push("<!--[4-->");
$$renderer.push(`<svg class="h-8 w-8 text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" x2="2" y1="12" y2="12"></line><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path></svg>`);
} else if (icon === "users") {
$$renderer.push("<!--[5-->");
$$renderer.push(`<svg class="h-8 w-8 text-[var(--color-brand-500)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>`);
} else {
$$renderer.push("<!--[-1-->");
}
$$renderer.push(`<!--]--></div> <h3 class="text-base font-semibold text-[var(--text-primary)]">${escape_html(title)}</h3> `);
if (description) {
$$renderer.push("<!--[0-->");
$$renderer.push(`<p class="mt-1 max-w-sm text-sm text-[var(--text-secondary)]">${escape_html(description)}</p>`);
} else {
$$renderer.push("<!--[-1-->");
}
$$renderer.push(`<!--]--> `);
if (actionLabel) {
$$renderer.push("<!--[0-->");
if (actionHref) {
$$renderer.push("<!--[0-->");
$$renderer.push(`<a${attr("href", actionHref)} class="mt-4 inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-[var(--color-brand-700)] active:animate-press"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path><path d="M12 5v14"></path></svg> ${escape_html(actionLabel)}</a>`);
} else if (onaction) {
$$renderer.push("<!--[1-->");
$$renderer.push(`<button type="button" class="mt-4 inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-[var(--color-brand-700)] active:animate-press"><svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"></path><path d="M12 5v14"></path></svg> ${escape_html(actionLabel)}</button>`);
} else {
$$renderer.push("<!--[-1-->");
}
$$renderer.push(`<!--]-->`);
} else {
$$renderer.push("<!--[-1-->");
}
$$renderer.push(`<!--]--></div>`);
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let settings = {
auth_mode: "local"
};
let users = [];
let saving = false;
let newUsername = "";
let newPassword = "";
let newEmail = "";
let newRole = "viewer";
$$renderer2.push(`<div class="space-y-6"><div><h2 class="text-lg font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.title"))}</h2> <p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.description"))}</p></div> `);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> `);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> <div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]"><h3 class="text-base font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.authMode"))}</h3> <div class="mt-4 flex gap-4"><label class="flex items-center gap-2 cursor-pointer"><input type="radio"${attr("checked", settings.auth_mode === "local", true)} value="local" class="h-4 w-4 text-[var(--color-brand-600)] focus:ring-[var(--color-brand-500)]"/> <span class="text-sm font-medium text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.local"))}</span></label> <label class="flex items-center gap-2 cursor-pointer"><input type="radio"${attr("checked", settings.auth_mode === "oidc", true)} value="oidc" class="h-4 w-4 text-[var(--color-brand-600)] focus:ring-[var(--color-brand-500)]"/> <span class="text-sm font-medium text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.oidc"))}</span></label></div></div> `);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> <button${attr("disabled", saving, true)} class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-[var(--color-brand-700)] disabled:opacity-50 transition-colors active:animate-press">`);
{
$$renderer2.push("<!--[-1-->");
}
$$renderer2.push(`<!--]--> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.saveSettings"))}</button> <div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-6 shadow-[var(--shadow-sm)]"><h3 class="text-base font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.localUsers"))}</h3> `);
if (users.length > 0) {
$$renderer2.push("<!--[0-->");
$$renderer2.push(`<div class="mt-4 overflow-hidden rounded-xl border border-[var(--border-primary)]"><table class="min-w-full divide-y divide-[var(--border-primary)]"><thead class="bg-[var(--surface-card-hover)]"><tr><th class="px-4 py-2.5 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.username"))}</th><th class="px-4 py-2.5 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.email"))}</th><th class="px-4 py-2.5 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.role"))}</th><th class="px-4 py-2.5 text-left text-xs font-medium uppercase text-[var(--text-tertiary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.created"))}</th><th class="px-4 py-2.5"></th></tr></thead><tbody class="divide-y divide-[var(--border-secondary)]"><!--[-->`);
const each_array_1 = ensure_array_like(users);
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
let user = each_array_1[$$index_1];
$$renderer2.push(`<tr class="hover:bg-[var(--surface-card-hover)] transition-colors"><td class="px-4 py-2.5 text-sm text-[var(--text-primary)]">${escape_html(user.username)}</td><td class="px-4 py-2.5 text-sm text-[var(--text-secondary)]">${escape_html(user.email || "-")}</td><td class="px-4 py-2.5"><span${attr_class(`inline-flex rounded-full px-2 py-0.5 text-xs font-semibold ${stringify(user.role === "admin" ? "bg-purple-50 text-purple-700" : "bg-gray-100 text-gray-700")}`)}>${escape_html(user.role)}</span></td><td class="px-4 py-2.5 text-sm text-[var(--text-secondary)]">${escape_html(user.created_at)}</td><td class="px-4 py-2.5 text-right"><button class="rounded-lg p-1.5 text-[var(--text-tertiary)] hover:bg-red-50 hover:text-red-600 transition-colors">`);
IconTrash($$renderer2, { size: 16 });
$$renderer2.push(`<!----></button></td></tr>`);
}
$$renderer2.push(`<!--]--></tbody></table></div>`);
} else {
$$renderer2.push("<!--[-1-->");
$$renderer2.push(`<div class="mt-4">`);
EmptyState($$renderer2, {
title: store_get($$store_subs ??= {}, "$t", t)("empty.noUsers"),
description: store_get($$store_subs ??= {}, "$t", t)("empty.noUsersDesc"),
icon: "users"
});
$$renderer2.push(`<!----></div>`);
}
$$renderer2.push(`<!--]--> <div class="mt-6 border-t border-[var(--border-primary)] pt-4"><h4 class="text-sm font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.addUser"))}</h4> <div class="mt-3 grid grid-cols-2 gap-3"><input type="text"${attr("value", newUsername)}${attr("placeholder", store_get($$store_subs ??= {}, "$t", t)("settingsAuth.username"))} class="rounded-lg border border-[var(--border-input)] bg-[var(--surface-input)] px-3 py-2 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)]"/> <input type="password"${attr("value", newPassword)}${attr("placeholder", store_get($$store_subs ??= {}, "$t", t)("settingsAuth.password"))} class="rounded-lg border border-[var(--border-input)] bg-[var(--surface-input)] px-3 py-2 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)]"/> <input type="email"${attr("value", newEmail)}${attr("placeholder", `${stringify(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.email"))} (optional)`)} class="rounded-lg border border-[var(--border-input)] bg-[var(--surface-input)] px-3 py-2 text-sm text-[var(--text-primary)] placeholder:text-[var(--text-tertiary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)]"/> `);
$$renderer2.select(
{
value: newRole,
class: "rounded-lg border border-[var(--border-input)] bg-[var(--surface-input)] px-3 py-2 text-sm text-[var(--text-primary)] focus:border-[var(--color-brand-500)] focus:outline-none focus:ring-2 focus:ring-[var(--color-brand-500)]"
},
($$renderer3) => {
$$renderer3.option({ value: "viewer" }, ($$renderer4) => {
$$renderer4.push(`${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.viewer"))}`);
});
$$renderer3.option({ value: "admin" }, ($$renderer4) => {
$$renderer4.push(`${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.admin"))}`);
});
}
);
$$renderer2.push(`</div> <button class="mt-3 inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-[var(--color-brand-700)] transition-colors active:animate-press">`);
IconPlus($$renderer2, { size: 16 });
$$renderer2.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsAuth.addUser"))}</button></div></div></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,36 @@
import { h as head, e as escape_html, s as store_get, u as unsubscribe_stores } from "../../../../chunks/index.js";
import { S as Skeleton } from "../../../../chunks/Skeleton.js";
import "../../../../chunks/toast.js";
import { t } from "../../../../chunks/index2.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("5z23cl", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsCredentials.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer3.push(`<div class="space-y-6"><div><h2 class="text-lg font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsCredentials.title"))}</h2> <p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsCredentials.description"))}</p></div> `);
{
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<div class="space-y-4">`);
Skeleton($$renderer3, { height: "12rem" });
$$renderer3.push(`<!----></div>`);
}
$$renderer3.push(`<!--]--></div>`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};
@@ -0,0 +1,52 @@
import { h as head, e as escape_html, s as store_get, f as ensure_array_like, u as unsubscribe_stores } from "../../../../chunks/index.js";
import { S as Skeleton } from "../../../../chunks/Skeleton.js";
import "../../../../chunks/toast.js";
import { t } from "../../../../chunks/index2.js";
import { I as IconPlus } from "../../../../chunks/IconPlus.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let $$settled = true;
let $$inner_renderer;
function $$render_inner($$renderer3) {
head("p19oey", $$renderer3, ($$renderer4) => {
$$renderer4.title(($$renderer5) => {
$$renderer5.push(`<title>${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsRegistries.title"))} - ${escape_html(store_get($$store_subs ??= {}, "$t", t)("app.name"))}</title>`);
});
});
$$renderer3.push(`<div class="space-y-6"><div class="flex items-center justify-between"><div><h2 class="text-lg font-semibold text-[var(--text-primary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsRegistries.title"))}</h2> <p class="text-sm text-[var(--text-secondary)]">${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsRegistries.description"))}</p></div> `);
{
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<button class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-[var(--color-brand-700)] transition-colors active:animate-press">`);
IconPlus($$renderer3, { size: 16 });
$$renderer3.push(`<!----> ${escape_html(store_get($$store_subs ??= {}, "$t", t)("settingsRegistries.addRegistry"))}</button>`);
}
$$renderer3.push(`<!--]--></div> `);
{
$$renderer3.push("<!--[-1-->");
}
$$renderer3.push(`<!--]--> `);
{
$$renderer3.push("<!--[0-->");
$$renderer3.push(`<div class="space-y-3"><!--[-->`);
const each_array = ensure_array_like(Array(2));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
Skeleton($$renderer3, { height: "5rem" });
}
$$renderer3.push(`<!--]--></div>`);
}
$$renderer3.push(`<!--]--></div>`);
}
do {
$$settled = true;
$$inner_renderer = $$renderer2.copy();
$$render_inner($$inner_renderer);
} while (!$$settled);
$$renderer2.subsume($$inner_renderer);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};