craftui: add config UI
* with test cases!
* client and server side config interface
* update test suite and test sample data
Change-Id: I0dc676db61002606112f8cc9ed4f401cdc6dce46
diff --git a/craftui/.gitignore b/craftui/.gitignore
new file mode 100644
index 0000000..f972e18
--- /dev/null
+++ b/craftui/.gitignore
@@ -0,0 +1,2 @@
+*.swp
+.started
diff --git a/craftui/HOW.restart_if_changed b/craftui/HOW.restart_if_changed
index 1f3343e..8c17154 100644
--- a/craftui/HOW.restart_if_changed
+++ b/craftui/HOW.restart_if_changed
@@ -6,10 +6,13 @@
restart() {
[ -n "$pid" ] && kill $pid
+ echo "######################################################################"
echo "# starting craftui"
+ gpylint *.py
+ make test
./craftui &
pid=$!
- touch started
+ touch .started
}
onExit() {
@@ -25,7 +28,7 @@
restart
continue
fi
- f=$(find . -type f -newer started)
+ f=$(find . -name '*.swp' -prune -o -type f -newer .started -print)
if [ -n "$f" ]; then
restart
continue
diff --git a/craftui/Makefile b/craftui/Makefile
index 6b6757b..9f41971 100644
--- a/craftui/Makefile
+++ b/craftui/Makefile
@@ -17,10 +17,10 @@
test: lint
set -e; \
- for pytest in $(wildcard *_test.py); do \
+ for n in $(wildcard ./*_test.*); do \
echo; \
- echo "Testing $$pytest"; \
- $(PYTHON) $$pytest; \
+ echo "Testing $$n"; \
+ $$n; \
done
clean:
diff --git a/craftui/craftui b/craftui/craftui
index c78bc35..2f5e143 100755
--- a/craftui/craftui
+++ b/craftui/craftui
@@ -1,6 +1,6 @@
#! /bin/sh
-bin=/bin
+pycode=/bin/craftui.py
cw=/usr/catawampus
devcw=../../../../vendor/google/catawampus
localwww=./www
@@ -14,7 +14,8 @@
if [ "$isdev" = 1 ]; then
cw="$devcw"
args="$args --port=8888 --sim=./sim"
- bin=.
+ pycode=./craftui_fortesting.py
+ export PATH="$PWD/sim/bin:$PATH"
fi
# for debugging on the device, use the local (/tmp/www?) web tree
@@ -22,5 +23,10 @@
args="$args --www=$localwww"
fi
+# enable debugger
+if [ "$1" = -d ]; then
+ debug="-m pdb"
+fi
+
export PYTHONPATH="$cw/tr/vendor/tornado:$PYTHONPATH"
-exec python $bin/craftui.py $args
+exec python -u $debug $pycode $args
diff --git a/craftui/craftui.py b/craftui/craftui.py
index 8e06e65..e39faf1 100755
--- a/craftui/craftui.py
+++ b/craftui/craftui.py
@@ -20,15 +20,264 @@
import getopt
import json
import os
+import re
+import subprocess
import sys
import urllib2
import tornado.ioloop
import tornado.web
+class ConfigError(Exception):
+ """Configuration errors to pass to browser."""
+
+ def __init__(self, message):
+ super(ConfigError, self).__init__(message)
+
+
+class Validator(object):
+ """Validate the user value and convert to safe config value."""
+ pattern = r'^(.*)$'
+ example = 'any string'
+
+ def __init__(self):
+ self.Reset()
+
+ def Reset(self):
+ self.fields = ()
+ self.config = ''
+
+ def Validate(self, value):
+ self.Reset()
+ self.value = value
+ m = re.search(self.pattern, value)
+ if not m:
+ raise ConfigError('value "%s" does not match pattern "%s", eg: "%s"' %
+ (value, self.pattern, self.example))
+ self.fields = m.groups()
+ self.config = self.fields[0]
+
+
+class VInt(Validator):
+ """Validate as integer."""
+ pattern = r'^(\d+)$'
+ example = '123'
+
+
+class VRange(VInt):
+ """Validate as integer in a range."""
+
+ def __init__(self, low, high):
+ super(VRange, self).__init__()
+ self.low = low
+ self.high = high
+
+ def Validate(self, value):
+ super(VRange, self).Validate(value)
+ self.CheckInRange(int(self.config), self.low, self.high)
+
+ @staticmethod
+ def CheckInRange(num, low, high):
+ if num < low or num > high:
+ raise ConfigError('number %d is out of range %d-%d' % (num, low, high))
+
+
+class VSlash(Validator):
+ """Validate as slash notation (eg 192.168.1.1/24)."""
+ pattern = r'^((\d+).(\d+).(\d+).(\d+)/(\d+))$'
+ example = '192.168.1.1/24'
+
+ def __init__(self):
+ super(VSlash, self).__init__()
+
+ def Validate(self, value):
+ super(VSlash, self).Validate(value)
+ mask = int(self.fields[5])
+ VRange.CheckInRange(mask, 0, 32)
+ for dotted_quad_part in self.fields[1:4]:
+ num = int(dotted_quad_part)
+ VRange.CheckInRange(num, 0, 255)
+
+
+class VVlan(VRange):
+ """Validate as vlan."""
+
+ def __init__(self):
+ super(VVlan, self).__init__(0, 4095)
+
+
+class VFreqHi(VRange):
+ """Validate as Hi E-Band frequency."""
+
+ def __init__(self):
+ super(VFreqHi, self).__init__(82000000, 85000000)
+
+
+class VFreqLo(VRange):
+ """Validate as Low E-Band frequency."""
+
+ def __init__(self):
+ super(VFreqLo, self).__init__(72000000, 75000000)
+
+
+class VPower(VRange):
+ """Validate as PA power level."""
+
+ def __init__(self):
+ super(VPower, self).__init__(0, 2000000) # TODO(edjames)
+
+
+class VDict(Validator):
+ """Validate as member of dict."""
+ dict = {}
+
+ def Validate(self, value):
+ super(VDict, self).Validate(value)
+ if value not in self.dict:
+ keys = self.dict.keys()
+ raise ConfigError('value "%s" must be one of "%s"' % (value, keys))
+ self.config = self.dict[value]
+
+
+class VTx(VDict):
+ """Validate: tx/rx."""
+ dict = {'tx': 'tx', 'rx': 'rx'}
+
+
+class VTrueFalse(VDict):
+ """Validate as true or false."""
+ dict = {'true': 'true', 'false': 'false'}
+
+
+class Config(object):
+ """Configure the device after validation."""
+
+ def __init__(self, validator):
+ self.validator = validator()
+
+ def Validate(self, value):
+ self.validator.Validate(value)
+
+ def Configure(self):
+ raise Exception('override Config.Configure')
+
+ @staticmethod
+ def Run(command):
+ """Run a command."""
+ print 'running: %s' % command
+ try:
+ subprocess.check_output(command)
+ except subprocess.CalledProcessError as e:
+ print 'Run: ', str(e)
+ raise ConfigError('command failed with %d' % e.returncode)
+
+
+class PtpConfig(Config):
+ """Configure using ptp-config."""
+
+ def __init__(self, validator, key):
+ super(PtpConfig, self).__init__(validator)
+ self.key = key
+
+ def Configure(self):
+ Config.Run(['ptp-config', '-s', self.key, self.validator.config])
+
+
+class PtpActivate(Config):
+ """Configure using ptp-config."""
+
+ def __init__(self, validator, key):
+ super(PtpActivate, self).__init__(validator)
+ self.key = key
+
+ def Configure(self):
+ Config.Run(['ptp-config', '-i', self.key])
+
+
+class Glaukus(Config):
+ """Configure using glaukus json api."""
+
+ def __init__(self, validator, api, fmt):
+ super(Glaukus, self).__init__(validator)
+ self.api = api
+ self.fmt = fmt
+
+ def Configure(self):
+ """Handle a JSON request to glaukusd."""
+ url = 'http://localhost:8080' + self.api
+ payload = self.fmt % self.validator.config
+ # TODO(edjames)
+ print 'Glaukus: ', url, payload
+ try:
+ fd = urllib2.urlopen(url, payload)
+ except urllib2.URLError as ex:
+ print 'Connection to %s failed: %s' % (url, ex.reason)
+ raise ConfigError('failed to contact glaukus')
+ response = fd.read()
+ j = json.loads(response)
+ print j
+ if j['code'] != 'SUCCESS':
+ if j['message']:
+ raise ConfigError(j.message)
+ raise ConfigError('failed to configure glaukus')
+
+
+class Reboot(Config):
+ """Reboot."""
+
+ def Configure(self):
+ if self.validator.config == 'true':
+ Config.Run(['reboot'])
+
+
class CraftUI(object):
"""A web server that configures and displays Chimera data."""
+ handlers = {
+ 'craft_ipaddr': PtpConfig(VSlash, 'craft_ipaddr'),
+ 'link_ipaddr': PtpConfig(VSlash, 'local_ipaddr'),
+ 'peer_ipaddr': PtpConfig(VSlash, 'peer_ipaddr'),
+
+ 'vlan_inband': PtpConfig(VVlan, 'vlan_inband'),
+ 'vlan_peer': PtpConfig(VVlan, 'vlan_peer'),
+
+ 'craft_ipaddr_activate': PtpActivate(VTrueFalse, 'craft_ipaddr'),
+ 'link_ipaddr_activate': PtpActivate(VTrueFalse, 'local_ipaddr'),
+ 'peer_ipaddr_activate': PtpActivate(VTrueFalse, 'peer_ipaddr'),
+ 'vlan_inband_activate': PtpActivate(VTrueFalse, 'vlan_inband'),
+ 'vlan_peer_activate': PtpActivate(VTrueFalse, 'vlan_peer'),
+
+ 'freq_hi': Glaukus(VFreqHi, '/api/radio/frequency', '{"hiFrequency":%s}'),
+ 'freq_lo': Glaukus(VFreqLo, '/api/radio/frequency', '{"loFrequency":%s}'),
+ 'mode_hi': Glaukus(VTx, '/api/radio/hiTransceiver/mode', '%s'),
+ 'tx_powerlevel': Glaukus(VPower, '/api/radio/tx/paPowerSet', '%s'),
+ 'tx_on': Glaukus(VTrueFalse, '/api/radio/paLnaPowerEnabled', '%s'),
+
+ 'reboot': Reboot(VTrueFalse)
+ }
+ ifmap = {
+ 'craft0': 'craft',
+ 'eth1.inband': 'inband',
+ 'eth1.peer': 'link',
+ 'br0': 'poe'
+ }
+ ifvlan = [
+ 'eth1.inband',
+ 'eth1.peer'
+ ]
+ stats = [
+ 'multicast',
+ 'collisions',
+ 'rx_bytes',
+ 'rx_packets',
+ 'rx_errors',
+ 'rx_dropped',
+ 'tx_bytes',
+ 'tx_packets',
+ 'tx_errors',
+ 'tx_dropped'
+ ]
+
def __init__(self, wwwroot, port, sim):
"""initialize."""
self.wwwroot = wwwroot
@@ -37,7 +286,30 @@
self.data = {}
self.data['refreshCount'] = 0
+ def ApplyChanges(self, changes):
+ """Apply changes to system."""
+ if 'config' not in changes:
+ raise ConfigError('missing required config array')
+ conf = changes['config']
+ try:
+ # dry run to validate all
+ for c in conf:
+ for k, v in c.items():
+ if k not in self.handlers:
+ raise ConfigError('unknown key "%s"' % k)
+ h = self.handlers[k]
+ h.Validate(v)
+ # do it again for real
+ for c in conf:
+ for k, v in c.items():
+ h = self.handlers[k]
+ h.Validate(v)
+ h.Configure()
+ except ConfigError as e:
+ raise ConfigError('key "%s": %s' % (k, e))
+
def ReadFile(self, filepath):
+ """cat file."""
text = ''
try:
with open(filepath) as fd:
@@ -55,12 +327,59 @@
js = '{"platform":' + pj + ',"modem":' + mj + ',"radio":' + rj + '}'
return js
+ def AddIpAddr(self, data):
+ """Run ip addr and parse results."""
+ ipaddr = ''
+ try:
+ ipaddr = subprocess.check_output(['ip', '-o', 'addr'])
+ except subprocess.CalledProcessError as e:
+ print 'warning: "ip -o addr" failed: ', e
+ v = {}
+ for line in ipaddr.splitlines():
+ f = line.split()
+ m = re.search(r'scope (global|link)', line)
+ scope = m.group(1) if m else 'noscope'
+ v[f[1] + ':' + f[2] + ':' + scope] = f[3]
+ for ifname, uiname in self.ifmap.items():
+ for inet in ('inet', 'inet6'):
+ kglobal = ifname + ':' + inet + ':' + 'global'
+ vdata = v.get(kglobal, 'unknown')
+ kdata = 'active_' + uiname + '_' + inet
+ data[kdata] = vdata
+
+ def AddInterfaceStats(self, data):
+ """Get if stats."""
+ for ifname, uiname in self.ifmap.items():
+ d = self.sim + '/sys/class/net/' + ifname + '/statistics/'
+ for stat in self.stats:
+ k = uiname + '_' + stat
+ data[k] = self.ReadFile(d + stat)
+
+ def AddVlans(self, data):
+ """Run ip -d link and parse results for vlans."""
+ iplink = ''
+ try:
+ iplink = subprocess.check_output(['ip', '-o', '-d', 'link'])
+ except subprocess.CalledProcessError as e:
+ print 'warning: "ip -o -d link" failed: ', e
+ v = {}
+ for line in iplink.splitlines():
+ m = re.search(r'^\d+: ([\w\.]+)@\w+: .* vlan id (\w+)', line)
+ if m:
+ v[m.group(1)] = m.group(2)
+ for ifname in self.ifvlan:
+ uiname = self.ifmap[ifname]
+ vdata = v.get(ifname, 'unknown')
+ kdata = 'active_' + uiname + '_vlan'
+ data[kdata] = vdata
+
def GetPlatformData(self):
"""Get platform data, return a json string."""
data = self.data
sim = self.sim
if data['refreshCount'] == 0:
+ data['serialno'] = self.ReadFile(sim + '/etc/serial')
data['version'] = self.ReadFile(sim + '/etc/version')
data['platform'] = self.ReadFile(sim + '/etc/platform')
data['softwaredate'] = self.ReadFile(sim + '/etc/softwaredate')
@@ -69,10 +388,13 @@
data['ledstate'] = self.ReadFile(sim + '/tmp/gpio/ledstate')
cs = '/config/settings/'
data['craft_ipaddr'] = self.ReadFile(sim + cs + 'craft_ipaddr')
- data['local_ipaddr'] = self.ReadFile(sim + cs + 'local_ipaddr')
+ data['link_ipaddr'] = self.ReadFile(sim + cs + 'local_ipaddr')
data['peer_ipaddr'] = self.ReadFile(sim + cs + 'peer_ipaddr')
data['vlan_inband'] = self.ReadFile(sim + cs + 'vlan_inband')
- data['vlan_peer'] = self.ReadFile(sim + cs + 'vlan_peer')
+ data['vlan_link'] = self.ReadFile(sim + cs + 'vlan_peer')
+ self.AddIpAddr(data)
+ self.AddInterfaceStats(data)
+ self.AddVlans(data)
return json.dumps(data)
def GetModemData(self):
@@ -104,12 +426,20 @@
return response
class MainHandler(tornado.web.RequestHandler):
- """Displays the UI."""
+ """Displays the Craft UI."""
def get(self):
ui = self.settings['ui']
print 'GET craft HTML page'
- self.render(ui.wwwroot + '/index.thtml', peerurl='http://TODO')
+ self.render(ui.wwwroot + '/index.thtml', peerurl='/?peer=1')
+
+ class ConfigHandler(tornado.web.RequestHandler):
+ """Displays the Config page."""
+
+ def get(self):
+ ui = self.settings['ui']
+ print 'GET config HTML page'
+ self.render(ui.wwwroot + '/config.thtml', peerurl='/config/?peer=1')
class RestartHandler(tornado.web.RequestHandler):
"""Restart the box."""
@@ -135,12 +465,40 @@
self.write(jsonstring)
self.finish()
+ def post(self):
+ print 'POST JSON data for craft page'
+ request = self.request.body
+ result = {}
+ result['error'] = 0
+ result['errorstring'] = ''
+ try:
+ try:
+ json_args = json.loads(request)
+ request = json.dumps(json_args)
+ except ValueError as e:
+ print e
+ raise ConfigError('json format error')
+ ui = self.settings['ui']
+ ui.ApplyChanges(json_args)
+ except ConfigError as e:
+ print e
+ result['error'] += 1
+ result['errorstring'] += str(e)
+
+ response = json.dumps(result)
+ print 'request: ', request
+ print 'response: ', response
+ self.set_header('Content-Type', 'application/json')
+ self.write(response)
+ self.finish()
+
def RunUI(self):
"""Create the web server and run forever."""
handlers = [
- (r'/', CraftUI.MainHandler),
- (r'/content.json', CraftUI.JsonHandler),
- (r'/restart', CraftUI.RestartHandler),
+ (r'/', self.MainHandler),
+ (r'/config', self.ConfigHandler),
+ (r'/content.json', self.JsonHandler),
+ (r'/restart', self.RestartHandler),
(r'/static/([^/]*)$', tornado.web.StaticFileHandler,
{'path': self.wwwroot + '/static'}),
]
diff --git a/craftui/craftui_test.py b/craftui/craftui_fortesting.py
similarity index 75%
rename from craftui/craftui_test.py
rename to craftui/craftui_fortesting.py
index ae20df7..82bf147 100644
--- a/craftui/craftui_test.py
+++ b/craftui/craftui_fortesting.py
@@ -17,6 +17,14 @@
__author__ = 'edjames@google.com (Ed James)'
+import traceback
+import craftui
if __name__ == '__main__':
- print 'TODO(edjames)'
+ try:
+ craftui.main()
+ # pylint: disable=broad-except
+ except Exception as e:
+ traceback.print_exc()
+ # exit cleanly to close the socket so next listen doesn't fail with in-use
+ exit(1)
diff --git a/craftui/craftui_test.sh b/craftui/craftui_test.sh
new file mode 100755
index 0000000..9147945
--- /dev/null
+++ b/craftui/craftui_test.sh
@@ -0,0 +1,107 @@
+#! /bin/sh
+
+# some unit tests for the craft UI
+
+# save stdout to 3, dup stdout to a file
+log=.testlog.$$
+exec 3>&1
+exec >$log 2>&1
+
+failcount=0
+passcount=0
+
+fail() {
+ echo "FAIL: $*" >&3
+ echo "FAIL: $*"
+ ((failcount++))
+}
+
+pass() {
+ echo "PASS: $*" >&3
+ echo "PASS: $*"
+ ((passcount++))
+}
+
+testname() {
+ test="$*"
+ echo "---------------------------------------------------------"
+ echo "starting test $test"
+}
+
+check_success() {
+ status=$?
+ echo "check_success: last return code was $status, wanted 0"
+ if [ $status = 0 ]; then
+ pass $test
+ else
+ fail $test
+ fi
+}
+
+check_failure() {
+ status=$?
+ echo "check_failure: last return code was $status, wanted not-0"
+ if [ $status != 0 ]; then
+ pass $test
+ else
+ fail $test
+ fi
+}
+
+onexit() {
+ testname "process running at exit"
+ kill -0 $pid
+ check_success
+
+ # cleanup
+ kill -9 $pid
+
+ exec 1>&3
+ echo "SUMMARY: pass=$passcount fail=$failcount"
+ if [ $failcount -eq 0 ]; then
+ echo "SUCCESS: $passcount tests passed."
+ else
+ echo "FAILURE: $failcount tests failed."
+ echo "details follow:"
+ cat $log
+ fi
+ rm -f $log
+
+ exit $failcount
+}
+
+trap onexit 0 1 2 3
+
+testname "server not running"
+curl -s http://localhost:8888/
+check_failure
+
+./craftui > /tmp/LOG 2>&1 &
+pid=$!
+
+testname "process running"
+kill -0 $pid
+check_success
+
+sleep 1
+
+testname true
+true
+check_success
+
+testname false
+false
+check_failure
+
+testname "main web page"
+curl -s http://localhost:8888/ > /dev/null
+check_success
+
+testname "404 not found"
+curl -s http://localhost:8888/notexist | grep '404: Not Found'
+check_success
+
+testname "json"
+curl -s http://localhost:8888/content.json | grep '"platform": "GFCH100"'
+check_success
+
diff --git a/craftui/sim/bin/ip b/craftui/sim/bin/ip
new file mode 100755
index 0000000..0775940
--- /dev/null
+++ b/craftui/sim/bin/ip
@@ -0,0 +1,35 @@
+#! /bin/sh
+
+if [ "$3" = link ]; then
+ cat << EOF
+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+2: craft0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 532\ link/ether f4:f5:e8:01:a1:01 brd ff:ff:ff:ff:ff:ff
+3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP qlen 532\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff
+4: eth1.inband@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff\ vlan id 4090 <REORDER_HDR>
+5: eth1.peer@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff\ vlan id 2000 <REORDER_HDR>
+6: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff\ bridge
+EOF
+
+else
+ cat << EOF
+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+1: lo inet 127.0.0.1/32 scope host lo\ valid_lft forever preferred_lft forever
+1: lo inet 127.0.0.1/8 scope host lo\ valid_lft forever preferred_lft forever
+1: lo inet6 ::1/128 scope host \ valid_lft forever preferred_lft forever
+2: craft0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 532\ link/ether f4:f5:e8:01:a1:01 brd ff:ff:ff:ff:ff:ff
+2: craft0 inet 192.168.5.99/24 scope global craft0\ valid_lft forever preferred_lft forever
+2: craft0 inet6 fe80::f6f5:e8ff:fe01:a101/64 scope link \ valid_lft forever preferred_lft forever
+3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP qlen 532\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff
+3: eth1 inet6 fe80::f6f5:e8ff:fe01:a102/64 scope link \ valid_lft forever preferred_lft forever
+4: eth1.inband@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff
+4: eth1.inband inet6 fe80::f6f5:e8ff:fe01:a102/64 scope link \ valid_lft forever preferred_lft forever
+5: eth1.peer@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff
+5: eth1.peer inet 192.168.2.1/24 scope global eth1.peer\ valid_lft forever preferred_lft forever
+5: eth1.peer inet6 fe80::f6f5:e8ff:fe01:a102/64 scope link \ valid_lft forever preferred_lft forever
+6: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000\ link/ether f4:f5:e8:01:a1:02 brd ff:ff:ff:ff:ff:ff
+6: br0 inet 192.168.1.147/24 brd 192.168.1.255 scope global br0\ valid_lft forever preferred_lft forever
+6: br0 inet6 2605:a601:b17:d600:f6f5:e8ff:fe01:a102/64 scope global dynamic \ valid_lft 3595sec preferred_lft 3595sec
+6: br0 inet6 fe80::f6f5:e8ff:fe01:a102/64 scope link \ valid_lft forever preferred_lft forever
+EOF
+
+fi
diff --git a/craftui/sim/bin/ptp-config b/craftui/sim/bin/ptp-config
new file mode 100755
index 0000000..4031019
--- /dev/null
+++ b/craftui/sim/bin/ptp-config
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+echo "TODO: $0 $*"
diff --git a/craftui/sim/bin/reboot b/craftui/sim/bin/reboot
new file mode 100755
index 0000000..60ee746
--- /dev/null
+++ b/craftui/sim/bin/reboot
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+echo running: $0 $*
diff --git a/craftui/sim/etc/serial b/craftui/sim/etc/serial
new file mode 100644
index 0000000..72a6d63
--- /dev/null
+++ b/craftui/sim/etc/serial
@@ -0,0 +1 @@
+NPAPID1611E0001
diff --git a/craftui/sim/sys/class/net/br0/statistics/collisions b/craftui/sim/sys/class/net/br0/statistics/collisions
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/collisions
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/br0/statistics/multicast b/craftui/sim/sys/class/net/br0/statistics/multicast
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/multicast
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/br0/statistics/rx_bytes b/craftui/sim/sys/class/net/br0/statistics/rx_bytes
new file mode 100644
index 0000000..08e3da2
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/rx_bytes
@@ -0,0 +1 @@
+3519029
diff --git a/craftui/sim/sys/class/net/br0/statistics/rx_dropped b/craftui/sim/sys/class/net/br0/statistics/rx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/rx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/br0/statistics/rx_errors b/craftui/sim/sys/class/net/br0/statistics/rx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/rx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/br0/statistics/rx_packets b/craftui/sim/sys/class/net/br0/statistics/rx_packets
new file mode 100644
index 0000000..9c4044d
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/rx_packets
@@ -0,0 +1 @@
+13252
diff --git a/craftui/sim/sys/class/net/br0/statistics/tx_bytes b/craftui/sim/sys/class/net/br0/statistics/tx_bytes
new file mode 100644
index 0000000..3365165
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/tx_bytes
@@ -0,0 +1 @@
+2378483
diff --git a/craftui/sim/sys/class/net/br0/statistics/tx_dropped b/craftui/sim/sys/class/net/br0/statistics/tx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/tx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/br0/statistics/tx_errors b/craftui/sim/sys/class/net/br0/statistics/tx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/tx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/br0/statistics/tx_packets b/craftui/sim/sys/class/net/br0/statistics/tx_packets
new file mode 100644
index 0000000..bad2b41
--- /dev/null
+++ b/craftui/sim/sys/class/net/br0/statistics/tx_packets
@@ -0,0 +1 @@
+5530
diff --git a/craftui/sim/sys/class/net/craft0/statistics/collisions b/craftui/sim/sys/class/net/craft0/statistics/collisions
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/collisions
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/craft0/statistics/multicast b/craftui/sim/sys/class/net/craft0/statistics/multicast
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/multicast
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/craft0/statistics/rx_bytes b/craftui/sim/sys/class/net/craft0/statistics/rx_bytes
new file mode 100644
index 0000000..84f168f
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/rx_bytes
@@ -0,0 +1 @@
+3008350
diff --git a/craftui/sim/sys/class/net/craft0/statistics/rx_dropped b/craftui/sim/sys/class/net/craft0/statistics/rx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/rx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/craft0/statistics/rx_errors b/craftui/sim/sys/class/net/craft0/statistics/rx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/rx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/craft0/statistics/rx_packets b/craftui/sim/sys/class/net/craft0/statistics/rx_packets
new file mode 100644
index 0000000..6fc6a64
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/rx_packets
@@ -0,0 +1 @@
+18701
diff --git a/craftui/sim/sys/class/net/craft0/statistics/tx_bytes b/craftui/sim/sys/class/net/craft0/statistics/tx_bytes
new file mode 100644
index 0000000..924221a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/tx_bytes
@@ -0,0 +1 @@
+12081830
diff --git a/craftui/sim/sys/class/net/craft0/statistics/tx_dropped b/craftui/sim/sys/class/net/craft0/statistics/tx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/tx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/craft0/statistics/tx_errors b/craftui/sim/sys/class/net/craft0/statistics/tx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/tx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/craft0/statistics/tx_packets b/craftui/sim/sys/class/net/craft0/statistics/tx_packets
new file mode 100644
index 0000000..5c2b8bb
--- /dev/null
+++ b/craftui/sim/sys/class/net/craft0/statistics/tx_packets
@@ -0,0 +1 @@
+7473
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/collisions b/craftui/sim/sys/class/net/eth1.inband/statistics/collisions
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/collisions
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/multicast b/craftui/sim/sys/class/net/eth1.inband/statistics/multicast
new file mode 100644
index 0000000..91b629b
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/multicast
@@ -0,0 +1 @@
+156
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/rx_bytes b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_bytes
new file mode 100644
index 0000000..81fa122
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_bytes
@@ -0,0 +1 @@
+407824
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/rx_dropped b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/rx_errors b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/rx_packets b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_packets
new file mode 100644
index 0000000..79abba8
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/rx_packets
@@ -0,0 +1 @@
+1348
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/tx_bytes b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_bytes
new file mode 100644
index 0000000..cf34894
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_bytes
@@ -0,0 +1 @@
+442098
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/tx_dropped b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/tx_errors b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.inband/statistics/tx_packets b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_packets
new file mode 100644
index 0000000..ceb9a4e
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.inband/statistics/tx_packets
@@ -0,0 +1 @@
+1403
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/collisions b/craftui/sim/sys/class/net/eth1.peer/statistics/collisions
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/collisions
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/multicast b/craftui/sim/sys/class/net/eth1.peer/statistics/multicast
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/multicast
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/rx_bytes b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_bytes
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_bytes
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/rx_dropped b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/rx_errors b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/rx_packets b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_packets
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/rx_packets
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/tx_bytes b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_bytes
new file mode 100644
index 0000000..775971e
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_bytes
@@ -0,0 +1 @@
+648
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/tx_dropped b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_dropped
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_dropped
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/tx_errors b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_errors
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_errors
@@ -0,0 +1 @@
+0
diff --git a/craftui/sim/sys/class/net/eth1.peer/statistics/tx_packets b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_packets
new file mode 100644
index 0000000..45a4fb7
--- /dev/null
+++ b/craftui/sim/sys/class/net/eth1.peer/statistics/tx_packets
@@ -0,0 +1 @@
+8
diff --git a/craftui/sim/tmp/glaukus/modem.json b/craftui/sim/tmp/glaukus/modem.json
index ba7f021..9b51b70 100644
--- a/craftui/sim/tmp/glaukus/modem.json
+++ b/craftui/sim/tmp/glaukus/modem.json
@@ -1 +1 @@
-{"firmware":"\/etc\/glaukus\/firmware\/bcm85100mc_1007128.fw","network":{"rxCounters":{"broadcast":15449,"bytes":5662990,"crcErrors":0,"frames":17417,"frames1024_1518":0,"frames128_255":1968,"frames256_511":15449,"frames512_1023":0,"frames64":0,"frames65_127":0,"framesJumbo":0,"framesUndersized":0,"multicast":1968,"unicast":0},"status":0,"statusStr":"UP","txCounters":{"broadcast":0,"bytes":0,"crcErrors":0,"frames":0,"frames1024_1518":0,"frames128_255":0,"frames256_511":0,"frames512_1023":0,"frames64":0,"frames65_127":0,"framesJumbo":0,"framesUndersized":0,"multicast":0,"unicast":0}},"profile":"\/etc\/glaukus\/profiles\/C01_1007128_1500_48_C1_260X300_3c3f2.bin","status":{"absoluteMse":-32768,"acmEngineRxSensorsEnabled":true,"acmEngineTxSwitchEnabled":true,"acquireStatus":0,"acquireStatusStr":"Acquire in progress","carrierOffset":-3749990,"debugIndications":2,"externalAgc":122,"internalAgc":560,"lastAcquireError":3,"lastAcquireErrorStr":"Acquisition failed at frequency sweep.","normalizedMse":-32693,"radialMse":-32663,"resPhNoiseVal":0,"rxAcmProfile":0,"rxSymbolRate":374830203,"txAcmProfile":6,"txSymbolRate":375000000},"temperature":50.19281005859375,"transmitter":{"dcLeakageI":0,"dcLeakageQ":0,"mode":0,"modeStr":"NORMAL","sweepTime":0,"toneFreq":0,"toneSecFreq":0},"version":{"build":128,"chipType":"BCM85100IFSBG","major":100,"minor":7}}
\ No newline at end of file
+{"firmware":"\/etc\/glaukus\/firmware\/bcm85100mc_1007128.fw","network":{"rxCounters":{"broadcast":10859,"bytes":3978230,"crcErrors":0,"frames":12225,"frames1024_1518":0,"frames128_255":1366,"frames256_511":10859,"frames512_1023":0,"frames64":0,"frames65_127":0,"framesJumbo":0,"framesUndersized":0,"multicast":1366,"unicast":0},"status":0,"statusStr":"UP","txCounters":{"broadcast":10857,"bytes":3977010,"crcErrors":0,"frames":12219,"frames1024_1518":0,"frames128_255":1362,"frames256_511":10857,"frames512_1023":0,"frames64":0,"frames65_127":0,"framesJumbo":0,"framesUndersized":0,"multicast":1362,"unicast":0}},"profile":"\/etc\/glaukus\/profiles\/16qam_tx26bo_rx29bo_1500M_v128.bin","status":{"absoluteMse":-275,"acmEngineRxSensorsEnabled":false,"acmEngineTxSwitchEnabled":false,"acquireStatus":1,"acquireStatusStr":"Modem is locked","carrierOffset":35908,"debugIndications":12,"externalAgc":184,"internalAgc":182,"lastAcquireError":0,"lastAcquireErrorStr":"No Error","normalizedMse":-200,"radialMse":-204,"resPhNoiseVal":35,"rxAcmProfile":3,"rxSymbolRate":1499998768,"txAcmProfile":3,"txSymbolRate":1500000000},"temperature":67.972015380859375,"transmitter":{"dcLeakageI":0,"dcLeakageQ":0,"mode":0,"modeStr":"NORMAL","sweepTime":0,"toneFreq":0,"toneSecFreq":0},"version":{"build":128,"chipType":"BCM85100IFSBG","major":100,"minor":7}}
\ No newline at end of file
diff --git a/craftui/sim/tmp/glaukus/radio.json b/craftui/sim/tmp/glaukus/radio.json
index 4946225..eec8519 100644
--- a/craftui/sim/tmp/glaukus/radio.json
+++ b/craftui/sim/tmp/glaukus/radio.json
@@ -1 +1 @@
-{"heaterEnabled":false,"hiTransceiver":{"epot":{"control":"auto","driver":0,"lna":0,"pa":0},"icModel":"BGT80","mode":"tx","pll":{"frequency":85500000,"lockCounts":0,"locked":true},"temp":1481},"loTransceiver":{"epot":{"control":"auto","driver":0,"lna":0,"pa":0},"icModel":"BGT70","mode":"rx","pll":{"frequency":75500000,"lockCounts":0,"locked":true},"temp":1671},"paLnaPowerEnabled":true,"paLnaPowerStatus":"normal","rx":{"agcDigitalGain":42,"agcDigitalGainIndex":3,"lnaCurrentMeas":0,"lnaCurrentSet":0,"rssi":1342},"transceiversPowerEnabled":true,"tx":{"dcI":63,"dcQ":63,"driverCurrentMeas":0,"driverCurrentSet":0,"paCurrentMeas":1428,"paCurrentSet":0,"paTemp":4084,"txPowerControl":"auto","txPowerMeas":2895,"txPowerSet":0,"vgaGain":14},"version":{"hardware":{"major":1,"minor":1,"type":"Chimera V1 000001"},"software":{"build":0,"major":0,"minor":1}}}
\ No newline at end of file
+{"heaterEnabled":false,"hiTransceiver":{"epot":{"control":"auto","driver":3,"lna":10,"pa":10},"icModel":"BGT80","mode":"rx","pll":{"frequency":81500000,"lockCounts":0,"locked":true},"temp":42.2089},"loTransceiver":{"epot":{"control":"auto","driver":126,"lna":10,"pa":126},"icModel":"BGT70","mode":"tx","pll":{"frequency":71500000,"lockCounts":0,"locked":true},"temp":48.507800000000006},"mcuTemp":-128,"paLnaPowerEnabled":true,"paLnaPowerStatus":"normal","rx":{"agcDigitalGain":36,"agcDigitalGainIndex":1,"lnaCurrentMeas":0.049,"lnaCurrentSet":0,"rssi":1578},"transceiversPowerEnabled":true,"tx":{"dcI":41,"dcQ":29,"driverCurrentMeas":0.2157,"driverCurrentSet":0,"paCurrentMeas":0.3201,"paCurrentSet":0,"paPowerMeas":12.6419,"paPowerSet":1146588,"paTemp":125,"txPowerControl":"auto","txPowerMeas":-4.2567000000000006,"txPowerSet":0,"vgaGain":18},"version":{"hardware":{"major":2,"minor":1,"type":"Chimera V2 000001"},"software":{"build":0,"major":0,"minor":2}}}
\ No newline at end of file
diff --git a/craftui/www/config.thtml b/craftui/www/config.thtml
new file mode 100644
index 0000000..ca94b22
--- /dev/null
+++ b/craftui/www/config.thtml
@@ -0,0 +1,167 @@
+<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&lang=en">
+ <link rel=stylesheet href=static/default.css>
+</head>
+<body>
+ <header>
+ <section>
+ <h1><img src=static/logo.png alt="Google Fiber"></h1>
+ <nav>
+ <ul>
+ <li ><a href=/>GFCH100</a></li>
+ <li class=active><a href=/config>Configuration</a></li>
+ <li ><a href={{ peerurl }} target=_blank>Peer</a></li>
+ </ul>
+ </nav>
+ </section>
+ </header>
+ <br>
+ <div class="tabs">
+ <div class="tab">
+ <input type="radio" id="tab-1" name="tab-group-1" checked>
+ <label for="tab-1">Site Configuration</label>
+ <div class="content">
+ <b>Platfrom Parameters:</b>
+ <table>
+ <tr>
+ <td align=center><b>Parameter
+ <td align=center><b>Active Value
+ <td align=center><b>Last Configured
+ <td align=center><b>Configure
+ <td align=center><b>Status
+
+ <tr>
+ <td><b>Craft IP Address
+ <td align=right><span id="platform/active_craft_inet">...</span>
+ <td align=right>
+ <span id="platform/craft_ipaddr">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('craft_ipaddr', 1)">
+ <td>
+ <input id=craft_ipaddr type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('craft_ipaddr')">
+ <td>
+ <span id=craft_ipaddr_result>...</span>
+
+ <tr>
+ <td><b>Link IP Address
+ <td align=right><span id="platform/active_link_inet">...</span>
+ <td align=right>
+ <span id="platform/link_ipaddr">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('link_ipaddr', 1)">
+ <td>
+ <input id=link_ipaddr type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('link_ipaddr')">
+ <td>
+ <span id=link_ipaddr_result>...</span>
+
+ <tr>
+ <td><b>Peer IP Address
+ <td align=right>N/A
+ <td align=right>
+ <span id="platform/peer_ipaddr">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('peer_ipaddr', 1)">
+ <td>
+ <input id=peer_ipaddr type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('peer_ipaddr')">
+ <td>
+ <span id=peer_ipaddr_result>...</span>
+
+ <tr>
+ <td><b>Link VLAN (to peer)
+ <td align=right><span id="platform/active_link_vlan">...</span>
+ <td align=right>
+ <span id="platform/vlan_link">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_peer', 1)">
+ <td>
+ <input id=vlan_peer type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('vlan_peer')">
+ <td>
+ <span id=vlan_peer_result>...</span>
+
+ <tr>
+ <td><b>In-band Management VLAN
+ <td align=right><span id="platform/active_inband_vlan">...</span>
+ <td align=right>
+ <span id="platform/vlan_inband">...</span>
+ <input type=submit value="Apply Now" onclick="CraftUI.config('vlan_inband', 1)">
+ <td>
+ <input id=vlan_inband type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('vlan_inband')">
+ <td>
+ <span id=vlan_inband_result>...</span>
+
+ </table>
+ <b>Radio Parameters:</b>
+ <table>
+ <tr>
+ <td align=center><b>Parameter
+ <td align=center><b>Active Value
+ <td align=center><b>Configure and Apply
+ <td align=center><b>Status
+
+ <tr>
+ <td><b>High Frequency
+ <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')">
+ <td>
+ <span id=freq_hi_result>...</span>
+
+ <tr>
+ <td><b>Low Frequency
+ <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')">
+ <td>
+ <span id=freq_lo_result>...</span>
+
+ <tr>
+ <td><b>High Frequency Mode
+ <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')">
+ <td>
+ <span id=mode_hi_result>...</span>
+
+ <tr>
+ <td><b>Power Level
+ <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')">
+ <td>
+ <span id=tx_powerlevel_result>...</span>
+
+ <tr>
+ <td><b>Power Enabled
+ <td align=right><span id="radio/paLnaPowerEnabled">...</span>
+ <td>
+ <input id=tx_on type=text value="">
+ <input type=submit value=Configure onclick="CraftUI.config('tx_on')">
+ <td>
+ <span id=tx_on_result>...</span>
+
+ </table>
+ </div>
+ </div>
+ <div class="tab">
+ <input type="radio" id="tab-2" name="tab-group-1">
+ <label for="tab-2">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>
+ </div>
+ </div>
+ </div>
+ <script src="static/craft.js"></script>
+</body>
+</html>
diff --git a/craftui/www/index.thtml b/craftui/www/index.thtml
index 4388f77..5d3e841 100644
--- a/craftui/www/index.thtml
+++ b/craftui/www/index.thtml
@@ -11,11 +11,11 @@
<body>
<header>
<section>
- <h1><a href=https://fiber.google.com/myfiber/><img src=static/logo.png alt="Google Fiber"></a></h1>
+ <h1><img src=static/logo.png alt="Google Fiber"></h1>
<nav>
<ul>
<li class=active><a href=/>GFCH100</a></li>
- <li ><a href=craft/config/>Configuration</a></li>
+ <li ><a href=/config>Configuration</a></li>
<li ><a href={{ peerurl }} target=_blank>Peer</a></li>
</ul>
</nav>
@@ -27,6 +27,7 @@
<input type="radio" id="tab-1" name="tab-group-1" checked>
<label for="tab-1">Platform</label>
<div class="content">
+ <b>Serial Number:</b><span class="values" id="platform/serialno">...</span><br>
<b>Platform:</b><span class="values" id="platform/platform">...</span><br>
<b>Software Version:</b><span class="values" id="platform/version">...</span><br>
<b>Software Date:</b><span class="values" id="platform/softwaredate">...</span><br>
@@ -38,68 +39,227 @@
<input type="radio" id="tab-2" name="tab-group-1">
<label for="tab-2">Network</label>
<div class="content">
- <b>Craft Port IP Address:</b><span class="values" id="platform/craft_ipaddr">...</span><br>
- <b>Local IP Address:</b><span class="values" id="platform/local_ipaddr">...</span><br>
- <b>Peer IP Address:</b><span class="values" id="platform/peer_ipaddr">...</span><br>
- <b>Inband Vlan:</b><span class="values" id="platform/vlan_inband">...</span><br>
- <b>Peer Vlan:</b><span class="values" id="platform/vlan_peer">...</span><br>
+ <b>IP Addresses:</b>
+ <table>
+ <tr>
+ <td align=center><b>Port</b></td>
+ <td align=center><b>IPv4</b></td>
+ <td align=center><b>IPv6</b></td></tr>
+ <tr>
+ <td><b>Craft</b></td>
+ <td align=right><span id="platform/active_craft_inet">...</span></td>
+ <td align=right><span id="platform/active_craft_inet6">...</span></td></tr>
+ <tr>
+ <td><b>Out-of-Band (PoE)</b></td>
+ <td align=right><span id="platform/active_poe_inet">...</span></td>
+ <td align=right><span id="platform/active_poe_inet6">...</span></td></tr>
+ <tr>
+ <td><b>In-Band</b></td>
+ <td align=right><span id="platform/active_inband_inet">...</span></td>
+ <td align=right><span id="platform/active_inband_inet6">...</span></td></tr>
+ <tr>
+ <td><b>Link (to peer)</b></td>
+ <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>
<table>
<tr>
<td><b></b></td>
- <td colspan=3><b>received</b></td>
- <td colspan=3><b>transmitted</b></td></tr>
+ <td colspan=3 align=center><b>received</b></td>
+ <td colspan=3 align=center><b>transmitted</b></td>
+ <td colspan=9 align=center><b>errors</b></td></tr>
<tr>
- <td><b>interface</b></td>
- <td><b>bytes (UC BC MC)</b></td>
- <td><b>frames (<64 <128 <256 <512 <1024 jumbo)</b></td>
- <td><b>errors</b></td>
- <td><b>bytes (UC BC MC)</b></td>
- <td><b>frames</b></td>
- <td><b>errors</b></td></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>bytes</b></td>
+ <td align=center><b>frames</b></td>
+ <td align=center><b>multicast</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>modem (from/to switch)</td>
- <td>
- <span id="modem/network/rxCounters/bytes">...</span>
- (<span id="modem/network/rxCounters/unicast">...</span>
- <span id="modem/network/rxCounters/broadcast">...</span>
- <span id="modem/network/rxCounters/multicast">...</span>)</td>
- <td>
- <span id="modem/network/rxCounters/frames">...</span>
- (<span id="modem/network/rxCounters/frames64">...</span>
- <span id="modem/network/rxCounters/frames65_127">...</span>
- <span id="modem/network/rxCounters/frames128_255">...</span>
- <span id="modem/network/rxCounters/frames256_511">...</span>
- <span id="modem/network/rxCounters/frames512_1023">...</span>
- <span id="modem/network/rxCounters/frames1024_1518">...</span>
- <span id="modem/network/rxCounters/framesJumbo">...</span>)</td>
- <td>
- CRC: <span id="modem/network/rxCounters/crcErrors">...</span>
- Undersize: <span id="modem/network/rxCounters/framesUndersized">...</span></td>
- <td>
- <span id="modem/network/txCounters/bytes">...</span>
- (<span id="modem/network/txCounters/unicast">...</span>
- <span id="modem/network/txCounters/broadcast">...</span>
- <span id="modem/network/txCounters/multicast">...</span>)</td>
- <td>
- <span id="modem/network/txCounters/frames">...</span>
- (<span id="modem/network/txCounters/frames64">...</span>
- <span id="modem/network/txCounters/frames65_127">...</span>
- <span id="modem/network/txCounters/frames128_255">...</span>
- <span id="modem/network/txCounters/frames256_511">...</span>
- <span id="modem/network/txCounters/frames512_1023">...</span>
- <span id="modem/network/txCounters/frames1024_1518">...</span>
- <span id="modem/network/txCounters/framesJumbo">...</span>)</td>
- <td>
- CRC: <span id="modem/network/txCounters/crcErrors">...</span>
- Undersize: <span id="modem/network/txCounters/framesUndersized">...</span></td></tr>
+ <td><b>Modem (from/to switch)<b></td>
+ <td align=right><span id="modem/network/rxCounters/bytes">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/frames">...</span></td>
+ <td align=right><span id="modem/network/rxCounters/multicast">...</span></td>
+
+ <td align=right><span id="modem/network/txCounters/bytes">...</span></td>
+ <td align=right><span id="modem/network/txCounters/frames">...</span></td>
+ <td align=right><span id="modem/network/txCounters/multicast">...</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>
+
+ <tr>
+ <td><b>Craft<b></td>
+ <td align=right><span id="platform/craft_rx_bytes">...</span></td>
+ <td align=right><span id="platform/craft_rx_packets">...</span></td>
+ <td align=right><span id="platform/craft_multicast">...</span></td>
+
+ <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><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>
+ <td><b>Out-of-Band (PoE)<b></td>
+ <td align=right><span id="platform/poe_rx_bytes">...</span></td>
+ <td align=right><span id="platform/poe_rx_packets">...</span></td>
+ <td align=right><span id="platform/poe_multicast">...</span></td>
+
+ <td align=right><span id="platform/poe_tx_bytes">...</span></td>
+ <td align=right><span id="platform/poe_tx_packets">...</span></td>
+ <td align=right>-</td>
+
+ <td align=right><span id="platform/poe_rx_errors">...</span></td>
+ <td align=right><span id="platform/poe_rx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/poe_tx_errors">...</span></td>
+ <td align=right><span id="platform/poe_tx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/poe_collisions">...</span></td>
+
+ <tr>
+ <td><b>In-Band<b></td>
+ <td align=right><span id="platform/inband_rx_bytes">...</span></td>
+ <td align=right><span id="platform/inband_rx_packets">...</span></td>
+ <td align=right><span id="platform/inband_multicast">...</span></td>
+
+ <td align=right><span id="platform/inband_tx_bytes">...</span></td>
+ <td align=right><span id="platform/inband_tx_packets">...</span></td>
+ <td align=right>-</td>
+
+ <td align=right><span id="platform/inband_rx_errors">...</span></td>
+ <td align=right><span id="platform/inband_rx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/inband_tx_errors">...</span></td>
+ <td align=right><span id="platform/inband_tx_dropped">...</span></td>
+ <td align=right>-</td>
+ <td align=right>-</td>
+ <td align=right><span id="platform/inband_collisions">...</span></td>
+
+ <tr>
+ <td><b>Link (to peer)<b></td>
+ <td align=right><span id="platform/link_rx_bytes">...</span></td>
+ <td align=right><span id="platform/link_rx_packets">...</span></td>
+ <td align=right><span id="platform/link_multicast">...</span></td>
+
+ <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><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>
+
<tr>
- <td>SOC (from/to switch)</td>
+ <td><b>Switch Port 0/0 (PoE)</b></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>
+ <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>SOC (port 0)</td>
+ <td><b>Switch Port 0/4 (SOC)</b></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>
+ <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>POE (port 0)</td>
+ <td><b>Switch Port 0/24 (modem)</b></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>
+ <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>POE (port 0)</td>
+ <td><b>Switch Port 0/25 (SFP+)</b></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>
+ <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>
diff --git a/craftui/www/static/craft.js b/craftui/www/static/craft.js
index 620ff71..04e0f2c 100644
--- a/craftui/www/static/craft.js
+++ b/craftui/www/static/craft.js
@@ -68,9 +68,6 @@
CraftUI.flattenAndUpdateFields(list, '');
}
CraftUI.updateField('unhandled', self.unhandled);
- if (self.unhandled.length > 0) {
- console.log(self.unhandled);
- }
};
var payload = [];
payload.push('checksum=' + encodeURIComponent(CraftUI.info.checksum));
@@ -79,4 +76,35 @@
xhr.send();
};
+CraftUI.config = function(key, activate) {
+ // POST as json
+ var el = document.getElementById(key);
+ var value = el.value;
+ var xhr = new XMLHttpRequest();
+ xhr.open('post', 'content.json');
+ xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
+ var data;
+ if (activate) {
+ data = { config: [ { [key + "_activate"]: "true" } ] };
+ } else {
+ data = { config: [ { [key]: value } ] };
+ }
+ var txt = JSON.stringify(data);
+ var resultid = key + "_result"
+ var el = document.getElementById(resultid);
+ xhr.onload = function(e) {
+ var json = JSON.parse(xhr.responseText);
+ if (json.error == 0) {
+ el.innerHTML = "Success!";
+ } else {
+ el.innerHTML = "Error: " + json.errorstring;
+ }
+ CraftUI.getInfo();
+ }
+ xhr.onerror = function(e) {
+ el.innerHTML = xhr.statusText + xhr.responseText;
+ }
+ xhr.send(txt);
+};
+
new CraftUI();