107 lines
3.7 KiB
HTML
107 lines
3.7 KiB
HTML
{{define "task"}}
|
||
<div class="card">
|
||
<div><a href="/ui/tasks">← 返回任务列表</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>任务详情</h2>
|
||
<div class="row" style="margin-top:10px">
|
||
<div>
|
||
<div class="muted small">任务ID</div>
|
||
<div class="mono"><b>{{.Task.ID}}</b></div>
|
||
</div>
|
||
<div>
|
||
<div class="muted small">类型</div>
|
||
<div class="mono">{{.Task.Type}}</div>
|
||
</div>
|
||
<div>
|
||
<div class="muted small">状态</div>
|
||
<div>
|
||
{{if eq .Task.Status "success"}}<span class="pill ok">成功</span>
|
||
{{else if eq .Task.Status "failed"}}<span class="pill bad">失败</span>
|
||
{{else if eq .Task.Status "running"}}<span class="pill warn">执行中</span>
|
||
{{else}}<span class="pill">待执行</span>{{end}}
|
||
<span class="muted small mono" style="margin-left:8px">({{.Task.Status}})</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>设备执行情况</h2>
|
||
<div class="table-wrap" style="margin-top:10px">
|
||
<table>
|
||
<thead>
|
||
<tr><th>设备ID</th><th>状态</th><th>进度</th><th>错误</th></tr>
|
||
</thead>
|
||
<tbody id="devices-body">
|
||
{{range $id, $st := .Task.Devices}}
|
||
<tr data-device-id="{{$id}}">
|
||
<td class="mono">{{$id}}</td>
|
||
<td class="st">
|
||
{{if eq $st.Status "success"}}<span class="pill ok">成功</span>
|
||
{{else if eq $st.Status "failed"}}<span class="pill bad">失败</span>
|
||
{{else if eq $st.Status "running"}}<span class="pill warn">执行中</span>
|
||
{{else}}<span class="pill">待执行</span>{{end}}
|
||
</td>
|
||
<td class="pg mono">{{$st.Progress}}</td>
|
||
<td class="er">{{$st.Error}}</td>
|
||
</tr>
|
||
{{end}}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>实时推送(SSE)</h2>
|
||
<div class="muted small">订阅:<code class="mono">/api/tasks/{{.TaskID}}/events</code></div>
|
||
<div class="actions" style="margin-top:8px">
|
||
<button type="button" onclick="startSSE()">连接</button>
|
||
<button type="button" onclick="stopSSE()">断开</button>
|
||
<span class="muted small" id="sse-status">未连接</span>
|
||
</div>
|
||
<pre id="sse-log" style="margin-top:10px;max-height:320px"></pre>
|
||
</div>
|
||
|
||
<script>
|
||
let es;
|
||
function pill(status){
|
||
if(status === 'success') return '<span class="pill ok">成功</span>';
|
||
if(status === 'failed') return '<span class="pill bad">失败</span>';
|
||
if(status === 'running') return '<span class="pill warn">执行中</span>';
|
||
return '<span class="pill">待执行</span>';
|
||
}
|
||
function startSSE(){
|
||
stopSSE();
|
||
const url = `/api/tasks/{{.TaskID}}/events`;
|
||
es = new EventSource(url);
|
||
document.getElementById('sse-status').textContent = '连接中...';
|
||
es.onopen = () => { document.getElementById('sse-status').textContent = '已连接'; };
|
||
es.onerror = () => { document.getElementById('sse-status').textContent = '连接异常(自动重试)'; };
|
||
es.addEventListener('device_update', (ev) => {
|
||
const line = ev.data;
|
||
const log = document.getElementById('sse-log');
|
||
log.textContent += line + "\n";
|
||
log.scrollTop = log.scrollHeight;
|
||
try {
|
||
const u = JSON.parse(line);
|
||
const tr = document.querySelector(`tr[data-device-id="${cssEscape(u.device_id)}"]`);
|
||
if(tr){
|
||
tr.querySelector('.st').innerHTML = pill(u.status);
|
||
tr.querySelector('.pg').textContent = u.progress;
|
||
tr.querySelector('.er').textContent = u.error || '';
|
||
}
|
||
} catch(e){}
|
||
});
|
||
}
|
||
function stopSSE(){
|
||
if(es){ es.close(); es = null; }
|
||
document.getElementById('sse-status').textContent = '未连接';
|
||
}
|
||
function cssEscape(s){
|
||
return String(s).replace(/[^a-zA-Z0-9_-]/g, (c) => "\\"+c);
|
||
}
|
||
</script>
|
||
{{end}}
|