Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fe98cd64b0 | |||
| 731d41377c |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "site-gateway",
|
||||
"version": "0.11.44",
|
||||
"version": "0.11.48",
|
||||
"private": true,
|
||||
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
diff -ruN a/package.json b/package.json
|
||||
--- a/package.json 2026-09-13 18:26:23.565353612 +0000
|
||||
+++ b/package.json 2026-09-13 18:26:23.579589390 +0000
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "site-gateway",
|
||||
- "version": "0.11.45",
|
||||
+ "version": "0.11.46",
|
||||
"private": true,
|
||||
"description": "Site Gateway: simple self-hosted website publishing, reverse proxying, and automatic HTTPS.",
|
||||
"type": "module",
|
||||
diff -ruN a/src/public/features.js b/src/public/features.js
|
||||
--- a/src/public/features.js 2026-09-13 18:26:23.584420397 +0000
|
||||
+++ b/src/public/features.js 2026-09-13 18:26:23.586550870 +0000
|
||||
@@ -126,7 +126,7 @@
|
||||
document.querySelector("#backup-list").addEventListener("click", async event => { const button = event.target.closest("[data-backup-action]"), row = button?.closest("[data-backup]"); if (!button || !row) return; const filename = row.dataset.backup; try { if (button.dataset.backupAction === "delete") { if (!await themedConfirm("Delete backup?", `This permanently removes ${filename}. It cannot be restored unless you have another copy.`, "Delete backup")) return; await api(`/api/backups/${encodeURIComponent(filename)}`, {method:"DELETE"}); } else { if (!await themedConfirm("Restore this backup?", "Current data will be replaced after a safety backup is created. Site Gateway validates the archive and can roll back if restoration fails.", "Restore backup")) return; const password = document.querySelector('#backup-settings-form [name="backupPassword"]').value; await api(`/api/backups/${encodeURIComponent(filename)}/restore`, {method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({password})}); await refresh(); } state.backups = await api("/api/backups"); renderBackups(); toast(button.dataset.backupAction === "delete" ? "Backup deleted." : "Backup restored."); } catch (error) { toast(error.message); } });
|
||||
const restoreButton = document.querySelector("#restore-defaults"), restoreCredentialsBlock = document.querySelector(".danger-credentials"); if (restoreButton && restoreCredentialsBlock && !restoreButton.closest(".restore-form")) { const restoreForm = document.createElement("form"); restoreForm.className = "danger-form restore-form"; restoreCredentialsBlock.replaceWith(restoreForm); restoreForm.append(restoreCredentialsBlock, restoreButton); } const restoreCancel = document.createElement("button"); restoreCancel.type = "button"; restoreCancel.className = "button secondary"; restoreCancel.textContent = "Cancel"; restoreCancel.id = "restore-defaults-cancel"; const restoreActions = document.createElement("div"); restoreActions.className = "danger-actions"; restoreButton.parentNode.insertBefore(restoreActions, restoreButton); restoreActions.append(restoreCancel, restoreButton); restoreCancel.addEventListener("click", () => { document.querySelector("#restore-admin-username").value = ""; document.querySelector("#restore-admin-password").value = ""; document.querySelector("#restore-confirmation").value = ""; document.querySelector("#restore-defaults-error").textContent = ""; }); document.querySelector("#factory-reset-cancel")?.addEventListener("click", () => { document.querySelector("#factory-reset-form").reset(); document.querySelector("#factory-reset-error").textContent = ""; });
|
||||
|
||||
-document.querySelectorAll("#docs-content article").forEach((article, index) => { article.id = `doc-${index}`; }); document.querySelectorAll("[data-doc-jump]").forEach(button => button.addEventListener("click", () => { const key = button.dataset.docJump; const article = [...document.querySelectorAll("#docs-content article")].find(item => item.dataset.doc.includes(key)); article?.scrollIntoView({ behavior:"smooth", block:"start" }); })); document.querySelector("#doc-search").addEventListener("input", event => { const query = event.target.value.trim().toLowerCase(), articles = [...document.querySelectorAll("#docs-content article")]; let visible = 0; articles.forEach((article, index) => { const eyebrow = (article.querySelector(".eyebrow")?.textContent || "").toLowerCase(), heading = (article.querySelector("h2")?.textContent || "").toLowerCase(), keywords = (article.dataset.doc || "").toLowerCase(), topicMatch = !query || eyebrow.includes(query) || heading.includes(query), keywordMatch = !topicMatch && keywords.includes(query), match = topicMatch || keywordMatch || article.textContent.toLowerCase().includes(query); article.classList.toggle("hidden", !match); article.style.order = query && match ? (topicMatch ? index : keywordMatch ? index + articles.length : index + articles.length * 2) : ""; if (match) visible++; }); document.querySelector("#doc-empty").classList.toggle("hidden", visible > 0); });
|
||||
+document.querySelectorAll("#docs-content article").forEach((article, index) => { article.id = `doc-${index}`; }); document.querySelectorAll("[data-doc-jump]").forEach(button => button.addEventListener("click", () => { const key = button.dataset.docJump; const article = [...document.querySelectorAll("#docs-content article")].find(item => item.dataset.doc.includes(key)); article?.scrollIntoView({ behavior:"instant", block:"start" }); })); document.querySelector("#doc-search").addEventListener("input", event => { const query = event.target.value.trim().toLowerCase(), articles = [...document.querySelectorAll("#docs-content article")]; let visible = 0, topResult = null, topOrder = Infinity; articles.forEach((article, index) => { const eyebrow = (article.querySelector(".eyebrow")?.textContent || "").toLowerCase(), heading = (article.querySelector("h2")?.textContent || "").toLowerCase(), keywords = (article.dataset.doc || "").toLowerCase(), topicMatch = !query || eyebrow.includes(query) || heading.includes(query), keywordMatch = !topicMatch && keywords.includes(query), match = topicMatch || keywordMatch || article.textContent.toLowerCase().includes(query), order = topicMatch ? index : keywordMatch ? index + articles.length : index + articles.length * 2; article.classList.toggle("hidden", !match); article.style.order = query && match ? order : ""; if (match) { visible++; if (query && order < topOrder) { topOrder = order; topResult = article; } } }); document.querySelector("#doc-empty").classList.toggle("hidden", visible > 0); if (query && topResult) topResult.scrollIntoView({ behavior:"instant", block:"start" }); });
|
||||
|
||||
document.querySelector("#proxy-dialog").addEventListener("close", () => document.querySelector("#proxy-dialog details")?.removeAttribute("open"));
|
||||
document.querySelectorAll("#proxy-form, #settings-form").forEach(form => form.elements.tls.addEventListener("change", () => { const fields = form.querySelector("#custom-certificate-fields, .custom-certificate-fields"); fields?.classList.toggle("custom-certificate-visible", form.elements.tls.value === "custom"); }));
|
||||
@@ -126,7 +126,7 @@ document.querySelector("#backup-upload").addEventListener("change", async event
|
||||
document.querySelector("#backup-list").addEventListener("click", async event => { const button = event.target.closest("[data-backup-action]"), row = button?.closest("[data-backup]"); if (!button || !row) return; const filename = row.dataset.backup; try { if (button.dataset.backupAction === "delete") { if (!await themedConfirm("Delete backup?", `This permanently removes ${filename}. It cannot be restored unless you have another copy.`, "Delete backup")) return; await api(`/api/backups/${encodeURIComponent(filename)}`, {method:"DELETE"}); } else { if (!await themedConfirm("Restore this backup?", "Current data will be replaced after a safety backup is created. Site Gateway validates the archive and can roll back if restoration fails.", "Restore backup")) return; const password = document.querySelector('#backup-settings-form [name="backupPassword"]').value; await api(`/api/backups/${encodeURIComponent(filename)}/restore`, {method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({password})}); await refresh(); } state.backups = await api("/api/backups"); renderBackups(); toast(button.dataset.backupAction === "delete" ? "Backup deleted." : "Backup restored."); } catch (error) { toast(error.message); } });
|
||||
const restoreButton = document.querySelector("#restore-defaults"), restoreCredentialsBlock = document.querySelector(".danger-credentials"); if (restoreButton && restoreCredentialsBlock && !restoreButton.closest(".restore-form")) { const restoreForm = document.createElement("form"); restoreForm.className = "danger-form restore-form"; restoreCredentialsBlock.replaceWith(restoreForm); restoreForm.append(restoreCredentialsBlock, restoreButton); } const restoreCancel = document.createElement("button"); restoreCancel.type = "button"; restoreCancel.className = "button secondary"; restoreCancel.textContent = "Cancel"; restoreCancel.id = "restore-defaults-cancel"; const restoreActions = document.createElement("div"); restoreActions.className = "danger-actions"; restoreButton.parentNode.insertBefore(restoreActions, restoreButton); restoreActions.append(restoreCancel, restoreButton); restoreCancel.addEventListener("click", () => { document.querySelector("#restore-admin-username").value = ""; document.querySelector("#restore-admin-password").value = ""; document.querySelector("#restore-confirmation").value = ""; document.querySelector("#restore-defaults-error").textContent = ""; }); document.querySelector("#factory-reset-cancel")?.addEventListener("click", () => { document.querySelector("#factory-reset-form").reset(); document.querySelector("#factory-reset-error").textContent = ""; });
|
||||
|
||||
document.querySelectorAll("#docs-content article").forEach((article, index) => { article.id = `doc-${index}`; }); document.querySelectorAll("[data-doc-jump]").forEach(button => button.addEventListener("click", () => { const key = button.dataset.docJump; const article = [...document.querySelectorAll("#docs-content article")].find(item => item.dataset.doc.includes(key)); article?.scrollIntoView({ behavior:"smooth", block:"start" }); })); document.querySelector("#doc-search").addEventListener("input", event => { const query = event.target.value.trim().toLowerCase(), articles = [...document.querySelectorAll("#docs-content article")]; let visible = 0; articles.forEach((article, index) => { const heading = (article.querySelector("h2")?.textContent || "").toLowerCase(), keywords = (article.dataset.doc || "").toLowerCase(), titleMatch = !query || heading.includes(query) || keywords.includes(query), match = titleMatch || article.textContent.toLowerCase().includes(query); article.classList.toggle("hidden", !match); article.style.order = query && match ? (titleMatch ? index : index + articles.length) : ""; if (match) visible++; }); document.querySelector("#doc-empty").classList.toggle("hidden", visible > 0); });
|
||||
document.querySelectorAll("#docs-content article").forEach((article, index) => { article.id = `doc-${index}`; }); document.querySelectorAll("[data-doc-jump]").forEach(button => button.addEventListener("click", () => { const key = button.dataset.docJump; const article = [...document.querySelectorAll("#docs-content article")].find(item => item.dataset.doc.includes(key)); article?.scrollIntoView({ behavior:"instant", block:"start" }); })); document.querySelector("#doc-search").addEventListener("input", event => { const query = event.target.value.trim().toLowerCase(), articles = [...document.querySelectorAll("#docs-content article")]; let topResult = null, topOrder = Infinity; articles.forEach((article, index) => { const eyebrow = (article.querySelector(".eyebrow")?.textContent || "").toLowerCase(), heading = (article.querySelector("h2")?.textContent || "").toLowerCase(), keywords = (article.dataset.doc || "").toLowerCase(), topicMatch = !query || eyebrow.includes(query) || heading.includes(query), keywordMatch = !topicMatch && keywords.includes(query), match = topicMatch || keywordMatch || article.textContent.toLowerCase().includes(query), order = topicMatch ? index : keywordMatch ? index + articles.length : index + articles.length * 2; article.style.order = ""; if (match && order < topOrder) { topOrder = order; topResult = article; } }); articles.forEach(article => article.classList.toggle("hidden", Boolean(query) && article !== topResult)); document.querySelector("#doc-empty").classList.toggle("hidden", Boolean(!query || topResult)); });
|
||||
|
||||
document.querySelector("#proxy-dialog").addEventListener("close", () => document.querySelector("#proxy-dialog details")?.removeAttribute("open"));
|
||||
document.querySelectorAll("#proxy-form, #settings-form").forEach(form => form.elements.tls.addEventListener("change", () => { const fields = form.querySelector("#custom-certificate-fields, .custom-certificate-fields"); fields?.classList.toggle("custom-certificate-visible", form.elements.tls.value === "custom"); }));
|
||||
|
||||
@@ -131,7 +131,7 @@ dialog{max-height:calc(100vh - 28px);overflow:auto}
|
||||
|
||||
.danger-actions{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.danger-actions .button{margin:0}
|
||||
.docs-intro{margin-bottom:22px;padding:24px;border:1px solid var(--line);border-radius:16px;background:var(--bg);color:var(--text)}.docs-intro h2{margin:0 0 10px}.docs-intro p:last-child{margin:0;color:var(--muted);max-width:850px;line-height:1.6}
|
||||
.docs-layout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:22px;align-items:start}.docs-nav{position:sticky;top:18px;height:calc(100vh - 170px);overflow:auto;display:grid;align-content:start;gap:6px;padding:14px;border:1px solid var(--line);border-radius:14px;background:var(--panel)}.docs-nav .eyebrow{margin:4px 8px 8px}.docs-nav button{padding:9px 10px;border:0;border-radius:8px;background:transparent;color:var(--muted);text-align:left;cursor:pointer;font:inherit}.docs-nav button:hover{background:rgba(98,230,167,.1);color:var(--text)}.docs #docs-content{display:grid;gap:16px}.docs #docs-content article{scroll-margin-top:18px;padding:24px;border:1px solid var(--line);border-radius:16px;background:var(--panel);line-height:1.6}.docs #docs-content article h2{margin:0 0 12px}.docs #docs-content article h3{margin:20px 0 5px;color:var(--green);font-size:.86rem}.docs #docs-content article p{color:var(--muted)}.docs .doc-search{display:block;margin-top:18px}.docs .doc-search input{margin-top:8px}@media(max-width:800px){.docs-layout{grid-template-columns:1fr}.docs-nav{position:static;display:flex;flex-wrap:wrap}.docs-nav .eyebrow{width:100%}}
|
||||
.docs-layout{display:grid;grid-template-columns:210px minmax(0,1fr);gap:22px;align-items:start}.docs-nav{position:sticky;top:18px;height:calc(100vh - 170px);overflow:auto;display:grid;align-content:start;gap:6px;padding:14px;border:1px solid var(--line);border-radius:14px;background:var(--panel)}.docs-nav .eyebrow{margin:4px 8px 8px}.docs-nav button{padding:9px 10px;border:0;border-radius:8px;background:transparent;color:var(--muted);text-align:left;cursor:pointer;font:inherit}.docs-nav button:hover{background:rgba(98,230,167,.1);color:var(--text)}.docs-nav button:focus{outline:none}.docs-nav button:focus-visible{outline:2px solid var(--green);outline-offset:-2px}.docs #docs-content{display:grid;gap:16px}.docs #docs-content article{scroll-margin-top:18px;padding:24px;border:1px solid var(--line);border-radius:16px;background:var(--panel);line-height:1.6}.docs #docs-content article h2{margin:0 0 12px}.docs #docs-content article h3{margin:20px 0 5px;color:var(--green);font-size:.86rem}.docs #docs-content article p{color:var(--muted)}.docs .doc-search{display:block;margin-top:18px}.docs .doc-search input{margin-top:8px}@media(max-width:800px){.docs-layout{grid-template-columns:1fr}.docs-nav{position:static;display:flex;flex-wrap:wrap}.docs-nav .eyebrow{width:100%}}
|
||||
.docs-intro{padding:32px 34px}.docs-search-panel{max-width:880px;margin-top:24px;padding:18px 20px;border:1px solid var(--line);border-radius:14px;background:var(--panel2)}.docs-search-panel .doc-search{margin:0}.docs-search-panel .doc-search span{display:block;margin-bottom:8px;font-weight:750;color:var(--text)}.docs-search-panel small{display:block;margin-top:8px;color:var(--muted)}.docs #docs-content article ul{margin:10px 0 0;padding-left:22px;color:var(--muted)}.docs #docs-content article li{margin:8px 0}
|
||||
.docs-intro{width:100%;box-sizing:border-box;text-align:center}.docs-intro>p:not(.eyebrow){margin-left:auto;margin-right:auto}.docs-search-panel{max-width:none;text-align:left}.docs-search-panel .doc-search input{width:100%;box-sizing:border-box}
|
||||
/* Manual uses a full-width header, then a two-column reading layout. */
|
||||
|
||||
Reference in New Issue
Block a user