espilon-source/tools/C3PO/templates/device.html
Eun0us 79c2a4d4bf c3po: full server rewrite with modular routes and honeypot dashboard
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.
2026-02-28 20:12:27 +01:00

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);">&larr;</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 }}&gt;</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 %}