gfch100: make https standard, clean up html

	* add support for signed ssl cert if present
	* make https and http the same experience
	* fix up tests to handle https and http the same
	* add welcome page to link to https
	* split addresses and counters into 2 tabs
	* fix up css to make it fit
	* b/29192018

Change-Id: Ic0b870a71992c69ea85dc4dfefa2250c4e404303
diff --git a/craftui/.gitignore b/craftui/.gitignore
index 7b8a4bf..5d9b5ae 100644
--- a/craftui/.gitignore
+++ b/craftui/.gitignore
@@ -4,3 +4,4 @@
 .sim.extracted
 sim
 LOG
+tmp-certs
diff --git a/craftui/HOW.cert b/craftui/HOW.cert
new file mode 100644
index 0000000..6d75c7f
--- /dev/null
+++ b/craftui/HOW.cert
@@ -0,0 +1,42 @@
+# how to create a CA and issue certs and use curl
+
+tmp=tmp-certs
+rm -rf $tmp
+mkdir -p $tmp
+
+# create the rootCA's key
+openssl genrsa -out $tmp/rootCA.key 2048
+
+# create the self-signed rootCA certificate
+openssl req -x509 -new -nodes -key $tmp/rootCA.key -sha256 -days 10 -out $tmp/rootCA.pem << EOF
+US
+California
+Mountain View
+gfiber-embedded-networking
+developer testing
+Ed James
+edjames@google.com
+EOF
+
+fqdn=localhost
+
+# create a device cert (could use existing gfch100 here)
+openssl genrsa -out $tmp/$fqdn.key 2048
+
+# create the signing request for $fqdn (must match URL)
+openssl req -new -key $tmp/$fqdn.key -out $tmp/$fqdn.csr << EOF
+US
+California
+Mountain View
+gfiber-embedded-networking
+developer testing
+$fqdn
+edjames@google.com
+
+
+EOF
+
+openssl x509 -req -in $tmp/$fqdn.csr -CA $tmp/rootCA.pem -CAkey $tmp/rootCA.key -CAcreateserial -out $tmp/$fqdn.pem -days 5 -sha256
+
+# test with 
+# curl --cacert rootCA.pem http://$fqdn:8889/status
diff --git a/craftui/craftui.py b/craftui/craftui.py
index d7385d5..a87df55 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -372,12 +372,11 @@
   ]
   realm = 'gfch100'
 
-  def __init__(self, wwwroot, http_port, https_port, use_https, sim):
+  def __init__(self, wwwroot, http_port, https_port, sim):
     """initialize."""
     self.wwwroot = wwwroot
     self.http_port = http_port
     self.https_port = https_port
-    self.use_https = use_https
     self.sim = sim
     self.data = {}
     self.data['refreshCount'] = 0
@@ -565,25 +564,24 @@
       return False
     return True
 
-  class RedirectHandler(tornado.web.RequestHandler):
-    """Redirect to the https_port."""
+  class WelcomeHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
+    """Displays the Welcome page."""
 
     def get(self):
       ui = self.settings['ui']
-      print 'GET craft redirect page'
-      host = re.sub(r':.*', '', self.request.host)
-      port = ui.https_port
-      self.redirect('https://%s:%d/' % (host, port))
+      # no auth required for welcome page
+      print 'GET welcome HTML page'
+      self.render(ui.wwwroot + '/welcome.thtml', ipaddr='xxx')
 
-  class MainHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
-    """Displays the Craft UI."""
+  class StatusHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
+    """Displays the Status page."""
 
     def get(self):
       ui = self.settings['ui']
       if not ui.Authenticate(self):
         return
-      print 'GET craft HTML page'
-      self.render(ui.wwwroot + '/index.thtml', peerurl='/?peer=1')
+      print 'GET status HTML page'
+      self.render(ui.wwwroot + '/status.thtml', peerurl='/status/?peer=1')
 
   class ConfigHandler(digest.DigestAuthMixin, tornado.web.RequestHandler):
     """Displays the Config page."""
