Schlankes, erweiterbares Dashboard für Schweiz-Indikatoren mit DEFCON-Mapping. Läuft auf Shared-Hosting ohne Container, nur PHP, JS und CSS. Datenquellen sind vorzugsweise schluessellos, Plugins sind einfache PHP-Dateien in /api, die automatisch erkannt werden.
Plug-and-Play Plugins, automatische Discovery aus /api Kein Framework, kein Composer, null Vendor-Lockin Shared-Hosting-tauglich, nur curl, pdo_mysql, dom, simplexml Robuste Heuristiken, Defcon-Mapping aus einem Score 0..100 Optionales File-Caching, saubere Fallbacks, degradierte Anzeige statt Absturz Kategorien mit Gewichten, Gesamt-Index für die Schweiz
.
├─ api/ # deine Plugins, je eine Datei = eine Kachel
├─ assets/
│ ├─ css/ # Layout
│ └─ js/ # Frontend-Logik
├─ includes/
│ ├─ helpers.php # HTTP/Caching/Utils (optional)
│ └─ render.php # UI-Helfer
├─ cache/ # File-Cache (beschreibbar)
├─ config.php # deine Konfiguration
├─ index.php # Startseite, lädt alle Plugins
└─ README.md
PHP 8.1 oder neuer Extensions: curl, pdo_mysql, dom, simplexml MySQL oder MariaDB erreichbar Schreibrechte für cache/
Repo klonen oder als ZIP entpacken. config.php anlegen, Beispiel:
<?php
define('APP_BOOTSTRAP', true);
return [
'timezone' => 'Europe/Zurich',
'db' => [
'dsn' => 'mysql:host=localhost;dbname=ch_dashboard;charset=utf8mb4',
'user' => 'dbuser',
'pass' => 'secret',
],
'category_weights' => [
'Digital/Cyber' => 0.15,
'Energie' => 0.15,
'Gesundheit' => 0.15,
'Infrastruktur' => 0.15,
'Politik' => 0.15,
'Sicherheit' => 0.15,
'Umwelt' => 0.10,
'Wirtschaft' => 0.10,
],
// optional
'http' => [
'cache_ttl' => 120, // Sekunden
'user_agent' => 'CH-Dashboard/1.0 (+shared-hosting)',
'timeout' => 12,
],
];
cache/ beschreibbar machen, z. B. chmod 0775 cache. Auf Shared-Hosting hochladen oder lokal starten: php -S 127.0.0.1:8080
Jede Datei in /api returnt beim include ein Array mit Metadaten und einer fetch-Closure. Metadaten, empfohlen:
- schema Versionszahl, aktuell 1
- id eindeutige Kennung, z. B. energie_freq
- name kurzer Titel
- category z. B. Energie, Politik, Gesundheit, Digital/Cyber, Umwelt, Infrastruktur, Sicherheit, Wirtschaft
- desc, unit, weight, icon optional
function (?PDO $pdo, array $cfg): array
[
'value' => string|int|float, // Hauptwert für die Anzeige
'trend' => string, // kurzer Kontext
'score' => int|float, // 0..100, je höher desto kritischer
'updated_at' => ISO-8601, // Zeitpunkt der Daten
'alerts' => [], // optional, Array aus ['label','value']
'details' => [], // optional, Key-Value-Paare für Tooltip
]
Plugins dürfen keine globale Variable ändern. Direktzugriff wird geblockt durch:
if (!defined('APP_BOOTSTRAP')) { http_response_code(403); exit; }
Leg es als /api/_.php ab.
<?php
// /api/umwelt_beispiel.php
if (!defined('APP_BOOTSTRAP')) { http_response_code(403); exit; }
return [
'schema' => 1,
'id' => 'umwelt_beispiel',
'name' => 'Umwelt Beispiel',
'category' => 'Umwelt',
'icon' => 'leaf',
'desc' => 'Kurze Beschreibung',
'unit' => '',
'weight' => 1.0,
'fetch' => function (?PDO $pdo, array $cfg): array {
$http = function(string $url): string {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => $cfg['http']['timeout'] ?? 12,
CURLOPT_USERAGENT => $cfg['http']['user_agent'] ?? 'CH-Dashboard/1.0',
]);
$body = curl_exec($ch);
if ($body === false) throw new RuntimeException('HTTP Fehler: '.curl_error($ch));
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 400) throw new RuntimeException("HTTP $code");
return $body;
};
// Daten abrufen und prüfen
// $j = json_decode($http('https://api.example.org/data'), true);
// if (!is_array($j)) throw new RuntimeException('JSON ungueltig');
// Kennzahl und Score bestimmen
$value = 'Demo';
$score = 25;
return [
'value' => $value,
'trend' => 'Demo-Trend',
'score' => $score,
'updated_at' => date(DATE_ATOM),
'alerts' => [],
'details' => [],
];
},
];
Bevorzugt
- Offene, schluessellose APIs und Feeds: JSON, XML, ATOM, CSV
- Offizielle Portale: admin.ch, geo.admin.ch, meteoalarm.org, data.bs.ch, USGS, ECB, Open-Meteo
Vermeiden
- Fragile Scrapes, die bei kleinem HTML-Change brechen
- Aggressive Polls ohne Cache, unnötige Last für Dritte
- Personendaten oder anything non-public
Stabilität
- Immer Timeout und User-Agent setzen
- Primärquelle plus ein Fallback
- Bei Fehler: degradierte Kachel mit Hinweis, nicht crashen
- File-Cache 60 bis 300 s, je nach Quelle
Der Score ist die einzige Zahl, die das UI für DEFCON braucht. 0 ruhig, 100 kritisch. Nutze klare Heuristiken.
- Schwellen: z. B. Frequenzabweichung in mHz
- Prozentänderung: z. B. 24-h Bewegungen
- Vola: z. B. 30-Tage StdAbw von Returns
- Ereigniszähler: z. B. Warnungen in 72 h
- Kombinierte Signale: Bonus wenn mehrere Schwellen gleichzeitig reißen
Standard-Mapping im UI: 0–19 DEFCON 5, 20–39 DEFCON 4, 40–59 DEFCON 3, 60–79 DEFCON 2, 80–100 DEFCON 1.
Dokumentiere deine Logik knapp in desc oder details.
PDO wird in fetch($pdo, $cfg) mitgegeben. Halte es sparsam.
- Tabellen idempotent anlegen
- Zeitreihen für Historie, keine riesigen Deltas pro Render
- Indizes auf Datumsspalten
- Kein ungesicherter User-Input
Beispiel:
$pdo->exec("CREATE TABLE IF NOT EXISTS my_series (
ts DATETIME PRIMARY KEY,
val DOUBLE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$stmt = $pdo->prepare("INSERT IGNORE INTO my_series (ts, val) VALUES (NOW(), :v)");
$stmt->execute([':v' => $value]);
Leichtes File-Caching reduziert Rate-Limits und beschleunigt das UI.
$cacheGet = function(string $key, callable $load, int $ttl=120) use ($cfg): string {
$file = __DIR__.'/../cache/'.preg_replace('/[^a-z0-9_\\-]/i','_',$key).'.cache';
if (is_file($file) && time() - filemtime($file) < $ttl) return file_get_contents($file) ?: '';
$data = $load();
@file_put_contents($file, $data);
return $data;
};
Aufruf:
$body = $cacheGet('openmeteo_uv_ch', fn()=> $http($url), 180);
- Ausgabe immer escapen, im Renderer ist eine e()-Funktion vorhanden
- Keine Secrets in Plugins, Parametrisierung in config.php *Prüfe Rückgabewerte penibel, keine stillen Notices
- HTTP 3xx als OK behandeln, wenn sinnvoll
- Bei HTML-Parsing nur als Fallback arbeiten
Pull Requests sind ausdrücklich erwünscht, insbesondere:
- neue Datenquellen oder komplett neue Plugins
- Stabilisierung durch Fallbacks oder Caching
- bessere Scoring-Heuristiken mit nachvollziehbarer Begründung
- UI-Verbesserungen, Accessibility, Performance
- Issue aufmachen: kurz Quelle, Aktualität, Beispiel-URL, Rate-Limit, Datennutzung.
- Branch anlegen, Plugin unter /api hinzufügen, Naming: _.php.
- Kurzen Hinweis in desc, Einheit in unit, Score-Logik kommentieren.
- Falls DB genutzt wird, Tabelle idempotent anlegen.
- Test: lokaler Lauf, Fehlerlog clean, Kachel rendert mit sinnvollen Werten.
- PR erstellen, im Text bitte:
- Quelle und Format der Daten
- Beispiel-Response oder kurzer Dump
- Scoring-Formel
- Bekannte Grenzen und Fallbacks
Code-Style
