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.
126 lines
5.6 KiB
HTML
126 lines
5.6 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Network - ESPILON{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page" x-data="networkApp()" x-init="init()">
|
|
<div class="split-h" style="flex:1;">
|
|
|
|
<!-- Left: Command forms -->
|
|
<div class="panel" style="width:360px;min-width:280px;">
|
|
<div class="panel-header">
|
|
<span>Network Commands</span>
|
|
</div>
|
|
<div class="panel-body panel-body-pad" style="overflow-y:auto;">
|
|
<div class="form-group">
|
|
<label>Target Device</label>
|
|
<select class="select w-full" x-model="device">
|
|
<option value="">select device...</option>
|
|
<template x-for="d in $store.app.connectedDevices()" :key="d.id">
|
|
<option :value="d.id" x-text="d.id"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Ping -->
|
|
<div style="border-bottom:1px solid var(--border-subtle);padding-bottom:12px;margin-bottom:12px;">
|
|
<div class="form-row">
|
|
<span class="form-label">Ping</span>
|
|
<input type="text" class="input flex-1" x-model="pingHost" placeholder="8.8.8.8">
|
|
<button class="btn btn-sm btn-primary" @click="run('ping', [pingHost])">Go</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ARP Scan -->
|
|
<div style="border-bottom:1px solid var(--border-subtle);padding-bottom:12px;margin-bottom:12px;">
|
|
<div class="form-row">
|
|
<span class="form-label">ARP Scan</span>
|
|
<button class="btn btn-sm btn-primary" @click="run('arp_scan')">Scan</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DoS -->
|
|
<div>
|
|
<div class="form-row">
|
|
<span class="form-label">DoS TCP</span>
|
|
<input type="text" class="input" x-model="dosIp" placeholder="target IP" style="width:100px;">
|
|
<input type="text" class="input" x-model="dosPort" placeholder="port" style="width:60px;">
|
|
<input type="text" class="input" x-model="dosCount" placeholder="count" style="width:60px;">
|
|
<button class="btn btn-sm btn-danger" @click="run('dos_tcp', [dosIp, dosPort, dosCount])">Go</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="resizer"></div>
|
|
|
|
<!-- Right: Output -->
|
|
<div class="panel flex-1">
|
|
<div class="panel-header">
|
|
<span>Output</span>
|
|
<button class="btn btn-sm" @click="outputLines = []">Clear</button>
|
|
</div>
|
|
<div class="term-output" style="flex:1;">
|
|
<template x-for="(l, i) in outputLines" :key="i">
|
|
<div class="term-line" :class="l.cls || ''" x-html="l.html"></div>
|
|
</template>
|
|
<template x-if="outputLines.length === 0">
|
|
<div class="term-line term-system">Run a command to see output here.</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function networkApp() {
|
|
return {
|
|
...commander(),
|
|
device: '',
|
|
outputLines: [],
|
|
pingHost: '',
|
|
dosIp: '', dosPort: '', dosCount: '100',
|
|
|
|
init() {},
|
|
|
|
async run(command, argv) {
|
|
if (!this.device) { toast('Select a device', 'error'); return; }
|
|
argv = (argv || []).filter(Boolean);
|
|
this.outputLines.push({ html: '<span class="term-cmd">' + escapeHtml(this.device) + '> ' + escapeHtml(command + ' ' + argv.join(' ')) + '</span>' });
|
|
|
|
try {
|
|
const data = await this.sendCommand([this.device], command, argv);
|
|
const r = (data.results || [])[0];
|
|
if (r && r.status === 'ok' && r.request_id) {
|
|
this.outputLines.push({ html: '<span class="term-pending">pending...</span>', cls: '' });
|
|
const lineIdx = this.outputLines.length - 1;
|
|
let attempts = 0;
|
|
const iv = setInterval(async () => {
|
|
attempts++;
|
|
try {
|
|
const res = await fetch('/api/commands/' + encodeURIComponent(r.request_id));
|
|
const d = await res.json();
|
|
if (d.status === 'completed' || d.status === 'error' || attempts >= 60) {
|
|
clearInterval(iv);
|
|
this.outputLines.splice(lineIdx, 1);
|
|
if (d.output && d.output.length > 0) {
|
|
d.output.forEach(l => this.outputLines.push({ html: escapeHtml(l) }));
|
|
} else {
|
|
this.outputLines.push({ html: '<span class="term-success">OK</span>' });
|
|
}
|
|
}
|
|
} catch (e) {}
|
|
}, 500);
|
|
} else if (r) {
|
|
this.outputLines.push({ html: '<span class="term-error">' + escapeHtml(r.message || 'Error') + '</span>' });
|
|
}
|
|
} catch (e) {
|
|
this.outputLines.push({ html: '<span class="term-error">' + escapeHtml(e.message) + '</span>' });
|
|
}
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|