Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Schweizer-Gesundheitsdashboard

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.

Features

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

Verzeichnisstruktur

.
├─ 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

Voraussetzungen

PHP 8.1 oder neuer Extensions: curl, pdo_mysql, dom, simplexml MySQL oder MariaDB erreichbar Schreibrechte für cache/

Setup

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

Plugin-Interface

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

fetch-Closure:

function (?PDO $pdo, array $cfg): array

Rückgabe von fetch:

[
  '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
]

Wichtig

Plugins dürfen keine globale Variable ändern. Direktzugriff wird geblockt durch:

if (!defined('APP_BOOTSTRAP')) { http_response_code(403); exit; }

Minimal-Template für ein Plugin

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'    => [],
    ];
  },
];

Richtlinien für Datenquellen

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

Scoring-Leitplanken

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.

DB-Nutzung

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]);

Caching

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);

Qualität und Sicherheit

  • 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

Contributing

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

Vorgehen

  1. Issue aufmachen: kurz Quelle, Aktualität, Beispiel-URL, Rate-Limit, Datennutzung.
  2. Branch anlegen, Plugin unter /api hinzufügen, Naming: _.php.
  3. Kurzen Hinweis in desc, Einheit in unit, Score-Logik kommentieren.
  4. Falls DB genutzt wird, Tabelle idempotent anlegen.
  5. Test: lokaler Lauf, Fehlerlog clean, Kachel rendert mit sinnvollen Werten.
  6. PR erstellen, im Text bitte:
  • Quelle und Format der Daten
  • Beispiel-Response oder kurzer Dump
  • Scoring-Formel
  • Bekannte Grenzen und Fallbacks

Code-Style

  • PSR-12 nah genug, kurze Closures ok

  • Keine globalen Seiteneffekte im Plugin-Scope

  • Kurze, deterministische Regex, defensiv beim Parsen

    image

About

Schlankes, erweiterbares Dashboard für Schweiz-Indikatoren mit DEFCON-Mapping.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages