Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ several `FSD` notifications into one executed action. [PR #3097]

- `upsset` should now recognize `RANGE NUMBER` and `NUMBER` types. [#3164]

- `upsstats` tool updates:
- `upsstats` CGI tool updates:
* Now has JSON output mode via `?json` (or `&json`) CGI query parameter.
New `@TREELINK_JSON@` keyword added and HTML templates updated to expose
these JSON documents when browsing. [issue #2524, PRs #3171, #3249]
Expand All @@ -357,7 +357,7 @@ several `FSD` notifications into one executed action. [PR #3097]
[issue #3252, PR #3249]
* (Experimental) Custom templates other than `upsstats{,-single}.html` can
now be specified as CGI parameters, if locally permitted via `hosts.conf`.
[issue #2524, PR #3304]
Modern-looking templates were provided. [issues #2524, #3316, PR #3304]

- `upssched` tool updates:
* Previously in PR #2896 (NUT releases v2.8.3 and v2.8.4) the `UPSNAME` and
Expand Down
30 changes: 20 additions & 10 deletions clients/upsstats.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ static char *upsimgpath = "upsimage.cgi" EXEEXT, *upsstatpath = "upsstats.cgi" E
*template_single = NULL, *template_list = NULL;
static UPSCONN_t ups;

#define DEFAULT_TEMPLATE_SINGLE "upsstats-single.html"
#define DEFAULT_TEMPLATE_LIST "upsstats.html"

static FILE *tf;
static long forofs = 0;

Expand Down Expand Up @@ -546,11 +549,11 @@ static void do_hostlink(void)

printf("<a href=\"%s?host=%s", upsstatpath, currups->sys);

if (template_single && strcmp(template_single, "upsstats-single.html")) {
if (template_single && strcmp(template_single, DEFAULT_TEMPLATE_SINGLE)) {
printf("&amp;template_single=%s", template_single);
}

if (template_list && strcmp(template_list, "upsstats.html")) {
if (template_list && strcmp(template_list, DEFAULT_TEMPLATE_LIST)) {
printf("&amp;template_list=%s", template_list);
}

Expand All @@ -574,11 +577,11 @@ static void do_treelink_json(const char *text)
printf("<a href=\"%s?host=%s&amp;json",
upsstatpath, currups->sys);

if (template_single && strcmp(template_single, "upsstats-single.html")) {
if (template_single && strcmp(template_single, DEFAULT_TEMPLATE_SINGLE)) {
printf("&amp;template_single=%s", template_single);
}

if (template_list && strcmp(template_list, "upsstats.html")) {
if (template_list && strcmp(template_list, DEFAULT_TEMPLATE_LIST)) {
printf("&amp;template_list=%s", template_list);
}

Expand All @@ -604,11 +607,11 @@ static void do_treelink(const char *text)
printf("<a href=\"%s?host=%s&amp;treemode",
upsstatpath, currups->sys);

if (template_single && strcmp(template_single, "upsstats-single.html")) {
if (template_single && strcmp(template_single, DEFAULT_TEMPLATE_SINGLE)) {
printf("&amp;template_single=%s", template_single);
}

if (template_list && strcmp(template_list, "upsstats.html")) {
if (template_list && strcmp(template_list, DEFAULT_TEMPLATE_LIST)) {
printf("&amp;template_list=%s", template_list);
}

Expand All @@ -630,6 +633,13 @@ static void do_ifsupp(const char *var, const char *val)

upsdebug_call_starting3("for '%s' ( =? '%s')", NUT_STRARG(var), NUT_STRARG(val));

if (!strcmp(var, "upsstats.use_celsius")) {
snprintf(dummy, sizeof(dummy), "%d", use_celsius);
skip_clause = 1;
upsdebug_call_finished1(": get_var() returned unexpected val");
return;
}

/* if not connected, act like it's not supported and skip the rest */
if (!check_ups_fd(0)) {
skip_clause = 1;
Expand Down Expand Up @@ -1720,8 +1730,8 @@ int main(int argc, char **argv)
}

/* Built-in defaults */
template_single = xstrdup("upsstats-single.html");
template_list = xstrdup("upsstats.html");
template_single = xstrdup(DEFAULT_TEMPLATE_SINGLE);
template_list = xstrdup(DEFAULT_TEMPLATE_LIST);

extractcgiargs();

Expand Down Expand Up @@ -1762,8 +1772,8 @@ int main(int argc, char **argv)

/* if a host is specified, use upsstats-single.html instead
* of listing whatever we know about with upsstats.html */
add_allowed_template_single("upsstats-single.html");
add_allowed_template_list("upsstats.html");
add_allowed_template_single(DEFAULT_TEMPLATE_SINGLE);
add_allowed_template_list(DEFAULT_TEMPLATE_LIST);
if (monhost) {
load_hosts_conf(0);
display_single();
Expand Down
2 changes: 2 additions & 0 deletions conf/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/upssched.conf.sample
/upsstats-single.html.sample
/upsstats.html.sample
/upsstats-modern-single.html.sample
/upsstats-modern-list.html.sample
4 changes: 3 additions & 1 deletion conf/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ SECFILES_GENERATED = upsmon.conf.sample
PUBFILES_STATIC = nut.conf.sample
PUBFILES_GENERATED = upssched.conf.sample
CGIPUB_STATIC = hosts.conf.sample upsset.conf.sample
CGIPUB_GENERATED = upsstats.html.sample upsstats-single.html.sample
CGIPUB_GENERATED = upsstats.html.sample upsstats-single.html.sample \
upsstats-modern-list.html.sample upsstats-modern-single.html.sample

SECFILES = $(SECFILES_STATIC) $(SECFILES_GENERATED)

Expand All @@ -31,6 +32,7 @@ nodist_conf_examples_DATA = $(SECFILES_GENERATED) $(PUBFILES_GENERATED) \

SPELLCHECK_SRC = $(dist_sysconf_DATA) \
upssched.conf.sample.in upsmon.conf.sample.in \
upsstats-modern-list.html.sample.in upsstats-modern-single.html.sample.in \
upsstats.html.sample.in upsstats-single.html.sample.in

# NOTE: Due to portability, we do not use a GNU percent-wildcard extension.
Expand Down
8 changes: 8 additions & 0 deletions conf/hosts.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
# and contain `.htm` in the file name.
#
# CUSTOM_TEMPLATE_LIST <filename>
#
# Examples:
#
# CUSTOM_TEMPLATE_LIST "upsstats-modern-list.html"

# -----------------------------------------------------------------------
#
Expand All @@ -59,3 +63,7 @@
# configuration directory and contain `.htm` in the file name.
#
# CUSTOM_TEMPLATE_SINGLE <filename>
#
# Examples:
#
# CUSTOM_TEMPLATE_SINGLE "upsstats-modern-single.html"
221 changes: 221 additions & 0 deletions conf/upsstats-modern-list.html.sample.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
@NUT_UPSSTATS_TEMPLATE default@
<!-- upsstats template file -->

<!--
This (upsstats-modern-list.html) is the custom template file which
is used when upsstats.cgi is loaded with no data selection arguments.

It usually contains a FOREACHUPS block to iterate through every
UPS in the hosts.conf.

See upsstats.html(5) for more information on template files.
-->

<!-- change this to TEMPF if you don't like Celsius. -->

@TEMPC@

<!-- Note: on Windows you may have to use the `*.cgi.exe` in PATHs below -->

@UPSSTATSPATH upsstats.cgi@EXEEXT@@
@UPSIMAGEPATH upsimage.cgi@EXEEXT@@

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@REFRESH@
<title>Network UPS Tools: Overview</title>
<style type="text/css">
:root {
--bg: #0f172a; --card: #1e293b; --card-hover: #2d3748;
--text: #f8fafc; --dim: #94a3b8; --green: #4ade80; --border: #334155;
--accent: #38bdf8;
}
* { box-sizing: border-box; }

body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; padding: 4vh 4vw; margin: 0; display: flex; justify-content: center; }
.dashboard { width: 100%; max-width: 1400px; display: flex; flex-direction: column; gap: 2rem; }

header { border-bottom: 1px solid var(--border); padding-bottom: 1rem; display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 1rem;}
h1 { margin: 0; font-size: 1.5rem; color: var(--green); }

/* Modern Table Wrapper */
.table-container { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 0.5rem 1.5rem; overflow-x: auto; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }

table { width: 100%; border-collapse: collapse; text-align: left; white-space: nowrap; }
th { padding: 1.25rem 1rem; color: var(--dim); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 2px solid var(--border); font-weight: bold; }
td { padding: 1rem; font-size: 0.95rem; border-bottom: 1px solid rgba(255,255,255,0.05); vertical-align: middle; }

/* Row Hover Effect */
tr:last-child td { border-bottom: none; }
tbody tr:hover { background-color: var(--card-hover); transition: background-color 0.2s; }

/* Button Styles */
a { text-decoration: none; }

.host-link a, .tree-link a {
display: inline-block;
padding: 6px 14px;
background-color: rgba(56, 189, 248, 0.1);
color: var(--accent);
border: 1px solid rgba(56, 189, 248, 0.25);
border-radius: 6px;
font-weight: 600;
transition: all 0.2s ease;
}
.host-link a:hover, .tree-link a:hover {
background-color: var(--accent);
color: var(--bg);
}

.status-badge { display: inline-block; padding: 4px 10px; border-radius: 6px; font-size: 0.85rem; font-weight: bold; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); }
</style>
<!-- link rel="stylesheet" type="text/css" href="nut.css" / -->
</head>
<body>

<div class="dashboard">
<header>
<div>
<h1>Network UPS Tools</h1>
<div style="color: var(--dim)">System Overview</div>
</div>
<div style="color: var(--dim)">@DATE %a %b %d %X %Z %Y@</div>
</header>

<div class="table-container">
<table>
<thead>
<tr>
<th>System</th>
<th>Model</th>
<th>Status</th>
<th>Battery</th>
<th>Input</th>
<th>Output</th>
<th>Load</th>
<th>Temp</th>
<th>Runtime</th>
<th>Details</th>
</tr>
</thead>
<tbody>
@FOREACHUPS@
<tr>
<td class="host-link">@HOSTLINK@</td>

<td>
@IFSUPP device.model@
@VAR device.model@
@ELSE@
@VAR ups.model@
@ENDIF@
</td>

<td><span class="status-badge" style="color: @STATUSCOLOR@;">@STATUS@</span></td>

<td>@IFSUPP battery.charge@@VAR battery.charge@ %@ENDIF@</td>

@IFSUPP input.L2-L3.voltage@
@IFBETWEEN input.transfer.low input.transfer.high input.L1-L2.voltage@
@IFBETWEEN input.transfer.low input.transfer.high input.L2-L3.voltage@
@IFBETWEEN input.transfer.low input.transfer.high input.L3-L1.voltage@
<td>
@ELSE@
@IFSUPP input.L2-N.voltage@
@IFBETWEEN input.transfer.low input.transfer.high input.L1-N.voltage@
@IFBETWEEN input.transfer.low input.transfer.high input.L2-N.voltage@
@IFBETWEEN input.transfer.low input.transfer.high input.L3-N.voltage@
<td>
@ELSE@
@IFBETWEEN input.transfer.low input.transfer.high input.voltage@
<td>
@ELSE@
<td style="background:red;">
@ENDIF@

@IFSUPP input.L2-L3.voltage@
L1-L2: @VAR input.L1-L2.voltage@ V<br/>
L2-L3: @VAR input.L2-L3.voltage@ V<br/>
L3-L1: @VAR input.L3-L1.voltage@ V
@ELSE@
@IFSUPP input.L2-N.voltage@
L1-N: @VAR input.L1-N.voltage@ V<br/>
L2-N: @VAR input.L2-N.voltage@ V<br/>
L3-N: @VAR input.L3-N.voltage@ V
@ELSE@
@IFSUPP input.voltage@
@VAR input.voltage@ V
@ENDIF@
</td>

<td>
@IFSUPP output.L2-L3.voltage@
L1-L2: @VAR output.L1-L2.voltage@ V<br/>
L2-L3: @VAR output.L2-L3.voltage@ V<br/>
L3-L1: @VAR output.L3-L1.voltage@ V
@ELSE@
@IFSUPP output.L2-N.voltage@
L1-N: @VAR output.L1-N.voltage@ V<br/>
L2-N: @VAR output.L2-N.voltage@ V<br/>
L3-N: @VAR output.L3-N.voltage@ V
@ELSE@
@IFSUPP output.voltage@
@VAR output.voltage@ V
@ENDIF@
</td>

<td>
@IFSUPP output.L2.power.percent@
L1: @VAR output.L1.power.percent@%<br/>
L2: @VAR output.L2.power.percent@%<br/>
L3: @VAR output.L3.power.percent@%
@ELSE@
@IFSUPP output.L2.realpower.percent@
L1: @VAR output.L1.realpower.percent@%<br/>
L2: @VAR output.L2.realpower.percent@%<br/>
L3: @VAR output.L3.realpower.percent@%
@ELSE@
@IFSUPP ups.load@
@VAR ups.load@ %
@ENDIF@
</td>

<td>
@IFSUPP ups.temperature@
@UPSTEMP@ @DEGREES@
@ELSE@
@IFSUPP battery.temperature@
@BATTTEMP@ @DEGREES@
@ENDIF@
</td>

<td>@IFSUPP battery.runtime@@RUNTIME@@ENDIF@</td>

<td class="tree-link" align="center">@TREELINK@ @TREELINK_JSON@</td>
</tr>
@ENDFOR@
</tbody>
</table>

<hr><div><small>
<a href="?json" title="All devices as one JSON document">JSON</a>
<a href="https://jigsaw.w3.org/css-validator/check/referer"><img
style="float:right" src="https://jigsaw.w3.org/css-validator/images/vcss"
alt="Valid CSS" title="Valid CSS" height="31" width="88"></a>
<a href="https://validator.w3.org/check?uri=referer"><img
style="float:right" src="https://www.w3.org/Icons/valid-html401"
alt="Valid HTML 4.01 Strict" title="Valid HTML 4.01 Strict"
height="31" width="88"></a>
<p align="right">Network UPS Tools upsstats @ESCAPED_TEMPLATE_VERSION@</p>
</small></div>

</div>
</div>

</body></html>
Loading
Loading