This commit is contained in:
Mateusz Gruszczyński committed 2026-10-13 23:24:14 +02:00
1 parent 8b9ca4db44
commit 00218a72df
35 files changed
+790 -870

No files matched your search

+4 -3
View File
@@ -2,7 +2,7 @@
"openapi": "3.0.3",
"info": {
"title": "RustProxy Administration API",
"version": "0.32.4",
"version": "0.32.5",
"description": "Administration API for the active panel origin. Sign in through the panel; the same-origin HttpOnly cookie authenticates all private operations. Mutations also require the exact Origin, X-RustProxy-Request: 1 and a session-bound X-CSRF-Token. Loopback HTTP is the default; Settings can enable HTTPS before using a non-loopback administration bind. This document contains no live configuration or credentials. Object schemas marked extensible are summaries: full configuration validation is authoritative in Rust."
},
"servers": [
@@ -251,7 +251,8 @@
"type": "object",
"properties": {
"network": {
"type": "string"
"type": "string",
"description": "Client IPv4/IPv6 address, CIDR, or allow-only DNS/DDNS hostname (A/AAAA refreshed every 60s; failed lookups deny the hostname)."
},
"action": {
"type": "string",
@@ -11871,4 +11872,4 @@
}
}
}
}
}
+22 -2
View File
@@ -222,6 +222,7 @@
const addButton = $("acl-profile-rule-add"), importButton = $("acl-profile-import-rules");
const previewDefault = $("acl-profile-preview-default"), previewFlow = $("acl-profile-preview-flow"), previewNote = $("acl-profile-preview-note");
const countryHelp = $("acl-profile-country-help"), countryLabel = $("acl-profile-country-label"), ipHelp = $("acl-profile-ip-help");
const dnsHint = i18n("p", "aclDnsHelp", "fine"); ipHelp?.after(dnsHint);
const modeInputs = [...form.querySelectorAll('input[name="acl-profile-mode"]')];
const i18n = (tag, key, cls) => { const node = el(tag, t(key), cls); node.dataset.i18n = key; return node; };
const selectedMode = () => modeInputs.find(node => node.checked)?.value || "allow_exceptions";
@@ -240,6 +241,13 @@
if (network.includes(":")) return `${network}/128`;
return network;
}
const ruleKind = value => /[a-z]/i.test(value) && value.includes(".") && !value.includes(":") && !value.includes("/") ? "dns" : "ip";
function syncRuleKind(row) {
const kind = row.querySelector(".acl-profile-rule-type").value;
const network = row.querySelector(".acl-profile-rule-network");
network.maxLength = kind === "dns" ? 253 : 64;
network.placeholder = kind === "dns" ? "office.example.ddns.net" : "10.0.0.0/8 or 203.0.113.5";
}
function addRule(rule = null, mark = true) {
if (ruleRows.children.length >= 128) { toast(t("vhRowLimit"), true); return; }
const mode = selectedMode(), fallbackAction = mode === "custom" ? "allow" : guidedAction(mode);
@@ -250,11 +258,18 @@
for (const actionValue of ["allow", "deny"]) { const option = el("option", t(actionValue === "allow" ? "aclAllow" : "vhDeny")); option.value = actionValue; actionSelect.append(option); }
actionSelect.value = value.action || fallbackAction; actionLabel.append(actionSelect);
const actionBadge = el("span", "", "acl-profile-rule-action-badge"); actionLabel.append(actionBadge);
const typeLabel = el("label"); typeLabel.append(i18n("span", "aclRuleType"));
const kind = el("select"); kind.className = "acl-profile-rule-type";
for (const [id, key] of [["ip", "aclRuleIp"], ["dns", "aclRuleDns"]]) {
const item = i18n("option", key); item.value = id; kind.append(item);
}
kind.value = ruleKind(value.network || ""); typeLabel.append(kind);
const networkLabel = el("label"); networkLabel.append(i18n("span", "aclNetwork"));
const network = el("input"); network.className = "acl-profile-rule-network"; network.type = "text"; network.required = true; network.maxLength = 64; network.autocomplete = "off"; network.spellcheck = false; network.placeholder = "10.0.0.0/8 or 203.0.113.5"; network.value = value.network || ""; networkLabel.append(network);
const network = el("input"); network.className = "acl-profile-rule-network"; network.type = "text"; network.required = true; network.autocomplete = "off"; network.spellcheck = false; network.value = value.network || ""; networkLabel.append(network);
network.addEventListener("blur", () => { const normalized = normalizeNetwork(network.value); if (normalized !== network.value) { network.value = normalized; syncRulesText(); renderPreview(); } });
kind.addEventListener("change", () => { network.value = ""; syncRuleKind(row); syncRulesText(); dirty = true; syncPolicy(); network.focus(); });
const remove = action(t("remove"), () => { row.remove(); syncRulesText(); dirty = true; syncPolicy(); }); remove.classList.add("acl-profile-rule-remove");
row.append(actionLabel, networkLabel, remove); ruleRows.append(row);
row.append(actionLabel, typeLabel, networkLabel, remove); ruleRows.append(row); syncRuleKind(row);
if (mark) { syncRulesText(); dirty = true; }
syncRuleRows();
}
@@ -370,6 +385,9 @@
function refreshTranslations() {
$("acl-profile-editing").textContent = t(editing ? "aclProfileEdit" : "aclProfileNew");
for (const row of ruleRows.children) {
const network = row.querySelector(".acl-profile-rule-network");
const kind = row.querySelector(".acl-profile-rule-type");
if (kind) { kind.value = ruleKind(network?.value || ""); syncRuleKind(row); }
const select = row.querySelector(".acl-profile-rule-action");
if (select?.options?.length >= 2) {
select.options[0].textContent = t("aclAllow");
@@ -433,6 +451,8 @@
}
const finalMode = selectedMode(), expectedAction = finalMode === "custom" ? null : guidedAction(finalMode);
const aclRules = collectRuleRows().map(rule => ({ ...rule, action: expectedAction || rule.action }));
if (aclRules.some(rule => rule.action === "deny" && ruleKind(rule.network) === "dns"))
throw fieldError(ruleRows, t("aclDnsAllowOnly"));
if (aclRules.some(rule => !rule.network)) { const input = [...ruleRows.querySelectorAll(".acl-profile-rule-network")].find(node => !node.value.trim()); throw fieldError(input || rules, t("ruleSyntax")); }
for (const [row, rule] of [...ruleRows.children].map((row, index) => [row, aclRules[index]])) row.querySelector(".acl-profile-rule-network").value = rule.network;
const finalDefault = finalMode === "custom" ? aclDefault.value : guidedDefault(finalMode);
+183 -679
View File
File diff suppressed because it is too large. Load diff