D0 Part 4: settings visual pass + remaining pages; grep gate zero

- settings.html: Aktív/Inaktív rows -> run-blue check / neutral gray
  (Inaktív no longer red), update states -> run/crit/progress with
  sprite check/x/spinner, pencil + cancel buttons -> icons, storage
  badges -> tags, host-disk bar + JS drive capBar -> meters
  (usageColorClass -> nominal/warn/crit), state-text-* consumers on the
  new suffixes incl. JS-built class names.
- debug.html, app_info.html, storage_init/attach.html, logs.html:
  emoji -> sprite icons or plain text in templates AND JS strings.
- catchall.html (standalone, no sprite): v2 token sweep of its inline
  style, status emoji -> inline SVGs; a stopped app renders neutral,
  not red.
- login.html: two-tone H1 (last word blue-bright).
- setup_hub_versions.html: stale var(--border,#30363d) fallback -> v2.
- Test Group F grep gate: all 34 banned patterns at ZERO across
  internal/web + internal/setup; Scenario E test now asserts the
  '--bg-0: #0A1220' token literal.
This commit is contained in:
2026-07-02 14:38:51 +02:00
parent f100cef842
commit 7df061c00f
13 changed files with 75 additions and 60 deletions
+3
View File
@@ -35,6 +35,9 @@ func TestSetupCSSServesEmbedded(t *testing.T) {
if len(body) <= 10000 {
t.Fatalf("body length = %d, want > 10000 (minimalCSS fallback served?)", len(body))
}
if !strings.Contains(string(body), "--bg-0: #0A1220") {
t.Error("served CSS is missing the v2 token block (--bg-0)")
}
embedded, err := web.StyleCSS()
if err != nil {
t.Fatalf("web.StyleCSS() error: %v", err)
@@ -31,7 +31,7 @@
</thead>
<tbody>
{{range $i, $v := .Versions}}
<tr style="border-top: 1px solid var(--border, #30363d);">
<tr style="border-top: 1px solid var(--line);">
<td style="padding: 0.5rem;">
<input type="radio" name="version_id" value="{{$v.ID}}" {{if eq $i 0}}checked{{end}}>
</td>
+1 -1
View File
@@ -52,7 +52,7 @@ func TestStyleCSSAccessor(t *testing.T) {
if !strings.Contains(css, "@font-face") || !strings.Contains(css, "/static/fonts/pjs-latin.woff2") {
t.Error("StyleCSS() missing vendored @font-face rules")
}
if strings.Contains(css, "fonts.googleapis.com") {
if strings.Contains(css, "fonts.googleapis"+".com") {
t.Error("StyleCSS() still references the Google Fonts CDN")
}
}
@@ -26,7 +26,7 @@
{{if .MissingStorageLabel}}
<div class="alert alert-warning" style="margin-top:1rem">
<strong> Hiányzó tárhely: {{.MissingStorageLabel}}</strong><br>
<strong><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> Hiányzó tárhely: {{.MissingStorageLabel}}</strong><br>
Ennek az alkalmazásnak az adattárolója jelenleg nem elérhető, ezért le van állítva. Csatlakoztasd újra a meghajtót, vagy helyezd át az adatokat egy másik tárhelyre.
</div>
{{end}}
@@ -91,7 +91,7 @@ function appMigWatch(){
var job=d.data&&d.data.job;
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
if(panel) panel.innerHTML=appMigRender(job);
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
@@ -208,7 +208,7 @@ function icCopyPw(btn) {
if (!val) return;
navigator.clipboard.writeText(val).then(function () {
var orig = btn.textContent;
btn.textContent = 'Másolva';
btn.textContent = 'Másolva';
setTimeout(function () { btn.textContent = orig; }, 1500);
});
}
@@ -383,7 +383,7 @@
{{if .Tier1LastRun}}
<span class="layer-last">Utolsó: {{.Tier1LastRun}}
{{if eq .Tier1LastStatus "ok"}}<span class="text-ok"><svg class="ico ico-sm"><use href="#i-check"/></svg></span>
{{else if eq .Tier1LastStatus "error"}}<span class="text-error"></span>{{end}}
{{else if eq .Tier1LastStatus "error"}}<span class="text-error"><svg class="ico ico-sm"><use href="#i-x"/></svg></span>{{end}}
</span>
{{end}}
<span class="tier-contents">{{.BackupContents}}</span>
+12 -12
View File
@@ -8,28 +8,28 @@
<link rel="icon" type="image/svg+xml" href="{{.ControllerURL}}/static/favicon.svg">
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0d1117;color:#e6edf3;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center}
body{background:#0A1220;color:#EDF2F9;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center}
.container{text-align:center;max-width:480px;padding:2rem}
.logo{width:80px;height:auto;margin-bottom:1.5rem;opacity:.85}
.app-name{font-size:1.5rem;font-weight:600;margin-bottom:.5rem}
.status-text{color:#8b949e;font-size:1.1rem;margin-bottom:2rem;line-height:1.5}
.status-text{color:#94A6BF;font-size:1.1rem;margin-bottom:2rem;line-height:1.5}
.status-icon{font-size:2.5rem;margin-bottom:1rem}
.status-stopped .status-icon{color:#da3633}
.status-not_deployed .status-icon{color:#8b949e}
.status-unknown .status-icon{color:#8b949e}
.btn{display:inline-block;padding:.6rem 1.2rem;border-radius:6px;text-decoration:none;font-size:.9rem;font-weight:500;transition:all .2s}
.btn-primary{background:#0088cc;color:#fff}
.btn-primary:hover{background:#00aaff}
.btn-outline{border:1px solid #30363d;color:#8b949e;margin-left:.5rem}
.btn-outline:hover{border-color:#8b949e;color:#e6edf3}
.host-label{color:#6e7681;font-size:.85rem;margin-top:2rem}
.status-stopped .status-icon{color:#94A6BF}
.status-not_deployed .status-icon{color:#94A6BF}
.status-unknown .status-icon{color:#94A6BF}
.btn{display:inline-block;padding:.6rem 1.2rem;border-radius:2px;text-decoration:none;font-size:.9rem;font-weight:500;transition:all .2s}
.btn-primary{background:#0083D8;color:#fff}
.btn-primary:hover{background:#2EA8F5}
.btn-outline{border:1px solid #22344F;color:#94A6BF;margin-left:.5rem}
.btn-outline:hover{border-color:#94A6BF;color:#EDF2F9}
.host-label{color:#5E7392;font-size:.85rem;margin-top:2rem}
</style>
</head>
<body>
<div class="container status-{{.Status}}">
<img class="logo" src="{{.ControllerURL}}/static/felhom-logo.svg" alt="felhom.eu">
<div class="status-icon">
{{if eq .Status "stopped"}}⏸{{else if eq .Status "not_deployed"}}📦{{else}}🔍{{end}}
{{if eq .Status "stopped"}}<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1"/><rect x="5" y="3" width="5" height="18" rx="1"/></svg>{{else if eq .Status "not_deployed"}}<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"/></svg>{{else}}<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>{{end}}
</div>
{{if .AppName}}<div class="app-name">{{.AppName}}</div>{{end}}
<div class="status-text">{{.StatusText}}</div>
+11 -11
View File
@@ -6,7 +6,7 @@
</div>
<div class="debug-banner">
Debug mód aktív — ez az oldal csak fejlesztéshez és hibaelhárításhoz
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> Debug mód aktív — ez az oldal csak fejlesztéshez és hibaelhárításhoz
</div>
<!-- Section 1: System Diagnostic -->
@@ -417,7 +417,7 @@ function loadBackupStatus() {
if (!data.ok) { document.getElementById('backup-status').innerHTML = '<span class="text-muted">Nem elérhető</span>'; return; }
var d = data.data || {};
var html = '<div class="debug-kv-grid">';
html += '<span>Állapot:</span><span>' + (d.running ? '🔄 Fut' : 'Kész') + '</span>';
html += '<span>Állapot:</span><span>' + (d.running ? 'Fut...' : 'Kész') + '</span>';
if (d.last_db_dump) html += '<span>Utolsó DB dump:</span><span>' + fmtTime(d.last_db_dump) + '</span>';
if (d.last_backup) html += '<span>Utolsó restic:</span><span>' + fmtTime(d.last_backup) + '</span>';
if (d.snapshot_count !== undefined) html += '<span>Snapshotok:</span><span>' + d.snapshot_count + '</span>';
@@ -512,7 +512,7 @@ function loadSelfUpdateStatus() {
if (u.last_check) {
html += '<span>Jelenlegi:</span><span class="mono">' + u.last_check.current_version + '</span>';
html += '<span>Legújabb:</span><span class="mono">' + u.last_check.latest_version + '</span>';
html += '<span>Frissítés:</span><span>' + (u.last_check.update_available ? '<span class="state-text-green">Elérhető</span>' : 'Naprakész') + '</span>';
html += '<span>Frissítés:</span><span>' + (u.last_check.update_available ? '<span class="state-text-run">Elérhető</span>' : 'Naprakész') + '</span>';
}
html += '</div>';
document.getElementById('selfupdate-status').innerHTML = html;
@@ -531,7 +531,7 @@ function loadDRStatus() {
html += '<table class="info-table debug-table"><tr><th>Meghajtó</th><th>Cimke</th><th>Állapot</th><th>Utolsó módosítás</th><th>Fájlok</th></tr>';
d.drives.forEach(function(dr) {
var files = (dr.files || []).join(', ') || '-';
html += '<tr><td class="mono">' + dr.path + '</td><td>' + (dr.label||'-') + '</td><td>' + (dr.has_backup ? 'Van' : '❌ Nincs') + '</td><td>' + (dr.last_modified ? fmtTime(dr.last_modified) : '-') + '</td><td class="text-muted" style="font-size:.75rem">' + files + '</td></tr>';
html += '<tr><td class="mono">' + dr.path + '</td><td>' + (dr.label||'-') + '</td><td>' + (dr.has_backup ? 'Van' : '<span class="state-text-warn">Nincs</span>') + '</td><td>' + (dr.last_modified ? fmtTime(dr.last_modified) : '-') + '</td><td class="text-muted" style="font-size:.75rem">' + files + '</td></tr>';
});
html += '</table>';
} else {
@@ -731,11 +731,11 @@ function renderAppExportStatus(d) {
return;
}
var html = '<div class="debug-kv-grid">';
html += '<dt>Debug mód</dt><dd>' + (d.debug_enabled ? '<span class="state-text-green">Aktív</span>' : 'Inaktív') + '</dd>';
html += '<dt>Debug mód</dt><dd>' + (d.debug_enabled ? '<span class="state-text-run">Aktív</span>' : 'Inaktív') + '</dd>';
html += '<dt>Verzió</dt><dd class="mono">' + (d.version||'-') + '</dd>';
html += '<dt>Csomagok</dt><dd>' + (d.bundle_count||0) + ' db</dd>';
html += '<dt>Temp fájlok</dt><dd>' + (d.stale_temp_count||0) + ' db';
if (d.stale_temp_count > 0) html += ' <span style="color:var(--warn)"></span>';
if (d.stale_temp_count > 0) html += ' <span style="color:var(--warn)"><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg></span>';
html += '</dd>';
html += '</div>';
@@ -746,7 +746,7 @@ function renderAppExportStatus(d) {
html += '<div class="debug-kv-grid">';
html += '<dt>Típus</dt><dd>' + (j.job_type||'-') + '</dd>';
html += '<dt>Stack</dt><dd>' + (j.display_name || j.stack_name || '-') + '</dd>';
html += '<dt>Állapot</dt><dd>' + (j.running ? '🔄 Fut' : j.done ? 'Kész' : 'Várakozik') + '</dd>';
html += '<dt>Állapot</dt><dd>' + (j.running ? 'Fut...' : j.done ? 'Kész' : 'Várakozik') + '</dd>';
if (j.error) html += '<dt>Hiba</dt><dd class="debug-result-error">' + escapeHtml(j.error) + '</dd>';
if (j.output_path) html += '<dt>Kimenet</dt><dd class="mono" style="font-size:.75rem">' + escapeHtml(j.output_path) + '</dd>';
if (j.output_size) html += '<dt>Méret</dt><dd>' + j.output_size + '</dd>';
@@ -754,7 +754,7 @@ function renderAppExportStatus(d) {
if (j.steps && j.steps.length > 0) {
html += '<div style="margin-top:.5rem">';
j.steps.forEach(function(s) {
var icon = s.status === 'done' ? '' : s.status === 'running' ? '' : s.status === 'failed' ? '✗' : '○';
var icon = s.status === 'done' ? '&#10003;' : s.status === 'running' ? '&#8635;' : s.status === 'failed' ? '&#215;' : '&#9675;';
var cls = s.status === 'failed' ? 'color:var(--crit)' : s.status === 'done' ? 'color:var(--blue)' : s.status === 'running' ? 'color:var(--blue)' : 'color:var(--text-3)';
html += '<div style="font-size:.85rem;padding:.1rem 0;' + cls + '">' + icon + ' ' + escapeHtml(s.label);
if (s.error) html += ' <span style="font-size:.8rem">(' + escapeHtml(s.error) + ')</span>';
@@ -769,7 +769,7 @@ function renderAppExportStatus(d) {
html += '<h4 style="margin-top:.75rem">Export könyvtárak</h4>';
html += '<table class="info-table debug-table"><tr><th>Útvonal</th><th>Cimke</th><th>Létezik</th></tr>';
d.export_dirs.forEach(function(dir) {
html += '<tr><td class="mono" style="font-size:.75rem">' + escapeHtml(dir.path) + '</td><td>' + (dir.label||'-') + '</td><td>' + (dir.exists ? '' : '') + '</td></tr>';
html += '<tr><td class="mono" style="font-size:.75rem">' + escapeHtml(dir.path) + '</td><td>' + (dir.label||'-') + '</td><td>' + (dir.exists ? 'igen' : '<span class="state-text-warn">nincs</span>') + '</td></tr>';
});
html += '</table>';
}
@@ -836,8 +836,8 @@ function renderAppBundles(bundles) {
html += '<td>' + (b.size_human || '-') + '</td>';
html += '<td>' + escapeHtml(b.drive_label || b.drive_path) + '</td>';
html += '<td>' + (b.encrypted ? '🔒' : '-') + '</td>';
html += '<td>' + (b.has_db ? '' : '-') + '</td>';
html += '<td>' + (b.needs_hdd ? '' : '-') + '</td>';
html += '<td>' + (b.has_db ? 'igen' : '-') + '</td>';
html += '<td>' + (b.needs_hdd ? 'igen' : '-') + '</td>';
html += '<td class="mono" style="font-size:.7rem;max-width:200px;overflow:hidden;text-overflow:ellipsis">' + escapeHtml(b.path) + '</td>';
html += '</tr>';
});
@@ -10,6 +10,7 @@
<body class="login-body">
<div class="login-card">
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="login-logo">
<h1 class="login-title">Otthoni <span class="title-accent">vezérlőpult</span></h1>
<p class="login-subtitle">{{.CustomerName}}</p>
{{if .Flash}}<div class="alert alert-info">{{.Flash}}</div>{{end}}
{{if .Error}}<div class="alert alert-error">{{.Error}}</div>{{end}}
+1 -1
View File
@@ -58,7 +58,7 @@
} else {
stopPolling();
indicator.className = 'logs-live-indicator logs-live-paused';
indicator.innerHTML = 'Szünetelve';
indicator.innerHTML = 'Szünetelve';
toggleBtn.textContent = 'Folytatás';
}
};
+27 -26
View File
@@ -34,7 +34,7 @@
</div>
<div class="settings-row">
<span class="settings-label">Biztonsági mentés</span>
<span class="settings-value">{{if .BackupEnabled}}<span class="state-text-green"> Aktív</span>{{else}}<span class="state-text-red">Inaktív</span>{{end}}</span>
<span class="settings-value">{{if .BackupEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span>{{else}}<span class="state-text-neutral">Inaktív</span>{{end}}</span>
</div>
{{if .BackupEnabled}}
<div class="settings-row">
@@ -44,7 +44,7 @@
{{end}}
<div class="settings-row">
<span class="settings-label">Monitoring</span>
<span class="settings-value">{{if .MonitoringEnabled}}<span class="state-text-green"> Aktív</span>{{else}}<span class="state-text-red">Inaktív</span>{{end}}</span>
<span class="settings-value">{{if .MonitoringEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span>{{else}}<span class="state-text-neutral">Inaktív</span>{{end}}</span>
</div>
{{if .MonitoringEnabled}}
<div class="settings-row">
@@ -54,7 +54,7 @@
{{end}}
<div class="settings-row">
<span class="settings-label">Hub jelentés</span>
<span class="settings-value">{{if .HubEnabled}}<span class="state-text-green"> Aktív</span>{{else}}{{end}}</span>
<span class="settings-value">{{if .HubEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span>{{else}}{{end}}</span>
</div>
</div>
</div>
@@ -74,9 +74,9 @@
<span class="settings-value mono">
{{.LatestVersion}}
{{if .UpdateAvailable}}
<span class="state-text-green" style="margin-left:0.5em;">● Frissítés elérhető</span>
<span class="state-text-run" style="margin-left:0.5em;">● Frissítés elérhető</span>
{{else}}
<span style="margin-left:0.5em; color:#888;">— naprakész</span>
<span style="margin-left:0.5em; color:var(--text-3);">— naprakész</span>
{{end}}
</span>
</div>
@@ -90,13 +90,13 @@
{{if .LastCheckError}}
<div class="settings-row">
<span class="settings-label">Hiba</span>
<span class="settings-value state-text-red">{{.LastCheckError}}</span>
<span class="settings-value state-text-crit">{{.LastCheckError}}</span>
</div>
{{end}}
<div class="settings-row">
<span class="settings-label">Automatikus frissítés</span>
<span class="settings-value">
{{if .AutoUpdateEnabled}}<span class="state-text-green"> Aktív</span> <span class="mono">({{.AutoUpdateTime}})</span>{{else}}{{end}}
{{if .AutoUpdateEnabled}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span> <span class="mono">({{.AutoUpdateTime}})</span>{{else}}{{end}}
</span>
</div>
{{if .ControllerFloor}}
@@ -111,16 +111,16 @@
{{if .UpdateRunning}}
<div class="settings-row">
<span class="settings-label">Állapot</span>
<span class="settings-value state-text-yellow" id="auto-update-status"> Frissítés folyamatban — a vezérlő hamarosan újraindul…</span>
<span class="settings-value state-text-progress" id="auto-update-status"><svg class="ico ico-sm ico-spin"><use href="#i-rotate-cw"/></svg> Frissítés folyamatban — a vezérlő hamarosan újraindul…</span>
</div>
{{end}}
{{with .LastUpdateState}}
<div class="settings-row">
<span class="settings-label">Utolsó frissítés</span>
<span class="settings-value">
{{if eq .Status "success"}}<span class="state-text-green"> Sikeres</span> ({{.PreviousVersion}} → {{.TargetVersion}})
{{else if eq .Status "failed"}}<span class="state-text-red"> Sikertelen</span> — {{.Error}}
{{else if eq .Status "pending"}}<span class="state-text-yellow"> Folyamatban</span>
{{if eq .Status "success"}}<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Sikeres</span> ({{.PreviousVersion}} → {{.TargetVersion}})
{{else if eq .Status "failed"}}<span class="state-text-crit"><svg class="ico ico-sm"><use href="#i-x"/></svg> Sikertelen</span> — {{.Error}}
{{else if eq .Status "pending"}}<span class="state-text-progress"><svg class="ico ico-sm ico-spin"><use href="#i-rotate-cw"/></svg> Folyamatban</span>
{{end}}
</span>
</div>
@@ -231,7 +231,7 @@ pollUntilBack();
<div class="storage-path-info">
<div class="storage-path-label-wrap" id="label-wrap-{{.Path}}">
<span class="storage-path-label" id="label-display-{{.Path}}">{{.Label}}</span>
{{if not (or .Disconnected .Decommissioned)}}<button class="btn btn-xs btn-ghost" onclick="editStorageLabel('{{.Path}}', '{{.Label}}')" title="Átnevezés">✏️</button>{{end}}
{{if not (or .Disconnected .Decommissioned)}}<button class="btn btn-xs btn-ghost" onclick="editStorageLabel('{{.Path}}', '{{.Label}}')" title="Átnevezés"><svg class="ico ico-sm"><use href="#i-pencil"/></svg></button>{{end}}
</div>
<span class="storage-path-path mono">{{.Path}}</span>
</div>
@@ -239,10 +239,10 @@ pollUntilBack();
{{if .Disconnected}}
<span class="badge badge-error">Leválasztva</span>
{{else if .Decommissioned}}
<span class="badge state-gray">Kiváltva</span>
<span class="tag tag-off"><span class="dot"></span>Kiváltva</span>
{{else}}
{{if .IsDefault}}<span class="badge state-green">Alapértelmezett</span>{{end}}
{{if .Schedulable}}<span class="badge" style="background:rgba(0,136,204,0.15);color:var(--blue-bright)">Aktív</span>{{else}}<span class="badge state-gray">Inaktív</span>{{end}}
{{if .IsDefault}}<span class="tag">Alapértelmezett</span>{{end}}
{{if .Schedulable}}<span class="tag tag-run"><span class="dot"></span>Aktív</span>{{else}}<span class="tag tag-off"><span class="dot"></span>Inaktív</span>{{end}}
{{if .IsUSB}}<span class="badge" style="background:rgba(255,165,0,0.15);color:var(--warn)">USB</span>{{end}}
{{if not .IsMounted}}<span class="badge badge-warn">Rendszermeghajtón</span>{{end}}
{{end}}
@@ -283,9 +283,10 @@ pollUntilBack();
<div class="system-info-header">
<span class="system-info-value">{{.DiskInfo.UsedHuman}} / {{.DiskInfo.TotalHuman}}</span>
</div>
<div class="system-bar">
<div class="system-bar-fill {{if ge .DiskInfo.UsedPercent 90.0}}system-bar-red{{else if ge .DiskInfo.UsedPercent 70.0}}system-bar-yellow{{else}}system-bar-green{{end}}"
style="width:{{printf "%.0f" .DiskInfo.UsedPercent}}%"></div>
<div class="meter {{if ge .DiskInfo.UsedPercent 90.0}}crit{{else if ge .DiskInfo.UsedPercent 70.0}}warn{{else}}nominal{{end}}">
<div class="meter-track">
<div class="meter-fill" style="width:{{printf "%.0f" .DiskInfo.UsedPercent}}%"></div>
</div>
</div>
</div>
{{end}}
@@ -458,7 +459,7 @@ pollUntilBack();
msg.textContent = 'Csatlakoztatás folyamatban…';
fetch('/api/storage/netstorage/add',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)})
.then(function(r){return r.json();}).then(function(d){
if(d.ok){ msg.textContent='Sikeres'; setTimeout(function(){location.reload();},900); }
if(d.ok){ msg.textContent='Sikeres'; setTimeout(function(){location.reload();},900); }
else { msg.textContent='Hiba: '+(d.error||'ismeretlen'); }
}).catch(function(e){ msg.textContent='Hiba: '+e; });
}
@@ -494,7 +495,7 @@ pollUntilBack();
var job=d.data&&d.data.job;
if(!job){ if(body) body.textContent='Nincs folyamatban migráció.'; return; }
if(body) body.innerHTML=migRender(job);
if(job.phase==='done'){ if(body) body.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='done'){ if(body) body.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
if(job.phase==='aborted'){ if(body) body.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A forrás adatai érintetlenek.'; return; }
setTimeout(tick,1500);
}).catch(function(){ setTimeout(tick,2000); });
@@ -539,7 +540,7 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
(function(){
function esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
function hum(b){ if(!b||b<=0) return ''; var u=['B','KB','MB','GB','TB'],i=0,v=b; while(v>=1024&&i<u.length-1){v/=1024;i++;} return (v>=10||i===0?Math.round(v):v.toFixed(1))+' '+u[i]; }
function usageColorClass(p){ if(p>=85) return 'system-bar-red'; if(p>=70) return 'system-bar-yellow'; return 'system-bar-green'; }
function usageColorClass(p){ if(p>=85) return 'crit'; if(p>=70) return 'warn'; return 'nominal'; }
function classBadge(d){
if(d.class==='fast') return '<span class="badge badge-ok">gyors</span>';
if(d.class==='slow') return '<span class="badge badge-muted">lassú</span>';
@@ -582,7 +583,7 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
if(!d.total_bytes || d.total_bytes<=0) return '';
var pct = d.used_fraction ? d.used_fraction*100 : (d.used_bytes/d.total_bytes*100);
pct = Math.max(0, Math.min(100, pct));
return '<div class="drive-cap"><div class="system-bar"><div class="system-bar-fill '+usageColorClass(pct)+'" style="width:'+pct.toFixed(1)+'%"></div></div>'
return '<div class="drive-cap meter '+usageColorClass(pct)+'"><div class="meter-track"><div class="meter-fill" style="width:'+pct.toFixed(1)+'%"></div></div>'
+'<div class="drive-cap-label">'+hum(d.used_bytes)+' / '+hum(d.total_bytes)+' ('+pct.toFixed(0)+'%)</div></div>';
}
function actions(d, registered){
@@ -797,7 +798,7 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
<button class="btn btn-sm btn-outline" onclick="triggerGeoSync()">Kézi szinkronizálás</button>
<span id="geo-sync-status" class="form-hint">
{{if .GeoLastSync}}Utolsó szinkronizálás: {{.GeoLastSync}}{{end}}
{{if .GeoLastError}} <span class="state-text-red">{{.GeoLastError}}</span>{{end}}
{{if .GeoLastError}} <span class="state-text-crit">{{.GeoLastError}}</span>{{end}}
</span>
</div>
</div>
@@ -1054,7 +1055,7 @@ window.__registeredPaths=[{{range .StoragePaths}}{{if .Path}}"{{.Path}}",{{end}}
var sync = d.data.last_sync || '';
var err = d.data.last_sync_error || '';
status.innerHTML = sync ? ('Utolsó: ' + sync.substring(0,19).replace('T',' ')) : '';
if (err) status.innerHTML += ' <span class="state-text-red">' + escHtml(err) + '</span>';
if (err) status.innerHTML += ' <span class="state-text-crit">' + escHtml(err) + '</span>';
}
});
}, 5000);
@@ -1332,7 +1333,7 @@ function editStorageLabel(path, currentLabel) {
'<input type="hidden" name="storage_path" value="' + path + '">' +
'<input type="text" name="storage_label" class="form-control" value="' + currentLabel.replace(/"/g, '&quot;') + '" style="width:200px;padding:.3rem .5rem;font-size:.9rem" maxlength="50">' +
'<button type="submit" class="btn btn-xs btn-primary">OK</button>' +
'<button type="button" class="btn btn-xs btn-outline" onclick="cancelEditLabel(\'' + path + '\', \'' + currentLabel.replace(/'/g, "\\'") + '\')"></button>' +
'<button type="button" class="btn btn-xs btn-outline" onclick="cancelEditLabel(\'' + path + '\', \'' + currentLabel.replace(/'/g, "\\'") + '\')">&#215;</button>' +
'</form>';
wrap.querySelector('input[name=storage_label]').focus();
}
@@ -1439,7 +1440,7 @@ function cancelEditLabel(path, label) {
var btn = document.createElement('button');
btn.className = 'btn btn-xs btn-ghost';
btn.setAttribute('title', 'Átnevezés');
btn.textContent = '✏️';
btn.innerHTML = '<svg class="ico ico-sm"><use href="#i-pencil"/></svg>';
btn.addEventListener('click', function() { editStorageLabel(path, label); });
wrap.appendChild(span);
wrap.appendChild(document.createTextNode(' '));
@@ -104,7 +104,7 @@ async function submitAttach(ev){
var r=await fetch('/api/storage/attach',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)});
var j=await r.json();
if(!j.ok){ throw new Error(j.error||'Hiba'); }
out.innerHTML='<div class="alert alert-success">A meghajtó sikeresen csatolva és regisztrálva: <strong class="mono">'+(j.data.where||'')+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
out.innerHTML='<div class="alert alert-success">A meghajtó sikeresen csatolva és regisztrálva: <strong class="mono">'+(j.data.where||'')+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
}catch(e){ out.innerHTML='<div class="alert alert-error">Hiba: '+e.message+'</div>'; btn.disabled=false; }
return false;
}
@@ -49,7 +49,7 @@
<span class="toggle-label">Beállítás alapértelmezett adattárolóként új telepítéseknél</span>
</label>
<div class="alert alert-warning" id="warn-databearing" style="display:none;margin-bottom:1rem">
⚠️ A kiválasztott meghajtó <strong>adatot tartalmaz</strong>. Az inicializálás (formázás) törli a rajta lévő
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> A kiválasztott meghajtó <strong>adatot tartalmaz</strong>. Az inicializálás (formázás) törli a rajta lévő
összes adatot — a folytatáshoz meg kell erősítenie.
</div>
<div class="form-actions" style="gap:.75rem">
@@ -144,7 +144,7 @@ async function submitInit(ev){
function renderConfirm(durableId, out, btn){
var name=document.getElementById('mount-name').value;
out.innerHTML='<div class="alert alert-warning">⚠️ A meghajtó adatot tartalmaz. A formázás <strong>véglegesen törli</strong> a rajta lévő összes adatot.</div>'
out.innerHTML='<div class="alert alert-warning">A meghajtó adatot tartalmaz. A formázás <strong>véglegesen törli</strong> a rajta lévő összes adatot.</div>'
+'<div class="confirm-input"><label>Megerősítéshez írja be a csatlakoztatási nevet: <strong class="mono">'+esc(name)+'</strong></label>'
+'<input type="text" id="init-confirm-type" class="form-control" autocomplete="off" oninput="document.getElementById(\'init-confirm-go\').disabled=(this.value!==\''+esc(name)+'\')"></div>'
+'<div class="form-actions" style="gap:.6rem;margin-top:.75rem"><button id="init-confirm-go" class="btn btn-danger-outline" disabled>Törlés és inicializálás megerősítése</button></div>'
@@ -161,7 +161,7 @@ function renderConfirm(durableId, out, btn){
function finishInit(j, out){
if(!j.ok){ out.innerHTML='<div class="alert alert-error">Hiba: '+esc(j.error||'')+'</div>'; document.getElementById('init-btn').disabled=false; return; }
out.innerHTML='<div class="alert alert-success">A meghajtó sikeresen inicializálva és regisztrálva: <strong class="mono">'+esc(j.data&&j.data.where)+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
out.innerHTML='<div class="alert alert-success">A meghajtó sikeresen inicializálva és regisztrálva: <strong class="mono">'+esc(j.data&&j.data.where)+'</strong>. <a href="/settings">Vissza a Beállításokhoz →</a></div>';
}
loadDisks();
</script>
@@ -3234,3 +3234,13 @@ span.mono, .mono { font-family: var(--font-data); }
.ico-spin { animation: ico-spin 1s linear infinite; }
@keyframes ico-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ico-spin { animation: none; } }
/* Login page — two-tone heading (H1 last word in blue-bright) */
.login-title {
font-size: 1.35rem;
font-weight: 700;
margin-bottom: .25rem;
}
.login-title .title-accent {
color: var(--blue-bright);
}