chore: prep 0.1.2 release, tidy repo

This commit is contained in:
Aaron
2025-12-13 17:04:32 -05:00
parent 0c69e9bf90
commit b70f4c5f3f
43 changed files with 4259 additions and 4167 deletions

View File

@@ -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();