From 609fea008b0091715bcdba1239b8126069c3473e Mon Sep 17 00:00:00 2001 From: Egor Tensin <Egor.Tensin@gmail.com> Date: Mon, 11 Apr 2022 01:26:12 +0200 Subject: put things into proper directories --- html/index.html | 344 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 html/index.html (limited to 'html/index.html') diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..5a4f56f --- /dev/null +++ b/html/index.html @@ -0,0 +1,344 @@ +<!DOCTYPE HTML> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <title>-</title> + <link rel="stylesheet" href="css/bootstrap.min.css"> + </head> + <body> + <div class="container"> + <div class="row mt-3"> + <div class="col"> + <h1 id="hostname">-</h1> + <p class="mb-0"><small>refreshed every <span id="status_refresh_interval">-</span> seconds</small></p> + </div> + <div class="col h1 text-right"> + <div class="btn-group align-middle" role="group"> + <a role="button" class="btn btn-sm btn-warning" href="#" onclick="reboot();">Reboot</a> + <a role="button" class="btn btn-sm btn-danger" href="#" onclick="shutdown();">Shutdown</a> + </div> + </div> + </div> + <div class="row"> + <div class="col"> + <hr> + <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_top">+</button><a href="#collapse_top" data-toggle="collapse"><code>top</code></a> <span class="float-right"><small>refreshed every <span id="top_refresh_interval">-</span> seconds</small></span></p> + <div class="collapse show" id="collapse_top"> + <pre class="pre-scrollable" id="top"></pre> + </div> + <hr> + <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_docker">+</button><a href="#collapse_docker" data-toggle="collapse"><code>docker ps -a</code></a></p> + <div class="collapse" id="collapse_docker"> + <div id="docker">No data has been loaded yet.</div> + </div> + <hr> + <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_failed_system">+</button><a href="#collapse_failed_system" data-toggle="collapse"><code>systemctl --system list-units --failed</code></a></p> + <div class="collapse show" id="collapse_failed_system"> + <pre class="pre-scrollable" id="failed_system"></pre> + </div> + <hr> + <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_overview_system">+</button><a href="#collapse_overview_system" data-toggle="collapse"><code>systemctl --system status</code></a></p> + <div class="collapse" id="collapse_overview_system"> + <pre class="pre-scrollable" id="overview_system"></pre> + </div> + <hr> + <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_timers_system">+</button><a href="#collapse_timers_system" data-toggle="collapse"><code>systemctl --system list-timers --all</code></a></p> + <div class="collapse" id="collapse_timers_system"> + <pre class="pre-scrollable" id="timers_system"></pre> + </div> + <hr> + <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_journal_system">+</button><a href="#collapse_journal_system" data-toggle="collapse"><code>journalctl --system -b --lines=20</code></a></p> + <div class="collapse" id="collapse_journal_system"> + <pre class="pre-scrollable" id="journal_system"></pre> + </div> + <hr> + </div> + </div> + <div id="users"> + </div> + </div> + <script src="js/jquery.min.js"></script> + <script src="js/bootstrap.bundle.min.js"></script> + <script> +function format_duration(duration) { + let MSECS_IN_MIN = 60 * 1000; + let MSECS_IN_HOUR = 60 * MSECS_IN_MIN; + let MSECS_IN_DAY = 24 * MSECS_IN_HOUR; + + let days = Math.floor(duration / MSECS_IN_DAY); + duration -= days * MSECS_IN_DAY; + + let hours = Math.floor(duration / MSECS_IN_HOUR); + duration -= hours * MSECS_IN_HOUR; + + let mins = Math.floor(duration / MSECS_IN_MIN); + duration -= mins * MSECS_IN_MIN; + + if (days > 0) { + let result = `${days}d`; + if (days == 1 && hours > 0) + result += ` ${hours}h`; + return result; + } + + if (hours > 0) { + let result = `${hours}h`; + if (hours == 1 && mins > 0) + result += ` ${mins}m`; + return result; + } + + return ` ${mins}m`; +} + +function dump_fail(data) { + console.log('Response code was: ' + data.status + ' ' + data.statusText); + console.log('Response was:\n' + data.responseText); +} + +function get(url, success_callback) { + $.get(url, success_callback).fail(dump_fail); +} + +function reboot() { + get('reboot'); +} + +function shutdown() { + get('poweroff'); +} + +function set_hostname(data) { + $('#hostname').text(data); + $('title').text(data); +} + +function set_top(data) { + $('#top').text(data); +} + +function refresh_top() { + get('top', function(data) { + set_top(JSON.parse(data)); + }); +} + +var top_refresh_interval_seconds = 5; + +function loop_top() { + setInterval(function() { refresh_top(); }, top_refresh_interval_seconds * 1000); + $('#top_refresh_interval').text(top_refresh_interval_seconds); +} + +function docker_container_is_ok(info) { + if (info.status == 'restarting' || info.status == 'dead') + return false; + if (info.status == 'exited' && info.exit_code != 0) + return false; + if (info.health == 'unhealthy') + return false; + return true; +} + +function docker_container_format_status(info) { + let result = info.status; + result = result.charAt(0).toUpperCase() + result.slice(1); + + if (info.status == 'exited' && info.exit_code != 0) + result += ` (${info.exit_code})`; + + if (info.status == 'running') { + if (info.health == 'unhealthy') { + result = 'Up (unhealthy)'; + } else { + let since = new Date(info.started_at); + result = `Up ${format_duration(Date.now() - since)}`; + } + } + + return result; +} + +function docker_fill_data(data) { + data.forEach(function(info) { + info.ok = docker_container_is_ok(info); + info.pretty_status = docker_container_format_status(info); + }); +} + +function make_docker_table_header() { + return $('<thead/>') + .append($('<tr/>') + .append($('<th/>')) + .append($('<th/>').text('Container')) + .append($('<th/>').text('Image')) + .append($('<th/>').text('Status'))); +} + +function make_docker_table_row(info) { + let success_mark = $('<span/>', {'class': 'text-success'}).html('✔'); + let failure_mark = $('<span/>', {'class': 'text-danger'}).html('✘'); + let success_class = 'table-light'; + let failure_class = 'table-warning'; + + let mark = success_mark; + let _class = success_class; + if (!info.ok) { + mark = failure_mark; + _class = failure_class; + } + + return $('<tr/>', {'class': _class}) + .append($('<td/>').html(mark)) + .append($('<td/>').append($('<code/>', {'class': 'text-reset'}).text(info.name))) + .append($('<td/>').append($('<code/>', {'class': 'text-reset'}).text(info.image))) + .append($('<td/>').text(info.pretty_status)); +} + +function make_docker_table(data) { + let body = $('<tbody/>'); + data.forEach(function(info) { + body.append(make_docker_table_row(info)); + }); + let table = $('<div/>', {'class': 'table-responsive'}) + .append($('<table/>', {'class': 'table table-hover table-sm text-nowrap'}) + .append(make_docker_table_header()) + .append(body)); + return table; +} + +function set_docker(data) { + docker_fill_data(data); + + $('#docker').empty(); + $('#docker').append(make_docker_table(data)); + + data.forEach(function(info) { + if (!info.ok) + $('#collapse_docker').addClass('show'); + }); +} + +function set_system(data) { + if ('docker' in data) { + set_docker(data['docker']); + } + if ('failed' in data) { + $('#failed_system').text(data['failed']); + } + if ('overview' in data) { + $('#overview_system').text(data['overview']); + } + if ('timers' in data) { + $('#timers_system').text(data['timers']); + } + if ('journal' in data) { + $('#journal_system').text(data['journal']); + } +} + +var users = []; + +function create_user_block(name, lbl, cmd) { + let pre_id = `${lbl}_user_${name}`; + let collapse_id = `collapse_${pre_id}`; + let button_params = { + 'class': 'btn btn-outline-primary btn-sm mr-3', + 'data-toggle': 'collapse', + 'data-target': '#' + collapse_id + }; + let a_params = { + href: '#' + collapse_id, + 'data-toggle': 'collapse' + }; + return $('<div/>') + .append($('<p/>') + .append($('<button/>', button_params).text('+')) + .append($('<a/>', a_params) + .append($('<code/>').text(cmd)))) + .append($('<div/>', {'class': 'collapse', id: collapse_id}) + .append($('<pre/>', {'class': 'pre-scrollable', id: pre_id}))) + .append($('<hr/>')); +} + +function add_user(name) { + if (users.includes(name)) { + return; + } + let container = $('<div/>', {'class': 'row', id: 'user_' + name}) + .append($('<div/>', {'class': 'col'}) + .append($('<h2/>').text(name)) + .append($('<hr/>')) + .append(create_user_block(name, 'failed', 'systemctl --user list-units --failed')) + .append(create_user_block(name, 'overview', 'systemctl --user status')) + .append(create_user_block(name, 'timers', 'systemctl --user list-timers --all')) + .append(create_user_block(name, 'journal', 'journalctl --user -b --lines=20'))); + + $('#users').append(container); + $('#collapse_failed_user_' + name).addClass('show'); + users.push(name); +} + +function set_user(name, data) { + add_user(name); + if ('failed' in data) { + $('#failed_user_' + name).text(data['failed']); + } + if ('overview' in data) { + $('#overview_user_' + name).text(data['overview']); + } + if ('timers' in data) { + $('#timers_user_' + name).text(data['timers']); + } + if ('journal' in data) { + $('#journal_user_' + name).text(data['journal']); + } +} + +function set_users(data) { + users.forEach(function(name) { + if (!(name in data)) { + $('#user_' + name).remove(); + let i = users.indexOf(name); + if (i > -1) { + users.splice(i, 1); + } + } + }); + Object.keys(data).forEach(function(name) { + set_user(name, data[name]); + }); +} + +function refresh_status() { + get('status', function(data) { + data = JSON.parse(data); + set_hostname(data['hostname']); + set_system(data['system']); + set_users(data['user']); + }); +} + +var status_refresh_interval_seconds = 30; + +function loop_status() { + setInterval(function() { + refresh_status(); + }, status_refresh_interval_seconds * 1000); + $('#status_refresh_interval').text(status_refresh_interval_seconds); +} + +function main() { + refresh_top(); + refresh_status(); + loop_top(); + loop_status(); +} + +$(function() { + main(); +}); + </script> + </body> +</html> -- cgit v1.2.3 From ee66f91e68bbe489146f087d3bb12c124ce2b1c2 Mon Sep 17 00:00:00 2001 From: Egor Tensin <Egor.Tensin@gmail.com> Date: Mon, 11 Apr 2022 01:44:19 +0200 Subject: index.html: display placeholders if no data --- html/index.html | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'html/index.html') diff --git a/html/index.html b/html/index.html index 5a4f56f..a116867 100644 --- a/html/index.html +++ b/html/index.html @@ -25,32 +25,34 @@ <hr> <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_top">+</button><a href="#collapse_top" data-toggle="collapse"><code>top</code></a> <span class="float-right"><small>refreshed every <span id="top_refresh_interval">-</span> seconds</small></span></p> <div class="collapse show" id="collapse_top"> - <pre class="pre-scrollable" id="top"></pre> + <pre class="pre-scrollable" id="top">Data not loaded.</pre> </div> <hr> <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_docker">+</button><a href="#collapse_docker" data-toggle="collapse"><code>docker ps -a</code></a></p> <div class="collapse" id="collapse_docker"> - <div id="docker">No data has been loaded yet.</div> + <div id="docker"> + <pre class="pre-scrollable">Data not loaded.</pre> + </div> </div> <hr> <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_failed_system">+</button><a href="#collapse_failed_system" data-toggle="collapse"><code>systemctl --system list-units --failed</code></a></p> <div class="collapse show" id="collapse_failed_system"> - <pre class="pre-scrollable" id="failed_system"></pre> + <pre class="pre-scrollable" id="failed_system">Data not loaded.</pre> </div> <hr> <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_overview_system">+</button><a href="#collapse_overview_system" data-toggle="collapse"><code>systemctl --system status</code></a></p> <div class="collapse" id="collapse_overview_system"> - <pre class="pre-scrollable" id="overview_system"></pre> + <pre class="pre-scrollable" id="overview_system">Data not loaded.</pre> </div> <hr> <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_timers_system">+</button><a href="#collapse_timers_system" data-toggle="collapse"><code>systemctl --system list-timers --all</code></a></p> <div class="collapse" id="collapse_timers_system"> - <pre class="pre-scrollable" id="timers_system"></pre> + <pre class="pre-scrollable" id="timers_system">Data not loaded.</pre> </div> <hr> <p><button type="button" class="btn btn-outline-primary btn-sm mr-3" data-toggle="collapse" data-target="#collapse_journal_system">+</button><a href="#collapse_journal_system" data-toggle="collapse"><code>journalctl --system -b --lines=20</code></a></p> <div class="collapse" id="collapse_journal_system"> - <pre class="pre-scrollable" id="journal_system"></pre> + <pre class="pre-scrollable" id="journal_system">Data not loaded.</pre> </div> <hr> </div> @@ -258,7 +260,8 @@ function create_user_block(name, lbl, cmd) { .append($('<a/>', a_params) .append($('<code/>').text(cmd)))) .append($('<div/>', {'class': 'collapse', id: collapse_id}) - .append($('<pre/>', {'class': 'pre-scrollable', id: pre_id}))) + .append($('<pre/>', {'class': 'pre-scrollable', id: pre_id}) + .text('Data not loaded.'))) .append($('<hr/>')); } -- cgit v1.2.3 From 0973338dbd24ff914ad8e5694b61f0c6c706c1d9 Mon Sep 17 00:00:00 2001 From: Egor Tensin <Egor.Tensin@gmail.com> Date: Mon, 11 Apr 2022 02:55:56 +0200 Subject: index.html: use flexbox for header Drop the weird workaround. Also, make the power buttons stack vertically on my phone. --- html/index.html | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) (limited to 'html/index.html') diff --git a/html/index.html b/html/index.html index a116867..659b3b4 100644 --- a/html/index.html +++ b/html/index.html @@ -10,13 +10,15 @@ <div class="container"> <div class="row mt-3"> <div class="col"> - <h1 id="hostname">-</h1> - <p class="mb-0"><small>refreshed every <span id="status_refresh_interval">-</span> seconds</small></p> - </div> - <div class="col h1 text-right"> - <div class="btn-group align-middle" role="group"> - <a role="button" class="btn btn-sm btn-warning" href="#" onclick="reboot();">Reboot</a> - <a role="button" class="btn btn-sm btn-danger" href="#" onclick="shutdown();">Shutdown</a> + <div style="display: flex; justify-content: space-between; align-items: center; gap: .5em;"> + <div> + <h1 id="hostname">-</h1> + <p class="mb-0 small">refreshed every <span id="status_refresh_interval">-</span> seconds</p> + </div> + <div class="btn-group" role="group" id="power_buttons"> + <a role="button" class="btn btn-sm btn-warning" href="#" onclick="reboot();">Reboot</a> + <a role="button" class="btn btn-sm btn-danger" href="#" onclick="shutdown();">Shutdown</a> + </div> </div> </div> </div> @@ -342,6 +344,18 @@ function main() { $(function() { main(); }); + +function on_resize(width) { + if (width < 576) { // xs, in Bootstrap's terms + $('#power_buttons').attr('class', 'btn-group-vertical'); + } else { + $('#power_buttons').attr('class', 'btn-group'); + } +} + +$(window).bind('resize', function() { + on_resize($(this).width()); +}).trigger('resize'); </script> </body> </html> -- cgit v1.2.3 From b2f70aa8bbdafb3768a11bbca85a60f7100a3aeb Mon Sep 17 00:00:00 2001 From: Egor Tensin <Egor.Tensin@gmail.com> Date: Mon, 11 Apr 2022 03:15:11 +0200 Subject: index.html: add a footer --- html/index.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'html/index.html') diff --git a/html/index.html b/html/index.html index 659b3b4..99a09be 100644 --- a/html/index.html +++ b/html/index.html @@ -6,8 +6,8 @@ <title>-</title> <link rel="stylesheet" href="css/bootstrap.min.css"> </head> - <body> - <div class="container"> + <body class="d-flex flex-column h-100"> + <div class="container flex-shrink-0"> <div class="row mt-3"> <div class="col"> <div style="display: flex; justify-content: space-between; align-items: center; gap: .5em;"> @@ -62,6 +62,11 @@ <div id="users"> </div> </div> + <footer class="mt-auto py-3 border-top bg-light text-center text-muted small"> + <div class="container"> + <a href="https://github.com/egor-tensin/linux-status">linux-status</a> — simple Linux server monitoring by <a href="mailto:Egor.Tensin@gmail.com">Egor Tensin</a> + </div> + </footer> <script src="js/jquery.min.js"></script> <script src="js/bootstrap.bundle.min.js"></script> <script> -- cgit v1.2.3 From eeef1164c22a73d04eb0bd643a07f1d901425770 Mon Sep 17 00:00:00 2001 From: Egor Tensin <Egor.Tensin@gmail.com> Date: Mon, 11 Apr 2022 03:32:28 +0200 Subject: index.html: get rid of extra whitespace --- html/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'html/index.html') diff --git a/html/index.html b/html/index.html index 99a09be..944005c 100644 --- a/html/index.html +++ b/html/index.html @@ -211,7 +211,7 @@ function make_docker_table(data) { body.append(make_docker_table_row(info)); }); let table = $('<div/>', {'class': 'table-responsive'}) - .append($('<table/>', {'class': 'table table-hover table-sm text-nowrap'}) + .append($('<table/>', {'class': 'table table-hover table-sm text-nowrap mb-0'}) .append(make_docker_table_header()) .append(body)); return table; -- cgit v1.2.3