3588AdminBackend/internal/web/ui/templates/api.html

173 lines
6.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{{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">请求体JSONGET/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}}