D1 Part 2: settings.html split into four pages + sidebar restructure
- settings.html (1451 lines) deleted; sections moved verbatim into
settings_system.html (Rendszer konfiguráció, Verzió és frissítés,
Vezérlő/Kiszolgáló újraindítása + update/restart JS),
settings_notifications.html (Értesítések, Alkalmazás-email),
settings_security.html (Jelszó módosítás, Földrajzi korlátozás + geo
JS, Vészhelyzeti információk — heading + section copy accents fixed),
storage.html (Adattárolók, NAS, migrate progress, agent view + all
storage JS; wizard entry links now /storage/init|attach with sprite
icons instead of emoji). The NAS + migrate sections were nested inside
{{if .StoragePaths}} in the monolith and vanished with zero drives —
now unconditional on /storage.
- layout.html: Tárhely main-nav item (hard-drive icon) + the
'Beállítások' sidebar group with Rendszer / Értesítések / Biztonság és
hozzáférés sub-links (active-state per page key); orphaned
.sidebar-settings-link CSS deleted (grep-zero), .nav-group-label /
.nav-links-sub added.
- Handlers wired to their own builders + templates; the legacy
settingsData() merge deleted.
- scripts/template_id_gate.py: the §10 JS element-ID integrity gate
(getElementById/querySelector('#…') must resolve in the SAME template;
JS-created + template-parameterized IDs handled; layout modal IDs
allowlisted). Red-proven: a storage function planted in the
notifications template failed the gate with 'static #migrate-progress
not defined'.
- Tests: per-page section markers + cross-leak assertions, h3 section
inventory (all 11 old headings accounted for; typo rename asserted).
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""D1 §10 JS element-ID integrity gate.
|
||||
|
||||
For every template under internal/web/templates and internal/setup/templates, extract each
|
||||
getElementById('X') / querySelector('#X') literal used by the file's inline JS and assert an
|
||||
id="X" exists in the SAME file — or that the ID is created by that file's own JS (innerHTML /
|
||||
insertAdjacentHTML string containing id="X" / id='X'), or is explicitly allowlisted below with
|
||||
a justification.
|
||||
|
||||
Exit 1 on any unresolved reference. Run from the repo's controller/ directory:
|
||||
python scripts/template_id_gate.py
|
||||
"""
|
||||
import io, os, re, sys
|
||||
|
||||
ROOTS = [
|
||||
os.path.join("internal", "web", "templates"),
|
||||
os.path.join("internal", "setup", "templates"),
|
||||
]
|
||||
|
||||
# Dynamic-ID exceptions: (template, id-prefix-or-name) -> justification.
|
||||
# Suffix-parameterized IDs (id + variable) are handled generically below; these are the rest.
|
||||
ALLOW = {
|
||||
# layout.html builds the alert/delete/remove modals entirely in JS and later looks them up.
|
||||
("layout.html", "alert-modal"): "created by showAlert() via innerHTML in the same file",
|
||||
("layout.html", "delete-modal"): "created by deleteOrphanStack() via innerHTML",
|
||||
("layout.html", "remove-modal"): "created by removeStack() via innerHTML",
|
||||
("layout.html", "confirm-delete-btn"): "created inside the delete-modal innerHTML",
|
||||
("layout.html", "confirm-remove-btn"): "created inside the remove-modal innerHTML",
|
||||
("layout.html", "delete-hdd-check"): "created inside the delete-modal innerHTML",
|
||||
("layout.html", "remove-hdd-check"): "created inside the remove-modal innerHTML",
|
||||
("layout.html", "remove-backup-check"): "created inside the remove-modal innerHTML",
|
||||
("layout.html", "remove-hdd-keep-warning"): "created inside the remove-modal innerHTML",
|
||||
("layout.html", "sync-btn"): "lives on stacks.html; syncTemplates() is shared layout JS guarded by if(!btn)return",
|
||||
("layout.html", "sync-toast"): "lives on stacks.html; guarded null-check",
|
||||
}
|
||||
|
||||
GET_RE = re.compile(r"getElementById\(\s*['\"]([A-Za-z0-9_-]+)['\"]\s*\)")
|
||||
GET_DYN_RE = re.compile(r"getElementById\(\s*['\"]([A-Za-z0-9_-]+)['\"]\s*\+")
|
||||
QS_RE = re.compile(r"querySelector\(\s*['\"]#([A-Za-z0-9_-]+)['\"]\s*\)")
|
||||
ID_ATTR_RE = re.compile(r"""id=["']([A-Za-z0-9_{}\. $-]+)["']""")
|
||||
ID_IN_JS_RE = re.compile(r"""id=\\?["']([A-Za-z0-9_-]+)\\?["']""")
|
||||
|
||||
|
||||
def check(path):
|
||||
fname = os.path.basename(path)
|
||||
src = io.open(path, encoding="utf-8").read()
|
||||
static_refs = set(GET_RE.findall(src)) | set(QS_RE.findall(src))
|
||||
dyn_prefixes = set(GET_DYN_RE.findall(src))
|
||||
# static refs regex also matches the dynamic form's literal — subtract prefixes used with '+'
|
||||
static_refs -= dyn_prefixes
|
||||
defined = set(ID_ATTR_RE.findall(src)) | set(ID_IN_JS_RE.findall(src))
|
||||
defined_prefixes = tuple(d.split("{{")[0] for d in defined if "{{" in d or d.endswith("-"))
|
||||
|
||||
problems = []
|
||||
for ref in sorted(static_refs):
|
||||
if ref in defined:
|
||||
continue
|
||||
# a template-parameterized id like id="field-{{.EnvVar}}" legitimately renders
|
||||
# ids such as field-SUBDOMAIN — match static refs against those prefixes
|
||||
if defined_prefixes and ref.startswith(defined_prefixes):
|
||||
continue
|
||||
if (fname, ref) in ALLOW:
|
||||
continue
|
||||
problems.append("static #%s not defined in %s" % (ref, fname))
|
||||
for pref in sorted(dyn_prefixes):
|
||||
# a dynamic lookup 'x-' + var needs SOME id starting with that prefix (template- or JS-created)
|
||||
if any(d.startswith(pref) for d in defined) or pref in defined_prefixes:
|
||||
continue
|
||||
if (fname, pref) in ALLOW:
|
||||
continue
|
||||
problems.append("dynamic prefix #%s* not defined in %s" % (pref, fname))
|
||||
return problems
|
||||
|
||||
|
||||
def main():
|
||||
bad = []
|
||||
for root in ROOTS:
|
||||
for fn in sorted(os.listdir(root)):
|
||||
if not fn.endswith(".html"):
|
||||
continue
|
||||
bad += check(os.path.join(root, fn))
|
||||
if bad:
|
||||
print("INTEGRITY GATE FAILED (%d):" % len(bad))
|
||||
for b in bad:
|
||||
print(" -", b)
|
||||
sys.exit(1)
|
||||
print("integrity gate OK — every JS element-ID reference resolves within its own template")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user