localization
This commit is contained in:
@@ -19,9 +19,9 @@ namespace {
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
// Nur reine Dateinamen/Labels aus Formularfeldern akzeptieren - kein
|
||||
// "/" und kein ".." - damit ueber die WebUI kein Ausbruch aus dem
|
||||
// jeweiligen SD-Verzeichnis moeglich ist (Pfad-Traversal).
|
||||
// Only accept plain filenames/labels from form fields - no
|
||||
// "/" and no ".." - so that no escape from the respective
|
||||
// SD directory is possible via the WebUI (path traversal).
|
||||
bool isSafeName(const String& name) {
|
||||
if (name.length() == 0 || name.length() > 40) return false;
|
||||
if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) return false;
|
||||
@@ -29,14 +29,14 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Absichtliche Duplikate der gleichnamigen (lokalen/statischen) Funktionen
|
||||
// aus radar_screen.cpp - dort nicht exportiert, und nach der im Projekt
|
||||
// etablierten Konvention "jeder Screen/jedes Modul dupliziert seine
|
||||
// eigenen kleinen Helfer statt eines gemeinsamen Moduls" (siehe z.B.
|
||||
// timeout_screen.cpp) bewusst hier erneut definiert statt radar_screen.cpp
|
||||
// umzubauen. Bei Aenderungen an der Logik in radar_screen.cpp bitte diese
|
||||
// Kopie hier synchron halten, damit das WebUI-Radar dieselben Ringe/
|
||||
// Markierungen zeigt wie das Geraete-Display.
|
||||
// Intentional duplicates of the same-named (local/static) functions
|
||||
// from radar_screen.cpp - not exported there, and following the project's
|
||||
// established convention "each screen/module duplicates its own small
|
||||
// helpers instead of a shared module" (see e.g. timeout_screen.cpp),
|
||||
// deliberately redefined here instead of refactoring radar_screen.cpp.
|
||||
// If the logic in radar_screen.cpp changes, please keep this copy
|
||||
// in sync so that the WebUI radar shows the same rings/markers as the
|
||||
// device display.
|
||||
bool isEmergencySquawkWeb(const char* squawk) {
|
||||
if (!squawk[0]) return false;
|
||||
for (uint8_t i = 0; i < Config::EMERGENCY_SQUAWK_COUNT; i++) {
|
||||
@@ -45,15 +45,15 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// "Auffaellig" (oranger Ring) beschraenkt sich aktuell auf gar nichts -
|
||||
// der Teil ueber Militaer-/Regierungs-Rufzeichen-Praefixe
|
||||
// (Config::NOTABLE_CALLSIGN_PREFIXES) ist NICHT umgesetzt, weil diese
|
||||
// Konstante nirgends im Projekt existiert (auch nicht in
|
||||
// radar_screen.cpp) - das zugehoerige Feature wurde bisher nie
|
||||
// tatsaechlich spezifiziert/implementiert. "notable" wird deshalb unten
|
||||
// in handleRadarJson() fest auf false gesetzt. Sobald es eine echte
|
||||
// Praefixliste gibt, hier eine isNotableCallsignWeb()-Funktion analog zu
|
||||
// isEmergencySquawkWeb() ergaenzen.
|
||||
// "Notable" (orange ring) is currently limited to nothing at all -
|
||||
// the part about military/government callsign prefixes
|
||||
// (Config::NOTABLE_CALLSIGN_PREFIXES) is NOT implemented, because this
|
||||
// constant does not exist anywhere in the project (not even in
|
||||
// radar_screen.cpp) - the associated feature has never actually been
|
||||
// specified/implemented. "notable" is therefore hardcoded to false
|
||||
// below in handleRadarJson(). As soon as there is a real prefix list,
|
||||
// add an isNotableCallsignWeb() function analogous to
|
||||
// isEmergencySquawkWeb() here.
|
||||
bool isHeavyCategoryWeb(const char* category) {
|
||||
return category[0] == 'A' && category[1] == '5';
|
||||
}
|
||||
@@ -73,10 +73,9 @@ namespace {
|
||||
html += "button{background:#0a0f0d;color:#ff3b3b;border:1px solid #ff3b3b;border-radius:4px;padding:3px 10px;font-family:inherit;cursor:pointer;}";
|
||||
html += "button:hover{background:#ff3b3b;color:#0a0f0d;}";
|
||||
html += ".dl{color:#39ff14;text-decoration:none;border:1px solid #39ff14;border-radius:4px;padding:3px 10px;margin-right:6px;display:inline-block;}";
|
||||
// Gruen statt Rot fuer "Hinzufuegen"-Buttons - die roten button{}-Regeln
|
||||
// oben bleiben fuer alle destruktiven "Entfernen/Loeschen"-Buttons
|
||||
// unveraendert, .addbtn ist ausschliesslich fuer die neuen Listen-
|
||||
// Formulare (siehe handleLists()) gedacht.
|
||||
// Green instead of red for "Add" buttons - the red button{} rules
|
||||
// above stay unchanged for all destructive "Remove/Delete" buttons.
|
||||
// .addbtn is exclusively for the new list forms (see handleLists()).
|
||||
html += ".addbtn{background:#0a0f0d;color:#39ff14;border:1px solid #39ff14;border-radius:4px;padding:3px 10px;font-family:inherit;cursor:pointer;}";
|
||||
html += ".addbtn:hover{background:#39ff14;color:#0a0f0d;}";
|
||||
html += "input[type=text]{background:#0a0f0d;color:#39ff14;border:1px solid #39ff14;border-radius:4px;padding:5px 8px;font-family:inherit;}";
|
||||
@@ -92,19 +91,19 @@ namespace {
|
||||
return html;
|
||||
}
|
||||
|
||||
// Live-Radar-Ansicht fuer die Startseite: ein <canvas>, das per JavaScript
|
||||
// alle paar Sekunden /radar.json abruft und die Flugzeuge polar (Peilung/
|
||||
// Distanz, genau wie auf dem Geraete-Display) zeichnet. Bewusst per
|
||||
// fetch()-Polling statt WebSocket/SSE gehalten - deutlich weniger Code
|
||||
// und Speicherbedarf auf dem ESP32, und fuer eine gelegentlich vom Handy
|
||||
// aus aufgerufene Seite voellig ausreichend.
|
||||
// Live radar view for the start page: a <canvas> that uses JavaScript
|
||||
// to fetch /radar.json every few seconds and draw the aircraft polar
|
||||
// (bearing/distance, exactly like on the device display). Intentionally
|
||||
// kept with fetch()-polling instead of WebSocket/SSE - significantly less
|
||||
// code and memory usage on the ESP32, and perfectly adequate for a page
|
||||
// occasionally opened from a phone.
|
||||
//
|
||||
// Der Reichweiten-Waehler (<select>) ist rein clientseitig/pro Seiten-
|
||||
// aufruf - er aendert NICHT die Geraete-Einstellung (SettingsStore::
|
||||
// rangeIndex()), sondern wird als "range_km"-Query-Parameter an
|
||||
// /radar.json mitgeschickt (siehe handleRadarJson()) und erlaubt so ein
|
||||
// unabhaengiges Herein-/Herauszoomen auf dem Handy, ohne das Geraete-
|
||||
// Display zu beeinflussen. Default ist die aktuelle Geraete-Reichweite.
|
||||
// The range selector (<select>) is purely client-side/per page view -
|
||||
// it does NOT change the device setting (SettingsStore::
|
||||
// rangeIndex()), but is sent as a "range_km" query parameter to
|
||||
// /radar.json (see handleRadarJson()) and allows independent zooming
|
||||
// in/out on the phone without affecting the device display. Default is
|
||||
// the current device range.
|
||||
void appendRadarSection(String& html) {
|
||||
float deviceRangeKm = Config::RANGE_STEPS_KM[SettingsStore::rangeIndex()];
|
||||
|
||||
@@ -131,10 +130,10 @@ namespace {
|
||||
html += "var markers=[];";
|
||||
html += "var selectedHex=null;";
|
||||
|
||||
// Hintergrund-Sterne AUSSERHALB des Radarkreises - gleiches Prinzip
|
||||
// wie updateBgStars()/initBgStarsIfNeeded() in radar_screen.cpp
|
||||
// (Rejection-Sampling, damit kein Stern innerhalb des Kreises
|
||||
// landet), hier per Canvas/JS statt TFT_eSPI nachgebaut.
|
||||
// Background stars OUTSIDE the radar circle - same principle
|
||||
// as updateBgStars()/initBgStarsIfNeeded() in radar_screen.cpp
|
||||
// (rejection sampling, so no star lands inside the circle),
|
||||
// replicated here via Canvas/JS instead of TFT_eSPI.
|
||||
html += "var stars=[];";
|
||||
html += "(function(){var minDistSq=(R+6)*(R+6);for(var i=0;i<24;i++){var x,y,tries=0;";
|
||||
html += "do{x=4+Math.random()*(W-8);y=4+Math.random()*(H-8);tries++;}";
|
||||
@@ -155,9 +154,9 @@ namespace {
|
||||
html += "for(var ring=1;ring<=3;ring++){var r=R*ring/3;ctx.beginPath();ctx.arc(cx,cy,r,0,Math.PI*2);ctx.stroke();";
|
||||
html += "ctx.fillText(Math.round(data.range_km*ring/3)+' km',cx+4,cy-r+10);}";
|
||||
html += "ctx.strokeStyle='#12261a';ctx.beginPath();ctx.moveTo(cx-R,cy);ctx.lineTo(cx+R,cy);ctx.moveTo(cx,cy-R);ctx.lineTo(cx,cy+R);ctx.stroke();";
|
||||
// Alle vier Himmelsrichtungen (N/E/S/W), genau wie
|
||||
// drawStaticBackground() in radar_screen.cpp - vorher stand hier nur
|
||||
// "N", was auf Nachfrage ergaenzt wurde.
|
||||
// All four cardinal directions (N/E/S/W), exactly like
|
||||
// drawStaticBackground() in radar_screen.cpp - previously only
|
||||
// "N" was shown here, which was expanded upon request.
|
||||
html += "ctx.fillStyle='#39ff14';ctx.textAlign='center';ctx.fillText('N',cx,cy-R-8);";
|
||||
html += "ctx.fillText('S',cx,cy+R+16);";
|
||||
html += "ctx.textAlign='left';ctx.fillText('E',cx+R+4,cy+3);";
|
||||
@@ -180,26 +179,26 @@ namespace {
|
||||
html += "if(a.emergency){ctx.strokeStyle='#ff3b3b';ctx.beginPath();ctx.arc(x,y,9,0,Math.PI*2);ctx.stroke();}";
|
||||
html += "else if(a.watched){ctx.strokeStyle='#00e5ff';ctx.beginPath();ctx.arc(x,y,9,0,Math.PI*2);ctx.stroke();}";
|
||||
html += "else if(a.notable){ctx.strokeStyle='#ff9f1a';ctx.beginPath();ctx.arc(x,y,9,0,Math.PI*2);ctx.stroke();}";
|
||||
// Ausgewaehltes Flugzeug (per Klick/Tap, siehe unten) bekommt einen
|
||||
// weissen Auswahlring, gleiches Prinzip wie isSelected auf dem
|
||||
// Geraete-Display (radar_screen.cpp render()).
|
||||
// Selected aircraft (by click/tap, see below) gets a
|
||||
// white selection ring, same principle as isSelected on the
|
||||
// device display (radar_screen.cpp render()).
|
||||
html += "if(a.hex===selectedHex){ctx.strokeStyle='#ffffff';ctx.beginPath();ctx.arc(x,y,11,0,Math.PI*2);ctx.stroke();}";
|
||||
html += "ctx.fillStyle=color;ctx.textAlign='center';ctx.fillText(a.callsign,x,y-8);";
|
||||
html += "markers.push({x:x,y:y,a:a});";
|
||||
html += "});";
|
||||
html += "status.textContent=(data.aircraft||[]).length+' aircraft \\u00b7 range '+data.range_km+' km';";
|
||||
// Falls das ausgewaehlte Flugzeug in dieser Aktualisierung nicht
|
||||
// mehr vorkommt (z.B. aus der Reichweite geflogen), Infobox wieder
|
||||
// schliessen statt veraltete Daten stehen zu lassen.
|
||||
// If the selected aircraft no longer appears in this update
|
||||
// (e.g. flew out of range), close the info box instead of
|
||||
// showing stale data.
|
||||
html += "if(selectedHex){var found=markers.filter(function(m){return m.a.hex===selectedHex;})[0];";
|
||||
html += "if(found){showInfo(found.a);}else{selectedHex=null;hideInfo();}}";
|
||||
html += "}";
|
||||
|
||||
// Info-Panel fuer ein angetipptes Flugzeug - bewusst eine eigene,
|
||||
// stehenbleibende Box (kein Tooltip/Popup, das beim naechsten
|
||||
// Neuzeichnen einfach verschwindet), mit explizitem Schliessen-Link,
|
||||
// gleiches Grundprinzip wie infoScreen() am Geraet: der Nutzer soll
|
||||
// aktiv entscheiden, wann die Info wieder verschwindet.
|
||||
// Info panel for a tapped aircraft - intentionally a dedicated,
|
||||
// persistent box (not a tooltip/popup that disappears on the next
|
||||
// redraw), with an explicit close link, same basic principle as
|
||||
// infoScreen() on the device: the user should actively decide when
|
||||
// the info disappears.
|
||||
html += "function fmtNum(n,d){return (typeof n==='number')?n.toFixed(d):'?';}";
|
||||
html += "function showInfo(a){";
|
||||
html += "var lines=[];";
|
||||
@@ -233,11 +232,11 @@ namespace {
|
||||
html += "}";
|
||||
html += "poll();";
|
||||
html += "setInterval(poll,3000);";
|
||||
// Schnellerer, rein lokaler Redraw-Takt (alle 150ms, ohne Netzwerk-
|
||||
// Anfrage) nur fuer das Sternenfunkeln + die Auswahlmarkierung -
|
||||
// gleiches Grundprinzip wie tick() vs. render() auf dem
|
||||
// Geraete-Display: Flugzeugpositionen aktualisieren sich weiterhin
|
||||
// nur alle 3s per poll(), die Sterne twinkeln aber fluessig dazwischen.
|
||||
// Faster, purely local redraw interval (every 150ms, without network
|
||||
// request) only for star twinkling + the selection marker -
|
||||
// same basic principle as tick() vs. render() on the
|
||||
// device display: aircraft positions still update
|
||||
// only every 3s via poll(), but stars twinkle smoothly in between.
|
||||
html += "setInterval(function(){draw(lastData);},150);";
|
||||
html += "})();</script>";
|
||||
}
|
||||
@@ -297,12 +296,12 @@ namespace {
|
||||
if (line.length() == 0) continue;
|
||||
server.sendContent(String(days[i].date) + "," + line + "\n");
|
||||
|
||||
// Wichtig: Ohne regelmaessiges Abgeben der CPU haengt dieser
|
||||
// Task (Core 0, Prioritaet 1) die Idle-Task aus, die den
|
||||
// Task-Watchdog fuettert. Bei groesseren Logbuechern fuehrt
|
||||
// das nach ca. 5s ohne Yield zu einem Watchdog-Reset -
|
||||
// genau der Reboot mitten im CSV-Download, den der Nutzer
|
||||
// beobachtet hat. delay(1) erzwingt einen Kontextwechsel.
|
||||
// Important: Without regularly yielding the CPU, this
|
||||
// task (Core 0, priority 1) starves the idle task that feeds
|
||||
// the task watchdog. With larger logbooks, this causes a
|
||||
// watchdog reset after approx. 5s without yield -
|
||||
// exactly the reboot during CSV download that the user
|
||||
// observed. delay(1) forces a context switch.
|
||||
if (++linesSent % 10 == 0) {
|
||||
delay(1);
|
||||
}
|
||||
@@ -348,14 +347,14 @@ namespace {
|
||||
server.send(303);
|
||||
}
|
||||
|
||||
// Verwaltung von Airline-Filter und Beobachtungsliste per Browser -
|
||||
// beide Backend-Module (AirlineFilter/AircraftWatchlist) sind seit
|
||||
// dieser Erweiterung mutex-geschuetzt (siehe dort), da sie jetzt sowohl
|
||||
// von Core 1 (Radar-/Menue-Screens) als auch von hier aus - Core 0,
|
||||
// WebExportServer laeuft innerhalb von NetTask - aufgerufen werden.
|
||||
// Praktisch vor allem fuer laengere Eingaben (Rufzeichen, ICAO-Codes),
|
||||
// die sich per Handy-Tastatur deutlich bequemer eintippen lassen als
|
||||
// ueber die kleine Bildschirmtastatur des Geraets.
|
||||
// Management of airline filter and watchlist via browser -
|
||||
// both backend modules (AirlineFilter/AircraftWatchlist) are now
|
||||
// mutex-protected (see there), since they are called both
|
||||
// from Core 1 (radar/menu screens) and from here - Core 0,
|
||||
// WebExportServer runs within NetTask.
|
||||
// Practically most useful for longer inputs (callsigns, ICAO codes)
|
||||
// that are much easier to type on a phone keyboard than
|
||||
// via the device's small on-screen keyboard.
|
||||
void handleLists() {
|
||||
String html = htmlHeader("Leos Flightradar - Lists");
|
||||
|
||||
@@ -449,19 +448,18 @@ namespace {
|
||||
server.send(303);
|
||||
}
|
||||
|
||||
// Datenquelle fuer das Live-Radar auf der Startseite (siehe
|
||||
// appendRadarSection()). Wendet dieselben Filter/Prioritaeten an wie das
|
||||
// Geraete-Display (render() in radar_screen.cpp): Reichweite, "Boden-
|
||||
// fahrzeuge ausblenden", Airline-Filter, sowie Notfall/Beobachtungsliste/
|
||||
// "auffaellig" als sich gegenseitig ausschliessende Ring-Markierungen in
|
||||
// genau dieser Prioritaet.
|
||||
// Data source for the live radar on the start page (see
|
||||
// appendRadarSection()). Applies the same filters/priorities as the
|
||||
// device display (render() in radar_screen.cpp): range, "hide ground
|
||||
// vehicles", airline filter, and emergency/watchlist/"notable" as
|
||||
// mutually exclusive ring markers in exactly this priority.
|
||||
void handleRadarJson() {
|
||||
float rangeKm = Config::RANGE_STEPS_KM[SettingsStore::rangeIndex()];
|
||||
// Erlaubt der Web-Ansicht ein eigenes, unabhaengiges Zoomen (siehe
|
||||
// Reichweiten-Waehler in appendRadarSection()), OHNE die Geraete-
|
||||
// Einstellung zu veraendern. Nur einen der bekannten
|
||||
// Config::RANGE_STEPS_KM-Werte akzeptieren - ein fehlender oder
|
||||
// nicht erkannter Parameter faellt auf die Geraete-Reichweite zurueck.
|
||||
// Allows the web view its own independent zooming (see
|
||||
// range selector in appendRadarSection()), WITHOUT changing the
|
||||
// device setting. Only accept one of the known
|
||||
// Config::RANGE_STEPS_KM values - a missing or
|
||||
// unknown parameter falls back to the device range.
|
||||
if (server.hasArg("range_km")) {
|
||||
float requested = server.arg("range_km").toFloat();
|
||||
for (uint8_t i = 0; i < Config::RANGE_STEP_COUNT; i++) {
|
||||
@@ -494,13 +492,13 @@ namespace {
|
||||
bool isHeavy = isHeavyCategoryWeb(a.category);
|
||||
bool isEmergency = emergencyOn && isEmergencySquawkWeb(a.squawk);
|
||||
bool isWatched = watchOn && AircraftWatchlist::isWatched(a.callsign);
|
||||
// "notable" (oranger Ring) ist fuer auffaellige Rufzeichen
|
||||
// (Militaer/Regierung) reserviert - Heavy-Flugzeuge bekommen
|
||||
// stattdessen die eigene Markerform (siehe "heavy" oben). Es
|
||||
// gibt aktuell aber keine Militaer-/Regierungs-Praefixliste im
|
||||
// Projekt (auch nicht am Geraete-Display, siehe
|
||||
// radar_screen.cpp) - "notable" bleibt daher bis auf Weiteres
|
||||
// immer false.
|
||||
// "notable" (orange ring) is reserved for conspicuous callsigns
|
||||
// (military/government) - heavy aircraft instead get their
|
||||
// own marker shape (see "heavy" above). However, there is
|
||||
// currently no military/government prefix list in the
|
||||
// project (not even on the device display, see
|
||||
// radar_screen.cpp) - "notable" therefore remains
|
||||
// always false for now.
|
||||
bool isNotable = false;
|
||||
|
||||
JsonObject o = arr.add<JsonObject>();
|
||||
@@ -516,10 +514,10 @@ namespace {
|
||||
o["emergency"] = isEmergency;
|
||||
o["watched"] = isWatched;
|
||||
o["notable"] = isNotable;
|
||||
// Zusaetzliche Felder nur fuer das Info-Panel bei Klick/Tap auf
|
||||
// ein Flugzeug (siehe showInfo() in appendRadarSection()) - beide
|
||||
// stehen bereits verlustfrei im Aircraft-Snapshot, kein
|
||||
// zusaetzlicher Netzwerk-/SD-Zugriff noetig.
|
||||
// Additional fields only for the info panel on click/tap on
|
||||
// an aircraft (see showInfo() in appendRadarSection()) - both
|
||||
// are already losslessly in the Aircraft snapshot, no
|
||||
// additional network/SD access needed.
|
||||
o["speed_kt"] = a.groundSpeedKt;
|
||||
o["squawk"] = a.squawk;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user