fix: admin dashboard format errors — use JS for stats, string concat for SMTP values, fix layout overlap

This commit is contained in:
mirivlad 2026-06-01 23:59:15 +08:00
parent 99e47fcb17
commit 0f5c584c50
1 changed files with 24 additions and 10 deletions

View File

@ -330,6 +330,7 @@ func (s *Server) routes() *http.ServeMux {
mux.HandleFunc("/logout", s.handleUserWebLogout)
mux.HandleFunc("/admin/login", s.handleAdminLogin)
mux.HandleFunc("/admin/dashboard", s.handleAdminDashboard)
mux.HandleFunc("/admin/api/stats", s.handleAdminStats)
mux.HandleFunc("/admin/", s.handleAdminAPI)
mux.HandleFunc("/", s.handleNotFound)
return mux
@ -1336,8 +1337,8 @@ a{color:#6366f1}
h1{border-bottom:1px solid #2a2a3c;padding-bottom:12px}
h2{margin-top:24px;font-size:16px}
.stat{background:#1a1a28;border:1px solid #2a2a3c;padding:12px 16px;border-radius:8px;margin:8px 0}
.row{display:flex;gap:16px;flex-wrap:wrap}
.col{flex:1;min-width:300px}
.row{display:flex;gap:20px;flex-wrap:wrap;margin-top:8px}
.col{flex:1;min-width:280px;max-width:50%}
table{width:100%;border-collapse:collapse;margin-top:8px}
th,td{text-align:left;padding:8px 12px;border-bottom:1px solid #2a2a3c}
th{font-size:12px;color:#888;text-transform:uppercase}
@ -1360,8 +1361,8 @@ form{margin-top:8px}
</head><body>
<h1>Verstak Sync Server</h1>
<div class="row">
<div class="stat"><strong>Устройств:</strong> %d</div>
<div class="stat"><strong>Операций:</strong> %d</div>
<div class="stat"><strong>Устройств:</strong> <span id="dev-count">0</span></div>
<div class="stat"><strong>Операций:</strong> <span id="op-count">0</span></div>
</div>
<div class="row">
@ -1376,6 +1377,10 @@ fetch('/admin/api/keys').then(r=>r.json()).then(keys=>{
keys.map(k=>'<tr><td>'+k.name+'</td><td class="key-cell" title="'+k.api_key+'">'+k.api_key+'</td>'+
'<td><button class="btn copy-btn" onclick="copyKey(\''+k.api_key+'\',this)">Копировать</button></td>'+
'<td><button class="btn btn-danger" onclick="delKey(\''+k.id+'\')">Удалить</button></td></tr>').join('')+'</table>'
document.getElementById('dev-count').textContent=keys.length
})
fetch('/admin/api/stats').then(r=>r.json()).then(stats=>{
document.getElementById('op-count').textContent=stats.ops||'0'
})
function copyKey(key,btn){
navigator.clipboard.writeText(key).then(()=>{
@ -1396,12 +1401,12 @@ function delKey(id){if(confirm('Удалить ключ?'))fetch('/admin/api/key
<h2>SMTP (для писем)</h2>
<div class="section">
<form action="/admin/api/smtp" method="POST">
<div class="form-row"><label>Сервер</label><input name="smtp_host" value="%s" placeholder="smtp.example.com"></div>
<div class="form-row"><label>Порт</label><input name="smtp_port" value="%s" placeholder="587"></div>
<div class="form-row"><label>Логин</label><input name="smtp_user" value="%s" placeholder="user@example.com"></div>
<div class="form-row"><label>Сервер</label><input name="smtp_host" value="` + smtpHost + `" placeholder="smtp.example.com"></div>
<div class="form-row"><label>Порт</label><input name="smtp_port" value="` + smtpPort + `" placeholder="587"></div>
<div class="form-row"><label>Логин</label><input name="smtp_user" value="` + smtpUser + `" placeholder="user@example.com"></div>
<div class="form-row"><label>Пароль</label><input type="password" name="smtp_pass" placeholder="••••••••"></div>
<div class="form-row"><label>От кого</label><input name="smtp_from" value="%s" placeholder="noreply@example.com"></div>
<div class="form-row"><label>URL сервера</label><input name="server_url" value="%s" placeholder="https://example.com:47732"></div>
<div class="form-row"><label>От кого</label><input name="smtp_from" value="` + smtpFrom + `" placeholder="noreply@example.com"></div>
<div class="form-row"><label>URL сервера</label><input name="server_url" value="` + srvURL + `" placeholder="https://example.com:47732"></div>
<div style="margin-top:8px"><button class="btn btn-primary">Сохранить SMTP</button></div>
</form>
</div>
@ -1409,10 +1414,19 @@ function delKey(id){if(confirm('Удалить ключ?'))fetch('/admin/api/key
</div>
<p style="margin-top:16px"><a href="/api/v1/health">Health check</a></p>
</body></html>`, deviceCount, opsCount, smtpHost, smtpPort, smtpUser, smtpFrom, srvURL)
</body></html>`)
w.Write([]byte(html))
}
func (s *Server) handleAdminStats(w http.ResponseWriter, r *http.Request) {
if !s.requireAdmin(w, r) {
return
}
var opsCount int
s.db.QueryRow("SELECT COUNT(*) FROM server_ops").Scan(&opsCount)
jsonOK(w, map[string]int{"ops": opsCount})
}
func (s *Server) handleAdminAPI(w http.ResponseWriter, r *http.Request) {
if !s.requireAdmin(w, r) {
return