Files
tiny-forge/web/.svelte-kit/output/server/entries/pages/_layout.svelte.js
T
alexei.dolgolyov 670948f113 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
2026-04-02 14:54:15 +03:00

295 lines
20 KiB
JavaScript

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
};