implement basic status checking
diff --git a/service.go b/service.go
index 916299c..03fd218 100644
--- a/service.go
+++ b/service.go
@@ -14,6 +14,8 @@
 
 	"code.hackerspace.pl/q3k/topo/graph"
 	"code.hackerspace.pl/q3k/topo/state"
+
+	pb "code.hackerspace.pl/q3k/topo/proto/control"
 )
 
 type ServiceConfig struct {
@@ -34,9 +36,6 @@
 	}
 }
 
-type TopologyStatus struct {
-}
-
 const topologyFragment = `
   <script src="/assets/viz.js"></script>
   <script>
@@ -59,58 +58,179 @@
   <div id="graph" style="text-align: center;"></div>
 `
 
+const switchportsFragment = `
+    <style type="text/css">
+		.table td,th {
+			background-color: #eee;
+			padding: 0.2em 0.4em 0.2em 0.4em;
+		}
+		.table th {
+			background-color: #c0c0c0;
+		}
+		.table {
+			background-color: #fff;
+			border-spacing: 0.2em;
+			margin-left: auto;
+			margin-right: auto;
+		}
+	</style>
+	<div>
+	<table class="table">
+		<tr>
+      		<th>Switch</th>
+      		<th>Port</th>
+			<th>Link State</th>
+			<th>Port Mode</th>
+			<th>MTU</th>
+			<th>Sync Status</th>
+		</tr>
+		{{range .Ports }}
+		{{ if .Managed }}
+		<tr>
+		{{ else }}
+		<tr style="opacity: 0.5">
+		{{ end}}
+			<td style="text-align: right;">{{ .Switch }}</td>
+			<td>{{ .Name }}</td>
+			{{ if eq .State "DOWN" }}
+			<td style="background-color: #ff3030;">{{ .State }}</td>
+			{{ else }}
+			<td>{{ .State }}</td>
+			{{ end }}
+			<td>{{ .Mode }}</td>
+			<td>{{ .MTU }}</td>
+			{{ if .Managed }}
+			<td style="background-color: #30ff30;">OK</td>
+			{{ else }}
+			<td><i>Unmanaged</i></td>
+			{{ end }}
+		</tr>
+		{{end}}
+	</table>
+	</div>
+`
+
 func (s *Service) Run() {
 	assets := packr.NewBox("./assets")
 	http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(assets)))
-
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		http.Redirect(w, r, "/debug/status", http.StatusSeeOther)
 	})
+	http.HandleFunc("/debug/graphviz", s.httpHandleGraphviz)
 
