161 lines
6.3 KiB
HTML
161 lines
6.3 KiB
HTML
{{define "devices"}}
|
|
<div class="stats">
|
|
<div class="stat accent-teal">
|
|
<div class="k metric-label">{{icon "devices"}}<span>设备总数</span></div>
|
|
<div class="v">{{.DeviceCount}}</div>
|
|
<div class="hint">当前纳管设备</div>
|
|
</div>
|
|
<div class="stat accent-green">
|
|
<div class="k metric-label">{{icon "online"}}<span>在线设备</span></div>
|
|
<div class="v">{{.OnlineCount}}</div>
|
|
<div class="hint">可接收状态与控制</div>
|
|
</div>
|
|
<div class="stat accent-slate">
|
|
<div class="k">离线设备</div>
|
|
<div class="v">{{.OfflineCount}}</div>
|
|
<div class="hint">需要检查网络或进程</div>
|
|
</div>
|
|
<div class="stat accent-amber">
|
|
<div class="k">失败操作</div>
|
|
<div class="v">{{.FailedTaskCount}}</div>
|
|
<div class="hint">最近任务失败数</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="section-title">
|
|
<div>
|
|
<h2>设备列表</h2>
|
|
</div>
|
|
<div class="table-tools">
|
|
<input id="device-filter" placeholder="搜索设备名、ID 或 IP" />
|
|
</div>
|
|
</div>
|
|
|
|
<form method="post" action="/ui/devices/batch-action">
|
|
{{if .SelectedDeviceIDs}}
|
|
<div class="batch-toolbar" id="batch-config">
|
|
<div>
|
|
<div class="batch-toolbar-count">已选 {{len .SelectedDeviceIDs}} 台</div>
|
|
</div>
|
|
<div class="actions">
|
|
<button type="submit" name="action" value="media_restart" class="primary">重启服务</button>
|
|
<button type="submit" name="action" value="media_start" class="secondary">启动服务</button>
|
|
<button type="submit" name="action" value="media_stop" class="danger">停止服务</button>
|
|
<button type="submit" name="action" value="reload" class="secondary" {{if .ReloadSummary}}onclick='return confirm("将重载当前场景配置:{{.ReloadSummary}}")'{{end}}>重载配置</button>
|
|
<button type="submit" name="action" value="rollback" class="secondary" {{if .RollbackSummary}}onclick='return confirm("将回滚到上一版场景配置:{{.RollbackSummary}}")'{{end}}>回滚配置</button>
|
|
<a class="btn secondary" href="{{.BatchConfigURL}}">下发场景配置</a>
|
|
<a class="btn secondary" href="/ui/devices">清空选择</a>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="table-wrap">
|
|
<table id="device-list">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:52px">选中</th>
|
|
<th>设备</th>
|
|
<th>状态</th>
|
|
<th>当前配置</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .DeviceRows}}
|
|
<tr>
|
|
<td class="select-cell">
|
|
<input type="checkbox" name="device_id" value="{{.Device.DeviceID}}" {{if hasString $.SelectedDeviceIDs .Device.DeviceID}}checked{{end}} />
|
|
</td>
|
|
<td>
|
|
<div class="device-cell">
|
|
<div class="device-avatar">{{icon "device"}}</div>
|
|
<div>
|
|
<div class="device-name">{{displayDeviceName .Device .ConfigStatus}}</div>
|
|
<div class="device-meta-line">
|
|
{{if displayDeviceTechnicalName .Device}}<span>{{displayDeviceTechnicalName .Device}}</span>{{end}}
|
|
<span class="mono">{{.Device.IP}}</span>
|
|
{{if .Device.Version}}<span class="mono">{{.Device.Version}}</span>{{end}}
|
|
{{if .Device.BuildID}}<span class="mono">#{{shortHash .Device.BuildID}}</span>{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="state-stack">
|
|
<div class="state-row">
|
|
{{if .Device.Online}}<span class="status-dot ok"></span><span class="status-text">在线</span>{{else}}<span class="status-dot bad"></span><span class="status-text">离线</span>{{end}}
|
|
{{if .ConfigStatus}}
|
|
{{if .ConfigStatus.MediaServer.Running}}<span class="pill ok">运行中</span>{{else}}<span class="pill bad">未运行</span>{{end}}
|
|
{{else if .Device.Online}}
|
|
<span class="pill warn">待确认</span>
|
|
{{else}}
|
|
<span class="pill bad">未知</span>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="config-inline">
|
|
{{if and .ConfigStatus .ConfigStatus.Metadata.ConfigID}}
|
|
<div class="mono">{{.ConfigStatus.Metadata.ConfigID}}</div>
|
|
{{else if .ConfigStatusErr}}
|
|
<div class="muted small">未取到配置摘要</div>
|
|
{{else}}
|
|
<div class="muted small">暂无配置摘要</div>
|
|
{{end}}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="actions">
|
|
<a class="btn ghost" href="/ui/devices/{{.Device.DeviceID}}">{{icon "detail"}}<span>详情</span></a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{else}}
|
|
<tr>
|
|
<td colspan="5">
|
|
<div class="empty-state">
|
|
<div class="empty-title">还没有设备</div>
|
|
<div class="muted">当前后台还没有发现或录入任何设备。</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
(() => {
|
|
const input = document.getElementById('device-filter');
|
|
const table = document.getElementById('device-list');
|
|
const selectedBoxes = table ? table.querySelectorAll('input[type="checkbox"][name="device_id"]') : [];
|
|
if (!input || !table) return;
|
|
input.addEventListener('input', () => {
|
|
const q = (input.value || '').trim().toLowerCase();
|
|
for (const row of table.tBodies[0].rows) {
|
|
const text = row.innerText.toLowerCase();
|
|
row.style.display = (!q || text.includes(q)) ? '' : 'none';
|
|
}
|
|
});
|
|
const syncSelected = () => {
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.delete('selected');
|
|
for (const box of selectedBoxes) {
|
|
if (box.checked) {
|
|
url.searchParams.append('selected', box.value);
|
|
}
|
|
}
|
|
const next = `${url.pathname}${url.searchParams.toString() ? `?${url.searchParams.toString()}` : ''}`;
|
|
window.location.assign(next);
|
|
};
|
|
for (const box of selectedBoxes) {
|
|
box.addEventListener('change', syncSelected);
|
|
}
|
|
})();
|
|
</script>
|
|
{{end}}
|