From 8437fa6d9c3baedb450bebc94befdf86fea95d10 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 19 Apr 2026 18:13:34 -0700 Subject: [PATCH] lots of php proofreading and html tab tidying up --- files/api/DriveServer.py | 4 - files/api/StorageApi.py | 6 - files/api/new_descriptors.json | 3 +- files/{api => archive}/archive.py | 0 files/{ => archive}/vizz/docker/Dockerfile | 0 .../vizz/docker/apache_ports.conf | 0 .../vizz/docker/apache_vhost.conf | 0 .../vizz/docker/supervisord.conf | 0 .../vizz/docker/web/html/index.php | 0 .../vizz/docker/web/html/src/redis.js | 0 .../vizz/docker/web/html/src/styles.css | 0 .../docker/web/html/src/system_metrics.js | 0 .../vizz/docker/web/html/test.php | 0 .../vizz/docker/web/node_server/package.json | 0 .../vizz/docker/web/node_server/server.js | 0 .../vizz/docker/web/proxy/nginx.conf | 0 files/docker/apis/StorageSummary/Routes.py | 49 +- .../__pycache__/Routes.cpython-313.pyc | Bin 5706 -> 5538 bytes .../__pycache__/Storage.cpython-313.pyc | Bin 7493 -> 7493 bytes files/docker/apis/StorageSummary/app.py | 1 + .../apis/StorageSummary/storage_api_state.pkl | Bin 0 -> 8528 bytes files/docker/web/html/index.php | 184 ++++--- files/docker/web/html/src/styles.css | 147 +++++- files/docker/web/html/test.php | 477 ------------------ files/scripts/check_cosmostat.sh | 2 + 25 files changed, 248 insertions(+), 625 deletions(-) delete mode 100644 files/api/DriveServer.py delete mode 100644 files/api/StorageApi.py rename files/{api => archive}/archive.py (100%) rename files/{ => archive}/vizz/docker/Dockerfile (100%) rename files/{ => archive}/vizz/docker/apache_ports.conf (100%) rename files/{ => archive}/vizz/docker/apache_vhost.conf (100%) rename files/{ => archive}/vizz/docker/supervisord.conf (100%) rename files/{ => archive}/vizz/docker/web/html/index.php (100%) rename files/{ => archive}/vizz/docker/web/html/src/redis.js (100%) rename files/{ => archive}/vizz/docker/web/html/src/styles.css (100%) rename files/{ => archive}/vizz/docker/web/html/src/system_metrics.js (100%) rename files/{ => archive}/vizz/docker/web/html/test.php (100%) rename files/{ => archive}/vizz/docker/web/node_server/package.json (100%) rename files/{ => archive}/vizz/docker/web/node_server/server.js (100%) rename files/{ => archive}/vizz/docker/web/proxy/nginx.conf (100%) create mode 100644 files/docker/apis/StorageSummary/storage_api_state.pkl delete mode 100644 files/docker/web/html/test.php diff --git a/files/api/DriveServer.py b/files/api/DriveServer.py deleted file mode 100644 index 9a269b9..0000000 --- a/files/api/DriveServer.py +++ /dev/null @@ -1,4 +0,0 @@ -# This is the class definition for the remote Storage Systems -# There will be a StorageLinux and StorageWindows Class, as well as the LocalServer Class -# The LocalServer class will mostly be a List of Storage server objects and Class functions for interacting with them -# The actual Storage server Classes will mostly just be collections of variables diff --git a/files/api/StorageApi.py b/files/api/StorageApi.py deleted file mode 100644 index b06fc69..0000000 --- a/files/api/StorageApi.py +++ /dev/null @@ -1,6 +0,0 @@ -### This file contains the flask routes for interfacing with the DriveServer objects -### I need routes for adding/updating windows/linux hosts, as well as a query route -### There needs to also be a Redis handler, meaning also that this will render with PHP -### but have javascript to update if any Redis data happens -### This won't happen a lot, but it will happen occasionally - diff --git a/files/api/new_descriptors.json b/files/api/new_descriptors.json index b7f3250..1c4caba 100644 --- a/files/api/new_descriptors.json +++ b/files/api/new_descriptors.json @@ -1,6 +1,7 @@ [ { - "notes:": "this is both a scratch file and a reference for new component descriptors" + "notes:": "this is both a scratch file and a reference for new component descriptors", + "descriptor descriptor": "the reference descriptor is the next key" }, { "name": "", diff --git a/files/api/archive.py b/files/archive/archive.py similarity index 100% rename from files/api/archive.py rename to files/archive/archive.py diff --git a/files/vizz/docker/Dockerfile b/files/archive/vizz/docker/Dockerfile similarity index 100% rename from files/vizz/docker/Dockerfile rename to files/archive/vizz/docker/Dockerfile diff --git a/files/vizz/docker/apache_ports.conf b/files/archive/vizz/docker/apache_ports.conf similarity index 100% rename from files/vizz/docker/apache_ports.conf rename to files/archive/vizz/docker/apache_ports.conf diff --git a/files/vizz/docker/apache_vhost.conf b/files/archive/vizz/docker/apache_vhost.conf similarity index 100% rename from files/vizz/docker/apache_vhost.conf rename to files/archive/vizz/docker/apache_vhost.conf diff --git a/files/vizz/docker/supervisord.conf b/files/archive/vizz/docker/supervisord.conf similarity index 100% rename from files/vizz/docker/supervisord.conf rename to files/archive/vizz/docker/supervisord.conf diff --git a/files/vizz/docker/web/html/index.php b/files/archive/vizz/docker/web/html/index.php similarity index 100% rename from files/vizz/docker/web/html/index.php rename to files/archive/vizz/docker/web/html/index.php diff --git a/files/vizz/docker/web/html/src/redis.js b/files/archive/vizz/docker/web/html/src/redis.js similarity index 100% rename from files/vizz/docker/web/html/src/redis.js rename to files/archive/vizz/docker/web/html/src/redis.js diff --git a/files/vizz/docker/web/html/src/styles.css b/files/archive/vizz/docker/web/html/src/styles.css similarity index 100% rename from files/vizz/docker/web/html/src/styles.css rename to files/archive/vizz/docker/web/html/src/styles.css diff --git a/files/vizz/docker/web/html/src/system_metrics.js b/files/archive/vizz/docker/web/html/src/system_metrics.js similarity index 100% rename from files/vizz/docker/web/html/src/system_metrics.js rename to files/archive/vizz/docker/web/html/src/system_metrics.js diff --git a/files/vizz/docker/web/html/test.php b/files/archive/vizz/docker/web/html/test.php similarity index 100% rename from files/vizz/docker/web/html/test.php rename to files/archive/vizz/docker/web/html/test.php diff --git a/files/vizz/docker/web/node_server/package.json b/files/archive/vizz/docker/web/node_server/package.json similarity index 100% rename from files/vizz/docker/web/node_server/package.json rename to files/archive/vizz/docker/web/node_server/package.json diff --git a/files/vizz/docker/web/node_server/server.js b/files/archive/vizz/docker/web/node_server/server.js similarity index 100% rename from files/vizz/docker/web/node_server/server.js rename to files/archive/vizz/docker/web/node_server/server.js diff --git a/files/vizz/docker/web/proxy/nginx.conf b/files/archive/vizz/docker/web/proxy/nginx.conf similarity index 100% rename from files/vizz/docker/web/proxy/nginx.conf rename to files/archive/vizz/docker/web/proxy/nginx.conf diff --git a/files/docker/apis/StorageSummary/Routes.py b/files/docker/apis/StorageSummary/Routes.py index 2fe819b..a574430 100644 --- a/files/docker/apis/StorageSummary/Routes.py +++ b/files/docker/apis/StorageSummary/Routes.py @@ -12,7 +12,6 @@ from requests import RequestException, Response # import needed Class Libraries from Storage import * from Helpers import * -#SummaryServer = DriveHealthServer(get_hostname()) SummaryServer = load_state() @@ -20,12 +19,12 @@ if SummaryServer is None: SummaryServer = DriveHealthServer(get_hostname()) print("Created new SummaryServer") -# declare flask apps +# declare flask app app = Flask(__name__) -#scheduler = APScheduler() - +############################ # Flask routes +############################ # client update @app.route('/storage_client_update', methods=['POST']) @@ -49,7 +48,6 @@ def storage_client_delete(): print(result) return jsonify(result) - # client details @app.route('/client_details', methods=['GET']) def client_details(): @@ -86,20 +84,15 @@ def test_route(): "DriveHealthServer": f"{SummaryServer}" }) - # test route 2 @app.route('/test_storage_summary', methods=['GET']) def test_storage_summary(): - return jsonify({ - "message": "Hello world!", - "hostname": get_hostname(), - "DriveHealthServer": f"{SummaryServer}" - }) - - + return test_route() +############################ # Route Helpers +############################ # helper function for client_update route # handles the submission data from the flask route @@ -112,6 +105,12 @@ def client_update_helper(payload: dict): result = client_processor(processed_payload) return result +# client processing function, add/update logic in Class Methods +def client_processor(client_dict: dict): + result = SummaryServer.process_client_data(client_dict) + save_state(SummaryServer) + return result + # handle submission from remove route def client_remove_helper(payload: dict): result = None @@ -147,30 +146,12 @@ def post_processor(client_dict: dict, required_keys: dict): client_dict["processed_at"] = time.time() return client_dict -# Main functions - -# client processing function, add/update logic in Class Methods -def client_processor(client_dict: dict): - result = SummaryServer.process_client_data(client_dict) - save_state(SummaryServer) - return result - -def background_loop(): - return True +############################ +# Main function +############################ def run_main(): - #if SummaryServer is none: - - #atexit.register(lambda: save_state(SummaryServer)) test - # Flask scheduler for background loop, run if requested - #scheduler.add_job(id='background_loop', - # func=background_loop, - # trigger='interval', - # seconds=60) - #scheduler.init_app(app) - #scheduler.start() # Flask API - background_loop() app.run(debug=False, host='0.0.0.0', port=5001) diff --git a/files/docker/apis/StorageSummary/__pycache__/Routes.cpython-313.pyc b/files/docker/apis/StorageSummary/__pycache__/Routes.cpython-313.pyc index 36b9b84a9f29339a06ec344d5904c702f83b6e8b..ed1dd4fae1cf647f4246fa89f918d1c774c7a50e 100644 GIT binary patch delta 655 zcmXv~&ubG=5Z>8MVwz-=O|skku!-9=wg!y{S+&$Olr}aOiHBA6Pz<3-rD9@uYZ0Mf zq~H$(wag`Yv3M$opyUteRRke7A%}{fXIB!u>bwm;nD6^$-pres_xaS@lfj`Z`vf{q zH-2uu3GW8uu<>!u5YH#qW(?6hniOui*l{!NcvFryYto&WqKL~(JCQl_SW;joZb~`5 z*A<_LknT;p-vQ)$Up#F9qgV8n01CaGiCusaYW`1r%#ZZ^;!vemR>#&Z0Wdr9! zGB(0py-aB=!wdN9y3VF}p!6S_n2O}l2<~{7DU_4g3LXUGsf*-TE&`p-xMZ4_aIkO` zEQxHjR}O^$mhqaB@i<*p@qtqDt?)b!+xS&^fiD9ZoW||&4E~9nlf{8lYqtjy+iKB1 z#b{i;L7A`ktCIvBVdN3qUTjA-`k+VAZt?;}S%HI2Fz{BZty;5Dt+F+oSF@L#k6Ge3 zuKMQsU6vp@Ud|cUnT2;?iyYD%`2GWLp?Rmib-PhyEpma(dXDeay!2g3;y2X-74K}a*RB8cwCiHC{4KeK;zzuZam!T2o##D|g~p0&BM)gutu%Z>yhTb0I|$PReMn#hjC VAhzeS1)>jEMx2HV5*W@<{{UKbl(YZ< delta 803 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 literal 0 HcmV?d00001 diff --git a/files/docker/web/html/index.php b/files/docker/web/html/index.php index bfe7cf4..fe07203 100644 --- a/files/docker/web/html/index.php +++ b/files/docker/web/html/index.php @@ -9,7 +9,7 @@ date_default_timezone_set('America/Los_Angeles'); $remove_hosts = []; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { if ($_POST['action'] === 'remove') { - // The Remove form sends a comma‑separated string of short_id’s + // The Remove form sends a comma-separated string of short_id’s if (!empty($_POST['remove_hosts'])) { $remove_hosts = array_filter( explode(',', $_POST['remove_hosts']), @@ -156,7 +156,7 @@ if (!in_array($mode, $validModes, true)) { $apiConfig = [ 'cosmostat' => ['bind' => '10.200.27.20', 'port' => '5000'], /*'gali' => ['bind' => '10.200.27.20', 'port' => '5000'], // same as cosmostat*/ - 'drive_health' => ['bind' => '0.0.0.0', 'port' => '5001'], // new API + 'drive_health' => ['bind' => '172.25.1.18', 'port' => '5001'], // new API ]; /* ---------- Helper: fetch client details ---------- */ @@ -237,9 +237,6 @@ $systemComponents = $properties['system_components'] ?? []; $selectedHost = $clients[$selectedIdx]['hostname'] ?? 'Unknown'; - -/* ---- ---- */ - /* ---- Sidebar Renderer ---- */ function renderSidebar(string $mode){ @@ -255,74 +252,54 @@ function renderSidebar(string $mode){ @@ -344,7 +321,8 @@ function renderMainContent(string $mode){ echo '

