fix(webui): services table — show state badge, full status on click
Replace raw systemctl output in table cell with: - state badge (active/failed/inactive) — click to expand - full systemctl status in collapsible pre block (max 200px scroll) Fixes layout explosion from multi-line status text in table. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,6 +57,7 @@ type networkManager interface {
|
|||||||
|
|
||||||
type serviceManager interface {
|
type serviceManager interface {
|
||||||
ListBeeServices() ([]string, error)
|
ListBeeServices() ([]string, error)
|
||||||
|
ServiceState(name string) string
|
||||||
ServiceStatus(name string) (string, error)
|
ServiceStatus(name string) (string, error)
|
||||||
ServiceDo(name string, action platform.ServiceAction) (string, error)
|
ServiceDo(name string, action platform.ServiceAction) (string, error)
|
||||||
}
|
}
|
||||||
@@ -356,6 +357,10 @@ func (a *App) ListBeeServices() ([]string, error) {
|
|||||||
return a.services.ListBeeServices()
|
return a.services.ListBeeServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) ServiceState(name string) string {
|
||||||
|
return a.services.ServiceState(name)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) ServiceStatus(name string) (string, error) {
|
func (a *App) ServiceStatus(name string) (string, error) {
|
||||||
return a.services.ServiceStatus(name)
|
return a.services.ServiceStatus(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,10 @@ func (f fakeServices) ListBeeServices() ([]string, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f fakeServices) ServiceState(name string) string {
|
||||||
|
return "active"
|
||||||
|
}
|
||||||
|
|
||||||
func (f fakeServices) ServiceStatus(name string) (string, error) {
|
func (f fakeServices) ServiceStatus(name string) (string, error) {
|
||||||
return f.serviceStatusFn(name)
|
return f.serviceStatusFn(name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,13 +236,15 @@ func (h *handler) handleAPIServicesList(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
type serviceInfo struct {
|
type serviceInfo struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
State string `json:"state"`
|
||||||
|
Body string `json:"body"`
|
||||||
}
|
}
|
||||||
result := make([]serviceInfo, 0, len(names))
|
result := make([]serviceInfo, 0, len(names))
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
status, _ := h.opts.App.ServiceStatus(name)
|
state := h.opts.App.ServiceState(name)
|
||||||
result = append(result, serviceInfo{Name: name, Status: status})
|
body, _ := h.opts.App.ServiceStatus(name)
|
||||||
|
result = append(result, serviceInfo{Name: name, State: state, Body: body})
|
||||||
}
|
}
|
||||||
writeJSON(w, result)
|
writeJSON(w, result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -515,9 +515,16 @@ func renderServices() string {
|
|||||||
function loadServices() {
|
function loadServices() {
|
||||||
fetch('/api/services').then(r=>r.json()).then(svcs => {
|
fetch('/api/services').then(r=>r.json()).then(svcs => {
|
||||||
const rows = svcs.map(s => {
|
const rows = svcs.map(s => {
|
||||||
const st = s.status||'unknown';
|
const st = s.state||'unknown';
|
||||||
const badge = st.includes('active') ? 'badge-ok' : st.includes('failed') ? 'badge-err' : 'badge-warn';
|
const badge = st==='active' ? 'badge-ok' : st==='failed' ? 'badge-err' : 'badge-warn';
|
||||||
return '<tr><td>'+s.name+'</td><td><span class="badge '+badge+'">'+st+'</span></td><td>' +
|
const id = 'svc-body-'+s.name.replace(/[^a-z0-9]/g,'-');
|
||||||
|
const body = (s.body||'').replace(/</g,'<').replace(/>/g,'>');
|
||||||
|
return '<tr>' +
|
||||||
|
'<td style="white-space:nowrap">'+s.name+'</td>' +
|
||||||
|
'<td style="white-space:nowrap"><span class="badge '+badge+'" style="cursor:pointer" onclick="toggleBody(\''+id+'\')">'+st+' ▾</span>' +
|
||||||
|
'<div id="'+id+'" style="display:none;margin-top:6px"><pre style="font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;background:#0a0d14;padding:8px;border-radius:6px;color:#94a3b8">'+body+'</pre></div>' +
|
||||||
|
'</td>' +
|
||||||
|
'<td style="white-space:nowrap">' +
|
||||||
'<button class="btn btn-sm btn-secondary" onclick="svcAction(\''+s.name+'\',\'start\')">Start</button> ' +
|
'<button class="btn btn-sm btn-secondary" onclick="svcAction(\''+s.name+'\',\'start\')">Start</button> ' +
|
||||||
'<button class="btn btn-sm btn-secondary" onclick="svcAction(\''+s.name+'\',\'stop\')">Stop</button> ' +
|
'<button class="btn btn-sm btn-secondary" onclick="svcAction(\''+s.name+'\',\'stop\')">Stop</button> ' +
|
||||||
'<button class="btn btn-sm btn-secondary" onclick="svcAction(\''+s.name+'\',\'restart\')">Restart</button>' +
|
'<button class="btn btn-sm btn-secondary" onclick="svcAction(\''+s.name+'\',\'restart\')">Restart</button>' +
|
||||||
@@ -527,6 +534,10 @@ function loadServices() {
|
|||||||
'<table><tr><th>Service</th><th>Status</th><th>Actions</th></tr>'+rows+'</table>';
|
'<table><tr><th>Service</th><th>Status</th><th>Actions</th></tr>'+rows+'</table>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function toggleBody(id) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.style.display = el.style.display==='none' ? 'block' : 'none';
|
||||||
|
}
|
||||||
function svcAction(name, action) {
|
function svcAction(name, action) {
|
||||||
fetch('/api/services/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,action})})
|
fetch('/api/services/action',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name,action})})
|
||||||
.then(r=>r.json()).then(d => {
|
.then(r=>r.json()).then(d => {
|
||||||
|
|||||||
Reference in New Issue
Block a user