feat: fold single-device operations into device workspace
This commit is contained in:
parent
661b0b0afd
commit
74dfbbf7b5
@ -481,19 +481,14 @@ func (u *UI) pageDeviceAdd(w http.ResponseWriter, r *http.Request) {
|
||||
func (u *UI) pageDeviceConfig(w http.ResponseWriter, r *http.Request) {
|
||||
u.ensureDevicesLoaded()
|
||||
u.render(w, r, "device_config", PageData{
|
||||
Title: "配置管理",
|
||||
Title: "设备配置入口",
|
||||
Devices: u.registry.GetDevices(),
|
||||
})
|
||||
}
|
||||
|
||||
func (u *UI) pageDeviceConfigDetail(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
dev, ok := u.findDevice(id)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
u.render(w, r, "device_control", u.deviceConfigWorkspacePageData(dev))
|
||||
http.Redirect(w, r, "/ui/devices/"+url.PathEscape(id)+"#device-config", http.StatusFound)
|
||||
}
|
||||
|
||||
func (u *UI) actionDeviceAdd(w http.ResponseWriter, r *http.Request) {
|
||||
@ -667,26 +662,18 @@ func (u *UI) pageDevice(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
status, raw, statusErr := u.loadConfigStatus(dev)
|
||||
data := PageData{Title: "设备详情", Device: dev, ConfigStatus: status, ConfigStatusText: raw}
|
||||
if statusErr != nil {
|
||||
data.ConfigStatusErr = statusErr.Error()
|
||||
}
|
||||
u.render(w, r, "device", data)
|
||||
u.render(w, r, "device", u.deviceDetailPageData(dev))
|
||||
}
|
||||
|
||||
func (u *UI) deviceDetailPageData(dev *models.Device) PageData {
|
||||
status, raw, statusErr := u.loadConfigStatus(dev)
|
||||
data := PageData{Title: "设备详情", Device: dev, ConfigStatus: status, ConfigStatusText: raw}
|
||||
if statusErr != nil {
|
||||
data.ConfigStatusErr = statusErr.Error()
|
||||
}
|
||||
data := u.deviceControlPageData(dev)
|
||||
data.Title = "设备详情"
|
||||
return data
|
||||
}
|
||||
|
||||
func (u *UI) pageDeviceControl(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
http.Redirect(w, r, "/ui/device-config/"+url.PathEscape(id), http.StatusFound)
|
||||
http.Redirect(w, r, "/ui/devices/"+url.PathEscape(id)+"#device-config", http.StatusFound)
|
||||
}
|
||||
|
||||
func (u *UI) actionDeviceAction(w http.ResponseWriter, r *http.Request) {
|
||||
@ -727,14 +714,14 @@ func (u *UI) actionDeviceAction(w http.ResponseWriter, r *http.Request) {
|
||||
msg := fmt.Sprintf("%s %s -> %d", method, path, code)
|
||||
returnTo := strings.TrimSpace(r.FormValue("return_to"))
|
||||
if returnTo == "control" || returnTo == "config" {
|
||||
data := u.deviceConfigWorkspacePageData(dev)
|
||||
data := u.deviceDetailPageData(dev)
|
||||
data.Message = msg
|
||||
data.RawText = string(body)
|
||||
data.ResultTitle = "执行结果摘要"
|
||||
if err != nil {
|
||||
data.Error = err.Error()
|
||||
}
|
||||
u.render(w, r, "device_control", data)
|
||||
u.render(w, r, "device", data)
|
||||
return
|
||||
}
|
||||
data := PageData{Title: "设备详情", Device: dev, Message: msg, RawText: string(body)}
|
||||
@ -1463,7 +1450,7 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req
|
||||
returnTo := strings.TrimSpace(r.FormValue("return_to"))
|
||||
var data PageData
|
||||
if returnTo == "control" || returnTo == "config" {
|
||||
data = u.deviceConfigWorkspacePageData(dev)
|
||||
data = u.deviceDetailPageData(dev)
|
||||
} else {
|
||||
data = u.configPreviewPageData(dev)
|
||||
}
|
||||
@ -1488,7 +1475,7 @@ func (u *UI) actionDeviceConfigCandidateApply(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
if returnTo == "control" || returnTo == "config" {
|
||||
u.render(w, r, "device_control", data)
|
||||
u.render(w, r, "device", data)
|
||||
return
|
||||
}
|
||||
u.render(w, r, "config_preview", data)
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
<button type="submit" formaction="/ui/devices/{{.Device.DeviceID}}/config-candidate/apply">应用候选配置</button>
|
||||
<input type="hidden" name="json" value="{{.ConfigPreview.JSON}}" />
|
||||
{{end}}
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}/control">返回设备控制</a>
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}#device-config">返回设备详情</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,28 +1,60 @@
|
||||
{{define "device"}}
|
||||
{{template "device_header" .}}
|
||||
{{template "device_tabs" .}}
|
||||
|
||||
<div class="detail-grid">
|
||||
<div class="card">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>设备工作台</h2>
|
||||
<div class="muted small">单设备查看、服务控制、配置应用、模型资源和日志指标都收敛在这里完成。</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="btn ghost" href="/ui/devices">返回设备列表</a>
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}/config-preview">{{icon "preview"}}<span>打开配置预览</span></a>
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}/logs?limit=200">诊断日志</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-strip control-summary">
|
||||
<div class="summary-chip">
|
||||
<div class="summary-chip-label">设备状态</div>
|
||||
<div class="summary-chip-value">{{if .Device.Online}}在线{{else}}离线{{end}}</div>
|
||||
</div>
|
||||
<div class="summary-chip">
|
||||
<div class="summary-chip-label">当前配置</div>
|
||||
<div class="summary-chip-value">{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}待读取{{end}}</div>
|
||||
</div>
|
||||
<div class="summary-chip">
|
||||
<div class="summary-chip-label">服务状态</div>
|
||||
<div class="summary-chip-value">{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}未确认{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="actions compact">
|
||||
<a class="btn ghost" href="#device-overview">概览</a>
|
||||
<a class="btn ghost" href="#device-runtime">运行与服务</a>
|
||||
<a class="btn ghost" href="#device-config">设备配置</a>
|
||||
<a class="btn ghost" href="#device-models">模型与资源</a>
|
||||
<a class="btn ghost" href="#device-observability">日志与指标</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-grid" id="device-overview">
|
||||
<div class="card">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2 class="title-with-icon">{{icon "status"}}<span>设备状态</span></h2>
|
||||
<h2 class="title-with-icon">{{icon "overview"}}<span>概览</span></h2>
|
||||
</div>
|
||||
{{if .Device.Online}}<span class="pill ok">在线</span>{{else}}<span class="pill bad">离线</span>{{end}}
|
||||
</div>
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/alias" class="field-grid compact-form" style="margin-bottom:12px">
|
||||
<label class="full"><span>设备别名</span><input name="device_alias" value="{{.Device.DeviceAlias}}" placeholder="{{if .Device.Hostname}}{{.Device.Hostname}}{{else}}{{.Device.DeviceName}}{{end}}" /></label>
|
||||
<div class="actions"><button type="submit" class="secondary">{{icon "apply"}}<span>保存别名</span></button></div>
|
||||
</form>
|
||||
<div class="info-list">
|
||||
<div><span>当前业务配置</span><strong>{{if and .ConfigStatus .ConfigStatus.Metadata.BusinessName}}{{.ConfigStatus.Metadata.BusinessName}}{{else}}-{{end}}</strong></div>
|
||||
{{if displayDeviceTechnicalName .Device}}<div><span>设备主机名</span><strong>{{displayDeviceTechnicalName .Device}}</strong></div>{{end}}
|
||||
<div><span>设备名称</span><strong>{{displayDeviceName .Device .ConfigStatus}}</strong></div>
|
||||
<div><span>设备 ID</span><strong class="mono">{{.Device.DeviceID}}</strong></div>
|
||||
<div><span>管理地址</span><strong class="mono">{{.Device.IP}}:{{.Device.AgentPort}}</strong></div>
|
||||
<div><span>视频端口</span><strong class="mono">{{.Device.MediaPort}}</strong></div>
|
||||
<div><span>最后心跳</span><strong>{{ago .Device.LastSeenMs}}</strong></div>
|
||||
<div><span>版本</span><strong class="mono">{{if .Device.Version}}{{.Device.Version}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>Build ID</span><strong class="mono">{{if .Device.BuildID}}{{.Device.BuildID}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>当前业务配置</span><strong>{{if and .ConfigStatus .ConfigStatus.Metadata.BusinessName}}{{.ConfigStatus.Metadata.BusinessName}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>通道名</span><strong>{{if and .ConfigStatus .ConfigStatus.Metadata.InstanceName}}{{.ConfigStatus.Metadata.InstanceName}}{{else if .Device.InstanceName}}{{.Device.InstanceName}}{{else}}-{{end}}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,13 +70,10 @@
|
||||
<div class="info-list">
|
||||
<div><span>配置 ID</span><strong class="mono">{{if .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}未标记{{end}}</strong></div>
|
||||
<div><span>配置版本</span><strong class="mono">{{if .ConfigStatus.Metadata.ConfigVersion}}{{.ConfigStatus.Metadata.ConfigVersion}}{{else}}未标记{{end}}</strong></div>
|
||||
<div><span>业务名称</span><strong>{{if .ConfigStatus.Metadata.BusinessName}}{{.ConfigStatus.Metadata.BusinessName}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>模板</span><strong>{{if .ConfigStatus.Metadata.Template}}{{.ConfigStatus.Metadata.Template}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>业务配置</span><strong>{{if .ConfigStatus.Metadata.Profile}}{{.ConfigStatus.Metadata.Profile}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>配置叠加项</span><strong class="mono">{{if .ConfigStatus.Metadata.Overlays}}{{range $i, $name := .ConfigStatus.Metadata.Overlays}}{{if $i}}, {{end}}{{$name}}{{end}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>服务状态</span><strong>{{if .ConfigStatus.MediaServer.Running}}运行中{{else}}未运行{{end}}</strong></div>
|
||||
<div><span>叠加项</span><strong class="mono">{{if .ConfigStatus.Metadata.Overlays}}{{range $i, $name := .ConfigStatus.Metadata.Overlays}}{{if $i}}, {{end}}{{$name}}{{end}}{{else}}-{{end}}</strong></div>
|
||||
<div><span>配置文件</span><strong class="mono">{{.ConfigStatus.ConfigPath}}</strong></div>
|
||||
<div><span>SHA</span><strong class="mono">{{shortHash .ConfigStatus.Sha256}}</strong></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-state compact">
|
||||
@ -55,27 +84,95 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="section-title">
|
||||
<div class="control-grid" id="device-runtime">
|
||||
<section class="panel-block">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2 class="title-with-icon">{{icon "overview"}}<span>最近状态摘要</span></h2>
|
||||
<h3 class="title-with-icon">{{icon "service"}}<span>运行与服务</span></h3>
|
||||
</div>
|
||||
{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}<span class="pill ok">运行中</span>{{else}}<span class="pill warn">待确认</span>{{end}}
|
||||
</div>
|
||||
<div class="summary-strip">
|
||||
<div class="summary-chip">
|
||||
<div class="summary-chip-label">服务</div>
|
||||
<div class="summary-chip-value">{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}未确认{{end}}</div>
|
||||
<div class="info-list compact-list">
|
||||
<div><span>媒体服务</span><strong>{{if and .ConfigStatus .ConfigStatus.MediaServer.Running}}运行中{{else}}未确认{{end}}</strong></div>
|
||||
<div><span>上一份配置</span><strong class="mono">{{if and .ConfigStatus .ConfigStatus.PreviousConfig .ConfigStatus.PreviousConfig.Metadata.ConfigID}}{{.ConfigStatus.PreviousConfig.Metadata.ConfigID}}{{else}}-{{end}}</strong></div>
|
||||
</div>
|
||||
<div class="summary-chip">
|
||||
<div class="summary-chip-label">配置</div>
|
||||
<div class="summary-chip-value">{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}待读取{{end}}</div>
|
||||
<div class="actions stack">
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/action">
|
||||
<input type="hidden" name="action" value="media_start" />
|
||||
<input type="hidden" name="return_to" value="config" />
|
||||
<button type="submit" class="secondary">启动服务</button>
|
||||
</form>
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/action">
|
||||
<input type="hidden" name="action" value="media_restart" />
|
||||
<input type="hidden" name="return_to" value="config" />
|
||||
<button type="submit" class="primary">重启服务</button>
|
||||
</form>
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/action">
|
||||
<input type="hidden" name="action" value="media_stop" />
|
||||
<input type="hidden" name="return_to" value="config" />
|
||||
<button type="submit" class="danger">停止服务</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="summary-chip">
|
||||
<div class="summary-chip-label">版本</div>
|
||||
<div class="summary-chip-value">{{if .Device.Version}}{{.Device.Version}}{{else}}-{{end}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{template "device_panel_end" .}}
|
||||
<section class="panel-block" id="device-config">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h3 class="title-with-icon">{{icon "apply"}}<span>设备配置</span></h3>
|
||||
</div>
|
||||
<a class="btn secondary" href="/ui/devices/{{.Device.DeviceID}}/config-preview">{{icon "preview"}}<span>编辑和上传候选配置</span></a>
|
||||
</div>
|
||||
<div class="info-list compact-list">
|
||||
<div><span>当前配置</span><strong class="mono">{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}{{.ConfigStatus.Metadata.ConfigID}}{{else}}待读取{{end}}</strong></div>
|
||||
<div><span>候选配置</span><strong class="mono">{{if and .ConfigStatus .ConfigStatus.Candidate .ConfigStatus.Candidate.Exists}}{{if .ConfigStatus.Candidate.Metadata.ConfigID}}{{.ConfigStatus.Candidate.Metadata.ConfigID}}{{else}}已存在{{end}}{{else}}未上传{{end}}</strong></div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/config-candidate/apply">
|
||||
<input type="hidden" name="return_to" value="config" />
|
||||
<button type="submit" class="primary">应用候选配置</button>
|
||||
</form>
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/action">
|
||||
<input type="hidden" name="action" value="rollback" />
|
||||
<input type="hidden" name="return_to" value="config" />
|
||||
<button type="submit" class="secondary">回滚到上一份</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-block" id="device-models">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h3 class="title-with-icon">{{icon "assets"}}<span>模型与资源</span></h3>
|
||||
</div>
|
||||
<a class="btn secondary" href="/ui/models">进入模型管理</a>
|
||||
</div>
|
||||
<div class="info-list compact-list">
|
||||
<div><span>模型入口</span><strong>通过模型管理页上传到设备</strong></div>
|
||||
<div><span>人脸库</span><strong>通过识别配置与资源页维护</strong></div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<form method="post" action="/ui/devices/{{.Device.DeviceID}}/face-gallery/reload">
|
||||
<button type="submit" class="secondary">重载人脸库</button>
|
||||
</form>
|
||||
<a class="btn ghost" href="/ui/assets">查看识别配置资产</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-block" id="device-observability">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h3 class="title-with-icon">{{icon "result"}}<span>日志与指标</span></h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions stack">
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}/logs?limit=200">诊断日志</a>
|
||||
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}/graphs">运行指标</a>
|
||||
<a class="btn ghost" href="/ui/api">高级调试</a>
|
||||
</div>
|
||||
{{if .ResultTitle}}<div class="muted small" style="margin-top:12px">{{.ResultTitle}}</div>{{end}}
|
||||
{{if .Message}}<div class="msg">{{.Message}}</div>{{end}}
|
||||
{{if .Error}}<div class="error">{{.Error}}</div>{{end}}
|
||||
{{if .RawText}}<pre style="margin-top:10px">{{.RawText}}</pre>{{end}}
|
||||
</section>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@ -2,62 +2,36 @@
|
||||
<div class="card">
|
||||
<div class="section-title">
|
||||
<div>
|
||||
<h2>配置管理</h2>
|
||||
<div class="muted small">面向单台设备的设备运行配置、服务状态和服务管理。</div>
|
||||
<h2>单设备配置已并入设备详情</h2>
|
||||
<div class="muted small">配置查看、服务控制、候选配置应用和回滚,都已经收敛到设备详情工作台。</div>
|
||||
</div>
|
||||
<a class="btn ghost" href="/ui/devices">返回设备列表</a>
|
||||
<a class="btn ghost" href="/ui/devices">去设备列表</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>设备运行配置</h2>
|
||||
<div class="table-wrap" style="margin-top:10px">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>设备</th><th>状态</th><th>管理地址</th><th>服务状态</th><th>配置操作</th><th>服务管理</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<h2>设备入口</h2>
|
||||
<div class="device-selector-grid" style="margin-top:12px">
|
||||
{{range .Devices}}
|
||||
<tr>
|
||||
<td><a class="mono" href="/ui/devices/{{.DeviceID}}">{{if .DeviceName}}{{.DeviceName}}{{else}}{{.DeviceID}}{{end}}</a><div class="muted small mono">{{.DeviceID}}</div></td>
|
||||
<td>{{if .Online}}<span class="pill ok">在线</span>{{else}}<span class="pill bad">离线</span>{{end}}</td>
|
||||
<td class="mono">{{.IP}}:{{.AgentPort}}</td>
|
||||
<td>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/action">
|
||||
<input type="hidden" name="action" value="media_status" />
|
||||
<button type="submit" class="btn ghost">查看服务状态</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<a class="btn ghost" href="/ui/devices/{{.DeviceID}}/config-ui">配置查看</a>
|
||||
<a class="btn ghost" href="/ui/devices/{{.DeviceID}}/config-friendly">配置修改</a>
|
||||
<a class="selector-card" href="/ui/devices/{{.DeviceID}}#device-config">
|
||||
<div class="selector-head">
|
||||
<div>
|
||||
<div class="selector-title">{{if .DeviceName}}{{.DeviceName}}{{else}}{{.DeviceID}}{{end}}</div>
|
||||
<div class="muted small mono">{{.DeviceID}}</div>
|
||||
</div>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/media-server/configs/upload" enctype="multipart/form-data" class="row compact-form">
|
||||
<input name="name" placeholder="方案名" />
|
||||
<input type="file" name="file" accept="application/json,.json" />
|
||||
<button type="submit" class="btn ghost">配置上传</button>
|
||||
</form>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/media-server/configs/upload-batch" enctype="multipart/form-data" class="row compact-form">
|
||||
<input type="file" name="files" multiple accept="application/json,.json" />
|
||||
<button type="submit" class="btn ghost">批量上传</button>
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/action"><input type="hidden" name="action" value="media_start" /><button type="submit" class="btn ghost">启动视频分析</button></form>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/action"><input type="hidden" name="action" value="media_restart" /><button type="submit" class="btn ghost">重启视频分析</button></form>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/action"><input type="hidden" name="action" value="media_stop" /><button type="submit" class="btn ghost">停止视频分析</button></form>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/action"><input type="hidden" name="action" value="reload" /><button type="submit" class="btn ghost">重载识别服务</button></form>
|
||||
<form method="post" action="/ui/devices/{{.DeviceID}}/action"><input type="hidden" name="action" value="rollback" /><button type="submit" class="btn ghost">回滚识别配置</button></form>
|
||||
{{if .Online}}<span class="pill ok">在线</span>{{else}}<span class="pill bad">离线</span>{{end}}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<div class="muted small mono">{{.IP}}:{{.AgentPort}}</div>
|
||||
<div class="actions" style="margin-top:12px">
|
||||
<span class="btn secondary">打开设备详情</span>
|
||||
</div>
|
||||
</a>
|
||||
{{else}}
|
||||
<tr><td colspan="6" class="muted">暂无设备。请先在“新增设备”页扫描或手动添加。</td></tr>
|
||||
<div class="empty-state">
|
||||
<div class="empty-title">暂无设备</div>
|
||||
<div class="muted">请先去设备页扫描或新增设备。</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@ -2155,3 +2155,19 @@ func TestUI_DeviceDetailActsAsSingleDeviceWorkspace(t *testing.T) {
|
||||
t.Fatalf("device detail should not contain cross-module config shortcut: %s", html)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUI_DeviceWorkspaceContainsSingleDeviceOperations(t *testing.T) {
|
||||
ui := newTestUI(t)
|
||||
html := renderPage(t, ui, "/ui/devices/edge-01")
|
||||
|
||||
for _, text := range []string{"概览", "运行与服务", "设备配置", "模型与资源", "日志与指标"} {
|
||||
if !strings.Contains(html, text) {
|
||||
t.Fatalf("expected device workspace section %q in html: %s", text, html)
|
||||
}
|
||||
}
|
||||
for _, removed := range []string{"返回设备选择", "配置管理面向单台设备"} {
|
||||
if strings.Contains(html, removed) {
|
||||
t.Fatalf("did not expect legacy standalone config flow text %q in html: %s", removed, html)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user