]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
Add a status tab to the UI
authorAndreas Öman <andreas@lonelycoder.com>
Thu, 18 Oct 2012 09:09:33 +0000 (11:09 +0200)
committerAndreas Öman <andreas@lonelycoder.com>
Thu, 18 Oct 2012 09:09:33 +0000 (11:09 +0200)
Currently it will display active subscriptions with info about
total errors and current bandwidth

src/dvr/dvr_rec.c
src/htsp.c
src/main.c
src/subscriptions.c
src/subscriptions.h
src/webui/extjs.c
src/webui/static/app/status.js [new file with mode: 0644]
src/webui/static/app/tvheadend.js
src/webui/webui.c

index 9ba9e10323aa89843e68757fbe8f4e4688ed0f49..a46987fbde22d54ff58de78c2d7053716a517c1c 100755 (executable)
@@ -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);
 }
index fc2bce35e5f222c62770059a74e61f62c5cfb02a..99813b0acf2e85d324377421aaade2963a79642f 100644 (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;
 }
 
index 770d71f1b7e5cd876861a62700535cced222ee03..9cca79fdff63412a0bfbb79bffc652358d997cee 100644 (file)
@@ -415,6 +415,8 @@ main(int argc, char **argv)
 
   channels_init();
 
+  subscription_init();
+
   access_init(createdefault);
 
   tcp_server_init();
index 4249a38220da819889ca539e52434705dbafcc05..0f816ba271575f212384570cd898da8a270c65dd 100644 (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);
+}
index 527380414b5bd60dee479afe78b6eed3d5a313e9..ff5d1d1d929874af045ea88d7f3478f9c7334076 100644 (file)
 #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 */
index cf318fc5db3423cd5878f6a509dba771ea8bfb0d..7ede18ae584fe5c50a785e14598c38990f02e8ca 100644 (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);
diff --git a/src/webui/static/app/status.js b/src/webui/static/app/status.js
new file mode 100644 (file)
index 0000000..44f999d
--- /dev/null
@@ -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;
+}
+
index ab942852c9053505728b851c7b1891d3f4518d45..3b44f6cadc417cff1a9ecb5f8cc7936c26b7b42b 100644 (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,
index 6c1041e763d1ffe332d4a467f0e0ee02404a4b3f..c4f0b787464e0312077cc883054895668c64cabe 100644 (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) {