1
0
mirror of https://gitlab.xiph.org/xiph/icecast-server.git synced 2024-12-04 14:46:30 -05:00

Merge branch 'feature-dashboard'

This commit is contained in:
Philipp Schafft 2020-10-03 08:07:51 +00:00
commit 1e105ff2e9
7 changed files with 302 additions and 11 deletions

67
admin/dashboard.xsl Normal file
View File

@ -0,0 +1,67 @@
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" version = "1.0" xmlns="http://www.w3.org/1999/xhtml">
<xsl:output omit-xml-declaration="no" method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" indent="yes" encoding="UTF-8" />
<xsl:include href="includes/page.xsl"/>
<xsl:variable name="title">Dashboard</xsl:variable>
<xsl:template name="content">
<h2><xsl:value-of select="$title" /></h2>
<xsl:for-each select="/report/incident">
<xsl:for-each select="resource[@name='overall-status']">
<section class="box">
<h3 class="box_title">Overview for <code><xsl:value-of select="value[@member='global-config']/value[@member='hostname']/@value" /></code></h3>
<ul class="boxnav">
<li><a href="reloadconfig.xsl">Reload Configuration</a></li>
</ul>
<div class="side-by-side">
<div>
<h4>Health</h4>
<div class="trafficlight colour-{value[@member='status']/@value}">&#160;</div>
</div>
<div>
<h4>Current load</h4>
<table class="table-block">
<tbody>
<xsl:for-each select="value[@member='global-current']/value">
<tr>
<xsl:variable name="member" select="@member" />
<xsl:variable name="of" select="../../value[@member='global-config']/value[@member=$member]/@value" />
<td><xsl:value-of select="concat(translate(substring(@member, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), substring(@member, 2))" /></td>
<td class="barmeter">
<span><xsl:value-of select="@value" /> of <xsl:value-of select="$of" /></span>
<div style="width: calc(100% * {@value} / {$of});">&#160;</div>
</td>
</tr>
</xsl:for-each>
</tbody>
</table>
</div>
</div>
</section>
</xsl:for-each>
<xsl:for-each select="resource[@name='maintenance']">
<section class="box">
<h3 class="box_title">Maintenance</h3>
<xsl:choose>
<xsl:when test="value">
<ul class="maintenance-container">
<xsl:for-each select="value">
<li class="maintenance-level-{value[@member='type']/@value}">
<p><xsl:value-of select="text/text()" /></p>
<ul class="references">
<xsl:for-each select="reference">
<li><a href="{@href}"><xsl:value-of select="concat(translate(substring(@type, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), substring(@type, 2))" /></a></li>
</xsl:for-each>
</ul>
</li>
</xsl:for-each>
</ul>
</xsl:when>
<xsl:otherwise>
<p>Nothing to do.</p>
</xsl:otherwise>
</xsl:choose>
</section>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

View File

@ -9,6 +9,7 @@
<h1>Icecast Server administration</h1>
</a>
<ul>
<li class="adminlink"><a href="/admin/dashboard.xsl">Dashboard</a></li>
<li class="adminlink"><a href="/admin/stats.xsl">Server status</a></li>
<li class="adminlink"><a href="/admin/listmounts.xsl">Mountpoint list</a></li>
<li class="adminlink"><a href="/admin/showlog.xsl">Logfiles</a></li>

View File

@ -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 <hostname>.", NULL);
if (config->config_problems & CONFIG_PROBLEM_LOCATION)
__reportxml_add_maintenance(resource, "warning", "No useful location is given in <location>.", NULL);
if (config->config_problems & CONFIG_PROBLEM_ADMIN)
__reportxml_add_maintenance(resource, "warning", "No admin contact given in <admin>. 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);
}

View File

@ -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, <location> 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,

View File

@ -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;

View File

@ -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 <hostname>, Consider setting it to the system's name \"%s\".", hostname);
}
config_release_config();

View File

@ -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 {