Replace monolithic CLI and web server with route-based Flask API. New routes: api_commands, api_build, api_can, api_monitor, api_ota, api_tunnel. Add honeypot security dashboard with real-time SSE, MITRE ATT&CK mapping, kill chain analysis. New TUI with commander/help modules. Add session management, tunnel proxy core, CAN bus data store. Docker support.
137 lines
5.5 KiB
HTML
137 lines
5.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}{{ device_id }} - ESPILON{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page" x-data="deviceDetail()" x-init="init()">
|
|
|
|
<!-- Top: Device Info -->
|
|
<div class="panel">
|
|
<div class="panel-header">
|
|
<span>
|
|
<a href="/dashboard" style="color:var(--text-muted);">←</a>
|
|
{{ device_id }}
|
|
<span class="badge" :class="dev.status==='Connected' ? 'badge-ok' : 'badge-warn'" x-text="dev.status || '--'"></span>
|
|
</span>
|
|
</div>
|
|
<div class="panel-body">
|
|
<div class="kv">
|
|
<div class="kv-row"><span class="kv-key">ID</span><span class="kv-val">{{ device_id }}</span></div>
|
|
<div class="kv-row"><span class="kv-key">IP</span><span class="kv-val" x-text="dev.ip || '--'"></span></div>
|
|
<div class="kv-row"><span class="kv-key">Port</span><span class="kv-val" x-text="dev.port || '--'"></span></div>
|
|
<div class="kv-row"><span class="kv-key">Chip</span><span class="kv-val" x-text="dev.chip || '--'"></span></div>
|
|
<div class="kv-row"><span class="kv-key">Modules</span><span class="kv-val" x-text="dev.modules || '--'"></span></div>
|
|
<div class="kv-row"><span class="kv-key">Uptime</span><span class="kv-val" x-text="formatDuration(dev.connected_for_seconds)"></span></div>
|
|
<div class="kv-row"><span class="kv-key">Last Seen</span><span class="kv-val" x-text="formatDuration(dev.last_seen_ago_seconds) + ' ago'"></span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resizer -->
|
|
<div class="resizer resizer-h"></div>
|
|
|
|
<!-- Bottom: Command -->
|
|
<div class="panel" style="flex:1;">
|
|
<div class="panel-header">
|
|
<span>Command</span>
|
|
</div>
|
|
<div class="panel-body" style="display:flex;flex-direction:column;">
|
|
<div class="term-output" id="cmd-output" style="flex:1;">
|
|
<template x-for="(entry, i) in log" :key="i">
|
|
<div class="term-line">
|
|
<span class="term-cmd" x-text="'> ' + entry.cmd"></span>
|
|
<template x-if="entry.status === 'pending'">
|
|
<span class="term-pending"> pending...</span>
|
|
</template>
|
|
<template x-if="entry.output.length > 0">
|
|
<div>
|
|
<template x-for="(line, j) in entry.output" :key="j">
|
|
<div x-text="line"></div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
<template x-if="entry.status === 'completed' && entry.output.length === 0">
|
|
<span class="term-success"> OK</span>
|
|
</template>
|
|
<template x-if="entry.status === 'error'">
|
|
<span class="term-error"> error</span>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="term-input-row">
|
|
<span class="term-prompt">{{ device_id }}></span>
|
|
<input type="text" class="term-input" x-model="cmdText" @keydown.enter="send()"
|
|
placeholder="system_info" autocomplete="off" spellcheck="false">
|
|
<button class="btn btn-primary btn-sm" @click="send()" style="margin-left:8px;">Send</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function deviceDetail() {
|
|
const DEVICE_ID = '{{ device_id }}';
|
|
return {
|
|
...commander(),
|
|
dev: {},
|
|
cmdText: '',
|
|
log: [],
|
|
|
|
async init() {
|
|
this.loadDevice();
|
|
setInterval(() => this.loadDevice(), 5000);
|
|
},
|
|
|
|
async loadDevice() {
|
|
try {
|
|
const res = await fetch('/api/devices');
|
|
const data = await res.json();
|
|
const d = (data.devices || []).find(d => d.id === DEVICE_ID);
|
|
if (d) this.dev = d;
|
|
} catch (e) {}
|
|
this.updatePending();
|
|
},
|
|
|
|
async send() {
|
|
const line = this.cmdText.trim();
|
|
if (!line) return;
|
|
const parts = line.split(/\s+/);
|
|
const cmd = parts[0];
|
|
const argv = parts.slice(1);
|
|
this.cmdText = '';
|
|
|
|
const entry = { cmd: line, status: 'pending', output: [], requestId: null };
|
|
this.log.push(entry);
|
|
|
|
try {
|
|
const data = await this.sendCommand([DEVICE_ID], cmd, argv);
|
|
const r = (data.results || [])[0];
|
|
if (r && r.status === 'ok') {
|
|
entry.requestId = r.request_id;
|
|
} else {
|
|
entry.status = 'error';
|
|
entry.output = [r ? r.message : 'unknown error'];
|
|
}
|
|
} catch (e) {
|
|
entry.status = 'error';
|
|
entry.output = [e.message];
|
|
}
|
|
},
|
|
|
|
updatePending() {
|
|
for (const entry of this.log) {
|
|
if (entry.status !== 'pending' || !entry.requestId) continue;
|
|
const p = this.cmdPending[entry.requestId];
|
|
if (p) {
|
|
entry.output = p.output;
|
|
if (p.status !== 'pending') entry.status = p.status;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|