f8e18a9ec9
- 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).
92 lines
4.1 KiB
Python
92 lines
4.1 KiB
Python
# -*- 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()
|