make html output to use Bootstrap and DataTables

This make the table output good looking, and allows users to click table
headings to sort data by column without rerunning the analysis.
Unfortunately this change is breaking change, meaning the old CSS tags are
no longer supported, nor partial html output that printed only the table.

Proposed-by: Aaron Paetznick <aaronp@critd.com>
Signed-off-by: Sami Kerola <kerolasa@iki.fi>
This commit is contained in:
Sami Kerola 2015-11-28 17:59:42 +00:00
parent c7917152d3
commit 1299737d76
No known key found for this signature in database
GPG key ID: A9553245FDE9B739
6 changed files with 170 additions and 209 deletions

6
README
View file

@ -43,6 +43,12 @@ Dependencies to other projects.
See quick start.
https://getbootstrap.com/
https://datatables.net/
Java Bootstrap and DataTables java scripts are used in html
output.
Test data wanted.
Maintainer is interested to get copy of your dhcpd.conf

View file

@ -68,12 +68,10 @@ Sort results in reverse order.
Output format.
Text
.RI ( t ).
Standard html
.RI ( h )
outputs only the HTML tables, and is useful for embedding more complex web
pages. Full-html
Full-html
.RI ( H )
provides complete HTML headers, etc., including in-line CSS. The
page output.
The
.RI ( c )
stands for comma-separated values. Output format xml
.RI ( x )

View file

@ -145,7 +145,6 @@ int main(int argc, char **argv)
config.output_limit[0] = (*tmp - '0');
tmp++;
config.output_limit[1] = (*tmp - '0');
config.fullhtml = false;
/* Make sure some output format is selected by default */
strncpy(config.output_format, OUTPUT_FORMAT, (size_t)1);
/* Default sort order is by IPs small to big */
@ -245,11 +244,10 @@ int main(int argc, char **argv)
output_analysis = output_alarming;
break;
case 'h':
output_analysis = output_html;
error(EXIT_FAILURE, 0, "html table only output format is deprecated");
break;
case 'H':
output_analysis = output_html;
config.fullhtml = true;
break;
case 'x':
output_analysis = output_xml;

View file

