Dieser letzte Teil der Serie beschreibt nun das HTML-Frontend. Wie das PHP-Backend aussieht, hatte ich in den vorangegangen Teilen beschrieben. Erinnern wir uns: Die Applikation soll alle Ports der Steckdosenleiste grafisch mit ihrem Schaltzustand anzeigen. Neben den Ports soll die Grafik mit den angeschlossenen Geräten beschriftet werden. Ein Klick auf das jeweilige Steckdosensymbol soll einen Schaltvorgang auslösen. Aus dieser kurzen Funktionsbeschreibung folgt, welche Funktionalität unser Frontend bereitstellen muss:

  • Grafische Darstellung der Steckdosenleiste mit Angabe der Portbeschreibungen
  • Dynamische Aktualisierung der Portzustände per Ajax
  • Schalten der Steckdosen bei Klick auf die entsprechenden Stellen in der Grafik

Das Frontend und das Backend kommunizieren per Ajax. In regelmäßigen Abständen ruft das Frontend die Schaltzustände vom Backend ab und visualisiert sie entsprechend. Klickt man auf ein Steckdosensymbol, soll die entsprechende Steckdose ein- oder ausgeschaltet werden. Auch hierzu ruft das Frontend per Ajax die entsprechende Backendfunktion auf.

Wenn man die Seite im Browser aufruft, liefert der Webserver die Ausgabe von unserer index.php auf, ohne dass Parameter angegeben sind. Folglich füllt index.php das Template und liefert es an den Browser zur Anzeige aus. Das sieht dann so aus:

Screenshot der Applikation

Schauen wir uns also das Template an:

<head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
        <script src="apcpdu.js"></script>

        <script>
                var outletNames = [
                {% foreach( $outlets as $outlet ) { %}
                        '{{{ $outlet['name'] }}}',
                {% }; %}
                ];
        </script>        
</head>

<body>

        <h1>Willkommen in der Welt der APC PDUs</h1>

        <h1>{{{ $pduName }}}</h1>
        <p>Modell: {{{ $pduIdentModelNumber }}}</p>
        <p>Firmware: {{{ $identFirmwareRev }}}</p>

        <iframe id="apcpdu" height="305px" width="782px" src="APC_PDU.svg" scrolling="no" style="border:0"></iframe>

</body>

Im Kopfteil werden die JavaScripts eingebunden. Neben der bekannten jQuery-Bibliothek sind das unser eigenes Frontend-Script apcpdu.js sowie ein Inline-Script, das ein JavaScript-Array mit den Beschriftungen der Ports initialisiert. Unsere Template-Engine erlaubt die Angabe von PHP-Code, der in {% %} eingeschlossen ist. Hier ist es eine foreach-Schleife, die uns die Portbeschreibungen als JavaScript-Strings erzeugt. Der resultierende JavaScript-Code sieht wie folgt aus:

var outletNames = [
                                        'HP Switch',
                                        'PC',
                                        'Tagschatten',
                                        'Ohnezahn',
                                        'Testserver',
                                        'Storage',
                                        'Outlet 7',
                                        'Outlet 8',
                                ];

Im Body erzeugen wir die Überschrift und geben die Modellbezeichnung sowie die Firmwareversion mittels der {{{ }}}-Syntax der Template-Engine aus. Dann binden wir die eigentliche Grafik als SVG ein. SVG hat hier den großen Vorteil, dass wir eine Hintergrundgrafik verwenden können, die wir durch zusätzliche Elemente ergänzen. Solche Elemente lassen sich im Browser-DOM ansprechen und per jQuery modifizieren.

SVG-Datei in Inkscape erstellen

Wir starten nun also Inkscape und bauen uns eine entsprechende SVG-Grafik zusammen. Neben einer Hintergrundgrafik fügen wir jeder Steckdose Indikatoren für den Schaltzustand, eine transparente Schaltfläche über der Steckdose sowie Beschriftungsfelder hinzu. Diese Elemente reichern wir mit data-Attributen z. B. für die Portnummern an. Diese Data-Attribute können bei der DOM-Suche per jQuery als Selektoren verwendet werden. Zudem lassen sie sich in auslesen, um die richtige Eventreaktion bei Klicks oder beim Setzen des Schaltzustands zu bekommen.

Das JavaScript setzt beim Onload-Event zunächst die Beschriftungen in der SVG. Dazu iteriert es über alle Elemente der SVG, die ein data-outletdesc-Attribut besitzen. Dieses Attribut gibt die Portnummer an. Mit diesem Index liest das Script die Bezeichnung aus dem globalen Feld outletNames und setzt das Textfeld in der SVG:

