Refactoring des Web Interfaces und der REST API

Abend zusammen,
da ich gerade an einem Redesign der Web UI arbeite (kleiner Teaser unter Erweiterung der Weboberfläche - #99 von sonovice), würde ich ganz gerne sowohl im Front- als auch Backend etwas aufräumen.

Augenblicklich ist es im Backend mit all den anonymen Funktionen etwas unübersichtlich. Ganz grundlegend wäre es schön, eine ordentliche JSON REST-API anzubieten, mit der man etwas vereinheitlichter Daten lesen und schreiben kann. Mir schwebt da in etwa soetwas vor:

Endpoints mit jeweils einer Funktion im Backend pro Endpoint / HTTP method Kombination:

/api/execute?cmd=reboot
  GET:  Einfache Befehle (auch shutdown, play/pause etc.)

/api/status
  GET:  Infos über aktuell gespielten Track / aktuelle RFID-Karte etc.
        WebSockets machen hier vermutlich Sinn

/api/cover
  GET:  Aktuelles Cover-Image holen

/api/settings[?section=wifi]
  GET:  Aktuelle Settings holen (inkl. WLAN, FTP, MQTT, ...)
        Optional auch nur Teile davon
  PUT:  Settings setzen

/api/debug
  GET:  Systemparameter holen
        Quasi das aktuelle "/info". Momentan ist es einfacher Text. Ich würde hier JSON mit fester Struktur bevorzugen.
        Englisch-only reicht, ist ja quasi nur für Debugging.

/api/log
  GET:  Log holen
        Hier ist Plaintext völlig okay

Dazu kommt noch ein files Endpoint, den ich erstmal etwas außen vor gelassen habe. Wird genutzt, um den Dateibaum anzuzeigen, Dateien/Ordner hochzuladen etc. Das muss ich mir noch genauer anschauen, scheint ja schon einiges an Hirnschmalz reingeflossen zu sein.

/api/files
  GET:  Datenverzeichnis holen

/api/files/upload
  POST: File upload
...

An alle Schlauberger, die schon länger mit den ESPuino Sources arbeiten: Habe ich etwas wichtiges vergessen? Macht es so Sinn?

1 „Gefällt mir“

@ sonovice Also erstmal finde ich es super das Du Dich des Themas annehmen möchtest, die ersten Entwürde sehen Klasse aus! Habe vorher noch nie von diesen Frameworks gehört ;-(

Die Webschnittstelle ist hier im Laufe der Jahre(!) natürlich gewachsen, ein Refactoring macht Sinn ist aber auch recht komplex. Dazu müsste praktisch Web.cpp neu geschrieben werden. Da ist also auch viel Backend Arbeit notwendig. Möchtest Du Dir das antun? Wenn ja::

Einfache Aufrufe wie /Info, /Logo /restart usw. können natürlich über ein Endpunkt zusammengefasst werden.Der Endpunkt /explorer bietet ja jetzt schon eine echte REST-Struktur mit Get für’s Anzeigen, Put zum Anlegen eines Ordner, Patch für’s Umbenennen, Delete zum Löschen usw. Finde jetzt den /explorer Endpunkt total OK so wie er implementiert wurde, Deine obige Idee nicht so…

Mir fallen zwei kritische Punkte ein die Du beachten solltest:

  • Der Template Processor. Die statische Webseite wird durch Platzhalter vor dem Ausliefern mit Werten aus der Konfiguration gefüttert. Das muss ersetzt werden damit die Webseite so statisch ausgeliefert werden kann
  • Viel Kommunikation über das Websocket um Daten in Echtzeit zu aktualisieren (RFID-Kennung, Lautstärke, Status des Webplayers, Coverbild)

@Christian hat auch super Ideen in seinem Fork umgesetzt wie die Symbole für Batterie, WLAN-Stärke und Heartbeat, die lassen sich aber bislang nicht so leicht integrieren.

Bin gespannt wie’s weiter gehen kann…

1 „Gefällt mir“

Danke für deine Gedanken, @tueddy! Erstmal zu den kritischen Punkten:

Genau das würde ich gerne umgehen, und zwar aus mehreren Gründen: Zum einen könnte man ohne Templating die Webseite komprimieren mit GZIP oder besser noch brotli. Spart im Schnitt >75%. Zum anderen wird das funktionale Design auf beiden Seiten deutlich vereinfacht, wenn ich einfach beim Laden der Webseite einmalig die benötigten Variablen per REST abhole. Das ganze ist dann natürlich eine dynamische Webseite, was hier aber mehr Vor- als Nachteile hat.

Bei Änderung von RFID, Track, Lautstärke und Musikstatus eine Meldung vom ESP gepushed zu bekommen sollte eigentlich nicht wild sein. Cover kann man ja nur dann abholen, wenn sich RFID/Track verändern. Echtzeit bzw. konstante Datenströme sind da in meinen Augen nicht nötig. (Allenfalls, wenn man später Features wie aktuelle Position im Track etc. haben möchte, aber Zusatzfeatures stelle ich erstmal hinten an. Es geht mir im ersten Schritt nur um’s Refactoring.)

Japp, will ich. :wink: Super komplex scheint es mir nicht zu sein und 1365 LOC sind doch noch sehr überschaubar. Ich möchte hier nicht das Rad komplett neuerfinden (z.B. beim /explorer Endpoint), aber ein sauberes Refactoring würde dem ganzen doch ganz gut tun, denke ich.

EDIT: Dein letzter Absatz ist mir durchgerutscht…

Ja, das hatte mich mir angeschaut und finde es auch sehr schön. Mit den angestrebten Änderungen wäre soetwas quasi banal zu implementieren, auf beiden Seiten. (Vorausgesetzt, ich bekomme diese Sensorwerte problemlos ausgelesen in der Web.cpp…)

Ja Klasse, da bin ich gespannt! Machst Du einen Fork mit dem wir testen können?

Sobald es eine erste funktionale Version gibt - sehr gerne!

Erstmal stricke ich noch etwas an der UI rum und richte mir alles so ein, wie ich mir das Zusammenspiel mit dem Backend vorstelle und dann wird letzteres angegangen. Ich muss mich auch noch etwas gedulden auf meinen neuen ESP32, habe momentan gar nichts zum flashen hier…

Das ist bei mir alles ein „endpoint“ welcher ein JSON bekommt wo drin steht was passieren soll.

  • Execute CMD - Hier übergebe ich z.B. einfach Befehle aus der values.h
  • Get-Settings
  • Get-File-List
  • usw.

Will sagen, ich habe das nicht auf verschiedene Endpoints aufgeteilt, sondern in der web.ccp eine Funktion, die den empfangenen JSON auswertet. Aber kann man sicher auch anders machen.

Die gibt es ja schon - unter anderem Namen. Da musst Du nicht viel machen.

Ich habe fast alles auf WebSockets umgestellt. Aber das ist halt die Frage. Wenn Du vom Client per GET abfragst und immer nur eine Antwort bekommst, kommst Du auch ohne WebSockets aus. Das macht es deutlich einfacher. Ich habe es so implementiert, dass der Client per WebSocket einen Befehl sendet und dann auch per WebSocket 1-n Antworten bekommt. Das hat z.B. den Vorteil, dass wenn große Verzeichnisse gelesen werden, sehr schnell die ersten Daten kommen und sich das Verzeichnis in der UI aufbaut.
Wenn der Client ein „Init-Me“ sendet - sich quasi anmeldet - bekommt er alle x-Sekunden eine Status-Info. Er würde z.B. auch die Info bekommen, dass ein neues Cover-Bild vorhanden ist, dieses müsste er dann aber per HTTP-GET abholen.

So gesehen ist Dein Ansatz, erst einmal aufzuräumen, sicher gut - Ich habe zu viele Funktionen gleichzeitig implementiert. Ich geniesse es allerdings auch im privaten Bereich mal herrlich unstrukturiert das zu machen was gerade Spaß macht :smiley:

Guten Morgen und danke für die Einblicke, @Christian.

Ich bin großer Verfechter von „separation of concerns“ in der Softwarearchitektur, weil es langfristig einfach den Code sehr viel wartbarer macht. Daher die strikte Aufteilung der Endpoints. Ich möchte genau die Daten holen oder schreiben können, die mich interessieren, ohne den Overhead dafür zu erhöhen. Macht die Code Basis letztlich schlanker.

Auch da würde ich mich auf Sockets beschränken, wo es notwendig ist. Für konstante Datenströme oder viel Verkehr (z.B. der Explorer) ist das natürlich vorteilhaft.

Was man, wenn man eh keine externen Quellen nachladen muss, direkt aufgeben sollte, ist die Access-GUI. Da sollte man dann einfach immer die Mgmt-GUI anzeigen.

Nur was dann aber auf jeden Fall rein muss, ist ein PWD-Schutz. Das wurde hier immer mal wieder andiskutiert, aber letztlich war der potentielle „Schadvektor“ über die Access-GUI auch ziemlich begrenzt. Das würde sich jetzt natürlich ändern. Vielleicht macht man das der Einfachheit halber jedoch so, dass man nicht die GUI selbst sondern den Zugang für den AP des ESP32 mit einen PWD versieht. Dafür braucht man hier lediglich ein zweites Argument für das Passwort. Vorteil: Das Passwort gibt man dann pro Zugangsgerät (Handy, Laptop) nur exakt einmal ein, wenn man sich erstmalig mit dem Netzwerk verbinden will und dann nervt das nie wieder. Man muss sich nur überlegen, wo man das PWD speichert. Also ob man es via settings.h hartkodiert oder ob man es in die GUI zieht und im NVS speichert. Der zweite Fall ist sicherlich komfortabler, allerdings sollte man dann das PWD beim Start in der seriellen Konsole anzeigen (das halte ich sicherheitstechnisch für vertretbar), da sich sonst garantiert irgendwelche Leute früher oder später aussperren :slight_smile:.

Also grundsätzlich bin ich dafür, und das hast du mir per PM ja auch vorgeschlagen, dass man die aktuelle Funktionalität einfach erstmal abbildet und keine neuen Features einbaut. Aber die Abschaffung der Access-GUI ist aus meiner Sicht ein logischer Schritt, den man hier gleich mitmachen sollte. Weil räumt sofort den Code auf, bringt Komfort und ist eine logische Konsequenz.

Cool!

Ja, und ja. Access-Seite würde ich auch direkt droppen bzw. die Funktionalität mit integrieren, Passwort gehört (meiner Ansicht nach) ins NVS und ebenso konfigurierbar in die UI. Ich werde den Use case bzw. Workflow dafür nochmal genau aufdröseln, wenn es soweit ist.

Kleiner Zwischenbericht

Settings-Seite sollte demnächst abgeschlossen sein inkl. Anbindung an’s (noch imaginäre) Backend. Als „Seiten“ sind nach eingehenden Monologen nur noch „Control“, „Collection“ und „Settings“ übriggeblieben, der Rest ist zu diesen Karten unter „Settings“ zusammengefasst worden.

Wording in den unteren Texten ist noch etwas „lala“ bzw. für den einfachen Benutzer zu technisch, fixe ich im Laufe der Weiterentwicklung.

Habe einen kleinen Mock-Server, um auch ohne Hardware ein Backend zum Testen zu haben. Soweit klappt alles erstmal ganz gut. Die kommende Woche ist dann die Control-Seite dran.

Die Räder vom Bus dreh’n rundherum… rundherum…

2 „Gefällt mir“

Kleine Info: Bei jedem Commit wird jetzt eine Vorschau gebaut, zu erreichen unter https://sonovice.github.io/espuino-ui/ .

Falls jemand Lust und Laune hat, ein paar Regler zu schieben. Funktionieren tut davon noch nichts wirklich. Der „Reset“-Button setzt momentan auch alle anderen Regler auf der Seite zurück, weil der Mock-Server zu dämlich ist…

2 „Gefällt mir“

Coole Sache!
Was ist denn „Sammlung“?

Mein Bislang schlechter Begriff von Audio + RFID :smiley:

1 „Gefällt mir“

Sehr geil, die UI fühlt sich super modern an :+1:
Klasse auch das wir die Aktualiserungen in Echtzeit sehen können.

Svelte, kommt das aus Dänemark?
Sammlung → „Kartenzuweisung“, „Kartenverwaltung“, „RFID Karten“…
Für die Steuerung evt. noch ein Lautstärkeregler, kenne ich so von iOS Music und Spotify.

Der Hauptentwickler ist jedenfalls kein Däne, aber der Name hat definitiv etwas Nordisches.

Beim Begriff komme ich immer noch ins Straucheln: Nicht alle benutzen Karten, manchmal sind es auch Sticker (oder - Gott verhüte - originale Ton**figuren). „RFID“ hingegen mutet für meinen Geschmack etwas zu technisch an, wenn man z.B. (wie in meinem Falle) so eine Box an technisch eher wenig versierte Menschen verschenken möchte. Vllt einfach „Verknüpfungen“?

Lautstärke- und Helligkeitsdimmer kommt noch rein, ich hab erst gestern Abend mit der Control Seite angefangen.

Man könnte auch einfach den englischen Begriff „Tag“ oder lose übersetzt „Marke“ verwenden. Generisch vielleicht auch „Auslöser“.

Aber generell zum Thema: Finde es sehr gut und wichtig die Endpoints von der UI zu trennen. So ist es wesentlich einfacher neue Features hinzuzufügen.
Ich z.B. wollte andere Optionen für die Batteriekonfiguration hinzufügen (Fuel Gauge). Das war aber sehr umständlich und ich hab es dann bis eine neue UI kommt aufgeschoben.
Kann mir aber auch vorstellen, dass man dann zukünftig die ein oder andere Option die nur in settings.h einstellbar ist im Frontend verfügbar macht.

Dann wäre es sogar möglich, die komplette UI ohne viel Aufwand auszutauschen.
Falls das jemand braucht, ist auch eine Integration in Heimautomatisierungs-Software machbar (ohne MQTT).

„Tags“ als Überschrift gefällt mir ganz gut!

Ja, der Aufbau einer in sich geschlossenen REST API ohne den Zuschnitt auf ein ganz bestimmtes Frontend hat definitiv seine Vorzüge. Sobald hier die ESPs eintrudeln, fange ich damit an.

Mittlerweile ändert sich meine Meinung ein wenig… Ohne Web Sockets gehen einige Sachen deutlich schlechter oder gar nicht. Vllt plane ich doch von Anfang an, alles darüber laufen zu lassen. Hab mir dazu auch Web UIs von kommerziellen Geräten (v.a. Audio-Interface aus dem Pro-Segment) angeguckt, die lösen das tatsächlich auch durch die Bank weg mit Sockets, aus recht gutem Grund.

Und da geht die saubere Strukturierung vorerst flöten… :woozy_face:

Willkommen in meiner Welt :grin:

1 „Gefällt mir“

Ich finde den Regler echt schwer zu sehen, so ganz ohne Kontrast zum Hintergrund…
(Wenn der ganz links oder rechts steht noch schlimmer)

aber sonst siehts sehr schick aus :smiley:

1 „Gefällt mir“