Add a status tab to the UI

Currently it will display active subscriptions with info about
total errors and current bandwidth
This commit is contained in:
Andreas Öman 2012-10-18 11:09:33 +02:00
parent 86a9626346
commit 014f55a463
9 changed files with 333 additions and 8 deletions

View file

@ -86,7 +86,10 @@ dvr_rec_subscribe(dvr_entry_t *de)
}
de->de_s = subscription_create_from_channel(de->de_channel, weight,
buf, st, flags);
buf, st, flags,
NULL, "DVR",
lang_str_get(de->de_title,
NULL));
pthread_create(&de->de_thread, NULL, dvr_thread, de);
}

View file

@ -1167,7 +1167,10 @@ htsp_method_subscribe(htsp_connection_t *htsp, htsmsg_t *in)
hs->hs_s = subscription_create_from_channel(ch, weight,
htsp->htsp_logname,
st, 0);
st, 0,
htsp->htsp_peername,
htsp->htsp_username,
htsp->htsp_clientname);
return NULL;
}

View file

@ -415,6 +415,8 @@ main(int argc, char **argv)
channels_init();
subscription_init();
access_init(createdefault);
tcp_server_init();

View file

@ -36,6 +36,9 @@
#include "streaming.h"
#include "channels.h"
#include "service.h"
#include "htsmsg.h"
#include "notify.h"
#include "atomic.h"
struct th_subscription_list subscriptions;
static gtimer_t subscription_reschedule_timer;
@ -129,12 +132,16 @@ subscription_unlink_service(th_subscription_t *s, int reason)
}
/**
*
*/
static void
subscription_reschedule_cb(void *aux)
{
subscription_reschedule();
}
/**
*
*/
@ -212,9 +219,13 @@ subscription_unsubscribe(th_subscription_t *s)
streaming_msg_free(s->ths_start_message);
free(s->ths_title);
free(s->ths_hostname);
free(s->ths_username);
free(s->ths_client);
free(s);
subscription_reschedule();
notify_reload("subscriptions");
}
@ -261,6 +272,14 @@ subscription_input(void *opauqe, streaming_message_t *sm)
streaming_msg_free(sm);
return;
}
if(sm->sm_type == SMT_PACKET) {
th_pkt_t *pkt = sm->sm_data;
if(pkt->pkt_err)
s->ths_total_err++;
s->ths_bytes += pkt->pkt_payload->pb_size;
}
streaming_target_deliver(s->ths_output, sm);
}
@ -282,10 +301,12 @@ subscription_input_direct(void *opauqe, streaming_message_t *sm)
*/
static th_subscription_t *
subscription_create(int weight, const char *name, streaming_target_t *st,
int flags, int direct)
int flags, int direct, const char *hostname,
const char *username, const char *client)
{
th_subscription_t *s = calloc(1, sizeof(th_subscription_t));
int reject = 0;
static int tally;
if(flags & SUBSCRIPTION_RAW_MPEGTS)
reject |= SMT_TO_MASK(SMT_PACKET); // Reject parsed frames
@ -298,11 +319,17 @@ subscription_create(int weight, const char *name, streaming_target_t *st,
s->ths_weight = weight;
s->ths_title = strdup(name);
s->ths_hostname = hostname ? strdup(hostname) : NULL;
s->ths_username = username ? strdup(username) : NULL;
s->ths_client = client ? strdup(client) : NULL;
s->ths_total_err = 0;
s->ths_output = st;
s->ths_flags = flags;
time(&s->ths_start);
s->ths_id = ++tally;
LIST_INSERT_SORTED(&subscriptions, s, ths_global_link, subscription_sort);
return s;
@ -315,9 +342,11 @@ subscription_create(int weight, const char *name, streaming_target_t *st,
th_subscription_t *
subscription_create_from_channel(channel_t *ch, unsigned int weight,
const char *name, streaming_target_t *st,
int flags)
int flags, const char *hostname,
const char *username, const char *client)
{
th_subscription_t *s = subscription_create(weight, name, st, flags, 0);
th_subscription_t *s = subscription_create(weight, name, st, flags, 0,
hostname, username, client);
s->ths_channel = ch;
LIST_INSERT_HEAD(&ch->ch_subscriptions, s, ths_channel_link);
@ -349,6 +378,7 @@ subscription_create_from_channel(channel_t *ch, unsigned int weight,
service_source_info_free(&si);
}
notify_reload("subscriptions");
return s;
}
@ -360,7 +390,8 @@ th_subscription_t *
subscription_create_from_service(service_t *t, const char *name,
streaming_target_t *st, int flags)
{
th_subscription_t *s = subscription_create(INT32_MAX, name, st, flags, 1);
th_subscription_t *s = subscription_create(INT32_MAX, name, st, flags, 1,
NULL, NULL, NULL);
source_info_t si;
int r;
@ -391,6 +422,7 @@ subscription_create_from_service(service_t *t, const char *name,
service_source_info_free(&si);
subscription_link_service(s, t);
notify_reload("subscriptions");
return s;
}
@ -478,3 +510,91 @@ subscription_dummy_join(const char *id, int first)
tvhlog(LOG_NOTICE, "subscription",
"Dummy join %s ok", id);
}
/**
*
*/
htsmsg_t *
subscription_create_msg(th_subscription_t *s)
{
htsmsg_t *m = htsmsg_create_map();
htsmsg_add_u32(m, "id", s->ths_id);
htsmsg_add_u32(m, "start", s->ths_start);
htsmsg_add_u32(m, "errors", s->ths_total_err);
const char *state;
switch(s->ths_state) {
default:
state = "Idle";
break;
case SUBSCRIPTION_TESTING_SERVICE:
state = "Testing";
break;
case SUBSCRIPTION_GOT_SERVICE:
state = "Running";
break;
case SUBSCRIPTION_BAD_SERVICE:
state = "Bad";
break;
}
htsmsg_add_str(m, "state", state);
if (s->ths_hostname && s->ths_username && s->ths_client) {
htsmsg_add_str(m, "hostname", s->ths_hostname);
htsmsg_add_str(m, "username", s->ths_username);
htsmsg_add_str(m, "title", s->ths_client);
} else {
htsmsg_add_str(m, "title", s->ths_title);
}
if(s->ths_channel != NULL)
htsmsg_add_str(m, "channel", s->ths_channel->ch_name);
if(s->ths_service != NULL)
htsmsg_add_str(m, "service", s->ths_service->s_nicename);
return m;
}
static gtimer_t every_sec;
/**
*
*/
static void
every_sec_cb(void *aux)
{
th_subscription_t *s;
gtimer_arm(&every_sec, every_sec_cb, NULL, 1);
LIST_FOREACH(s, &subscriptions, ths_global_link) {
int errors = s->ths_total_err;
int bw = atomic_exchange(&s->ths_bytes, 0);
htsmsg_t *m = subscription_create_msg(s);
htsmsg_delete_field(m, "errors");
htsmsg_add_u32(m, "errors", errors);
htsmsg_add_u32(m, "bw", bw);
htsmsg_add_u32(m, "updateEntry", 1);
notify_by_msg("subscriptions", m);
}
}
/**
*
*/
void
subscription_init(void)
{
gtimer_arm(&every_sec, every_sec_cb, NULL, 1);
}

View file

@ -19,9 +19,14 @@
#ifndef SUBSCRIPTIONS_H
#define SUBSCRIPTIONS_H
extern struct th_subscription_list subscriptions;
#define SUBSCRIPTION_RAW_MPEGTS 0x1
typedef struct th_subscription {
int ths_id;
LIST_ENTRY(th_subscription) ths_global_link;
int ths_weight;
@ -46,6 +51,7 @@ typedef struct th_subscription {
char *ths_title; /* display title */
time_t ths_start; /* time when subscription started */
int ths_total_err; /* total errors during entire subscription */
int ths_bytes; // Reset every second to get aprox. bandwidth
streaming_target_t ths_input;
@ -55,12 +61,19 @@ typedef struct th_subscription {
streaming_message_t *ths_start_message;
char *ths_hostname;
char *ths_username;
char *ths_client;
} th_subscription_t;
/**
* Prototypes
*/
void subscription_init(void);
void subscription_unsubscribe(th_subscription_t *s);
void subscription_set_weight(th_subscription_t *s, unsigned int weight);
@ -71,7 +84,10 @@ th_subscription_t *subscription_create_from_channel(struct channel *ch,
unsigned int weight,
const char *name,
streaming_target_t *st,
int flags);
int flags,
const char *hostname,
const char *username,
const char *client);
th_subscription_t *subscription_create_from_service(struct service *t,
@ -89,4 +105,7 @@ void subscription_dummy_join(const char *id, int first);
int subscriptions_active(void);
struct htsmsg;
struct htsmsg *subscription_create_msg(th_subscription_t *s);
#endif /* SUBSCRIPTIONS_H */

View file

@ -46,6 +46,7 @@
#include "epggrab/private.h"
#include "config2.h"
#include "lang_codes.h"
#include "subscriptions.h"
/**
*
@ -138,6 +139,7 @@ extjs_root(http_connection_t *hc, const char *remain, void *opaque)
extjs_load(hq, "static/app/dvr.js");
extjs_load(hq, "static/app/epggrab.js");
extjs_load(hq, "static/app/config.js");
extjs_load(hq, "static/app/status.js");
/**
* Finally, the app itself
@ -1386,6 +1388,41 @@ extjs_dvrlist(http_connection_t *hc, const char *remain, void *opaque)
return 0;
}
/**
*
*/
static int
extjs_subscriptions(http_connection_t *hc, const char *remain, void *opaque)
{
htsbuf_queue_t *hq = &hc->hc_reply;
htsmsg_t *out, *array;
th_subscription_t *s;
pthread_mutex_lock(&global_lock);
if(http_access_verify(hc, ACCESS_ADMIN)) {
pthread_mutex_unlock(&global_lock);
return HTTP_STATUS_UNAUTHORIZED;
}
out = htsmsg_create_map();
array = htsmsg_create_list();
LIST_FOREACH(s, &subscriptions, ths_global_link)
htsmsg_add_msg(array, NULL, subscription_create_msg(s));
pthread_mutex_unlock(&global_lock);
htsmsg_add_msg(out, "entries", array);
htsmsg_json_serialize(out, hq, 0);
htsmsg_destroy(out);
http_output_content(hc, "text/x-json; charset=UTF-8");
return 0;
}
/**
*
*/
@ -1878,6 +1915,7 @@ extjs_start(void)
http_path_add("/epgobject", NULL, extjs_epgobject, ACCESS_WEB_INTERFACE);
http_path_add("/dvr", NULL, extjs_dvr, ACCESS_WEB_INTERFACE);
http_path_add("/dvrlist", NULL, extjs_dvrlist, ACCESS_WEB_INTERFACE);
http_path_add("/subscriptions", NULL, extjs_subscriptions, ACCESS_WEB_INTERFACE);
http_path_add("/ecglist", NULL, extjs_ecglist, ACCESS_WEB_INTERFACE);
http_path_add("/config", NULL, extjs_config, ACCESS_WEB_INTERFACE);
http_path_add("/languages", NULL, extjs_languages, ACCESS_WEB_INTERFACE);

View file

@ -0,0 +1,134 @@
/**
*
*/
tvheadend.status = function() {
tvheadend.subsStore = new Ext.data.JsonStore({
root : 'entries',
totalProperty : 'totalCount',
fields : [ {
name : 'id'
}, {
name : 'hostname'
}, {
name : 'username'
}, {
name : 'title'
}, {
name : 'channel'
}, {
name : 'service'
}, {
name : 'state'
}, {
name : 'errors'
}, {
name : 'bw'
}, {
name : 'start',
type : 'date',
dateFormat : 'U' /* unix time */
} ],
url : 'subscriptions',
autoLoad : true,
id : 'id'
});
tvheadend.comet.on('subscriptions', function(m) {
if (m.reload != null) tvheadend.subsStore.reload();
if (m.updateEntry != null) {
r = tvheadend.subsStore.getById(m.id)
if (typeof r === 'undefined') {
tvheadend.subsStore.reload();
return;
}
r.data.channel = m.channel;
r.data.service = m.service;
r.data.state = m.state;
r.data.errors = m.errors;
r.data.bw = m.bw
tvheadend.subsStore.afterEdit(r);
tvheadend.subsStore.fireEvent('updated', tvheadend.subsStore, r,
Ext.data.Record.COMMIT);
}
});
function renderDate(value) {
var dt = new Date(value);
return dt.format('D j M H:i');
}
function renderBw(value) {
return parseInt(value / 125);
}
var subsCm = new Ext.grid.ColumnModel([{
width : 50,
id : 'hostname',
header : "Hostname",
dataIndex : 'hostname'
}, {
width : 50,
id : 'username',
header : "Username",
dataIndex : 'username'
}, {
width : 80,
id : 'title',
header : "Title",
dataIndex : 'title'
}, {
width : 50,
id : 'channel',
header : "Channel",
dataIndex : 'channel'
}, {
width : 200,
id : 'service',
header : "Service",
dataIndex : 'service',
}, {
width : 50,
id : 'start',
header : "Start",
dataIndex : 'start',
renderer : renderDate
}, {
width : 50,
id : 'state',
header : "State",
dataIndex : 'state'
}, {
width : 50,
id : 'errors',
header : "Errors",
dataIndex : 'errors'
}, {
width : 50,
id : 'bw',
header : "Bandwidth (kb/s)",
dataIndex : 'bw',
renderer: renderBw
} ]);
var panel = new Ext.grid.GridPanel({
loadMask : true,
stripeRows : true,
disableSelection : true,
title : 'Active subscriptions',
iconCls : 'eye',
store : tvheadend.subsStore,
cm : subsCm,
viewConfig : {
forceFit : true
}
});
return panel;
}

View file

@ -244,6 +244,11 @@ function accessUpdate(o) {
tvheadend.rootTabPanel.add(tvheadend.confpanel);
}
if (o.admin == true && tvheadend.statuspanel == null) {
tvheadend.statuspanel = new tvheadend.status;
tvheadend.rootTabPanel.add(tvheadend.statuspanel);
}
if (tvheadend.aboutPanel == null) {
tvheadend.aboutPanel = new Ext.Panel({
border : false,

View file

@ -607,7 +607,8 @@ http_stream_channel(http_connection_t *hc, channel_t *ch)
}
pthread_mutex_lock(&global_lock);
s = subscription_create_from_channel(ch, priority, "HTTP", st, flags);
s = subscription_create_from_channel(ch, priority, "HTTP", st, flags,
NULL, NULL, NULL);
pthread_mutex_unlock(&global_lock);
if(s) {