Commit a7261348 authored by ale's avatar ale

Display stream metadata in the status page

parent 70b755b8
Pipeline #2717 failed with stages
in 2 minutes and 51 seconds
......@@ -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
}
......
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
}
......@@ -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")
......
......@@ -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">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment