Andrew Boyson / web

Dependents:   oldheating gps motorhome heating

Files at this revision

API Documentation at this revision

Comitter:
andrewboyson
Date:
Tue Apr 30 12:45:08 2019 +0000
Parent:
109:3e82f62c7e1f
Child:
111:aaa858678e34
Commit message:
Tidied. About to rename to web.

Changed in this revision

base/clock/web-clock-ajax.c Show annotated file Show diff for this revision Revisions of this file
base/clock/web-clock-class.inc Show annotated file Show diff for this revision Revisions of this file
base/clock/web-clock-class.js Show annotated file Show diff for this revision Revisions of this file
base/clock/web-clock-html.c Show annotated file Show diff for this revision Revisions of this file
base/clock/web-clock-script.c Show annotated file Show diff for this revision Revisions of this file
base/clock/web-clock-script.inc Show annotated file Show diff for this revision Revisions of this file
base/clock/web-clock-script.js Show annotated file Show diff for this revision Revisions of this file
base/clock/web-web-query.c Show annotated file Show diff for this revision Revisions of this file
base/css/web-base-css.c Show annotated file Show diff for this revision Revisions of this file
base/css/web-base-css.inc Show annotated file Show diff for this revision Revisions of this file
base/css/web-nav-css.c Show annotated file Show diff for this revision Revisions of this file
base/css/web-nav-css.inc Show annotated file Show diff for this revision Revisions of this file
base/fault/web-fault-html.c Show annotated file Show diff for this revision Revisions of this file
base/fault/web-fault-query.c Show annotated file Show diff for this revision Revisions of this file
base/favicon/web-favicon.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-ajax.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-html.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-post.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-query.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-script.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-script.inc Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware-script.js Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware.c Show annotated file Show diff for this revision Revisions of this file
base/firmware/web-firmware.h Show annotated file Show diff for this revision Revisions of this file
base/log/web-log-html.c Show annotated file Show diff for this revision Revisions of this file
base/log/web-log-query.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login-css.inc Show annotated file Show diff for this revision Revisions of this file
base/login/web-login-html.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login-password.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login-query.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login-session-id.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login-session-name.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login.c Show annotated file Show diff for this revision Revisions of this file
base/login/web-login.h Show annotated file Show diff for this revision Revisions of this file
base/net-trace/web-trace-ajax.c Show annotated file Show diff for this revision Revisions of this file
base/net-trace/web-trace-html.c Show annotated file Show diff for this revision Revisions of this file
base/net-trace/web-trace-query.c Show annotated file Show diff for this revision Revisions of this file
base/net-trace/web-trace-script.c Show annotated file Show diff for this revision Revisions of this file
base/net-trace/web-trace-script.inc Show annotated file Show diff for this revision Revisions of this file
base/net-trace/web-trace-script.js Show annotated file Show diff for this revision Revisions of this file
base/net/web-net-class.inc Show annotated file Show diff for this revision Revisions of this file
base/net/web-net-class.js Show annotated file Show diff for this revision Revisions of this file
base/net/web-net-html.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net4-ajax.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net4-html.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net4-script.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net4-script.inc Show annotated file Show diff for this revision Revisions of this file
base/net/web-net4-script.js Show annotated file Show diff for this revision Revisions of this file
base/net/web-net6-ajax.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net6-html.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net6-script.c Show annotated file Show diff for this revision Revisions of this file
base/net/web-net6-script.inc Show annotated file Show diff for this revision Revisions of this file
base/net/web-net6-script.js Show annotated file Show diff for this revision Revisions of this file
base/web-ajax-class.inc Show diff for this revision Revisions of this file
base/web-ajax-class.js Show diff for this revision Revisions of this file
base/web-nav-base.c Show annotated file Show diff for this revision Revisions of this file
base/web-nav-base.h Show annotated file Show diff for this revision Revisions of this file
base/web-pages-base.h Show annotated file Show diff for this revision Revisions of this file
base/web-server-base.c Show annotated file Show diff for this revision Revisions of this file
base/web-server-base.h Show annotated file Show diff for this revision Revisions of this file
clock/web-clock-ajax.c Show diff for this revision Revisions of this file
clock/web-clock-class.inc Show diff for this revision Revisions of this file
clock/web-clock-class.js Show diff for this revision Revisions of this file
clock/web-clock-html.c Show diff for this revision Revisions of this file
clock/web-clock-script.c Show diff for this revision Revisions of this file
clock/web-clock-script.inc Show diff for this revision Revisions of this file
clock/web-clock-script.js Show diff for this revision Revisions of this file
clock/web-web-query.c Show diff for this revision Revisions of this file
core/web-add.c Show annotated file Show diff for this revision Revisions of this file
core/web-add.h Show annotated file Show diff for this revision Revisions of this file
core/web-ajax-class.inc Show annotated file Show diff for this revision Revisions of this file
core/web-ajax-class.js Show annotated file Show diff for this revision Revisions of this file
css/web-base-css.c Show diff for this revision Revisions of this file
css/web-base-css.inc Show diff for this revision Revisions of this file
css/web-nav-css.c Show diff for this revision Revisions of this file
css/web-nav-css.inc Show diff for this revision Revisions of this file
fault/web-fault-html.c Show diff for this revision Revisions of this file
fault/web-fault-query.c Show diff for this revision Revisions of this file
favicon/web-favicon.c Show diff for this revision Revisions of this file
firmware/web-firmware-ajax.c Show diff for this revision Revisions of this file
firmware/web-firmware-html.c Show diff for this revision Revisions of this file
firmware/web-firmware-post.c Show diff for this revision Revisions of this file
firmware/web-firmware-query.c Show diff for this revision Revisions of this file
firmware/web-firmware-script.c Show diff for this revision Revisions of this file
firmware/web-firmware-script.inc Show diff for this revision Revisions of this file
firmware/web-firmware-script.js Show diff for this revision Revisions of this file
firmware/web-firmware.c Show diff for this revision Revisions of this file
firmware/web-firmware.h Show diff for this revision Revisions of this file
log/web-log-html.c Show diff for this revision Revisions of this file
log/web-log-query.c Show diff for this revision Revisions of this file
login/web-login-css.inc Show diff for this revision Revisions of this file
login/web-login-html.c Show diff for this revision Revisions of this file
login/web-login-password.c Show diff for this revision Revisions of this file
login/web-login-query.c Show diff for this revision Revisions of this file
login/web-login-session-id.c Show diff for this revision Revisions of this file
login/web-login-session-name.c Show diff for this revision Revisions of this file
login/web-login.c Show diff for this revision Revisions of this file
login/web-login.h Show diff for this revision Revisions of this file
net-trace/web-trace-ajax.c Show diff for this revision Revisions of this file
net-trace/web-trace-html.c Show diff for this revision Revisions of this file
net-trace/web-trace-query.c Show diff for this revision Revisions of this file
net-trace/web-trace-script.c Show diff for this revision Revisions of this file
net-trace/web-trace-script.inc Show diff for this revision Revisions of this file
net-trace/web-trace-script.js Show diff for this revision Revisions of this file
net/web-net-class.inc Show diff for this revision Revisions of this file
net/web-net-class.js Show diff for this revision Revisions of this file
net/web-net-html.c Show diff for this revision Revisions of this file
net/web-net4-ajax.c Show diff for this revision Revisions of this file
net/web-net4-html.c Show diff for this revision Revisions of this file
net/web-net4-script.c Show diff for this revision Revisions of this file
net/web-net4-script.inc Show diff for this revision Revisions of this file
net/web-net4-script.js Show diff for this revision Revisions of this file
net/web-net6-ajax.c Show diff for this revision Revisions of this file
net/web-net6-html.c Show diff for this revision Revisions of this file
net/web-net6-script.c Show diff for this revision Revisions of this file
net/web-net6-script.inc Show diff for this revision Revisions of this file
net/web-net6-script.js Show diff for this revision Revisions of this file
page/web-nav-base.c Show diff for this revision Revisions of this file
page/web-nav-base.h Show diff for this revision Revisions of this file
page/web-page-base.h Show diff for this revision Revisions of this file
web-add.c Show diff for this revision Revisions of this file
web-add.h Show diff for this revision Revisions of this file
web-base.h Show diff for this revision Revisions of this file
web-server-base.c Show diff for this revision Revisions of this file
web-server-base.h Show diff for this revision Revisions of this file
web.c Show annotated file Show diff for this revision Revisions of this file
web.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-ajax.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,64 @@
+#include  <stdint.h>
+#include   <stdio.h>
+
+#include "http.h"
+#include "rtc.h"
+#include "clk.h"
+#include "clktime.h"
+#include "clkgov.h"
+#include "clkutc.h"
+#include "ntpclient.h"
+#include "scan.h"
+
+void WebClockAjax()
+{
+    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
+    
+    //Time and UTC
+    clktime now = ClkNowTai();
+    clktime fraction = now & ((1UL << CLK_TIME_ONE_SECOND_SHIFT) - 1);
+    clktime ms = (fraction * 1000) >> CLK_TIME_ONE_SECOND_SHIFT;
+    HttpAddInt16AsHex(ms                           ); HttpAddChar('\n');
+    char byte = 0;
+    if (RtcIsSet()                ) byte |= 0x01;
+    if (ClkTimeIsSet()            ) byte |= 0x02;
+    if (ClkGovIsReceivingTime     ) byte |= 0x04;
+    if (ClkGovRateIsSynced        ) byte |= 0x08;
+    if (ClkGovTimeIsSynced        ) byte |= 0x10;
+    if (ClkUtcGetNextLeapEnable() ) byte |= 0x20;
+    if (ClkUtcGetNextLeapForward()) byte |= 0x40;
+    if (ClkGovTrace)                byte |= 0x80;
+    HttpAddByteAsHex (byte                         ); HttpAddChar('\n');
+    HttpAddInt12AsHex(ClkUtcGetNextEpochMonth1970()); HttpAddChar('\n');
+    HttpAddInt16AsHex(ClkUtcGetEpochOffset()       ); HttpAddChar('\n');
+    HttpAddChar('\f');
+    
+    //Governer
+    HttpAddInt32AsHex(ClkGovGetPpb()               ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovFreqDivisor            ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovFreqChangeMaxPpb       ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovFreqSyncedLimPpb       ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovFreqSyncedHysPpb       ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovSlewDivisor            ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovSlewChangeMaxMs        ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovSlewSyncedLimNs        ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovSlewSyncedHysNs        ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ClkGovSlewOffsetMaxSecs      ); HttpAddChar('\n');
+    HttpAddChar('\f');
+    
+    //NTP
+    HttpAddText      (NtpClientQueryServerName     ); HttpAddChar('\n');
+    HttpAddInt32AsHex(NtpClientQueryInitialInterval); HttpAddChar('\n');
+    HttpAddInt32AsHex(NtpClientQueryNormalInterval ); HttpAddChar('\n');
+    HttpAddInt32AsHex(NtpClientQueryRetryInterval  ); HttpAddChar('\n');
+    HttpAddInt32AsHex(NtpClientReplyOffsetMs       ); HttpAddChar('\n');
+    HttpAddInt32AsHex(NtpClientReplyMaxDelayMs     ); HttpAddChar('\n');
+    HttpAddChar('\f');
+    
+    //Scan
+    HttpAddInt32AsHex(ScanAverage                  ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ScanMaximum                  ); HttpAddChar('\n');
+    HttpAddInt32AsHex(ScanMinimum                  ); HttpAddChar('\n');
+    HttpAddChar('\f');
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-class.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,108 @@
+"//Clock class\n"
+"'use strict';\n"
+"\n"
+"class Clock\n"
+"{\n"
+"    constructor()\n"
+"    {\n"
+"        this.leapEnable  = false;\n"
+"        this.leapForward = false;\n"
+"        this.leapMonth   = 0;\n"
+"        this.leapYear    = 0;\n"
+"        this.leaps       = 0;\n"
+"        this.ms          = 0;\n"
+"    }\n"
+"    \n"
+"    set months1970(value)\n"
+"    {\n"
+"        this.leapMonth   =  value                   % 12;\n"
+"        this.leapYear    = (value - this.leapMonth) / 12;\n"
+"        this.leapMonth  += 1;\n"
+"        this.leapYear   += 1970;\n"
+"    }\n"
+"    get months1970()\n"
+"    {\n"
+"        if (this.leapYear  <= 0) return 0;\n"
+"        if (this.leapMonth <= 0) return 0;\n"
+"        return (this.leapYear - 1970) * 12 + this.leapMonth - 1;\n"
+"    }\n"
+"    \n"
+"    formatNumbers00(i)\n"
+"    {\n"
+"       if (i < 10) return '0' + i;\n"
+"       return i;\n"
+"    }\n"
+"    formatDayOfWeek(wday)\n"
+"    {\n"
+"        switch(wday)\n"
+"        {\n"
+"            case  0: return 'Sun';\n"
+"            case  1: return 'Mon';\n"
+"            case  2: return 'Tue';\n"
+"            case  3: return 'Wed';\n"
+"            case  4: return 'Thu';\n"
+"            case  5: return 'Fri';\n"
+"            case  6: return 'Sat';\n"
+"            default: return '---';\n"
+"        }\n"
+"    }\n"
+"    adjustLeap(baseMs)\n"
+"    {\n"
+"        if (this.ms == 0) return; //Don't attempt to adjust an invalid time\n"
+"        \n"
+"        if (!this.leapEnable) return; // Adjustment disabled\n"
+"        \n"
+"        //Get the calander date and time from the ms\n"
+"        let now       = this.ms + baseMs;\n"
+"        let leapStart = Date.UTC(this.leapYear, this.leapMonth - 1, 1, 0, 0, this.leapForward ? 0: -1);\n"
+"        \n"
+"        if (now < leapStart) return; //Do nothing until reached the leap start\n"
+"        \n"
+"        if (this.leapForward) { this.ms -= 1000; this.leaps += 1; } //repeat 59\n"
+"        else                  { this.ms += 1000; this.leaps -= 1; } //skip   59\n"
+"        \n"
+"        this.leapEnable = false;\n"
+"    }\n"
+"    displayTime(baseMs)\n"
+"    {\n"
+"        if (this.ms == 0) return; //Don't attempt to display an invalid time\n"
+"        \n"
+"        //Get the calander date and time from the ms\n"
+"        let now = new Date(this.ms + baseMs);\n"
+"        let   y = now.getUTCFullYear();\n"
+"        let   n = now.getUTCMonth   () + 1;\n"
+"        let   d = now.getUTCDate    ();\n"
+"        let   w = now.getUTCDay     (); // 0 == Sunday\n"
+"        let   h = now.getUTCHours   ();\n"
+"        let   m = now.getUTCMinutes ();\n"
+"        let   s = now.getUTCSeconds ();\n"
+"        \n"
+"        //Format time\n"
+"        n = this.formatNumbers00(n);\n"
+"        d = this.formatNumbers00(d);\n"
+"        h = this.formatNumbers00(h);\n"
+"        m = this.formatNumbers00(m);\n"
+"        s = this.formatNumbers00(s);\n"
+"        w = this.formatDayOfWeek(w);\n"
+"        \n"
+"        //Display time\n"
+"        let elem;\n"
+"        elem = document.getElementById('ajax-date-utc');\n"
+"        if (elem) elem.textContent = y + '-' + n + '-' + d + ' ' + w + ' ' + h + ':' + m + ':' + s + ' TAI-UTC=' + this.leaps;\n"
+"    \n"
+"        elem = document.getElementById('ajax-date-pc');\n"
+"        let options = \n"
+"        {\n"
+"            year:         'numeric',\n"
+"            month:        'short',\n"
+"            day:          '2-digit',\n"
+"            weekday:      'short',\n"
+"            hour:         '2-digit',\n"
+"            minute:       '2-digit',\n"
+"            second:       '2-digit',\n"
+"            timeZoneName: 'short'\n"
+"        };\n"
+"        if (elem) elem.textContent = now.toLocaleString(undefined, options);\n"
+"    }\n"
+"}\n"
+""
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-class.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,107 @@
+//Clock class
+'use strict';
+
+class Clock
+{
+    constructor()
+    {
+        this.leapEnable  = false;
+        this.leapForward = false;
+        this.leapMonth   = 0;
+        this.leapYear    = 0;
+        this.leaps       = 0;
+        this.ms          = 0;
+    }
+    
+    set months1970(value)
+    {
+        this.leapMonth   =  value                   % 12;
+        this.leapYear    = (value - this.leapMonth) / 12;
+        this.leapMonth  += 1;
+        this.leapYear   += 1970;
+    }
+    get months1970()
+    {
+        if (this.leapYear  <= 0) return 0;
+        if (this.leapMonth <= 0) return 0;
+        return (this.leapYear - 1970) * 12 + this.leapMonth - 1;
+    }
+    
+    formatNumbers00(i)
+    {
+       if (i < 10) return '0' + i;
+       return i;
+    }
+    formatDayOfWeek(wday)
+    {
+        switch(wday)
+        {
+            case  0: return 'Sun';
+            case  1: return 'Mon';
+            case  2: return 'Tue';
+            case  3: return 'Wed';
+            case  4: return 'Thu';
+            case  5: return 'Fri';
+            case  6: return 'Sat';
+            default: return '---';
+        }
+    }
+    adjustLeap(baseMs)
+    {
+        if (this.ms == 0) return; //Don't attempt to adjust an invalid time
+        
+        if (!this.leapEnable) return; // Adjustment disabled
+        
+        //Get the calander date and time from the ms
+        let now       = this.ms + baseMs;
+        let leapStart = Date.UTC(this.leapYear, this.leapMonth - 1, 1, 0, 0, this.leapForward ? 0: -1);
+        
+        if (now < leapStart) return; //Do nothing until reached the leap start
+        
+        if (this.leapForward) { this.ms -= 1000; this.leaps += 1; } //repeat 59
+        else                  { this.ms += 1000; this.leaps -= 1; } //skip   59
+        
+        this.leapEnable = false;
+    }
+    displayTime(baseMs)
+    {
+        if (this.ms == 0) return; //Don't attempt to display an invalid time
+        
+        //Get the calander date and time from the ms
+        let now = new Date(this.ms + baseMs);
+        let   y = now.getUTCFullYear();
+        let   n = now.getUTCMonth   () + 1;
+        let   d = now.getUTCDate    ();
+        let   w = now.getUTCDay     (); // 0 == Sunday
+        let   h = now.getUTCHours   ();
+        let   m = now.getUTCMinutes ();
+        let   s = now.getUTCSeconds ();
+        
+        //Format time
+        n = this.formatNumbers00(n);
+        d = this.formatNumbers00(d);
+        h = this.formatNumbers00(h);
+        m = this.formatNumbers00(m);
+        s = this.formatNumbers00(s);
+        w = this.formatDayOfWeek(w);
+        
+        //Display time
+        let elem;
+        elem = document.getElementById('ajax-date-utc');
+        if (elem) elem.textContent = y + '-' + n + '-' + d + ' ' + w + ' ' + h + ':' + m + ':' + s + ' TAI-UTC=' + this.leaps;
+    
+        elem = document.getElementById('ajax-date-pc');
+        let options = 
+        {
+            year:         'numeric',
+            month:        'short',
+            day:          '2-digit',
+            weekday:      'short',
+            hour:         '2-digit',
+            minute:       '2-digit',
+            second:       '2-digit',
+            timeZoneName: 'short'
+        };
+        if (elem) elem.textContent = now.toLocaleString(undefined, options);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,68 @@
+#include <time.h>
+
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-add.h"
+
+void WebClockHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Clock", "settings.css", "clock.js");
+    WebAddNav(CLOCK_PAGE);
+    WebAddH1("Clock");
+    
+    WebAddH2("Status");
+    WebAddAjaxLed("RTC is set"           , "ajax-rtc-set"    );
+    WebAddAjaxLed("Clock is set"         , "ajax-clock-set"  );
+    WebAddAjaxLed("External source is ok", "ajax-source-ok"  );
+    WebAddAjaxLed("Time synchronised"    , "ajax-time-locked");
+    WebAddAjaxLed("Rate synchronised"    , "ajax-rate-locked");
+    
+    WebAddH2("Server UTC time");
+    HttpAddText("<div id='ajax-date-utc'></div>\r\n");
+
+    WebAddH2("Server local time");
+    HttpAddText("<div id='ajax-date-pc'></div>\r\n");
+    
+    WebAddH2("Server - PC (ms)");
+    HttpAddText("<div id='ajax-date-diff'></div>\r\n");    
+    
+    WebAddH2("UTC");
+    WebAddAjaxInputToggle("Enable epoch change"     ,    "ajax-leap-enable"  , "chg-clock-leap-enable" );
+    WebAddAjaxInputToggle("Direction of next epoch" ,    "ajax-leap-forward" , "chg-clock-leap-forward");
+    WebAddAjaxInput      ("Year next epoch starts"  , 4, "ajax-leap-year"    , "set-clock-leap-year"   );
+    WebAddAjaxInput      ("Month next epoch starts" , 4, "ajax-leap-month"   , "set-clock-leap-month"  );
+    WebAddAjaxInput      ("Current era offset"      , 4, "ajax-leap-count"   , "set-clock-leap-count"  );
+
+    HttpAddText("<div><button type='button' onclick='displayLeap()'>Display leap</button></div>\r\n");
+    
+    HttpAddText("<div>The leap seconds list is available <a href='https://www.ietf.org/timezones/data/leap-seconds.list' target='_blank'>here</a></div>\r\n");
+        
+    WebAddH2("Governer");
+    WebAddAjaxInput      ("Ppb"                     , 5, "ajax-ppb"          , "ppb"           );
+    WebAddAjaxInput      ("Ppb divisor"             , 5, "ajax-ppb-divisor"  , "ppbdivisor"    );
+    WebAddAjaxInput      ("Ppb max change"          , 5, "ajax-ppb-max-chg"  , "ppbmaxchange"  );
+    WebAddAjaxInput      ("Ppb synced limit"        , 5, "ajax-ppb-syn-lim"  , "syncedlimitppb");
+    WebAddAjaxInput      ("Ppb synced hysteresis"   , 5, "ajax-ppb-syn-hys"  , "syncedhysppb"  );
+    WebAddAjaxInput      ("Offset divisor"          , 5, "ajax-off-divisor"  , "slewdivisor"   );
+    WebAddAjaxInput      ("Offset max (ms)"         , 5, "ajax-off-max"      , "slewmax"       );
+    WebAddAjaxInput      ("Offset synced limit (ms)", 5, "ajax-off-syn-lim"  , "syncedlimitns" );
+    WebAddAjaxInput      ("Offset synced hys (ms)"  , 5, "ajax-off-syn-hys"  , "syncedhysns"   );
+    WebAddAjaxInput      ("Offset reset limit (s)"  , 5, "ajax-off-rst-lim"  , "maxoffsetsecs" );
+    WebAddAjaxInputToggle("Trace"                      , "ajax-gov-trace"    , "clockgovtrace" );
+
+    WebAddH2("NTP");
+    WebAddAjaxInput      ("Server url"              , 5, "ajax-ntp-server"   , "ntpserver"     );
+    WebAddAjaxInput      ("Initial interval (s)"    , 5, "ajax-ntp-initial"  , "clockinitial"  );
+    WebAddAjaxInput      ("Normal interval (m)"     , 5, "ajax-ntp-normal"   , "clocknormal"   );
+    WebAddAjaxInput      ("Retry interval (s)"      , 5, "ajax-ntp-retry"    , "clockretry"    );
+    WebAddAjaxInput      ("Offset (ms)"             , 5, "ajax-ntp-offset"   , "clockoffset"   );
+    WebAddAjaxInput      ("Max delay (ms)"          , 5, "ajax-ntp-max-delay", "clockmaxdelay" );
+
+    WebAddH2("Scan times");
+    WebAddAjaxLabelled   ("Program cycles avg", "ajax-scan-avg");
+    WebAddAjaxLabelled   ("Program cycles max", "ajax-scan-max");
+    WebAddAjaxLabelled   ("Program cycles min", "ajax-scan-min");
+
+    WebAddEnd();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-script.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,18 @@
+#include "http.h"
+
+//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
+//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
+
+const char* WebClockScriptDate = __DATE__;
+const char* WebClockScriptTime = __TIME__;
+
+static const char* script =
+#include "web-clock-class.inc"
+#include "../core/web-ajax-class.inc"
+#include "web-clock-script.inc"
+;
+void WebClockScript()
+{
+    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebClockScriptDate, WebClockScriptTime);
+    HttpAddText(script);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-script.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,172 @@
+"//Clock script\n"
+"'use strict';\n"
+"\n"
+"let pseudo          = new Clock();\n"
+"let rtc             = new Clock();\n"
+"\n"
+"let pseudoDisplay   = false;\n"
+"let pseudoStartMs   = 0;\n"
+"\n"
+"let diffMs          = 0;\n"
+"let rtcIsSet        = false;\n"
+"let clockIsSet      = false;\n"
+"let sourceIsOk      = false;\n"
+"let rateIsLocked    = false;\n"
+"let timeIsLocked    = false;\n"
+"\n"
+"let ppb             = 0;\n"
+"let ppbdivisor      = 0;\n"
+"let ppbmaxchange    = 0;\n"
+"let syncedlimitppb  = 0;\n"
+"let syncedhysppb    = 0;\n"
+"let slewdivisor     = 0;\n"
+"let slewmax         = 0;\n"
+"let syncedlimitns   = 0;\n"
+"let syncedhysns     = 0;\n"
+"let maxoffsetsecs   = 0;\n"
+"let govTrace        = false;\n"
+"\n"
+"let ntpserver       = '';\n"
+"let ntpinitial      = 0;\n"
+"let ntpnormal       = 0;\n"
+"let ntpretry        = 0;\n"
+"let ntpoffset       = 0;\n"
+"let ntpmaxdelay     = 0;\n"
+"\n"
+"let scanavg         = 0;\n"
+"let scanmax         = 0;\n"
+"let scanmin         = 0;\n"
+"\n"
+"const DISPLAY_LEAP_MS = 10000;\n"
+"\n"
+"function parseLinesTime(text)\n"
+"{\n"
+"    let lines = text.split('\\n');\n"
+"    rtc.ms          = Ajax.date.getTime();\n"
+"    rtc.ms         += parseInt(lines[0], 16);\n"
+"    rtc.ms         -= Ajax.ms;\n"
+"    diffMs          = rtc.ms + Ajax.ms - Date.now();\n"
+"    rtcIsSet        = Ajax.hexToBit(lines[1], 0);\n"
+"    clockIsSet      = Ajax.hexToBit(lines[1], 1);\n"
+"    sourceIsOk      = Ajax.hexToBit(lines[1], 2);\n"
+"    rateIsLocked    = Ajax.hexToBit(lines[1], 3);\n"
+"    timeIsLocked    = Ajax.hexToBit(lines[1], 4);\n"
+"    rtc.leapEnable  = Ajax.hexToBit(lines[1], 5);\n"
+"    rtc.leapForward = Ajax.hexToBit(lines[1], 6);\n"
+"    govTrace        = Ajax.hexToBit(lines[1], 7);\n"
+"    rtc.months1970  = parseInt(lines[2], 16);\n"
+"    rtc.leaps       = parseInt(lines[3], 16);\n"
+"}\n"
+"function parseLinesGov(text)\n"
+"{\n"
+"    let lines = text.split('\\n');\n"
+"    ppb             = parseInt(lines[0], 16);\n"
+"    ppbdivisor      = parseInt(lines[1], 16);\n"
+"    ppbmaxchange    = parseInt(lines[2], 16);\n"
+"    syncedlimitppb  = parseInt(lines[3], 16);\n"
+"    syncedhysppb    = parseInt(lines[4], 16);\n"
+"    slewdivisor     = parseInt(lines[5], 16);\n"
+"    slewmax         = parseInt(lines[6], 16);\n"
+"    syncedlimitns   = parseInt(lines[7], 16);\n"
+"    syncedhysns     = parseInt(lines[8], 16);\n"
+"    maxoffsetsecs   = parseInt(lines[9], 16);\n"
+"}\n"
+"function parseLinesNtp(text)\n"
+"{\n"
+"    let lines = text.split('\\n');\n"
+"    ntpserver       =          lines[0];\n"
+"    ntpinitial      = parseInt(lines[1], 16);\n"
+"    ntpnormal       = parseInt(lines[2], 16);\n"
+"    ntpretry        = parseInt(lines[3], 16);\n"
+"    ntpoffset       = parseInt(lines[4], 16);\n"
+"    ntpmaxdelay     = parseInt(lines[5], 16);\n"
+"}\n"
+"function parseLinesScan(text)\n"
+"{\n"
+"    let lines = text.split('\\n');\n"
+"    scanavg         = parseInt(lines[0], 16);\n"
+"    scanmax         = parseInt(lines[1], 16);\n"
+"    scanmin         = parseInt(lines[2], 16);\n"
+"}\n"
+"function parse()\n"
+"{\n"
+"    let topics = Ajax.response.split('\\f');\n"
+"    parseLinesTime(topics[0]);\n"
+"    parseLinesGov (topics[1]);\n"
+"    parseLinesNtp (topics[2]);\n"
+"    parseLinesScan(topics[3]);\n"
+"}\n"
+"function display()\n"
+"{\n"
+"    let elem;\n"
+"    elem = Ajax.getElementOrNull('ajax-rtc-set'      ); if (elem) elem.setAttribute('dir', rtcIsSet     ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-clock-set'    ); if (elem) elem.setAttribute('dir', clockIsSet   ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-source-ok'    ); if (elem) elem.setAttribute('dir', sourceIsOk   ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-rate-locked'  ); if (elem) elem.setAttribute('dir', rateIsLocked ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-time-locked'  ); if (elem) elem.setAttribute('dir', timeIsLocked ? 'rtl' : 'ltr');\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-leap-enable'  ); if (elem) elem.setAttribute('dir', rtc.leapEnable   ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-leap-forward' ); if (elem) elem.setAttribute('dir', rtc.leapForward  ? 'rtl' : 'ltr');\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-leap-year'    ); if (elem) elem.value = rtc.months1970 ? rtc.leapYear  : '';\n"
+"    elem = Ajax.getElementOrNull('ajax-leap-month'   ); if (elem) elem.value = rtc.months1970 ? rtc.leapMonth : '';\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-leap-count'   ); if (elem) elem.value = rtc.leaps;\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-ppb'          ); if (elem) elem.value = ppb;\n"
+"    elem = Ajax.getElementOrNull('ajax-ppb-divisor'  ); if (elem) elem.value = ppbdivisor;\n"
+"    elem = Ajax.getElementOrNull('ajax-ppb-max-chg'  ); if (elem) elem.value = ppbmaxchange;\n"
+"    elem = Ajax.getElementOrNull('ajax-ppb-syn-lim'  ); if (elem) elem.value = syncedlimitppb;\n"
+"    elem = Ajax.getElementOrNull('ajax-ppb-syn-hys'  ); if (elem) elem.value = syncedhysppb;\n"
+"    elem = Ajax.getElementOrNull('ajax-off-divisor'  ); if (elem) elem.value = slewdivisor;\n"
+"    elem = Ajax.getElementOrNull('ajax-off-max'      ); if (elem) elem.value = slewmax;\n"
+"    elem = Ajax.getElementOrNull('ajax-off-syn-lim'  ); if (elem) elem.value = syncedlimitns / 1000000;\n"
+"    elem = Ajax.getElementOrNull('ajax-off-syn-hys'  ); if (elem) elem.value = syncedhysns   / 1000000;\n"
+"    elem = Ajax.getElementOrNull('ajax-off-rst-lim'  ); if (elem) elem.value = maxoffsetsecs;\n"
+"    elem = Ajax.getElementOrNull('ajax-gov-trace'    ); if (elem) elem.setAttribute('dir', govTrace     ? 'rtl' : 'ltr');\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-server'   ); if (elem) elem.value = ntpserver;\n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-initial'  ); if (elem) elem.value = ntpinitial;\n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-normal'   ); if (elem) elem.value = ntpnormal / 60;\n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-retry'    ); if (elem) elem.value = ntpretry;\n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-offset'   ); if (elem) elem.value = ntpoffset;\n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-max-delay'); if (elem) elem.value = ntpmaxdelay;\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-scan-avg'     ); if (elem) elem.textContent = scanavg;\n"
+"    elem = Ajax.getElementOrNull('ajax-scan-max'     ); if (elem) elem.textContent = scanmax;\n"
+"    elem = Ajax.getElementOrNull('ajax-scan-min'     ); if (elem) elem.textContent = scanmin;\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-date-diff'    ); if (elem) elem.textContent = diffMs;\n"
+"}\n"
+"\n"
+"function handleTick() //This typically called every 100ms\n"
+"{\n"
+"    if (pseudoDisplay)\n"
+"    {\n"
+"        pseudo.adjustLeap (Ajax.ms);\n"
+"        pseudo.displayTime(Ajax.ms);\n"
+"        if (Ajax.ms >= pseudoStartMs + DISPLAY_LEAP_MS + 500) pseudoDisplay = false;\n"
+"    }\n"
+"    else\n"
+"    {\n"
+"        rtc.adjustLeap (Ajax.ms);\n"
+"        rtc.displayTime(Ajax.ms);\n"
+"    }\n"
+"}\n"
+"\n"
+"function displayLeap() //Called by display leap button in HTML\n"
+"{\n"
+"   pseudoDisplay = true;\n"
+"   pseudoStartMs = Ajax.ms;\n"
+"   \n"
+"   pseudo.leapEnable  = true;\n"
+"   pseudo.leapForward = rtc.leapForward;\n"
+"   pseudo.leaps       = rtc.leaps;\n"
+"   pseudo.leapMonth   = rtc.leapMonth;\n"
+"   pseudo.leapYear    = rtc.leapYear;\n"
+"   pseudo.ms          = Date.UTC(rtc.leapYear, rtc.leapMonth - 1, 1) - DISPLAY_LEAP_MS / 2 - Ajax.ms;\n"
+"}\n"
+"Ajax.server     = '/clock-ajax';\n"
+"Ajax.onResponse = function() { parse(); display(); };\n"
+"Ajax.onTick     = handleTick;\n"
+"Ajax.init();"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-clock-script.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,172 @@
+//Clock script
+'use strict';
+
+let pseudo          = new Clock();
+let rtc             = new Clock();
+
+let pseudoDisplay   = false;
+let pseudoStartMs   = 0;
+
+let diffMs          = 0;
+let rtcIsSet        = false;
+let clockIsSet      = false;
+let sourceIsOk      = false;
+let rateIsLocked    = false;
+let timeIsLocked    = false;
+
+let ppb             = 0;
+let ppbdivisor      = 0;
+let ppbmaxchange    = 0;
+let syncedlimitppb  = 0;
+let syncedhysppb    = 0;
+let slewdivisor     = 0;
+let slewmax         = 0;
+let syncedlimitns   = 0;
+let syncedhysns     = 0;
+let maxoffsetsecs   = 0;
+let govTrace        = false;
+
+let ntpserver       = '';
+let ntpinitial      = 0;
+let ntpnormal       = 0;
+let ntpretry        = 0;
+let ntpoffset       = 0;
+let ntpmaxdelay     = 0;
+
+let scanavg         = 0;
+let scanmax         = 0;
+let scanmin         = 0;
+
+const DISPLAY_LEAP_MS = 10000;
+
+function parseLinesTime(text)
+{
+    let lines = text.split('\n');
+    rtc.ms          = Ajax.date.getTime();
+    rtc.ms         += parseInt(lines[0], 16);
+    rtc.ms         -= Ajax.ms;
+    diffMs          = rtc.ms + Ajax.ms - Date.now();
+    rtcIsSet        = Ajax.hexToBit(lines[1], 0);
+    clockIsSet      = Ajax.hexToBit(lines[1], 1);
+    sourceIsOk      = Ajax.hexToBit(lines[1], 2);
+    rateIsLocked    = Ajax.hexToBit(lines[1], 3);
+    timeIsLocked    = Ajax.hexToBit(lines[1], 4);
+    rtc.leapEnable  = Ajax.hexToBit(lines[1], 5);
+    rtc.leapForward = Ajax.hexToBit(lines[1], 6);
+    govTrace        = Ajax.hexToBit(lines[1], 7);
+    rtc.months1970  = parseInt(lines[2], 16);
+    rtc.leaps       = parseInt(lines[3], 16);
+}
+function parseLinesGov(text)
+{
+    let lines = text.split('\n');
+    ppb             = parseInt(lines[0], 16);
+    ppbdivisor      = parseInt(lines[1], 16);
+    ppbmaxchange    = parseInt(lines[2], 16);
+    syncedlimitppb  = parseInt(lines[3], 16);
+    syncedhysppb    = parseInt(lines[4], 16);
+    slewdivisor     = parseInt(lines[5], 16);
+    slewmax         = parseInt(lines[6], 16);
+    syncedlimitns   = parseInt(lines[7], 16);
+    syncedhysns     = parseInt(lines[8], 16);
+    maxoffsetsecs   = parseInt(lines[9], 16);
+}
+function parseLinesNtp(text)
+{
+    let lines = text.split('\n');
+    ntpserver       =          lines[0];
+    ntpinitial      = parseInt(lines[1], 16);
+    ntpnormal       = parseInt(lines[2], 16);
+    ntpretry        = parseInt(lines[3], 16);
+    ntpoffset       = parseInt(lines[4], 16);
+    ntpmaxdelay     = parseInt(lines[5], 16);
+}
+function parseLinesScan(text)
+{
+    let lines = text.split('\n');
+    scanavg         = parseInt(lines[0], 16);
+    scanmax         = parseInt(lines[1], 16);
+    scanmin         = parseInt(lines[2], 16);
+}
+function parse()
+{
+    let topics = Ajax.response.split('\f');
+    parseLinesTime(topics[0]);
+    parseLinesGov (topics[1]);
+    parseLinesNtp (topics[2]);
+    parseLinesScan(topics[3]);
+}
+function display()
+{
+    let elem;
+    elem = Ajax.getElementOrNull('ajax-rtc-set'      ); if (elem) elem.setAttribute('dir', rtcIsSet     ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-clock-set'    ); if (elem) elem.setAttribute('dir', clockIsSet   ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-source-ok'    ); if (elem) elem.setAttribute('dir', sourceIsOk   ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-rate-locked'  ); if (elem) elem.setAttribute('dir', rateIsLocked ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-time-locked'  ); if (elem) elem.setAttribute('dir', timeIsLocked ? 'rtl' : 'ltr');
+    
+    elem = Ajax.getElementOrNull('ajax-leap-enable'  ); if (elem) elem.setAttribute('dir', rtc.leapEnable   ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-leap-forward' ); if (elem) elem.setAttribute('dir', rtc.leapForward  ? 'rtl' : 'ltr');
+    
+    elem = Ajax.getElementOrNull('ajax-leap-year'    ); if (elem) elem.value = rtc.months1970 ? rtc.leapYear  : '';
+    elem = Ajax.getElementOrNull('ajax-leap-month'   ); if (elem) elem.value = rtc.months1970 ? rtc.leapMonth : '';
+    
+    elem = Ajax.getElementOrNull('ajax-leap-count'   ); if (elem) elem.value = rtc.leaps;
+    
+    elem = Ajax.getElementOrNull('ajax-ppb'          ); if (elem) elem.value = ppb;
+    elem = Ajax.getElementOrNull('ajax-ppb-divisor'  ); if (elem) elem.value = ppbdivisor;
+    elem = Ajax.getElementOrNull('ajax-ppb-max-chg'  ); if (elem) elem.value = ppbmaxchange;
+    elem = Ajax.getElementOrNull('ajax-ppb-syn-lim'  ); if (elem) elem.value = syncedlimitppb;
+    elem = Ajax.getElementOrNull('ajax-ppb-syn-hys'  ); if (elem) elem.value = syncedhysppb;
+    elem = Ajax.getElementOrNull('ajax-off-divisor'  ); if (elem) elem.value = slewdivisor;
+    elem = Ajax.getElementOrNull('ajax-off-max'      ); if (elem) elem.value = slewmax;
+    elem = Ajax.getElementOrNull('ajax-off-syn-lim'  ); if (elem) elem.value = syncedlimitns / 1000000;
+    elem = Ajax.getElementOrNull('ajax-off-syn-hys'  ); if (elem) elem.value = syncedhysns   / 1000000;
+    elem = Ajax.getElementOrNull('ajax-off-rst-lim'  ); if (elem) elem.value = maxoffsetsecs;
+    elem = Ajax.getElementOrNull('ajax-gov-trace'    ); if (elem) elem.setAttribute('dir', govTrace     ? 'rtl' : 'ltr');
+    
+    elem = Ajax.getElementOrNull('ajax-ntp-server'   ); if (elem) elem.value = ntpserver;
+    elem = Ajax.getElementOrNull('ajax-ntp-initial'  ); if (elem) elem.value = ntpinitial;
+    elem = Ajax.getElementOrNull('ajax-ntp-normal'   ); if (elem) elem.value = ntpnormal / 60;
+    elem = Ajax.getElementOrNull('ajax-ntp-retry'    ); if (elem) elem.value = ntpretry;
+    elem = Ajax.getElementOrNull('ajax-ntp-offset'   ); if (elem) elem.value = ntpoffset;
+    elem = Ajax.getElementOrNull('ajax-ntp-max-delay'); if (elem) elem.value = ntpmaxdelay;
+    
+    elem = Ajax.getElementOrNull('ajax-scan-avg'     ); if (elem) elem.textContent = scanavg;
+    elem = Ajax.getElementOrNull('ajax-scan-max'     ); if (elem) elem.textContent = scanmax;
+    elem = Ajax.getElementOrNull('ajax-scan-min'     ); if (elem) elem.textContent = scanmin;
+    
+    elem = Ajax.getElementOrNull('ajax-date-diff'    ); if (elem) elem.textContent = diffMs;
+}
+
+function handleTick() //This typically called every 100ms
+{
+    if (pseudoDisplay)
+    {
+        pseudo.adjustLeap (Ajax.ms);
+        pseudo.displayTime(Ajax.ms);
+        if (Ajax.ms >= pseudoStartMs + DISPLAY_LEAP_MS + 500) pseudoDisplay = false;
+    }
+    else
+    {
+        rtc.adjustLeap (Ajax.ms);
+        rtc.displayTime(Ajax.ms);
+    }
+}
+
+function displayLeap() //Called by display leap button in HTML
+{
+   pseudoDisplay = true;
+   pseudoStartMs = Ajax.ms;
+   
+   pseudo.leapEnable  = true;
+   pseudo.leapForward = rtc.leapForward;
+   pseudo.leaps       = rtc.leaps;
+   pseudo.leapMonth   = rtc.leapMonth;
+   pseudo.leapYear    = rtc.leapYear;
+   pseudo.ms          = Date.UTC(rtc.leapYear, rtc.leapMonth - 1, 1) - DISPLAY_LEAP_MS / 2 - Ajax.ms;
+}
+Ajax.server     = '/clock-ajax';
+Ajax.onResponse = function() { parse(); display(); };
+Ajax.onTick     = handleTick;
+Ajax.init();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/clock/web-web-query.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,62 @@
+#include "http.h"
+#include "clkgov.h"
+#include "clkutc.h"
+#include "led.h"
+#include "settings.h"
+
+void WebClockQuery(char* pQuery)
+{
+    while (pQuery)
+    {
+        char* pName;
+        char* pValue;
+        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
+        int value = HttpQueryValueAsInt(pValue);
+        
+        if (HttpSameStr(pName, "chg-clock-leap-enable" )) ClkUtcTglNextLeapEnable ();
+        if (HttpSameStr(pName, "chg-clock-leap-forward")) ClkUtcTglNextLeapForward();
+        
+        int months1970 = ClkUtcGetNextEpochMonth1970();
+        int months     = months1970 % 12;
+        int years      = months1970 / 12;
+        
+        if (HttpSameStr(pName, "set-clock-leap-year"    ))
+        {
+            years = value - 1970;
+            if (years < 0) years = 0;
+            ClkUtcSetNextEpochMonth1970(years * 12 + months);     
+        }
+        if (HttpSameStr(pName, "set-clock-leap-month"   ))
+        {
+            months = value - 1;
+            if (months < 0) months = 0;
+            ClkUtcSetNextEpochMonth1970(years * 12 + months);     
+        }
+        if (HttpSameStr(pName, "set-clock-leap-count"   ))
+        {
+            uint16_t leaps = value;
+            ClkUtcSetEpochOffset(leaps);     
+        }
+        
+        if (HttpSameStr(pName, "ppb"           )) ClkGovSetPpb               (value           );
+        if (HttpSameStr(pName, "slewdivisor"   )) SetClockSlewDivisor        (value           );
+        if (HttpSameStr(pName, "slewmax"       )) SetClockSlewMaxMs          (value           );
+        if (HttpSameStr(pName, "ppbdivisor"    )) SetClockPpbDivisor         (value           );
+        if (HttpSameStr(pName, "ppbmaxchange"  )) SetClockPpbChangeMax       (value           );
+        if (HttpSameStr(pName, "syncedlimitns" )) SetClockSyncedLimitNs      (value * 1000000 );
+        if (HttpSameStr(pName, "syncedhysns"   )) SetClockSyncedHysterisNs   (value * 1000000 );
+        if (HttpSameStr(pName, "syncedlimitppb")) SetClockSyncedLimitPpb     (value           );
+        if (HttpSameStr(pName, "syncedhysppb"  )) SetClockSyncedHysterisPpb  (value           );
+        if (HttpSameStr(pName, "maxoffsetsecs" )) SetClockMaxOffsetSecs      (value           );
+        if (HttpSameStr(pName, "clockgovtrace" )) ChgTraceSync();
+        
+        if (HttpSameStr(pName, "ntpserver"     )) SetNtpClientServerName     (pValue          );
+        if (HttpSameStr(pName, "clockinitial"  )) SetNtpClientInitialInterval(value           );
+        if (HttpSameStr(pName, "clocknormal"   )) SetNtpClientNormalInterval (value *      60 );
+        if (HttpSameStr(pName, "clockretry"    )) SetNtpClientRetryInterval  (value           );
+        if (HttpSameStr(pName, "clockoffset"   )) SetNtpClientOffsetMs       (value           );
+        if (HttpSameStr(pName, "clockmaxdelay" )) SetNtpClientMaxDelayMs     (value           );
+
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/css/web-base-css.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,13 @@
+#include "http.h"
+
+static const char* cssBase =
+#include "web-base-css.inc"
+;
+const char* WebBaseCssDate = __DATE__;
+const char* WebBaseCssTime = __TIME__;
+
+void WebBaseCss()
+{
+    HttpOk("text/css; charset=UTF-8", "max-age=3600", WebBaseCssDate, WebBaseCssTime);
+    HttpAddText(cssBase);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/css/web-base-css.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,28 @@
+"                            * { font-size:6vw; box-sizing: border-box; }\r\n"
+"@media (min-width: 600px) { * { font-size:36px; } }\r\n"
+".line                     { display:flex; justify-content:space-between; align-items:center; width:15em; }\r\n"
+"\r\n"
+"*                                { font-family:Segoe UI, Calibri, Helvetica, Arial, sans-serif; }\r\n"
+"code, textarea, input[type=text] { font-family:Consolas, Lucida Console, Droid Sans Mono, Courier New, Courier, monospace; white-space:pre; }\r\n"
+"body                             { margin-left:0.1em; line-height:1.1em; padding:0; border:0; }\r\n"
+"h1                               { color:darkblue; font-size:120%; font-weight:normal; margin-bottom:0.3em; }\r\n"
+"h2                               { color:darkblue; font-size:110%; font-weight:normal; margin-bottom:0.3em; }\r\n"
+"a                                { text-decoration:none; }\r\n"
+"div                              { line-height:1.2em; }\r\n"
+"form                             { line-height:1.2em; margin:0; }\r\n"
+"button, input[type=submit]       { border-radius:0.2em; border:0; box-shadow:none; margin-top:0.3em; }\r\n"
+"button:hover             { color:red; }\r\n"
+"input[type=submit]:hover { color:red; }\r\n"
+"input[type=text]         { border:thin none gray; margin:0; padding:0; color:darkblue; text-align:right; }\r\n"
+"\r\n"
+".toggle                  { display:inline-block; position:relative; cursor:pointer; width: 1.2em;  height: 0.6em; }\r\n"
+".slot                    { background-color:lightsteelblue; display:block; position:absolute; top:0.1em; left:0.1em; bottom:0.1em; right:0.1em; border-radius:0.6em; transition:background 0.4s; }\r\n"
+".knob                    { background-color:steelblue;      display:block; position:absolute; top:0.0em; left:0.0em; bottom:0.0em; width:0.6em; border-radius:0.6em; transition:background 0.4s, margin 0.4s; }\r\n"
+".toggle[dir=rtl] > .slot { background-color:lightsteelblue; }\r\n"
+".toggle[dir=rtl] > .knob { background-color:royalblue;      margin-left:0.6em}\r\n"
+".led                     { background-color:lightgray; display:inline-block; width:0.6em; height:0.6em; border-radius:50%; }\r\n"
+".led[dir=rtl]            { background-color:royalblue; }\r\n"
+"\r\n"
+".hamburger { display:inline-block; width:1em}\r\n"
+".bar       { height:0.15em; background-color:#111; border-radius:0.15em; }\r\n"
+".space     { height:0.2em; }\r\n"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/css/web-nav-css.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,13 @@
+#include "http.h"
+
+static const char* cssNav =
+#include "web-nav-css.inc"
+;
+const char* WebNavCssDate = __DATE__;
+const char* WebNavCssTime = __TIME__;
+
+void WebNavCss()
+{
+    HttpOk("text/css; charset=UTF-8", "max-age=3600", WebNavCssDate, WebNavCssTime);
+    HttpAddText(cssNav);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/css/web-nav-css.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,20 @@
+//Override the base font size 1vw = 1% width so for a width of 600px 4vw equates to 24px
+"                            * { font-size:4vw;  } \r\n"
+"@media (min-width: 600px) { * { font-size:24px; } }\r\n"
+
+//Override the left margin to fit the nav bar and make the line spacing smaller
+"body              { margin-left:6em; }\r\n"
+
+//Overide the line container to match
+".line             { width:17em; }\r\n"
+
+//Specify the tab access
+".tab-shortcut       { position:absolute; top:-1000em;}\r\n"
+".tab-shortcut:focus { position:fixed; top:0; left:0; z-index:999; padding:0.5em; background-color:darkblue; color:white; }\r\n"
+
+//Specify the nav bar itself
+"nav ul            { list-style-type:none; margin:0; padding:0; overflow:hidden; position:fixed; top:0; left:0; width:5em; font-size:1.2em; }\r\n"
+"nav ul li         { background:lightskyblue; padding:0; border-style:none; margin:0.2em; }\r\n"
+"nav ul li.this    { background:coral; }\r\n"
+"nav ul li a       { display:block; color:black; border:0;margin:0; padding:0.5em; }\r\n"
+"nav ul li a:hover { color:darkred; }\r\n"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/fault/web-fault-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,34 @@
+#include <stdio.h>
+
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-add.h"
+#include "fault.h"
+
+void WebFaultHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Fault", "settings.css", NULL);
+    WebAddNav(FAULT_PAGE);
+    WebAddH1("Fault");
+    
+    WebAddH2("Last fault");
+    int faultType = FaultTypeGet();
+    char text[20];
+    FaultTypeToString(faultType, sizeof(text), text);
+    WebAddLabelledText("Fault type", text);
+    if (faultType)
+    {
+        FaultZoneToString(FaultZoneGet(), sizeof(text), text);
+        WebAddLabelledText("Fault zone" , text);
+        WebAddLabelledInt ("After point", FaultPointGet());
+        WebAddInputButton ("Clear fault", "Clear", "/fault", "faultclear");
+    }
+    else
+    {
+        WebAddInputButton("Test fault"   ,  "Test",  "/fault", "faulttest");
+    }
+    
+    WebAddEnd();
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/fault/web-fault-query.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,21 @@
+#include "http.h"
+#include "fault.h"
+
+void WebFaultQuery(char* pQuery)
+{
+    while (pQuery)
+    {
+        char* pName;
+        char* pValue;
+        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
+        
+        if (HttpSameStr(pName, "faultclear")) FaultReset();
+        
+        if (HttpSameStr(pName, "faulttest" ))
+        {
+            FaultPoint = 999;
+            *(volatile int *)0 = 0; //Dereferencing address 0 will hard fault the processor
+        }
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/favicon/web-favicon.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,16 @@
+#include "http.h"
+
+//Use http://tomeko.net/online_tools/file_to_hex.php to convert a favicon.ico into hex readable as an array
+static const char bytes[] = {
+#include "favicon/web-favicon.inc"
+};
+
+const char* WebFaviconDate = __DATE__;
+const char* WebFaviconTime = __TIME__;
+const int   WebFaviconSize = sizeof(bytes);
+
+void WebFavicon()
+{
+    HttpOk("image/x-icon", "max-age=3600", WebFaviconDate, WebFaviconTime);
+    HttpAddData(bytes, WebFaviconSize);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-ajax.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,40 @@
+#include   <stdio.h>
+
+#include "http.h"
+#include "firmware.h"
+#include "web-firmware.h"
+
+void WebFirmwareAjax()
+{
+    //Header
+    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
+    
+    //Upload status
+    if (FirmwareSentLength == FirmwareFileLength)
+    {
+        HttpAddText("Length ok\r\n");
+    }
+    else
+    {
+        HttpAddText("Length error\r\n");
+        HttpAddF   ("  sent %6d\r\n", FirmwareSentLength);
+        HttpAddF   ("  rcvd %6d\r\n", FirmwareRcvdLength);
+        HttpAddF   ("  file %6d\r\n", FirmwareFileLength);
+    }
+    
+    //Save status
+    if (FirmwareFailed)
+    {
+        HttpAddText("Save failed - see log");
+    }
+    else
+    {
+        HttpAddText("Saved  ok");
+    }
+    
+    //Delimiter
+    HttpAddChar('\f');
+    
+    //New directory list
+    WebFirmwareListSemihostFiles();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,34 @@
+#include <stdio.h>
+
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-add.h"
+#include "web-firmware.h"
+
+void WebFirmwareHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Firmware", "settings.css", "firmware.js");
+    WebAddNav(FIRMWARE_PAGE);
+    WebAddH1("Firmware");
+    
+    WebAddH2("Existing files on the device");
+
+    HttpAddText("<code id='list'>");
+    WebFirmwareListSemihostFiles();
+    HttpAddText("</code>\r\n");
+    
+    WebAddH2("Choose a local file");
+    HttpAddText("<div><input type='file' id='fileInput'/></div>\r\n");
+    
+    WebAddH2("Upload the local file to the device");
+    HttpAddText("<div><button onclick='startUpload();'>Upload</button></div>\r\n");
+    HttpAddText("<code id='uploadresult'></code>\r\n");
+    
+    WebAddH2("Restart the device");
+    HttpAddText("<div><button onclick='restart();'>Restart</button></div>\r\n");
+    HttpAddText("<code id='restartresult'></code>\r\n");
+
+
+    WebAddEnd();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-post.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,15 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "firmware.h"
+
+void WebFirmwarePost (int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete)
+{
+    if (!positionInRequestStream)
+    {
+        FirmwareStart(contentLength);
+    }
+    char* pDataStart  = pRequestStream + contentStart;
+    int    dataLength =           size - contentStart;
+    *pComplete = FirmwareAdd(pDataStart, dataLength);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-query.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,15 @@
+#include "http.h"
+#include "semihost.h"
+
+void WebFirmwareQuery(char* pQuery)
+{
+    while (pQuery)
+    {
+        char* pName;
+        char* pValue;
+        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
+        int value = HttpQueryValueAsInt(pValue);    
+        
+        if (HttpSameStr(pName, "restart" )) SemihostReset();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-script.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,16 @@
+#include "http.h"
+
+//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
+//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
+
+const char* WebFirmwareScriptDate = __DATE__;
+const char* WebFirmwareScriptTime = __TIME__;
+
+static const char* script =
+#include "web-firmware-script.inc"
+;
+void WebFirmwareScript()
+{
+    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebFirmwareScriptDate, WebFirmwareScriptTime);
+    HttpAddText(script);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-script.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,88 @@
+"'use strict';\n"
+"\n"
+"var file;\n"
+"var xhr;\n"
+"\n"
+"function logUpload(text)\n"
+"{\n"
+"    document.getElementById('uploadresult').textContent = text;\n"
+"}\n"
+"function xhrUploadResponse()\n"
+"{\n"
+"    var topics = xhr.responseText.split('\\f');\n"
+"    logUpload(topics[0]);\n"
+"    if (topics.length > 1) document.getElementById('list').textContent = topics[1];\n"
+"}\n"
+"function xhrUploadOnLoad()\n"
+"{\n"
+"    if (xhr.status == 200) xhrUploadResponse();\n"
+"    else                   logUpload('Upload failed');\n"
+"}\n"
+"function xhrUploadOnError()\n"
+"{\n"
+"    logUpload('Upload error');\n"
+"}\n"
+"function xhrUploadStart()\n"
+"{\n"
+"    logUpload('Uploading...');\n"
+"    \n"
+"    xhr = new XMLHttpRequest();\n"
+"\n"
+"    xhr.onload  = xhrUploadOnLoad;\n"
+"    xhr.onerror = xhrUploadOnError;\n"
+"\n"
+"    xhr.open('POST', '/firmware-ajax'); //Defaults to async=true\n"
+"    xhr.send(file);\n"
+"}\n"
+"\n"
+"function startUpload()\n"
+"{\n"
+"    var fileInput = document.getElementById('fileInput');\n"
+"\n"
+"    if (fileInput.files.length == 0)\n"
+"    {\n"
+"        logUpload('Please choose a file');\n"
+"        return;\n"
+"    }\n"
+"\n"
+"    if (fileInput.files.length > 1)\n"
+"    {\n"
+"        logUpload('Please choose just one file');\n"
+"        return;\n"
+"    }\n"
+"    \n"
+"    file = fileInput.files[0];\n"
+"    \n"
+"    xhrUploadStart();\n"
+"}\n"
+"function logRestart(text)\n"
+"{\n"
+"    document.getElementById('restartresult').textContent = text;\n"
+"}\n"
+"function redirect()\n"
+"{\n"
+"    location.href = '/firmware';\n"
+"}\n"
+"function xhrRestartOnLoad()\n"
+"{\n"
+"    if (xhr.status == 200) logRestart('Restart should never have returned');\n"
+"    else                   logRestart('Restart failed');\n"
+"}\n"
+"function xhrRestartStart()\n"
+"{\n"
+"    logRestart('Restarting...');\n"
+"    \n"
+"    xhr = new XMLHttpRequest();\n"
+"\n"
+"    xhr.onload  = xhrRestartOnLoad;\n"
+"\n"
+"    xhr.open('GET', '/firmware-ajax?restart='); //Defaults to async=true\n"
+"    xhr.send();\n"
+"    \n"
+"    setTimeout(redirect, 2000);\n"
+"}\n"
+"function restart()\n"
+"{\n"
+"    xhrRestartStart();\n"
+"}\n"
+""
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware-script.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,87 @@
+'use strict';
+
+var file;
+var xhr;
+
+function logUpload(text)
+{
+    document.getElementById('uploadresult').textContent = text;
+}
+function xhrUploadResponse()
+{
+    var topics = xhr.responseText.split('\f');
+    logUpload(topics[0]);
+    if (topics.length > 1) document.getElementById('list').textContent = topics[1];
+}
+function xhrUploadOnLoad()
+{
+    if (xhr.status == 200) xhrUploadResponse();
+    else                   logUpload('Upload failed');
+}
+function xhrUploadOnError()
+{
+    logUpload('Upload error');
+}
+function xhrUploadStart()
+{
+    logUpload('Uploading...');
+    
+    xhr = new XMLHttpRequest();
+
+    xhr.onload  = xhrUploadOnLoad;
+    xhr.onerror = xhrUploadOnError;
+
+    xhr.open('POST', '/firmware-ajax'); //Defaults to async=true
+    xhr.send(file);
+}
+
+function startUpload()
+{
+    var fileInput = document.getElementById('fileInput');
+
+    if (fileInput.files.length == 0)
+    {
+        logUpload('Please choose a file');
+        return;
+    }
+
+    if (fileInput.files.length > 1)
+    {
+        logUpload('Please choose just one file');
+        return;
+    }
+    
+    file = fileInput.files[0];
+    
+    xhrUploadStart();
+}
+function logRestart(text)
+{
+    document.getElementById('restartresult').textContent = text;
+}
+function redirect()
+{
+    location.href = '/firmware';
+}
+function xhrRestartOnLoad()
+{
+    if (xhr.status == 200) logRestart('Restart should never have returned');
+    else                   logRestart('Restart failed');
+}
+function xhrRestartStart()
+{
+    logRestart('Restarting...');
+    
+    xhr = new XMLHttpRequest();
+
+    xhr.onload  = xhrRestartOnLoad;
+
+    xhr.open('GET', '/firmware-ajax?restart='); //Defaults to async=true
+    xhr.send();
+    
+    setTimeout(redirect, 2000);
+}
+function restart()
+{
+    xhrRestartStart();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,16 @@
+#include "semihost.h"
+#include "http.h"
+
+void WebFirmwareListSemihostFiles()
+{
+    XFINFO info;
+    info.fileID = 0;
+    while (SemihostXffind ("*.*", &info) == 0)
+    {
+        HttpAddF("%13s %7d bytes\r\n", info.name, info.size);
+    }
+    if (info.fileID == 0)
+    {
+        HttpAddText("No files\r\n");
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/firmware/web-firmware.h	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,1 @@
+extern void WebFirmwareListSemihostFiles(void);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/log/web-log-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,22 @@
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-add.h"
+#include "log.h"
+
+void WebLogHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Log", "settings.css", NULL);
+    WebAddNav(LOG_PAGE);
+    WebAddH1("Log");
+    
+                 WebAddInputButton("Existing content",      "Clear",    "/log", "clearlog"    );
+    if (LogUart) WebAddInputButton("Output to uart is on",  "Turn off", "/log", "chg-log-uart");
+    else         WebAddInputButton("Output to uart is off", "Turn on",  "/log", "chg-log-uart");
+    
+    HttpAddText("<code>");
+    HttpAddStream(LogEnumerateStart, LogEnumerate);
+    HttpAddText("</code>");
+    
+    WebAddEnd();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/log/web-log-query.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,15 @@
+#include "http.h"
+#include "log.h"
+#include "settings.h"
+
+void WebLogQuery(char* pQuery)
+{
+    while (pQuery)
+    {
+        char* pName;
+        char* pValue;
+        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);                    
+        if (HttpSameStr(pName, "clearlog"    )) LogClear();
+        if (HttpSameStr(pName, "chg-log-uart")) ChgLogUart();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login-css.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,12 @@
+"                            * { font-size:4vw; box-sizing: border-box; }\r\n"
+"@media (min-width: 600px) { * { font-size:24px; } }\r\n"
+"\r\n"
+"*                        { font-family:'Segoe UI', Calibri, Arial, sans-serif; }\r\n"
+"*:focus                  { outline: 0; }\r\n"
+"body                     { margin-left:0.1em; line-height:1.1em; padding:0; border:0; }\r\n"
+"h1                       { color:darkblue; font-size:120%; font-weight:normal; margin-bottom:0.3em; }\r\n"
+"h2                       { color:darkblue; font-size:110%; font-weight:normal; margin-bottom:0.3em; }\r\n"
+"div                      { line-height:1.2em; }\r\n"
+"form                     { line-height:1.2em; margin:0; }\r\n"
+"input[type=text]         { border:thin solid gray; border-radius:0.2em; margin:0; padding:0.1em; width:9em; text-align:left}\r\n"
+"input[type=submit]       { display:none; }\r\n"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,41 @@
+#include "http.h"
+#include "web-add.h"
+#include "web-login.h"
+#include "web-pages-base.h"
+
+void WebLoginHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Login", NULL, NULL);
+    HttpAddText(
+"<style>"
+#include "web-login-css.inc"
+"</style>"
+    );
+    WebAddH1("Login");
+    if (WebLoginPasswordIsSet())
+    {
+        WebAddH2("Welcome - please enter the password");
+    }
+    else
+    {
+        WebAddH2("Please enter a new password following user reset");
+        HttpAddText("<p>Be careful to make it the one people expect!</p>");
+    }
+    
+    HttpAddText("<form action='/login' method='get' autocomplete='off'>\r\n");
+    HttpAddText("  <div style='width:8em; display:inline-block;'>Password</div>\r\n");
+    HttpAddF   ("  <input type='hidden' name='todo'     value='%d'>\r\n", WebLoginOriginalToDo);
+    HttpAddText("  <input type='text'   name='password' value='' autofocus>\r\n");
+    HttpAddText("  <input type='submit'                 value='' >\r\n");
+    HttpAddF   ("</form>\r\n");
+
+    WebAddEnd();
+}
+
+/*
+Device sends request for resource to server
+Server cannot validate the session cookie (or there isn't one) so sends login form with original resource number as a hidden input
+Device does a GET for /login with query containing password and original resource
+Server validates password and sends original resource with a valid session cookie.
+*/
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login-password.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,52 @@
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "http.h"
+
+#define GPREG2 (*((volatile unsigned *) 0x4002404C))
+
+static uint32_t hash;
+static bool hashIsSet = false;
+
+void  WebLoginPasswordRestore()
+{
+    hash = GPREG2;
+    hashIsSet = true;
+}
+
+uint32_t hasher(char *p) //Jenkins 'one at a time' hash
+{
+    uint32_t h = 0;
+    
+    while (*p)
+    {
+        h += *p;
+        h += (h << 10);
+        h ^= (h >> 6);
+        p++;
+    }
+    h += (h << 3);
+    h ^= (h >> 11);
+    h += (h << 15);
+        
+    return h;
+}
+bool WebLoginPasswordIsSet()
+{
+    return hashIsSet;
+}
+void WebLoginPasswordSet(char* password)
+{
+    if (!password)  return;
+    if (!*password) return;
+    hash = hasher(password);
+    GPREG2 = hash;
+    hashIsSet = true;
+}
+bool WebLoginPasswordMatches(char* password)
+{
+    if (!hashIsSet) return false;
+    if (!password)  return false;
+    if (!*password) return false;
+    return hash == hasher(password);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login-query.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,35 @@
+
+#include "http.h"
+#include "web-pages-base.h"
+#include "web-login.h"
+
+bool WebLoginQueryPasswordOk = false;
+
+void WebLoginQuery(char* pQuery)
+{
+    WebLoginQueryPasswordOk = false;
+    
+    while (pQuery)
+    {
+        char* pName;
+        char* pValue;
+        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
+        
+        int value = HttpQueryValueAsInt(pValue);
+        if (HttpSameStr(pName, "todo"     )) WebLoginOriginalToDo = value;
+        
+        HttpQueryUnencode(pValue);
+        if (HttpSameStr(pName, "password" ))
+        {
+            if (!WebLoginPasswordIsSet()) //This is if there has been a normal reset: not a fault nor a power on.
+            {
+                WebLoginPasswordSet(pValue);
+                WebLoginQueryPasswordOk = true;
+            }
+            else
+            {
+                WebLoginQueryPasswordOk = WebLoginPasswordMatches(pValue);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login-session-id.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,50 @@
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "random.h"
+
+#define SESSION_ID_BIT_LENGTH 36
+
+static char sessionId[(SESSION_ID_BIT_LENGTH + 5) / 6 + 1]; //Bit lengths not divisible by 6 require an extra space
+
+void WebLoginSessionIdNew()
+{
+    char acc = 0;
+    
+    for (int i = 0; i < SESSION_ID_BIT_LENGTH; i++)
+    {
+        int     srcByte = i / 8; 
+        int     srcBit  = i - srcByte * 8;
+        uint8_t srcMask = 1 << srcBit;
+        
+        int     dstByte = i / 6;
+        int     dstBit  = i - dstByte * 6;
+        uint8_t dstMask = 1 << dstBit;
+        
+        //Reset the accumulator to zero at start of a 6 bit word
+        if (!dstBit) acc = 0;
+        
+        //Add the bit to the accumulator
+        if (RandomBytes[srcByte] & srcMask) acc |= dstMask;
+        
+        //Convert the accumulator to base64 and store in the session Id
+        if (dstBit == 5 || i == SESSION_ID_BIT_LENGTH - 1)
+        {
+            if      (acc < 26) sessionId[dstByte] = acc -  0 + 'A';
+            else if (acc < 52) sessionId[dstByte] = acc - 26 + 'a';
+            else if (acc < 62) sessionId[dstByte] = acc - 52 + '0';
+            else if (acc < 63) sessionId[dstByte] =            '+';
+            else               sessionId[dstByte] =            '/';
+        }
+    }
+    sessionId[(SESSION_ID_BIT_LENGTH + 5) / 6] = 0;
+}
+
+bool WebLoginSessionIdIsSet()
+{
+    return sessionId[0];
+}
+char* WebLoginSessionIdGet()
+{   
+    return sessionId;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login-session-name.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,37 @@
+#include "web-site-name.h"
+
+#define SESSION_LIFE 10 * 365 * 24 * 60 * 60
+
+//Do not use ' ', ';', '=' as they are part of the cookie protocol: name1=value1; name2=value2; name3=value3 etc
+static char sessionName[9]; //Limit the name to 8 characters (plus terminating zero) to keep things quick
+
+char* WebLoginSessionNameGet()
+{
+    return sessionName;
+}
+
+void WebLoginSessionNameCreate()
+{
+    //Make the session name from the site name - eg  "Heating" and "GPS Clock" will be "heat_sid" and "gpsc_sid"
+    int i = 0;
+    int j = 0;
+    while (1)
+    {
+        if (i >= sizeof(sessionName) - 4 - 1) break; //Leave room for the "_sid" and the terminating NUL character
+        char c = WEB_SITE_NAME[j++];
+        if (!c) break;                               //Stop if run out of site name
+        if (c >= 'A' && c <= 'Z') c |= 0x20;         //Make lower case
+        if (c < 'a' || c > 'z') continue;            //Skip anything other than letters
+        sessionName[i++] = c;                        //Add the first characters of the site name for which there is room
+    }
+    for (int k = 0; k < 4; k++)                      //Add "_sid"
+    {
+        sessionName[i++] = "_sid"[k];
+    }
+    sessionName[i] = 0;                              //Add the terminating NUL character
+}
+
+int WebLoginSessionNameLife()
+{
+    return SESSION_LIFE;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,30 @@
+
+#include "web-pages-base.h"
+#include "web-login.h"
+#include "http.h"
+#include "fault.h"
+
+int WebLoginOriginalToDo = 0;
+
+bool WebLoginCookiesContainValidSessionId(char* pCookies)
+{
+    if (!WebLoginSessionIdIsSet()) return false;
+    
+    while (pCookies)
+    {
+        char* pName;
+        char* pValue;
+        pCookies = HttpCookiesSplit(pCookies, &pName, &pValue);
+        
+        if (HttpSameStr(pName, WebLoginSessionNameGet())) //HttpSameStr handles NULLs correctly
+        {
+            if (HttpSameStr(pValue, WebLoginSessionIdGet())) return true;
+        }
+    }
+    return false;
+}
+void WebLoginInit()
+{
+    if (FaultTypeGet()) WebLoginPasswordRestore();
+    WebLoginSessionNameCreate();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/login/web-login.h	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,8 @@
+#include <stdbool.h>
+
+extern void  WebLoginSessionNameCreate(void);
+
+extern void  WebLoginPasswordRestore  (void);
+extern void  WebLoginPasswordSet      (char* password);
+extern bool  WebLoginPasswordIsSet    (void);
+extern bool  WebLoginPasswordMatches  (char* password);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net-trace/web-trace-ajax.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,101 @@
+#include  <stdint.h>
+
+#include       "http.h"
+#include        "log.h"
+#include        "net.h"
+#include       "link.h"
+#include        "dns.h"
+#include    "dnsname.h"
+#include   "dnsquery.h"
+#include   "dnsreply.h"
+#include  "dnsserver.h"
+#include        "ntp.h"
+#include       "dhcp.h"
+#include         "ns.h"
+#include        "nr4.h"
+#include        "nr6.h"
+#include      "echo4.h"
+#include      "echo6.h"
+#include      "dest6.h"
+#include         "ra.h"
+#include         "rs.h"
+#include        "ar4.h"
+#include        "ar6.h"
+#include        "arp.h"
+#include        "ip4.h"
+#include        "ip6.h"
+#include        "udp.h"
+#include        "tcp.h"
+#include       "http.h"
+#include       "tftp.h"
+#include  "ntpclient.h"
+
+void WebTraceAjax()
+{
+    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
+    char nibble;
+    
+    nibble = 0; //0
+    if ( DnsSendRequestsViaIp4) nibble |= 2;
+    if ( NtpClientQuerySendRequestsViaIp4) nibble |= 4;
+    if (TftpSendRequestsViaIp4) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    HttpAddByteAsHex(NetTraceHost[0]);  //1, 2
+    HttpAddByteAsHex(NetTraceHost[1]);  //3, 4
+    
+    nibble = 0; //5
+    if (NetTraceStack   ) nibble |= 1;
+    if (NetTraceNewLine ) nibble |= 2;
+    if (NetTraceVerbose ) nibble |= 4;
+    if (LinkTrace       ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //6
+    if (DnsNameTrace    ) nibble |= 1;
+    if (DnsQueryTrace   ) nibble |= 2;
+    if (DnsReplyTrace   ) nibble |= 4;
+    if (DnsServerTrace  ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //7
+    if (NtpTrace        ) nibble |= 1;
+    if (DhcpTrace       ) nibble |= 2;
+    if (NsTraceRecvSol  ) nibble |= 4;
+    if (NsTraceRecvAdv  ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //8
+    if (NsTraceSendSol  ) nibble |= 1;
+    if (Nr4Trace        ) nibble |= 2;
+    if (Nr6Trace        ) nibble |= 4;
+    if (NtpClientTrace  ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //9
+    if (Echo4Trace      ) nibble |= 4;
+    if (Echo6Trace      ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //10
+    if (Dest6Trace      ) nibble |= 1;
+    if (RaTrace         ) nibble |= 2;
+    if (RsTrace         ) nibble |= 4;
+    if (Ar4Trace        ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //11
+    if (Ar6Trace        ) nibble |= 1;
+    if (ArpTrace        ) nibble |= 2;
+    if (Ip4Trace        ) nibble |= 4;
+    if (Ip6Trace        ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+    
+    nibble = 0; //12
+    if (UdpTrace        ) nibble |= 1;
+    if (TcpTrace        ) nibble |= 2;
+    if (HttpTrace       ) nibble |= 4;
+    if (TftpTrace       ) nibble |= 8;
+    HttpAddNibbleAsHex(nibble);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net-trace/web-trace-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,55 @@
+#include "web-add.h"
+#include "web-nav-base.h"
+#include "http.h"
+
+void WebTraceHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Net Trace", "settings.css", "trace.js");
+    WebAddNav(TRACE_PAGE);
+    WebAddH1("Net Trace");
+    
+    WebAddH2("General");
+    WebAddAjaxInput      ("Trace host"      , 5  , "ajax-trace-net-host"   , "set-trace-net-host"   );
+    WebAddAjaxInputToggle("Trace stack"          , "ajax-trace-net-stack"  , "chg-trace-net-stack"  );
+    WebAddAjaxInputToggle("Trace new line"       , "ajax-trace-net-newline", "chg-trace-net-newline");
+    WebAddAjaxInputToggle("Trace verbose"        , "ajax-trace-net-verbose", "chg-trace-net-verbose");
+    WebAddH2("Net");
+    WebAddAjaxInputToggle("MAC"                  , "ajax-trace-link"       , "chg-trace-link"       );
+    WebAddAjaxInputToggle("Ip4 filtered"         , "ajax-trace-ip4"        , "chg-trace-ip4"        );
+    WebAddAjaxInputToggle("Ip6 filtered"         , "ajax-trace-ip6"        , "chg-trace-ip6"        );
+    WebAddAjaxInputToggle("Udp filtered"         , "ajax-trace-udp"        , "chg-trace-udp"        );
+    WebAddAjaxInputToggle("Tcp filtered"         , "ajax-trace-tcp"        , "chg-trace-tcp"        );
+    WebAddAjaxInputToggle("Echo4 (ping4)"        , "ajax-trace-echo4"      , "chg-trace-echo4"      );
+    WebAddAjaxInputToggle("Echo6 (ping6)"        , "ajax-trace-echo6"      , "chg-trace-echo6"      );
+    WebAddAjaxInputToggle("Dest6 unreacheable"   , "ajax-trace-dest6"      , "chg-trace-dest6"      );
+    WebAddAjaxInputToggle("HTTP"                 , "ajax-trace-http"       , "chg-trace-http"       );
+    WebAddAjaxInputToggle("TFTP"                 , "ajax-trace-tftp"       , "chg-trace-tftp"       );
+    WebAddH2("Send requests via IPv4");
+    WebAddAjaxInputToggle("DNS request via IPv4" , "ajax-trace-dns-ip4"    , "chg-send-dns-ip4"     );
+    WebAddAjaxInputToggle("NTP request via IPv4" , "ajax-trace-ntp-ip4"    , "chg-send-ntp-ip4"     );
+    WebAddAjaxInputToggle("TFTP request via IPv4", "ajax-trace-tftp-ip4"   , "chg-send-tftp-ip4"    );
+    WebAddH2("Router Resolution");
+    WebAddAjaxInputToggle("Router advertise"     , "ajax-trace-ra"         , "chg-trace-ra"         );
+    WebAddAjaxInputToggle("Router solicit"       , "ajax-trace-rs"         , "chg-trace-rs"         );
+    WebAddAjaxInputToggle("DHCP"                 , "ajax-trace-dhcp"       , "chg-trace-dhcp"       );
+    WebAddH2("Address Resolution");
+    WebAddAjaxInputToggle("IP4 cache"            , "ajax-trace-ar4"        , "chg-trace-ar4"        );
+    WebAddAjaxInputToggle("IP6 cache"            , "ajax-trace-ar6"        , "chg-trace-ar6"        );
+    WebAddAjaxInputToggle("ARP"                  , "ajax-trace-arp"        , "chg-trace-arp"        );
+    WebAddAjaxInputToggle("NS server"            , "ajax-trace-ns-recv-sol", "chg-trace-ns-recv-sol");
+    WebAddAjaxInputToggle("NS client reply"      , "ajax-trace-ns-recv-adv", "chg-trace-ns-recv-adv");
+    WebAddAjaxInputToggle("NS client query"      , "ajax-trace-ns-send-sol", "chg-trace-ns-send-sol");
+    WebAddH2("Name Resolution");
+    WebAddAjaxInputToggle("IP4 cache"            , "ajax-trace-nr4"        , "chg-trace-nr4"        );
+    WebAddAjaxInputToggle("IP6 cache"            , "ajax-trace-nr6"        , "chg-trace-nr6"        );
+    WebAddAjaxInputToggle("DNS name"             , "ajax-trace-dns-name"   , "chg-trace-dns-name"   );
+    WebAddAjaxInputToggle("DNS client query"     , "ajax-trace-dns-query"  , "chg-trace-dns-query"  );
+    WebAddAjaxInputToggle("DNS client reply"     , "ajax-trace-dns-reply"  , "chg-trace-dns-reply"  );
+    WebAddAjaxInputToggle("DNS server"           , "ajax-trace-dns-server" , "chg-trace-dns-server" );
+    WebAddH2("NTP");
+    WebAddAjaxInputToggle("NTP"                  , "ajax-trace-ntp"        , "chg-trace-ntp"        );
+    WebAddAjaxInputToggle("NTP client"           , "ajax-trace-ntp-client" , "chg-trace-ntp-client" );
+    
+    WebAddEnd();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net-trace/web-trace-query.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,49 @@
+#include "http.h"
+#include "settings.h"
+
+void WebTraceQuery(char* pQuery)
+{
+    while (pQuery)
+    {
+        char* pName;
+        char* pValue;
+        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
+        
+        if (HttpSameStr(pName, "chg-send-dns-ip4"      )) ChgDnsSendRequestsViaIp4();
+        if (HttpSameStr(pName, "chg-send-ntp-ip4"      )) ChgNtpSendRequestsViaIp4();
+        if (HttpSameStr(pName, "chg-send-tftp-ip4"     )) ChgTftpSendRequestsViaIp4();
+        if (HttpSameStr(pName, "set-trace-net-host"    )) SetTraceNetHost(pValue);                    
+        if (HttpSameStr(pName, "chg-trace-net-stack"   )) ChgTraceNetStack();
+        if (HttpSameStr(pName, "chg-trace-net-newline" )) ChgTraceNetNewLine();
+        if (HttpSameStr(pName, "chg-trace-net-verbose" )) ChgTraceNetVerbose();
+        if (HttpSameStr(pName, "chg-trace-link"        )) ChgTraceLink();
+        if (HttpSameStr(pName, "chg-trace-dns-name"    )) ChgTraceDnsName();
+        if (HttpSameStr(pName, "chg-trace-dns-query"   )) ChgTraceDnsQuery();
+        if (HttpSameStr(pName, "chg-trace-dns-reply"   )) ChgTraceDnsReply();
+        if (HttpSameStr(pName, "chg-trace-dns-server"  )) ChgTraceDnsServer();
+        if (HttpSameStr(pName, "chg-trace-ntp"         )) ChgTraceNtp();
+        if (HttpSameStr(pName, "chg-trace-dhcp"        )) ChgTraceDhcp();
+        if (HttpSameStr(pName, "chg-trace-ns-recv-sol" )) ChgTraceNsRecvSol();
+        if (HttpSameStr(pName, "chg-trace-ns-recv-adv" )) ChgTraceNsRecvAdv();
+        if (HttpSameStr(pName, "chg-trace-ns-send-sol" )) ChgTraceNsSendSol();
+        if (HttpSameStr(pName, "chg-trace-nr4"         )) ChgTraceNr4();
+        if (HttpSameStr(pName, "chg-trace-nr6"         )) ChgTraceNr6();
+        if (HttpSameStr(pName, "chg-trace-ntp-client"  )) ChgTraceNtpClient();
+        if (HttpSameStr(pName, "chg-trace-sync"        )) ChgTraceSync();
+        if (HttpSameStr(pName, "chg-trace-echo4"       )) ChgTraceEcho4();
+        if (HttpSameStr(pName, "chg-trace-echo6"       )) ChgTraceEcho6();
+        if (HttpSameStr(pName, "chg-trace-dest6"       )) ChgTraceDest6();
+        if (HttpSameStr(pName, "chg-trace-ra"          )) ChgTraceRa();
+        if (HttpSameStr(pName, "chg-trace-rs"          )) ChgTraceRs();
+        if (HttpSameStr(pName, "chg-trace-ar4"         )) ChgTraceAr4();
+        if (HttpSameStr(pName, "chg-trace-ar6"         )) ChgTraceAr6();
+        if (HttpSameStr(pName, "chg-trace-arp"         )) ChgTraceArp();
+        if (HttpSameStr(pName, "chg-trace-ip4"         )) ChgTraceIp4();
+        if (HttpSameStr(pName, "chg-trace-ip6"         )) ChgTraceIp6();
+        if (HttpSameStr(pName, "chg-trace-udp"         )) ChgTraceUdp();
+        if (HttpSameStr(pName, "chg-trace-tcp"         )) ChgTraceTcp();
+        if (HttpSameStr(pName, "chg-trace-http"        )) ChgTraceHttp();
+        if (HttpSameStr(pName, "chg-trace-tftp"        )) ChgTraceTftp();
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net-trace/web-trace-script.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,17 @@
+#include "http.h"
+
+//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
+//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
+
+const char* WebTraceScriptDate = __DATE__;
+const char* WebTraceScriptTime = __TIME__;
+
+static const char* script =
+#include "../core/web-ajax-class.inc"
+#include "web-trace-script.inc"
+;
+void WebTraceScript()
+{
+    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebTraceScriptDate, WebTraceScriptTime);
+    HttpAddText(script);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net-trace/web-trace-script.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,48 @@
+"//Net trace script\n"
+"'use strict';\n"
+"function setDirection(elem, iChar, iBit)\n"
+"{\n"
+"    elem.setAttribute('dir', Ajax.hexToBit(Ajax.response.charAt(iChar), iBit) ? 'rtl' : 'ltr');\n"
+"}\n"
+"function display()\n"
+"{\n"
+"   var elem;\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dns-ip4'     ); if (elem) setDirection(elem,  0, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ntp-ip4'     ); if (elem) setDirection(elem,  0, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-tftp-ip4'    ); if (elem) setDirection(elem,  0, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-net-host'    ); if (elem) elem.value = Ajax.response.substr( 1, 4);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-net-stack'   ); if (elem) setDirection(elem,  5, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-net-newline' ); if (elem) setDirection(elem,  5, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-net-verbose' ); if (elem) setDirection(elem,  5, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-link'        ); if (elem) setDirection(elem,  5, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dns-name'    ); if (elem) setDirection(elem,  6, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dns-query'   ); if (elem) setDirection(elem,  6, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dns-reply'   ); if (elem) setDirection(elem,  6, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dns-server'  ); if (elem) setDirection(elem,  6, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ntp'         ); if (elem) setDirection(elem,  7, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dhcp'        ); if (elem) setDirection(elem,  7, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-sol' ); if (elem) setDirection(elem,  7, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-adv' ); if (elem) setDirection(elem,  7, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ns-send-sol' ); if (elem) setDirection(elem,  8, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-nr4'         ); if (elem) setDirection(elem,  8, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-nr6'         ); if (elem) setDirection(elem,  8, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ntp-client'  ); if (elem) setDirection(elem,  8, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-echo4'       ); if (elem) setDirection(elem,  9, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-echo6'       ); if (elem) setDirection(elem,  9, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-dest6'       ); if (elem) setDirection(elem, 10, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ra'          ); if (elem) setDirection(elem, 10, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-rs'          ); if (elem) setDirection(elem, 10, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ar4'         ); if (elem) setDirection(elem, 10, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ar6'         ); if (elem) setDirection(elem, 11, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-arp'         ); if (elem) setDirection(elem, 11, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ip4'         ); if (elem) setDirection(elem, 11, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-ip6'         ); if (elem) setDirection(elem, 11, 3);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-udp'         ); if (elem) setDirection(elem, 12, 0);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-tcp'         ); if (elem) setDirection(elem, 12, 1);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-http'        ); if (elem) setDirection(elem, 12, 2);\n"
+"   elem = Ajax.getElementOrNull('ajax-trace-tftp'        ); if (elem) setDirection(elem, 12, 3);\n"
+"}\n"
+"\n"
+"Ajax.server     = '/trace-ajax';\n"
+"Ajax.onResponse = display;\n"
+"Ajax.init();"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net-trace/web-trace-script.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,48 @@
+//Net trace script
+'use strict';
+function setDirection(elem, iChar, iBit)
+{
+    elem.setAttribute('dir', Ajax.hexToBit(Ajax.response.charAt(iChar), iBit) ? 'rtl' : 'ltr');
+}
+function display()
+{
+   var elem;
+   elem = Ajax.getElementOrNull('ajax-trace-dns-ip4'     ); if (elem) setDirection(elem,  0, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-ntp-ip4'     ); if (elem) setDirection(elem,  0, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-tftp-ip4'    ); if (elem) setDirection(elem,  0, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-net-host'    ); if (elem) elem.value = Ajax.response.substr( 1, 4);
+   elem = Ajax.getElementOrNull('ajax-trace-net-stack'   ); if (elem) setDirection(elem,  5, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-net-newline' ); if (elem) setDirection(elem,  5, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-net-verbose' ); if (elem) setDirection(elem,  5, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-link'        ); if (elem) setDirection(elem,  5, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-dns-name'    ); if (elem) setDirection(elem,  6, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-dns-query'   ); if (elem) setDirection(elem,  6, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-dns-reply'   ); if (elem) setDirection(elem,  6, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-dns-server'  ); if (elem) setDirection(elem,  6, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-ntp'         ); if (elem) setDirection(elem,  7, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-dhcp'        ); if (elem) setDirection(elem,  7, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-sol' ); if (elem) setDirection(elem,  7, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-adv' ); if (elem) setDirection(elem,  7, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-ns-send-sol' ); if (elem) setDirection(elem,  8, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-nr4'         ); if (elem) setDirection(elem,  8, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-nr6'         ); if (elem) setDirection(elem,  8, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-ntp-client'  ); if (elem) setDirection(elem,  8, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-echo4'       ); if (elem) setDirection(elem,  9, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-echo6'       ); if (elem) setDirection(elem,  9, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-dest6'       ); if (elem) setDirection(elem, 10, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-ra'          ); if (elem) setDirection(elem, 10, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-rs'          ); if (elem) setDirection(elem, 10, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-ar4'         ); if (elem) setDirection(elem, 10, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-ar6'         ); if (elem) setDirection(elem, 11, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-arp'         ); if (elem) setDirection(elem, 11, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-ip4'         ); if (elem) setDirection(elem, 11, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-ip6'         ); if (elem) setDirection(elem, 11, 3);
+   elem = Ajax.getElementOrNull('ajax-trace-udp'         ); if (elem) setDirection(elem, 12, 0);
+   elem = Ajax.getElementOrNull('ajax-trace-tcp'         ); if (elem) setDirection(elem, 12, 1);
+   elem = Ajax.getElementOrNull('ajax-trace-http'        ); if (elem) setDirection(elem, 12, 2);
+   elem = Ajax.getElementOrNull('ajax-trace-tftp'        ); if (elem) setDirection(elem, 12, 3);
+}
+
+Ajax.server     = '/trace-ajax';
+Ajax.onResponse = display;
+Ajax.init();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net-class.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,69 @@
+"//Net class\n"
+"'use strict';\n"
+"\n"
+"class Net\n"
+"{\n"
+"    static makeIp4(text)\n"
+"    {\n"
+"        let result = '';\n"
+"        result += parseInt(text.substr(6, 2), 16).toString();\n"
+"        result += '.';\n"
+"        result += parseInt(text.substr(4, 2), 16).toString();\n"
+"        result += '.';\n"
+"        result += parseInt(text.substr(2, 2), 16).toString();\n"
+"        result += '.';\n"
+"        result += parseInt(text.substr(0, 2), 16).toString();\n"
+"        return result;\n"
+"    }\n"
+"    static makeMac(text)\n"
+"    {\n"
+"        text = text.toLowerCase();\n"
+"        let result = '';\n"
+"        result += text.substr( 0, 2);\n"
+"        result += ':';\n"
+"        result += text.substr( 2, 2);\n"
+"        result += ':';\n"
+"        result += text.substr( 4, 2);\n"
+"        result += ':';\n"
+"        result += text.substr( 6, 2);\n"
+"        result += ':';\n"
+"        result += text.substr( 8, 2);\n"
+"        result += ':';\n"
+"        result += text.substr(10, 2);\n"
+"        return result;\n"
+"    }\n"
+"\n"
+"    static hexToBit(text, iBit)\n"
+"    {\n"
+"       let value = parseInt(text, 16);\n"
+"       value >>= iBit;\n"
+"       return value & 1;\n"
+"    }\n"
+"    static makeIp6(text)\n"
+"    {\n"
+"        function makeWord(text)\n"
+"        {\n"
+"            let word = parseInt(text, 16);\n"
+"            if (word === 0) return '';\n"
+"            return word.toString(16);\n"
+"        }\n"
+"        text = text.toLowerCase();\n"
+"        let result = '';\n"
+"        result += makeWord(text.substr( 0, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr( 4, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr( 8, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr(12, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr(16, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr(20, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr(24, 4));\n"
+"        result += ':';\n"
+"        result += makeWord(text.substr(28, 4));\n"
+"        return result;\n"
+"    }\n"
+"}"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net-class.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,69 @@
+//Net class
+'use strict';
+
+class Net
+{
+    static makeIp4(text)
+    {
+        let result = '';
+        result += parseInt(text.substr(6, 2), 16).toString();
+        result += '.';
+        result += parseInt(text.substr(4, 2), 16).toString();
+        result += '.';
+        result += parseInt(text.substr(2, 2), 16).toString();
+        result += '.';
+        result += parseInt(text.substr(0, 2), 16).toString();
+        return result;
+    }
+    static makeMac(text)
+    {
+        text = text.toLowerCase();
+        let result = '';
+        result += text.substr( 0, 2);
+        result += ':';
+        result += text.substr( 2, 2);
+        result += ':';
+        result += text.substr( 4, 2);
+        result += ':';
+        result += text.substr( 6, 2);
+        result += ':';
+        result += text.substr( 8, 2);
+        result += ':';
+        result += text.substr(10, 2);
+        return result;
+    }
+
+    static hexToBit(text, iBit)
+    {
+       let value = parseInt(text, 16);
+       value >>= iBit;
+       return value & 1;
+    }
+    static makeIp6(text)
+    {
+        function makeWord(text)
+        {
+            let word = parseInt(text, 16);
+            if (word === 0) return '';
+            return word.toString(16);
+        }
+        text = text.toLowerCase();
+        let result = '';
+        result += makeWord(text.substr( 0, 4));
+        result += ':';
+        result += makeWord(text.substr( 4, 4));
+        result += ':';
+        result += makeWord(text.substr( 8, 4));
+        result += ':';
+        result += makeWord(text.substr(12, 4));
+        result += ':';
+        result += makeWord(text.substr(16, 4));
+        result += ':';
+        result += makeWord(text.substr(20, 4));
+        result += ':';
+        result += makeWord(text.substr(24, 4));
+        result += ':';
+        result += makeWord(text.substr(28, 4));
+        return result;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,18 @@
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-add.h"
+#include "mac.h"
+
+void WebNetHtml()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Net", "settings.css", NULL);
+    WebAddNav(NET_PAGE);
+    WebAddH1("Net");
+                     
+    WebAddH2("Ethernet");
+    WebAddLabelledMac  ("MAC",                   MacLocal);
+    
+    WebAddEnd();
+                        
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net4-ajax.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,31 @@
+#include   <stdio.h>
+
+#include "http.h"
+#include "ar4.h"
+#include "nr4.h"
+#include "dhcp.h"
+
+void WebNet4Ajax()
+{
+    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
+    
+    HttpAddInt32AsHex(DhcpLocalIp);          HttpAddChar('\n');
+    HttpAddText      (DhcpDomainName);       HttpAddChar('\n');
+    HttpAddText      (DhcpHostName);         HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpNtpIp);            HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpDnsServerIp);      HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpServerIp);         HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpRouterIp);         HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpSubnetMask);       HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpBroadcastIp);      HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpLeaseTime);        HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpRenewalT1);        HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpRenewalT2);        HttpAddChar('\n');
+    HttpAddInt32AsHex(DhcpGetElapsedLife()); HttpAddChar('\n');
+    HttpAddChar('\f');
+    
+    Ar4SendAjax();
+    HttpAddChar('\f');
+    
+    Nr4SendAjax();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net4-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,34 @@
+#include "http.h"
+#include "web-add.h"
+#include "web-nav-base.h"
+
+void WebNet4Html()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Net IPv4", "settings.css", "net4.js");
+    WebAddNav(NET4_PAGE);
+    WebAddH1("Net IPv4");
+    
+    WebAddH2("ARP");
+    HttpAddText("<code id='ajax-arp'></code>\r\n");
+    WebAddH2("DNS");
+    HttpAddText("<code id='ajax-dns'></code>\r\n");
+    
+    WebAddH2("DHCP");
+    WebAddAjaxLabelled("IP4 address",   "ajax-local-ip"    );
+    WebAddAjaxLabelled("Domain",        "ajax-domain-name" );
+    WebAddAjaxLabelled("Host name",     "ajax-host-name"   );
+    WebAddAjaxLabelled("NTP server",    "ajax-ntp-ip"      );
+    WebAddAjaxLabelled("DNS server",    "ajax-dns-ip"      );
+    WebAddAjaxLabelled("DHCP server",   "ajax-dhcp-ip"     );
+    WebAddAjaxLabelled("Router",        "ajax-router-ip"   );
+    WebAddAjaxLabelled("Subnet mask",   "ajax-subnet-mask" );
+    WebAddAjaxLabelled("Broadcast IP",  "ajax-broadcast-ip");
+    WebAddAjaxLabelled("Lease time IP", "ajax-lease-time"  );
+    WebAddAjaxLabelled("Renewal T1",    "ajax-renewal-t1"  );
+    WebAddAjaxLabelled("Renewal T2",    "ajax-renewal-t2"  );
+    WebAddAjaxLabelled("Elapsed",       "ajax-elapsed"     );
+    
+    WebAddEnd();
+                        
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net4-script.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,18 @@
+#include "http.h"
+
+//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
+//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
+
+const char* WebNet4ScriptDate = __DATE__;
+const char* WebNet4ScriptTime = __TIME__;
+
+static const char* script =
+#include "../core/web-ajax-class.inc"
+#include "web-net-class.inc"
+#include "web-net4-script.inc"
+;
+void WebNet4Script()
+{
+    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebNet4ScriptDate, WebNet4ScriptTime);
+    HttpAddText(script);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net4-script.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,102 @@
+"//Net4 script\n"
+"'use strict';\n"
+"\n"
+"let localIp     = '';\n"
+"let domainName  = '';\n"
+"let hostName    = '';\n"
+"let ntpIp       = '';\n"
+"let dnsIp       = '';\n"
+"let dhcpIp      = '';\n"
+"let routerIp    = '';\n"
+"let subnetMask  = '';\n"
+"let broadcastIp = '';\n"
+"let leaseTime   = '';\n"
+"let renewalT1   = '';\n"
+"let renewalt2   = '';\n"
+"let elapsed     = '';\n"
+"let arp         = '';\n"
+"let dns         = '';\n"
+"\n"
+"function parseArpLine(line)\n"
+"{\n"
+"    if (line.length == 0) return;\n"
+"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
+"    arp += Math.floor(minutes).toString().padStart(4, ' ');\n"
+"    arp += ' ';\n"
+"    arp += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');\n"
+"    arp += ' ';\n"
+"    arp += Net.makeMac(line.substr(16, 12));\n"
+"    arp += '\\r\\n';\n"
+"}\n"
+"function parseDnsLine(line)\n"
+"{\n"
+"    if (line.length == 0) return;\n"
+"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
+"    dns += Math.floor(minutes).toString().padStart(4, ' ');\n"
+"    dns += ' ';\n"
+"    dns += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');\n"
+"    dns += ' ';\n"
+"    dns += line.substr(16, 1);\n"
+"    dns += ' ';\n"
+"    dns += line.substr(17);\n"
+"    dns += '\\r\\n';\n"
+"}\n"
+"function parseArpLines(text)\n"
+"{\n"
+"    arp = '';\n"
+"    text.split('\\n').forEach(parseArpLine);\n"
+"}\n"
+"function parseDnsLines(text)\n"
+"{\n"
+"    dns = '';\n"
+"    text.split('\\n').forEach(parseDnsLine);\n"
+"}\n"
+"function parseGenLines(text)\n"
+"{\n"
+"    let lines = text.split('\\n');\n"
+"    localIp     = Net.makeIp4(lines[ 0])    ;\n"
+"    domainName  =             lines[ 1]     ;\n"
+"    hostName    =             lines[ 2]     ;\n"
+"    ntpIp       = Net.makeIp4(lines[ 3])    ;\n"
+"    dnsIp       = Net.makeIp4(lines[ 4])    ;\n"
+"    dhcpIp      = Net.makeIp4(lines[ 5])    ;\n"
+"    routerIp    = Net.makeIp4(lines[ 6])    ;\n"
+"    subnetMask  = Net.makeIp4(lines[ 7])    ;\n"
+"    broadcastIp = Net.makeIp4(lines[ 8])    ;\n"
+"    leaseTime   =    parseInt(lines[ 9], 16);\n"
+"    renewalT1   =    parseInt(lines[10], 16);\n"
+"    renewalt2   =    parseInt(lines[11], 16);\n"
+"    elapsed     =    parseInt(lines[12], 16);\n"
+"}\n"
+"function parse()\n"
+"{\n"
+"    let topics = Ajax.response.split('\\f');\n"
+"    parseGenLines(topics[0]);\n"
+"    parseArpLines(topics[1]);\n"
+"    parseDnsLines(topics[2]);\n"
+"}\n"
+"function display()\n"
+"{\n"
+"    let elem;\n"
+"\n"
+"    elem = Ajax.getElementOrNull('ajax-local-ip'    ); if (elem) elem.textContent = localIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-domain-name' ); if (elem) elem.textContent = domainName;\n"
+"    elem = Ajax.getElementOrNull('ajax-host-name'   ); if (elem) elem.textContent = hostName;\n"
+"    elem = Ajax.getElementOrNull('ajax-ntp-ip'      ); if (elem) elem.textContent = ntpIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-dns-ip'      ); if (elem) elem.textContent = dnsIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-dhcp-ip'     ); if (elem) elem.textContent = dhcpIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-router-ip'   ); if (elem) elem.textContent = routerIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-subnet-mask' ); if (elem) elem.textContent = subnetMask;\n"
+"    elem = Ajax.getElementOrNull('ajax-broadcast-ip'); if (elem) elem.textContent = broadcastIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-lease-time'  ); if (elem) elem.textContent = leaseTime;\n"
+"    elem = Ajax.getElementOrNull('ajax-renewal-t1'  ); if (elem) elem.textContent = renewalT1;\n"
+"    elem = Ajax.getElementOrNull('ajax-renewal-t2'  ); if (elem) elem.textContent = renewalt2;\n"
+"    elem = Ajax.getElementOrNull('ajax-elapsed'     ); if (elem) elem.textContent = elapsed;\n"
+"    elem = Ajax.getElementOrNull('ajax-arp'         ); if (elem) elem.textContent = arp;\n"
+"    elem = Ajax.getElementOrNull('ajax-dns'         ); if (elem) elem.textContent = dns;\n"
+"}\n"
+"\n"
+"Ajax.server     = '/net4-ajax';\n"
+"Ajax.onResponse = function() { parse(); display(); };\n"
+"Ajax.init();\n"
+""
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net4-script.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,101 @@
+//Net4 script
+'use strict';
+
+let localIp     = '';
+let domainName  = '';
+let hostName    = '';
+let ntpIp       = '';
+let dnsIp       = '';
+let dhcpIp      = '';
+let routerIp    = '';
+let subnetMask  = '';
+let broadcastIp = '';
+let leaseTime   = '';
+let renewalT1   = '';
+let renewalt2   = '';
+let elapsed     = '';
+let arp         = '';
+let dns         = '';
+
+function parseArpLine(line)
+{
+    if (line.length == 0) return;
+    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
+    arp += Math.floor(minutes).toString().padStart(4, ' ');
+    arp += ' ';
+    arp += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');
+    arp += ' ';
+    arp += Net.makeMac(line.substr(16, 12));
+    arp += '\r\n';
+}
+function parseDnsLine(line)
+{
+    if (line.length == 0) return;
+    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
+    dns += Math.floor(minutes).toString().padStart(4, ' ');
+    dns += ' ';
+    dns += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');
+    dns += ' ';
+    dns += line.substr(16, 1);
+    dns += ' ';
+    dns += line.substr(17);
+    dns += '\r\n';
+}
+function parseArpLines(text)
+{
+    arp = '';
+    text.split('\n').forEach(parseArpLine);
+}
+function parseDnsLines(text)
+{
+    dns = '';
+    text.split('\n').forEach(parseDnsLine);
+}
+function parseGenLines(text)
+{
+    let lines = text.split('\n');
+    localIp     = Net.makeIp4(lines[ 0])    ;
+    domainName  =             lines[ 1]     ;
+    hostName    =             lines[ 2]     ;
+    ntpIp       = Net.makeIp4(lines[ 3])    ;
+    dnsIp       = Net.makeIp4(lines[ 4])    ;
+    dhcpIp      = Net.makeIp4(lines[ 5])    ;
+    routerIp    = Net.makeIp4(lines[ 6])    ;
+    subnetMask  = Net.makeIp4(lines[ 7])    ;
+    broadcastIp = Net.makeIp4(lines[ 8])    ;
+    leaseTime   =    parseInt(lines[ 9], 16);
+    renewalT1   =    parseInt(lines[10], 16);
+    renewalt2   =    parseInt(lines[11], 16);
+    elapsed     =    parseInt(lines[12], 16);
+}
+function parse()
+{
+    let topics = Ajax.response.split('\f');
+    parseGenLines(topics[0]);
+    parseArpLines(topics[1]);
+    parseDnsLines(topics[2]);
+}
+function display()
+{
+    let elem;
+
+    elem = Ajax.getElementOrNull('ajax-local-ip'    ); if (elem) elem.textContent = localIp;
+    elem = Ajax.getElementOrNull('ajax-domain-name' ); if (elem) elem.textContent = domainName;
+    elem = Ajax.getElementOrNull('ajax-host-name'   ); if (elem) elem.textContent = hostName;
+    elem = Ajax.getElementOrNull('ajax-ntp-ip'      ); if (elem) elem.textContent = ntpIp;
+    elem = Ajax.getElementOrNull('ajax-dns-ip'      ); if (elem) elem.textContent = dnsIp;
+    elem = Ajax.getElementOrNull('ajax-dhcp-ip'     ); if (elem) elem.textContent = dhcpIp;
+    elem = Ajax.getElementOrNull('ajax-router-ip'   ); if (elem) elem.textContent = routerIp;
+    elem = Ajax.getElementOrNull('ajax-subnet-mask' ); if (elem) elem.textContent = subnetMask;
+    elem = Ajax.getElementOrNull('ajax-broadcast-ip'); if (elem) elem.textContent = broadcastIp;
+    elem = Ajax.getElementOrNull('ajax-lease-time'  ); if (elem) elem.textContent = leaseTime;
+    elem = Ajax.getElementOrNull('ajax-renewal-t1'  ); if (elem) elem.textContent = renewalT1;
+    elem = Ajax.getElementOrNull('ajax-renewal-t2'  ); if (elem) elem.textContent = renewalt2;
+    elem = Ajax.getElementOrNull('ajax-elapsed'     ); if (elem) elem.textContent = elapsed;
+    elem = Ajax.getElementOrNull('ajax-arp'         ); if (elem) elem.textContent = arp;
+    elem = Ajax.getElementOrNull('ajax-dns'         ); if (elem) elem.textContent = dns;
+}
+
+Ajax.server     = '/net4-ajax';
+Ajax.onResponse = function() { parse(); display(); };
+Ajax.init();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net6-ajax.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,38 @@
+#include   <stdio.h>
+
+#include "http.h"
+#include "ndp.h"
+#include "slaac.h"
+#include "ar6.h"
+#include "nr6.h"
+
+void WebNet6Ajax()
+{
+    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
+    
+    char nibble;
+    nibble = 0;
+    if (NdpManagedConfiguration) nibble |= 1; //4
+    if (NdpOtherConfiguration  ) nibble |= 2; //4
+    if (NdpPrefixFlagL         ) nibble |= 4; //4
+    if (NdpPrefixFlagA         ) nibble |= 8; //4
+    HttpAddNibbleAsHex(nibble);                                                            HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpHopLimit);                                                        HttpAddChar('\n');
+    for (char* p = NdpRouterMac; p < NdpRouterMac + 6; p++) HttpAddByteAsHex(*p);          HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpPrefixLength);                                                    HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpPrefixValidLifetime);                                             HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpPrefixPreferredLifetime);                                         HttpAddChar('\n');
+    for (char* p = NdpPrefix; p < NdpPrefix + 16; p++) HttpAddByteAsHex(*p);               HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpDnsLifetime);                                                     HttpAddChar('\n');
+    for (char* p = NdpDnsServer; p < NdpDnsServer + 16; p++) HttpAddByteAsHex(*p);         HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpGetLease());                                                      HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpGetElapsedLife());                                                HttpAddChar('\n');
+    for (char* p = SlaacLinkLocalIp; p < SlaacLinkLocalIp + 16; p++) HttpAddByteAsHex(*p); HttpAddChar('\n');
+    HttpAddInt32AsHex(NdpMtu);                                                             HttpAddChar('\n');
+    HttpAddChar('\f');
+    
+    Ar6SendAjax();
+    HttpAddChar('\f');
+    
+    Nr6SendAjax();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net6-html.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,37 @@
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-add.h"
+
+void WebNet6Html()
+{
+    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
+    WebAddHeader("Net IPv6", "settings.css", "net6.js");
+    WebAddNav(NET6_PAGE);
+    WebAddH1("Net IPv6");
+
+    WebAddH2("ARP");
+    HttpAddText("<code id='ajax-arp'></code>\r\n");
+    WebAddH2("DNS");
+    HttpAddText("<code id='ajax-dns'></code>\r\n");
+    
+    WebAddH2("NDP");
+    WebAddAjaxLabelled("Hop limit",             "ajax-hop-limit");
+    WebAddAjaxLed     ("Managed address",       "ajax-managed");
+    WebAddAjaxLed     ("Other configuration",   "ajax-other");
+    WebAddAjaxLabelled("Router MAC",            "ajax-router-mac");
+    WebAddAjaxLabelled("Prefix length",         "ajax-prefix-length");
+    WebAddAjaxLed     ("Prefix flag L",         "ajax-prefix-l");
+    WebAddAjaxLed     ("Prefix flag A",         "ajax-prefix-a");
+    WebAddAjaxLabelled("Prefix valid secs",     "ajax-prefix-limit");
+    WebAddAjaxLabelled("Prefix preferred secs", "ajax-prefix-preferred");
+    WebAddAjaxLabelled("Prefix",                "ajax-prefix");
+    WebAddAjaxLabelled("DNS life secs",         "ajax-dns-life");
+    WebAddAjaxLabelled("DNS server",            "ajax-dns-ip");
+    WebAddAjaxLabelled("Lease time",            "ajax-ndp-lease");
+    WebAddAjaxLabelled("Elapsed",               "ajax-ndp-elapsed");
+    WebAddAjaxLabelled("SLAAC",                 "ajax-slaac");
+    WebAddAjaxLabelled("MTU",                   "ajax-mtu");
+    
+    WebAddEnd();
+                        
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net6-script.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,18 @@
+#include "http.h"
+
+//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
+//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
+
+const char* WebNet6ScriptDate = __DATE__;
+const char* WebNet6ScriptTime = __TIME__;
+
+static const char* script =
+#include "../core/web-ajax-class.inc"
+#include "web-net-class.inc"
+#include "web-net6-script.inc"
+;
+void WebNet6Script()
+{
+    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebNet6ScriptDate, WebNet6ScriptTime);
+    HttpAddText(script);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net6-script.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,111 @@
+"//Net6 script\n"
+"'use strict';\n"
+"\n"
+"let arp             = '';\n"
+"let dns             = '';\n"
+"let hopLimit        = '';\n"
+"let managed         = false;\n"
+"let other           = false;\n"
+"let routerMac       = '';\n"
+"let prefixLength    = '';\n"
+"let prefixL         = false;\n"
+"let prefixA         = false;\n"
+"let prefixLimit     = '';\n"
+"let prefixPreferred = '';\n"
+"let prefix          = '';\n"
+"let dnsLife         = '';\n"
+"let dnsIp           = '';\n"
+"let ndpLease        = '';\n"
+"let ndpElapsed      = '';\n"
+"let slaac           = '';\n"
+"let mtu             = '';\n"
+"\n"
+"function parseArpLine(line)\n"
+"{\n"
+"    if (line.length == 0) return;\n"
+"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
+"    arp += Math.floor(minutes).toString().padStart(4, ' ');\n"
+"    arp += ' ';\n"
+"    arp += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');\n"
+"    arp += ' ';\n"
+"    arp += Net.makeMac(line.substr(40, 12));\n"
+"    arp += '\\r\\n';\n"
+"}\n"
+"function parseDnsLine(line)\n"
+"{\n"
+"    if (line.length == 0) return;\n"
+"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
+"    dns += Math.floor(minutes).toString().padStart(4, ' ');\n"
+"    dns += ' ';\n"
+"    dns += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');\n"
+"    dns += ' ';\n"
+"    dns += line.substr(40, 1);\n"
+"    dns += ' ';\n"
+"    dns += line.substr(41);\n"
+"    dns += '\\r\\n';\n"
+"}\n"
+"function parseArpLines(text)\n"
+"{\n"
+"    arp = '';\n"
+"    text.split('\\n').forEach(parseArpLine);\n"
+"}\n"
+"function parseDnsLines(text)\n"
+"{\n"
+"    dns = '';\n"
+"    text.split('\\n').forEach(parseDnsLine);\n"
+"}\n"
+"function parseGenLines(text)\n"
+"{\n"
+"    let lines = text.split('\\n');\n"
+"    \n"
+"    hopLimit        =     parseInt(lines[ 1], 16);\n"
+"    managed         = Net.hexToBit(lines[ 0],  0);\n"
+"    other           = Net.hexToBit(lines[ 0],  1);\n"
+"    routerMac       = Net.makeMac (lines[ 2], 16);\n"
+"    prefixLength    =     parseInt(lines[ 3], 16);\n"
+"    prefixL         = Net.hexToBit(lines[ 0],  2);\n"
+"    prefixA         = Net.hexToBit(lines[ 0],  3);\n"
+"    prefixLimit     =     parseInt(lines[ 4], 16);\n"
+"    prefixPreferred =     parseInt(lines[ 5], 16);\n"
+"    prefix          = Net.makeIp6 (lines[ 6]    );\n"
+"    dnsLife         =     parseInt(lines[ 7], 16);\n"
+"    dnsIp           = Net.makeIp6 (lines[ 8]    );\n"
+"    ndpLease        =     parseInt(lines[ 9], 16);\n"
+"    ndpElapsed      =     parseInt(lines[10], 16);\n"
+"    slaac           = Net.makeIp6 (lines[11]    );\n"
+"    mtu             =     parseInt(lines[12], 16);\n"
+"}\n"
+"function parse()\n"
+"{\n"
+"    let topics = Ajax.response.split('\\f');\n"
+"    parseGenLines(topics[0]);\n"
+"    parseArpLines(topics[1]);\n"
+"    parseDnsLines(topics[2]);\n"
+"}\n"
+"function display()\n"
+"{\n"
+"    let elem;\n"
+"    \n"
+"    elem = Ajax.getElementOrNull('ajax-arp'             ); if (elem) elem.textContent = arp;\n"
+"    elem = Ajax.getElementOrNull('ajax-dns'             ); if (elem) elem.textContent = dns;\n"
+"    elem = Ajax.getElementOrNull('ajax-hop-limit'       ); if (elem) elem.textContent = hopLimit;\n"
+"    elem = Ajax.getElementOrNull('ajax-managed'         ); if (elem) elem.setAttribute('dir', managed ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-other'           ); if (elem) elem.setAttribute('dir', other   ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-router-mac'      ); if (elem) elem.textContent = routerMac;\n"
+"    elem = Ajax.getElementOrNull('ajax-prefix-length'   ); if (elem) elem.textContent = prefixLength;\n"
+"    elem = Ajax.getElementOrNull('ajax-prefix-l'        ); if (elem) elem.setAttribute('dir', prefixL ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-prefix-a'        ); if (elem) elem.setAttribute('dir', prefixA ? 'rtl' : 'ltr');\n"
+"    elem = Ajax.getElementOrNull('ajax-prefix-limit'    ); if (elem) elem.textContent = prefixLimit;\n"
+"    elem = Ajax.getElementOrNull('ajax-prefix-preferred'); if (elem) elem.textContent = prefixPreferred;\n"
+"    elem = Ajax.getElementOrNull('ajax-prefix'          ); if (elem) elem.textContent = prefix;\n"
+"    elem = Ajax.getElementOrNull('ajax-dns-life'        ); if (elem) elem.textContent = dnsLife;\n"
+"    elem = Ajax.getElementOrNull('ajax-dns-ip'          ); if (elem) elem.textContent = dnsIp;\n"
+"    elem = Ajax.getElementOrNull('ajax-ndp-lease'       ); if (elem) elem.textContent = ndpLease;\n"
+"    elem = Ajax.getElementOrNull('ajax-ndp-elapsed'     ); if (elem) elem.textContent = ndpElapsed;\n"
+"    elem = Ajax.getElementOrNull('ajax-slaac'           ); if (elem) elem.textContent = slaac;\n"
+"    elem = Ajax.getElementOrNull('ajax-mtu'             ); if (elem) elem.textContent = mtu;\n"
+"}\n"
+"\n"
+"Ajax.server     = '/net6-ajax';\n"
+"Ajax.onResponse = function() { parse(); display(); };\n"
+"Ajax.init();"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/net/web-net6-script.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,111 @@
+//Net6 script
+'use strict';
+
+let arp             = '';
+let dns             = '';
+let hopLimit        = '';
+let managed         = false;
+let other           = false;
+let routerMac       = '';
+let prefixLength    = '';
+let prefixL         = false;
+let prefixA         = false;
+let prefixLimit     = '';
+let prefixPreferred = '';
+let prefix          = '';
+let dnsLife         = '';
+let dnsIp           = '';
+let ndpLease        = '';
+let ndpElapsed      = '';
+let slaac           = '';
+let mtu             = '';
+
+function parseArpLine(line)
+{
+    if (line.length == 0) return;
+    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
+    arp += Math.floor(minutes).toString().padStart(4, ' ');
+    arp += ' ';
+    arp += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');
+    arp += ' ';
+    arp += Net.makeMac(line.substr(40, 12));
+    arp += '\r\n';
+}
+function parseDnsLine(line)
+{
+    if (line.length == 0) return;
+    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
+    dns += Math.floor(minutes).toString().padStart(4, ' ');
+    dns += ' ';
+    dns += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');
+    dns += ' ';
+    dns += line.substr(40, 1);
+    dns += ' ';
+    dns += line.substr(41);
+    dns += '\r\n';
+}
+function parseArpLines(text)
+{
+    arp = '';
+    text.split('\n').forEach(parseArpLine);
+}
+function parseDnsLines(text)
+{
+    dns = '';
+    text.split('\n').forEach(parseDnsLine);
+}
+function parseGenLines(text)
+{
+    let lines = text.split('\n');
+    
+    hopLimit        =     parseInt(lines[ 1], 16);
+    managed         = Net.hexToBit(lines[ 0],  0);
+    other           = Net.hexToBit(lines[ 0],  1);
+    routerMac       = Net.makeMac (lines[ 2], 16);
+    prefixLength    =     parseInt(lines[ 3], 16);
+    prefixL         = Net.hexToBit(lines[ 0],  2);
+    prefixA         = Net.hexToBit(lines[ 0],  3);
+    prefixLimit     =     parseInt(lines[ 4], 16);
+    prefixPreferred =     parseInt(lines[ 5], 16);
+    prefix          = Net.makeIp6 (lines[ 6]    );
+    dnsLife         =     parseInt(lines[ 7], 16);
+    dnsIp           = Net.makeIp6 (lines[ 8]    );
+    ndpLease        =     parseInt(lines[ 9], 16);
+    ndpElapsed      =     parseInt(lines[10], 16);
+    slaac           = Net.makeIp6 (lines[11]    );
+    mtu             =     parseInt(lines[12], 16);
+}
+function parse()
+{
+    let topics = Ajax.response.split('\f');
+    parseGenLines(topics[0]);
+    parseArpLines(topics[1]);
+    parseDnsLines(topics[2]);
+}
+function display()
+{
+    let elem;
+    
+    elem = Ajax.getElementOrNull('ajax-arp'             ); if (elem) elem.textContent = arp;
+    elem = Ajax.getElementOrNull('ajax-dns'             ); if (elem) elem.textContent = dns;
+    elem = Ajax.getElementOrNull('ajax-hop-limit'       ); if (elem) elem.textContent = hopLimit;
+    elem = Ajax.getElementOrNull('ajax-managed'         ); if (elem) elem.setAttribute('dir', managed ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-other'           ); if (elem) elem.setAttribute('dir', other   ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-router-mac'      ); if (elem) elem.textContent = routerMac;
+    elem = Ajax.getElementOrNull('ajax-prefix-length'   ); if (elem) elem.textContent = prefixLength;
+    elem = Ajax.getElementOrNull('ajax-prefix-l'        ); if (elem) elem.setAttribute('dir', prefixL ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-prefix-a'        ); if (elem) elem.setAttribute('dir', prefixA ? 'rtl' : 'ltr');
+    elem = Ajax.getElementOrNull('ajax-prefix-limit'    ); if (elem) elem.textContent = prefixLimit;
+    elem = Ajax.getElementOrNull('ajax-prefix-preferred'); if (elem) elem.textContent = prefixPreferred;
+    elem = Ajax.getElementOrNull('ajax-prefix'          ); if (elem) elem.textContent = prefix;
+    elem = Ajax.getElementOrNull('ajax-dns-life'        ); if (elem) elem.textContent = dnsLife;
+    elem = Ajax.getElementOrNull('ajax-dns-ip'          ); if (elem) elem.textContent = dnsIp;
+    elem = Ajax.getElementOrNull('ajax-ndp-lease'       ); if (elem) elem.textContent = ndpLease;
+    elem = Ajax.getElementOrNull('ajax-ndp-elapsed'     ); if (elem) elem.textContent = ndpElapsed;
+    elem = Ajax.getElementOrNull('ajax-slaac'           ); if (elem) elem.textContent = slaac;
+    elem = Ajax.getElementOrNull('ajax-mtu'             ); if (elem) elem.textContent = mtu;
+}
+
+Ajax.server     = '/net6-ajax';
+Ajax.onResponse = function() { parse(); display(); };
+Ajax.init();
\ No newline at end of file
--- a/base/web-ajax-class.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-"//Ajax class\n"
-"'use strict';\n"
-"\n"
-"//Exposed properties\n"
-"let ajaxResponse_   = '';\n"
-"let ajaxHeaders_    = '';\n"
-"let ajaxDate_       = null;\n"
-"let ajaxMs_         =  0;\n"
-"let ajaxOnResponse_ = null;\n"
-"let ajaxOnTick_     = null;\n"
-"let ajaxServer_     = '';\n"
-"\n"
-"//Private variables\n"
-"let   ajaxOverrideBlockUpdateOnFocus_ = false;\n"
-"let   ajaxXhr_                        =  null;\n"
-"let   ajaxMsCountAtAjaxSend_          =     0;\n"
-"const ajaxTickMs_                     =   100;\n"
-"const ajaxUpdateMs_                   = 10000;\n"
-"\n"
-"//Private utilities\n"
-"function ajaxGetElementOrNull_(elementName) //Returns the element if it: exists; block is overidden; does not have focus\n"
-"{\n"
-"    let elem = document.getElementById(elementName);\n"
-"    if (!elem) return null;\n"
-"    if (ajaxOverrideBlockUpdateOnFocus_) return elem;\n"
-"    if (elem !== document.activeElement) return elem;\n"
-"    return null;\n"
-"}\n"
-"function ajaxHexToBit_(text, iBit)\n"
-"{\n"
-"   let value = parseInt(text, 16);\n"
-"   value >>= iBit;\n"
-"   return value & 1;\n"
-"}\n"
-"function ajaxHexToSignedInt8_(text)\n"
-"{\n"
-"    let value = parseInt(text, 16);\n"
-"    if (value < 0x80) return value;\n"
-"    return value - 0x100;\n"
-"}\n"
-"function ajaxHexToSignedInt16_(text)\n"
-"{\n"
-"    let value = parseInt(text, 16);\n"
-"    if (value < 0x8000) return value;\n"
-"    return value - 0x10000;\n"
-"}\n"
-"function ajaxHexToSignedInt32_(text)\n"
-"{\n"
-"    let value = parseInt(text, 16);\n"
-"    if (value < 0x80000000) return value;\n"
-"    return value - 0x100000000;\n"
-"}\n"
-"\n"
-"\n"
-"//Private ajax functions\n"
-"function ajaxHandleAjaxResponse_()\n"
-"{\n"
-"   if (ajaxXhr_.readyState == 4 && ajaxXhr_.status == 200)\n"
-"   {\n"
-"        ajaxResponse_ = ajaxXhr_.responseText;\n"
-"        ajaxHeaders_  = ajaxXhr_.getAllResponseHeaders();\n"
-"        let iDateStart = Ajax.headers.toLowerCase().indexOf('date:');\n"
-"        let iDateEnd   = Ajax.headers.indexOf('\\r', iDateStart);\n"
-"        ajaxDate_      = new Date(Ajax.headers.slice(iDateStart + 5, iDateEnd));\n"
-"\n"
-"        let elem;\n"
-"        elem = ajaxGetElementOrNull_('ajax-response'   ); if (elem) elem.textContent = ajaxResponse_;\n"
-"        elem = ajaxGetElementOrNull_('ajax-headers'    ); if (elem) elem.textContent = ajaxHeaders_;\n"
-"        elem = ajaxGetElementOrNull_('ajax-date-local' );\n"
-"        if (elem)\n"
-"        {\n"
-"            elem.textContent = ajaxDate_.toLocaleString(    undefined, {  weekday     : 'short'  ,\n"
-"                                                                          day         : '2-digit',\n"
-"                                                                          month       : 'short'  ,\n"
-"                                                                          year        : 'numeric',\n"
-"                                                                          hour        : '2-digit',\n"
-"                                                                          minute      : '2-digit',\n"
-"                                                                          timeZoneName: 'short'\n"
-"                                                                       }\n"
-"                                                       );\n"
-"        }\n"
-"        if (ajaxOnResponse_) ajaxOnResponse_();\n"
-"        ajaxOverrideBlockUpdateOnFocus_ = false; //Received response so reset override after display\n"
-"   }\n"
-"}\n"
-"function ajaxSendAjaxRequest_(request) //Used by this script and from HTML page\n"
-"{\n"
-"    ajaxXhr_ = new XMLHttpRequest();\n"
-"    ajaxXhr_.onreadystatechange = ajaxHandleAjaxResponse_;\n"
-"    if (request)\n"
-"    {\n"
-"        request = request.split('+').join('%2B');\n"
-"        ajaxXhr_.open('GET', ajaxServer_ + '?' + request, true);\n"
-"    }\n"
-"    else\n"
-"    {\n"
-"        ajaxXhr_.open('GET', ajaxServer_                , true);\n"
-"    }\n"
-"    ajaxXhr_.send();\n"
-"    ajaxMsCountAtAjaxSend_ = ajaxMs_;\n"
-"}\n"
-"function AjaxRequest(request) //From html\n"
-"{\n"
-"    ajaxOverrideBlockUpdateOnFocus_ = true; //Request has come from an update\n"
-"    ajaxSendAjaxRequest_(request);\n"
-"}\n"
-"\n"
-"//Private functions\n"
-"function ajaxTick_() //Called about every 100ms\n"
-"{\n"
-"    ajaxMs_ += ajaxTickMs_; //Don't use Date.now() as we don't know when the PC's clock will be updated around a leap second\n"
-"    if (ajaxMs_ >= ajaxMsCountAtAjaxSend_ + ajaxUpdateMs_) ajaxSendAjaxRequest_('');\n"
-"    if (ajaxOnTick_) ajaxOnTick_();\n"
-"}\n"
-"function ajaxInit_()\n"
-"{\n"
-"    setInterval(ajaxTick_, ajaxTickMs_);\n"
-"    ajaxSendAjaxRequest_('');\n"
-"}\n"
-"\n"
-"//Exposed public\n"
-"class Ajax\n"
-"{\n"
-"    static get ms        ()  { return ajaxMs_         ; }\n"
-"    static get response  ()  { return ajaxResponse_   ; }\n"
-"    static get headers   ()  { return ajaxHeaders_    ; }\n"
-"    static get date      ()  { return ajaxDate_       ; }\n"
-"    \n"
-"    static set tickMs    (v) { ajaxTickMs_     = v; }\n"
-"    static set updateMs  (v) { ajaxUpdateMs_   = v; }\n"
-"    static set server    (v) { ajaxServer_     = v; }\n"
-"    static set onResponse(v) { ajaxOnResponse_ = v; }\n"
-"    static set onTick    (v) { ajaxOnTick_     = v; }\n"
-"\n"
-"    static getElementOrNull(elementName) { return ajaxGetElementOrNull_(elementName) ; }\n"
-"    static hexToBit        (text, iBit ) { return ajaxHexToBit_        (text, iBit ) ; }\n"
-"    static hexToSignedInt8 (text       ) { return ajaxHexToSignedInt8_ (text       ) ; }\n"
-"    static hexToSignedInt16(text       ) { return ajaxHexToSignedInt16_(text       ) ; }\n"
-"    static hexToSignedInt32(text       ) { return ajaxHexToSignedInt32_(text       ) ; }\n"
-"    \n"
-"    static init()\n"
-"    {\n"
-"        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ajaxInit_ ); // Loading hasn't finished yet\n"
-"        else                                                                                 ajaxInit_(); //`DOMContentLoaded` has already fired\n"
-"    }\n"
-"}"
\ No newline at end of file
--- a/base/web-ajax-class.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-//Ajax class
-'use strict';
-
-//Exposed properties
-let ajaxResponse_   = '';
-let ajaxHeaders_    = '';
-let ajaxDate_       = null;
-let ajaxMs_         =  0;
-let ajaxOnResponse_ = null;
-let ajaxOnTick_     = null;
-let ajaxServer_     = '';
-
-//Private variables
-let   ajaxOverrideBlockUpdateOnFocus_ = false;
-let   ajaxXhr_                        =  null;
-let   ajaxMsCountAtAjaxSend_          =     0;
-const ajaxTickMs_                     =   100;
-const ajaxUpdateMs_                   = 10000;
-
-//Private utilities
-function ajaxGetElementOrNull_(elementName) //Returns the element if it: exists; block is overidden; does not have focus
-{
-    let elem = document.getElementById(elementName);
-    if (!elem) return null;
-    if (ajaxOverrideBlockUpdateOnFocus_) return elem;
-    if (elem !== document.activeElement) return elem;
-    return null;
-}
-function ajaxHexToBit_(text, iBit)
-{
-   let value = parseInt(text, 16);
-   value >>= iBit;
-   return value & 1;
-}
-function ajaxHexToSignedInt8_(text)
-{
-    let value = parseInt(text, 16);
-    if (value < 0x80) return value;
-    return value - 0x100;
-}
-function ajaxHexToSignedInt16_(text)
-{
-    let value = parseInt(text, 16);
-    if (value < 0x8000) return value;
-    return value - 0x10000;
-}
-function ajaxHexToSignedInt32_(text)
-{
-    let value = parseInt(text, 16);
-    if (value < 0x80000000) return value;
-    return value - 0x100000000;
-}
-
-
-//Private ajax functions
-function ajaxHandleAjaxResponse_()
-{
-   if (ajaxXhr_.readyState == 4 && ajaxXhr_.status == 200)
-   {
-        ajaxResponse_ = ajaxXhr_.responseText;
-        ajaxHeaders_  = ajaxXhr_.getAllResponseHeaders();
-        let iDateStart = Ajax.headers.toLowerCase().indexOf('date:');
-        let iDateEnd   = Ajax.headers.indexOf('\r', iDateStart);
-        ajaxDate_      = new Date(Ajax.headers.slice(iDateStart + 5, iDateEnd));
-
-        let elem;
-        elem = ajaxGetElementOrNull_('ajax-response'   ); if (elem) elem.textContent = ajaxResponse_;
-        elem = ajaxGetElementOrNull_('ajax-headers'    ); if (elem) elem.textContent = ajaxHeaders_;
-        elem = ajaxGetElementOrNull_('ajax-date-local' );
-        if (elem)
-        {
-            elem.textContent = ajaxDate_.toLocaleString(    undefined, {  weekday     : 'short'  ,
-                                                                          day         : '2-digit',
-                                                                          month       : 'short'  ,
-                                                                          year        : 'numeric',
-                                                                          hour        : '2-digit',
-                                                                          minute      : '2-digit',
-                                                                          timeZoneName: 'short'
-                                                                       }
-                                                       );
-        }
-        if (ajaxOnResponse_) ajaxOnResponse_();
-        ajaxOverrideBlockUpdateOnFocus_ = false; //Received response so reset override after display
-   }
-}
-function ajaxSendAjaxRequest_(request) //Used by this script and from HTML page
-{
-    ajaxXhr_ = new XMLHttpRequest();
-    ajaxXhr_.onreadystatechange = ajaxHandleAjaxResponse_;
-    if (request)
-    {
-        request = request.split('+').join('%2B');
-        ajaxXhr_.open('GET', ajaxServer_ + '?' + request, true);
-    }
-    else
-    {
-        ajaxXhr_.open('GET', ajaxServer_                , true);
-    }
-    ajaxXhr_.send();
-    ajaxMsCountAtAjaxSend_ = ajaxMs_;
-}
-function AjaxRequest(request) //From html
-{
-    ajaxOverrideBlockUpdateOnFocus_ = true; //Request has come from an update
-    ajaxSendAjaxRequest_(request);
-}
-
-//Private functions
-function ajaxTick_() //Called about every 100ms
-{
-    ajaxMs_ += ajaxTickMs_; //Don't use Date.now() as we don't know when the PC's clock will be updated around a leap second
-    if (ajaxMs_ >= ajaxMsCountAtAjaxSend_ + ajaxUpdateMs_) ajaxSendAjaxRequest_('');
-    if (ajaxOnTick_) ajaxOnTick_();
-}
-function ajaxInit_()
-{
-    setInterval(ajaxTick_, ajaxTickMs_);
-    ajaxSendAjaxRequest_('');
-}
-
-//Exposed public
-class Ajax
-{
-    static get ms        ()  { return ajaxMs_         ; }
-    static get response  ()  { return ajaxResponse_   ; }
-    static get headers   ()  { return ajaxHeaders_    ; }
-    static get date      ()  { return ajaxDate_       ; }
-    
-    static set tickMs    (v) { ajaxTickMs_     = v; }
-    static set updateMs  (v) { ajaxUpdateMs_   = v; }
-    static set server    (v) { ajaxServer_     = v; }
-    static set onResponse(v) { ajaxOnResponse_ = v; }
-    static set onTick    (v) { ajaxOnTick_     = v; }
-
-    static getElementOrNull(elementName) { return ajaxGetElementOrNull_(elementName) ; }
-    static hexToBit        (text, iBit ) { return ajaxHexToBit_        (text, iBit ) ; }
-    static hexToSignedInt8 (text       ) { return ajaxHexToSignedInt8_ (text       ) ; }
-    static hexToSignedInt16(text       ) { return ajaxHexToSignedInt16_(text       ) ; }
-    static hexToSignedInt32(text       ) { return ajaxHexToSignedInt32_(text       ) ; }
-    
-    static init()
-    {
-        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ajaxInit_ ); // Loading hasn't finished yet
-        else                                                                                 ajaxInit_(); //`DOMContentLoaded` has already fired
-    }
-}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/web-nav-base.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,14 @@
+#include "web-add.h"
+#include "web-nav-base.h"
+
+void WebNavBase(int page)
+{
+    WebAddNavItem(page ==    CLOCK_PAGE, "/clock",    "Clock"    );
+    WebAddNavItem(page ==    FAULT_PAGE, "/fault",    "Fault"    );
+    WebAddNavItem(page ==      NET_PAGE, "/net",      "Net"      );
+    WebAddNavItem(page ==     NET4_PAGE, "/net4",     "Net IPv4" );
+    WebAddNavItem(page ==     NET6_PAGE, "/net6",     "Net IPv6" );
+    WebAddNavItem(page ==    TRACE_PAGE, "/trace",    "Net Trace");
+    WebAddNavItem(page ==      LOG_PAGE, "/log",      "Log"      );
+    WebAddNavItem(page == FIRMWARE_PAGE, "/firmware", "Firmware" );
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/web-nav-base.h	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,10 @@
+extern void WebNavBase(int page);
+
+#define    FAULT_PAGE 0
+#define    CLOCK_PAGE 1
+#define      NET_PAGE 2
+#define     NET4_PAGE 3
+#define     NET6_PAGE 4
+#define    TRACE_PAGE 5
+#define      LOG_PAGE 6
+#define FIRMWARE_PAGE 7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/web-pages-base.h	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,69 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+extern void        WebLoginHtml     (void);
+extern void        WebLoginQuery    (char* pQuery);
+extern bool        WebLoginQueryPasswordOk;
+extern int         WebLoginOriginalToDo;
+extern bool        WebLoginCookiesContainValidSessionId(char* pCookies);
+extern char*       WebLoginSessionNameGet(void);
+extern int         WebLoginSessionNameLife(void);
+extern char*       WebLoginSessionIdGet(void);
+extern void        WebLoginSessionIdNew(void);
+extern bool        WebLoginSessionIdIsSet(void);
+extern void        WebLoginInit(void);
+
+extern void        WebFavicon       (void);
+extern const char* WebFaviconDate;
+extern const char* WebFaviconTime;
+extern const int   WebFaviconSize;
+
+extern void        WebBaseCss       (void);
+extern const char* WebBaseCssDate;
+extern const char* WebBaseCssTime;
+extern void        WebNavCss        (void);
+extern const char* WebNavCssDate;
+extern const char* WebNavCssTime;
+
+extern void        WebClockHtml     (void);
+extern void        WebClockScript   (void);
+extern const char* WebClockScriptDate;
+extern const char* WebClockScriptTime;
+extern void        WebClockAjax     (void);
+extern void        WebClockQuery    (char* pQuery);
+
+extern void        WebLogHtml       (void);
+extern void        WebLogQuery      (char* pQuery);
+
+extern void        WebTraceHtml     (void);
+extern void        WebTraceScript   (void);
+extern const char* WebTraceScriptDate;
+extern const char* WebTraceScriptTime;
+extern void        WebTraceAjax     (void);
+extern void        WebTraceQuery    (char* pQuery);
+
+extern void        WebNetHtml       (void);
+extern void        WebNet4Html      (void);
+extern void        WebNet4Script    (void);
+extern const char* WebNet4ScriptDate;
+extern const char* WebNet4ScriptTime;
+extern void        WebNet4Ajax      (void);
+extern void        WebNet6Html      (void);
+extern void        WebNet6Script    (void);
+extern const char* WebNet6ScriptDate;
+extern const char* WebNet6ScriptTime;
+extern void        WebNet6Ajax      (void);
+
+extern void        WebFaultHtml     (void);
+extern void        WebFaultQuery    (char* pQuery);
+
+extern void        WebFirmwareHtml  (void);
+extern void        WebFirmwareScript(void);
+extern const char* WebFirmwareScriptDate;
+extern const char* WebFirmwareScriptTime;
+extern void        WebFirmwareQuery (char* pQuery);
+extern int         WebFirmwareTargetLength;
+extern int         WebFirmwareActualLength;
+extern char*       WebFirmwareFileName;
+extern void        WebFirmwarePost  (int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete);
+extern void        WebFirmwareAjax  (void);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/web-server-base.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,104 @@
+#include "http.h"
+#include "web.h"
+#include "web-pages-base.h"
+
+#define DO_FAVICON         DO_BASE +  1
+#define DO_BASE_CSS        DO_BASE +  2
+#define DO_NAV_CSS         DO_BASE +  3
+#define DO_CLOCK_HTML      DO_BASE +  4
+#define DO_CLOCK_AJAX      DO_BASE +  5
+#define DO_CLOCK_SCRIPT    DO_BASE +  6
+#define DO_NET_HTML        DO_BASE +  7
+#define DO_NET4_HTML       DO_BASE +  8
+#define DO_NET4_AJAX       DO_BASE +  9
+#define DO_NET4_SCRIPT     DO_BASE + 10
+#define DO_NET6_HTML       DO_BASE + 11
+#define DO_NET6_AJAX       DO_BASE + 12
+#define DO_NET6_SCRIPT     DO_BASE + 13
+#define DO_TRACE_HTML      DO_BASE + 14
+#define DO_TRACE_AJAX      DO_BASE + 15
+#define DO_TRACE_SCRIPT    DO_BASE + 16
+#define DO_LOG_HTML        DO_BASE + 17
+#define DO_FAULT_HTML      DO_BASE + 18
+#define DO_FIRMWARE_HTML   DO_BASE + 19
+#define DO_FIRMWARE_AJAX   DO_BASE + 20
+#define DO_FIRMWARE_SCRIPT DO_BASE + 21
+
+int WebServerBaseDecideWhatToDo(char *pPath, char* pLastModified)
+{
+    if (HttpSameStr(pPath, "/clock"        )) return DO_CLOCK_HTML;
+    if (HttpSameStr(pPath, "/clock-ajax"   )) return DO_CLOCK_AJAX;
+    if (HttpSameStr(pPath, "/net"          )) return DO_NET_HTML;
+    if (HttpSameStr(pPath, "/net4"         )) return DO_NET4_HTML;
+    if (HttpSameStr(pPath, "/net4-ajax"    )) return DO_NET4_AJAX;
+    if (HttpSameStr(pPath, "/net6"         )) return DO_NET6_HTML;
+    if (HttpSameStr(pPath, "/net6-ajax"    )) return DO_NET6_AJAX;
+    if (HttpSameStr(pPath, "/log"          )) return DO_LOG_HTML;
+    if (HttpSameStr(pPath, "/trace"        )) return DO_TRACE_HTML;
+    if (HttpSameStr(pPath, "/trace-ajax"   )) return DO_TRACE_AJAX;
+    if (HttpSameStr(pPath, "/fault"        )) return DO_FAULT_HTML;
+    if (HttpSameStr(pPath, "/firmware"     )) return DO_FIRMWARE_HTML;
+    if (HttpSameStr(pPath, "/firmware-ajax")) return DO_FIRMWARE_AJAX;
+    
+    if (HttpSameStr(pPath, "/favicon.ico"  )) return HttpSameDate(WebFaviconDate,        WebFaviconTime,        pLastModified) ? DO_NOT_MODIFIED : DO_FAVICON;
+    if (HttpSameStr(pPath, "/base.css"     )) return HttpSameDate(WebBaseCssDate,        WebBaseCssTime,        pLastModified) ? DO_NOT_MODIFIED : DO_BASE_CSS;
+    if (HttpSameStr(pPath, "/settings.css" )) return HttpSameDate(WebNavCssDate,         WebNavCssTime,         pLastModified) ? DO_NOT_MODIFIED : DO_NAV_CSS;
+    if (HttpSameStr(pPath, "/net4.js"      )) return HttpSameDate(WebNet4ScriptDate,     WebNet4ScriptTime,     pLastModified) ? DO_NOT_MODIFIED : DO_NET4_SCRIPT;
+    if (HttpSameStr(pPath, "/net6.js"      )) return HttpSameDate(WebNet6ScriptDate,     WebNet6ScriptTime,     pLastModified) ? DO_NOT_MODIFIED : DO_NET6_SCRIPT;
+    if (HttpSameStr(pPath, "/trace.js"     )) return HttpSameDate(WebTraceScriptDate,    WebTraceScriptTime,    pLastModified) ? DO_NOT_MODIFIED : DO_TRACE_SCRIPT;
+    if (HttpSameStr(pPath, "/clock.js"     )) return HttpSameDate(WebClockScriptDate,    WebClockScriptTime,    pLastModified) ? DO_NOT_MODIFIED : DO_CLOCK_SCRIPT;
+    if (HttpSameStr(pPath, "/firmware.js"  )) return HttpSameDate(WebFirmwareScriptDate, WebFirmwareScriptTime, pLastModified) ? DO_NOT_MODIFIED : DO_FIRMWARE_SCRIPT;
+
+    return DO_NOT_FOUND;
+}
+
+bool WebServerBaseHandleQuery(int todo, char* pQuery)
+{
+    switch (todo)
+    {
+        case DO_TRACE_AJAX:    WebTraceQuery   (pQuery); return true;
+        case DO_CLOCK_AJAX:    WebClockQuery   (pQuery); return true;
+        case DO_CLOCK_HTML:    WebClockQuery   (pQuery); return true;
+        case DO_LOG_HTML:      WebLogQuery     (pQuery); return true;
+        case DO_FAULT_HTML:    WebFaultQuery   (pQuery); return true;
+        case DO_FIRMWARE_HTML: WebFirmwareQuery(pQuery); return true;
+        case DO_FIRMWARE_AJAX: WebFirmwareQuery(pQuery); return true;
+    }
+    return false;
+}
+bool WebServerBasePost(int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete)
+{
+    switch (todo)
+    {
+        case DO_FIRMWARE_AJAX: WebFirmwarePost(contentLength, contentStart, size, pRequestStream, positionInRequestStream, pComplete); return true;
+    }
+    return false;
+}
+bool WebServerBaseReply(int todo)
+{
+    switch (todo)
+    {
+        case DO_FAVICON:         WebFavicon       (); return true;
+        case DO_BASE_CSS:        WebBaseCss       (); return true;
+        case DO_NAV_CSS:         WebNavCss        (); return true;
+        case DO_TRACE_HTML:      WebTraceHtml     (); return true;
+        case DO_TRACE_AJAX:      WebTraceAjax     (); return true;
+        case DO_TRACE_SCRIPT:    WebTraceScript   (); return true;
+        case DO_CLOCK_HTML:      WebClockHtml     (); return true;
+        case DO_CLOCK_AJAX:      WebClockAjax     (); return true;
+        case DO_CLOCK_SCRIPT:    WebClockScript   (); return true;
+        case DO_NET_HTML:        WebNetHtml       (); return true;
+        case DO_NET4_HTML:       WebNet4Html      (); return true;
+        case DO_NET4_AJAX:       WebNet4Ajax      (); return true;
+        case DO_NET4_SCRIPT:     WebNet4Script    (); return true;
+        case DO_NET6_HTML:       WebNet6Html      (); return true;
+        case DO_NET6_AJAX:       WebNet6Ajax      (); return true;
+        case DO_NET6_SCRIPT:     WebNet6Script    (); return true;
+        case DO_LOG_HTML:        WebLogHtml       (); return true;
+        case DO_FAULT_HTML:      WebFaultHtml     (); return true;
+        case DO_FIRMWARE_HTML:   WebFirmwareHtml  (); return true;
+        case DO_FIRMWARE_AJAX:   WebFirmwareAjax  (); return true;
+        case DO_FIRMWARE_SCRIPT: WebFirmwareScript(); return true;
+    }
+    return false;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/base/web-server-base.h	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,7 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+extern int  WebServerBaseDecideWhatToDo(char *pPath, char* pLastModified);
+extern bool WebServerBaseHandleQuery   (int todo, char* pQuery);
+extern bool WebServerBasePost          (int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete);
+extern bool WebServerBaseReply         (int todo);
--- a/clock/web-clock-ajax.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-#include  <stdint.h>
-#include   <stdio.h>
-
-#include "http.h"
-#include "rtc.h"
-#include "clk.h"
-#include "clktime.h"
-#include "clkgov.h"
-#include "clkutc.h"
-#include "ntpclient.h"
-#include "scan.h"
-
-void WebClockAjax()
-{
-    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
-    
-    //Time and UTC
-    clktime now = ClkNowTai();
-    clktime fraction = now & ((1UL << CLK_TIME_ONE_SECOND_SHIFT) - 1);
-    clktime ms = (fraction * 1000) >> CLK_TIME_ONE_SECOND_SHIFT;
-    HttpAddInt16AsHex(ms                           ); HttpAddChar('\n');
-    char byte = 0;
-    if (RtcIsSet()                ) byte |= 0x01;
-    if (ClkTimeIsSet()            ) byte |= 0x02;
-    if (ClkGovIsReceivingTime     ) byte |= 0x04;
-    if (ClkGovRateIsSynced        ) byte |= 0x08;
-    if (ClkGovTimeIsSynced        ) byte |= 0x10;
-    if (ClkUtcGetNextLeapEnable() ) byte |= 0x20;
-    if (ClkUtcGetNextLeapForward()) byte |= 0x40;
-    if (ClkGovTrace)                byte |= 0x80;
-    HttpAddByteAsHex (byte                         ); HttpAddChar('\n');
-    HttpAddInt12AsHex(ClkUtcGetNextEpochMonth1970()); HttpAddChar('\n');
-    HttpAddInt16AsHex(ClkUtcGetEpochOffset()       ); HttpAddChar('\n');
-    HttpAddChar('\f');
-    
-    //Governer
-    HttpAddInt32AsHex(ClkGovGetPpb()               ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovFreqDivisor            ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovFreqChangeMaxPpb       ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovFreqSyncedLimPpb       ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovFreqSyncedHysPpb       ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovSlewDivisor            ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovSlewChangeMaxMs        ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovSlewSyncedLimNs        ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovSlewSyncedHysNs        ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ClkGovSlewOffsetMaxSecs      ); HttpAddChar('\n');
-    HttpAddChar('\f');
-    
-    //NTP
-    HttpAddText      (NtpClientQueryServerName     ); HttpAddChar('\n');
-    HttpAddInt32AsHex(NtpClientQueryInitialInterval); HttpAddChar('\n');
-    HttpAddInt32AsHex(NtpClientQueryNormalInterval ); HttpAddChar('\n');
-    HttpAddInt32AsHex(NtpClientQueryRetryInterval  ); HttpAddChar('\n');
-    HttpAddInt32AsHex(NtpClientReplyOffsetMs       ); HttpAddChar('\n');
-    HttpAddInt32AsHex(NtpClientReplyMaxDelayMs     ); HttpAddChar('\n');
-    HttpAddChar('\f');
-    
-    //Scan
-    HttpAddInt32AsHex(ScanAverage                  ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ScanMaximum                  ); HttpAddChar('\n');
-    HttpAddInt32AsHex(ScanMinimum                  ); HttpAddChar('\n');
-    HttpAddChar('\f');
-}
-
--- a/clock/web-clock-class.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-"//Clock class\n"
-"'use strict';\n"
-"\n"
-"class Clock\n"
-"{\n"
-"    constructor()\n"
-"    {\n"
-"        this.leapEnable  = false;\n"
-"        this.leapForward = false;\n"
-"        this.leapMonth   = 0;\n"
-"        this.leapYear    = 0;\n"
-"        this.leaps       = 0;\n"
-"        this.ms          = 0;\n"
-"    }\n"
-"    \n"
-"    set months1970(value)\n"
-"    {\n"
-"        this.leapMonth   =  value                   % 12;\n"
-"        this.leapYear    = (value - this.leapMonth) / 12;\n"
-"        this.leapMonth  += 1;\n"
-"        this.leapYear   += 1970;\n"
-"    }\n"
-"    get months1970()\n"
-"    {\n"
-"        if (this.leapYear  <= 0) return 0;\n"
-"        if (this.leapMonth <= 0) return 0;\n"
-"        return (this.leapYear - 1970) * 12 + this.leapMonth - 1;\n"
-"    }\n"
-"    \n"
-"    formatNumbers00(i)\n"
-"    {\n"
-"       if (i < 10) return '0' + i;\n"
-"       return i;\n"
-"    }\n"
-"    formatDayOfWeek(wday)\n"
-"    {\n"
-"        switch(wday)\n"
-"        {\n"
-"            case  0: return 'Sun';\n"
-"            case  1: return 'Mon';\n"
-"            case  2: return 'Tue';\n"
-"            case  3: return 'Wed';\n"
-"            case  4: return 'Thu';\n"
-"            case  5: return 'Fri';\n"
-"            case  6: return 'Sat';\n"
-"            default: return '---';\n"
-"        }\n"
-"    }\n"
-"    adjustLeap(baseMs)\n"
-"    {\n"
-"        if (this.ms == 0) return; //Don't attempt to adjust an invalid time\n"
-"        \n"
-"        if (!this.leapEnable) return; // Adjustment disabled\n"
-"        \n"
-"        //Get the calander date and time from the ms\n"
-"        let now       = this.ms + baseMs;\n"
-"        let leapStart = Date.UTC(this.leapYear, this.leapMonth - 1, 1, 0, 0, this.leapForward ? 0: -1);\n"
-"        \n"
-"        if (now < leapStart) return; //Do nothing until reached the leap start\n"
-"        \n"
-"        if (this.leapForward) { this.ms -= 1000; this.leaps += 1; } //repeat 59\n"
-"        else                  { this.ms += 1000; this.leaps -= 1; } //skip   59\n"
-"        \n"
-"        this.leapEnable = false;\n"
-"    }\n"
-"    displayTime(baseMs)\n"
-"    {\n"
-"        if (this.ms == 0) return; //Don't attempt to display an invalid time\n"
-"        \n"
-"        //Get the calander date and time from the ms\n"
-"        let now = new Date(this.ms + baseMs);\n"
-"        let   y = now.getUTCFullYear();\n"
-"        let   n = now.getUTCMonth   () + 1;\n"
-"        let   d = now.getUTCDate    ();\n"
-"        let   w = now.getUTCDay     (); // 0 == Sunday\n"
-"        let   h = now.getUTCHours   ();\n"
-"        let   m = now.getUTCMinutes ();\n"
-"        let   s = now.getUTCSeconds ();\n"
-"        \n"
-"        //Format time\n"
-"        n = this.formatNumbers00(n);\n"
-"        d = this.formatNumbers00(d);\n"
-"        h = this.formatNumbers00(h);\n"
-"        m = this.formatNumbers00(m);\n"
-"        s = this.formatNumbers00(s);\n"
-"        w = this.formatDayOfWeek(w);\n"
-"        \n"
-"        //Display time\n"
-"        let elem;\n"
-"        elem = document.getElementById('ajax-date-utc');\n"
-"        if (elem) elem.textContent = y + '-' + n + '-' + d + ' ' + w + ' ' + h + ':' + m + ':' + s + ' TAI-UTC=' + this.leaps;\n"
-"    \n"
-"        elem = document.getElementById('ajax-date-pc');\n"
-"        let options = \n"
-"        {\n"
-"            year:         'numeric',\n"
-"            month:        'short',\n"
-"            day:          '2-digit',\n"
-"            weekday:      'short',\n"
-"            hour:         '2-digit',\n"
-"            minute:       '2-digit',\n"
-"            second:       '2-digit',\n"
-"            timeZoneName: 'short'\n"
-"        };\n"
-"        if (elem) elem.textContent = now.toLocaleString(undefined, options);\n"
-"    }\n"
-"}\n"
-""
\ No newline at end of file
--- a/clock/web-clock-class.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-//Clock class
-'use strict';
-
-class Clock
-{
-    constructor()
-    {
-        this.leapEnable  = false;
-        this.leapForward = false;
-        this.leapMonth   = 0;
-        this.leapYear    = 0;
-        this.leaps       = 0;
-        this.ms          = 0;
-    }
-    
-    set months1970(value)
-    {
-        this.leapMonth   =  value                   % 12;
-        this.leapYear    = (value - this.leapMonth) / 12;
-        this.leapMonth  += 1;
-        this.leapYear   += 1970;
-    }
-    get months1970()
-    {
-        if (this.leapYear  <= 0) return 0;
-        if (this.leapMonth <= 0) return 0;
-        return (this.leapYear - 1970) * 12 + this.leapMonth - 1;
-    }
-    
-    formatNumbers00(i)
-    {
-       if (i < 10) return '0' + i;
-       return i;
-    }
-    formatDayOfWeek(wday)
-    {
-        switch(wday)
-        {
-            case  0: return 'Sun';
-            case  1: return 'Mon';
-            case  2: return 'Tue';
-            case  3: return 'Wed';
-            case  4: return 'Thu';
-            case  5: return 'Fri';
-            case  6: return 'Sat';
-            default: return '---';
-        }
-    }
-    adjustLeap(baseMs)
-    {
-        if (this.ms == 0) return; //Don't attempt to adjust an invalid time
-        
-        if (!this.leapEnable) return; // Adjustment disabled
-        
-        //Get the calander date and time from the ms
-        let now       = this.ms + baseMs;
-        let leapStart = Date.UTC(this.leapYear, this.leapMonth - 1, 1, 0, 0, this.leapForward ? 0: -1);
-        
-        if (now < leapStart) return; //Do nothing until reached the leap start
-        
-        if (this.leapForward) { this.ms -= 1000; this.leaps += 1; } //repeat 59
-        else                  { this.ms += 1000; this.leaps -= 1; } //skip   59
-        
-        this.leapEnable = false;
-    }
-    displayTime(baseMs)
-    {
-        if (this.ms == 0) return; //Don't attempt to display an invalid time
-        
-        //Get the calander date and time from the ms
-        let now = new Date(this.ms + baseMs);
-        let   y = now.getUTCFullYear();
-        let   n = now.getUTCMonth   () + 1;
-        let   d = now.getUTCDate    ();
-        let   w = now.getUTCDay     (); // 0 == Sunday
-        let   h = now.getUTCHours   ();
-        let   m = now.getUTCMinutes ();
-        let   s = now.getUTCSeconds ();
-        
-        //Format time
-        n = this.formatNumbers00(n);
-        d = this.formatNumbers00(d);
-        h = this.formatNumbers00(h);
-        m = this.formatNumbers00(m);
-        s = this.formatNumbers00(s);
-        w = this.formatDayOfWeek(w);
-        
-        //Display time
-        let elem;
-        elem = document.getElementById('ajax-date-utc');
-        if (elem) elem.textContent = y + '-' + n + '-' + d + ' ' + w + ' ' + h + ':' + m + ':' + s + ' TAI-UTC=' + this.leaps;
-    
-        elem = document.getElementById('ajax-date-pc');
-        let options = 
-        {
-            year:         'numeric',
-            month:        'short',
-            day:          '2-digit',
-            weekday:      'short',
-            hour:         '2-digit',
-            minute:       '2-digit',
-            second:       '2-digit',
-            timeZoneName: 'short'
-        };
-        if (elem) elem.textContent = now.toLocaleString(undefined, options);
-    }
-}
--- a/clock/web-clock-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-#include <time.h>
-
-#include "http.h"
-#include "web-page-base.h"
-#include "web-add.h"
-
-void WebClockHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Clock", "settings.css", "clock.js");
-    WebAddNav(CLOCK_PAGE);
-    WebAddH1("Clock");
-    
-    WebAddH2("Status");
-    WebAddAjaxLed("RTC is set"           , "ajax-rtc-set"    );
-    WebAddAjaxLed("Clock is set"         , "ajax-clock-set"  );
-    WebAddAjaxLed("External source is ok", "ajax-source-ok"  );
-    WebAddAjaxLed("Time synchronised"    , "ajax-time-locked");
-    WebAddAjaxLed("Rate synchronised"    , "ajax-rate-locked");
-    
-    WebAddH2("Server UTC time");
-    HttpAddText("<div id='ajax-date-utc'></div>\r\n");
-
-    WebAddH2("Server local time");
-    HttpAddText("<div id='ajax-date-pc'></div>\r\n");
-    
-    WebAddH2("Server - PC (ms)");
-    HttpAddText("<div id='ajax-date-diff'></div>\r\n");    
-    
-    WebAddH2("UTC");
-    WebAddAjaxInputToggle("Enable epoch change"     ,    "ajax-leap-enable"  , "chg-clock-leap-enable" );
-    WebAddAjaxInputToggle("Direction of next epoch" ,    "ajax-leap-forward" , "chg-clock-leap-forward");
-    WebAddAjaxInput      ("Year next epoch starts"  , 4, "ajax-leap-year"    , "set-clock-leap-year"   );
-    WebAddAjaxInput      ("Month next epoch starts" , 4, "ajax-leap-month"   , "set-clock-leap-month"  );
-    WebAddAjaxInput      ("Current era offset"      , 4, "ajax-leap-count"   , "set-clock-leap-count"  );
-
-    HttpAddText("<div><button type='button' onclick='displayLeap()'>Display leap</button></div>\r\n");
-    
-    HttpAddText("<div>The leap seconds list is available <a href='https://www.ietf.org/timezones/data/leap-seconds.list' target='_blank'>here</a></div>\r\n");
-        
-    WebAddH2("Governer");
-    WebAddAjaxInput      ("Ppb"                     , 5, "ajax-ppb"          , "ppb"           );
-    WebAddAjaxInput      ("Ppb divisor"             , 5, "ajax-ppb-divisor"  , "ppbdivisor"    );
-    WebAddAjaxInput      ("Ppb max change"          , 5, "ajax-ppb-max-chg"  , "ppbmaxchange"  );
-    WebAddAjaxInput      ("Ppb synced limit"        , 5, "ajax-ppb-syn-lim"  , "syncedlimitppb");
-    WebAddAjaxInput      ("Ppb synced hysteresis"   , 5, "ajax-ppb-syn-hys"  , "syncedhysppb"  );
-    WebAddAjaxInput      ("Offset divisor"          , 5, "ajax-off-divisor"  , "slewdivisor"   );
-    WebAddAjaxInput      ("Offset max (ms)"         , 5, "ajax-off-max"      , "slewmax"       );
-    WebAddAjaxInput      ("Offset synced limit (ms)", 5, "ajax-off-syn-lim"  , "syncedlimitns" );
-    WebAddAjaxInput      ("Offset synced hys (ms)"  , 5, "ajax-off-syn-hys"  , "syncedhysns"   );
-    WebAddAjaxInput      ("Offset reset limit (s)"  , 5, "ajax-off-rst-lim"  , "maxoffsetsecs" );
-    WebAddAjaxInputToggle("Trace"                      , "ajax-gov-trace"    , "clockgovtrace" );
-
-    WebAddH2("NTP");
-    WebAddAjaxInput      ("Server url"              , 5, "ajax-ntp-server"   , "ntpserver"     );
-    WebAddAjaxInput      ("Initial interval (s)"    , 5, "ajax-ntp-initial"  , "clockinitial"  );
-    WebAddAjaxInput      ("Normal interval (m)"     , 5, "ajax-ntp-normal"   , "clocknormal"   );
-    WebAddAjaxInput      ("Retry interval (s)"      , 5, "ajax-ntp-retry"    , "clockretry"    );
-    WebAddAjaxInput      ("Offset (ms)"             , 5, "ajax-ntp-offset"   , "clockoffset"   );
-    WebAddAjaxInput      ("Max delay (ms)"          , 5, "ajax-ntp-max-delay", "clockmaxdelay" );
-
-    WebAddH2("Scan times");
-    WebAddAjaxLabelled   ("Program cycles avg", "ajax-scan-avg");
-    WebAddAjaxLabelled   ("Program cycles max", "ajax-scan-max");
-    WebAddAjaxLabelled   ("Program cycles min", "ajax-scan-min");
-
-    WebAddEnd();
-}
--- a/clock/web-clock-script.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#include "http.h"
-
-//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
-//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
-
-const char* WebClockScriptDate = __DATE__;
-const char* WebClockScriptTime = __TIME__;
-
-static const char* script =
-#include "web-clock-class.inc"
-#include "../base/web-ajax-class.inc"
-#include "web-clock-script.inc"
-;
-void WebClockScript()
-{
-    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebClockScriptDate, WebClockScriptTime);
-    HttpAddText(script);
-}
--- a/clock/web-clock-script.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-"//Clock script\n"
-"'use strict';\n"
-"\n"
-"let pseudo          = new Clock();\n"
-"let rtc             = new Clock();\n"
-"\n"
-"let pseudoDisplay   = false;\n"
-"let pseudoStartMs   = 0;\n"
-"\n"
-"let diffMs          = 0;\n"
-"let rtcIsSet        = false;\n"
-"let clockIsSet      = false;\n"
-"let sourceIsOk      = false;\n"
-"let rateIsLocked    = false;\n"
-"let timeIsLocked    = false;\n"
-"\n"
-"let ppb             = 0;\n"
-"let ppbdivisor      = 0;\n"
-"let ppbmaxchange    = 0;\n"
-"let syncedlimitppb  = 0;\n"
-"let syncedhysppb    = 0;\n"
-"let slewdivisor     = 0;\n"
-"let slewmax         = 0;\n"
-"let syncedlimitns   = 0;\n"
-"let syncedhysns     = 0;\n"
-"let maxoffsetsecs   = 0;\n"
-"let govTrace        = false;\n"
-"\n"
-"let ntpserver       = '';\n"
-"let ntpinitial      = 0;\n"
-"let ntpnormal       = 0;\n"
-"let ntpretry        = 0;\n"
-"let ntpoffset       = 0;\n"
-"let ntpmaxdelay     = 0;\n"
-"\n"
-"let scanavg         = 0;\n"
-"let scanmax         = 0;\n"
-"let scanmin         = 0;\n"
-"\n"
-"const DISPLAY_LEAP_MS = 10000;\n"
-"\n"
-"function parseLinesTime(text)\n"
-"{\n"
-"    let lines = text.split('\\n');\n"
-"    rtc.ms          = Ajax.date.getTime();\n"
-"    rtc.ms         += parseInt(lines[0], 16);\n"
-"    rtc.ms         -= Ajax.ms;\n"
-"    diffMs          = rtc.ms + Ajax.ms - Date.now();\n"
-"    rtcIsSet        = Ajax.hexToBit(lines[1], 0);\n"
-"    clockIsSet      = Ajax.hexToBit(lines[1], 1);\n"
-"    sourceIsOk      = Ajax.hexToBit(lines[1], 2);\n"
-"    rateIsLocked    = Ajax.hexToBit(lines[1], 3);\n"
-"    timeIsLocked    = Ajax.hexToBit(lines[1], 4);\n"
-"    rtc.leapEnable  = Ajax.hexToBit(lines[1], 5);\n"
-"    rtc.leapForward = Ajax.hexToBit(lines[1], 6);\n"
-"    govTrace        = Ajax.hexToBit(lines[1], 7);\n"
-"    rtc.months1970  = parseInt(lines[2], 16);\n"
-"    rtc.leaps       = parseInt(lines[3], 16);\n"
-"}\n"
-"function parseLinesGov(text)\n"
-"{\n"
-"    let lines = text.split('\\n');\n"
-"    ppb             = parseInt(lines[0], 16);\n"
-"    ppbdivisor      = parseInt(lines[1], 16);\n"
-"    ppbmaxchange    = parseInt(lines[2], 16);\n"
-"    syncedlimitppb  = parseInt(lines[3], 16);\n"
-"    syncedhysppb    = parseInt(lines[4], 16);\n"
-"    slewdivisor     = parseInt(lines[5], 16);\n"
-"    slewmax         = parseInt(lines[6], 16);\n"
-"    syncedlimitns   = parseInt(lines[7], 16);\n"
-"    syncedhysns     = parseInt(lines[8], 16);\n"
-"    maxoffsetsecs   = parseInt(lines[9], 16);\n"
-"}\n"
-"function parseLinesNtp(text)\n"
-"{\n"
-"    let lines = text.split('\\n');\n"
-"    ntpserver       =          lines[0];\n"
-"    ntpinitial      = parseInt(lines[1], 16);\n"
-"    ntpnormal       = parseInt(lines[2], 16);\n"
-"    ntpretry        = parseInt(lines[3], 16);\n"
-"    ntpoffset       = parseInt(lines[4], 16);\n"
-"    ntpmaxdelay     = parseInt(lines[5], 16);\n"
-"}\n"
-"function parseLinesScan(text)\n"
-"{\n"
-"    let lines = text.split('\\n');\n"
-"    scanavg         = parseInt(lines[0], 16);\n"
-"    scanmax         = parseInt(lines[1], 16);\n"
-"    scanmin         = parseInt(lines[2], 16);\n"
-"}\n"
-"function parse()\n"
-"{\n"
-"    let topics = Ajax.response.split('\\f');\n"
-"    parseLinesTime(topics[0]);\n"
-"    parseLinesGov (topics[1]);\n"
-"    parseLinesNtp (topics[2]);\n"
-"    parseLinesScan(topics[3]);\n"
-"}\n"
-"function display()\n"
-"{\n"
-"    let elem;\n"
-"    elem = Ajax.getElementOrNull('ajax-rtc-set'      ); if (elem) elem.setAttribute('dir', rtcIsSet     ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-clock-set'    ); if (elem) elem.setAttribute('dir', clockIsSet   ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-source-ok'    ); if (elem) elem.setAttribute('dir', sourceIsOk   ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-rate-locked'  ); if (elem) elem.setAttribute('dir', rateIsLocked ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-time-locked'  ); if (elem) elem.setAttribute('dir', timeIsLocked ? 'rtl' : 'ltr');\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-leap-enable'  ); if (elem) elem.setAttribute('dir', rtc.leapEnable   ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-leap-forward' ); if (elem) elem.setAttribute('dir', rtc.leapForward  ? 'rtl' : 'ltr');\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-leap-year'    ); if (elem) elem.value = rtc.months1970 ? rtc.leapYear  : '';\n"
-"    elem = Ajax.getElementOrNull('ajax-leap-month'   ); if (elem) elem.value = rtc.months1970 ? rtc.leapMonth : '';\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-leap-count'   ); if (elem) elem.value = rtc.leaps;\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-ppb'          ); if (elem) elem.value = ppb;\n"
-"    elem = Ajax.getElementOrNull('ajax-ppb-divisor'  ); if (elem) elem.value = ppbdivisor;\n"
-"    elem = Ajax.getElementOrNull('ajax-ppb-max-chg'  ); if (elem) elem.value = ppbmaxchange;\n"
-"    elem = Ajax.getElementOrNull('ajax-ppb-syn-lim'  ); if (elem) elem.value = syncedlimitppb;\n"
-"    elem = Ajax.getElementOrNull('ajax-ppb-syn-hys'  ); if (elem) elem.value = syncedhysppb;\n"
-"    elem = Ajax.getElementOrNull('ajax-off-divisor'  ); if (elem) elem.value = slewdivisor;\n"
-"    elem = Ajax.getElementOrNull('ajax-off-max'      ); if (elem) elem.value = slewmax;\n"
-"    elem = Ajax.getElementOrNull('ajax-off-syn-lim'  ); if (elem) elem.value = syncedlimitns / 1000000;\n"
-"    elem = Ajax.getElementOrNull('ajax-off-syn-hys'  ); if (elem) elem.value = syncedhysns   / 1000000;\n"
-"    elem = Ajax.getElementOrNull('ajax-off-rst-lim'  ); if (elem) elem.value = maxoffsetsecs;\n"
-"    elem = Ajax.getElementOrNull('ajax-gov-trace'    ); if (elem) elem.setAttribute('dir', govTrace     ? 'rtl' : 'ltr');\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-server'   ); if (elem) elem.value = ntpserver;\n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-initial'  ); if (elem) elem.value = ntpinitial;\n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-normal'   ); if (elem) elem.value = ntpnormal / 60;\n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-retry'    ); if (elem) elem.value = ntpretry;\n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-offset'   ); if (elem) elem.value = ntpoffset;\n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-max-delay'); if (elem) elem.value = ntpmaxdelay;\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-scan-avg'     ); if (elem) elem.textContent = scanavg;\n"
-"    elem = Ajax.getElementOrNull('ajax-scan-max'     ); if (elem) elem.textContent = scanmax;\n"
-"    elem = Ajax.getElementOrNull('ajax-scan-min'     ); if (elem) elem.textContent = scanmin;\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-date-diff'    ); if (elem) elem.textContent = diffMs;\n"
-"}\n"
-"\n"
-"function handleTick() //This typically called every 100ms\n"
-"{\n"
-"    if (pseudoDisplay)\n"
-"    {\n"
-"        pseudo.adjustLeap (Ajax.ms);\n"
-"        pseudo.displayTime(Ajax.ms);\n"
-"        if (Ajax.ms >= pseudoStartMs + DISPLAY_LEAP_MS + 500) pseudoDisplay = false;\n"
-"    }\n"
-"    else\n"
-"    {\n"
-"        rtc.adjustLeap (Ajax.ms);\n"
-"        rtc.displayTime(Ajax.ms);\n"
-"    }\n"
-"}\n"
-"\n"
-"function displayLeap() //Called by display leap button in HTML\n"
-"{\n"
-"   pseudoDisplay = true;\n"
-"   pseudoStartMs = Ajax.ms;\n"
-"   \n"
-"   pseudo.leapEnable  = true;\n"
-"   pseudo.leapForward = rtc.leapForward;\n"
-"   pseudo.leaps       = rtc.leaps;\n"
-"   pseudo.leapMonth   = rtc.leapMonth;\n"
-"   pseudo.leapYear    = rtc.leapYear;\n"
-"   pseudo.ms          = Date.UTC(rtc.leapYear, rtc.leapMonth - 1, 1) - DISPLAY_LEAP_MS / 2 - Ajax.ms;\n"
-"}\n"
-"Ajax.server     = '/clock-ajax';\n"
-"Ajax.onResponse = function() { parse(); display(); };\n"
-"Ajax.onTick     = handleTick;\n"
-"Ajax.init();"
\ No newline at end of file
--- a/clock/web-clock-script.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,172 +0,0 @@
-//Clock script
-'use strict';
-
-let pseudo          = new Clock();
-let rtc             = new Clock();
-
-let pseudoDisplay   = false;
-let pseudoStartMs   = 0;
-
-let diffMs          = 0;
-let rtcIsSet        = false;
-let clockIsSet      = false;
-let sourceIsOk      = false;
-let rateIsLocked    = false;
-let timeIsLocked    = false;
-
-let ppb             = 0;
-let ppbdivisor      = 0;
-let ppbmaxchange    = 0;
-let syncedlimitppb  = 0;
-let syncedhysppb    = 0;
-let slewdivisor     = 0;
-let slewmax         = 0;
-let syncedlimitns   = 0;
-let syncedhysns     = 0;
-let maxoffsetsecs   = 0;
-let govTrace        = false;
-
-let ntpserver       = '';
-let ntpinitial      = 0;
-let ntpnormal       = 0;
-let ntpretry        = 0;
-let ntpoffset       = 0;
-let ntpmaxdelay     = 0;
-
-let scanavg         = 0;
-let scanmax         = 0;
-let scanmin         = 0;
-
-const DISPLAY_LEAP_MS = 10000;
-
-function parseLinesTime(text)
-{
-    let lines = text.split('\n');
-    rtc.ms          = Ajax.date.getTime();
-    rtc.ms         += parseInt(lines[0], 16);
-    rtc.ms         -= Ajax.ms;
-    diffMs          = rtc.ms + Ajax.ms - Date.now();
-    rtcIsSet        = Ajax.hexToBit(lines[1], 0);
-    clockIsSet      = Ajax.hexToBit(lines[1], 1);
-    sourceIsOk      = Ajax.hexToBit(lines[1], 2);
-    rateIsLocked    = Ajax.hexToBit(lines[1], 3);
-    timeIsLocked    = Ajax.hexToBit(lines[1], 4);
-    rtc.leapEnable  = Ajax.hexToBit(lines[1], 5);
-    rtc.leapForward = Ajax.hexToBit(lines[1], 6);
-    govTrace        = Ajax.hexToBit(lines[1], 7);
-    rtc.months1970  = parseInt(lines[2], 16);
-    rtc.leaps       = parseInt(lines[3], 16);
-}
-function parseLinesGov(text)
-{
-    let lines = text.split('\n');
-    ppb             = parseInt(lines[0], 16);
-    ppbdivisor      = parseInt(lines[1], 16);
-    ppbmaxchange    = parseInt(lines[2], 16);
-    syncedlimitppb  = parseInt(lines[3], 16);
-    syncedhysppb    = parseInt(lines[4], 16);
-    slewdivisor     = parseInt(lines[5], 16);
-    slewmax         = parseInt(lines[6], 16);
-    syncedlimitns   = parseInt(lines[7], 16);
-    syncedhysns     = parseInt(lines[8], 16);
-    maxoffsetsecs   = parseInt(lines[9], 16);
-}
-function parseLinesNtp(text)
-{
-    let lines = text.split('\n');
-    ntpserver       =          lines[0];
-    ntpinitial      = parseInt(lines[1], 16);
-    ntpnormal       = parseInt(lines[2], 16);
-    ntpretry        = parseInt(lines[3], 16);
-    ntpoffset       = parseInt(lines[4], 16);
-    ntpmaxdelay     = parseInt(lines[5], 16);
-}
-function parseLinesScan(text)
-{
-    let lines = text.split('\n');
-    scanavg         = parseInt(lines[0], 16);
-    scanmax         = parseInt(lines[1], 16);
-    scanmin         = parseInt(lines[2], 16);
-}
-function parse()
-{
-    let topics = Ajax.response.split('\f');
-    parseLinesTime(topics[0]);
-    parseLinesGov (topics[1]);
-    parseLinesNtp (topics[2]);
-    parseLinesScan(topics[3]);
-}
-function display()
-{
-    let elem;
-    elem = Ajax.getElementOrNull('ajax-rtc-set'      ); if (elem) elem.setAttribute('dir', rtcIsSet     ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-clock-set'    ); if (elem) elem.setAttribute('dir', clockIsSet   ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-source-ok'    ); if (elem) elem.setAttribute('dir', sourceIsOk   ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-rate-locked'  ); if (elem) elem.setAttribute('dir', rateIsLocked ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-time-locked'  ); if (elem) elem.setAttribute('dir', timeIsLocked ? 'rtl' : 'ltr');
-    
-    elem = Ajax.getElementOrNull('ajax-leap-enable'  ); if (elem) elem.setAttribute('dir', rtc.leapEnable   ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-leap-forward' ); if (elem) elem.setAttribute('dir', rtc.leapForward  ? 'rtl' : 'ltr');
-    
-    elem = Ajax.getElementOrNull('ajax-leap-year'    ); if (elem) elem.value = rtc.months1970 ? rtc.leapYear  : '';
-    elem = Ajax.getElementOrNull('ajax-leap-month'   ); if (elem) elem.value = rtc.months1970 ? rtc.leapMonth : '';
-    
-    elem = Ajax.getElementOrNull('ajax-leap-count'   ); if (elem) elem.value = rtc.leaps;
-    
-    elem = Ajax.getElementOrNull('ajax-ppb'          ); if (elem) elem.value = ppb;
-    elem = Ajax.getElementOrNull('ajax-ppb-divisor'  ); if (elem) elem.value = ppbdivisor;
-    elem = Ajax.getElementOrNull('ajax-ppb-max-chg'  ); if (elem) elem.value = ppbmaxchange;
-    elem = Ajax.getElementOrNull('ajax-ppb-syn-lim'  ); if (elem) elem.value = syncedlimitppb;
-    elem = Ajax.getElementOrNull('ajax-ppb-syn-hys'  ); if (elem) elem.value = syncedhysppb;
-    elem = Ajax.getElementOrNull('ajax-off-divisor'  ); if (elem) elem.value = slewdivisor;
-    elem = Ajax.getElementOrNull('ajax-off-max'      ); if (elem) elem.value = slewmax;
-    elem = Ajax.getElementOrNull('ajax-off-syn-lim'  ); if (elem) elem.value = syncedlimitns / 1000000;
-    elem = Ajax.getElementOrNull('ajax-off-syn-hys'  ); if (elem) elem.value = syncedhysns   / 1000000;
-    elem = Ajax.getElementOrNull('ajax-off-rst-lim'  ); if (elem) elem.value = maxoffsetsecs;
-    elem = Ajax.getElementOrNull('ajax-gov-trace'    ); if (elem) elem.setAttribute('dir', govTrace     ? 'rtl' : 'ltr');
-    
-    elem = Ajax.getElementOrNull('ajax-ntp-server'   ); if (elem) elem.value = ntpserver;
-    elem = Ajax.getElementOrNull('ajax-ntp-initial'  ); if (elem) elem.value = ntpinitial;
-    elem = Ajax.getElementOrNull('ajax-ntp-normal'   ); if (elem) elem.value = ntpnormal / 60;
-    elem = Ajax.getElementOrNull('ajax-ntp-retry'    ); if (elem) elem.value = ntpretry;
-    elem = Ajax.getElementOrNull('ajax-ntp-offset'   ); if (elem) elem.value = ntpoffset;
-    elem = Ajax.getElementOrNull('ajax-ntp-max-delay'); if (elem) elem.value = ntpmaxdelay;
-    
-    elem = Ajax.getElementOrNull('ajax-scan-avg'     ); if (elem) elem.textContent = scanavg;
-    elem = Ajax.getElementOrNull('ajax-scan-max'     ); if (elem) elem.textContent = scanmax;
-    elem = Ajax.getElementOrNull('ajax-scan-min'     ); if (elem) elem.textContent = scanmin;
-    
-    elem = Ajax.getElementOrNull('ajax-date-diff'    ); if (elem) elem.textContent = diffMs;
-}
-
-function handleTick() //This typically called every 100ms
-{
-    if (pseudoDisplay)
-    {
-        pseudo.adjustLeap (Ajax.ms);
-        pseudo.displayTime(Ajax.ms);
-        if (Ajax.ms >= pseudoStartMs + DISPLAY_LEAP_MS + 500) pseudoDisplay = false;
-    }
-    else
-    {
-        rtc.adjustLeap (Ajax.ms);
-        rtc.displayTime(Ajax.ms);
-    }
-}
-
-function displayLeap() //Called by display leap button in HTML
-{
-   pseudoDisplay = true;
-   pseudoStartMs = Ajax.ms;
-   
-   pseudo.leapEnable  = true;
-   pseudo.leapForward = rtc.leapForward;
-   pseudo.leaps       = rtc.leaps;
-   pseudo.leapMonth   = rtc.leapMonth;
-   pseudo.leapYear    = rtc.leapYear;
-   pseudo.ms          = Date.UTC(rtc.leapYear, rtc.leapMonth - 1, 1) - DISPLAY_LEAP_MS / 2 - Ajax.ms;
-}
-Ajax.server     = '/clock-ajax';
-Ajax.onResponse = function() { parse(); display(); };
-Ajax.onTick     = handleTick;
-Ajax.init();
\ No newline at end of file
--- a/clock/web-web-query.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-#include "http.h"
-#include "clkgov.h"
-#include "clkutc.h"
-#include "led.h"
-#include "settings.h"
-
-void WebClockQuery(char* pQuery)
-{
-    while (pQuery)
-    {
-        char* pName;
-        char* pValue;
-        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
-        int value = HttpQueryValueAsInt(pValue);
-        
-        if (HttpSameStr(pName, "chg-clock-leap-enable" )) ClkUtcTglNextLeapEnable ();
-        if (HttpSameStr(pName, "chg-clock-leap-forward")) ClkUtcTglNextLeapForward();
-        
-        int months1970 = ClkUtcGetNextEpochMonth1970();
-        int months     = months1970 % 12;
-        int years      = months1970 / 12;
-        
-        if (HttpSameStr(pName, "set-clock-leap-year"    ))
-        {
-            years = value - 1970;
-            if (years < 0) years = 0;
-            ClkUtcSetNextEpochMonth1970(years * 12 + months);     
-        }
-        if (HttpSameStr(pName, "set-clock-leap-month"   ))
-        {
-            months = value - 1;
-            if (months < 0) months = 0;
-            ClkUtcSetNextEpochMonth1970(years * 12 + months);     
-        }
-        if (HttpSameStr(pName, "set-clock-leap-count"   ))
-        {
-            uint16_t leaps = value;
-            ClkUtcSetEpochOffset(leaps);     
-        }
-        
-        if (HttpSameStr(pName, "ppb"           )) ClkGovSetPpb               (value           );
-        if (HttpSameStr(pName, "slewdivisor"   )) SetClockSlewDivisor        (value           );
-        if (HttpSameStr(pName, "slewmax"       )) SetClockSlewMaxMs          (value           );
-        if (HttpSameStr(pName, "ppbdivisor"    )) SetClockPpbDivisor         (value           );
-        if (HttpSameStr(pName, "ppbmaxchange"  )) SetClockPpbChangeMax       (value           );
-        if (HttpSameStr(pName, "syncedlimitns" )) SetClockSyncedLimitNs      (value * 1000000 );
-        if (HttpSameStr(pName, "syncedhysns"   )) SetClockSyncedHysterisNs   (value * 1000000 );
-        if (HttpSameStr(pName, "syncedlimitppb")) SetClockSyncedLimitPpb     (value           );
-        if (HttpSameStr(pName, "syncedhysppb"  )) SetClockSyncedHysterisPpb  (value           );
-        if (HttpSameStr(pName, "maxoffsetsecs" )) SetClockMaxOffsetSecs      (value           );
-        if (HttpSameStr(pName, "clockgovtrace" )) ChgTraceSync();
-        
-        if (HttpSameStr(pName, "ntpserver"     )) SetNtpClientServerName     (pValue          );
-        if (HttpSameStr(pName, "clockinitial"  )) SetNtpClientInitialInterval(value           );
-        if (HttpSameStr(pName, "clocknormal"   )) SetNtpClientNormalInterval (value *      60 );
-        if (HttpSameStr(pName, "clockretry"    )) SetNtpClientRetryInterval  (value           );
-        if (HttpSameStr(pName, "clockoffset"   )) SetNtpClientOffsetMs       (value           );
-        if (HttpSameStr(pName, "clockmaxdelay" )) SetNtpClientMaxDelayMs     (value           );
-
-    }
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/web-add.c	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,215 @@
+#include <stdio.h>
+
+#include "http.h"
+#include "web-nav-base.h"
+#include "web-nav-derived.h"
+#include "web-site-name.h"
+#include "mac.h"
+#include "ip4addr.h"
+#include "ip6addr.h"
+
+void WebAddNavItem(int highlight, char* href, char* title)
+{
+    char *p;
+    HttpAddText("<li ");
+    if (highlight) p = "class='this'";
+    else           p = "            ";
+    HttpAddText(p);
+    HttpAddText("><a href='");
+    HttpAddText(href);
+    HttpAddText("'>");
+    HttpAddText(title);
+    HttpAddText("</a></li>\r\n");
+}
+void WebAddNav(int page)
+{
+    HttpAddText("<a class='tab-shortcut' href='#main-content'>Skip to content</a>\r\n");
+
+    HttpAddText("<nav><ul>\r\n");
+    WebNavDerived(page);
+    WebNavBase(page);
+    HttpAddText("</ul></nav>\r\n");
+}
+
+void WebAddHeader(const char* title, const char* style, const char* script)
+{
+    HttpAddText("<!DOCTYPE html>\r\n"
+                     "<html>\r\n"
+                     "<head>\r\n");
+    HttpAddText("   <title>");
+    HttpAddText(WEB_SITE_NAME);
+    if (title)
+    {
+        HttpAddText(" - ");
+        HttpAddText(title);
+    }
+    HttpAddText("</title>\r\n");
+
+    HttpAddText("   <link rel='stylesheet' href='/base.css' type='text/css'/>\r\n");
+    if (style)
+    {
+        HttpAddText("   <link rel='stylesheet' href='/");
+        HttpAddText(style);
+        HttpAddText("' type='text/css'/>\r\n");
+    }
+    if (script)
+    {
+        HttpAddText("   <script src='/");
+        HttpAddText(script);
+        HttpAddText("' type='text/javascript'></script>\r\n");
+    }
+    HttpAddText("   <meta name='viewport' content='width=device-width, initial-scale=1'>\r\n"
+                     "   <link rel='icon'       href='/favicon.ico' type='image/x-icon'/>\r\n"
+                     "</head>\r\n"
+                     "<body>\r\n");
+
+}
+void WebAddH1(const char* pageName)
+{
+    HttpAddText("<h1 id='main-content'>");
+    HttpAddText(WEB_SITE_NAME);
+    HttpAddText(" - ");
+    HttpAddText(pageName);
+    HttpAddText("</h1>\r\n");
+}
+void WebAddH2(const char* text)
+{
+    HttpAddText("<h2>");
+    HttpAddText(text);
+    HttpAddText("</h2>\r\n");
+}
+void WebAddEnd()
+{
+    HttpAddText("</body>\r\n"
+                "</html>\r\n");
+}
+
+void WebAddLabelledPrefixSuffix(char* label, char* prefix, char* text, char* suffix)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <div>%s%s%s</div>\r\n", prefix, text, suffix);
+    HttpAddText("</div>\r\n");
+}
+void WebAddLabelledText(char* label, char* text)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <div>%s</div>\r\n", text);
+    HttpAddText("</div>\r\n");
+}
+
+void WebAddLabelledMac(char* label, char* mac)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddText("  <div>"); MacHttp(mac); HttpAddText("</div>\r\n");
+    HttpAddText("</div>\r\n");
+}
+
+void WebAddLabelledIp4(char* label, uint32_t ip)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddText("  <div>"); Ip4AddressHttp(ip); HttpAddText("</div>\r\n");
+    HttpAddText("</div>\r\n");
+}
+
+void WebAddLabelledIp6(char* label, char* ip)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddText("  <div>"); Ip6AddressHttp(ip); HttpAddText("</div>\r\n");
+    HttpAddText("</div>\r\n");
+}
+void WebAddLabelledOnOff(char* label, bool value)
+{
+    if (value) WebAddLabelledText(label, "On");
+    else       WebAddLabelledText(label, "Off");
+}
+void WebAddLabelledInt(char* label, int value)
+{
+    char text[30];
+    snprintf(text, sizeof(text), "%8d", value); //Right align with enough spaces so that the length is always constant. 
+    WebAddLabelledText(label, text);
+}
+void WebAddInputText(char* label, float inputwidth, char* value, char* action, char* name)
+{
+    HttpAddF   ("<form action='%s' method='get'>\r\n", action);
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <input type='text' name='%s' style='width:%.1fem;' value='%s'>\r\n", name, inputwidth, value);
+    HttpAddText("</div>\r\n");
+    HttpAddText("<input type='submit' value='Set' style='display:none;'>\r\n");
+    HttpAddF   ("</form>\r\n");
+
+}
+void WebAddInputInt(char* label, float inputwidth, int value, char* action, char* name)
+{    
+    char text[30];
+    snprintf(text, sizeof(text), "%d", value);
+    WebAddInputText(label, inputwidth, text, action, name);
+}
+void WebAddInputButton(char* label, char* value, char* action, char* name)
+{
+    HttpAddF   ("<form action='%s' method='get'>\r\n", action);
+    HttpAddF   ("<input type='hidden' name='%s'>\r\n", name);
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <input type='submit' value='%s'>\r\n", value);
+    HttpAddText("</div>\r\n");
+    HttpAddText("</form>\r\n");
+}
+void WebAddAjaxInputToggle(char* label, char* id, char* name)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <div class='toggle' id='%s' tabindex='0' dir='ltr' onclick='AjaxRequest(\"%s=1\")' onkeydown='return event.keyCode != 13 || AjaxRequest(\"%s=1\")'>\r\n", id, name, name);
+    HttpAddText("    <div class='slot'></div><div class='knob'></div>\r\n");
+    HttpAddText("  </div>\r\n");
+    HttpAddText("</div>\r\n");
+}
+void WebAddAjaxLed(char* label, char* id)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <div class='led' id='%s' dir='ltr'></div>\r\n", id);
+    HttpAddText("</div>\r\n");
+}
+void WebAddAjaxInput(char* label, float inputwidth, char* id, char* name)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <input type='text' style='width:%.1fem;' id='%s' onchange='AjaxRequest(\"%s=\" + this.value)'>\r\n", inputwidth, id, name);
+    HttpAddText("</div>\r\n");
+}
+void WebAddAjaxInputSuffix(char* label, float inputwidth, char* id, char* name, char* suffix)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <input type='text' style='width:%.1fem;' id='%s' onchange='AjaxRequest(\"%s=\" + this.value)'>%s\r\n", inputwidth, id, name, suffix);
+    HttpAddText("</div>\r\n");
+}
+void WebAddAjaxLabelled(char* label, char* id)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <div id='%s'></div>\r\n", id);
+    HttpAddText("</div>\r\n");
+}
+void WebAddAjaxLabelledSuffix(char* label, char* id, char* suffix)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div>%s</div>\r\n", label);
+    HttpAddF   ("  <div><span id='%s'></span>%s</div>\r\n", id, suffix);
+    HttpAddText("</div>\r\n");
+}
+void WebAddAjaxInputLabelId(char* labelId, float inputwidth, char* id, char* name)
+{
+    HttpAddText("<div class='line'>\r\n");
+    HttpAddF   ("  <div id='%s'></div>\r\n", labelId);
+    HttpAddF   ("  <input type='text' style='width:%.1fem;' id='%s' onchange='AjaxRequest(\"%s=\" + this.value)'>\r\n", inputwidth, id, name);
+    HttpAddText("</div>\r\n");
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/web-add.h	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,29 @@
+#include <stdint.h>
+#include <stdbool.h>
+
+extern void WebAddNavItem      (int highlight, char* href, char* title);
+extern void WebAddNav          (int page);
+extern void WebAddHeader       (const char* title, const char* style, const char* script);
+extern void WebAddH1           (const char* pageName);
+extern void WebAddH2           (const char* text);
+extern void WebAddEnd          (void);
+
+extern void WebAddLabelledText        (char* label,                     char* text);
+extern void WebAddLabelledPrefixSuffix(char* label,   char* prefix,     char* text,  char* suffix);
+extern void WebAddLabelledMac         (char* label,                     char*   mac);
+extern void WebAddLabelledIp4         (char* label,                     uint32_t ip);
+extern void WebAddLabelledIp6         (char* label,                     char*    ip);
+extern void WebAddLabelledOnOff       (char* label,                     bool  value);
+extern void WebAddLabelledInt         (char* label,                     int   value);
+
+extern void WebAddInputText           (char* label,   float inputwidth, char* value, char* action, char* name);
+extern void WebAddInputInt            (char* label,   float inputwidth, int   value, char* action, char* name);
+extern void WebAddInputButton         (char* label,                     char* value, char* action, char* name);
+
+extern void WebAddAjaxLed             (char* label,                     char* id);
+extern void WebAddAjaxLabelled        (char* label,                     char* id);
+extern void WebAddAjaxLabelledSuffix  (char* label,                     char* id, char* suffix);
+extern void WebAddAjaxInputToggle     (char* label,                     char* id, char* name);
+extern void WebAddAjaxInput           (char* label,   float inputwidth, char* id, char* name);
+extern void WebAddAjaxInputSuffix     (char* label,   float inputwidth, char* id, char* name, char* suffix);
+extern void WebAddAjaxInputLabelId    (char* labelId, float inputwidth, char* id, char* name);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/web-ajax-class.inc	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,146 @@
+"//Ajax class\n"
+"'use strict';\n"
+"\n"
+"//Exposed properties\n"
+"let ajaxResponse_   = '';\n"
+"let ajaxHeaders_    = '';\n"
+"let ajaxDate_       = null;\n"
+"let ajaxMs_         =  0;\n"
+"let ajaxOnResponse_ = null;\n"
+"let ajaxOnTick_     = null;\n"
+"let ajaxServer_     = '';\n"
+"\n"
+"//Private variables\n"
+"let   ajaxOverrideBlockUpdateOnFocus_ = false;\n"
+"let   ajaxXhr_                        =  null;\n"
+"let   ajaxMsCountAtAjaxSend_          =     0;\n"
+"const ajaxTickMs_                     =   100;\n"
+"const ajaxUpdateMs_                   = 10000;\n"
+"\n"
+"//Private utilities\n"
+"function ajaxGetElementOrNull_(elementName) //Returns the element if it: exists; block is overidden; does not have focus\n"
+"{\n"
+"    let elem = document.getElementById(elementName);\n"
+"    if (!elem) return null;\n"
+"    if (ajaxOverrideBlockUpdateOnFocus_) return elem;\n"
+"    if (elem !== document.activeElement) return elem;\n"
+"    return null;\n"
+"}\n"
+"function ajaxHexToBit_(text, iBit)\n"
+"{\n"
+"   let value = parseInt(text, 16);\n"
+"   value >>= iBit;\n"
+"   return value & 1;\n"
+"}\n"
+"function ajaxHexToSignedInt8_(text)\n"
+"{\n"
+"    let value = parseInt(text, 16);\n"
+"    if (value < 0x80) return value;\n"
+"    return value - 0x100;\n"
+"}\n"
+"function ajaxHexToSignedInt16_(text)\n"
+"{\n"
+"    let value = parseInt(text, 16);\n"
+"    if (value < 0x8000) return value;\n"
+"    return value - 0x10000;\n"
+"}\n"
+"function ajaxHexToSignedInt32_(text)\n"
+"{\n"
+"    let value = parseInt(text, 16);\n"
+"    if (value < 0x80000000) return value;\n"
+"    return value - 0x100000000;\n"
+"}\n"
+"\n"
+"\n"
+"//Private ajax functions\n"
+"function ajaxHandleAjaxResponse_()\n"
+"{\n"
+"   if (ajaxXhr_.readyState == 4 && ajaxXhr_.status == 200)\n"
+"   {\n"
+"        ajaxResponse_ = ajaxXhr_.responseText;\n"
+"        ajaxHeaders_  = ajaxXhr_.getAllResponseHeaders();\n"
+"        let iDateStart = Ajax.headers.toLowerCase().indexOf('date:');\n"
+"        let iDateEnd   = Ajax.headers.indexOf('\\r', iDateStart);\n"
+"        ajaxDate_      = new Date(Ajax.headers.slice(iDateStart + 5, iDateEnd));\n"
+"\n"
+"        let elem;\n"
+"        elem = ajaxGetElementOrNull_('ajax-response'   ); if (elem) elem.textContent = ajaxResponse_;\n"
+"        elem = ajaxGetElementOrNull_('ajax-headers'    ); if (elem) elem.textContent = ajaxHeaders_;\n"
+"        elem = ajaxGetElementOrNull_('ajax-date-local' );\n"
+"        if (elem)\n"
+"        {\n"
+"            elem.textContent = ajaxDate_.toLocaleString(    undefined, {  weekday     : 'short'  ,\n"
+"                                                                          day         : '2-digit',\n"
+"                                                                          month       : 'short'  ,\n"
+"                                                                          year        : 'numeric',\n"
+"                                                                          hour        : '2-digit',\n"
+"                                                                          minute      : '2-digit',\n"
+"                                                                          timeZoneName: 'short'\n"
+"                                                                       }\n"
+"                                                       );\n"
+"        }\n"
+"        if (ajaxOnResponse_) ajaxOnResponse_();\n"
+"        ajaxOverrideBlockUpdateOnFocus_ = false; //Received response so reset override after display\n"
+"   }\n"
+"}\n"
+"function ajaxSendAjaxRequest_(request) //Used by this script and from HTML page\n"
+"{\n"
+"    ajaxXhr_ = new XMLHttpRequest();\n"
+"    ajaxXhr_.onreadystatechange = ajaxHandleAjaxResponse_;\n"
+"    if (request)\n"
+"    {\n"
+"        request = request.split('+').join('%2B');\n"
+"        ajaxXhr_.open('GET', ajaxServer_ + '?' + request, true);\n"
+"    }\n"
+"    else\n"
+"    {\n"
+"        ajaxXhr_.open('GET', ajaxServer_                , true);\n"
+"    }\n"
+"    ajaxXhr_.send();\n"
+"    ajaxMsCountAtAjaxSend_ = ajaxMs_;\n"
+"}\n"
+"function AjaxRequest(request) //From html\n"
+"{\n"
+"    ajaxOverrideBlockUpdateOnFocus_ = true; //Request has come from an update\n"
+"    ajaxSendAjaxRequest_(request);\n"
+"}\n"
+"\n"
+"//Private functions\n"
+"function ajaxTick_() //Called about every 100ms\n"
+"{\n"
+"    ajaxMs_ += ajaxTickMs_; //Don't use Date.now() as we don't know when the PC's clock will be updated around a leap second\n"
+"    if (ajaxMs_ >= ajaxMsCountAtAjaxSend_ + ajaxUpdateMs_) ajaxSendAjaxRequest_('');\n"
+"    if (ajaxOnTick_) ajaxOnTick_();\n"
+"}\n"
+"function ajaxInit_()\n"
+"{\n"
+"    setInterval(ajaxTick_, ajaxTickMs_);\n"
+"    ajaxSendAjaxRequest_('');\n"
+"}\n"
+"\n"
+"//Exposed public\n"
+"class Ajax\n"
+"{\n"
+"    static get ms        ()  { return ajaxMs_         ; }\n"
+"    static get response  ()  { return ajaxResponse_   ; }\n"
+"    static get headers   ()  { return ajaxHeaders_    ; }\n"
+"    static get date      ()  { return ajaxDate_       ; }\n"
+"    \n"
+"    static set tickMs    (v) { ajaxTickMs_     = v; }\n"
+"    static set updateMs  (v) { ajaxUpdateMs_   = v; }\n"
+"    static set server    (v) { ajaxServer_     = v; }\n"
+"    static set onResponse(v) { ajaxOnResponse_ = v; }\n"
+"    static set onTick    (v) { ajaxOnTick_     = v; }\n"
+"\n"
+"    static getElementOrNull(elementName) { return ajaxGetElementOrNull_(elementName) ; }\n"
+"    static hexToBit        (text, iBit ) { return ajaxHexToBit_        (text, iBit ) ; }\n"
+"    static hexToSignedInt8 (text       ) { return ajaxHexToSignedInt8_ (text       ) ; }\n"
+"    static hexToSignedInt16(text       ) { return ajaxHexToSignedInt16_(text       ) ; }\n"
+"    static hexToSignedInt32(text       ) { return ajaxHexToSignedInt32_(text       ) ; }\n"
+"    \n"
+"    static init()\n"
+"    {\n"
+"        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ajaxInit_ ); // Loading hasn't finished yet\n"
+"        else                                                                                 ajaxInit_(); //`DOMContentLoaded` has already fired\n"
+"    }\n"
+"}"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/web-ajax-class.js	Tue Apr 30 12:45:08 2019 +0000
@@ -0,0 +1,146 @@
+//Ajax class
+'use strict';
+
+//Exposed properties
+let ajaxResponse_   = '';
+let ajaxHeaders_    = '';
+let ajaxDate_       = null;
+let ajaxMs_         =  0;
+let ajaxOnResponse_ = null;
+let ajaxOnTick_     = null;
+let ajaxServer_     = '';
+
+//Private variables
+let   ajaxOverrideBlockUpdateOnFocus_ = false;
+let   ajaxXhr_                        =  null;
+let   ajaxMsCountAtAjaxSend_          =     0;
+const ajaxTickMs_                     =   100;
+const ajaxUpdateMs_                   = 10000;
+
+//Private utilities
+function ajaxGetElementOrNull_(elementName) //Returns the element if it: exists; block is overidden; does not have focus
+{
+    let elem = document.getElementById(elementName);
+    if (!elem) return null;
+    if (ajaxOverrideBlockUpdateOnFocus_) return elem;
+    if (elem !== document.activeElement) return elem;
+    return null;
+}
+function ajaxHexToBit_(text, iBit)
+{
+   let value = parseInt(text, 16);
+   value >>= iBit;
+   return value & 1;
+}
+function ajaxHexToSignedInt8_(text)
+{
+    let value = parseInt(text, 16);
+    if (value < 0x80) return value;
+    return value - 0x100;
+}
+function ajaxHexToSignedInt16_(text)
+{
+    let value = parseInt(text, 16);
+    if (value < 0x8000) return value;
+    return value - 0x10000;
+}
+function ajaxHexToSignedInt32_(text)
+{
+    let value = parseInt(text, 16);
+    if (value < 0x80000000) return value;
+    return value - 0x100000000;
+}
+
+
+//Private ajax functions
+function ajaxHandleAjaxResponse_()
+{
+   if (ajaxXhr_.readyState == 4 && ajaxXhr_.status == 200)
+   {
+        ajaxResponse_ = ajaxXhr_.responseText;
+        ajaxHeaders_  = ajaxXhr_.getAllResponseHeaders();
+        let iDateStart = Ajax.headers.toLowerCase().indexOf('date:');
+        let iDateEnd   = Ajax.headers.indexOf('\r', iDateStart);
+        ajaxDate_      = new Date(Ajax.headers.slice(iDateStart + 5, iDateEnd));
+
+        let elem;
+        elem = ajaxGetElementOrNull_('ajax-response'   ); if (elem) elem.textContent = ajaxResponse_;
+        elem = ajaxGetElementOrNull_('ajax-headers'    ); if (elem) elem.textContent = ajaxHeaders_;
+        elem = ajaxGetElementOrNull_('ajax-date-local' );
+        if (elem)
+        {
+            elem.textContent = ajaxDate_.toLocaleString(    undefined, {  weekday     : 'short'  ,
+                                                                          day         : '2-digit',
+                                                                          month       : 'short'  ,
+                                                                          year        : 'numeric',
+                                                                          hour        : '2-digit',
+                                                                          minute      : '2-digit',
+                                                                          timeZoneName: 'short'
+                                                                       }
+                                                       );
+        }
+        if (ajaxOnResponse_) ajaxOnResponse_();
+        ajaxOverrideBlockUpdateOnFocus_ = false; //Received response so reset override after display
+   }
+}
+function ajaxSendAjaxRequest_(request) //Used by this script and from HTML page
+{
+    ajaxXhr_ = new XMLHttpRequest();
+    ajaxXhr_.onreadystatechange = ajaxHandleAjaxResponse_;
+    if (request)
+    {
+        request = request.split('+').join('%2B');
+        ajaxXhr_.open('GET', ajaxServer_ + '?' + request, true);
+    }
+    else
+    {
+        ajaxXhr_.open('GET', ajaxServer_                , true);
+    }
+    ajaxXhr_.send();
+    ajaxMsCountAtAjaxSend_ = ajaxMs_;
+}
+function AjaxRequest(request) //From html
+{
+    ajaxOverrideBlockUpdateOnFocus_ = true; //Request has come from an update
+    ajaxSendAjaxRequest_(request);
+}
+
+//Private functions
+function ajaxTick_() //Called about every 100ms
+{
+    ajaxMs_ += ajaxTickMs_; //Don't use Date.now() as we don't know when the PC's clock will be updated around a leap second
+    if (ajaxMs_ >= ajaxMsCountAtAjaxSend_ + ajaxUpdateMs_) ajaxSendAjaxRequest_('');
+    if (ajaxOnTick_) ajaxOnTick_();
+}
+function ajaxInit_()
+{
+    setInterval(ajaxTick_, ajaxTickMs_);
+    ajaxSendAjaxRequest_('');
+}
+
+//Exposed public
+class Ajax
+{
+    static get ms        ()  { return ajaxMs_         ; }
+    static get response  ()  { return ajaxResponse_   ; }
+    static get headers   ()  { return ajaxHeaders_    ; }
+    static get date      ()  { return ajaxDate_       ; }
+    
+    static set tickMs    (v) { ajaxTickMs_     = v; }
+    static set updateMs  (v) { ajaxUpdateMs_   = v; }
+    static set server    (v) { ajaxServer_     = v; }
+    static set onResponse(v) { ajaxOnResponse_ = v; }
+    static set onTick    (v) { ajaxOnTick_     = v; }
+
+    static getElementOrNull(elementName) { return ajaxGetElementOrNull_(elementName) ; }
+    static hexToBit        (text, iBit ) { return ajaxHexToBit_        (text, iBit ) ; }
+    static hexToSignedInt8 (text       ) { return ajaxHexToSignedInt8_ (text       ) ; }
+    static hexToSignedInt16(text       ) { return ajaxHexToSignedInt16_(text       ) ; }
+    static hexToSignedInt32(text       ) { return ajaxHexToSignedInt32_(text       ) ; }
+    
+    static init()
+    {
+        if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ajaxInit_ ); // Loading hasn't finished yet
+        else                                                                                 ajaxInit_(); //`DOMContentLoaded` has already fired
+    }
+}
\ No newline at end of file
--- a/css/web-base-css.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-#include "http.h"
-
-static const char* cssBase =
-#include "web-base-css.inc"
-;
-const char* WebBaseCssDate = __DATE__;
-const char* WebBaseCssTime = __TIME__;
-
-void WebBaseCss()
-{
-    HttpOk("text/css; charset=UTF-8", "max-age=3600", WebBaseCssDate, WebBaseCssTime);
-    HttpAddText(cssBase);
-}
--- a/css/web-base-css.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-"                            * { font-size:6vw; box-sizing: border-box; }\r\n"
-"@media (min-width: 600px) { * { font-size:36px; } }\r\n"
-".line                     { display:flex; justify-content:space-between; align-items:center; width:15em; }\r\n"
-"\r\n"
-"*                                { font-family:Segoe UI, Calibri, Helvetica, Arial, sans-serif; }\r\n"
-"code, textarea, input[type=text] { font-family:Consolas, Lucida Console, Droid Sans Mono, Courier New, Courier, monospace; white-space:pre; }\r\n"
-"body                             { margin-left:0.1em; line-height:1.1em; padding:0; border:0; }\r\n"
-"h1                               { color:darkblue; font-size:120%; font-weight:normal; margin-bottom:0.3em; }\r\n"
-"h2                               { color:darkblue; font-size:110%; font-weight:normal; margin-bottom:0.3em; }\r\n"
-"a                                { text-decoration:none; }\r\n"
-"div                              { line-height:1.2em; }\r\n"
-"form                             { line-height:1.2em; margin:0; }\r\n"
-"button, input[type=submit]       { border-radius:0.2em; border:0; box-shadow:none; margin-top:0.3em; }\r\n"
-"button:hover             { color:red; }\r\n"
-"input[type=submit]:hover { color:red; }\r\n"
-"input[type=text]         { border:thin none gray; margin:0; padding:0; color:darkblue; text-align:right; }\r\n"
-"\r\n"
-".toggle                  { display:inline-block; position:relative; cursor:pointer; width: 1.2em;  height: 0.6em; }\r\n"
-".slot                    { background-color:lightsteelblue; display:block; position:absolute; top:0.1em; left:0.1em; bottom:0.1em; right:0.1em; border-radius:0.6em; transition:background 0.4s; }\r\n"
-".knob                    { background-color:steelblue;      display:block; position:absolute; top:0.0em; left:0.0em; bottom:0.0em; width:0.6em; border-radius:0.6em; transition:background 0.4s, margin 0.4s; }\r\n"
-".toggle[dir=rtl] > .slot { background-color:lightsteelblue; }\r\n"
-".toggle[dir=rtl] > .knob { background-color:royalblue;      margin-left:0.6em}\r\n"
-".led                     { background-color:lightgray; display:inline-block; width:0.6em; height:0.6em; border-radius:50%; }\r\n"
-".led[dir=rtl]            { background-color:royalblue; }\r\n"
-"\r\n"
-".hamburger { display:inline-block; width:1em}\r\n"
-".bar       { height:0.15em; background-color:#111; border-radius:0.15em; }\r\n"
-".space     { height:0.2em; }\r\n"
--- a/css/web-nav-css.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-#include "http.h"
-
-static const char* cssNav =
-#include "web-nav-css.inc"
-;
-const char* WebNavCssDate = __DATE__;
-const char* WebNavCssTime = __TIME__;
-
-void WebNavCss()
-{
-    HttpOk("text/css; charset=UTF-8", "max-age=3600", WebNavCssDate, WebNavCssTime);
-    HttpAddText(cssNav);
-}
--- a/css/web-nav-css.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-//Override the base font size 1vw = 1% width so for a width of 600px 4vw equates to 24px
-"                            * { font-size:4vw;  } \r\n"
-"@media (min-width: 600px) { * { font-size:24px; } }\r\n"
-
-//Override the left margin to fit the nav bar and make the line spacing smaller
-"body              { margin-left:6em; }\r\n"
-
-//Overide the line container to match
-".line             { width:17em; }\r\n"
-
-//Specify the tab access
-".tab-shortcut       { position:absolute; top:-1000em;}\r\n"
-".tab-shortcut:focus { position:fixed; top:0; left:0; z-index:999; padding:0.5em; background-color:darkblue; color:white; }\r\n"
-
-//Specify the nav bar itself
-"nav ul            { list-style-type:none; margin:0; padding:0; overflow:hidden; position:fixed; top:0; left:0; width:5em; font-size:1.2em; }\r\n"
-"nav ul li         { background:lightskyblue; padding:0; border-style:none; margin:0.2em; }\r\n"
-"nav ul li.this    { background:coral; }\r\n"
-"nav ul li a       { display:block; color:black; border:0;margin:0; padding:0.5em; }\r\n"
-"nav ul li a:hover { color:darkred; }\r\n"
--- a/fault/web-fault-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#include <stdio.h>
-
-#include "http.h"
-#include "web-page-base.h"
-#include "web-add.h"
-#include "fault.h"
-
-void WebFaultHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Fault", "settings.css", NULL);
-    WebAddNav(FAULT_PAGE);
-    WebAddH1("Fault");
-    
-    WebAddH2("Last fault");
-    int faultType = FaultTypeGet();
-    char text[20];
-    FaultTypeToString(faultType, sizeof(text), text);
-    WebAddLabelledText("Fault type", text);
-    if (faultType)
-    {
-        FaultZoneToString(FaultZoneGet(), sizeof(text), text);
-        WebAddLabelledText("Fault zone" , text);
-        WebAddLabelledInt ("After point", FaultPointGet());
-        WebAddInputButton ("Clear fault", "Clear", "/fault", "faultclear");
-    }
-    else
-    {
-        WebAddInputButton("Test fault"   ,  "Test",  "/fault", "faulttest");
-    }
-    
-    WebAddEnd();
-}
-
--- a/fault/web-fault-query.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-#include "http.h"
-#include "fault.h"
-
-void WebFaultQuery(char* pQuery)
-{
-    while (pQuery)
-    {
-        char* pName;
-        char* pValue;
-        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
-        
-        if (HttpSameStr(pName, "faultclear")) FaultReset();
-        
-        if (HttpSameStr(pName, "faulttest" ))
-        {
-            FaultPoint = 999;
-            *(volatile int *)0 = 0; //Dereferencing address 0 will hard fault the processor
-        }
-        
-    }
-}
--- a/favicon/web-favicon.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-#include "http.h"
-
-//Use http://tomeko.net/online_tools/file_to_hex.php to convert a favicon.ico into hex readable as an array
-static const char bytes[] = {
-#include "favicon/web-favicon.inc"
-};
-
-const char* WebFaviconDate = __DATE__;
-const char* WebFaviconTime = __TIME__;
-const int   WebFaviconSize = sizeof(bytes);
-
-void WebFavicon()
-{
-    HttpOk("image/x-icon", "max-age=3600", WebFaviconDate, WebFaviconTime);
-    HttpAddData(bytes, WebFaviconSize);
-}
--- a/firmware/web-firmware-ajax.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-#include   <stdio.h>
-
-#include "http.h"
-#include "web-base.h"
-#include "firmware.h"
-#include "web-firmware.h"
-
-void WebFirmwareAjax()
-{
-    //Header
-    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
-    
-    //Upload status
-    if (FirmwareSentLength == FirmwareFileLength)
-    {
-        HttpAddText("Length ok\r\n");
-    }
-    else
-    {
-        HttpAddText("Length error\r\n");
-        HttpAddF   ("  sent %6d\r\n", FirmwareSentLength);
-        HttpAddF   ("  rcvd %6d\r\n", FirmwareRcvdLength);
-        HttpAddF   ("  file %6d\r\n", FirmwareFileLength);
-    }
-    
-    //Save status
-    if (FirmwareFailed)
-    {
-        HttpAddText("Save failed - see log");
-    }
-    else
-    {
-        HttpAddText("Saved  ok");
-    }
-    
-    //Delimiter
-    HttpAddChar('\f');
-    
-    //New directory list
-    WebFirmwareListSemihostFiles();
-}
--- a/firmware/web-firmware-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#include <stdio.h>
-
-#include "http.h"
-#include "web-page-base.h"
-#include "web-add.h"
-#include "web-firmware.h"
-
-void WebFirmwareHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Firmware", "settings.css", "firmware.js");
-    WebAddNav(FIRMWARE_PAGE);
-    WebAddH1("Firmware");
-    
-    WebAddH2("Existing files on the device");
-
-    HttpAddText("<code id='list'>");
-    WebFirmwareListSemihostFiles();
-    HttpAddText("</code>\r\n");
-    
-    WebAddH2("Choose a local file");
-    HttpAddText("<div><input type='file' id='fileInput'/></div>\r\n");
-    
-    WebAddH2("Upload the local file to the device");
-    HttpAddText("<div><button onclick='startUpload();'>Upload</button></div>\r\n");
-    HttpAddText("<code id='uploadresult'></code>\r\n");
-    
-    WebAddH2("Restart the device");
-    HttpAddText("<div><button onclick='restart();'>Restart</button></div>\r\n");
-    HttpAddText("<code id='restartresult'></code>\r\n");
-
-
-    WebAddEnd();
-}
--- a/firmware/web-firmware-post.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-#include <stdint.h>
-#include <stdbool.h>
-
-#include "firmware.h"
-
-bool WebFirmwarePost (int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream)
-{
-    if (!positionInRequestStream)
-    {
-        FirmwareStart(contentLength);
-    }
-    char* pDataStart  = pRequestStream + contentStart;
-    int    dataLength =           size - contentStart;
-    return FirmwareAdd(pDataStart, dataLength);
-}
\ No newline at end of file
--- a/firmware/web-firmware-query.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-#include "http.h"
-#include "semihost.h"
-
-void WebFirmwareQuery(char* pQuery)
-{
-    while (pQuery)
-    {
-        char* pName;
-        char* pValue;
-        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
-        int value = HttpQueryValueAsInt(pValue);    
-        
-        if (HttpSameStr(pName, "restart" )) SemihostReset();
-    }
-}
--- a/firmware/web-firmware-script.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-#include "http.h"
-
-//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
-//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
-
-const char* WebFirmwareScriptDate = __DATE__;
-const char* WebFirmwareScriptTime = __TIME__;
-
-static const char* script =
-#include "web-firmware-script.inc"
-;
-void WebFirmwareScript()
-{
-    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebFirmwareScriptDate, WebFirmwareScriptTime);
-    HttpAddText(script);
-}
--- a/firmware/web-firmware-script.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-"'use strict';\n"
-"\n"
-"var file;\n"
-"var xhr;\n"
-"\n"
-"function logUpload(text)\n"
-"{\n"
-"    document.getElementById('uploadresult').textContent = text;\n"
-"}\n"
-"function xhrUploadResponse()\n"
-"{\n"
-"    var topics = xhr.responseText.split('\\f');\n"
-"    logUpload(topics[0]);\n"
-"    if (topics.length > 1) document.getElementById('list').textContent = topics[1];\n"
-"}\n"
-"function xhrUploadOnLoad()\n"
-"{\n"
-"    if (xhr.status == 200) xhrUploadResponse();\n"
-"    else                   logUpload('Upload failed');\n"
-"}\n"
-"function xhrUploadOnError()\n"
-"{\n"
-"    logUpload('Upload error');\n"
-"}\n"
-"function xhrUploadStart()\n"
-"{\n"
-"    logUpload('Uploading...');\n"
-"    \n"
-"    xhr = new XMLHttpRequest();\n"
-"\n"
-"    xhr.onload  = xhrUploadOnLoad;\n"
-"    xhr.onerror = xhrUploadOnError;\n"
-"\n"
-"    xhr.open('POST', '/firmware-ajax'); //Defaults to async=true\n"
-"    xhr.send(file);\n"
-"}\n"
-"\n"
-"function startUpload()\n"
-"{\n"
-"    var fileInput = document.getElementById('fileInput');\n"
-"\n"
-"    if (fileInput.files.length == 0)\n"
-"    {\n"
-"        logUpload('Please choose a file');\n"
-"        return;\n"
-"    }\n"
-"\n"
-"    if (fileInput.files.length > 1)\n"
-"    {\n"
-"        logUpload('Please choose just one file');\n"
-"        return;\n"
-"    }\n"
-"    \n"
-"    file = fileInput.files[0];\n"
-"    \n"
-"    xhrUploadStart();\n"
-"}\n"
-"function logRestart(text)\n"
-"{\n"
-"    document.getElementById('restartresult').textContent = text;\n"
-"}\n"
-"function redirect()\n"
-"{\n"
-"    location.href = '/firmware';\n"
-"}\n"
-"function xhrRestartOnLoad()\n"
-"{\n"
-"    if (xhr.status == 200) logRestart('Restart should never have returned');\n"
-"    else                   logRestart('Restart failed');\n"
-"}\n"
-"function xhrRestartStart()\n"
-"{\n"
-"    logRestart('Restarting...');\n"
-"    \n"
-"    xhr = new XMLHttpRequest();\n"
-"\n"
-"    xhr.onload  = xhrRestartOnLoad;\n"
-"\n"
-"    xhr.open('GET', '/firmware-ajax?restart='); //Defaults to async=true\n"
-"    xhr.send();\n"
-"    \n"
-"    setTimeout(redirect, 2000);\n"
-"}\n"
-"function restart()\n"
-"{\n"
-"    xhrRestartStart();\n"
-"}\n"
-""
\ No newline at end of file
--- a/firmware/web-firmware-script.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-'use strict';
-
-var file;
-var xhr;
-
-function logUpload(text)
-{
-    document.getElementById('uploadresult').textContent = text;
-}
-function xhrUploadResponse()
-{
-    var topics = xhr.responseText.split('\f');
-    logUpload(topics[0]);
-    if (topics.length > 1) document.getElementById('list').textContent = topics[1];
-}
-function xhrUploadOnLoad()
-{
-    if (xhr.status == 200) xhrUploadResponse();
-    else                   logUpload('Upload failed');
-}
-function xhrUploadOnError()
-{
-    logUpload('Upload error');
-}
-function xhrUploadStart()
-{
-    logUpload('Uploading...');
-    
-    xhr = new XMLHttpRequest();
-
-    xhr.onload  = xhrUploadOnLoad;
-    xhr.onerror = xhrUploadOnError;
-
-    xhr.open('POST', '/firmware-ajax'); //Defaults to async=true
-    xhr.send(file);
-}
-
-function startUpload()
-{
-    var fileInput = document.getElementById('fileInput');
-
-    if (fileInput.files.length == 0)
-    {
-        logUpload('Please choose a file');
-        return;
-    }
-
-    if (fileInput.files.length > 1)
-    {
-        logUpload('Please choose just one file');
-        return;
-    }
-    
-    file = fileInput.files[0];
-    
-    xhrUploadStart();
-}
-function logRestart(text)
-{
-    document.getElementById('restartresult').textContent = text;
-}
-function redirect()
-{
-    location.href = '/firmware';
-}
-function xhrRestartOnLoad()
-{
-    if (xhr.status == 200) logRestart('Restart should never have returned');
-    else                   logRestart('Restart failed');
-}
-function xhrRestartStart()
-{
-    logRestart('Restarting...');
-    
-    xhr = new XMLHttpRequest();
-
-    xhr.onload  = xhrRestartOnLoad;
-
-    xhr.open('GET', '/firmware-ajax?restart='); //Defaults to async=true
-    xhr.send();
-    
-    setTimeout(redirect, 2000);
-}
-function restart()
-{
-    xhrRestartStart();
-}
--- a/firmware/web-firmware.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-#include "semihost.h"
-#include "http.h"
-
-void WebFirmwareListSemihostFiles()
-{
-    XFINFO info;
-    info.fileID = 0;
-    while (SemihostXffind ("*.*", &info) == 0)
-    {
-        HttpAddF("%13s %7d bytes\r\n", info.name, info.size);
-    }
-    if (info.fileID == 0)
-    {
-        HttpAddText("No files\r\n");
-    }
-}
\ No newline at end of file
--- a/firmware/web-firmware.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-extern void WebFirmwareListSemihostFiles(void);
\ No newline at end of file
--- a/log/web-log-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-#include "http.h"
-#include "web-page-base.h"
-#include "web-add.h"
-#include "log.h"
-
-void WebLogHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Log", "settings.css", NULL);
-    WebAddNav(LOG_PAGE);
-    WebAddH1("Log");
-    
-                 WebAddInputButton("Existing content",      "Clear",    "/log", "clearlog"    );
-    if (LogUart) WebAddInputButton("Output to uart is on",  "Turn off", "/log", "chg-log-uart");
-    else         WebAddInputButton("Output to uart is off", "Turn on",  "/log", "chg-log-uart");
-    
-    HttpAddText("<code>");
-    HttpAddStream(LogEnumerateStart, LogEnumerate);
-    HttpAddText("</code>");
-    
-    WebAddEnd();
-}
--- a/log/web-log-query.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-#include "http.h"
-#include "log.h"
-#include "settings.h"
-
-void WebLogQuery(char* pQuery)
-{
-    while (pQuery)
-    {
-        char* pName;
-        char* pValue;
-        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);                    
-        if (HttpSameStr(pName, "clearlog"    )) LogClear();
-        if (HttpSameStr(pName, "chg-log-uart")) ChgLogUart();
-    }
-}
--- a/login/web-login-css.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-"                            * { font-size:4vw; box-sizing: border-box; }\r\n"
-"@media (min-width: 600px) { * { font-size:24px; } }\r\n"
-"\r\n"
-"*                        { font-family:'Segoe UI', Calibri, Arial, sans-serif; }\r\n"
-"*:focus                  { outline: 0; }\r\n"
-"body                     { margin-left:0.1em; line-height:1.1em; padding:0; border:0; }\r\n"
-"h1                       { color:darkblue; font-size:120%; font-weight:normal; margin-bottom:0.3em; }\r\n"
-"h2                       { color:darkblue; font-size:110%; font-weight:normal; margin-bottom:0.3em; }\r\n"
-"div                      { line-height:1.2em; }\r\n"
-"form                     { line-height:1.2em; margin:0; }\r\n"
-"input[type=text]         { border:thin solid gray; border-radius:0.2em; margin:0; padding:0.1em; width:9em; text-align:left}\r\n"
-"input[type=submit]       { display:none; }\r\n"
\ No newline at end of file
--- a/login/web-login-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-#include "http.h"
-#include "web-add.h"
-#include "web-login.h"
-#include "web-base.h"
-#include "web-page-base.h"
-
-void WebLoginHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Login", NULL, NULL);
-    HttpAddText(
-"<style>"
-#include "web-login-css.inc"
-"</style>"
-    );
-    WebAddH1("Login");
-    if (WebLoginPasswordIsSet())
-    {
-        WebAddH2("Welcome - please enter the password");
-    }
-    else
-    {
-        WebAddH2("Please enter a new password following user reset");
-        HttpAddText("<p>Be careful to make it the one people expect!</p>");
-    }
-    
-    HttpAddText("<form action='/login' method='get' autocomplete='off'>\r\n");
-    HttpAddText("  <div style='width:8em; display:inline-block;'>Password</div>\r\n");
-    HttpAddF   ("  <input type='hidden' name='todo'     value='%d'>\r\n", WebLoginOriginalToDo);
-    HttpAddText("  <input type='text'   name='password' value='' autofocus>\r\n");
-    HttpAddText("  <input type='submit'                 value='' >\r\n");
-    HttpAddF   ("</form>\r\n");
-
-    WebAddEnd();
-}
-
-/*
-Device sends request for resource to server
-Server cannot validate the session cookie (or there isn't one) so sends login form with original resource number as a hidden input
-Device does a GET for /login with query containing password and original resource
-Server validates password and sends original resource with a valid session cookie.
-*/
\ No newline at end of file
--- a/login/web-login-password.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,52 +0,0 @@
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "http.h"
-
-#define GPREG2 (*((volatile unsigned *) 0x4002404C))
-
-static uint32_t hash;
-static bool hashIsSet = false;
-
-void  WebLoginPasswordRestore()
-{
-    hash = GPREG2;
-    hashIsSet = true;
-}
-
-uint32_t hasher(char *p) //Jenkins 'one at a time' hash
-{
-    uint32_t h = 0;
-    
-    while (*p)
-    {
-        h += *p;
-        h += (h << 10);
-        h ^= (h >> 6);
-        p++;
-    }
-    h += (h << 3);
-    h ^= (h >> 11);
-    h += (h << 15);
-        
-    return h;
-}
-bool WebLoginPasswordIsSet()
-{
-    return hashIsSet;
-}
-void WebLoginPasswordSet(char* password)
-{
-    if (!password)  return;
-    if (!*password) return;
-    hash = hasher(password);
-    GPREG2 = hash;
-    hashIsSet = true;
-}
-bool WebLoginPasswordMatches(char* password)
-{
-    if (!hashIsSet) return false;
-    if (!password)  return false;
-    if (!*password) return false;
-    return hash == hasher(password);
-}
--- a/login/web-login-query.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-
-#include "http.h"
-#include "web-base.h"
-#include "web-login.h"
-
-bool WebLoginQueryPasswordOk = false;
-
-void WebLoginQuery(char* pQuery)
-{
-    WebLoginQueryPasswordOk = false;
-    
-    while (pQuery)
-    {
-        char* pName;
-        char* pValue;
-        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
-        
-        int value = HttpQueryValueAsInt(pValue);
-        if (HttpSameStr(pName, "todo"     )) WebLoginOriginalToDo = value;
-        
-        HttpQueryUnencode(pValue);
-        if (HttpSameStr(pName, "password" ))
-        {
-            if (!WebLoginPasswordIsSet()) //This is if there has been a normal reset: not a fault nor a power on.
-            {
-                WebLoginPasswordSet(pValue);
-                WebLoginQueryPasswordOk = true;
-            }
-            else
-            {
-                WebLoginQueryPasswordOk = WebLoginPasswordMatches(pValue);
-            }
-        }
-    }
-}
--- a/login/web-login-session-id.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-#include <stdbool.h>
-#include <stdint.h>
-
-#include "random.h"
-
-#define SESSION_ID_BIT_LENGTH 36
-
-static char sessionId[(SESSION_ID_BIT_LENGTH + 5) / 6 + 1]; //Bit lengths not divisible by 6 require an extra space
-
-void WebLoginSessionIdNew()
-{
-    char acc = 0;
-    
-    for (int i = 0; i < SESSION_ID_BIT_LENGTH; i++)
-    {
-        int     srcByte = i / 8; 
-        int     srcBit  = i - srcByte * 8;
-        uint8_t srcMask = 1 << srcBit;
-        
-        int     dstByte = i / 6;
-        int     dstBit  = i - dstByte * 6;
-        uint8_t dstMask = 1 << dstBit;
-        
-        //Reset the accumulator to zero at start of a 6 bit word
-        if (!dstBit) acc = 0;
-        
-        //Add the bit to the accumulator
-        if (RandomBytes[srcByte] & srcMask) acc |= dstMask;
-        
-        //Convert the accumulator to base64 and store in the session Id
-        if (dstBit == 5 || i == SESSION_ID_BIT_LENGTH - 1)
-        {
-            if      (acc < 26) sessionId[dstByte] = acc -  0 + 'A';
-            else if (acc < 52) sessionId[dstByte] = acc - 26 + 'a';
-            else if (acc < 62) sessionId[dstByte] = acc - 52 + '0';
-            else if (acc < 63) sessionId[dstByte] =            '+';
-            else               sessionId[dstByte] =            '/';
-        }
-    }
-    sessionId[(SESSION_ID_BIT_LENGTH + 5) / 6] = 0;
-}
-
-bool WebLoginSessionIdIsSet()
-{
-    return sessionId[0];
-}
-char* WebLoginSessionIdGet()
-{   
-    return sessionId;
-}
--- a/login/web-login-session-name.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-#include "web-site-name.h"
-
-#define SESSION_LIFE 10 * 365 * 24 * 60 * 60
-
-//Do not use ' ', ';', '=' as they are part of the cookie protocol: name1=value1; name2=value2; name3=value3 etc
-static char sessionName[9]; //Limit the name to 8 characters (plus terminating zero) to keep things quick
-
-char* WebLoginSessionNameGet()
-{
-    return sessionName;
-}
-
-void WebLoginSessionNameCreate()
-{
-    //Make the session name from the site name - eg  "Heating" and "GPS Clock" will be "heat_sid" and "gpsc_sid"
-    int i = 0;
-    int j = 0;
-    while (1)
-    {
-        if (i >= sizeof(sessionName) - 4 - 1) break; //Leave room for the "_sid" and the terminating NUL character
-        char c = WEB_SITE_NAME[j++];
-        if (!c) break;                               //Stop if run out of site name
-        if (c >= 'A' && c <= 'Z') c |= 0x20;         //Make lower case
-        if (c < 'a' || c > 'z') continue;            //Skip anything other than letters
-        sessionName[i++] = c;                        //Add the first characters of the site name for which there is room
-    }
-    for (int k = 0; k < 4; k++)                      //Add "_sid"
-    {
-        sessionName[i++] = "_sid"[k];
-    }
-    sessionName[i] = 0;                              //Add the terminating NUL character
-}
-
-int WebLoginSessionNameLife()
-{
-    return SESSION_LIFE;
-}
--- a/login/web-login.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-
-#include "web-base.h"
-#include "web-login.h"
-#include "http.h"
-#include "fault.h"
-
-int WebLoginOriginalToDo = 0;
-
-bool WebLoginCookiesContainValidSessionId(char* pCookies)
-{
-    if (!WebLoginSessionIdIsSet()) return false;
-    
-    while (pCookies)
-    {
-        char* pName;
-        char* pValue;
-        pCookies = HttpCookiesSplit(pCookies, &pName, &pValue);
-        
-        if (HttpSameStr(pName, WebLoginSessionNameGet())) //HttpSameStr handles NULLs correctly
-        {
-            if (HttpSameStr(pValue, WebLoginSessionIdGet())) return true;
-        }
-    }
-    return false;
-}
-void WebLoginInit()
-{
-    if (FaultTypeGet()) WebLoginPasswordRestore();
-    WebLoginSessionNameCreate();
-}
\ No newline at end of file
--- a/login/web-login.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-#include <stdbool.h>
-
-extern void  WebLoginSessionNameCreate(void);
-
-extern void  WebLoginPasswordRestore  (void);
-extern void  WebLoginPasswordSet      (char* password);
-extern bool  WebLoginPasswordIsSet    (void);
-extern bool  WebLoginPasswordMatches  (char* password);
\ No newline at end of file
--- a/net-trace/web-trace-ajax.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-#include  <stdint.h>
-
-#include       "http.h"
-#include   "web-base.h"
-#include        "log.h"
-#include        "net.h"
-#include       "link.h"
-#include        "dns.h"
-#include    "dnsname.h"
-#include   "dnsquery.h"
-#include   "dnsreply.h"
-#include  "dnsserver.h"
-#include        "ntp.h"
-#include       "dhcp.h"
-#include         "ns.h"
-#include        "nr4.h"
-#include        "nr6.h"
-#include      "echo4.h"
-#include      "echo6.h"
-#include      "dest6.h"
-#include         "ra.h"
-#include         "rs.h"
-#include        "ar4.h"
-#include        "ar6.h"
-#include        "arp.h"
-#include        "ip4.h"
-#include        "ip6.h"
-#include        "udp.h"
-#include        "tcp.h"
-#include       "http.h"
-#include       "tftp.h"
-#include  "ntpclient.h"
-
-void WebTraceAjax()
-{
-    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
-    char nibble;
-    
-    nibble = 0; //0
-    if ( DnsSendRequestsViaIp4) nibble |= 2;
-    if ( NtpClientQuerySendRequestsViaIp4) nibble |= 4;
-    if (TftpSendRequestsViaIp4) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    HttpAddByteAsHex(NetTraceHost[0]);  //1, 2
-    HttpAddByteAsHex(NetTraceHost[1]);  //3, 4
-    
-    nibble = 0; //5
-    if (NetTraceStack   ) nibble |= 1;
-    if (NetTraceNewLine ) nibble |= 2;
-    if (NetTraceVerbose ) nibble |= 4;
-    if (LinkTrace       ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //6
-    if (DnsNameTrace    ) nibble |= 1;
-    if (DnsQueryTrace   ) nibble |= 2;
-    if (DnsReplyTrace   ) nibble |= 4;
-    if (DnsServerTrace  ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //7
-    if (NtpTrace        ) nibble |= 1;
-    if (DhcpTrace       ) nibble |= 2;
-    if (NsTraceRecvSol  ) nibble |= 4;
-    if (NsTraceRecvAdv  ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //8
-    if (NsTraceSendSol  ) nibble |= 1;
-    if (Nr4Trace        ) nibble |= 2;
-    if (Nr6Trace        ) nibble |= 4;
-    if (NtpClientTrace  ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //9
-    if (Echo4Trace      ) nibble |= 4;
-    if (Echo6Trace      ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //10
-    if (Dest6Trace      ) nibble |= 1;
-    if (RaTrace         ) nibble |= 2;
-    if (RsTrace         ) nibble |= 4;
-    if (Ar4Trace        ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //11
-    if (Ar6Trace        ) nibble |= 1;
-    if (ArpTrace        ) nibble |= 2;
-    if (Ip4Trace        ) nibble |= 4;
-    if (Ip6Trace        ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-    
-    nibble = 0; //12
-    if (UdpTrace        ) nibble |= 1;
-    if (TcpTrace        ) nibble |= 2;
-    if (HttpTrace       ) nibble |= 4;
-    if (TftpTrace       ) nibble |= 8;
-    HttpAddNibbleAsHex(nibble);
-}
-
--- a/net-trace/web-trace-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-#include "web-add.h"
-#include "web-page-base.h"
-#include "http.h"
-
-void WebTraceHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Net Trace", "settings.css", "trace.js");
-    WebAddNav(TRACE_PAGE);
-    WebAddH1("Net Trace");
-    
-    WebAddH2("General");
-    WebAddAjaxInput      ("Trace host"      , 5  , "ajax-trace-net-host"   , "set-trace-net-host"   );
-    WebAddAjaxInputToggle("Trace stack"          , "ajax-trace-net-stack"  , "chg-trace-net-stack"  );
-    WebAddAjaxInputToggle("Trace new line"       , "ajax-trace-net-newline", "chg-trace-net-newline");
-    WebAddAjaxInputToggle("Trace verbose"        , "ajax-trace-net-verbose", "chg-trace-net-verbose");
-    WebAddH2("Net");
-    WebAddAjaxInputToggle("MAC"                  , "ajax-trace-link"       , "chg-trace-link"       );
-    WebAddAjaxInputToggle("Ip4 filtered"         , "ajax-trace-ip4"        , "chg-trace-ip4"        );
-    WebAddAjaxInputToggle("Ip6 filtered"         , "ajax-trace-ip6"        , "chg-trace-ip6"        );
-    WebAddAjaxInputToggle("Udp filtered"         , "ajax-trace-udp"        , "chg-trace-udp"        );
-    WebAddAjaxInputToggle("Tcp filtered"         , "ajax-trace-tcp"        , "chg-trace-tcp"        );
-    WebAddAjaxInputToggle("Echo4 (ping4)"        , "ajax-trace-echo4"      , "chg-trace-echo4"      );
-    WebAddAjaxInputToggle("Echo6 (ping6)"        , "ajax-trace-echo6"      , "chg-trace-echo6"      );
-    WebAddAjaxInputToggle("Dest6 unreacheable"   , "ajax-trace-dest6"      , "chg-trace-dest6"      );
-    WebAddAjaxInputToggle("HTTP"                 , "ajax-trace-http"       , "chg-trace-http"       );
-    WebAddAjaxInputToggle("TFTP"                 , "ajax-trace-tftp"       , "chg-trace-tftp"       );
-    WebAddH2("Send requests via IPv4");
-    WebAddAjaxInputToggle("DNS request via IPv4" , "ajax-trace-dns-ip4"    , "chg-send-dns-ip4"     );
-    WebAddAjaxInputToggle("NTP request via IPv4" , "ajax-trace-ntp-ip4"    , "chg-send-ntp-ip4"     );
-    WebAddAjaxInputToggle("TFTP request via IPv4", "ajax-trace-tftp-ip4"   , "chg-send-tftp-ip4"    );
-    WebAddH2("Router Resolution");
-    WebAddAjaxInputToggle("Router advertise"     , "ajax-trace-ra"         , "chg-trace-ra"         );
-    WebAddAjaxInputToggle("Router solicit"       , "ajax-trace-rs"         , "chg-trace-rs"         );
-    WebAddAjaxInputToggle("DHCP"                 , "ajax-trace-dhcp"       , "chg-trace-dhcp"       );
-    WebAddH2("Address Resolution");
-    WebAddAjaxInputToggle("IP4 cache"            , "ajax-trace-ar4"        , "chg-trace-ar4"        );
-    WebAddAjaxInputToggle("IP6 cache"            , "ajax-trace-ar6"        , "chg-trace-ar6"        );
-    WebAddAjaxInputToggle("ARP"                  , "ajax-trace-arp"        , "chg-trace-arp"        );
-    WebAddAjaxInputToggle("NS server"            , "ajax-trace-ns-recv-sol", "chg-trace-ns-recv-sol");
-    WebAddAjaxInputToggle("NS client reply"      , "ajax-trace-ns-recv-adv", "chg-trace-ns-recv-adv");
-    WebAddAjaxInputToggle("NS client query"      , "ajax-trace-ns-send-sol", "chg-trace-ns-send-sol");
-    WebAddH2("Name Resolution");
-    WebAddAjaxInputToggle("IP4 cache"            , "ajax-trace-nr4"        , "chg-trace-nr4"        );
-    WebAddAjaxInputToggle("IP6 cache"            , "ajax-trace-nr6"        , "chg-trace-nr6"        );
-    WebAddAjaxInputToggle("DNS name"             , "ajax-trace-dns-name"   , "chg-trace-dns-name"   );
-    WebAddAjaxInputToggle("DNS client query"     , "ajax-trace-dns-query"  , "chg-trace-dns-query"  );
-    WebAddAjaxInputToggle("DNS client reply"     , "ajax-trace-dns-reply"  , "chg-trace-dns-reply"  );
-    WebAddAjaxInputToggle("DNS server"           , "ajax-trace-dns-server" , "chg-trace-dns-server" );
-    WebAddH2("NTP");
-    WebAddAjaxInputToggle("NTP"                  , "ajax-trace-ntp"        , "chg-trace-ntp"        );
-    WebAddAjaxInputToggle("NTP client"           , "ajax-trace-ntp-client" , "chg-trace-ntp-client" );
-    
-    WebAddEnd();
-}
--- a/net-trace/web-trace-query.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-#include "http.h"
-#include "settings.h"
-
-void WebTraceQuery(char* pQuery)
-{
-    while (pQuery)
-    {
-        char* pName;
-        char* pValue;
-        pQuery = HttpQuerySplit(pQuery, &pName, &pValue);
-        
-        if (HttpSameStr(pName, "chg-send-dns-ip4"      )) ChgDnsSendRequestsViaIp4();
-        if (HttpSameStr(pName, "chg-send-ntp-ip4"      )) ChgNtpSendRequestsViaIp4();
-        if (HttpSameStr(pName, "chg-send-tftp-ip4"     )) ChgTftpSendRequestsViaIp4();
-        if (HttpSameStr(pName, "set-trace-net-host"    )) SetTraceNetHost(pValue);                    
-        if (HttpSameStr(pName, "chg-trace-net-stack"   )) ChgTraceNetStack();
-        if (HttpSameStr(pName, "chg-trace-net-newline" )) ChgTraceNetNewLine();
-        if (HttpSameStr(pName, "chg-trace-net-verbose" )) ChgTraceNetVerbose();
-        if (HttpSameStr(pName, "chg-trace-link"        )) ChgTraceLink();
-        if (HttpSameStr(pName, "chg-trace-dns-name"    )) ChgTraceDnsName();
-        if (HttpSameStr(pName, "chg-trace-dns-query"   )) ChgTraceDnsQuery();
-        if (HttpSameStr(pName, "chg-trace-dns-reply"   )) ChgTraceDnsReply();
-        if (HttpSameStr(pName, "chg-trace-dns-server"  )) ChgTraceDnsServer();
-        if (HttpSameStr(pName, "chg-trace-ntp"         )) ChgTraceNtp();
-        if (HttpSameStr(pName, "chg-trace-dhcp"        )) ChgTraceDhcp();
-        if (HttpSameStr(pName, "chg-trace-ns-recv-sol" )) ChgTraceNsRecvSol();
-        if (HttpSameStr(pName, "chg-trace-ns-recv-adv" )) ChgTraceNsRecvAdv();
-        if (HttpSameStr(pName, "chg-trace-ns-send-sol" )) ChgTraceNsSendSol();
-        if (HttpSameStr(pName, "chg-trace-nr4"         )) ChgTraceNr4();
-        if (HttpSameStr(pName, "chg-trace-nr6"         )) ChgTraceNr6();
-        if (HttpSameStr(pName, "chg-trace-ntp-client"  )) ChgTraceNtpClient();
-        if (HttpSameStr(pName, "chg-trace-sync"        )) ChgTraceSync();
-        if (HttpSameStr(pName, "chg-trace-echo4"       )) ChgTraceEcho4();
-        if (HttpSameStr(pName, "chg-trace-echo6"       )) ChgTraceEcho6();
-        if (HttpSameStr(pName, "chg-trace-dest6"       )) ChgTraceDest6();
-        if (HttpSameStr(pName, "chg-trace-ra"          )) ChgTraceRa();
-        if (HttpSameStr(pName, "chg-trace-rs"          )) ChgTraceRs();
-        if (HttpSameStr(pName, "chg-trace-ar4"         )) ChgTraceAr4();
-        if (HttpSameStr(pName, "chg-trace-ar6"         )) ChgTraceAr6();
-        if (HttpSameStr(pName, "chg-trace-arp"         )) ChgTraceArp();
-        if (HttpSameStr(pName, "chg-trace-ip4"         )) ChgTraceIp4();
-        if (HttpSameStr(pName, "chg-trace-ip6"         )) ChgTraceIp6();
-        if (HttpSameStr(pName, "chg-trace-udp"         )) ChgTraceUdp();
-        if (HttpSameStr(pName, "chg-trace-tcp"         )) ChgTraceTcp();
-        if (HttpSameStr(pName, "chg-trace-http"        )) ChgTraceHttp();
-        if (HttpSameStr(pName, "chg-trace-tftp"        )) ChgTraceTftp();
-    }
-}
-
--- a/net-trace/web-trace-script.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-#include "http.h"
-
-//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
-//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
-
-const char* WebTraceScriptDate = __DATE__;
-const char* WebTraceScriptTime = __TIME__;
-
-static const char* script =
-#include "../base/web-ajax-class.inc"
-#include "web-trace-script.inc"
-;
-void WebTraceScript()
-{
-    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebTraceScriptDate, WebTraceScriptTime);
-    HttpAddText(script);
-}
--- a/net-trace/web-trace-script.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-"//Net trace script\n"
-"'use strict';\n"
-"function setDirection(elem, iChar, iBit)\n"
-"{\n"
-"    elem.setAttribute('dir', Ajax.hexToBit(Ajax.response.charAt(iChar), iBit) ? 'rtl' : 'ltr');\n"
-"}\n"
-"function display()\n"
-"{\n"
-"   var elem;\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dns-ip4'     ); if (elem) setDirection(elem,  0, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ntp-ip4'     ); if (elem) setDirection(elem,  0, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-tftp-ip4'    ); if (elem) setDirection(elem,  0, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-net-host'    ); if (elem) elem.value = Ajax.response.substr( 1, 4);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-net-stack'   ); if (elem) setDirection(elem,  5, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-net-newline' ); if (elem) setDirection(elem,  5, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-net-verbose' ); if (elem) setDirection(elem,  5, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-link'        ); if (elem) setDirection(elem,  5, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dns-name'    ); if (elem) setDirection(elem,  6, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dns-query'   ); if (elem) setDirection(elem,  6, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dns-reply'   ); if (elem) setDirection(elem,  6, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dns-server'  ); if (elem) setDirection(elem,  6, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ntp'         ); if (elem) setDirection(elem,  7, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dhcp'        ); if (elem) setDirection(elem,  7, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-sol' ); if (elem) setDirection(elem,  7, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-adv' ); if (elem) setDirection(elem,  7, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ns-send-sol' ); if (elem) setDirection(elem,  8, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-nr4'         ); if (elem) setDirection(elem,  8, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-nr6'         ); if (elem) setDirection(elem,  8, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ntp-client'  ); if (elem) setDirection(elem,  8, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-echo4'       ); if (elem) setDirection(elem,  9, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-echo6'       ); if (elem) setDirection(elem,  9, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-dest6'       ); if (elem) setDirection(elem, 10, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ra'          ); if (elem) setDirection(elem, 10, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-rs'          ); if (elem) setDirection(elem, 10, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ar4'         ); if (elem) setDirection(elem, 10, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ar6'         ); if (elem) setDirection(elem, 11, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-arp'         ); if (elem) setDirection(elem, 11, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ip4'         ); if (elem) setDirection(elem, 11, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-ip6'         ); if (elem) setDirection(elem, 11, 3);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-udp'         ); if (elem) setDirection(elem, 12, 0);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-tcp'         ); if (elem) setDirection(elem, 12, 1);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-http'        ); if (elem) setDirection(elem, 12, 2);\n"
-"   elem = Ajax.getElementOrNull('ajax-trace-tftp'        ); if (elem) setDirection(elem, 12, 3);\n"
-"}\n"
-"\n"
-"Ajax.server     = '/trace-ajax';\n"
-"Ajax.onResponse = display;\n"
-"Ajax.init();"
\ No newline at end of file
--- a/net-trace/web-trace-script.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-//Net trace script
-'use strict';
-function setDirection(elem, iChar, iBit)
-{
-    elem.setAttribute('dir', Ajax.hexToBit(Ajax.response.charAt(iChar), iBit) ? 'rtl' : 'ltr');
-}
-function display()
-{
-   var elem;
-   elem = Ajax.getElementOrNull('ajax-trace-dns-ip4'     ); if (elem) setDirection(elem,  0, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-ntp-ip4'     ); if (elem) setDirection(elem,  0, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-tftp-ip4'    ); if (elem) setDirection(elem,  0, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-net-host'    ); if (elem) elem.value = Ajax.response.substr( 1, 4);
-   elem = Ajax.getElementOrNull('ajax-trace-net-stack'   ); if (elem) setDirection(elem,  5, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-net-newline' ); if (elem) setDirection(elem,  5, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-net-verbose' ); if (elem) setDirection(elem,  5, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-link'        ); if (elem) setDirection(elem,  5, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-dns-name'    ); if (elem) setDirection(elem,  6, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-dns-query'   ); if (elem) setDirection(elem,  6, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-dns-reply'   ); if (elem) setDirection(elem,  6, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-dns-server'  ); if (elem) setDirection(elem,  6, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-ntp'         ); if (elem) setDirection(elem,  7, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-dhcp'        ); if (elem) setDirection(elem,  7, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-sol' ); if (elem) setDirection(elem,  7, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-ns-recv-adv' ); if (elem) setDirection(elem,  7, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-ns-send-sol' ); if (elem) setDirection(elem,  8, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-nr4'         ); if (elem) setDirection(elem,  8, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-nr6'         ); if (elem) setDirection(elem,  8, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-ntp-client'  ); if (elem) setDirection(elem,  8, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-echo4'       ); if (elem) setDirection(elem,  9, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-echo6'       ); if (elem) setDirection(elem,  9, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-dest6'       ); if (elem) setDirection(elem, 10, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-ra'          ); if (elem) setDirection(elem, 10, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-rs'          ); if (elem) setDirection(elem, 10, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-ar4'         ); if (elem) setDirection(elem, 10, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-ar6'         ); if (elem) setDirection(elem, 11, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-arp'         ); if (elem) setDirection(elem, 11, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-ip4'         ); if (elem) setDirection(elem, 11, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-ip6'         ); if (elem) setDirection(elem, 11, 3);
-   elem = Ajax.getElementOrNull('ajax-trace-udp'         ); if (elem) setDirection(elem, 12, 0);
-   elem = Ajax.getElementOrNull('ajax-trace-tcp'         ); if (elem) setDirection(elem, 12, 1);
-   elem = Ajax.getElementOrNull('ajax-trace-http'        ); if (elem) setDirection(elem, 12, 2);
-   elem = Ajax.getElementOrNull('ajax-trace-tftp'        ); if (elem) setDirection(elem, 12, 3);
-}
-
-Ajax.server     = '/trace-ajax';
-Ajax.onResponse = display;
-Ajax.init();
\ No newline at end of file
--- a/net/web-net-class.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-"//Net class\n"
-"'use strict';\n"
-"\n"
-"class Net\n"
-"{\n"
-"    static makeIp4(text)\n"
-"    {\n"
-"        let result = '';\n"
-"        result += parseInt(text.substr(6, 2), 16).toString();\n"
-"        result += '.';\n"
-"        result += parseInt(text.substr(4, 2), 16).toString();\n"
-"        result += '.';\n"
-"        result += parseInt(text.substr(2, 2), 16).toString();\n"
-"        result += '.';\n"
-"        result += parseInt(text.substr(0, 2), 16).toString();\n"
-"        return result;\n"
-"    }\n"
-"    static makeMac(text)\n"
-"    {\n"
-"        text = text.toLowerCase();\n"
-"        let result = '';\n"
-"        result += text.substr( 0, 2);\n"
-"        result += ':';\n"
-"        result += text.substr( 2, 2);\n"
-"        result += ':';\n"
-"        result += text.substr( 4, 2);\n"
-"        result += ':';\n"
-"        result += text.substr( 6, 2);\n"
-"        result += ':';\n"
-"        result += text.substr( 8, 2);\n"
-"        result += ':';\n"
-"        result += text.substr(10, 2);\n"
-"        return result;\n"
-"    }\n"
-"\n"
-"    static hexToBit(text, iBit)\n"
-"    {\n"
-"       let value = parseInt(text, 16);\n"
-"       value >>= iBit;\n"
-"       return value & 1;\n"
-"    }\n"
-"    static makeIp6(text)\n"
-"    {\n"
-"        function makeWord(text)\n"
-"        {\n"
-"            let word = parseInt(text, 16);\n"
-"            if (word === 0) return '';\n"
-"            return word.toString(16);\n"
-"        }\n"
-"        text = text.toLowerCase();\n"
-"        let result = '';\n"
-"        result += makeWord(text.substr( 0, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr( 4, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr( 8, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr(12, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr(16, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr(20, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr(24, 4));\n"
-"        result += ':';\n"
-"        result += makeWord(text.substr(28, 4));\n"
-"        return result;\n"
-"    }\n"
-"}"
\ No newline at end of file
--- a/net/web-net-class.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-//Net class
-'use strict';
-
-class Net
-{
-    static makeIp4(text)
-    {
-        let result = '';
-        result += parseInt(text.substr(6, 2), 16).toString();
-        result += '.';
-        result += parseInt(text.substr(4, 2), 16).toString();
-        result += '.';
-        result += parseInt(text.substr(2, 2), 16).toString();
-        result += '.';
-        result += parseInt(text.substr(0, 2), 16).toString();
-        return result;
-    }
-    static makeMac(text)
-    {
-        text = text.toLowerCase();
-        let result = '';
-        result += text.substr( 0, 2);
-        result += ':';
-        result += text.substr( 2, 2);
-        result += ':';
-        result += text.substr( 4, 2);
-        result += ':';
-        result += text.substr( 6, 2);
-        result += ':';
-        result += text.substr( 8, 2);
-        result += ':';
-        result += text.substr(10, 2);
-        return result;
-    }
-
-    static hexToBit(text, iBit)
-    {
-       let value = parseInt(text, 16);
-       value >>= iBit;
-       return value & 1;
-    }
-    static makeIp6(text)
-    {
-        function makeWord(text)
-        {
-            let word = parseInt(text, 16);
-            if (word === 0) return '';
-            return word.toString(16);
-        }
-        text = text.toLowerCase();
-        let result = '';
-        result += makeWord(text.substr( 0, 4));
-        result += ':';
-        result += makeWord(text.substr( 4, 4));
-        result += ':';
-        result += makeWord(text.substr( 8, 4));
-        result += ':';
-        result += makeWord(text.substr(12, 4));
-        result += ':';
-        result += makeWord(text.substr(16, 4));
-        result += ':';
-        result += makeWord(text.substr(20, 4));
-        result += ':';
-        result += makeWord(text.substr(24, 4));
-        result += ':';
-        result += makeWord(text.substr(28, 4));
-        return result;
-    }
-}
\ No newline at end of file
--- a/net/web-net-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#include "http.h"
-#include "web-page-base.h"
-#include "web-add.h"
-#include "mac.h"
-
-void WebNetHtml()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Net", "settings.css", NULL);
-    WebAddNav(NET_PAGE);
-    WebAddH1("Net");
-                     
-    WebAddH2("Ethernet");
-    WebAddLabelledMac  ("MAC",                   MacLocal);
-    
-    WebAddEnd();
-                        
-}
--- a/net/web-net4-ajax.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-#include   <stdio.h>
-
-#include "http.h"
-#include "web-base.h"
-#include "ar4.h"
-#include "nr4.h"
-#include "dhcp.h"
-
-void WebNet4Ajax()
-{
-    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
-    
-    HttpAddInt32AsHex(DhcpLocalIp);          HttpAddChar('\n');
-    HttpAddText      (DhcpDomainName);       HttpAddChar('\n');
-    HttpAddText      (DhcpHostName);         HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpNtpIp);            HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpDnsServerIp);      HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpServerIp);         HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpRouterIp);         HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpSubnetMask);       HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpBroadcastIp);      HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpLeaseTime);        HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpRenewalT1);        HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpRenewalT2);        HttpAddChar('\n');
-    HttpAddInt32AsHex(DhcpGetElapsedLife()); HttpAddChar('\n');
-    HttpAddChar('\f');
-    
-    Ar4SendAjax();
-    HttpAddChar('\f');
-    
-    Nr4SendAjax();
-}
--- a/net/web-net4-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#include "http.h"
-#include "web-add.h"
-#include "web-page-base.h"
-
-void WebNet4Html()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Net IPv4", "settings.css", "net4.js");
-    WebAddNav(NET4_PAGE);
-    WebAddH1("Net IPv4");
-    
-    WebAddH2("ARP");
-    HttpAddText("<code id='ajax-arp'></code>\r\n");
-    WebAddH2("DNS");
-    HttpAddText("<code id='ajax-dns'></code>\r\n");
-    
-    WebAddH2("DHCP");
-    WebAddAjaxLabelled("IP4 address",   "ajax-local-ip"    );
-    WebAddAjaxLabelled("Domain",        "ajax-domain-name" );
-    WebAddAjaxLabelled("Host name",     "ajax-host-name"   );
-    WebAddAjaxLabelled("NTP server",    "ajax-ntp-ip"      );
-    WebAddAjaxLabelled("DNS server",    "ajax-dns-ip"      );
-    WebAddAjaxLabelled("DHCP server",   "ajax-dhcp-ip"     );
-    WebAddAjaxLabelled("Router",        "ajax-router-ip"   );
-    WebAddAjaxLabelled("Subnet mask",   "ajax-subnet-mask" );
-    WebAddAjaxLabelled("Broadcast IP",  "ajax-broadcast-ip");
-    WebAddAjaxLabelled("Lease time IP", "ajax-lease-time"  );
-    WebAddAjaxLabelled("Renewal T1",    "ajax-renewal-t1"  );
-    WebAddAjaxLabelled("Renewal T2",    "ajax-renewal-t2"  );
-    WebAddAjaxLabelled("Elapsed",       "ajax-elapsed"     );
-    
-    WebAddEnd();
-                        
-}
--- a/net/web-net4-script.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#include "http.h"
-
-//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
-//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
-
-const char* WebNet4ScriptDate = __DATE__;
-const char* WebNet4ScriptTime = __TIME__;
-
-static const char* script =
-#include "../base/web-ajax-class.inc"
-#include "web-net-class.inc"
-#include "web-net4-script.inc"
-;
-void WebNet4Script()
-{
-    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebNet4ScriptDate, WebNet4ScriptTime);
-    HttpAddText(script);
-}
--- a/net/web-net4-script.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-"//Net4 script\n"
-"'use strict';\n"
-"\n"
-"let localIp     = '';\n"
-"let domainName  = '';\n"
-"let hostName    = '';\n"
-"let ntpIp       = '';\n"
-"let dnsIp       = '';\n"
-"let dhcpIp      = '';\n"
-"let routerIp    = '';\n"
-"let subnetMask  = '';\n"
-"let broadcastIp = '';\n"
-"let leaseTime   = '';\n"
-"let renewalT1   = '';\n"
-"let renewalt2   = '';\n"
-"let elapsed     = '';\n"
-"let arp         = '';\n"
-"let dns         = '';\n"
-"\n"
-"function parseArpLine(line)\n"
-"{\n"
-"    if (line.length == 0) return;\n"
-"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
-"    arp += Math.floor(minutes).toString().padStart(4, ' ');\n"
-"    arp += ' ';\n"
-"    arp += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');\n"
-"    arp += ' ';\n"
-"    arp += Net.makeMac(line.substr(16, 12));\n"
-"    arp += '\\r\\n';\n"
-"}\n"
-"function parseDnsLine(line)\n"
-"{\n"
-"    if (line.length == 0) return;\n"
-"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
-"    dns += Math.floor(minutes).toString().padStart(4, ' ');\n"
-"    dns += ' ';\n"
-"    dns += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');\n"
-"    dns += ' ';\n"
-"    dns += line.substr(16, 1);\n"
-"    dns += ' ';\n"
-"    dns += line.substr(17);\n"
-"    dns += '\\r\\n';\n"
-"}\n"
-"function parseArpLines(text)\n"
-"{\n"
-"    arp = '';\n"
-"    text.split('\\n').forEach(parseArpLine);\n"
-"}\n"
-"function parseDnsLines(text)\n"
-"{\n"
-"    dns = '';\n"
-"    text.split('\\n').forEach(parseDnsLine);\n"
-"}\n"
-"function parseGenLines(text)\n"
-"{\n"
-"    let lines = text.split('\\n');\n"
-"    localIp     = Net.makeIp4(lines[ 0])    ;\n"
-"    domainName  =             lines[ 1]     ;\n"
-"    hostName    =             lines[ 2]     ;\n"
-"    ntpIp       = Net.makeIp4(lines[ 3])    ;\n"
-"    dnsIp       = Net.makeIp4(lines[ 4])    ;\n"
-"    dhcpIp      = Net.makeIp4(lines[ 5])    ;\n"
-"    routerIp    = Net.makeIp4(lines[ 6])    ;\n"
-"    subnetMask  = Net.makeIp4(lines[ 7])    ;\n"
-"    broadcastIp = Net.makeIp4(lines[ 8])    ;\n"
-"    leaseTime   =    parseInt(lines[ 9], 16);\n"
-"    renewalT1   =    parseInt(lines[10], 16);\n"
-"    renewalt2   =    parseInt(lines[11], 16);\n"
-"    elapsed     =    parseInt(lines[12], 16);\n"
-"}\n"
-"function parse()\n"
-"{\n"
-"    let topics = Ajax.response.split('\\f');\n"
-"    parseGenLines(topics[0]);\n"
-"    parseArpLines(topics[1]);\n"
-"    parseDnsLines(topics[2]);\n"
-"}\n"
-"function display()\n"
-"{\n"
-"    let elem;\n"
-"\n"
-"    elem = Ajax.getElementOrNull('ajax-local-ip'    ); if (elem) elem.textContent = localIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-domain-name' ); if (elem) elem.textContent = domainName;\n"
-"    elem = Ajax.getElementOrNull('ajax-host-name'   ); if (elem) elem.textContent = hostName;\n"
-"    elem = Ajax.getElementOrNull('ajax-ntp-ip'      ); if (elem) elem.textContent = ntpIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-dns-ip'      ); if (elem) elem.textContent = dnsIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-dhcp-ip'     ); if (elem) elem.textContent = dhcpIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-router-ip'   ); if (elem) elem.textContent = routerIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-subnet-mask' ); if (elem) elem.textContent = subnetMask;\n"
-"    elem = Ajax.getElementOrNull('ajax-broadcast-ip'); if (elem) elem.textContent = broadcastIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-lease-time'  ); if (elem) elem.textContent = leaseTime;\n"
-"    elem = Ajax.getElementOrNull('ajax-renewal-t1'  ); if (elem) elem.textContent = renewalT1;\n"
-"    elem = Ajax.getElementOrNull('ajax-renewal-t2'  ); if (elem) elem.textContent = renewalt2;\n"
-"    elem = Ajax.getElementOrNull('ajax-elapsed'     ); if (elem) elem.textContent = elapsed;\n"
-"    elem = Ajax.getElementOrNull('ajax-arp'         ); if (elem) elem.textContent = arp;\n"
-"    elem = Ajax.getElementOrNull('ajax-dns'         ); if (elem) elem.textContent = dns;\n"
-"}\n"
-"\n"
-"Ajax.server     = '/net4-ajax';\n"
-"Ajax.onResponse = function() { parse(); display(); };\n"
-"Ajax.init();\n"
-""
\ No newline at end of file
--- a/net/web-net4-script.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,101 +0,0 @@
-//Net4 script
-'use strict';
-
-let localIp     = '';
-let domainName  = '';
-let hostName    = '';
-let ntpIp       = '';
-let dnsIp       = '';
-let dhcpIp      = '';
-let routerIp    = '';
-let subnetMask  = '';
-let broadcastIp = '';
-let leaseTime   = '';
-let renewalT1   = '';
-let renewalt2   = '';
-let elapsed     = '';
-let arp         = '';
-let dns         = '';
-
-function parseArpLine(line)
-{
-    if (line.length == 0) return;
-    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
-    arp += Math.floor(minutes).toString().padStart(4, ' ');
-    arp += ' ';
-    arp += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');
-    arp += ' ';
-    arp += Net.makeMac(line.substr(16, 12));
-    arp += '\r\n';
-}
-function parseDnsLine(line)
-{
-    if (line.length == 0) return;
-    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
-    dns += Math.floor(minutes).toString().padStart(4, ' ');
-    dns += ' ';
-    dns += Net.makeIp4(line.substr(8, 8)).padEnd(15, ' ');
-    dns += ' ';
-    dns += line.substr(16, 1);
-    dns += ' ';
-    dns += line.substr(17);
-    dns += '\r\n';
-}
-function parseArpLines(text)
-{
-    arp = '';
-    text.split('\n').forEach(parseArpLine);
-}
-function parseDnsLines(text)
-{
-    dns = '';
-    text.split('\n').forEach(parseDnsLine);
-}
-function parseGenLines(text)
-{
-    let lines = text.split('\n');
-    localIp     = Net.makeIp4(lines[ 0])    ;
-    domainName  =             lines[ 1]     ;
-    hostName    =             lines[ 2]     ;
-    ntpIp       = Net.makeIp4(lines[ 3])    ;
-    dnsIp       = Net.makeIp4(lines[ 4])    ;
-    dhcpIp      = Net.makeIp4(lines[ 5])    ;
-    routerIp    = Net.makeIp4(lines[ 6])    ;
-    subnetMask  = Net.makeIp4(lines[ 7])    ;
-    broadcastIp = Net.makeIp4(lines[ 8])    ;
-    leaseTime   =    parseInt(lines[ 9], 16);
-    renewalT1   =    parseInt(lines[10], 16);
-    renewalt2   =    parseInt(lines[11], 16);
-    elapsed     =    parseInt(lines[12], 16);
-}
-function parse()
-{
-    let topics = Ajax.response.split('\f');
-    parseGenLines(topics[0]);
-    parseArpLines(topics[1]);
-    parseDnsLines(topics[2]);
-}
-function display()
-{
-    let elem;
-
-    elem = Ajax.getElementOrNull('ajax-local-ip'    ); if (elem) elem.textContent = localIp;
-    elem = Ajax.getElementOrNull('ajax-domain-name' ); if (elem) elem.textContent = domainName;
-    elem = Ajax.getElementOrNull('ajax-host-name'   ); if (elem) elem.textContent = hostName;
-    elem = Ajax.getElementOrNull('ajax-ntp-ip'      ); if (elem) elem.textContent = ntpIp;
-    elem = Ajax.getElementOrNull('ajax-dns-ip'      ); if (elem) elem.textContent = dnsIp;
-    elem = Ajax.getElementOrNull('ajax-dhcp-ip'     ); if (elem) elem.textContent = dhcpIp;
-    elem = Ajax.getElementOrNull('ajax-router-ip'   ); if (elem) elem.textContent = routerIp;
-    elem = Ajax.getElementOrNull('ajax-subnet-mask' ); if (elem) elem.textContent = subnetMask;
-    elem = Ajax.getElementOrNull('ajax-broadcast-ip'); if (elem) elem.textContent = broadcastIp;
-    elem = Ajax.getElementOrNull('ajax-lease-time'  ); if (elem) elem.textContent = leaseTime;
-    elem = Ajax.getElementOrNull('ajax-renewal-t1'  ); if (elem) elem.textContent = renewalT1;
-    elem = Ajax.getElementOrNull('ajax-renewal-t2'  ); if (elem) elem.textContent = renewalt2;
-    elem = Ajax.getElementOrNull('ajax-elapsed'     ); if (elem) elem.textContent = elapsed;
-    elem = Ajax.getElementOrNull('ajax-arp'         ); if (elem) elem.textContent = arp;
-    elem = Ajax.getElementOrNull('ajax-dns'         ); if (elem) elem.textContent = dns;
-}
-
-Ajax.server     = '/net4-ajax';
-Ajax.onResponse = function() { parse(); display(); };
-Ajax.init();
--- a/net/web-net6-ajax.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-#include   <stdio.h>
-
-#include "http.h"
-#include "web-base.h"
-#include "ndp.h"
-#include "slaac.h"
-#include "ar6.h"
-#include "nr6.h"
-
-void WebNet6Ajax()
-{
-    HttpOk("text/plain; charset=UTF-8", "no-cache", NULL, NULL);
-    
-    char nibble;
-    nibble = 0;
-    if (NdpManagedConfiguration) nibble |= 1; //4
-    if (NdpOtherConfiguration  ) nibble |= 2; //4
-    if (NdpPrefixFlagL         ) nibble |= 4; //4
-    if (NdpPrefixFlagA         ) nibble |= 8; //4
-    HttpAddNibbleAsHex(nibble);                                                            HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpHopLimit);                                                        HttpAddChar('\n');
-    for (char* p = NdpRouterMac; p < NdpRouterMac + 6; p++) HttpAddByteAsHex(*p);          HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpPrefixLength);                                                    HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpPrefixValidLifetime);                                             HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpPrefixPreferredLifetime);                                         HttpAddChar('\n');
-    for (char* p = NdpPrefix; p < NdpPrefix + 16; p++) HttpAddByteAsHex(*p);               HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpDnsLifetime);                                                     HttpAddChar('\n');
-    for (char* p = NdpDnsServer; p < NdpDnsServer + 16; p++) HttpAddByteAsHex(*p);         HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpGetLease());                                                      HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpGetElapsedLife());                                                HttpAddChar('\n');
-    for (char* p = SlaacLinkLocalIp; p < SlaacLinkLocalIp + 16; p++) HttpAddByteAsHex(*p); HttpAddChar('\n');
-    HttpAddInt32AsHex(NdpMtu);                                                             HttpAddChar('\n');
-    HttpAddChar('\f');
-    
-    Ar6SendAjax();
-    HttpAddChar('\f');
-    
-    Nr6SendAjax();
-}
--- a/net/web-net6-html.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-#include "http.h"
-#include "web-page-base.h"
-#include "web-add.h"
-
-void WebNet6Html()
-{
-    HttpOk("text/html; charset=UTF-8", "no-cache", NULL, NULL);
-    WebAddHeader("Net IPv6", "settings.css", "net6.js");
-    WebAddNav(NET6_PAGE);
-    WebAddH1("Net IPv6");
-
-    WebAddH2("ARP");
-    HttpAddText("<code id='ajax-arp'></code>\r\n");
-    WebAddH2("DNS");
-    HttpAddText("<code id='ajax-dns'></code>\r\n");
-    
-    WebAddH2("NDP");
-    WebAddAjaxLabelled("Hop limit",             "ajax-hop-limit");
-    WebAddAjaxLed     ("Managed address",       "ajax-managed");
-    WebAddAjaxLed     ("Other configuration",   "ajax-other");
-    WebAddAjaxLabelled("Router MAC",            "ajax-router-mac");
-    WebAddAjaxLabelled("Prefix length",         "ajax-prefix-length");
-    WebAddAjaxLed     ("Prefix flag L",         "ajax-prefix-l");
-    WebAddAjaxLed     ("Prefix flag A",         "ajax-prefix-a");
-    WebAddAjaxLabelled("Prefix valid secs",     "ajax-prefix-limit");
-    WebAddAjaxLabelled("Prefix preferred secs", "ajax-prefix-preferred");
-    WebAddAjaxLabelled("Prefix",                "ajax-prefix");
-    WebAddAjaxLabelled("DNS life secs",         "ajax-dns-life");
-    WebAddAjaxLabelled("DNS server",            "ajax-dns-ip");
-    WebAddAjaxLabelled("Lease time",            "ajax-ndp-lease");
-    WebAddAjaxLabelled("Elapsed",               "ajax-ndp-elapsed");
-    WebAddAjaxLabelled("SLAAC",                 "ajax-slaac");
-    WebAddAjaxLabelled("MTU",                   "ajax-mtu");
-    
-    WebAddEnd();
-                        
-}
--- a/net/web-net6-script.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-#include "http.h"
-
-//Use http://tomeko.net/online_tools/cpp_text_escape.php   to convert from text to c-multiline
-//Use http://tomeko.net/online_tools/cpp_text_unescape.php to convert from c-multiline to text
-
-const char* WebNet6ScriptDate = __DATE__;
-const char* WebNet6ScriptTime = __TIME__;
-
-static const char* script =
-#include "../base/web-ajax-class.inc"
-#include "web-net-class.inc"
-#include "web-net6-script.inc"
-;
-void WebNet6Script()
-{
-    HttpOk("application/javascript; charset=UTF-8", "max-age=3600", WebNet6ScriptDate, WebNet6ScriptTime);
-    HttpAddText(script);
-}
--- a/net/web-net6-script.inc	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-"//Net6 script\n"
-"'use strict';\n"
-"\n"
-"let arp             = '';\n"
-"let dns             = '';\n"
-"let hopLimit        = '';\n"
-"let managed         = false;\n"
-"let other           = false;\n"
-"let routerMac       = '';\n"
-"let prefixLength    = '';\n"
-"let prefixL         = false;\n"
-"let prefixA         = false;\n"
-"let prefixLimit     = '';\n"
-"let prefixPreferred = '';\n"
-"let prefix          = '';\n"
-"let dnsLife         = '';\n"
-"let dnsIp           = '';\n"
-"let ndpLease        = '';\n"
-"let ndpElapsed      = '';\n"
-"let slaac           = '';\n"
-"let mtu             = '';\n"
-"\n"
-"function parseArpLine(line)\n"
-"{\n"
-"    if (line.length == 0) return;\n"
-"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
-"    arp += Math.floor(minutes).toString().padStart(4, ' ');\n"
-"    arp += ' ';\n"
-"    arp += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');\n"
-"    arp += ' ';\n"
-"    arp += Net.makeMac(line.substr(40, 12));\n"
-"    arp += '\\r\\n';\n"
-"}\n"
-"function parseDnsLine(line)\n"
-"{\n"
-"    if (line.length == 0) return;\n"
-"    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;\n"
-"    dns += Math.floor(minutes).toString().padStart(4, ' ');\n"
-"    dns += ' ';\n"
-"    dns += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');\n"
-"    dns += ' ';\n"
-"    dns += line.substr(40, 1);\n"
-"    dns += ' ';\n"
-"    dns += line.substr(41);\n"
-"    dns += '\\r\\n';\n"
-"}\n"
-"function parseArpLines(text)\n"
-"{\n"
-"    arp = '';\n"
-"    text.split('\\n').forEach(parseArpLine);\n"
-"}\n"
-"function parseDnsLines(text)\n"
-"{\n"
-"    dns = '';\n"
-"    text.split('\\n').forEach(parseDnsLine);\n"
-"}\n"
-"function parseGenLines(text)\n"
-"{\n"
-"    let lines = text.split('\\n');\n"
-"    \n"
-"    hopLimit        =     parseInt(lines[ 1], 16);\n"
-"    managed         = Net.hexToBit(lines[ 0],  0);\n"
-"    other           = Net.hexToBit(lines[ 0],  1);\n"
-"    routerMac       = Net.makeMac (lines[ 2], 16);\n"
-"    prefixLength    =     parseInt(lines[ 3], 16);\n"
-"    prefixL         = Net.hexToBit(lines[ 0],  2);\n"
-"    prefixA         = Net.hexToBit(lines[ 0],  3);\n"
-"    prefixLimit     =     parseInt(lines[ 4], 16);\n"
-"    prefixPreferred =     parseInt(lines[ 5], 16);\n"
-"    prefix          = Net.makeIp6 (lines[ 6]    );\n"
-"    dnsLife         =     parseInt(lines[ 7], 16);\n"
-"    dnsIp           = Net.makeIp6 (lines[ 8]    );\n"
-"    ndpLease        =     parseInt(lines[ 9], 16);\n"
-"    ndpElapsed      =     parseInt(lines[10], 16);\n"
-"    slaac           = Net.makeIp6 (lines[11]    );\n"
-"    mtu             =     parseInt(lines[12], 16);\n"
-"}\n"
-"function parse()\n"
-"{\n"
-"    let topics = Ajax.response.split('\\f');\n"
-"    parseGenLines(topics[0]);\n"
-"    parseArpLines(topics[1]);\n"
-"    parseDnsLines(topics[2]);\n"
-"}\n"
-"function display()\n"
-"{\n"
-"    let elem;\n"
-"    \n"
-"    elem = Ajax.getElementOrNull('ajax-arp'             ); if (elem) elem.textContent = arp;\n"
-"    elem = Ajax.getElementOrNull('ajax-dns'             ); if (elem) elem.textContent = dns;\n"
-"    elem = Ajax.getElementOrNull('ajax-hop-limit'       ); if (elem) elem.textContent = hopLimit;\n"
-"    elem = Ajax.getElementOrNull('ajax-managed'         ); if (elem) elem.setAttribute('dir', managed ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-other'           ); if (elem) elem.setAttribute('dir', other   ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-router-mac'      ); if (elem) elem.textContent = routerMac;\n"
-"    elem = Ajax.getElementOrNull('ajax-prefix-length'   ); if (elem) elem.textContent = prefixLength;\n"
-"    elem = Ajax.getElementOrNull('ajax-prefix-l'        ); if (elem) elem.setAttribute('dir', prefixL ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-prefix-a'        ); if (elem) elem.setAttribute('dir', prefixA ? 'rtl' : 'ltr');\n"
-"    elem = Ajax.getElementOrNull('ajax-prefix-limit'    ); if (elem) elem.textContent = prefixLimit;\n"
-"    elem = Ajax.getElementOrNull('ajax-prefix-preferred'); if (elem) elem.textContent = prefixPreferred;\n"
-"    elem = Ajax.getElementOrNull('ajax-prefix'          ); if (elem) elem.textContent = prefix;\n"
-"    elem = Ajax.getElementOrNull('ajax-dns-life'        ); if (elem) elem.textContent = dnsLife;\n"
-"    elem = Ajax.getElementOrNull('ajax-dns-ip'          ); if (elem) elem.textContent = dnsIp;\n"
-"    elem = Ajax.getElementOrNull('ajax-ndp-lease'       ); if (elem) elem.textContent = ndpLease;\n"
-"    elem = Ajax.getElementOrNull('ajax-ndp-elapsed'     ); if (elem) elem.textContent = ndpElapsed;\n"
-"    elem = Ajax.getElementOrNull('ajax-slaac'           ); if (elem) elem.textContent = slaac;\n"
-"    elem = Ajax.getElementOrNull('ajax-mtu'             ); if (elem) elem.textContent = mtu;\n"
-"}\n"
-"\n"
-"Ajax.server     = '/net6-ajax';\n"
-"Ajax.onResponse = function() { parse(); display(); };\n"
-"Ajax.init();"
\ No newline at end of file
--- a/net/web-net6-script.js	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-//Net6 script
-'use strict';
-
-let arp             = '';
-let dns             = '';
-let hopLimit        = '';
-let managed         = false;
-let other           = false;
-let routerMac       = '';
-let prefixLength    = '';
-let prefixL         = false;
-let prefixA         = false;
-let prefixLimit     = '';
-let prefixPreferred = '';
-let prefix          = '';
-let dnsLife         = '';
-let dnsIp           = '';
-let ndpLease        = '';
-let ndpElapsed      = '';
-let slaac           = '';
-let mtu             = '';
-
-function parseArpLine(line)
-{
-    if (line.length == 0) return;
-    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
-    arp += Math.floor(minutes).toString().padStart(4, ' ');
-    arp += ' ';
-    arp += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');
-    arp += ' ';
-    arp += Net.makeMac(line.substr(40, 12));
-    arp += '\r\n';
-}
-function parseDnsLine(line)
-{
-    if (line.length == 0) return;
-    let minutes  = parseInt(line.substr(0, 8), 16) / 1000 / 60;
-    dns += Math.floor(minutes).toString().padStart(4, ' ');
-    dns += ' ';
-    dns += Net.makeIp6(line.substr(8, 32)).padEnd(40, ' ');
-    dns += ' ';
-    dns += line.substr(40, 1);
-    dns += ' ';
-    dns += line.substr(41);
-    dns += '\r\n';
-}
-function parseArpLines(text)
-{
-    arp = '';
-    text.split('\n').forEach(parseArpLine);
-}
-function parseDnsLines(text)
-{
-    dns = '';
-    text.split('\n').forEach(parseDnsLine);
-}
-function parseGenLines(text)
-{
-    let lines = text.split('\n');
-    
-    hopLimit        =     parseInt(lines[ 1], 16);
-    managed         = Net.hexToBit(lines[ 0],  0);
-    other           = Net.hexToBit(lines[ 0],  1);
-    routerMac       = Net.makeMac (lines[ 2], 16);
-    prefixLength    =     parseInt(lines[ 3], 16);
-    prefixL         = Net.hexToBit(lines[ 0],  2);
-    prefixA         = Net.hexToBit(lines[ 0],  3);
-    prefixLimit     =     parseInt(lines[ 4], 16);
-    prefixPreferred =     parseInt(lines[ 5], 16);
-    prefix          = Net.makeIp6 (lines[ 6]    );
-    dnsLife         =     parseInt(lines[ 7], 16);
-    dnsIp           = Net.makeIp6 (lines[ 8]    );
-    ndpLease        =     parseInt(lines[ 9], 16);
-    ndpElapsed      =     parseInt(lines[10], 16);
-    slaac           = Net.makeIp6 (lines[11]    );
-    mtu             =     parseInt(lines[12], 16);
-}
-function parse()
-{
-    let topics = Ajax.response.split('\f');
-    parseGenLines(topics[0]);
-    parseArpLines(topics[1]);
-    parseDnsLines(topics[2]);
-}
-function display()
-{
-    let elem;
-    
-    elem = Ajax.getElementOrNull('ajax-arp'             ); if (elem) elem.textContent = arp;
-    elem = Ajax.getElementOrNull('ajax-dns'             ); if (elem) elem.textContent = dns;
-    elem = Ajax.getElementOrNull('ajax-hop-limit'       ); if (elem) elem.textContent = hopLimit;
-    elem = Ajax.getElementOrNull('ajax-managed'         ); if (elem) elem.setAttribute('dir', managed ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-other'           ); if (elem) elem.setAttribute('dir', other   ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-router-mac'      ); if (elem) elem.textContent = routerMac;
-    elem = Ajax.getElementOrNull('ajax-prefix-length'   ); if (elem) elem.textContent = prefixLength;
-    elem = Ajax.getElementOrNull('ajax-prefix-l'        ); if (elem) elem.setAttribute('dir', prefixL ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-prefix-a'        ); if (elem) elem.setAttribute('dir', prefixA ? 'rtl' : 'ltr');
-    elem = Ajax.getElementOrNull('ajax-prefix-limit'    ); if (elem) elem.textContent = prefixLimit;
-    elem = Ajax.getElementOrNull('ajax-prefix-preferred'); if (elem) elem.textContent = prefixPreferred;
-    elem = Ajax.getElementOrNull('ajax-prefix'          ); if (elem) elem.textContent = prefix;
-    elem = Ajax.getElementOrNull('ajax-dns-life'        ); if (elem) elem.textContent = dnsLife;
-    elem = Ajax.getElementOrNull('ajax-dns-ip'          ); if (elem) elem.textContent = dnsIp;
-    elem = Ajax.getElementOrNull('ajax-ndp-lease'       ); if (elem) elem.textContent = ndpLease;
-    elem = Ajax.getElementOrNull('ajax-ndp-elapsed'     ); if (elem) elem.textContent = ndpElapsed;
-    elem = Ajax.getElementOrNull('ajax-slaac'           ); if (elem) elem.textContent = slaac;
-    elem = Ajax.getElementOrNull('ajax-mtu'             ); if (elem) elem.textContent = mtu;
-}
-
-Ajax.server     = '/net6-ajax';
-Ajax.onResponse = function() { parse(); display(); };
-Ajax.init();
\ No newline at end of file
--- a/page/web-nav-base.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,14 +0,0 @@
-#include "web-add.h"
-#include "web-page-base.h"
-
-void WebNavBase(int page)
-{
-    WebAddNavItem(page ==    CLOCK_PAGE, "/clock",    "Clock"    );
-    WebAddNavItem(page ==    FAULT_PAGE, "/fault",    "Fault"    );
-    WebAddNavItem(page ==      NET_PAGE, "/net",      "Net"      );
-    WebAddNavItem(page ==     NET4_PAGE, "/net4",     "Net IPv4" );
-    WebAddNavItem(page ==     NET6_PAGE, "/net6",     "Net IPv6" );
-    WebAddNavItem(page ==    TRACE_PAGE, "/trace",    "Net Trace");
-    WebAddNavItem(page ==      LOG_PAGE, "/log",      "Log"      );
-    WebAddNavItem(page == FIRMWARE_PAGE, "/firmware", "Firmware" );
-}
\ No newline at end of file
--- a/page/web-nav-base.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-extern void WebNavBase(int page);
\ No newline at end of file
--- a/page/web-page-base.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-#define    FAULT_PAGE 0
-#define    CLOCK_PAGE 1
-#define      NET_PAGE 2
-#define     NET4_PAGE 3
-#define     NET6_PAGE 4
-#define    TRACE_PAGE 5
-#define      LOG_PAGE 6
-#define FIRMWARE_PAGE 7
-
--- a/web-add.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,215 +0,0 @@
-#include <stdio.h>
-
-#include "http.h"
-#include "web-nav-base.h"
-#include "web-nav-derived.h"
-#include "web-site-name.h"
-#include "mac.h"
-#include "ip4addr.h"
-#include "ip6addr.h"
-
-void WebAddNavItem(int highlight, char* href, char* title)
-{
-    char *p;
-    HttpAddText("<li ");
-    if (highlight) p = "class='this'";
-    else           p = "            ";
-    HttpAddText(p);
-    HttpAddText("><a href='");
-    HttpAddText(href);
-    HttpAddText("'>");
-    HttpAddText(title);
-    HttpAddText("</a></li>\r\n");
-}
-void WebAddNav(int page)
-{
-    HttpAddText("<a class='tab-shortcut' href='#main-content'>Skip to content</a>\r\n");
-
-    HttpAddText("<nav><ul>\r\n");
-    WebNavDerived(page);
-    WebNavBase(page);
-    HttpAddText("</ul></nav>\r\n");
-}
-
-void WebAddHeader(const char* title, const char* style, const char* script)
-{
-    HttpAddText("<!DOCTYPE html>\r\n"
-                     "<html>\r\n"
-                     "<head>\r\n");
-    HttpAddText("   <title>");
-    HttpAddText(WEB_SITE_NAME);
-    if (title)
-    {
-        HttpAddText(" - ");
-        HttpAddText(title);
-    }
-    HttpAddText("</title>\r\n");
-
-    HttpAddText("   <link rel='stylesheet' href='/base.css' type='text/css'/>\r\n");
-    if (style)
-    {
-        HttpAddText("   <link rel='stylesheet' href='/");
-        HttpAddText(style);
-        HttpAddText("' type='text/css'/>\r\n");
-    }
-    if (script)
-    {
-        HttpAddText("   <script src='/");
-        HttpAddText(script);
-        HttpAddText("' type='text/javascript'></script>\r\n");
-    }
-    HttpAddText("   <meta name='viewport' content='width=device-width, initial-scale=1'>\r\n"
-                     "   <link rel='icon'       href='/favicon.ico' type='image/x-icon'/>\r\n"
-                     "</head>\r\n"
-                     "<body>\r\n");
-
-}
-void WebAddH1(const char* pageName)
-{
-    HttpAddText("<h1 id='main-content'>");
-    HttpAddText(WEB_SITE_NAME);
-    HttpAddText(" - ");
-    HttpAddText(pageName);
-    HttpAddText("</h1>\r\n");
-}
-void WebAddH2(const char* text)
-{
-    HttpAddText("<h2>");
-    HttpAddText(text);
-    HttpAddText("</h2>\r\n");
-}
-void WebAddEnd()
-{
-    HttpAddText("</body>\r\n"
-                "</html>\r\n");
-}
-
-void WebAddLabelledPrefixSuffix(char* label, char* prefix, char* text, char* suffix)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <div>%s%s%s</div>\r\n", prefix, text, suffix);
-    HttpAddText("</div>\r\n");
-}
-void WebAddLabelledText(char* label, char* text)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <div>%s</div>\r\n", text);
-    HttpAddText("</div>\r\n");
-}
-
-void WebAddLabelledMac(char* label, char* mac)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddText("  <div>"); MacHttp(mac); HttpAddText("</div>\r\n");
-    HttpAddText("</div>\r\n");
-}
-
-void WebAddLabelledIp4(char* label, uint32_t ip)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddText("  <div>"); Ip4AddressHttp(ip); HttpAddText("</div>\r\n");
-    HttpAddText("</div>\r\n");
-}
-
-void WebAddLabelledIp6(char* label, char* ip)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddText("  <div>"); Ip6AddressHttp(ip); HttpAddText("</div>\r\n");
-    HttpAddText("</div>\r\n");
-}
-void WebAddLabelledOnOff(char* label, bool value)
-{
-    if (value) WebAddLabelledText(label, "On");
-    else       WebAddLabelledText(label, "Off");
-}
-void WebAddLabelledInt(char* label, int value)
-{
-    char text[30];
-    snprintf(text, sizeof(text), "%8d", value); //Right align with enough spaces so that the length is always constant. 
-    WebAddLabelledText(label, text);
-}
-void WebAddInputText(char* label, float inputwidth, char* value, char* action, char* name)
-{
-    HttpAddF   ("<form action='%s' method='get'>\r\n", action);
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <input type='text' name='%s' style='width:%.1fem;' value='%s'>\r\n", name, inputwidth, value);
-    HttpAddText("</div>\r\n");
-    HttpAddText("<input type='submit' value='Set' style='display:none;'>\r\n");
-    HttpAddF   ("</form>\r\n");
-
-}
-void WebAddInputInt(char* label, float inputwidth, int value, char* action, char* name)
-{    
-    char text[30];
-    snprintf(text, sizeof(text), "%d", value);
-    WebAddInputText(label, inputwidth, text, action, name);
-}
-void WebAddInputButton(char* label, char* value, char* action, char* name)
-{
-    HttpAddF   ("<form action='%s' method='get'>\r\n", action);
-    HttpAddF   ("<input type='hidden' name='%s'>\r\n", name);
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <input type='submit' value='%s'>\r\n", value);
-    HttpAddText("</div>\r\n");
-    HttpAddText("</form>\r\n");
-}
-void WebAddAjaxInputToggle(char* label, char* id, char* name)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <div class='toggle' id='%s' tabindex='0' dir='ltr' onclick='AjaxRequest(\"%s=1\")' onkeydown='return event.keyCode != 13 || AjaxRequest(\"%s=1\")'>\r\n", id, name, name);
-    HttpAddText("    <div class='slot'></div><div class='knob'></div>\r\n");
-    HttpAddText("  </div>\r\n");
-    HttpAddText("</div>\r\n");
-}
-void WebAddAjaxLed(char* label, char* id)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <div class='led' id='%s' dir='ltr'></div>\r\n", id);
-    HttpAddText("</div>\r\n");
-}
-void WebAddAjaxInput(char* label, float inputwidth, char* id, char* name)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <input type='text' style='width:%.1fem;' id='%s' onchange='AjaxRequest(\"%s=\" + this.value)'>\r\n", inputwidth, id, name);
-    HttpAddText("</div>\r\n");
-}
-void WebAddAjaxInputSuffix(char* label, float inputwidth, char* id, char* name, char* suffix)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <input type='text' style='width:%.1fem;' id='%s' onchange='AjaxRequest(\"%s=\" + this.value)'>%s\r\n", inputwidth, id, name, suffix);
-    HttpAddText("</div>\r\n");
-}
-void WebAddAjaxLabelled(char* label, char* id)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <div id='%s'></div>\r\n", id);
-    HttpAddText("</div>\r\n");
-}
-void WebAddAjaxLabelledSuffix(char* label, char* id, char* suffix)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div>%s</div>\r\n", label);
-    HttpAddF   ("  <div><span id='%s'></span>%s</div>\r\n", id, suffix);
-    HttpAddText("</div>\r\n");
-}
-void WebAddAjaxInputLabelId(char* labelId, float inputwidth, char* id, char* name)
-{
-    HttpAddText("<div class='line'>\r\n");
-    HttpAddF   ("  <div id='%s'></div>\r\n", labelId);
-    HttpAddF   ("  <input type='text' style='width:%.1fem;' id='%s' onchange='AjaxRequest(\"%s=\" + this.value)'>\r\n", inputwidth, id, name);
-    HttpAddText("</div>\r\n");
-}
-
-
--- a/web-add.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-#include <stdint.h>
-#include <stdbool.h>
-
-extern void WebAddNavItem      (int highlight, char* href, char* title);
-extern void WebAddNav          (int page);
-extern void WebAddHeader       (const char* title, const char* style, const char* script);
-extern void WebAddH1           (const char* pageName);
-extern void WebAddH2           (const char* text);
-extern void WebAddEnd          (void);
-
-extern void WebAddLabelledText        (char* label,                     char* text);
-extern void WebAddLabelledPrefixSuffix(char* label,   char* prefix,     char* text,  char* suffix);
-extern void WebAddLabelledMac         (char* label,                     char*   mac);
-extern void WebAddLabelledIp4         (char* label,                     uint32_t ip);
-extern void WebAddLabelledIp6         (char* label,                     char*    ip);
-extern void WebAddLabelledOnOff       (char* label,                     bool  value);
-extern void WebAddLabelledInt         (char* label,                     int   value);
-
-extern void WebAddInputText           (char* label,   float inputwidth, char* value, char* action, char* name);
-extern void WebAddInputInt            (char* label,   float inputwidth, int   value, char* action, char* name);
-extern void WebAddInputButton         (char* label,                     char* value, char* action, char* name);
-
-extern void WebAddAjaxLed             (char* label,                     char* id);
-extern void WebAddAjaxLabelled        (char* label,                     char* id);
-extern void WebAddAjaxLabelledSuffix  (char* label,                     char* id, char* suffix);
-extern void WebAddAjaxInputToggle     (char* label,                     char* id, char* name);
-extern void WebAddAjaxInput           (char* label,   float inputwidth, char* id, char* name);
-extern void WebAddAjaxInputSuffix     (char* label,   float inputwidth, char* id, char* name, char* suffix);
-extern void WebAddAjaxInputLabelId    (char* labelId, float inputwidth, char* id, char* name);
--- a/web-base.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-#include <stdint.h>
-#include <stdbool.h>
-
-#include "web-derived.h"
-
-extern void        WebLoginHtml     (void);
-extern void        WebLoginQuery    (char* pQuery);
-extern bool        WebLoginQueryPasswordOk;
-extern int         WebLoginOriginalToDo;
-extern bool        WebLoginCookiesContainValidSessionId(char* pCookies);
-extern char*       WebLoginSessionNameGet(void);
-extern int         WebLoginSessionNameLife(void);
-extern char*       WebLoginSessionIdGet(void);
-extern void        WebLoginSessionIdNew(void);
-extern bool        WebLoginSessionIdIsSet(void);
-extern void        WebLoginInit(void);
-
-extern void        WebFavicon       (void);
-extern const char* WebFaviconDate;
-extern const char* WebFaviconTime;
-extern const int   WebFaviconSize;
-
-extern void        WebBaseCss       (void);
-extern const char* WebBaseCssDate;
-extern const char* WebBaseCssTime;
-extern void        WebNavCss        (void);
-extern const char* WebNavCssDate;
-extern const char* WebNavCssTime;
-
-extern void        WebTraceHtml     (void);
-extern void        WebTraceScript   (void);
-extern const char* WebTraceScriptDate;
-extern const char* WebTraceScriptTime;
-extern void        WebTraceAjax     (void);
-extern void        WebTraceQuery    (char* pQuery);
-
-extern void        WebClockHtml     (void);
-extern void        WebClockScript   (void);
-extern const char* WebClockScriptDate;
-extern const char* WebClockScriptTime;
-extern void        WebClockAjax     (void);
-extern void        WebClockQuery    (char* pQuery);
-
-extern void        WebLogHtml       (void);
-extern void        WebLogQuery      (char* pQuery);
-
-extern void        WebNetHtml       (void);
-extern void        WebNet4Html      (void);
-extern void        WebNet4Script    (void);
-extern const char* WebNet4ScriptDate;
-extern const char* WebNet4ScriptTime;
-extern void        WebNet4Ajax      (void);
-extern void        WebNet6Html      (void);
-extern void        WebNet6Script    (void);
-extern const char* WebNet6ScriptDate;
-extern const char* WebNet6ScriptTime;
-extern void        WebNet6Ajax      (void);
-
-extern void        WebFaultHtml     (void);
-extern void        WebFaultQuery    (char* pQuery);
-
-extern void        WebFirmwareHtml  (void);
-extern void        WebFirmwareScript(void);
-extern const char* WebFirmwareScriptDate;
-extern const char* WebFirmwareScriptTime;
-extern void        WebFirmwareQuery (char* pQuery);
-extern int         WebFirmwareTargetLength;
-extern int         WebFirmwareActualLength;
-extern char*       WebFirmwareFileName;
-extern bool        WebFirmwarePost  (int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream);
-extern void        WebFirmwareAjax  (void);
--- a/web-server-base.c	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-#include "web-base.h"
-#include "web-server-derived.h"
-#include "web-server-base.h"
-#include "http.h"
-#include "fault.h"
-#include "led.h"
-
-#define DO_FAVICON         DO_BASE +  0
-#define DO_BASE_CSS        DO_BASE +  1
-#define DO_NAV_CSS         DO_BASE +  2
-#define DO_TRACE_HTML      DO_BASE +  3
-#define DO_TRACE_AJAX      DO_BASE +  4
-#define DO_TRACE_SCRIPT    DO_BASE +  5
-#define DO_CLOCK_HTML      DO_BASE +  6
-#define DO_CLOCK_AJAX      DO_BASE +  7
-#define DO_CLOCK_SCRIPT    DO_BASE +  8
-#define DO_NET_HTML        DO_BASE +  9
-#define DO_NET4_HTML       DO_BASE + 10
-#define DO_NET4_AJAX       DO_BASE + 11
-#define DO_NET4_SCRIPT     DO_BASE + 12
-#define DO_NET6_HTML       DO_BASE + 13
-#define DO_NET6_AJAX       DO_BASE + 14
-#define DO_NET6_SCRIPT     DO_BASE + 15
-#define DO_LOG_HTML        DO_BASE + 16
-#define DO_FAULT_HTML      DO_BASE + 17
-#define DO_FIRMWARE_HTML   DO_BASE + 18
-#define DO_FIRMWARE_AJAX   DO_BASE + 19
-#define DO_FIRMWARE_SCRIPT DO_BASE + 20
-
-int WebServerBaseDecideWhatToDo(char *pPath, char* pLastModified)
-{
-    if (HttpSameStr(pPath, "/login"        )) return DO_LOGIN;
-    if (HttpSameStr(pPath, "/clock"        )) return DO_CLOCK_HTML;
-    if (HttpSameStr(pPath, "/clock-ajax"   )) return DO_CLOCK_AJAX;
-    if (HttpSameStr(pPath, "/net"          )) return DO_NET_HTML;
-    if (HttpSameStr(pPath, "/net4"         )) return DO_NET4_HTML;
-    if (HttpSameStr(pPath, "/net4-ajax"    )) return DO_NET4_AJAX;
-    if (HttpSameStr(pPath, "/net6"         )) return DO_NET6_HTML;
-    if (HttpSameStr(pPath, "/net6-ajax"    )) return DO_NET6_AJAX;
-    if (HttpSameStr(pPath, "/log"          )) return DO_LOG_HTML;
-    if (HttpSameStr(pPath, "/trace"        )) return DO_TRACE_HTML;
-    if (HttpSameStr(pPath, "/trace-ajax"   )) return DO_TRACE_AJAX;
-    if (HttpSameStr(pPath, "/fault"        )) return DO_FAULT_HTML;
-    if (HttpSameStr(pPath, "/firmware"     )) return DO_FIRMWARE_HTML;
-    if (HttpSameStr(pPath, "/firmware-ajax")) return DO_FIRMWARE_AJAX;
-
-    if (HttpSameStr(pPath, "/favicon.ico"  )) return HttpSameDate(WebFaviconDate,        WebFaviconTime,        pLastModified) ? DO_NOT_MODIFIED : DO_FAVICON;
-    if (HttpSameStr(pPath, "/base.css"     )) return HttpSameDate(WebBaseCssDate,        WebBaseCssTime,        pLastModified) ? DO_NOT_MODIFIED : DO_BASE_CSS;
-    if (HttpSameStr(pPath, "/settings.css" )) return HttpSameDate(WebNavCssDate,         WebNavCssTime,         pLastModified) ? DO_NOT_MODIFIED : DO_NAV_CSS;
-    if (HttpSameStr(pPath, "/net4.js"      )) return HttpSameDate(WebNet4ScriptDate,     WebNet4ScriptTime,     pLastModified) ? DO_NOT_MODIFIED : DO_NET4_SCRIPT;
-    if (HttpSameStr(pPath, "/net6.js"      )) return HttpSameDate(WebNet6ScriptDate,     WebNet6ScriptTime,     pLastModified) ? DO_NOT_MODIFIED : DO_NET6_SCRIPT;
-    if (HttpSameStr(pPath, "/trace.js"     )) return HttpSameDate(WebTraceScriptDate,    WebTraceScriptTime,    pLastModified) ? DO_NOT_MODIFIED : DO_TRACE_SCRIPT;
-    if (HttpSameStr(pPath, "/clock.js"     )) return HttpSameDate(WebClockScriptDate,    WebClockScriptTime,    pLastModified) ? DO_NOT_MODIFIED : DO_CLOCK_SCRIPT;
-    if (HttpSameStr(pPath, "/firmware.js"  )) return HttpSameDate(WebFirmwareScriptDate, WebFirmwareScriptTime, pLastModified) ? DO_NOT_MODIFIED : DO_FIRMWARE_SCRIPT;
-
-    return WebServerDerivedRequest(pPath, pLastModified);
-}
-void WebServerBaseHandleQuery(int todo, char* pQuery)
-{
-    switch (todo)
-    {
-        case DO_LOGIN:         WebLoginQuery   (pQuery); return;
-        case DO_TRACE_AJAX:    WebTraceQuery   (pQuery); return;
-        case DO_CLOCK_AJAX:    WebClockQuery   (pQuery); return;
-        case DO_CLOCK_HTML:    WebClockQuery   (pQuery); return;
-        case DO_LOG_HTML:      WebLogQuery     (pQuery); return;
-        case DO_FAULT_HTML:    WebFaultQuery   (pQuery); return;
-        case DO_FIRMWARE_HTML: WebFirmwareQuery(pQuery); return;
-        case DO_FIRMWARE_AJAX: WebFirmwareQuery(pQuery); return;
-    }
-    WebServerDerivedGet(todo, pQuery);
-}
-bool WebServerBaseHandlePost(int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream)
-{
-    switch (todo)
-    {
-        case DO_FIRMWARE_AJAX: return WebFirmwarePost(contentLength, contentStart, size, pRequestStream, positionInRequestStream);
-    }
-    return WebServerDerivedPost(todo, contentLength, size, pRequestStream, positionInRequestStream);
-}
-
-void WebServerBaseReply(int todo)
-{
-    //Try all the base modules
-    switch (todo)
-    {
-        case DO_NOT_FOUND:       HttpNotFound     (); return;
-        case DO_NOT_MODIFIED:    HttpNotModified  (); return;
-        case DO_LOGIN:           WebLoginHtml     (); return;
-        case DO_FAVICON:         WebFavicon       (); return;
-        case DO_BASE_CSS:        WebBaseCss       (); return;
-        case DO_NAV_CSS:         WebNavCss        (); return;
-        case DO_TRACE_HTML:      WebTraceHtml     (); return;
-        case DO_TRACE_AJAX:      WebTraceAjax     (); return;
-        case DO_TRACE_SCRIPT:    WebTraceScript   (); return;
-        case DO_CLOCK_HTML:      WebClockHtml     (); return;
-        case DO_CLOCK_AJAX:      WebClockAjax     (); return;
-        case DO_CLOCK_SCRIPT:    WebClockScript   (); return;
-        case DO_NET_HTML:        WebNetHtml       (); return;
-        case DO_NET4_HTML:       WebNet4Html      (); return;
-        case DO_NET4_AJAX:       WebNet4Ajax      (); return;
-        case DO_NET4_SCRIPT:     WebNet4Script    (); return;
-        case DO_NET6_HTML:       WebNet6Html      (); return;
-        case DO_NET6_AJAX:       WebNet6Ajax      (); return;
-        case DO_NET6_SCRIPT:     WebNet6Script    (); return;
-        case DO_LOG_HTML:        WebLogHtml       (); return;
-        case DO_FAULT_HTML:      WebFaultHtml     (); return;
-        case DO_FIRMWARE_HTML:   WebFirmwareHtml  (); return;
-        case DO_FIRMWARE_AJAX:   WebFirmwareAjax  (); return;
-        case DO_FIRMWARE_SCRIPT: WebFirmwareScript(); return;
-    }
-    
-    //If not called then call the derived (child) module
-    WebServerDerivedReply(todo);
-}
-
--- a/web-server-base.h	Mon Apr 29 14:45:30 2019 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-extern int  WebServerBaseDecideWhatToDo(char *pPath, char* pLastModified);
-extern void WebServerBaseHandleQuery(int todo, char* pQuery);
-extern bool WebServerBaseHandlePost(int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream);
-
-extern void WebServerBaseReply(int todo);
-
-#define DO_NOTHING           0
-#define DO_NOT_FOUND         1
-#define DO_NOT_MODIFIED      2
-#define DO_LOGIN             3
-#define DO_BASE            100
-#define DO_DERIVED         200
-#define DO_SEND_SESSION_ID 300
\ No newline at end of file
--- a/web.c	Mon Apr 29 14:45:30 2019 +0000
+++ b/web.c	Tue Apr 30 12:45:08 2019 +0000
@@ -1,10 +1,53 @@
 #include "http.h"
 #include "web-server-base.h"
-#include "web-base.h"
+#include "web-server-derived.h"
+#include "web.h"
+#include "web-pages-base.h"
 #include "mstimer.h"
 
 #define LOGIN_DELAY_MS 200
 
+#define DO_LOGIN DO_SERVER + 0
+
+static int decideWhatToDo(char *pPath, char* pLastModified)
+{
+    if (HttpSameStr(pPath, "/login")) return DO_LOGIN;
+    
+    int todo;
+    todo = WebServerBaseDecideWhatToDo   (pPath, pLastModified); if (todo != DO_NOT_FOUND) return todo;
+    todo = WebServerDerivedDecideWhatToDo(pPath, pLastModified); if (todo != DO_NOT_FOUND) return todo;
+    return DO_NOT_FOUND;
+}
+static void handleQuery(int todo, char* pQuery)
+{
+    switch (todo)
+    {
+        case DO_LOGIN:         WebLoginQuery   (pQuery); return;
+    }
+    if (WebServerBaseHandleQuery   (todo, pQuery)) return;
+    if (WebServerDerivedHandleQuery(todo, pQuery)) return;
+}
+static void handlePost(int todo, int contentLength, int contentStart, int size, char* pRequestStream, uint32_t positionInRequestStream, bool* pComplete)
+{
+    if (WebServerBasePost   (todo, contentLength, contentStart, size, pRequestStream, positionInRequestStream, pComplete)) return;
+    if (WebServerDerivedPost(todo, contentLength, contentStart, size, pRequestStream, positionInRequestStream, pComplete)) return;
+}
+
+static void reply(int todo)
+{
+    //Try all the base modules
+    switch (todo)
+    {
+        case DO_LOGIN:           WebLoginHtml     (); return;
+        case DO_NOT_FOUND:       HttpNotFound     (); return;
+        case DO_NOT_MODIFIED:    HttpNotModified  (); return;
+    }
+    
+    //If not called then call the derived (child) module
+    if (WebServerBaseReply   (todo)) return;
+    if (WebServerDerivedReply(todo)) return;
+}
+
 static void handleRequest(int size, char* pRequestStream, uint32_t positionInRequestStream, int* pToDo, bool* pPostComplete, uint32_t* pDelayUntil)
 {
     //Handle request for the first packet of data received but leave todo the same after that.
@@ -21,7 +64,7 @@
         contentStart = HttpRequestRead(pRequestStream, size, &pMethod, &pPath, &pQuery, &pLastModified, &pCookies, &contentLength);
         
         //Ask the web server what to do
-        *pToDo = WebServerBaseDecideWhatToDo(pPath, pLastModified);
+        *pToDo = decideWhatToDo(pPath, pLastModified);
         
         //If what to do is NOTHING, NOT_FOUND or NOT_MODIFIED then no query or post will be valid so stop now
         if (*pToDo < DO_LOGIN) { *pPostComplete = true; return; }
@@ -29,7 +72,7 @@
         //If what to do is LOGIN then the user has just returned the login form
         if (*pToDo == DO_LOGIN)
         {
-            WebServerBaseHandleQuery(*pToDo, pQuery);                  //Read the password and the original location
+            handleQuery(*pToDo, pQuery);                  //Read the password and the original location
             if (WebLoginQueryPasswordOk)
             {
                 if (!WebLoginSessionIdIsSet())           //If there isn't a session id already
@@ -54,11 +97,11 @@
         }
         
         //Handle the query
-        WebServerBaseHandleQuery(*pToDo, pQuery);
+        handleQuery(*pToDo, pQuery);
     }
     
     //Do the upload of anything that needs it. Todos it doesn't understand are ignored.
-    if (!*pPostComplete) *pPostComplete = WebServerBaseHandlePost(*pToDo, contentLength, contentStart, size, pRequestStream, positionInRequestStream);
+    if (!*pPostComplete) handlePost(*pToDo, contentLength, contentStart, size, pRequestStream, positionInRequestStream, pPostComplete);
 }
 
 static void sendReply(int todo)
@@ -78,7 +121,7 @@
         HttpOkCookieMaxAge = -1;
     }
     
-    WebServerBaseReply(todo);
+    reply(todo);
 }
 
 int WebInit()
--- a/web.h	Mon Apr 29 14:45:30 2019 +0000
+++ b/web.h	Tue Apr 30 12:45:08 2019 +0000
@@ -1,2 +1,10 @@
 
 extern int  WebInit(void);
+
+#define DO_NOTHING           0
+#define DO_NOT_FOUND         1
+#define DO_NOT_MODIFIED      2
+#define DO_SERVER           10
+#define DO_BASE            100
+#define DO_DERIVED         200
+#define DO_SEND_SESSION_ID 300
\ No newline at end of file