@@ -659,32 +657,35 @@
     """Create the http redirect and https web server and run forever."""
     sim = self.sim
 
-    redirect_handlers = [
-        (r'.*', self.RedirectHandler),
-    ]
     craftui_handlers = [
-        (r'/', self.MainHandler),
-        (r'/config', self.ConfigHandler),
-        (r'/content.json', self.JsonHandler),
-        (r'/restart', self.RestartHandler),
-        (r'/static/([^/]*)$', tornado.web.StaticFileHandler,
+        (r'^/$', self.WelcomeHandler),
+        (r'^/status/?$', self.StatusHandler),
+        (r'^/config/?$', self.ConfigHandler),
+        (r'^/content.json/?$', self.JsonHandler),
+        (r'^/restart/?$', self.RestartHandler),
+        (r'^/static/([^/]*)$', tornado.web.StaticFileHandler,
          {'path': self.wwwroot + '/static'}),
     ]
 
-    http_handlers = redirect_handlers if self.use_https else craftui_handlers
-
-    http_app = tornado.web.Application(http_handlers)
+    http_app = tornado.web.Application(craftui_handlers)
     http_app.settings['ui'] = self
     http_app.listen(self.http_port)
 
-    if self.use_https:
-      https_app = tornado.web.Application(craftui_handlers)
-      https_app.settings['ui'] = self
-      https_server = tornado.httpserver.HTTPServer(https_app, ssl_options={
-          'certfile': sim + '/tmp/ssl/certs/device.pem',
-          'keyfile': sim + '/tmp/ssl/private/device.key'
-      })
-      https_server.listen(self.https_port)
+    certfile = sim + '/tmp/ssl/certs/craftui.pem'
+    keyfile = sim + '/tmp/ssl/private/craftui.key'
+    # use the device cert if signed one is not available
+    if not os.path.exists(certfile) or not os.path.exists(keyfile):
+      certfile = sim + '/tmp/ssl/certs/device.pem'
+      keyfile = sim + '/tmp/ssl/private/device.key'
+
+    print 'certfile=', certfile
+    print 'keyfile=', keyfile
+
+    https_app = tornado.web.Application(craftui_handlers)
+    https_app.settings['ui'] = self
+    https_server = tornado.httpserver.HTTPServer(https_app, ssl_options={
+        'certfile': certfile, 'keyfile': keyfile})
+    https_server.listen(self.https_port)
 
     ioloop = tornado.ioloop.IOLoop.instance()
     ioloop.start()
@@ -700,12 +701,10 @@
   www = '/usr/craftui/www'
   http_port = 80
   https_port = 443
-  use_https = False
   sim = ''
   try:
     opts, args = getopt.getopt(sys.argv[1:], 's:p:P:w:S',
-                               ['sim=', 'http-port=', 'https-port=', 'www=',
-                                'use-https='])
+                               ['sim=', 'http-port=', 'https-port=', 'www='])
   except getopt.GetoptError as err:
     # print help information and exit:
     print str(err)
@@ -718,8 +717,6 @@
       http_port = int(a)
     elif o in ('-P', '--https-port'):
       https_port = int(a)
-    elif o in ('-S', '--use-https'):
-      use_https = True
     elif o in ('-w', '--www'):
       www = a
     else:
@@ -730,7 +727,7 @@
     assert False, 'extra args'
     Usage()
     sys.exit(1)
-  craftui = CraftUI(www, http_port, https_port, use_https, sim)
+  craftui = CraftUI(www, http_port, https_port, sim)
   craftui.RunUI()
 
 
