diff --git a/admin/dashboard.xsl b/admin/dashboard.xsl
new file mode 100644
index 00000000..75125add
--- /dev/null
+++ b/admin/dashboard.xsl
@@ -0,0 +1,67 @@
+
+
+
+ Dashboard
+
+
+
+
+
+
+ Overview for
+
+
+
+
+
Current load
+
+
+
+
+
+
+ |
+
+ of
+
+ |
+
+
+
+
+
+
+
+
+
+
+ Maintenance
+
+
+
+
+
+ Nothing to do.
+
+
+
+
+
+
+
diff --git a/admin/includes/header.xsl b/admin/includes/header.xsl
index 88a0ee2a..6c9e94f8 100644
--- a/admin/includes/header.xsl
+++ b/admin/includes/header.xsl
@@ -9,6 +9,7 @@
Icecast Server administration
+ - Dashboard
- Server status
- Mountpoint list
- Logfiles
diff --git a/src/admin.c b/src/admin.c
index f992aa69..6ba428e2 100644
--- a/src/admin.c
+++ b/src/admin.c
@@ -94,6 +94,8 @@
#define SHOWLOG_HTML_REQUEST "showlog.xsl"
#define MARKLOG_RAW_REQUEST "marklog"
#define MARKLOG_HTML_REQUEST "marklog.xsl"
+#define DASHBOARD_RAW_REQUEST "dashboard"
+#define DASHBOARD_HTML_REQUEST "dashboard.xsl"
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_HTML_REQUEST ""
#define BUILDM3U_RAW_REQUEST "buildm3u"
@@ -104,6 +106,7 @@ typedef struct {
const admin_command_handler_t *handlers;
} admin_command_table_t;
+static void command_default_selector (client_t *client, source_t *source, admin_format_t response);
static void command_fallback (client_t *client, source_t *source, admin_format_t response);
static void command_metadata (client_t *client, source_t *source, admin_format_t response);
static void command_shoutcast_metadata (client_t *client, source_t *source, admin_format_t response);
@@ -119,6 +122,7 @@ static void command_updatemetadata (client_t *client, source_t *source, adm
static void command_buildm3u (client_t *client, source_t *source, admin_format_t response);
static void command_show_log (client_t *client, source_t *source, admin_format_t response);
static void command_mark_log (client_t *client, source_t *source, admin_format_t response);
+static void command_dashboard (client_t *client, source_t *source, admin_format_t response);
static const admin_command_handler_t handlers[] = {
{ "*", ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, NULL, NULL}, /* for ACL framework */
@@ -154,8 +158,10 @@ static const admin_command_handler_t handlers[] = {
{ SHOWLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_show_log, NULL},
{ MARKLOG_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_mark_log, NULL},
{ MARKLOG_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_mark_log, NULL},
- { DEFAULT_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL},
- { DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_stats, NULL}
+ { DASHBOARD_RAW_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_RAW, command_dashboard, NULL},
+ { DASHBOARD_HTML_REQUEST, ADMINTYPE_GENERAL, ADMIN_FORMAT_HTML, command_dashboard, NULL},
+ { DEFAULT_HTML_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_default_selector, NULL},
+ { DEFAULT_RAW_REQUEST, ADMINTYPE_HYBRID, ADMIN_FORMAT_HTML, command_default_selector, NULL}
};
static admin_command_table_t command_tables[ADMIN_MAX_COMMAND_TABLES] = {
@@ -651,6 +657,16 @@ static void html_success(client_t *client, char *message)
}
+static void command_default_selector (client_t *client, source_t *source, admin_format_t response)
+{
+ if (client->mode == OMODE_LEGACY) {
+ command_stats(client, source, response);
+ } else {
+ command_dashboard(client, source, response);
+ }
+}
+
+
static void command_move_clients(client_t *client,
source_t *source,
admin_format_t response)
@@ -1336,3 +1352,131 @@ static void command_mark_log (client_t *client, source_t *source, adm
admin_send_response_simple(client, source, response, "Logfiles marked", 1);
}
+
+static void __reportxml_add_value(reportxml_node_t *parent, const char *type, const char *member, const char *str)
+{
+ reportxml_node_t *value = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(value, "type", type);
+ if (member)
+ reportxml_node_set_attribute(value, "member", member);
+ reportxml_node_set_attribute(value, "value", str);
+ reportxml_node_add_child(parent, value);
+ refobject_unref(value);
+}
+
+#define __reportxml_add_value_string(parent,member,value) __reportxml_add_value((parent), "string", (member), (value))
+#define __reportxml_add_value_enum(parent,member,value) __reportxml_add_value((parent), "enum", (member), (value))
+
+static void __reportxml_add_value_int(reportxml_node_t *parent, const char *member, long long int value)
+{
+ char buf[80];
+ snprintf(buf, sizeof(buf), "%lli", value);
+ __reportxml_add_value(parent, "int", member, buf);
+}
+
+static void __reportxml_add_maintenance(reportxml_node_t *parent, const char *type, const char *text, const char *docs)
+{
+ reportxml_node_t *maintenance = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+
+ reportxml_node_set_attribute(maintenance, "type", "structure");
+ reportxml_node_add_child(parent, maintenance);
+
+ __reportxml_add_value_enum(maintenance, "type", type);
+
+ if (text) {
+ reportxml_node_t *textnode = reportxml_node_new(REPORTXML_NODE_TYPE_TEXT, NULL, NULL, NULL);
+ reportxml_node_set_content(textnode, text);
+ reportxml_node_add_child(maintenance, textnode);
+ refobject_unref(textnode);
+ }
+
+ if (docs) {
+ reportxml_node_t *referenenode = reportxml_node_new(REPORTXML_NODE_TYPE_REFERENCE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(referenenode, "type", "documentation");
+ reportxml_node_set_attribute(referenenode, "href", docs);
+ reportxml_node_add_child(maintenance, referenenode);
+ refobject_unref(referenenode);
+ }
+
+ refobject_unref(maintenance);
+}
+
+static void command_dashboard (client_t *client, source_t *source, admin_format_t response)
+{
+ ice_config_t *config = config_get_config();
+ reportxml_t *report = client_get_reportxml("0aa76ea1-bf42-49d1-887e-ca95fb307dc4", NULL, NULL);
+ reportxml_node_t *incident = reportxml_get_node_by_type(report, REPORTXML_NODE_TYPE_INCIDENT, 0);
+ reportxml_node_t *resource;
+ reportxml_node_t *node;
+ int has_sources;
+ int has_many_clients;
+ int has_too_many_clients;
+
+
+ resource = reportxml_node_new(REPORTXML_NODE_TYPE_RESOURCE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(resource, "type", "result");
+ reportxml_node_set_attribute(resource, "name", "overall-status");
+
+ node = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(node, "type", "structure");
+ reportxml_node_set_attribute(node, "member", "global-config");
+ __reportxml_add_value_string(node, "hostname", config->hostname);
+ __reportxml_add_value_int(node, "clients", config->client_limit);
+ __reportxml_add_value_int(node, "sources", config->source_limit);
+ reportxml_node_add_child(resource, node);
+ refobject_unref(node);
+
+ node = reportxml_node_new(REPORTXML_NODE_TYPE_VALUE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(node, "type", "structure");
+ reportxml_node_set_attribute(node, "member", "global-current");
+ global_lock();
+ __reportxml_add_value_int(node, "clients", global.clients);
+ __reportxml_add_value_int(node, "sources", global.sources);
+ has_sources = global.sources > 0;
+ has_many_clients = global.clients > ((75 * config->client_limit) / 100);
+ has_too_many_clients = global.clients > ((90 * config->client_limit) / 100);
+ global_unlock();
+ reportxml_node_add_child(resource, node);
+ refobject_unref(node);
+
+ if (config->config_problems || has_too_many_clients) {
+ __reportxml_add_value_enum(resource, "status", "red");
+ } else if (!has_sources || has_many_clients) {
+ __reportxml_add_value_enum(resource, "status", "yellow");
+ } else {
+ __reportxml_add_value_enum(resource, "status", "green");
+ }
+
+ reportxml_node_add_child(incident, resource);
+ refobject_unref(resource);
+
+
+ resource = reportxml_node_new(REPORTXML_NODE_TYPE_RESOURCE, NULL, NULL, NULL);
+ reportxml_node_set_attribute(resource, "type", "result");
+ reportxml_node_set_attribute(resource, "name", "maintenance");
+
+ if (config->config_problems & CONFIG_PROBLEM_HOSTNAME)
+ __reportxml_add_maintenance(resource, "warning", "Hostname is not set to anything useful in .", NULL);
+ if (config->config_problems & CONFIG_PROBLEM_LOCATION)
+ __reportxml_add_maintenance(resource, "warning", "No useful location is given in .", NULL);
+ if (config->config_problems & CONFIG_PROBLEM_ADMIN)
+ __reportxml_add_maintenance(resource, "warning", "No admin contact given in . YP directory support will is disabled.", NULL);
+
+ if (!has_sources)
+ __reportxml_add_maintenance(resource, "info", "Currently no sources are connected to this server.", NULL);
+
+ if (has_too_many_clients) {
+ __reportxml_add_maintenance(resource, "warning", "More than 90% of the server's configured maximum clients are connected", NULL);
+ } else if (has_many_clients) {
+ __reportxml_add_maintenance(resource, "info", "More than 75% of the server's configured maximum clients are connected", NULL);
+ }
+
+ reportxml_node_add_child(incident, resource);
+ refobject_unref(resource);
+
+
+ refobject_unref(incident);
+
+ config_release_config();
+ client_send_reportxml(client, report, DOCUMENT_DOMAIN_ADMIN, DASHBOARD_HTML_REQUEST, response, 200, NULL);
+}
diff --git a/src/cfgfile.c b/src/cfgfile.c
index 3081ab47..1d661cac 100644
--- a/src/cfgfile.c
+++ b/src/cfgfile.c
@@ -936,6 +936,7 @@ static void _set_defaults(ice_config_t *configuration)
static inline void __check_hostname(ice_config_t *configuration)
{
+ int sane_hostname = 0;
char *p;
/* ensure we have a non-NULL buffer: */
@@ -948,10 +949,9 @@ static inline void __check_hostname(ice_config_t *configuration)
*p += 'a' - 'A';
}
- configuration->sane_hostname = 0;
switch (util_hostcheck(configuration->hostname)) {
case HOSTCHECK_SANE:
- configuration->sane_hostname = 1;
+ sane_hostname = 1;
break;
case HOSTCHECK_ERROR:
ICECAST_LOG_ERROR("Can not check hostname \"%s\".",
@@ -985,6 +985,9 @@ static inline void __check_hostname(ice_config_t *configuration)
"listings.");
break;
}
+
+ if (!sane_hostname)
+ configuration->config_problems |= CONFIG_PROBLEM_HOSTNAME;
}
static void _parse_root(xmlDocPtr doc,
@@ -1161,8 +1164,9 @@ static void _parse_root(xmlDocPtr doc,
strcmp(configuration->location, CONFIG_DEFAULT_LOCATION) == 0) {
ICECAST_LOG_WARN("Warning, not configured, using default "
"value \"%s\".", CONFIG_DEFAULT_LOCATION);
- if (!configuration->location)
- configuration->location = (char *) xmlCharStrdup(CONFIG_DEFAULT_LOCATION);
+ if (!configuration->location)
+ configuration->location = (char *) xmlCharStrdup(CONFIG_DEFAULT_LOCATION);
+ configuration->config_problems |= CONFIG_PROBLEM_LOCATION;
}
if (!configuration->admin ||
@@ -1171,9 +1175,10 @@ static void _parse_root(xmlDocPtr doc,
"default value \"%s\". This breaks YP directory listings. "
"YP directory support will be disabled.", CONFIG_DEFAULT_ADMIN);
/* FIXME actually disable YP */
- if (!configuration->admin)
- configuration->admin = (char *) xmlCharStrdup(CONFIG_DEFAULT_ADMIN);
- }
+ if (!configuration->admin)
+ configuration->admin = (char *) xmlCharStrdup(CONFIG_DEFAULT_ADMIN);
+ configuration->config_problems |= CONFIG_PROBLEM_ADMIN;
+ }
}
static void _parse_limits(xmlDocPtr doc,
diff --git a/src/cfgfile.h b/src/cfgfile.h
index a168d90e..edab5748 100644
--- a/src/cfgfile.h
+++ b/src/cfgfile.h
@@ -28,6 +28,10 @@
#define XMLSTR(str) ((xmlChar *)(str))
+#define CONFIG_PROBLEM_HOSTNAME 0x0001U
+#define CONFIG_PROBLEM_LOCATION 0x0002U
+#define CONFIG_PROBLEM_ADMIN 0x0004U
+
typedef enum _http_header_type {
/* static: headers are passed as is to the client. */
HTTP_HEADER_TYPE_STATIC,
@@ -198,6 +202,8 @@ typedef struct {
struct ice_config_tag {
char *config_filename;
+ unsigned int config_problems;
+
char *location;
char *admin;
@@ -220,7 +226,6 @@ struct ice_config_tag {
struct event_registration_tag *event;
char *hostname;
- int sane_hostname;
int port;
char *mimetypes_fn;
diff --git a/src/main.c b/src/main.c
index bf7eecfc..31af8b9f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -556,7 +556,7 @@ static inline void __log_system_name(void) {
if (have_hostname) {
config = config_get_config();
- if (!config->sane_hostname && util_hostcheck(hostname) == HOSTCHECK_SANE) {
+ if ((config->config_problems & CONFIG_PROBLEM_HOSTNAME) && util_hostcheck(hostname) == HOSTCHECK_SANE) {
ICECAST_LOG_WARN("Hostname is not set to anything useful in , Consider setting it to the system's name \"%s\".", hostname);
}
config_release_config();
diff --git a/web/assets/css/style.css b/web/assets/css/style.css
index fabea6ba..37a5bbad 100644
--- a/web/assets/css/style.css
+++ b/web/assets/css/style.css
@@ -302,6 +302,19 @@ aside {
margin: 0.4em;
}
+.side-by-side {
+ display: flex;
+ width: 100%;
+}
+
+.side-by-side > *:first-child {
+ margin-top: 0;
+}
+
+.side-by-side > * {
+ padding-right: 1em;
+}
+
.codeblock {
list-style: none;
width: 100%;
@@ -317,6 +330,62 @@ aside {
background-color: #cccccc;
}
+.barmeter > *:first-child {
+ float: right;
+ display: inline;
+}
+.barmeter > *:nth-child(2) {
+ background: #4f8cb0;
+ display: block;
+}
+
+.maintenance-container {
+ list-style: none;
+}
+
+.maintenance-level-warning > *:first-child::before {
+ font-weight: bold;
+ content: "Warning: ";
+}
+
+.maintenance-level-todo > *:first-child::before {
+ font-weight: bold;
+ content: "Todo: ";
+}
+
+.maintenance-level-info > *:first-child::before {
+ font-weight: bold;
+ content: "Info: ";
+}
+
+.references {
+ list-style: none;
+ padding: 0;
+}
+
+.references > li {
+ display: inline;
+ margin-right: 0.4em;
+}
+
+.trafficlight {
+ width: 4em;
+ height: 4em;
+ border-radius: 2em;
+}
+
+.colour-red {
+ background: red;
+}
+
+.colour-yellow {
+ background: yellow;
+}
+
+.colour-green {
+ background: limegreen;
+}
+
/* Error messages */
.error {