-	http.HandleFunc("/debug/graphviz", func(w http.ResponseWriter, r *http.Request) {
-		fmt.Fprintf(w, "graph G {\n")
-		fmt.Fprintf(w, "  ranksep = 2\n")
-		fmt.Fprintf(w, "  splines = polyline\n")
-		fmt.Fprintf(w, "  rankdir = LR\n")
-		for _, machine := range s.gr.Machines {
-			portNames := []string{}
-			for _, port := range machine.Ports {
-				name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
-				portNames = append(portNames, name)
-			}
-			ports := strings.Join(portNames, "|")
-			fmt.Fprintf(w, "  %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
-		}
-		for _, sw := range s.gr.Switches {
-			portNames := []string{}
-			portsOrdered := []*graph.SwitchPort{}
-			for _, port := range sw.Ports {
-				portsOrdered = append(portsOrdered, port)
-			}
-			sort.Slice(portsOrdered, func(i, j int) bool {
-				return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
-			})
-			for _, port := range portsOrdered {
-				name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
-				portNames = append(portNames, name)
-			}
-			ports := strings.Join(portNames, "|")
-			fmt.Fprintf(w, "  %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
-		}
-		for _, machine := range s.gr.Machines {
-			for _, port := range machine.Ports {
-				if port.OtherEnd == nil {
-					continue
-				}
-				fmt.Fprintf(w, "  %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
-			}
-		}
-		fmt.Fprintf(w, "}\n")
-	})
-
+	statusz.AddStatusPart("Switch Ports", switchportsFragment, s.statusHandleSwitchports)
 	statusz.AddStatusPart("Topology", topologyFragment, func(ctx context.Context) interface{} {
-		return &TopologyStatus{}
+		return nil
 	})
 	glog.Infof("Debug listening on %s....", s.config.DebugListen)
 	http.ListenAndServe(s.config.DebugListen, nil)
 }
+
+func (s *Service) statusHandleSwitchports(ctx context.Context) interface{} {
+	managedPorts := make(map[string]bool)
+	s.gr.Mu.RLock()
+	for _, sw := range s.gr.Switches {
+		for _, port := range sw.Ports {
+			managedPorts[sw.Name+"|"+port.Name] = true
+		}
+	}
+	s.gr.Mu.RUnlock()
+
+	s.stm.Mu.RLock()
+	defer s.stm.Mu.RUnlock()
+
+	res := struct {
+		Ports []*struct {
+			Switch  string
+			Name    string
+			State   string
+			Mode    string
+			Managed bool
+			MTU     string
+		}
+	}{}
+	for _, sw := range s.stm.Switches {
+		for _, po := range sw.Ports {
+			state := "INVALID"
+			switch po.Proto.LinkState {
+			case pb.SwitchPort_LINKSTATE_DOWN:
+				state = "DOWN"
+			case pb.SwitchPort_LINKSTATE_UP:
+				state = "UP"
+			}
+			mode := "INVALID"
+			switch po.Proto.PortMode {
+			case pb.SwitchPort_PORTMODE_SWITCHPORT_UNTAGGED:
+				mode = fmt.Sprintf("UNTAGGED (%d)", po.Proto.VlanNative)
+			case pb.SwitchPort_PORTMODE_SWITCHPORT_TAGGED:
+				mode = fmt.Sprintf("TAGGED (%v)", po.Proto.VlanTagged)
+			case pb.SwitchPort_PORTMODE_SWITCHPORT_GENERIC:
+				mode = "GENERIC"
+			case pb.SwitchPort_PORTMODE_ROUTED:
+				mode = "ROUTED"
+			case pb.SwitchPort_PORTMODE_MANGLED:
+				mode = "MANGLED"
+			}
+
+			managed := managedPorts[sw.Name+"|"+po.Proto.Name]
+			res.Ports = append(res.Ports, &struct {
+				Switch  string
+				Name    string
+				State   string
+				Mode    string
+				Managed bool
+				MTU     string
+			}{
+				Switch:  sw.Name,
+				Name:    po.Proto.Name,
+				State:   state,
+				Mode:    mode,
+				Managed: managed,
+				MTU:     fmt.Sprintf("%d", po.Proto.Mtu),
+			})
+		}
+	}
+
+	return res
+}
+
+func (s *Service) httpHandleGraphviz(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintf(w, "graph G {\n")
+	fmt.Fprintf(w, "  ranksep = 2\n")
+	fmt.Fprintf(w, "  splines = polyline\n")
+	fmt.Fprintf(w, "  rankdir = LR\n")
+	for _, machine := range s.gr.Machines {
+		portNames := []string{}
+		for _, port := range machine.Ports {
+			name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
+			portNames = append(portNames, name)
+		}
+		ports := strings.Join(portNames, "|")
+		fmt.Fprintf(w, "  %s [shape=record label=\"{ %s | { %s }}\"]\n", machine.Name, machine.Name, ports)
+	}
+	for _, sw := range s.gr.Switches {
+		portNames := []string{}
+		portsOrdered := []*graph.SwitchPort{}
+		for _, port := range sw.Ports {
+			portsOrdered = append(portsOrdered, port)
+		}
+		sort.Slice(portsOrdered, func(i, j int) bool {
+			return sortorder.NaturalLess(portsOrdered[i].Name, portsOrdered[j].Name)
+		})
+		for _, port := range portsOrdered {
+			name := fmt.Sprintf("<%s> %s", port.Name, port.Name)
+			portNames = append(portNames, name)
+		}
+		ports := strings.Join(portNames, "|")
+		fmt.Fprintf(w, "  %s [shape=record label=\"{{ %s } | %s}\"]\n", sw.Name, ports, sw.Name)
+	}
+	for _, machine := range s.gr.Machines {
+		for _, port := range machine.Ports {
+			if port.OtherEnd == nil {
+				continue
+			}
+			fmt.Fprintf(w, "  %s:%q:e -- %s:%q:w\n", machine.Name, port.Name, port.OtherEnd.Switch.Name, port.OtherEnd.Name)
+		}
+	}
+	fmt.Fprintf(w, "}\n")
+}