Skip to content
Snippets Groups Projects
Commit a7261348 authored by ale's avatar ale
Browse files

Display stream metadata in the status page

parent 70b755b8
No related branches found
No related tags found
1 merge request!1v2.0
...@@ -574,24 +574,26 @@ var _templatesIndexHtml = []byte(`<!DOCTYPE html> ...@@ -574,24 +574,26 @@ var _templatesIndexHtml = []byte(`<!DOCTYPE html>
<h4>Streams</h4> <h4>Streams</h4>
<ul> <ul>
{{$domain := .Domain}} {{$domain := .Domain}}
{{range .Mounts}} {{range $m := .Mounts}}
<li> <li>
<a href="http://{{$domain}}{{.Mount.Path}}" <a href="http://{{$domain}}{{$m.Mount.Path}}"
{{if .Mount.RelayUrl}} {{if $m.Mount.RelayUrl}}
data-toggle="tooltip" data-delay="300" title="relay of {{.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}} {{end}}
>{{.Mount.Path}}</a> >{{$m.Mount.Path}}</a>
<a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> <a href="http://{{$domain}}{{$m.Mount.Path}}.m3u">(m3u)</a>
<span class="badge badge-secondary">{{.Listeners}}</span> <span class="badge badge-secondary">{{$m.Listeners}}</span>
{{if .TransMounts}} {{if $m.TransMounts}}
<ul> <ul>
{{range .TransMounts}} {{range $tm := $m.TransMounts}}
<li> <li>
<a href="http://{{$domain}}{{.Mount.Path}}" <a href="http://{{$domain}}{{$tm.Mount.Path}}"
data-toggle="tooltip" data-delay="300" title="{{.Mount.TranscodeParams.String}}" data-toggle="tooltip" data-delay="300" title="{{$tm.Mount.TranscodeParams.String}}"
>{{.Mount.Path}}</a> >{{$tm.Mount.Path}}</a>
<a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> <a href="http://{{$domain}}{{$tm.Mount.Path}}.m3u">(m3u)</a>
<span class="badge badge-secondary">{{.Listeners}}</span> <span class="badge badge-secondary">{{$tm.Listeners}}</span>
</li> </li>
{{end}} {{end}}
</ul> </ul>
...@@ -604,16 +606,25 @@ var _templatesIndexHtml = []byte(`<!DOCTYPE html> ...@@ -604,16 +606,25 @@ var _templatesIndexHtml = []byte(`<!DOCTYPE html>
<div class="col-lg-6"> <div class="col-lg-6">
<h4>Nodes</h4> <h4>Nodes</h4>
<ul> <ul>
{{range .Nodes}} {{range $n := .Nodes}}
<li> <li>
{{.Name}} <span class="badge badge-secondary">{{.NumListeners}}</span> {{$n.Name}} <span class="badge badge-secondary">{{$n.NumListeners}}</span>
{{if not .IcecastOk}}<span class="label label-danger">IC_DOWN</span>{{end}} {{if not $n.IcecastOk}}<span class="badge badge-danger">IC_DOWN</span>{{end}}
</li> </li>
{{end}} {{end}}
</ul> </ul>
</div> </div>
</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"> <div class="footer">
powered by powered by
<a href="https://git.autistici.org/ale/autoradio"> <a href="https://git.autistici.org/ale/autoradio">
...@@ -639,7 +650,7 @@ func templatesIndexHtml() (*asset, error) { ...@@ -639,7 +650,7 @@ func templatesIndexHtml() (*asset, error) {
return nil, err 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} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
......
package node package node
import ( import (
"bytes"
"html/template"
"log"
"net/http"
"sort" "sort"
"strconv"
pb "git.autistici.org/ale/autoradio/proto" pb "git.autistici.org/ale/autoradio/proto"
) )
...@@ -9,12 +14,13 @@ import ( ...@@ -9,12 +14,13 @@ import (
// mountStatus reports the configuration and status of a mount, // mountStatus reports the configuration and status of a mount,
// including eventual transcoded mounts that source it. // including eventual transcoded mounts that source it.
type mountStatus struct { type mountStatus struct {
Mount *pb.Mount Mount *pb.Mount
Listeners int IcecastMount *pb.IcecastMount
TransMounts []*mountStatus 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 var listeners int
for _, n := range nodes { for _, n := range nodes {
for _, ims := range n.status.IcecastMounts { for _, ims := range n.status.IcecastMounts {
...@@ -25,8 +31,9 @@ func newMountStatus(m *pb.Mount, nodes []*nodeInfo) *mountStatus { ...@@ -25,8 +31,9 @@ func newMountStatus(m *pb.Mount, nodes []*nodeInfo) *mountStatus {
} }
} }
return &mountStatus{ return &mountStatus{
Mount: m, Mount: m,
Listeners: listeners, Listeners: listeners,
IcecastMount: icecastMounts[m.Path],
} }
} }
...@@ -42,14 +49,16 @@ func (l mountStatusList) Less(i, j int) bool { ...@@ -42,14 +49,16 @@ func (l mountStatusList) Less(i, j int) bool {
// current list of nodes) to a nicely sorted and tree-aggregated list // 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 // of mountStatus objects. The list of nodes can be nil, in which case
// listener statistics will be omitted. // 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. // 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) ms := make(map[string]*mountStatus)
for _, m := range mounts { for _, m := range mounts {
if m.HasTranscoder() { if m.HasTranscoder() {
continue continue
} }
ms[m.Path] = newMountStatus(m, nodes) ms[m.Path] = newMountStatus(m, nodes, icecastMounts)
} }
for _, m := range mounts { for _, m := range mounts {
if !m.HasTranscoder() { if !m.HasTranscoder() {
...@@ -59,7 +68,7 @@ func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus { ...@@ -59,7 +68,7 @@ func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus {
if src == nil { if src == nil {
continue continue
} }
src.TransMounts = append(src.TransMounts, newMountStatus(m, nodes)) src.TransMounts = append(src.TransMounts, newMountStatus(m, nodes, icecastMounts))
} }
msl := make([]*mountStatus, 0, len(ms)) msl := make([]*mountStatus, 0, len(ms))
for _, m := range ms { for _, m := range ms {
...@@ -75,3 +84,38 @@ func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus { ...@@ -75,3 +84,38 @@ func mountsToStatus(mounts []*pb.Mount, nodes []*nodeInfo) []*mountStatus {
} }
return msl 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
}
...@@ -4,7 +4,6 @@ package node ...@@ -4,7 +4,6 @@ package node
//go:generate go-bindata --nocompress --pkg node static/... templates/... //go:generate go-bindata --nocompress --pkg node static/... templates/...
import ( import (
"bytes"
"context" "context"
"flag" "flag"
"fmt" "fmt"
...@@ -209,33 +208,6 @@ func sendRedirect(w http.ResponseWriter, r *http.Request, targetURL string) { ...@@ -209,33 +208,6 @@ func sendRedirect(w http.ResponseWriter, r *http.Request, targetURL string) {
http.Redirect(w, r, targetURL, code) 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) { func addDefaultHeaders(w http.ResponseWriter) {
w.Header().Set("Expires", "-1") w.Header().Set("Expires", "-1")
w.Header().Set("Cache-Control", "no-cache,no-store") w.Header().Set("Cache-Control", "no-cache,no-store")
......
...@@ -22,24 +22,26 @@ ...@@ -22,24 +22,26 @@
<h4>Streams</h4> <h4>Streams</h4>
<ul> <ul>
{{$domain := .Domain}} {{$domain := .Domain}}
{{range .Mounts}} {{range $m := .Mounts}}
<li> <li>
<a href="http://{{$domain}}{{.Mount.Path}}" <a href="http://{{$domain}}{{$m.Mount.Path}}"
{{if .Mount.RelayUrl}} {{if $m.Mount.RelayUrl}}
data-toggle="tooltip" data-delay="300" title="relay of {{.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}} {{end}}
>{{.Mount.Path}}</a> >{{$m.Mount.Path}}</a>
<a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> <a href="http://{{$domain}}{{$m.Mount.Path}}.m3u">(m3u)</a>
<span class="badge badge-secondary">{{.Listeners}}</span> <span class="badge badge-secondary">{{$m.Listeners}}</span>
{{if .TransMounts}} {{if $m.TransMounts}}
<ul> <ul>
{{range .TransMounts}} {{range $tm := $m.TransMounts}}
<li> <li>
<a href="http://{{$domain}}{{.Mount.Path}}" <a href="http://{{$domain}}{{$tm.Mount.Path}}"
data-toggle="tooltip" data-delay="300" title="{{.Mount.TranscodeParams.String}}" data-toggle="tooltip" data-delay="300" title="{{$tm.Mount.TranscodeParams.String}}"
>{{.Mount.Path}}</a> >{{$tm.Mount.Path}}</a>
<a href="http://{{$domain}}{{.Mount.Path}}.m3u">(m3u)</a> <a href="http://{{$domain}}{{$tm.Mount.Path}}.m3u">(m3u)</a>
<span class="badge badge-secondary">{{.Listeners}}</span> <span class="badge badge-secondary">{{$tm.Listeners}}</span>
</li> </li>
{{end}} {{end}}
</ul> </ul>
...@@ -52,16 +54,25 @@ ...@@ -52,16 +54,25 @@
<div class="col-lg-6"> <div class="col-lg-6">
<h4>Nodes</h4> <h4>Nodes</h4>
<ul> <ul>
{{range .Nodes}} {{range $n := .Nodes}}
<li> <li>
{{.Name}} <span class="badge badge-secondary">{{.NumListeners}}</span> {{$n.Name}} <span class="badge badge-secondary">{{$n.NumListeners}}</span>
{{if not .IcecastOk}}<span class="badge badge-danger">IC_DOWN</span>{{end}} {{if not $n.IcecastOk}}<span class="badge badge-danger">IC_DOWN</span>{{end}}
</li> </li>
{{end}} {{end}}
</ul> </ul>
</div> </div>
</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"> <div class="footer">
powered by powered by
<a href="https://git.autistici.org/ale/autoradio"> <a href="https://git.autistici.org/ale/autoradio">
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment