chore: prep 0.1.2 release, tidy repo
This commit is contained in:
@@ -1,48 +1,16 @@
|
||||
import { addService, updateService, removeService } from "./api.js";
|
||||
import { logUi } from "./diaglog.js";
|
||||
import {
|
||||
DEFAULT_SELF_SIGNED_MSG,
|
||||
isValidLink,
|
||||
normalizePath,
|
||||
validateServiceFields,
|
||||
} from "./services-helpers.js";
|
||||
|
||||
// Renders service cards and wires UI controls for add/edit/remove operations.
|
||||
// All mutations round-trip through the API then invoke onChange to refresh data.
|
||||
|
||||
let noticeModalRefs = null;
|
||||
const DEFAULT_SELF_SIGNED_MSG =
|
||||
"This service uses a self-signed certificate. Expect a browser warning; proceed only if you trust this device.";
|
||||
|
||||
function isValidLink(str) {
|
||||
if (!str) return true; // empty is allowed
|
||||
try {
|
||||
const u = new URL(str);
|
||||
return u.protocol === "http:" || u.protocol === "https:";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePath(path) {
|
||||
if (!path) return "";
|
||||
const trimmed = path.trim();
|
||||
if (!trimmed) return "";
|
||||
if (/\s/.test(trimmed)) return null; // no spaces or tabs in paths
|
||||
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return null;
|
||||
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
||||
}
|
||||
|
||||
function validateServiceFields({ name, port, path, notice, notice_link }, setMsg, toast) {
|
||||
const fail = (m) => {
|
||||
setMsg("");
|
||||
toast?.(m, "error");
|
||||
return false;
|
||||
};
|
||||
if (!name || name.trim().length < 2) return fail("Name must be at least 2 characters.");
|
||||
if (name.length > 48) return fail("Name is too long (max 48 chars).");
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) return fail("Port must be 1-65535.");
|
||||
if (path === null) return fail("Path must be relative (e.g. /admin) or blank.");
|
||||
if (path.length > 200) return fail("Path is too long (max 200 chars).");
|
||||
if (notice && notice.length > 180) return fail("Notice text too long (max 180 chars).");
|
||||
if (notice_link && notice_link.length > 200) return fail("Notice link too long (max 200 chars).");
|
||||
if (!isValidLink(notice_link)) return fail("Enter a valid URL (http/https) or leave blank.");
|
||||
return true;
|
||||
}
|
||||
|
||||
function ensureNoticeModal() {
|
||||
if (noticeModalRefs) return noticeModalRefs;
|
||||
const modal = document.createElement("div");
|
||||
@@ -264,6 +232,7 @@ export function initServiceControls({ gridEl, menu, addForm, onChange, overlay,
|
||||
async function menuAction(action, body = {}) {
|
||||
if (!menuContext) return;
|
||||
msg.textContent = "";
|
||||
const original = { ...menuContext };
|
||||
try {
|
||||
const isRemove = action === "remove";
|
||||
const isSave = action === "save";
|
||||
@@ -285,6 +254,17 @@ export function initServiceControls({ gridEl, menu, addForm, onChange, overlay,
|
||||
}
|
||||
msg.textContent = "";
|
||||
toast?.(isRemove ? "Service removed" : "Service saved", "success");
|
||||
logUi(isRemove ? "Service removed" : "Service updated", "info", {
|
||||
name: body.name || original.name,
|
||||
port_from: original.port,
|
||||
port_to: body.new_port || original.port,
|
||||
scheme_from: original.scheme,
|
||||
scheme_to: body.scheme || original.scheme,
|
||||
path_from: original.path,
|
||||
path_to: body.path ?? original.path,
|
||||
notice_changed: body.notice !== undefined,
|
||||
self_signed: body.self_signed,
|
||||
});
|
||||
modal?.classList.add("hidden");
|
||||
menuContext = null;
|
||||
await onChange?.();
|
||||
@@ -292,6 +272,12 @@ export function initServiceControls({ gridEl, menu, addForm, onChange, overlay,
|
||||
const err = e.error || "Action failed.";
|
||||
msg.textContent = "";
|
||||
toast?.(err, "error");
|
||||
logUi("Service update failed", "error", {
|
||||
action,
|
||||
name: body.name || original.name,
|
||||
port: original.port,
|
||||
reason: err,
|
||||
});
|
||||
} finally {
|
||||
hideBusy();
|
||||
}
|
||||
@@ -310,8 +296,7 @@ export function initServiceControls({ gridEl, menu, addForm, onChange, overlay,
|
||||
if (
|
||||
!validateServiceFields(
|
||||
{ name, port: new_port, path, notice, notice_link },
|
||||
() => {},
|
||||
toast,
|
||||
(m) => toast?.(m, "error"),
|
||||
)
|
||||
)
|
||||
return;
|
||||
@@ -335,11 +320,7 @@ export function initServiceControls({ gridEl, menu, addForm, onChange, overlay,
|
||||
const notice_link = (addNoticeLinkInput?.value || "").trim();
|
||||
const self_signed = !!addSelfSignedInput?.checked;
|
||||
if (
|
||||
!validateServiceFields(
|
||||
{ name, port, path, notice, notice_link },
|
||||
() => {},
|
||||
toast,
|
||||
)
|
||||
!validateServiceFields({ name, port, path, notice, notice_link }, (m) => toast?.(m, "error"))
|
||||
)
|
||||
return;
|
||||
addBtn.disabled = true;
|
||||
@@ -348,11 +329,21 @@ export function initServiceControls({ gridEl, menu, addForm, onChange, overlay,
|
||||
await addService({ name, port, scheme, path, notice, notice_link, self_signed });
|
||||
addMsg.textContent = "";
|
||||
toast?.("Service added", "success");
|
||||
logUi("Service added", "info", {
|
||||
name,
|
||||
port,
|
||||
scheme,
|
||||
path,
|
||||
notice: !!notice,
|
||||
notice_link: !!notice_link,
|
||||
self_signed,
|
||||
});
|
||||
await onChange?.();
|
||||
} catch (e) {
|
||||
const err = e.error || "Failed to add.";
|
||||
addMsg.textContent = "";
|
||||
toast?.(err, "error");
|
||||
logUi("Service add failed", "error", { name, port, scheme, reason: err });
|
||||
} finally {
|
||||
addBtn.disabled = false;
|
||||
hideBusy();
|
||||
|
||||
Reference in New Issue
Block a user