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

improve status dashboard

Adds detailed information about the transcoding hierarchy and parameters.
parent 5d6cd5b8
No related branches found
No related tags found
No related merge requests found
......@@ -64,6 +64,27 @@ func NewEncodingParams() *EncodingParams {
}
}
func (p *EncodingParams) String() string {
var out []string
out = append(out, p.Format)
if p.BitRate > 0 {
out = append(out, fmt.Sprintf("%dkBps", p.BitRate))
}
if p.Quality > -1 {
out = append(out, fmt.Sprintf("q=%g", p.Quality))
}
switch p.Channels {
case 1:
out = append(out, "mono")
case 2:
out = append(out, "stereo")
}
if p.SampleRate > 0 {
out = append(out, fmt.Sprintf("%gkHz", p.SampleRate/1000))
}
return strings.Join(out, ", ")
}
func (p *EncodingParams) Valid() error {
switch p.Format {
case "mp3", "mp3.cbr", "mp3.abr", "vorbis.cbr", "vorbis.abr":
......
......@@ -12,6 +12,7 @@ import (
"net/url"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
......@@ -281,18 +282,81 @@ func (h *HttpRedirector) serveSource(mount *autoradio.Mount, w http.ResponseWrit
proxy.ServeHTTP(w, r)
}
type mountStatus struct {
Mount *autoradio.Mount
Listeners int
TransMounts []*mountStatus
}
func newMountStatus(m *autoradio.Mount, nodes []*autoradio.NodeStatus) *mountStatus {
var listeners int
for _, n := range nodes {
for _, ims := range n.Mounts {
if ims.Name == m.Name {
listeners += ims.Listeners
break
}
}
}
return &mountStatus{
Mount: m,
Listeners: listeners,
}
}
type mountStatusList []*mountStatus
func (l mountStatusList) Len() int { return len(l) }
func (l mountStatusList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l mountStatusList) Less(i, j int) bool {
return l[i].Mount.Name < l[j].Mount.Name
}
// Serve our cluster status page.
func (h *HttpRedirector) serveStatusPage(w http.ResponseWriter, r *http.Request) {
nodes, _ := h.client.GetNodes()
mounts, _ := h.client.ListMounts()
// Aggregate stats, and create a tree of transcoding mounts.
ms := make(map[string]*mountStatus)
for _, m := range mounts {
if m.HasTranscoder() {
continue
}
ms[m.Name] = newMountStatus(m, nodes)
}
for _, m := range mounts {
if !m.HasTranscoder() {
continue
}
src := ms[m.Transcoding.SourceName]
if src == nil {
continue
}
src.TransMounts = append(src.TransMounts, newMountStatus(m, nodes))
}
msl := make([]*mountStatus, 0, len(ms))
for _, m := range ms {
msl = append(msl, m)
}
// Sort everything.
sort.Sort(mountStatusList(msl))
for _, s := range msl {
if s.TransMounts != nil {
sort.Sort(mountStatusList(s.TransMounts))
}
}
ctx := struct {
Domain string
Nodes []*autoradio.NodeStatus
Mounts []*autoradio.Mount
}{h.domain, nodes, mounts}
Mounts []*mountStatus
}{h.domain, nodes, msl}
var buf bytes.Buffer
if err := h.template.ExecuteTemplate(&buf, "index.html", ctx); err != nil {
log.Printf("error rendering template: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
......
......@@ -40,6 +40,10 @@ func createTestHttpRedirector(t *testing.T) *HttpRedirector {
etcd.Set(autoradio.MountPrefix+"test.ogg",
`{"Name": "/test.ogg", "Username": "source1", "Password": "foo"}`,
86400)
etcd.Set(autoradio.MountPrefix+"test.mp3",
`{"Name": "/test.mp3", "Username": "source2", "Password": "foo",
"Transcoding": {"SourceName": "/test.ogg", "Format": "mp3", "BitRate": 32}}`,
86400)
etcd.Set(autoradio.MasterElectionPath,
`{"Name": "node1", "IP": ["127.0.0.1"]}`,
86400)
......@@ -88,8 +92,17 @@ func TestHttpRedirector_StatusPage(t *testing.T) {
// Retrieve the status page.
data := doHttpRequest(t, "GET", srv.URL, 200)
if !strings.Contains(data, "<div class=\"container\">") {
t.Errorf("Bad response:\n%s", data)
// Search for some expected content.
needles := []string{
"<div class=\"container\">",
"/test.ogg",
"/test.mp3",
}
for _, s := range needles {
if !strings.Contains(data, s) {
t.Errorf("Bad response ('%s' not found):\n%s", s, data)
}
}
}
......
......@@ -77,7 +77,3 @@ body {
z-index: -100;
opacity: 0.3;
}
.error {
color: red
}
\ No newline at end of file
......@@ -3,7 +3,7 @@
<head>
<title>stream.{{.Domain}}</title>
<link rel="stylesheet"
href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css">
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/style.css">
<link rel="shortcut icon" href="/static/radio52.png">
</head>
......@@ -21,22 +21,45 @@
<div class="row mainitems">
<div class="col-lg-6">
<h4>Streams</h4>
{{$domain := .Domain}}
{{range .Mounts}}
<p>
<a href="http://stream.{{$domain}}{{.Name}}">{{.Name}}</a>
<a href="http://stream.{{$domain}}{{.Name}}.m3u">(m3u)</a>
</p>
{{end}}
<ul>
{{$domain := .Domain}}
{{range .Mounts}}
<li>
<a href="http://stream.{{$domain}}{{.Mount.Name}}"
{{if .Mount.RelayUrl}}
data-toggle="tooltip" data-delay="300" title="relay of {{.Mount.RelayUrl}}"
{{end}}
>{{.Mount.Name}}</a>
<a href="http://stream.{{$domain}}{{.Mount.Name}}.m3u">(m3u)</a>
<span class="badge">{{.Listeners}}</span>
{{if .TransMounts}}
<ul>
{{range .TransMounts}}
<li>
<a href="http://stream.{{$domain}}{{.Mount.Name}}"
data-toggle="tooltip" data-delay="300" title="{{.Mount.Transcoding.String}}"
>{{.Mount.Name}}</a>
<a href="http://stream.{{$domain}}{{.Mount.Name}}.m3u">(m3u)</a>
<span class="badge">{{.Listeners}}</span>
</li>
{{end}}
</ul>
{{end}}
</li>
{{end}}
</ul>
</div>
<div class="col-lg-6">
<h4>Nodes</h4>
{{range .Nodes}}
<p>{{.IP}} ({{.NumListeners}})
{{if not .IcecastUp}}<span class="error">(IC_DOWN)</span>{{end}}
</p>
{{end}}
<ul>
{{range .Nodes}}
<li>
{{.Name}} <span class="badge">{{.NumListeners}}</span>
{{if not .IcecastUp}}<span class="label label-danger">IC_DOWN</span>{{end}}
</li>
{{end}}
</ul>
</div>
</div>
......@@ -48,5 +71,12 @@
</div>
</div>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script type="text/javascript">
$(function() {
$('[data-toggle="tooltip"]').tooltip();
});
</script>
</body>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment