diff --git a/node/bindata.go b/node/bindata.go index e14f70c9d1f63f87645f2b90b4b6e7de922b5ea4..dfadcf5f91e2966187baf97fe8baa16f4c765591 100644 --- a/node/bindata.go +++ b/node/bindata.go @@ -574,24 +574,26 @@ var _templatesIndexHtml = []byte(`<!DOCTYPE html> <h4>Streams</h4> <ul> {{$domain := .Domain}} - {{range .Mounts}} + {{range $m := .Mounts}} <li> - <a href="http://{{$domain}}{{.Mount.Path}}" - {{if .Mount.RelayUrl}} - data-toggle="tooltip" data-delay="300" title="relay of {{.Mount.RelayUrl}}" + <a href="http://{{$domain}}{{$m.Mount.Path}}" + {{if $m.Mount.RelayUrl}} + data-toggle="tooltip" data-delay="300" title="relay of {{$m.Mount.RelayUrl}}" + {{else if and $m.IcecastMount $m.IcecastMount.Description}} + data-toggle="tooltip" data-delay="300" title="{{$m.IcecastMount.Description}}" {{end}} - >{{.Mount.Path}}</a> - <a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> - <span class="badge badge-secondary">{{.Listeners}}</span> - {{if .TransMounts}} + >{{$m.Mount.Path}}</a> + <a href="http://{{$domain}}{{$m.Mount.Path}}.m3u">(m3u)</a> + <span class="badge badge-secondary">{{$m.Listeners}}</span> + {{if $m.TransMounts}} <ul> - {{range .TransMounts}} + {{range $tm := $m.TransMounts}} <li> - <a href="http://{{$domain}}{{.Mount.Path}}" - data-toggle="tooltip" data-delay="300" title="{{.Mount.TranscodeParams.String}}" - >{{.Mount.Path}}</a> - <a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> - <span class="badge badge-secondary">{{.Listeners}}</span> + <a href="http://{{$domain}}{{$tm.Mount.Path}}" + data-toggle="tooltip" data-delay="300" title="{{$tm.Mount.TranscodeParams.String}}" + >{{$tm.Mount.Path}}</a> + <a href="http://{{$domain}}{{$tm.Mount.Path}}.m3u">(m3u)</a> + <span class="badge badge-secondary">{{$tm.Listeners}}</span> </li> {{end}} </ul> @@ -604,16 +606,25 @@ var _templatesIndexHtml = []byte(`<!DOCTYPE html> <div class="col-lg-6"> <h4>Nodes</h4> <ul> - {{range .Nodes}} + {{range $n := .Nodes}} <li> - {{.Name}} <span class="badge badge-secondary">{{.NumListeners}}</span> - {{if not .IcecastOk}}<span class="label label-danger">IC_DOWN</span>{{end}} + {{$n.Name}} <span class="badge badge-secondary">{{$n.NumListeners}}</span> + {{if not $n.IcecastOk}}<span class="badge badge-danger">IC_DOWN</span>{{end}} </li> {{end}} </ul> </div> </div> + <div class="row"> + <p> + <small> + Click on a stream to listen to it.<br> + Hover on a stream to see more details. + </small> + </p> + </div> + <div class="footer"> powered by <a href="https://git.autistici.org/ale/autoradio"> @@ -639,7 +650,7 @@ func templatesIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/index.html", size: 2472, mode: os.FileMode(420), modTime: time.Unix(1555194569, 0)} + info := bindataFileInfo{name: "templates/index.html", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1555197346, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/node/debug.go b/node/debug.go index 5020d98e4972df0f5dfffe8b4dd093e8708bc535..89efa48ce420e72d64d245c21a4b06c1898557eb 100644 --- a/node/debug.go +++ b/node/debug.go @@ -1,7 +1,12 @@ package node import ( + "bytes" + "html/template" + "log" + "net/http" "sort" + "strconv" pb "git.autistici.org/ale/autoradio/proto" ) @@ -9,12 +14,13 @@ import ( // mountStatus reports the configuration and status of a mount, // including eventual transcoded mounts that source it. type mountStatus struct { - Mount *pb.Mount - Listeners int - TransMounts []*mountStatus + Mount *pb.Mount + IcecastMount *pb.IcecastMount + Listeners int + TransMounts []*mountStatus } -func newMountStatus(m *pb.Mount, nodes []*nodeInfo) *mountStatus { +func newMountStatus(m *pb.Mount, nodes []*nodeInfo, icecastMounts map[string]*pb.IcecastMount) *mountStatus { var listeners int for _, n := range nodes { for _, ims := range n.status.IcecastMounts { @@ -25,8 +31,9 @@ func newMountStatus(m *pb.Mount, nodes []*nodeInfo) *mountStatus { } } return &mountStatus{ - Mount: m, - Listeners: listeners, + Mount: m, + Listeners: listeners, + IcecastMount: icecastMounts[m.Path], } } @@ -42,14 +49,16 @@ func (l mountStatusList) Less(i, j int) bool { // current list of nodes) to a nicely sorted and tree-aggregated list // of mountStatus objects. The list of nodes can be nil, in which case // listener statistics will be omitted. -func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus { +func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo, icecastMounts map[string]*pb.IcecastMount) []*mountStatus { // Aggregate stats, and create a tree of transcoding mounts. + // We only recurse twice because we don't allow transcodes of + // transcoded streams. ms := make(map[string]*mountStatus) for _, m := range mounts { if m.HasTranscoder() { continue } - ms[m.Path] = newMountStatus(m, nodes) + ms[m.Path] = newMountStatus(m, nodes, icecastMounts) } for _, m := range mounts { if !m.HasTranscoder() { @@ -59,7 +68,7 @@ func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus { if src == nil { continue } - src.TransMounts = append(src.TransMounts, newMountStatus(m, nodes)) + src.TransMounts = append(src.TransMounts, newMountStatus(m, nodes, icecastMounts)) } msl := make([]*mountStatus, 0, len(ms)) for _, m := range ms { @@ -75,3 +84,38 @@ func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus { } return msl } + +func serveStatusPage(n *Node, w http.ResponseWriter, r *http.Request, tpl *template.Template, domain string) { + // Convert the list of nodes to just the status. While we're + // at it, build a map of mount path -> exemplary IcecastMount, + // which we use to show the current song artist / title. + nodes := n.lb.getNodes() + statuses := make([]*pb.Status, 0, len(nodes)) + exemplary := make(map[string]*pb.IcecastMount) + for _, ni := range nodes { + statuses = append(statuses, ni.status) + for _, im := range ni.status.IcecastMounts { + if _, ok := exemplary[im.Path]; !ok { + exemplary[im.Path] = im + } + } + } + + ms := mountsToStatus(n.mounts.GetMounts(), nodes, exemplary) + ctx := struct { + Domain string + Nodes []*pb.Status + Mounts []*mountStatus + }{domain, statuses, ms} + + var buf bytes.Buffer + if err := tpl.ExecuteTemplate(&buf, "index.html", ctx); err != nil { + log.Printf("error rendering status page: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Content-Length", strconv.Itoa(buf.Len())) + addDefaultHeaders(w) + w.Write(buf.Bytes()) //nolint +} diff --git a/node/http.go b/node/http.go index aed2762feaac7f1911709010e01e8d48d347f1c6..006b1b8b576cc6bdd127a097c17f6d0712a61373 100644 --- a/node/http.go +++ b/node/http.go @@ -4,7 +4,6 @@ package node //go:generate go-bindata --nocompress --pkg node static/... templates/... import ( - "bytes" "context" "flag" "fmt" @@ -209,33 +208,6 @@ func sendRedirect(w http.ResponseWriter, r *http.Request, targetURL string) { http.Redirect(w, r, targetURL, code) } -func serveStatusPage(n *Node, w http.ResponseWriter, r *http.Request, tpl *template.Template, domain string) { - // Convert the list of nodes to just the status. - nodes := n.lb.getNodes() - statuses := make([]*pb.Status, 0, len(nodes)) - for _, ni := range nodes { - statuses = append(statuses, ni.status) - } - - ms := mountsToStatus(n.mounts.GetMounts(), nodes) - ctx := struct { - Domain string - Nodes []*pb.Status - Mounts []*mountStatus - }{domain, statuses, ms} - - var buf bytes.Buffer - if err := tpl.ExecuteTemplate(&buf, "index.html", ctx); err != nil { - log.Printf("error rendering status page: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.Header().Set("Content-Length", strconv.Itoa(buf.Len())) - addDefaultHeaders(w) - w.Write(buf.Bytes()) //nolint -} - func addDefaultHeaders(w http.ResponseWriter) { w.Header().Set("Expires", "-1") w.Header().Set("Cache-Control", "no-cache,no-store") diff --git a/node/templates/index.html b/node/templates/index.html index 37e72821b90869b84d1f82cd1a64360eb600fe17..654896c65c118ac53cb97a13a93d6bb38d634714 100644 --- a/node/templates/index.html +++ b/node/templates/index.html @@ -22,24 +22,26 @@ <h4>Streams</h4> <ul> {{$domain := .Domain}} - {{range .Mounts}} + {{range $m := .Mounts}} <li> - <a href="http://{{$domain}}{{.Mount.Path}}" - {{if .Mount.RelayUrl}} - data-toggle="tooltip" data-delay="300" title="relay of {{.Mount.RelayUrl}}" + <a href="http://{{$domain}}{{$m.Mount.Path}}" + {{if $m.Mount.RelayUrl}} + data-toggle="tooltip" data-delay="300" title="relay of {{$m.Mount.RelayUrl}}" + {{else if and $m.IcecastMount $m.IcecastMount.Description}} + data-toggle="tooltip" data-delay="300" title="{{$m.IcecastMount.Description}}" {{end}} - >{{.Mount.Path}}</a> - <a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> - <span class="badge badge-secondary">{{.Listeners}}</span> - {{if .TransMounts}} + >{{$m.Mount.Path}}</a> + <a href="http://{{$domain}}{{$m.Mount.Path}}.m3u">(m3u)</a> + <span class="badge badge-secondary">{{$m.Listeners}}</span> + {{if $m.TransMounts}} <ul> - {{range .TransMounts}} + {{range $tm := $m.TransMounts}} <li> - <a href="http://{{$domain}}{{.Mount.Path}}" - data-toggle="tooltip" data-delay="300" title="{{.Mount.TranscodeParams.String}}" - >{{.Mount.Path}}</a> - <a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> - <span class="badge badge-secondary">{{.Listeners}}</span> + <a href="http://{{$domain}}{{$tm.Mount.Path}}" + data-toggle="tooltip" data-delay="300" title="{{$tm.Mount.TranscodeParams.String}}" + >{{$tm.Mount.Path}}</a> + <a href="http://{{$domain}}{{$tm.Mount.Path}}.m3u">(m3u)</a> + <span class="badge badge-secondary">{{$tm.Listeners}}</span> </li> {{end}} </ul> @@ -52,16 +54,25 @@ <div class="col-lg-6"> <h4>Nodes</h4> <ul> - {{range .Nodes}} + {{range $n := .Nodes}} <li> - {{.Name}} <span class="badge badge-secondary">{{.NumListeners}}</span> - {{if not .IcecastOk}}<span class="badge badge-danger">IC_DOWN</span>{{end}} + {{$n.Name}} <span class="badge badge-secondary">{{$n.NumListeners}}</span> + {{if not $n.IcecastOk}}<span class="badge badge-danger">IC_DOWN</span>{{end}} </li> {{end}} </ul> </div> </div> + <div class="row"> + <p> + <small> + Click on a stream to listen to it.<br> + Hover on a stream to see more details. + </small> + </p> + </div> + <div class="footer"> powered by <a href="https://git.autistici.org/ale/autoradio">