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