@ -124,7 +124,6 @@ struct configuration_t {
char *dhcpdconf_file;
char *dhcpdlease_file;
char output_format[2];
bool fullhtml;
char sort[6];
bool reverse_order;
char *output_file;

View file

@ -448,7 +448,6 @@ This is ISC dhcpd pools usage analyzer.\n\
fprintf(out, "\
-f, --format=[thHcxXjJ] output format\n\
t for text\n\
h for html table\n\
H for full html page\n\
x for xml\n\
X for xml with active lease details\n\

View file

@ -478,63 +478,23 @@ static void html_header(FILE *restrict f)
if (strftime(outstr, sizeof(outstr), nl_langinfo(D_T_FMT), &result) == 0) {
error(EXIT_FAILURE, 0, "html_header: strftime returned 0");
}
fprintf(f, "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n");
fprintf(f, "\"http://www.w3.org/TR/html4/loose.dtd\">\n");
fprintf(f, "<!DOCTYPE html>\n");
fprintf(f, "<html>\n");
fprintf(f, "<head>\n");
fprintf(f, "<meta http-equiv=\"Content-Type\" ");
fprintf(f, "content=\"text/html; charset=iso-8859-1\">\n");
fprintf(f, "<title>ISC dhcpd stats</title>\n");
fprintf(f, "<title>ISC dhcpd dhcpd-pools output</title>\n");
fprintf(f, "<meta charset=\"utf-8\">\n");
fprintf(f, "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n");
fprintf(f, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n");
fprintf(f, "<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css\" type=\"text/css\">\n");
fprintf(f, "<link rel=\"stylesheet\" href=\"https://cdn.datatables.net/1.10.10/css/jquery.dataTables.min.css\" type=\"text/css\">");
fprintf(f, "<style type=\"text/css\">\n");
fprintf(f, "<!--\n");
fprintf(f, "table.dhcpd-pools {\n");
fprintf(f, "color: black;\n");
fprintf(f, "vertical-align: middle;\n");
fprintf(f, "text-align: center;\n");
fprintf(f, "}\n");
fprintf(f, "table.dhcpd-pools th.section {\n");
fprintf(f, "color: black;\n");
fprintf(f, "font-size: large;\n");
fprintf(f, "vertical-align: middle;\n");
fprintf(f, "text-align: left;\n");
fprintf(f, "}\n");
fprintf(f, "table.dhcpd-pools th.lalign {\n");
fprintf(f, "color: black;\n");
fprintf(f, "vertical-align: middle;\n");
fprintf(f, "text-align: left;\n");
fprintf(f, "text-decoration: underline;\n");
fprintf(f, "}\n");
fprintf(f, "table.dhcpd-pools th.ralign {\n");
fprintf(f, "color: black;\n");
fprintf(f, "vertical-align: middle;\n");
fprintf(f, "text-align: right;\n");
fprintf(f, "text-decoration: underline;\n");
fprintf(f, "}\n");
fprintf(f, "table.dhcpd-pools td.lalign {\n");
fprintf(f, "color: black;\n");
fprintf(f, "vertical-align: middle;\n");
fprintf(f, "text-align: left;\n");
fprintf(f, "}\n");
fprintf(f, "table.dhcpd-pools td.ralign {\n");
fprintf(f, "color: black;\n");
fprintf(f, "vertical-align: middle;\n");
fprintf(f, "text-align: right;\n");
fprintf(f, "}\n");
fprintf(f, "p.created {\n");
fprintf(f, "font-size: small;\n");
fprintf(f, "color: grey;\n");
fprintf(f, "}\n");
fprintf(f, "p.updated {\n");
fprintf(f, "font-size: small;\n");
fprintf(f, "color: grey;\n");
fprintf(f, "font-style: italic;\n");
fprintf(f, "}\n");
fprintf(f, "-->\n");
fprintf(f, "table.dhcpd-pools th { text-transform: capitalize }\n");
fprintf(f, "</style>\n");
fprintf(f, "</head>\n");
fprintf(f, "<body>\n");
fprintf(f, "<a name=\"ranges\">The lease file mtime: %s</a>", outstr);
fprintf(f, "<div class=\"container\">\n");
fprintf(f, "<h2>ISC DHCPD status</h2>\n");
fprintf(f, "<small>File %s was last modified at %s</small><hr />\n", config.dhcpdlease_file, outstr);
}
/*! \brief Footer for full html output format.
@ -543,36 +503,36 @@ static void html_header(FILE *restrict f)
*/
static void html_footer(FILE *restrict f)
{
fprintf(f, "<p><br></p>\n");
fprintf(f, "<hr>\n");
fprintf(f, "<p class=\"created\">\nData generated by ");
fprintf(f, "<a href=\"%s\">", PACKAGE_URL);
fprintf(f, "%s</a>.\n</p>\n", PACKAGE_STRING);
fprintf(f, "<p class=\"updated\">\n");
fprintf(f, "<script type=\"text/javascript\">\n");
fprintf(f, "document.write(\"Last Updated On \" + ");
fprintf(f, "document.lastModified + \".\")\n");
fprintf(f, "</script>\n<br>\n</p>\n");
fprintf(f, "</body>\n");
fprintf(f, "</html>\n");
fprintf(f, "<br /><div class=\"well well-lg\">\n");
fprintf(f, "<small>Generated using %s<br />\n", PACKAGE_STRING);
fprintf(f, "More info at <a href=\"%s\">%s</a>\n", PACKAGE_URL, PACKAGE_URL);
fprintf(f, "</small></div></div>\n");
fprintf(f, "<script src=\"//code.jquery.com/jquery-2.1.4.min.js\" type=\"text/javascript\"></script>\n");
fprintf(f, "<script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js\" type=\"text/javascript\"></script>\n");
fprintf(f, "<script src=\"https://cdn.datatables.net/1.10.10/js/jquery.dataTables.min.js\" type=\"text/javascript\"></script>\n");
fprintf(f, "<script type=\"text/javascript\" class=\"init\">$(document).ready(function() { $('#s').DataTable(); } );</script>\n");
fprintf(f, "<script type=\"text/javascript\" class=\"init\">$(document).ready(function() { $('#r').DataTable(); } );</script>\n");
fprintf(f, "</body></html>\n");
}
/*! \brief A new row for html output format.
/*! \brief Start a html tag.
*
* \param f Output file descriptor.
* \param tag The html tag.
*/
static void newrow(FILE *restrict f)
static void start_tag(FILE *restrict f, char const *restrict tag)
{
fprintf(f, "<tr>\n");
fprintf(f, "<%s>\n", tag);
}
/*! \brief End a row for html output format.
/*! \brief End a html tag.
*
* \param f Output file descriptor.
* \param tag The html tag.
*/
static void endrow(FILE *restrict f)
static void end_tag(FILE *restrict f, char const *restrict tag)
{
fprintf(f, "</tr>\n");
fprintf(f, "</%s>\n", tag);
}
/*! \brief Line with text in html output format.
@ -583,9 +543,9 @@ static void endrow(FILE *restrict f)
* \param text Actual payload of the printout.
*/
static void output_line(FILE *restrict f, char const *restrict type,
char const *restrict class, char const *restrict text)
char const *restrict text)
{
fprintf(f, "<%s class=\"%s\">%s</%s>\n", type, class, text, type);
fprintf(f, "<%s>%s</%s>\n", type, text, type);
}
/*! \brief Line with digit in html output format.
@ -596,7 +556,7 @@ static void output_line(FILE *restrict f, char const *restrict type,
*/
static void output_double(FILE *restrict f, char const *restrict type, double d)
{
fprintf(f, "<%s class=\"ralign\">%g</%s>\n", type, d, type);
fprintf(f, "<%s>%g</%s>\n", type, d, type);
}
/*! \brief Line with float in html output format.
@ -605,20 +565,18 @@ static void output_double(FILE *restrict f, char const *restrict type, double d)
* \param type HTML tag name.
* \param fl Actual payload of the printout.
*/
static void output_float(FILE *f, char const *restrict type, float fl)
static void output_float(FILE *restrict f, char const *restrict type, float fl)
{
fprintf(f, "<%s class=\"ralign\">%.3f</%s>\n", type, fl, type);
fprintf(f, "<%s>%.3f</%s>\n", type, fl, type);
}
/*! \brief Begin table in html output format.
*
* \param f Output file descriptor.
*/
static void table_start(FILE *restrict f)
static void table_start(FILE *restrict f, char const *restrict id, char const *restrict summary)
{
fprintf(f, "<table width=\"75%%\" ");
fprintf(f, "class=\"%s\" ", PACKAGE_NAME);
fprintf(f, "summary=\"ISC dhcpd pool usage report\">\n");
fprintf(f, "<table id=\"%s\" class=\"dhcpd-pools order-column table table-striped table-hover\" summary=\"%s\">\n", id, summary);
}
/*! \brief End table in html output format.
@ -637,12 +595,7 @@ static void table_end(FILE *restrict f)
*/
static void newsection(FILE *restrict f, char const *restrict title)
{
newrow(f);
output_line(f, "td", "lalign", "&nbsp;");
endrow(f);
newrow(f);
output_line(f, "th", "section", title);
endrow(f);
output_line(f, "h3", title);
}
/*! \brief Output html format.
@ -669,123 +622,30 @@ int output_html(void)
range_p = ranges;
range_size = get_range_size(range_p);
shared_p = shared_networks;
if (config.fullhtml) {
html_header(outfile);
}
table_start(outfile);
if (config.output_limit[0] & BIT1) {
newsection(outfile, "Ranges:");
newrow(outfile);
output_line(outfile, "th", "lalign", "shared net name");
output_line(outfile, "th", "lalign", "first ip");
output_line(outfile, "th", "lalign", "last ip");
output_line(outfile, "th", "ralign", "max");
output_line(outfile, "th", "ralign", "cur");
output_line(outfile, "th", "ralign", "percent");
output_line(outfile, "th", "ralign", "touch");
output_line(outfile, "th", "ralign", "t+c");
output_line(outfile, "th", "ralign", "t+c perc");
if (config.backups_found == true) {
output_line(outfile, "th", "ralign", "bu");
output_line(outfile, "th", "ralign", "bu perc");
}
endrow(outfile);
}
if (config.output_limit[1] & BIT1) {
for (i = 0; i < num_ranges; i++) {
newrow(outfile);
if (range_p->shared_net) {
output_line(outfile, "td", "lalign", range_p->shared_net->name);
} else {
output_line(outfile, "td", "lalign", "not_defined");
}
output_line(outfile, "td", "lalign", ntop_ipaddr(&range_p->first_ip));
output_line(outfile, "td", "lalign", ntop_ipaddr(&range_p->last_ip));
output_double(outfile, "td", range_size);
output_double(outfile, "td", range_p->count);
output_float(outfile, "td", (float)(100 * range_p->count) / range_size);
output_double(outfile, "td", range_p->touched);
output_double(outfile, "td", range_p->touched + range_p->count);
output_float(outfile, "td",
(float)(100 *
(range_p->touched + range_p->count)) / range_size);
if (config.backups_found == true) {
output_double(outfile, "td", range_p->backups);
output_float(outfile, "td",
(float)(100 * range_p->backups) / range_size);
}
endrow(outfile);
range_p++;
range_size = get_range_size(range_p);
}
}
table_end(outfile);
table_start(outfile);
if (config.output_limit[0] & BIT2) {
newsection(outfile, "Shared networks:");
newrow(outfile);
output_line(outfile, "th", "lalign", "name");
output_line(outfile, "th", "ralign", "max");
output_line(outfile, "th", "ralign", "cur");
output_line(outfile, "th", "ralign", "percent");
output_line(outfile, "th", "ralign", "touch");
output_line(outfile, "th", "ralign", "t+c");
output_line(outfile, "th", "ralign", "t+c perc");
if (config.backups_found == true) {
output_line(outfile, "th", "ralign", "bu");
output_line(outfile, "th", "ralign", "bu perc");
}
endrow(outfile);
}
if (config.output_limit[1] & BIT2) {
for (i = 0; i < num_shared_networks; i++) {
shared_p++;
newrow(outfile);
output_line(outfile, "td", "lalign", shared_p->name);
output_double(outfile, "td", shared_p->available);
output_double(outfile, "td", shared_p->used);
output_float(outfile, "td",
shared_p->available ==
0 ? -NAN : (float)(100 * shared_p->used) /
shared_p->available);
output_double(outfile, "td", shared_p->touched);
output_double(outfile, "td", shared_p->touched + shared_p->used);
output_float(outfile, "td",
shared_p->available == 0 ? -NAN : (float)(100 *
(shared_p->touched +
shared_p->used)) /
shared_p->available);
if (config.backups_found == true) {
output_double(outfile, "td", shared_p->backups);
output_float(outfile, "td",
shared_p->available == 0 ? -NAN : (float)(100 *
shared_p->backups)
/ shared_p->available);
}
endrow(outfile);
}
}
newsection(outfile, "Sum of all");
table_start(outfile, "a", "all");
if (config.output_limit[0] & BIT3) {
newsection(outfile, "Sum of all ranges:");
newrow(outfile);
output_line(outfile, "th", "lalign", "name");
output_line(outfile, "th", "ralign", "max");
output_line(outfile, "th", "ralign", "cur");
output_line(outfile, "th", "ralign", "percent");
output_line(outfile, "th", "ralign", "touch");
output_line(outfile, "th", "ralign", "t+c");
output_line(outfile, "th", "ralign", "t+c perc");
start_tag(outfile, "thead");
start_tag(outfile, "tr");
output_line(outfile, "th", "name");
output_line(outfile, "th", "max");
output_line(outfile, "th", "cur");
output_line(outfile, "th", "percent");
output_line(outfile, "th", "touch");
output_line(outfile, "th", "t+c");
output_line(outfile, "th", "t+c perc");
if (config.backups_found == true) {
output_line(outfile, "th", "ralign", "bu");
output_line(outfile, "th", "ralign", "bu perc");
output_line(outfile, "th", "bu");
output_line(outfile, "th", "bu perc");
}
endrow(outfile);
end_tag(outfile, "tr");
end_tag(outfile, "thead");
}
if (config.output_limit[1] & BIT3) {
newrow(outfile);
output_line(outfile, "td", "lalign", shared_networks->name);
start_tag(outfile, "tbody");
start_tag(outfile, "tr");
output_line(outfile, "td", shared_networks->name);
output_double(outfile, "td", shared_networks->available);
output_double(outfile, "td", shared_networks->used);
output_float(outfile, "td",
@ -807,12 +667,113 @@ int output_html(void)
shared_networks->backups)
/ shared_networks->available);
}
endrow(outfile);
end_tag(outfile, "tr");
end_tag(outfile, "tbody");
}
table_end(outfile);
if (config.fullhtml) {
html_footer(outfile);
newsection(outfile, "Shared networks");
table_start(outfile, "s", "snet");
if (config.output_limit[0] & BIT2) {
start_tag(outfile, "thead");
start_tag(outfile, "tr");
output_line(outfile, "th", "name");
output_line(outfile, "th", "max");
output_line(outfile, "th", "cur");
output_line(outfile, "th", "percent");
output_line(outfile, "th", "touch");
output_line(outfile, "th", "t+c");
output_line(outfile, "th", "t+c perc");
if (config.backups_found == true) {
output_line(outfile, "th", "bu");
output_line(outfile, "th", "bu perc");
}
end_tag(outfile, "tr");
end_tag(outfile, "thead");
}
if (config.output_limit[1] & BIT2) {
start_tag(outfile, "tbody");
for (i = 0; i < num_shared_networks; i++) {
shared_p++;
start_tag(outfile, "tr");
output_line(outfile, "td", shared_p->name);
output_double(outfile, "td", shared_p->available);
output_double(outfile, "td", shared_p->used);
output_float(outfile, "td",
shared_p->available ==
0 ? -NAN : (float)(100 * shared_p->used) /
shared_p->available);
output_double(outfile, "td", shared_p->touched);
output_double(outfile, "td", shared_p->touched + shared_p->used);
output_float(outfile, "td",
shared_p->available == 0 ? -NAN : (float)(100 *
(shared_p->touched +
shared_p->used)) /
shared_p->available);
if (config.backups_found == true) {
output_double(outfile, "td", shared_p->backups);
output_float(outfile, "td",
shared_p->available == 0 ? -NAN : (float)(100 *
shared_p->backups)
/ shared_p->available);
}
end_tag(outfile, "tr");
}
end_tag(outfile, "tbody");
}
table_end(outfile);
newsection(outfile, "Ranges");
table_start(outfile, "r", "ranges");
if (config.output_limit[0] & BIT1) {
start_tag(outfile, "thead");
start_tag(outfile, "tr");
output_line(outfile, "th", "shared net name");
output_line(outfile, "th", "first ip");
output_line(outfile, "th", "last ip");
output_line(outfile, "th", "max");
output_line(outfile, "th", "cur");
output_line(outfile, "th", "percent");
output_line(outfile, "th", "touch");
output_line(outfile, "th", "t+c");
output_line(outfile, "th", "t+c perc");
if (config.backups_found == true) {
output_line(outfile, "th", "bu");
output_line(outfile, "th", "bu perc");
}
end_tag(outfile, "tr");
end_tag(outfile, "thead");
}
if (config.output_limit[1] & BIT1) {
start_tag(outfile, "tbody");
for (i = 0; i < num_ranges; i++) {
start_tag(outfile, "tr");
if (range_p->shared_net) {
output_line(outfile, "td", range_p->shared_net->name);
} else {
output_line(outfile, "td", "not_defined");
}
output_line(outfile, "td", ntop_ipaddr(&range_p->first_ip));
output_line(outfile, "td", ntop_ipaddr(&range_p->last_ip));
output_double(outfile, "td", range_size);
output_double(outfile, "td", range_p->count);
output_float(outfile, "td", (float)(100 * range_p->count) / range_size);
output_double(outfile, "td", range_p->touched);
output_double(outfile, "td", range_p->touched + range_p->count);
output_float(outfile, "td",
(float)(100 *
(range_p->touched + range_p->count)) / range_size);
if (config.backups_found == true) {
output_double(outfile, "td", range_p->backups);
output_float(outfile, "td",
(float)(100 * range_p->backups) / range_size);
}
end_tag(outfile, "tr");
range_p++;
range_size = get_range_size(range_p);
}
end_tag(outfile, "tbody");
}
table_end(outfile);
html_footer(outfile);
if (outfile == stdout) {
ret = fflush(stdout);
if (ret) {