initial commit
This commit is contained in:
@@ -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>
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user