No hosts selected.

'; return; } - echo '
'; + echo ' +
'; foreach ($selectedHosts as $sid) { // Find the client that matches this short_id $c = null; @@ -357,41 +335,49 @@ function renderMainContent(string $mode){ if ($c === null) continue; // safety $hostname = $c['name'] ?? 'Unknown'; - echo '
- '; - echo '

Drive Health - ' . h($hostname) . '

-

IP: '.h($c['ip']).'
- Timestamp: '.date('F j, Y g:i a', (int) $c['timestamp']).'

- '; + echo ' +
+

Drive Health - ' . h($hostname) . '

+

IP: '.h($c['ip']).'
+ Timestamp: '.date('F j, Y g:i a', (int) $c['timestamp']).'

+ '; if (isset($c['drives']) && is_array($c['drives']) && count($c['drives']) > 0) { - echo ''; - echo ' - '; - echo ' - '; + echo ' +
Drive LetterDisk IDHealth StatusModelCapacityPower On HoursHost WritesWear Level
+ + + + '; foreach ($c['drives'] as $drive) { - echo ' - '; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo ' - '; + $wear_gb = ''; + if (isset($drive['host_writes'])){ + $wear_gb = $drive['host_writes'].' wear'; + } + echo ' + + + + + + + + '; } - echo '
Drive LetterDisk IDHealth MetricsModelSerialCapacity
' . h($drive['drive_letter'] ?? '') . '' . h("".$drive['disk_id'] ?? '') . '' . h($drive['health_status'] ?? '') . '' . h($drive['model'] ?? '') . '' . h($drive['capacity'] ?? '') . '' . h($drive['power_on_hours'] ?? '') . '' . h($drive['host_writes'] ?? '') . '' . h($drive['wear_level'] ?? '') . '
' . h($drive['drive_letter'] ?? '') . '' . h("".$drive['disk_id'] ?? '') . '' . + h($drive['health_status'] ?? '').'
'. + h($drive['power_on_hours'] ?? '').'
'. + h($wear_gb ?? ''). + '
' . h($drive['model'] ?? '') . '' . h($drive['serial'] ?? '') . '' . h($drive['capacity'] ?? '') . '
- '; + echo ' + '; } else { echo '