diff --git a/craftui/craftui_test.sh b/craftui/craftui_test.sh
index 56be130..7c6a0c3 100755
--- a/craftui/craftui_test.sh
+++ b/craftui/craftui_test.sh
@@ -74,34 +74,23 @@
 }
 
 run_tests() {
-  local use_https http https url curl n arg secure_arg curl_arg
-  use_https=$1
+  local http https url curl n arg curl_arg
 
   http=8888
   https=8889
-  url=http://localhost:$http
-
-  if [ "$use_https" = 1 ]; then
-    url=https://localhost:$https
-    secure_arg=-S
-    curl_arg=-k
-
-    # not really testing here, just showing the mode change
-    testname "INFO: https mode"
-    true
-    check_success
-  else
-    # not really testing here, just showing the mode change
-    testname "INFO: http mode"
-    true
-    check_success
-  fi
+  curl_ca="--cacert tmp-certs/rootCA.pem"
 
   testname "server not running"
   curl -s http://localhost:8888/
   check_failure
 
-  ./craftui $secure_arg &
+  # add a signed cert (using our fake CA)
+  sh HOW.cert
+  chmod 750 sim/tmp/ssl/*
+  cp tmp-certs/localhost.pem sim/tmp/ssl/certs/craftui.pem
+  cp tmp-certs/localhost.key sim/tmp/ssl/private/craftui.key
+
+  ./craftui &
   pid=$!
 
   testname "process running"
@@ -110,94 +99,112 @@
 
   sleep 1
 
-  curl="curl -v -s -m 1 $curl_arg"
+  curl_noca="curl -v -s -m 1"
+  curl="$curl_noca $curl_ca"
 
-  if [ "$use_https" = 1 ]; then
-    for n in localhost 127.0.0.1; do
-      testname "redirect web page ($n)"
-      $curl "http://$n:8888/anything" |& grep "Location: https://$n:8889/"
+  # verify localhost works
+  testname https localhost
+  $curl https://localhost:$https/status |& grep 'WWW-Authenticate: Digest'
+  check_success
+
+  # but needs --cacert
+  testname https localhost without CA
+  $curl_noca https://localhost:$https/status |&
+    grep 'unable to get local issuer certificate'
+  check_success
+
+  # and 127.0.0.1 fails due to cert name mismatch
+  testname https 127.0.0.1
+  $curl https://127.0.0.1:$https/status |&
+    grep "certificate subject name 'localhost' does not match target host name"
+  check_success
+
+  for url in http://localhost:$http https://localhost:$https; do
+
+    testname "404 not found ($url)"
+    $curl $url/notexist |& grep '404: Not Found'
+    check_success
+
+    baduser_auth="--digest --user root:admin"
+    badpass_auth="--digest --user guest:admin"
+
+    testname "welcome web page ($url)"
+    $curl $url/ |& grep welcome.thtml
+    check_success
+
+    for auth in "" "$baduser_auth" "$badpass_auth"; do
+      for n in status config content.json; do
+	testname "page $n bad auth ($url, $auth)"
+	$curl $auth $url/$n |& grep 'WWW-Authenticate: Digest'
+	check_success
+      done
+    done
+
+    admin_auth="--digest --user admin:admin"
+    guest_auth="--digest --user guest:guest"
+
+    for auth in "$admin_auth" "$guest_auth"; do
+      testname "status web page ($url, $auth)"
+      $curl $auth $url/status |& grep status.thtml
+      check_success
+
+      testname "config web page ($url, $auth)"
+      $curl $auth $url/config |& grep config.thtml
+      check_success
+
+      testname "json ($url, $auth)"
+      $curl $auth $url/content.json |& grep '"platform": "GFCH100"'
       check_success
     done
-  fi
 
-  testname "404 not found"
-  $curl $url/notexist |& grep '404: Not Found'
-  check_success
+    testname "bad json to config page ($url)"
+    $curl $admin_auth -d 'duck' $url/content.json | grep "json format error"
+    check_success
 
-  baduser_auth="--digest --user root:admin"
-  badpass_auth="--digest --user guest:admin"
+    testname "good json config ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl $admin_auth -d $d $url/content.json |& grep '"error": 0}'
+    check_success
 
-  for auth in "" "$baduser_auth" "$badpass_auth"; do
-    for n in / /config /content.json; do
-      testname "page $n bad auth ($auth)"
-      $curl -v $auth $url/ |& grep 'WWW-Authenticate: Digest'
-      check_success
-    done
+    testname "good json config, bad value ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/240"}]}'
+    $curl $admin_auth -d $d $url/content.json |& grep '"error": 1}'
+    check_success
+
+    testname "good json config, guest access ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl $guest_auth -d $d $url/content.json |& grep '401 Unauthorized'
+    check_success
+
+    testname "good json config, no auth ($url)"
+    d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
+    $curl -d $d $url/content.json |& grep '401 Unauthorized'
+    check_success
+
+    testname "password is base64 ($url)"
+    admin=$(echo -n admin | base64)
+    new=$(echo -n ducky | base64)
+    d='{ "config": [ { "password_guest": {
+	  "admin": "'"$admin"'",
+	  "new": "'"$new"'",
+	  "confirm": "'"$new"'"
+	} } ] }'
+    $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 0}'
+    check_success
+
+    # TODO(edjames): duckduck does not fail.  Need to catch that.
+    testname "password not base64 ($url)"
+    new=ducky
+    d='{ "config": [ { "password_guest": {
+	  "admin": "'"$admin"'",
+	  "new": "'"$new"'",
+	  "confirm": "'"$new"'"
+	} } ] }'
+    $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 1}'
+    check_success
+
   done
 
-  admin_auth="--digest --user admin:admin"
-  guest_auth="--digest --user guest:guest"
-
-  for auth in "$admin_auth" "$guest_auth"; do
-    testname "main web page ($auth)"
-    $curl $auth $url/ |& grep index.thtml
-    check_success
-
-    testname "config web page ($auth)"
-    $curl $auth $url/config |& grep config.thtml
-    check_success
-
-    testname "json ($auth)"
-    $curl $auth $url/content.json |& grep '"platform": "GFCH100"'
-    check_success
-  done
-
-  testname "bad json to config page"
-  $curl $admin_auth -d 'duck' $url/content.json | grep "json format error"
-  check_success
-
-  testname "good json config"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
-  $curl $admin_auth -d $d $url/content.json |& grep '"error": 0}'
-  check_success
-
-  testname "good json config, bad value"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/240"}]}'
-  $curl $admin_auth -d $d $url/content.json |& grep '"error": 1}'
-  check_success
-
-  testname "good json config, guest access"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
-  $curl $guest_auth -d $d $url/content.json |& grep '401 Unauthorized'
-  check_success
-
-  testname "good json config, no auth"
-  d='{"config":[{"peer_ipaddr":"192.168.99.99/24"}]}'
-  $curl -d $d $url/content.json |& grep '401 Unauthorized'
-  check_success
-
-  testname "password is base64"
-  admin=$(echo -n admin | base64)
-  new=$(echo -n ducky | base64)
-  d='{ "config": [ { "password_guest": {
-        "admin": "'"$admin"'",
-        "new": "'"$new"'",
-        "confirm": "'"$new"'"
-      } } ] }'
-  $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 0}'
-  check_success
-
-  # TODO(edjames): duckduck does not fail.  Need to catch that.
-  testname "password not base64"
-  new=ducky
-  d='{ "config": [ { "password_guest": {
-        "admin": "'"$admin"'",
-        "new": "'"$new"'",
-        "confirm": "'"$new"'"
-      } } ] }'
-  $curl $admin_auth -d "$d" $url/content.json |& grep '"error": 1}'
-  check_success
-
   testname "process still running at end of test sequence"
   kill -0 $pid
   check_success
@@ -229,11 +236,8 @@
 false
 check_failure
 
-# run without https
-run_tests 0
-
-# run with https
-run_tests 1
+# run test suite
+run_tests
 
 # If there's a syntax error in this script, trap 0 will call onexit,
 # so indicate we really hit the end of the script.
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
index 5a3d96a..11736c3 100644
--- a/craftui/www/config.thtml
+++ b/craftui/www/config.thtml
@@ -14,7 +14,7 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li ><a href=/>GFCH100</a></li>
+          <li ><a href=/status>GFCH100</a></li>
           <li class=active><a href=/config>Configuration</a></li>
           <li ><a href={{ peerurl }} target=_blank>Peer</a></li>
         </ul>
@@ -73,7 +73,7 @@
 
     <div class="tab">
       <input type="radio" id="tab-3" name="tab-group-1">
-      <label for="tab-3">Network</label>
+      <label for="tab-3">Addresses</label>
       <div class="content">
         <b>Platform Parameters:</b>
         <table>
@@ -177,7 +177,7 @@
             <td align=right><span id="radio/hiTransceiver/pll/frequency">...</span>
             <td>
               <input id=freq_hi type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('freq_hi')">
+              <input type=submit value=Apply onclick="CraftUI.config('freq_hi')">
             <td>
               <span id=freq_hi_result>...</span>
 
@@ -186,7 +186,7 @@
             <td align=right><span id="radio/loTransceiver/pll/frequency">...</span>
             <td>
               <input id=freq_lo type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('freq_lo')">
+              <input type=submit value=Apply onclick="CraftUI.config('freq_lo')">
             <td>
               <span id=freq_lo_result>...</span>
 
@@ -195,7 +195,7 @@
             <td align=right><span id="radio/hiTransceiver/mode">...</span>
             <td>
               <input id=mode_hi type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('mode_hi')">
+              <input type=submit value=Apply onclick="CraftUI.config('mode_hi')">
             <td>
               <span id=mode_hi_result>...</span>
 
@@ -204,7 +204,7 @@
             <td align=right><span id="modem/status/acmEngineRxSensorsEnabled">...</span>
             <td>
               <input id=acm_on type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('acm_on')">
+              <input type=submit value=Apply onclick="CraftUI.config('acm_on')">
             <td>
               <span id=acm_on_result>...</span>
 
@@ -213,7 +213,7 @@
             <td align=right><span id="radio/tx/paPowerSet">...</span>
             <td>
               <input id=tx_powerlevel type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('tx_powerlevel')">
+              <input type=submit value=Apply onclick="CraftUI.config('tx_powerlevel')">
             <td>
               <span id=tx_powerlevel_result>...</span>
 
@@ -222,7 +222,7 @@
             <td align=right><span id="radio/tx/vgaGain">...</span>
             <td>
               <input id=tx_gain type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('tx_gain')">
+              <input type=submit value=Apply onclick="CraftUI.config('tx_gain')">
             <td>
               <span id=tx_gain_result>...</span>
 
@@ -231,7 +231,7 @@
             <td align=right><span id="radio/rx/agcDigitalGainIndex">...</span>
             <td>
               <input id=rx_gainindex type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('rx_gainindex')">
+              <input type=submit value=Apply onclick="CraftUI.config('rx_gainindex')">
             <td>
               <span id=rx_gainindex_result>...</span>
 
@@ -240,7 +240,7 @@
             <td align=right><span id="radio/paLnaPowerEnabled">...</span>
             <td>
               <input id=palna_on type=text value="">
-              <input type=submit value=Configure onclick="CraftUI.config('palna_on')">
+              <input type=submit value=Apply onclick="CraftUI.config('palna_on')">
             <td>
               <span id=palna_on_result>...</span>
 
@@ -252,7 +252,8 @@
       <label for="tab-5">Debug</label>
       <div class="content">
         <b>refreshCount:</b><span class="values" id="platform/refreshCount">...</span><br>
-        <b>unhandled xml:</b><span class="values" id="unhandled"></span>
+        <b>unhandled xml:</b><span class="values">
+          <textarea id=unhandled cols=60 rows=30>...</textarea></span><br>
       </div>
     </div>
   </div>
diff --git a/craftui/www/static/craft.css b/craftui/www/static/craft.css
index 2fd9201..5c383ab 100644
--- a/craftui/www/static/craft.css
+++ b/craftui/www/static/craft.css
@@ -1,10 +1,12 @@
 table, th, td {
+  margin: 1px;
+  margin-bottom: 20px;
   border: 1px solid #ccc;
   font-size:12px;
 }
 
 td {
-  padding: 5px;
+  padding: 2px;
 }
 
 .bit {
diff --git a/craftui/www/static/default.css b/craftui/www/static/default.css
index 1f51a21..852caad 100644
--- a/craftui/www/static/default.css
+++ b/craftui/www/static/default.css
@@ -15,7 +15,6 @@
   table {
     border-collapse: collapse;
     border-spacing: 0;
-    margin: 20px 0;
     table-layout: fixed;
     word-wrap: break-word;
   }
diff --git a/craftui/www/index.thtml b/craftui/www/status.thtml
similarity index 87%
rename from craftui/www/index.thtml
rename to craftui/www/status.thtml
index 3ca4237..2a56fdd 100644
--- a/craftui/www/index.thtml
+++ b/craftui/www/status.thtml
@@ -14,7 +14,7 @@
       <h1><img src=static/logo.png alt="Google Fiber"></h1>
       <nav>
         <ul>
-          <li class=active><a href=/>GFCH100</a></li>
+          <li class=active><a href=/status>GFCH100</a></li>
           <li ><a href=/config>Configuration</a></li>
           <li ><a href={{ peerurl }} target=_blank>Peer</a></li>
         </ul>
@@ -37,7 +37,7 @@
     </div>
     <div class="tab">
       <input type="radio" id="tab-2" name="tab-group-1">
-      <label for="tab-2">Network</label>
+      <label for="tab-2">Addresses</label>
       <div class="content">
         <b>Peer is up:</b><span class="values" id="platform/peer_up">...</span><br>
         <b>IP Addresses:</b>
@@ -73,13 +73,20 @@
 	    <td align=right><span id="platform/active_link_inet">...</span></td>
 	    <td align=right><span id="platform/active_link_inet6">...</span></td></tr>
 	</table>
-        <b>Packet Counters:</b>
+      </div>
+    </div>
+
+    <div class="tab">
+      <input type="radio" id="tab-3" name="tab-group-1">
+      <label for="tab-3">Counters</label>
+      <div class="content">
+        <b>Modem:</b>
         <table>
           <tr>
             <td><b></b></td>
             <td colspan=5 align=center><b>received</b></td>
             <td colspan=5 align=center><b>transmitted</b></td>
-            <td colspan=9 align=center><b>errors</b></td></tr>
+            <td colspan=4 align=center><b>errors</b></td></tr>
           <tr>
             <td align=center><b>interface</b></td>
 
@@ -95,15 +102,11 @@
             <td align=center><b>broadcast</b></td>
             <td align=center><b>unicast</b></td>
 
-            <td align=center><b>rx errors</b></td>
-            <td align=center><b>rx dropped</b></td>
             <td align=center><b>rx CRC</b></td>
             <td align=center><b>rx Undersize</b></td>
-            <td align=center><b>tx errors</b></td>
-            <td align=center><b>tx dropped</b></td>
             <td align=center><b>tx CRC</b></td>
             <td align=center><b>tx Undersize</b></td>
-            <td align=center><b>collisions</b></td>
+
           <tr>
             <td><b>Modem (from/to switch)<b></td>
             <td align=right><span id="modem/network/rxCounters/bytes">...</span></td>
@@ -118,16 +121,36 @@
 	    <td align=right><span id="modem/network/txCounters/broadcast">...</span></td>
 	    <td align=right><span id="modem/network/txCounters/unicast">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="modem/network/rxCounters/crcErrors">...</span></td>
             <td align=right><span id="modem/network/rxCounters/framesUndersized">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="modem/network/txCounters/crcErrors">...</span></td>
             <td align=right><span id="modem/network/txCounters/framesUndersized">...</span></td>
-            <td align=right>-</td></tr>
+        </table>
 
+        <b>SOC (CPU):</b>
+        <table>
+          <tr>
+            <td><b></b></td>
+            <td colspan=5 align=center><b>received</b></td>
+            <td colspan=2 align=center><b>transmitted</b></td>
+            <td colspan=5 align=center><b>errors</b></td></tr>
+          <tr>
+            <td align=center><b>interface</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>frames</b></td>
+            <td align=center><b>multicast</b></td>
+            <td align=center><b>broadcast</b></td>
+            <td align=center><b>unicast</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>frames</b></td>
+
+            <td align=center><b>rx errors</b></td>
+            <td align=center><b>rx dropped</b></td>
+            <td align=center><b>tx errors</b></td>
+            <td align=center><b>tx dropped</b></td>
+            <td align=center><b>collisions</b></td>
 	  <tr>
             <td><b>Craft<b></td>
             <td align=right><span id="platform/craft_rx_bytes">...</span></td>
@@ -138,18 +161,11 @@
 
             <td align=right><span id="platform/craft_tx_bytes">...</span></td>
             <td align=right><span id="platform/craft_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/craft_rx_errors">...</span></td>
             <td align=right><span id="platform/craft_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/craft_tx_errors">...</span></td>
             <td align=right><span id="platform/craft_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/craft_collisions">...</span></td>
 
 	  <tr>
@@ -162,18 +178,11 @@
 
             <td align=right><span id="platform/bridge_tx_bytes">...</span></td>
             <td align=right><span id="platform/bridge_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/bridge_rx_errors">...</span></td>
             <td align=right><span id="platform/bridge_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/bridge_tx_errors">...</span></td>
             <td align=right><span id="platform/bridge_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/bridge_collisions">...</span></td>
 
 	  <tr>
@@ -186,18 +195,11 @@
 
             <td align=right><span id="platform/ooband_tx_bytes">...</span></td>
             <td align=right><span id="platform/ooband_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/ooband_rx_errors">...</span></td>
             <td align=right><span id="platform/ooband_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/ooband_tx_errors">...</span></td>
             <td align=right><span id="platform/ooband_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/ooband_collisions">...</span></td>
 
 	  <tr>
@@ -210,122 +212,87 @@
 
             <td align=right><span id="platform/link_tx_bytes">...</span></td>
             <td align=right><span id="platform/link_tx_packets">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
 
             <td align=right><span id="platform/link_rx_errors">...</span></td>
             <td align=right><span id="platform/link_rx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/link_tx_errors">...</span></td>
             <td align=right><span id="platform/link_tx_dropped">...</span></td>
-            <td align=right>-</td>
-            <td align=right>-</td>
             <td align=right><span id="platform/link_collisions">...</span></td>
+        </table>
+
+        <b>Switch:</b>
+        <table>
+          <tr>
+            <td><b></b></td>
+            <td colspan=4 align=center><b>received</b></td>
+            <td colspan=4 align=center><b>transmitted</b></td>
+          <tr>
+            <td align=center><b>interface</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>multicast</b></td>
+            <td align=center><b>broadcast</b></td>
+            <td align=center><b>unicast</b></td>
+
+            <td align=center><b>bytes</b></td>
+            <td align=center><b>multicast</b></td>
+            <td align=center><b>broadcast</b></td>
+            <td align=center><b>unicast</b></td>
 
           <tr>
             <td><b>Switch Port 0/0 (PoE)</b></td>
             <td align=right><span id="platform/switch/0/0/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/0/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/0/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/0/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/0/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/0/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/0/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/0/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
           <tr>
             <td><b>Switch Port 0/4 (SOC)</b></td>
             <td align=right><span id="platform/switch/0/4/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/4/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/4/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/4/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/4/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/4/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/4/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/4/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
           <tr>
             <td><b>Switch Port 0/24 (modem)</b></td>
             <td align=right><span id="platform/switch/0/24/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/24/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/24/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/24/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/24/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/24/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/24/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/24/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
           <tr>
             <td><b>Switch Port 0/25 (SFP+)</b></td>
             <td align=right><span id="platform/switch/0/25/bytes_received">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/25/multicast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/25/broadcast_packets_received">...</span></td>
             <td align=right><span id="platform/switch/0/25/unicast_packets_received">...</span></td>
 
             <td align=right><span id="platform/switch/0/25/bytes_sent">...</span></td>
-            <td align=right>-</td>
             <td align=right><span id="platform/switch/0/25/multicast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/25/broadcast_packets_sent">...</span></td>
             <td align=right><span id="platform/switch/0/25/unicast_packets_sent">...</span></td>
 
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-            <td align=right>-</td>
-
         </table>
       </div>
     </div>
     <div class="tab">
-      <input type="radio" id="tab-3" name="tab-group-1">
-      <label for="tab-3">Modem</label>
+      <input type="radio" id="tab-4" name="tab-group-1">
+      <label for="tab-4">Modem</label>
       <div class="content">
         <b>Chip:</b><span class="values" id="modem/version/api/chipType">...</span><br>
         <b>Firmware:</b><span class="values" id="modem/firmware">...</span><br>
@@ -390,8 +357,8 @@
       </div>
     </div>
     <div class="tab">
-      <input type="radio" id="tab-4" name="tab-group-1">
-      <label for="tab-4">Radio</label>
+      <input type="radio" id="tab-5" name="tab-group-1">
+      <label for="tab-5">Radio</label>
       <div class="content">
         <b>Hardware Version:</b><span class="values">
           <span id="radio/version/hardware/type">...</span>&nbsp;
@@ -479,19 +446,20 @@
       </div>
     </div>
     <div class="tab">
-      <input type="radio" id="tab-5" name="tab-group-1">
-      <label for="tab-5">Debug</label>
+      <input type="radio" id="tab-6" name="tab-group-1">
+      <label for="tab-6">Debug</label>
       <div class="content">
         <form action=/startisostream method=post>
           {% module xsrf_form_html() %}
           <button id="isostream_button">Run Test</button>
         </form>
         <b>refreshCount:</b><span class="values" id="platform/refreshCount">...</span><br>
-        <b>unhandled xml:</b><span class="values" id="unhandled"></span>
+        <b>unhandled xml:</b><span class="values">
+          <textarea id=unhandled cols=60 rows=30>...</textarea></span><br>
       </div>
     </div>
   </div>
   <script src="static/craft.js"></script>
 </body>
 </html>
-<!-- end of index.thtml (used by unit test) -->
+<!-- end of status.thtml (used by unit test) -->
diff --git a/craftui/www/welcome.thtml b/craftui/www/welcome.thtml
new file mode 100644
index 0000000..f3a2b02
--- /dev/null
+++ b/craftui/www/welcome.thtml
@@ -0,0 +1,30 @@
+<html>
+<head>
+  <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+  <meta content="utf-8" http-equiv="encoding">
+  <script src="static/jquery-2.1.4.min.js"></script>
+  <link rel="stylesheet" type="text/css" href="static/craft.css">
+  <link rel=icon href=static/favicon.ico>
+  <link rel=stylesheet href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&amp;lang=en">
+  <link rel=stylesheet href=static/default.css>
+</head>
+<body>
+  <header>
+    <section>
+      <h1><img src=static/logo.png alt="Google Fiber"></h1>
+    </section>
+  </header>
+  <br>
+  Welcome to the GFCH100.<br>
+
+  To connect securely, use https.  <br>
+  Status: https://{{ipaddr}}/status<br>
+  Configuration: https://{{ipaddr}}/config<br>
+
+  If https is unavailable, you may connect insecurely:<br>
+  Status: http://{{ipaddr}}/status<br>
+  Configuration: http://{{ipaddr}}/config<br>
+
+</body>
+</html>
+<!-- end of welcome.thtml (used by unit test) -->