feat(ui): Sidebar-Gruppe Konfigurationen, /config-Seite, MeshDD-Dashboard (closes #4)

- app.js: Sidebar um Gruppen-Support erweitert; Konfigurationen-Gruppe
  mit Scheduler, NINA, Einstellungen (/config) als Untereinträge
- style.css: .sidebar-group-label + .sidebar-link-sub
- config.py: save()-Funktion für persistentes Schreiben in config.yaml
- webserver.py: GET/PUT /api/config + GET /config Route (Admin)
- static/config.html + static/js/config.js: neue Konfigurationsseite
  (Bot, Meshtastic, Web, Links editierbar)
- Alle HTML-Dateien: MeshDD-Bot → MeshDD-Dashboard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ppfeiffer 2026-02-20 22:33:18 +01:00
parent cbe934ef6e
commit 511ff20842
17 changed files with 371 additions and 32 deletions

View file

@ -1,5 +1,19 @@
# Changelog
## [0.08.24] - 2026-02-20
### Added
- **Sidebar: Konfigurationen-Gruppe** (closes #4 Aufgabe 12): Neue Gruppenüberschrift
mit eingerückten Untereinträgen für Scheduler, NINA und Konfiguration.
CSS-Klassen `.sidebar-group-label` und `.sidebar-link-sub` ergänzt.
- **Neue Seite `/config`** (closes #4 Aufgabe 34): Bearbeitbare Bot-Konfiguration
(Bot, Meshtastic, Web, Links). `GET/PUT /api/config` (Admin).
`config.py`: `save()`-Funktion für persistentes Schreiben in `config.yaml`.
### Changed
- **Umbenennung MeshDD-Bot → MeshDD-Dashboard** (closes #4 Aufgabe 5):
Alle HTML-Seiten (`<title>` + Navbar-Text) umbenannt.
## [0.08.23] - 2026-02-20
### Fixed

View file

@ -1,4 +1,4 @@
version: "0.08.23"
version: "0.08.24"
bot:
name: "MeshDD-Bot"

View file

@ -48,6 +48,24 @@ async def watch(interval: float = 2.0):
_reload_if_changed()
def _deep_merge(base: dict, updates: dict):
for k, v in updates.items():
if isinstance(v, dict) and isinstance(base.get(k), dict):
_deep_merge(base[k], v)
else:
base[k] = v
def save(updates: dict):
"""Deep-merge updates into the live config and persist to config.yaml."""
global _config, _mtime
_deep_merge(_config, updates)
with open(CONFIG_PATH, "w") as f:
yaml.dump(_config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
_mtime = os.path.getmtime(CONFIG_PATH)
logger.info("Config saved")
def get(key: str, default=None):
keys = key.split(".")
val = _config

View file

@ -81,6 +81,8 @@ class WebServer:
self.app.router.add_put("/api/nina/config", self._api_nina_update)
self.app.router.add_get("/api/nina/alerts", self._api_nina_alerts)
self.app.router.add_get("/api/links", self._api_links)
self.app.router.add_get("/api/config", self._api_config_get)
self.app.router.add_put("/api/config", self._api_config_update)
self.app.router.add_get("/login", self._serve_login)
self.app.router.add_get("/register", self._serve_login)
self.app.router.add_get("/admin", self._serve_admin)
@ -90,6 +92,7 @@ class WebServer:
self.app.router.add_get("/map", self._serve_map)
self.app.router.add_get("/packets", self._serve_packets)
self.app.router.add_get("/messages", self._serve_messages)
self.app.router.add_get("/config", self._serve_config)
self.app.router.add_get("/", self._serve_index)
self.app.router.add_static("/static", STATIC_DIR)
@ -220,6 +223,29 @@ class WebServer:
async def _serve_nina(self, request: web.Request) -> web.Response:
return web.FileResponse(os.path.join(STATIC_DIR, "nina.html"))
async def _serve_config(self, request: web.Request) -> web.Response:
return web.FileResponse(os.path.join(STATIC_DIR, "config.html"))
async def _api_config_get(self, request: web.Request) -> web.Response:
require_admin_api(request)
return web.json_response({
"bot": config.get("bot", {}),
"meshtastic": config.get("meshtastic", {}),
"web": {
"port": config.get("web.port", 8081),
"online_threshold": config.get("web.online_threshold", 900),
},
"links": config.get("links", []) or [],
})
async def _api_config_update(self, request: web.Request) -> web.Response:
require_admin_api(request)
data = await request.json()
allowed_keys = {"bot", "meshtastic", "web", "links"}
updates = {k: v for k, v in data.items() if k in allowed_keys}
config.save(updates)
return web.json_response({"ok": True})
async def _api_nina_get(self, request: web.Request) -> web.Response:
require_admin_api(request)
if not self.nina:

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Admin</title>
<title>MeshDD-Dashboard Admin</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -15,7 +15,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

147
static/config.html Normal file
View file

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="de" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Dashboard Konfiguration</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body class="antialiased">
<!-- Top Navbar -->
<nav class="top-navbar d-flex align-items-center px-3">
<button class="btn btn-link text-body p-0 me-2 d-lg-none" id="sidebarToggle">
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">
<span id="userMenu" class="d-none">
<small class="text-body-secondary me-1"><i class="bi bi-person-fill me-1"></i><span id="userName"></span></small>
<a href="/auth/logout" class="btn btn-sm btn-outline-secondary py-0 px-1" title="Abmelden">
<i class="bi bi-box-arrow-right" style="font-size:.75rem"></i>
</a>
</span>
<a href="/login" id="loginBtn" class="btn btn-sm btn-outline-info py-0 px-1 d-none">
<i class="bi bi-person" style="font-size:.75rem"></i> Login
</a>
<button class="btn btn-sm btn-outline-secondary py-0 px-1" id="themeToggle" title="Theme wechseln">
<i class="bi bi-sun-fill" id="themeIcon" style="font-size:.75rem"></i>
</button>
</div>
</nav>
<aside class="sidebar" id="sidebar"></aside>
<div class="sidebar-backdrop" id="sidebarBackdrop"></div>
<!-- Content -->
<main class="content-wrapper">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0"><i class="bi bi-sliders me-1 text-info"></i>Konfiguration</h6>
<div class="d-flex gap-2 align-items-center">
<span id="saveStatus" class="d-none small"></span>
<button class="btn btn-sm btn-primary" id="btnSave">
<i class="bi bi-floppy me-1"></i>Speichern
</button>
</div>
</div>
<div class="row g-3">
<!-- Bot -->
<div class="col-md-6">
<div class="card card-outline card-info">
<div class="card-header"><i class="bi bi-robot me-1"></i>Bot</div>
<div class="card-body">
<div class="mb-3">
<label for="botName" class="form-label">Name</label>
<input type="text" class="form-control form-control-sm" id="botName" placeholder="MeshDD-Bot">
</div>
<div class="mb-0">
<label for="botPrefix" class="form-label">Befehlspräfix</label>
<input type="text" class="form-control form-control-sm" id="botPrefix" maxlength="3" placeholder="?">
</div>
</div>
</div>
</div>
<!-- Meshtastic -->
<div class="col-md-6">
<div class="card card-outline card-warning">
<div class="card-header"><i class="bi bi-broadcast me-1"></i>Meshtastic</div>
<div class="card-body">
<div class="mb-3">
<label for="meshHost" class="form-label">Host / IP</label>
<input type="text" class="form-control form-control-sm" id="meshHost" placeholder="192.168.1.1">
</div>
<div class="mb-0">
<label for="meshPort" class="form-label">Port</label>
<input type="number" class="form-control form-control-sm" id="meshPort" min="1" max="65535" placeholder="4403">
</div>
</div>
</div>
</div>
<!-- Web -->
<div class="col-md-6">
<div class="card card-outline card-success">
<div class="card-header"><i class="bi bi-globe me-1"></i>Webserver</div>
<div class="card-body">
<div class="mb-3">
<label for="webPort" class="form-label">Port</label>
<input type="number" class="form-control form-control-sm" id="webPort" min="1" max="65535" placeholder="8081">
</div>
<div class="mb-0">
<label for="onlineThreshold" class="form-label">Online-Schwellwert (Sek.)</label>
<input type="number" class="form-control form-control-sm" id="onlineThreshold" min="60" step="60" placeholder="900">
<div class="form-text">Node gilt als online wenn zuletzt innerhalb dieser Zeit gesehen.</div>
</div>
</div>
</div>
</div>
<!-- Links -->
<div class="col-md-6">
<div class="card card-outline card-secondary">
<div class="card-header"><i class="bi bi-link-45deg me-1"></i>Links</div>
<div class="card-body">
<div class="table-responsive mb-2" style="max-height:160px;overflow-y:auto">
<table class="table table-sm table-hover table-striped mb-0 align-middle">
<thead class="table-dark">
<tr>
<th>Label</th>
<th>URL</th>
<th style="width:42px"></th>
</tr>
</thead>
<tbody id="linksList"></tbody>
</table>
</div>
<div class="row g-2">
<div class="col-4">
<input type="text" class="form-control form-control-sm" id="newLinkLabel" placeholder="Label">
</div>
<div class="col-6">
<input type="text" class="form-control form-control-sm" id="newLinkUrl" placeholder="https://...">
</div>
<div class="col-2">
<button class="btn btn-outline-secondary btn-sm w-100" id="btnAddLink" type="button">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer id="pageFooter" class="page-footer"></footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/app.js"></script>
<script src="/static/js/config.js"></script>
</body>
</html>

View file

@ -80,6 +80,22 @@
color: var(--bs-secondary-color);
}
.sidebar-group-label {
display: block;
padding: .75rem .75rem .2rem;
font-size: .65rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--bs-secondary-color);
border-top: 1px solid var(--tblr-border-color, var(--bs-border-color));
margin-top: .25rem;
}
.sidebar-link-sub {
padding-left: 1.5rem;
}
/* ── Mobile sidebar ──────────────────────────────────────────── */
.sidebar-backdrop {

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Dashboard</title>
<title>MeshDD-Dashboard Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
@ -16,7 +16,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

View file

@ -1,16 +1,17 @@
// MeshDD-Bot Shared page module
// MeshDD-Dashboard Shared page module
// Provides: initPage(), escapeHtml(), applyTheme()
// ── Sidebar definition ────────────────────────────────────────
const _SIDEBAR_LINKS = [
{ href: '/', icon: 'bi-speedometer2', label: 'Dashboard', admin: false, user: false },
{ href: '/scheduler', icon: 'bi-clock-history', label: 'Scheduler', admin: true, user: false },
{ href: '/nina', icon: 'bi-shield-exclamation', label: 'NINA', admin: true, user: false },
{ href: '/map', icon: 'bi-map', label: 'Karte', admin: false, user: false },
{ href: '/packets', icon: 'bi-reception-4', label: 'Pakete', admin: false, user: false },
{ href: '/messages', icon: 'bi-chat-dots', label: 'Nachrichten', admin: false, user: true },
{ href: '/settings', icon: 'bi-gear', label: 'Einstellungen',admin: true, user: false },
{ type: 'group', label: 'Konfigurationen',admin: true },
{ href: '/scheduler', icon: 'bi-clock-history', label: 'Scheduler', admin: true, user: false, sub: true },
{ href: '/nina', icon: 'bi-shield-exclamation', label: 'NINA', admin: true, user: false, sub: true },
{ href: '/config', icon: 'bi-sliders', label: 'Einstellungen', admin: true, user: false, sub: true },
{ href: '/admin', icon: 'bi-people', label: 'Benutzer', admin: true, user: false },
];
@ -20,10 +21,15 @@ function _injectSidebar() {
const currentPath = window.location.pathname;
sidebar.innerHTML = '<nav class="sidebar-nav">' +
_SIDEBAR_LINKS.map(link => {
if (link.type === 'group') {
const adm = link.admin ? ' sidebar-admin' : '';
return `<span class="sidebar-group-label${adm}">${link.label}</span>`;
}
const active = currentPath === link.href ? ' active' : '';
const adm = link.admin ? ' sidebar-admin' : '';
const usr = link.user ? ' sidebar-user' : '';
return `<a href="${link.href}" class="sidebar-link${active}${adm}${usr}">` +
const sub = link.sub ? ' sidebar-link-sub' : '';
return `<a href="${link.href}" class="sidebar-link${sub}${active}${adm}${usr}">` +
`<i class="bi ${link.icon}"></i><span>${link.label}</span></a>`;
}).join('') +
'</nav>';

112
static/js/config.js Normal file
View file

@ -0,0 +1,112 @@
let links = [];
initPage({ onAuth: (user) => {
if (!user || user.role !== 'admin') {
window.location.href = '/';
}
}});
// ── Links table ───────────────────────────────────────────────
function renderLinks() {
const tbody = document.getElementById('linksList');
if (links.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-body-secondary py-2"><small>Keine Links.</small></td></tr>';
return;
}
tbody.innerHTML = links.map((l, i) =>
`<tr>
<td><small>${escapeHtml(l.label)}</small></td>
<td><small class="text-body-secondary">${escapeHtml(l.url)}</small></td>
<td class="text-end">
<button type="button" class="btn btn-outline-danger btn-sm py-0 px-1"
onclick="removeLink(${i})" title="Entfernen">
<i class="bi bi-trash" style="font-size:.75rem"></i>
</button>
</td>
</tr>`
).join('');
}
function removeLink(idx) {
links.splice(idx, 1);
renderLinks();
}
document.getElementById('btnAddLink').addEventListener('click', () => {
const label = document.getElementById('newLinkLabel').value.trim();
const url = document.getElementById('newLinkUrl').value.trim();
if (!label || !url) return;
links.push({ label, url });
renderLinks();
document.getElementById('newLinkLabel').value = '';
document.getElementById('newLinkUrl').value = '';
});
// ── Load ─────────────────────────────────────────────────────
async function loadConfig() {
try {
const resp = await fetch('/api/config');
if (!resp.ok) return;
const cfg = await resp.json();
document.getElementById('botName').value = cfg.bot?.name ?? '';
document.getElementById('botPrefix').value = cfg.bot?.command_prefix ?? '';
document.getElementById('meshHost').value = cfg.meshtastic?.host ?? '';
document.getElementById('meshPort').value = cfg.meshtastic?.port ?? '';
document.getElementById('webPort').value = cfg.web?.port ?? '';
document.getElementById('onlineThreshold').value = cfg.web?.online_threshold ?? '';
links = Array.isArray(cfg.links) ? [...cfg.links] : [];
renderLinks();
} catch (e) {
console.error('Config load failed:', e);
}
}
// ── Save ──────────────────────────────────────────────────────
document.getElementById('btnSave').addEventListener('click', async () => {
const payload = {
bot: {
name: document.getElementById('botName').value.trim(),
command_prefix: document.getElementById('botPrefix').value.trim(),
},
meshtastic: {
host: document.getElementById('meshHost').value.trim(),
port: parseInt(document.getElementById('meshPort').value) || 4403,
},
web: {
port: parseInt(document.getElementById('webPort').value) || 8081,
online_threshold: parseInt(document.getElementById('onlineThreshold').value) || 900,
},
links: [...links],
};
const statusEl = document.getElementById('saveStatus');
try {
const resp = await fetch('/api/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (resp.ok) {
statusEl.textContent = 'Gespeichert ✓';
statusEl.className = 'small text-success';
statusEl.classList.remove('d-none');
setTimeout(() => statusEl.classList.add('d-none'), 3000);
} else {
statusEl.textContent = 'Fehler beim Speichern';
statusEl.className = 'small text-danger';
statusEl.classList.remove('d-none');
}
} catch (e) {
console.error('Save failed:', e);
statusEl.textContent = 'Netzwerkfehler';
statusEl.className = 'small text-danger';
statusEl.classList.remove('d-none');
}
});
loadConfig();

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Login</title>
<title>MeshDD-Dashboard Login</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -12,7 +12,7 @@
<!-- Top Navbar -->
<nav class="top-navbar d-flex align-items-center px-3">
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
</span>
<button class="btn btn-sm btn-outline-secondary py-0 px-1" id="themeToggle" title="Theme wechseln">
<i class="bi bi-sun-fill" id="themeIcon" style="font-size:.75rem"></i>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Karte</title>
<title>MeshDD-Dashboard Karte</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
@ -16,7 +16,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Nachrichten</title>
<title>MeshDD-Dashboard Nachrichten</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -15,7 +15,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot NINA</title>
<title>MeshDD-Dashboard NINA</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -15,7 +15,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Paket-Log</title>
<title>MeshDD-Dashboard Paket-Log</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -15,7 +15,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Scheduler</title>
<title>MeshDD-Dashboard Scheduler</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -15,7 +15,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MeshDD-Bot Einstellungen</title>
<title>MeshDD-Dashboard Einstellungen</title>
<link href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/static/css/style.css">
@ -15,7 +15,7 @@
<i class="bi bi-list fs-5"></i>
</button>
<span class="fw-bold me-auto">
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Bot
<i class="bi bi-broadcast-pin text-info me-1"></i>MeshDD-Dashboard
<small class="text-body-secondary fw-normal" id="versionLabel"></small>
</span>
<div class="d-flex align-items-center gap-2">