| package main |
| |
| import ( |
| "context" |
| "fmt" |
| "net/http" |
| "sort" |
| "strings" |
| |
| "github.com/gobuffalo/packr" |
| "github.com/golang/glog" |
| "github.com/q3k/statusz" |
| "vbom.ml/util/sortorder" |
| |
| "code.hackerspace.pl/q3k/topo/graph" |
| "code.hackerspace.pl/q3k/topo/state" |
| ) |
| |
| type ServiceConfig struct { |
| DebugListen string |
| } |
| |
| type Service struct { |
| gr *graph.Graph |
| stm *state.StateManager |
| config ServiceConfig |
| } |
| |
| func NewService(gr *graph.Graph, stm *state.StateManager, c ServiceConfig) *Service { |
| return &Service{ |
| gr: gr, |
| stm: stm, |
| config: c, |
| } |
| } |
| |
| type TopologyStatus struct { |
| } |
| |
| const topologyFragment = ` |
| <script src="/assets/viz.js"></script> |
| <script> |
| |
| var viz = new Viz({ workerURL: '/assets/full.render.js' }); |
| var xmlhttp = new XMLHttpRequest(); |
| xmlhttp.onreadystatechange = function() { |
| if (this.readyState == 4 && this.status == 200) { |
| var dot = this.responseText; |
| viz.renderSVGElement(dot) |
| .then(function(element) { |
| document.getElementById("graph").appendChild(element); |
| }); |
| } |
| }; |
| xmlhttp.open('GET', '/debug/graphviz'); |
| xmlhttp.send(); |
| |
| </script> |
| <div id="graph" style="text-align: center;"></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", 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("Topology", topologyFragment, func(ctx context.Context) interface{} { |
| return &TopologyStatus{} |
| }) |
| glog.Infof("Debug listening on %s....", s.config.DebugListen) |
| http.ListenAndServe(s.config.DebugListen, nil) |
| } |