initial commit

This commit is contained in:
Jack
2026-08-30 04:48:55 +00:00
commit 295410075f
9 changed files with 966 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}SNMP Monitor{% endblock %}</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #1a1a2e; color: #eee; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; }
h1 { margin-bottom: 20px; color: #00d9ff; }
.card { background: #16213e; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
.btn { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;
font-size: 14px; margin-right: 5px; }
.btn-primary { background: #00d9ff; color: #000; }
.btn-danger { background: #e94560; color: #fff; }
.btn-success { background: #0f3460; color: #00d9ff; border: 1px solid #00d9ff; }
input, select { padding: 8px; border-radius: 4px; border: 1px solid #0f3460;
background: #1a1a2e; color: #eee; margin-right: 10px; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #0f3460; }
th { color: #00d9ff; }
.status-ok { color: #4ade80; }
.status-error { color: #e94560; }
.form-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.7); justify-content: center; align-items: center; }
.modal.active { display: flex; }
.modal-content { background: #16213e; padding: 30px; border-radius: 8px;
max-width: 500px; width: 90%; }
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
{% block extra_js %}{% endblock %}
</body>
</html>
+79
View File
@@ -0,0 +1,79 @@
{% extends 'base.html' %}
{% block title %}Graph - {{ target.name }}{% endblock %}
{% block extra_css %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock %}
{% block content %}
<h1>📊 {{ target.name }} - Data Usage</h1>
<div class="card">
<div style="margin-bottom: 15px;">
<a href="/graph/{{ target.id }}?hours=6" class="btn btn-success">6h</a>
<a href="/graph/{{ target.id }}?hours=24" class="btn btn-success">24h</a>
<a href="/graph/{{ target.id }}?hours=168" class="btn btn-success">7d</a>
<a href="/" class="btn btn-primary">← Back</a>
</div>
<canvas id="usageChart" style="max-height: 400px;"></canvas>
</div>
<div class="card">
<h3 style="margin-bottom: 15px; color: #00d9ff;">Device Info</h3>
<p><strong>Name:</strong> {{ target.name }}</p>
<p><strong>Modem Type:</strong> {{ target.modem_type or 'rv50' }}</p>
<p><strong>IP:</strong> {{ target.ip_address }}:{{ target.port }}</p>
<p><strong>Community:</strong> {{ target.community }}</p>
<p><strong>Poll Period:</strong> {{ target.period_minutes }} minutes</p>
</div>
{% endblock %}
{% block extra_js %}
<script>
const labels = {{ labels | tojson }};
const sentData = {{ sent_data | tojson }};
const recvData = {{ recv_data | tojson }};
// Convert to KB for readability
const sentKB = sentData.map(v => v ? (v / 1024).toFixed(2) : 0);
const recvKB = recvData.map(v => v ? (v / 1024).toFixed(2) : 0);
new Chart(document.getElementById('usageChart'), {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Bytes Sent (KB)',
data: sentKB,
borderColor: '#00d9ff',
backgroundColor: 'rgba(0, 217, 255, 0.1)',
fill: true,
tension: 0.4
},
{
label: 'Bytes Received (KB)',
data: recvKB,
borderColor: '#4ade80',
backgroundColor: 'rgba(74, 222, 128, 0.1)',
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: { labels: { color: '#eee' } }
},
scales: {
x: { ticks: { color: '#888' }, grid: { color: '#0f3460' } },
y: { ticks: { color: '#888' }, grid: { color: '#0f3460' } }
}
}
});
</script>
{% endblock %}
+147
View File
@@ -0,0 +1,147 @@
{% extends 'base.html' %}
{% block title %}SNMP Monitor - Dashboard{% endblock %}
{% block content %}
<h1>📡 Cellular Data Usage Monitor</h1>
<!-- Add Target Form -->
<div class="card">
<h3 style="margin-bottom: 15px; color: #00d9ff;">Add New Target</h3>
<form action="/add" method="POST">
<div class="form-row">
<input type="text" name="name" placeholder="Device Name" required style="flex: 1;">
<select name="modem_type" style="width: 160px;">
<option value="rv50">Sierra RV50</option>
<option value="bullet-lte">Microhard Bullet-LTE</option>
</select>
<input type="text" name="ip_address" placeholder="IP Address" required style="flex: 1;">
<input type="number" name="port" value="161" placeholder="Port" style="width: 80px;">
<input type="text" name="community" value="public" placeholder="Community" style="flex: 1;">
<input type="number" name="period_minutes" value="5" placeholder="Minutes" style="width: 80px;">
<button type="submit" class="btn btn-primary">Add Target</button>
</div>
</form>
</div>
<!-- Targets Table -->
<div class="card">
<h3 style="margin-bottom: 15px; color: #00d9ff;">Monitored Devices</h3>
{% if targets %}
<table>
<thead>
<tr>
<th>Name</th>
<th>Modem</th>
<th>IP Address</th>
<th>Bytes Sent</th>
<th>Bytes Received</th>
<th>Rate Sent</th>
<th>Rate Recv</th>
<th>Period</th>
<th>Last Poll</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for target in targets %}
<tr>
<td>{{ target.name }}</td>
<td>{{ target.modem_type or 'rv50' }}</td>
<td>{{ target.ip_address }}:{{ target.port }}</td>
<td>{{ target.bytes_sent_fmt }}</td>
<td>{{ target.bytes_recv_fmt }}</td>
<td>{{ target.rate_sent_fmt }}</td>
<td>{{ target.rate_recv_fmt }}</td>
<td>{{ target.period_minutes }} min</td>
<td>{{ target.last_poll or 'Never' }}</td>
<td class="{{ 'status-ok' if target.enabled else 'status-error' }}">
{{ 'Active' if target.enabled else 'Paused' }}
</td>
<td>
<a href="/graph/{{ target.id }}?hours=24" class="btn btn-success">📊 Graph</a>
<button onclick="manualPoll({{ target.id }})" class="btn btn-primary">🔄 Poll</button>
<button onclick="editTarget({{ target.id }}, '{{ target.name }}', '{{ target.ip_address }}', {{ target.port }}, '{{ target.community }}', {{ target.period_minutes }}, {{ target.enabled }}, '{{ target.modem_type or 'rv50' }}')" class="btn btn-success">✏️ Edit</button>
<form action="/delete/{{ target.id }}" method="POST" style="display: inline;"
onsubmit="return confirm('Delete this target?')">
<button type="submit" class="btn btn-danger">🗑️</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p style="color: #888;">No targets configured. Add one above!</p>
{% endif %}
</div>
<!-- Edit Modal -->
<div id="editModal" class="modal">
<div class="modal-content">
<h3 style="margin-bottom: 15px; color: #00d9ff;">Edit Target</h3>
<form id="editForm" method="POST">
<div class="form-row">
<input type="text" name="name" id="edit_name" placeholder="Device Name" required style="flex: 1;">
</div>
<div class="form-row">
<select name="modem_type" id="edit_modem_type" style="width: 160px;">
<option value="rv50">Sierra RV50</option>
<option value="bullet-lte">Microhard Bullet-LTE</option>
</select>
</div>
<div class="form-row">
<input type="text" name="ip_address" id="edit_ip" placeholder="IP Address" required style="flex: 1;">
<input type="number" name="port" id="edit_port" value="161" style="width: 80px;">
</div>
<div class="form-row">
<input type="text" name="community" id="edit_community" placeholder="Community" style="flex: 1;">
<input type="number" name="period_minutes" id="edit_period" value="5" style="width: 80px;">
</div>
<div class="form-row">
<label><input type="checkbox" name="enabled" id="edit_enabled" checked> Enabled</label>
</div>
<div class="form-row">
<button type="submit" class="btn btn-primary">Save Changes</button>
<button type="button" onclick="closeEditModal()" class="btn btn-danger">Cancel</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function manualPoll(targetId) {
fetch(`/poll/${targetId}`, { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data.success) {
alert('Poll successful!');
location.reload();
} else {
alert('Poll failed: ' + (data.error || 'Unknown error'));
}
})
.catch(err => alert('Error: ' + err));
}
function editTarget(id, name, ip, port, community, period, enabled, modemType) {
document.getElementById('edit_name').value = name;
document.getElementById('edit_ip').value = ip;
document.getElementById('edit_port').value = port;
document.getElementById('edit_community').value = community;
document.getElementById('edit_period').value = period;
document.getElementById('edit_enabled').checked = enabled == 1;
document.getElementById('edit_modem_type').value = modemType;
document.getElementById('editForm').action = `/edit/${id}`;
document.getElementById('editModal').classList.add('active');
}
function closeEditModal() {
document.getElementById('editModal').classList.remove('active');
}
</script>
{% endblock %}