No drive data available for this host.

'; } - echo '
'; + echo ' +
'; } - echo '
'; + echo ' +
'; return; } ?> @@ -477,10 +463,12 @@ function renderMainContent(string $mode){
- - - - + + + + + +
@@ -507,11 +495,11 @@ function renderMainContent(string $mode){ diff --git a/files/docker/web/html/src/styles.css b/files/docker/web/html/src/styles.css index 0140e04..221f96a 100644 --- a/files/docker/web/html/src/styles.css +++ b/files/docker/web/html/src/styles.css @@ -6,12 +6,25 @@ --bg-body: #2c3e50; /* main page background */ --bg-card: #34495e; /* card / panel background */ --bg-sidebar: #3d566e; /* sidebar background (slightly lighter) */ - /* Accent / link colour */ + /* Accent / link color */ --clr-accent: #3498db; /* blue accent for links */ - /* Text colour */ + /* Text color */ --clr-text: #ecf0f1; /* light whiteish text */ /* Borders / accents */ - --clr-border: #1f2b38; /* dark border colour */ + --clr-border: #1f2b38; /* dark border color */ + /* button colors*/ + --clr-btn-bg: var(--bg-card); /* default button background */ + --clr-btn-text: var(--clr-text); /* button text color */ + --clr-btn-border: var(--clr-border); /* border color */ + --clr-btn-accent: var(--clr-accent); /* hover/focus color */ + --clr-btn-shadow: rgba(0,0,0,0.3); + /* select colors */ + --clr-select-bg: var(--bg-card); /* dropdown background */ + --clr-select-text: var(--clr-text); /* option text colour */ + --clr-select-border: var(--clr-border); + --clr-select-accent: var(--clr-accent);/* focus / hover border */ + --clr-select-shadow: rgba(0,0,0,.3); + } * { box-sizing: border-box; } @@ -84,7 +97,7 @@ table, th, td { } th, td { padding: 10px; } -/* Alternate row colour for metrics table */ +/* Alternate row color for metrics table */ #host_metrics_table tbody tr td:nth-of-type(even) { background: #3e5c78; /* slight contrast */ } @@ -154,7 +167,7 @@ li { margin-bottom: 10px; color: var(--clr-text); } margin-left: 6px; margin-right: 8px; vertical-align: middle; - background: #808080; /* default – unknown / no timestamp */ + background: #808080; /* default - unknown / no timestamp */ transition: background-color 1s linear; /* smooth fade */ } @@ -258,3 +271,127 @@ li { margin-bottom: 10px; color: var(--clr-text); } } +/* lets make buttons nice*/ + +/* 2. Base button style */ +.btn { + display: inline-block; + padding: .55rem 1.25rem; + font-size: .9rem; + font-weight: 500; + line-height: 1.4; + color: var(--clr-btn-text); + background: var(--clr-btn-bg); + border: 1px solid var(--clr-btn-border); + border-radius: .25rem; + cursor: pointer; + transition: background .15s ease, box-shadow .15s ease, transform .075s ease; + text-align: center; + user-select: none; + vertical-align: middle; +} + +/* 3. Primary button - accent color */ +.btn-primary { + background: var(--clr-btn-accent); + border-color: var(--clr-btn-accent); + color: #fff; /* brighter text for dark button */ +} + +/* 4. Secondary button - subtle background */ +.btn-secondary { + background: transparent; + border-color: var(--clr-btn-accent); + color: var(--clr-btn-accent); +} + +/* 5. Hover & focus states */ +.btn:hover, +.btn:focus-visible { + background: #2b3f4c; /* a slightly darker shade of card bg */ + box-shadow: 0 3px 8px var(--clr-btn-shadow); + transform: translateY(-1px); + outline: 0; +} + +/* 6. Disabled state */ +.btn:disabled, +.btn[aria-disabled="true"] { + opacity: .55; + cursor: not-allowed; + background: var(--bg-card); + border-color: var(--clr-border); +} + +/* 7. Extra - block‑level button */ +.btn-block { + display: block; + width: 100%; + text-align: center; +} + +/* let's also make the style */ +.select-dark { + appearance: none; /* hide native arrow in Chrome/Edge/FF */ + -webkit-appearance: none; + -moz-appearance: none; + + display: inline-block; + padding: .45rem 1.25rem .45rem .75rem; + font-size: .9rem; + line-height: 1.4; + color: var(--clr-select-text); + background: var(--clr-select-bg); + border: 1px solid var(--clr-select-border); + border-radius: .25rem; + cursor: pointer; + position: relative; + min-width: 150px; /* optional - forces a decent width */ + + /* Custom arrow - positioned after the element */ + background-image: linear-gradient(45deg, transparent 50%, var(--clr-select-accent) 50%), + linear-gradient(135deg, var(--clr-select-accent) 50%, transparent 50%), + linear-gradient(to right, var(--clr-select-border), var(--clr-select-border)); + background-position: + calc(100% - 20px) calc(50% + 2px), + calc(100% - 15px) calc(50% + 2px), + calc(100% - 3px) 50%; + background-size: + 5px 5px, + 5px 5px, + 1px 1.5rem; + background-repeat: no-repeat; +} + +/* Hover & focus - subtle accent border */ +.select-dark:hover, +.select-dark:focus-visible { + border-color: var(--clr-select-accent); + box-shadow: 0 0 3px var(--clr-select-shadow); + outline: none; /* rely on the box-shadow for focus */ +} + +/* Disabled state - greys out the control */ +.select-dark:disabled, +.select-dark[aria-disabled="true"] { + opacity: .55; + cursor: not-allowed; + background: var(--bg-card); + border-color: var(--clr-border); +} + +/*