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:
parent
86a9626346
commit
014f55a463
9 changed files with 333 additions and 8 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -415,6 +415,8 @@ main(int argc, char **argv)
|
|||
|
||||
channels_init();
|
||||
|
||||
subscription_init();
|
||||
|
||||
access_init(createdefault);
|
||||
|
||||
tcp_server_init();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
134
src/webui/static/app/status.js
Normal file
134
src/webui/static/app/status.js
Normal 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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue