174 lines
6.4 KiB
HTML
174 lines
6.4 KiB
HTML
{{define "api"}}
|
||
<div class="card">
|
||
<div class="muted">同域调用(不需要独立前端构建)。OpenAPI:<a class="mono" href="/openapi.json">/openapi.json</a></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>常用变量</h2>
|
||
<div class="row" style="margin-top:8px">
|
||
<div>
|
||
<div class="muted small">device_id</div>
|
||
<input id="deviceId" placeholder="..." />
|
||
</div>
|
||
<div>
|
||
<div class="muted small">task_id(用于 SSE)</div>
|
||
<input id="taskId" placeholder="..." />
|
||
</div>
|
||
<div>
|
||
<div class="muted small">template name</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">method</div>
|
||
<select id="method">
|
||
<option>GET</option>
|
||
<option>POST</option>
|
||
<option>PUT</option>
|
||
<option>DELETE</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<div class="muted small">path</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">body(JSON,GET/DELETE 会忽略)</div>
|
||
<textarea id="body" spellcheck="false">{}</textarea>
|
||
</div>
|
||
<div class="row" style="margin-top:10px">
|
||
<div>
|
||
<div class="muted small">status</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>SSE</h2>
|
||
<div class="muted small">订阅:<code class="mono">/api/tasks/{id}/events</code></div>
|
||
<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>
|
||
<div class="muted small"><code class="mono">/api/devices/{id}/models/upload</code></div>
|
||
<form id="uploadForm" class="row" style="margin-top:8px" onsubmit="return uploadModel()">
|
||
<div>
|
||
<div class="muted small">name</div>
|
||
<input name="name" placeholder="模型名" />
|
||
</div>
|
||
<div>
|
||
<div class="muted small">file</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('请先填写 task_id'); 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('device_id required'); 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}}
|