This commit is contained in:
Mateusz Gruszczyński
2026-03-05 15:53:33 +01:00
commit e8f6c4c609
74 changed files with 4482 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
{% extends "base.html" %}
{% block title %}Edit device - {{ device.name }} - MikroMon{% endblock %}
{% block content %}
<div class="d-flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
<div>
<h1 class="h3 mb-0">Edit device</h1>
<div class="text-muted">{{ device.name }} ({{ device.host }})</div>
</div>
<a class="btn btn-outline-secondary" href="{{ url_for('devices.view', device_id=device.id) }}"><i class="fa-solid fa-arrow-left me-1"></i>Back</a>
</div>
<div class="row g-3">
<div class="col-12 col-lg-7">
<div class="card shadow-sm">
<div class="card-body">
<form method="post" novalidate>
{{ form.hidden_tag() }}
<div class="row g-3">
<div class="col-12">
<label class="form-label">Name</label>
{{ form.name(class_="form-control") }}
</div>
<div class="col-12">
<label class="form-label">Host</label>
{{ form.host(class_="form-control") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">REST port</label>
{{ form.rest_port(class_="form-control") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">REST base path</label>
{{ form.rest_base_path(class_="form-control") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">Username</label>
{{ form.username(class_="form-control") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">Password</label>
{{ form.password(class_="form-control", placeholder="Leave blank to keep unchanged") }}
<div class="form-text">Leave blank to keep the current password.</div>
</div>
<div class="col-12">
<div class="form-check">
{{ form.allow_insecure_tls(class_="form-check-input") }}
<label class="form-check-label">Allow insecure TLS (self-signed)</label>
</div>
</div>
<div class="col-12">
<div class="form-check">
{{ form.ssh_enabled(class_="form-check-input", id="sshEnabled") }}
<label class="form-check-label" for="sshEnabled">Enable SSH connector</label>
</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">SSH port</label>
{{ form.ssh_port(class_="form-control") }}
<div class="form-text">Used only when SSH is enabled.</div>
</div>
</div>
<hr class="my-3">
<button class="btn btn-primary" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>Save changes</button>
</form>
</div>
</div>
</div>
<div class="col-12 col-lg-5">
<div class="card shadow-sm">
<div class="card-body">
<div class="fw-semibold mb-2"><i class="fa-solid fa-circle-info me-2"></i>Notes</div>
<ul class="small mb-0">
<li>Changing credentials updates the encrypted secret stored in the database.</li>
<li>If REST fails, verify host/port/path and TLS setting.</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}Devices - MikroMon{% endblock %}
{% block content %}
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h1 class="h3 mb-0">Devices</h1>
<div class="text-muted">Routers / hosts to monitor.</div>
</div>
<a class="btn btn-primary" href="{{ url_for('devices.new') }}"><i class="fa-solid fa-plus me-1"></i>Add device</a>
</div>
<div class="row g-3">
{% for d in devices %}
<div class="col-12 col-md-6 col-lg-4">
<div class="card shadow-sm h-100">
<div class="card-body">
<div class="fw-semibold">{{ d.name }}</div>
<div class="text-muted small">{{ d.host }}</div>
{% if d.last_error %}
<div class="mt-2 alert alert-warning py-2 px-3 mb-0 small"><i class="fa-solid fa-triangle-exclamation me-1"></i>{{ d.last_error }}</div>
{% endif %}
</div>
<div class="card-footer bg-white border-0 pt-0 pb-3 px-3">
<a class="btn btn-outline-primary btn-sm" href="{{ url_for('devices.view', device_id=d.id) }}"><i class="fa-solid fa-eye me-1"></i>Details</a>
</div>
</div>
</div>
{% else %}
<div class="col-12"><div class="alert alert-info mb-0">No devices.</div></div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -0,0 +1,87 @@
{% extends "base.html" %}
{% block title %}New device - MikroMon{% endblock %}
{% block content %}
<div class="d-flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
<div>
<h1 class="h3 mb-0">Add device</h1>
<div class="text-muted">Configure REST/SSH access.</div>
</div>
<a class="btn btn-outline-secondary" href="{{ url_for('devices.index') }}"><i class="fa-solid fa-arrow-left me-1"></i>Back</a>
</div>
<div class="row g-3">
<div class="col-12 col-lg-7">
<div class="card shadow-sm">
<div class="card-body">
<form method="post" novalidate>
{{ form.hidden_tag() }}
<div class="row g-3">
<div class="col-12">
<label class="form-label">Name</label>
{{ form.name(class_="form-control", placeholder="e.g. MikroTik RB4011") }}
</div>
<div class="col-12">
<label class="form-label">Host</label>
{{ form.host(class_="form-control", placeholder="192.168.1.1 or router.example.com") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">REST port</label>
{{ form.rest_port(class_="form-control") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">REST base path</label>
{{ form.rest_base_path(class_="form-control", placeholder="/rest") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">Username</label>
{{ form.username(class_="form-control") }}
</div>
<div class="col-12 col-md-6">
<label class="form-label">Password</label>
{{ form.password(class_="form-control", placeholder="••••••••") }}
</div>
<div class="col-12">
<div class="form-check">
{{ form.allow_insecure_tls(class_="form-check-input") }}
<label class="form-check-label">Allow insecure TLS (self-signed)</label>
</div>
</div>
<div class="col-12">
<div class="form-check">
{{ form.ssh_enabled(class_="form-check-input", id="sshEnabled") }}
<label class="form-check-label" for="sshEnabled">Enable SSH connector</label>
</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label">SSH port</label>
{{ form.ssh_port(class_="form-control") }}
<div class="form-text">Used only when SSH is enabled.</div>
</div>
</div>
<hr class="my-3">
<button class="btn btn-primary" type="submit"><i class="fa-solid fa-plus me-1"></i>Save</button>
</form>
</div>
</div>
</div>
<div class="col-12 col-lg-5">
<div class="card shadow-sm">
<div class="card-body">
<div class="fw-semibold mb-2"><i class="fa-solid fa-circle-info me-2"></i>Tips</div>
<ul class="small mb-0">
<li>REST uses the MikroTik API (<code>/rest</code>).</li>
<li>If you use a self-signed cert, enable insecure TLS.</li>
<li>SSH is optional (e.g. for commands/reads).</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,78 @@
{% extends "base.html" %}
{% block title %}{{ device.name }} - Device - MikroMon{% endblock %}
{% block content %}
<div class="d-flex align-items-start justify-content-between mb-3 flex-wrap gap-2">
<div>
<h1 class="h3 mb-0">{{ device.name }}</h1>
<div class="text-muted">{{ device.host }}</div>
</div>
<div class="d-flex gap-2">
<a class="btn btn-outline-secondary" href="{{ url_for('devices.index') }}"><i class="fa-solid fa-arrow-left me-1"></i>Back</a>
<a class="btn btn-outline-primary" href="{{ url_for('devices.edit', device_id=device.id) }}"><i class="fa-solid fa-pen-to-square me-1"></i>Edit</a>
<form method="post" action="{{ url_for('devices.delete', device_id=device.id) }}" onsubmit="return confirm('Delete this device?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button class="btn btn-outline-danger" type="submit"><i class="fa-solid fa-trash me-1"></i>Delete</button>
</form>
<button class="btn btn-primary" id="btnTest"><i class="fa-solid fa-plug-circle-check me-1"></i>Test REST</button>
<button class="btn btn-outline-primary" id="btnDiscover"><i class="fa-solid fa-magnifying-glass me-1"></i>Discover</button>
</div>
</div>
<div class="row g-3">
<div class="col-12 col-lg-5">
<div class="card shadow-sm">
<div class="card-body">
<div class="fw-semibold mb-2"><i class="fa-solid fa-server me-2"></i>Configuration</div>
<dl class="row mb-0 small">
<dt class="col-5 text-muted">REST</dt><dd class="col-7">{{ device.host }}:{{ device.rest_port }}{{ device.rest_base_path }}</dd>
<dt class="col-5 text-muted">TLS</dt><dd class="col-7">{{ 'insecure' if device.allow_insecure_tls else 'strict' }}</dd>
<dt class="col-5 text-muted">SSH</dt><dd class="col-7">{{ 'enabled' if device.ssh_enabled else 'disabled' }}{% if device.ssh_enabled %} ({{ device.ssh_port }}){% endif %}</dd>
<dt class="col-5 text-muted">Last error</dt><dd class="col-7">{{ device.last_error or '-' }}</dd>
<dt class="col-5 text-muted">Created</dt><dd class="col-7">{{ device.created_at }}</dd>
</dl>
</div>
</div>
</div>
<div class="col-12 col-lg-7">
<div class="card shadow-sm">
<div class="card-body">
<div class="fw-semibold mb-2"><i class="fa-solid fa-terminal me-2"></i>Result</div>
<pre class="bg-light border rounded p-3 mb-0" style="min-height: 240px" id="out">{}</pre>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
(function(){
const out = document.getElementById('out');
const csrf = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
function show(obj){ out.textContent = JSON.stringify(obj, null, 2); }
async function postJson(url){
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type':'application/json', 'X-CSRFToken': csrf },
body: JSON.stringify({})
});
let data = null;
try { data = await res.json(); } catch(e) { data = { ok:false, error:'Invalid JSON response' }; }
if(!res.ok) return { ok:false, ...data };
return data;
}
document.getElementById('btnTest')?.addEventListener('click', async ()=>{
show({loading:true});
const data = await postJson("{{ url_for('devices.test', device_id=device.id) }}");
show(data);
});
document.getElementById('btnDiscover')?.addEventListener('click', async ()=>{
show({loading:true});
const res = await fetch("{{ url_for('devices.discover', device_id=device.id) }}");
const data = await res.json();
show(data);
});
})();
</script>
{% endblock %}