$( function() {

    $( '#apcpdu' ).on( 'load', function() {
        var $svg = $( '#apcpdu' );
        $svg.contents().find( '[data-outletdescr]' ).each( function( index ) {
            // Portbeschreibungen in SVG eintragen
            var $outlet = $( this );
            var port = $outlet.data( 'outletdescr' ) - 1; 
            if ( outletNames[ port ] )
                $outlet.text( outletNames[ port ] );
        } );
    } );
}  );

Nun müssen wir dafür sorgen, dass die Schaltzustände regelmäßig aktualisiert werden. Dazu plant das Script die Funktion refreshOutletStates() im Onload-Event regelmäßig ein:

    // start refreshing
    window.setTimeout( refreshOutletStates, 0, 2000 );
function refreshOutletStates( timeout ) {
    getOutletStates().done( function( data ) {
        var $svg = $( '#apcpdu' ).contents();
        data.forEach( function( val, index ) {
            var port = index + 1;
            outletStates[  port ] = !!val;
            displayOutletState( port, val );
        } );
    } ).always( function() {
        if ( timeout )
            window.setTimeout( refreshOutletStates, timeout, timeout );
    });
}

refreshOutletStates ruft die Funktion getOutletStates() auf, die den Status per Ajax vom Backend abruft. getOutletStates() liefert ein Promise zurück. Im Erfolgsfall iteriert die Funktion nun über alle SVG-Elemente und ruft zur Darstellung die Funktion displayOutletState() auf. Dann plant sich refreshOutletState neu ein.

function getOutletStates() {
    return $.get(
        "index.php", { 
            command: "getOutletStates"
        }
    );
}

getOutletStates() ruft per Ajax die index.php, also unser Backend, mit dem Parameter „command=getOutletStates“ auf. Das Backend fragt die Daten nun per SNMP ab und liefert sie als JSON-Array an das Frontend zurück. Dabei wird das von getOutletStates zurückgegebene Promise aufgelöst und in refreshOutletStates der done-Part aufgerufen.

function displayOutletState( port, val ) {
    var $svg = $( '#apcpdu' ).contents();
    $svg.find( '[data-port="' + port + '"][data-portvalue="1"]' ).toggle( !!val );
    $svg.find( '[data-port="' + port + '"][data-portvalue="0"]' ).toggle( !val );

    $svg.find( '[data-outletdescr="' + port + '"]' ).parent().css( 'fill', !!val ? 'lime' : 'red' );
}

displayOutletState() sucht die Farbindikatoren in der SVG-Datei anhand des data-port-Attributs und schaltet deren Sichtbarkeit mit toggle() dem Zustand entsprechend ein oder aus. Ebenso wird die Farbe des Beschreibungstexts zwischen rot und grün umgeschaltet.

Nun müssen wir uns noch um das Ein- und Ausschalten per Klick kümmern. Dazu hatten wir in der SVG-Datei transparente Bereiche über die Steckdosen gelegt und mit entsprechenden Attributen versehen. Der Onload-Event kann nun onClick-Ereignisse auf diese Elemente legen und mit der Funktion onClickOutlet verknüpfen:

 $svg.contents().find( '[data-outlet]' ).click( function() {
            // Eventfunktion für Buttons
            var $outlet = $( this );
            var outlet = $outlet.data( 'outlet' );
            onClickOutlet( outlet );
        } );

onClick() toggelt eine globale Variable, die den Schaltzustand enthält und ruft die Ajax-Funktion setOutlet() zum Setzen auf:

function onClickOutlet( outlet ) {
    // Eventfunktion für Buttons
    outletStates[ outlet] = !outletStates[ outlet];
    var val = outletStates[ outlet] ? 1 : 0;
    setOutlet( outlet, val )
}

setOutlet arbeitet ähnlich wie die oben gezeigte Ajax-Funktion:

function setOutlet( port, mode ) {
    return $.get(
        "index.php", { 
            command: "setOutlet", 
            port : port,
            mode : mode 
        }
    );
}

Neben dem Kommand übergibt setOutlet noch die Portnummer und den Status an das Backend.

Die hier gezeigten Funkionen dienen nur der prinzipiellen Erklärung der Funktionsweise. In der Praxis muss man berücksichtigen, dass die Ajax-Aufrufe asynchron ablaufen und eine gewisse Laufzeit benötigen. Um eine unverzügliche Aktualisierung der Schaltzustände nach einem Klick zu bekommen, muss onClickOutlet z. B. die Darstellung sofort dem neuen Zustand anpassen. Dies führt dann allerdings mit einem möglicherweise gerade gleichzeitig ablaufenden getOutletStates()-Request zu einer inkonsistenten Darstellung. Diese Fälle müssen durch geeignete Sperrmechanismen im Frontend verhindert werden. Um eine klare und einfache Ablaufdarstellung zu ermöglichen, habe ich sie hier weggelassen.

Damit ist unsere kleine Serie über die Entwicklung einer Webapplikation für die IP-Steckdosen APC PDU 7920 abgeschlossen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert