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.
72 lines
3.5 KiB
HTML
72 lines
3.5 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Devices - ESPILON{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="page" x-data="deviceList()" x-init="init()">
|
|
<div class="panel" style="flex:1;">
|
|
<div class="panel-header">
|
|
<span>Devices <span x-text="'(' + rows.length + ')'"></span></span>
|
|
<div class="panel-header-actions">
|
|
<input type="text" class="input" placeholder="Filter..." x-model="filter" style="width:180px;">
|
|
</div>
|
|
</div>
|
|
<div class="panel-body">
|
|
<table class="dt">
|
|
<thead>
|
|
<tr>
|
|
<th @click="toggleSort('id')" :class="sortClass('id')">ID</th>
|
|
<th @click="toggleSort('status')" :class="sortClass('status')" class="col-shrink">Status</th>
|
|
<th @click="toggleSort('ip')" :class="sortClass('ip')">Address</th>
|
|
<th @click="toggleSort('chip')" :class="sortClass('chip')" class="col-shrink">Chip</th>
|
|
<th>Modules</th>
|
|
<th @click="toggleSort('connected_for_seconds')" :class="sortClass('connected_for_seconds')" class="col-right">Uptime</th>
|
|
<th @click="toggleSort('last_seen_ago_seconds')" :class="sortClass('last_seen_ago_seconds')" class="col-right">Last Seen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template x-for="d in pagedRows" :key="d.id">
|
|
<tr class="clickable" @click="window.location='/device/'+d.id">
|
|
<td x-text="d.id"></td>
|
|
<td>
|
|
<span class="badge" :class="d.status==='Connected' ? 'badge-ok' : 'badge-warn'" x-text="d.status"></span>
|
|
</td>
|
|
<td x-text="(d.ip||'-')+':'+(d.port||'-')"></td>
|
|
<td x-text="d.chip || '-'"></td>
|
|
<td>
|
|
<template x-for="m in (d.modules||'').split(',').filter(Boolean)" :key="m">
|
|
<span class="badge" x-text="m" style="margin-right:2px;"></span>
|
|
</template>
|
|
</td>
|
|
<td class="col-right" x-text="formatDuration(d.connected_for_seconds)"></td>
|
|
<td class="col-right" x-text="formatDuration(d.last_seen_ago_seconds)+' ago'"></td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
<template x-if="!loading && rows.length === 0">
|
|
<div class="dt-empty">No devices connected</div>
|
|
</template>
|
|
</div>
|
|
<div class="panel-footer" x-show="totalPages > 1">
|
|
Page <span x-text="page + 1"></span> / <span x-text="totalPages"></span>
|
|
<button class="btn btn-sm" @click="page = Math.max(0, page-1)" :disabled="page === 0">«</button>
|
|
<button class="btn btn-sm" @click="page = Math.min(totalPages-1, page+1)" :disabled="page >= totalPages-1">»</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
function deviceList() {
|
|
return {
|
|
...dataTable({ defaultSort: 'status', defaultDir: 'asc', extract: d => d.devices || [] }),
|
|
init() {
|
|
this.refresh('/api/devices');
|
|
setInterval(() => this.refresh('/api/devices'), 5000);
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|