173 lines
6.2 KiB
HTML
173 lines
6.2 KiB
HTML
{{define "api"}}
|
||
<div class="card">
|
||
<div class="crumb">诊断 / 高级调试</div>
|
||
<h2>高级调试</h2>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>常用变量</h2>
|
||
<div class="row" style="margin-top:8px">
|
||
<div>
|
||
<div class="muted small">节点标识</div>
|
||
<input id="deviceId" placeholder="..." />
|
||
</div>
|
||
<div>
|
||
<div class="muted small">任务标识</div>
|
||
<input id="taskId" placeholder="..." />
|
||
</div>
|
||
<div>
|
||
<div class="muted small">模板名称</div>
|
||
<input id="tplName" placeholder="..." />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>发起请求</h2>
|
||
<div class="row" style="margin-top:8px">
|
||
<div style="max-width:160px">
|
||
<div class="muted small">请求方法</div>
|
||
<select id="method">
|
||
<option>GET</option>
|
||
<option>POST</option>
|
||
<option>PUT</option>
|
||
<option>DELETE</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div class="muted small">接口路径</div>
|
||
<input id="path" value="/api/devices" />
|
||
</div>
|
||
<div style="align-self:end">
|
||
<button type="button" onclick="doCall()">发送</button>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:10px">
|
||
<div class="muted small">请求体(JSON,GET/DELETE 会忽略)</div>
|
||
<textarea id="body" spellcheck="false">{}</textarea>
|
||
</div>
|
||
<div class="row" style="margin-top:10px">
|
||
<div>
|
||
<div class="muted small">响应状态</div>
|
||
<input id="status" readonly />
|
||
</div>
|
||
</div>
|
||
<pre id="resp"></pre>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>快速预设</h2>
|
||
<div class="actions" style="margin-top:8px">
|
||
<button type="button" onclick="preset('POST','/api/discovery/search', JSON.stringify({timeout_ms:1200}))">扫描节点</button>
|
||
<button type="button" onclick="preset('GET','/api/devices','')">节点列表</button>
|
||
<button type="button" onclick="preset('GET','/api/devices/' + v('deviceId'),'')">节点详情</button>
|
||
<button type="button" onclick="preset('POST','/api/devices/' + v('deviceId') + '/reload','')">重载识别服务</button>
|
||
<button type="button" onclick="preset('POST','/api/devices/' + v('deviceId') + '/rollback','')">回滚识别配置</button>
|
||
<button type="button" onclick="preset('GET','/api/devices/' + v('deviceId') + '/graphs','')">运行指标</button>
|
||
<button type="button" onclick="preset('GET','/api/devices/' + v('deviceId') + '/logs?limit=200','')">诊断日志</button>
|
||
<button type="button" onclick="preset('POST','/api/devices/' + v('deviceId') + '/config/apply', JSON.stringify({config:{}}))">部署配置</button>
|
||
<button type="button" onclick="preset('GET','/api/templates','')">配置模板</button>
|
||
<button type="button" onclick="preset('GET','/api/templates/' + v('tplName'),'')">模板详情</button>
|
||
<button type="button" onclick="preset('GET','/api/tasks','')">任务接口</button>
|
||
<button type="button" onclick="preset('POST','/api/tasks', JSON.stringify({type:'config_apply', device_ids:[v('deviceId')], payload:{config:{}}}))">创建任务</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>任务实时事件</h2>
|
||
<div class="actions" style="margin-top:8px">
|
||
<button type="button" onclick="connectSSE()">连接</button>
|
||
<button type="button" onclick="disconnectSSE()">断开</button>
|
||
<span class="muted" id="sseState">未连接</span>
|
||
</div>
|
||
<pre id="sseOut" style="margin-top:10px;max-height:320px"></pre>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>模型上传调试</h2>
|
||
<form id="uploadForm" class="row" style="margin-top:8px" onsubmit="return uploadModel()">
|
||
<div>
|
||
<div class="muted small">模型名称</div>
|
||
<input name="name" placeholder="模型名" />
|
||
</div>
|
||
<div>
|
||
<div class="muted small">模型文件</div>
|
||
<input type="file" name="file" />
|
||
</div>
|
||
<div style="align-self:end">
|
||
<button type="submit">上传</button>
|
||
</div>
|
||
</form>
|
||
<pre id="uploadResp"></pre>
|
||
</div>
|
||
|
||
<script>
|
||
let es;
|
||
function v(id){ return (document.getElementById(id).value || '').trim(); }
|
||
function preset(m,p,b){
|
||
document.getElementById('method').value = m;
|
||
document.getElementById('path').value = p;
|
||
document.getElementById('body').value = b || '';
|
||
}
|
||
async function doCall(){
|
||
const method = document.getElementById('method').value;
|
||
const path = document.getElementById('path').value;
|
||
const body = document.getElementById('body').value;
|
||
const opts = { method, headers: {} };
|
||
if(method !== 'GET' && method !== 'DELETE'){
|
||
opts.headers['Content-Type'] = 'application/json';
|
||
opts.body = body || '{}';
|
||
}
|
||
const respBox = document.getElementById('resp');
|
||
respBox.textContent = '...';
|
||
try{
|
||
const res = await fetch(path, opts);
|
||
document.getElementById('status').value = res.status;
|
||
const txt = await res.text();
|
||
respBox.textContent = pretty(txt);
|
||
}catch(e){
|
||
document.getElementById('status').value = 'ERR';
|
||
respBox.textContent = String(e);
|
||
}
|
||
}
|
||
function pretty(txt){
|
||
try{ return JSON.stringify(JSON.parse(txt), null, 2); }catch(e){ return txt; }
|
||
}
|
||
function connectSSE(){
|
||
disconnectSSE();
|
||
const id = v('taskId');
|
||
if(!id){ alert('请先填写任务标识'); return; }
|
||
const url = `/api/tasks/${id}/events`;
|
||
es = new EventSource(url);
|
||
document.getElementById('sseState').textContent = '连接中...';
|
||
es.onopen = () => document.getElementById('sseState').textContent = '已连接';
|
||
es.onerror = () => document.getElementById('sseState').textContent = '连接异常(自动重试)';
|
||
es.addEventListener('device_update', (ev) => {
|
||
const out = document.getElementById('sseOut');
|
||
out.textContent += ev.data + "\n";
|
||
out.scrollTop = out.scrollHeight;
|
||
});
|
||
}
|
||
function disconnectSSE(){
|
||
if(es){ es.close(); es = null; }
|
||
document.getElementById('sseState').textContent = '未连接';
|
||
}
|
||
async function uploadModel(){
|
||
const did = v('deviceId');
|
||
if(!did){ alert('请先填写节点标识'); return false; }
|
||
const form = document.getElementById('uploadForm');
|
||
const fd = new FormData(form);
|
||
const box = document.getElementById('uploadResp');
|
||
box.textContent = '...';
|
||
try{
|
||
const res = await fetch(`/api/devices/${did}/models/upload`, { method: 'POST', body: fd });
|
||
const txt = await res.text();
|
||
box.textContent = `status ${res.status}\n` + pretty(txt);
|
||
}catch(e){
|
||
box.textContent = String(e);
|
||
}
|
||
return false;
|
||
}
|
||
</script>
|
||
{{end}}
|