lots of php proofreading and html tab tidying up
This commit is contained in:
@ -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
|
|
||||||
@ -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
|
|
||||||
|
|
||||||
@ -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": "",
|
"name": "",
|
||||||
|
|||||||
@ -12,7 +12,6 @@ from requests import RequestException, Response
|
|||||||
# import needed Class Libraries
|
# import needed Class Libraries
|
||||||
from Storage import *
|
from Storage import *
|
||||||
from Helpers import *
|
from Helpers import *
|
||||||
#SummaryServer = DriveHealthServer(get_hostname())
|
|
||||||
|
|
||||||
SummaryServer = load_state()
|
SummaryServer = load_state()
|
||||||
|
|
||||||
@ -20,12 +19,12 @@ if SummaryServer is None:
|
|||||||
SummaryServer = DriveHealthServer(get_hostname())
|
SummaryServer = DriveHealthServer(get_hostname())
|
||||||
print("Created new SummaryServer")
|
print("Created new SummaryServer")
|
||||||
|
|
||||||
# declare flask apps
|
# declare flask app
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
#scheduler = APScheduler()
|
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
# Flask routes
|
# Flask routes
|
||||||
|
############################
|
||||||
|
|
||||||
# client update
|
# client update
|
||||||
@app.route('/storage_client_update', methods=['POST'])
|
@app.route('/storage_client_update', methods=['POST'])
|
||||||
@ -49,7 +48,6 @@ def storage_client_delete():
|
|||||||
print(result)
|
print(result)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
# client details
|
# client details
|
||||||
@app.route('/client_details', methods=['GET'])
|
@app.route('/client_details', methods=['GET'])
|
||||||
def client_details():
|
def client_details():
|
||||||
@ -86,20 +84,15 @@ def test_route():
|
|||||||
"DriveHealthServer": f"{SummaryServer}"
|
"DriveHealthServer": f"{SummaryServer}"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# test route 2
|
# test route 2
|
||||||
@app.route('/test_storage_summary', methods=['GET'])
|
@app.route('/test_storage_summary', methods=['GET'])
|
||||||
def test_storage_summary():
|
def test_storage_summary():
|
||||||
return jsonify({
|
return test_route()
|
||||||
"message": "Hello world!",
|
|
||||||
"hostname": get_hostname(),
|
|
||||||
"DriveHealthServer": f"{SummaryServer}"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
############################
|
||||||
# Route Helpers
|
# Route Helpers
|
||||||
|
############################
|
||||||
|
|
||||||
# helper function for client_update route
|
# helper function for client_update route
|
||||||
# handles the submission data from the flask route
|
# handles the submission data from the flask route
|
||||||
@ -112,6 +105,12 @@ def client_update_helper(payload: dict):
|
|||||||
result = client_processor(processed_payload)
|
result = client_processor(processed_payload)
|
||||||
return result
|
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
|
# handle submission from remove route
|
||||||
def client_remove_helper(payload: dict):
|
def client_remove_helper(payload: dict):
|
||||||
result = None
|
result = None
|
||||||
@ -147,30 +146,12 @@ def post_processor(client_dict: dict, required_keys: dict):
|
|||||||
client_dict["processed_at"] = time.time()
|
client_dict["processed_at"] = time.time()
|
||||||
return client_dict
|
return client_dict
|
||||||
|
|
||||||
# Main functions
|
############################
|
||||||
|
# Main function
|
||||||
# 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
|
|
||||||
|
|
||||||
def run_main():
|
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
|
# Flask API
|
||||||
background_loop()
|
|
||||||
app.run(debug=False, host='0.0.0.0', port=5001)
|
app.run(debug=False, host='0.0.0.0', port=5001)
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -1,4 +1,5 @@
|
|||||||
# main function for storage API
|
# main function for storage API
|
||||||
|
# this is the barest of bones to load the api
|
||||||
|
|
||||||
# import class libraries
|
# import class libraries
|
||||||
from Routes import *
|
from Routes import *
|
||||||
|
|||||||
BIN
files/docker/apis/StorageSummary/storage_api_state.pkl
Normal file
BIN
files/docker/apis/StorageSummary/storage_api_state.pkl
Normal file
Binary file not shown.
@ -9,7 +9,7 @@ date_default_timezone_set('America/Los_Angeles');
|
|||||||
$remove_hosts = [];
|
$remove_hosts = [];
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
if ($_POST['action'] === 'remove') {
|
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'])) {
|
if (!empty($_POST['remove_hosts'])) {
|
||||||
$remove_hosts = array_filter(
|
$remove_hosts = array_filter(
|
||||||
explode(',', $_POST['remove_hosts']),
|
explode(',', $_POST['remove_hosts']),
|
||||||
@ -156,7 +156,7 @@ if (!in_array($mode, $validModes, true)) {
|
|||||||
$apiConfig = [
|
$apiConfig = [
|
||||||
'cosmostat' => ['bind' => '10.200.27.20', 'port' => '5000'],
|
'cosmostat' => ['bind' => '10.200.27.20', 'port' => '5000'],
|
||||||
/*'gali' => ['bind' => '10.200.27.20', 'port' => '5000'], // same as cosmostat*/
|
/*'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 ---------- */
|
/* ---------- Helper: fetch client details ---------- */
|
||||||
@ -237,9 +237,6 @@ $systemComponents = $properties['system_components'] ?? [];
|
|||||||
$selectedHost = $clients[$selectedIdx]['hostname'] ?? 'Unknown';
|
$selectedHost = $clients[$selectedIdx]['hostname'] ?? 'Unknown';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ---- ---- */
|
|
||||||
|
|
||||||
/* ---- Sidebar Renderer ---- */
|
/* ---- Sidebar Renderer ---- */
|
||||||
|
|
||||||
function renderSidebar(string $mode){
|
function renderSidebar(string $mode){
|
||||||
@ -255,74 +252,54 @@ function renderSidebar(string $mode){
|
|||||||
<nav class="sidebar">
|
<nav class="sidebar">
|
||||||
<form method="get" id="modeForm">
|
<form method="get" id="modeForm">
|
||||||
<label for="modeSelect">Mode:</label>
|
<label for="modeSelect">Mode:</label>
|
||||||
<select name="mode" id="modeSelect" onchange="this.form.submit()">
|
<select class="select-dark" name="mode" id="modeSelect" onchange="this.form.submit()">
|
||||||
<?php foreach ($modes as $key => $label): ?><option value="<?= h($key) ?>" <?= $mode === $key ? 'selected' : '' ?>><?= h($label) ?></option>
|
<?php foreach ($modes as $key => $label): ?><option value="<?= h($key) ?>" <?= $mode === $key ? 'selected' : '' ?>><?= h($label) ?></option>
|
||||||
<?php endforeach; ?></select>
|
<?php endforeach; ?></select>
|
||||||
</form>
|
</form>
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
<?php if ($mode === 'drive_health'): ?>
|
<?php if ($mode === 'drive_health'): ?>
|
||||||
|
<?php
|
||||||
<?php
|
if ( !is_array($clients) || empty($clients) ) {
|
||||||
if ( !is_array($clients) || empty($clients) ) {
|
// Graceful “no data” handling
|
||||||
// Graceful “no data” handling
|
echo '<p style="margin:1rem 0; font-style:italic;">'
|
||||||
echo '<p style="margin:1rem 0; font-style:italic;">'
|
. 'No hosts are available to manage at this time.'
|
||||||
. 'No hosts are available to manage at this time.'
|
. '</p>';
|
||||||
. '</p>';
|
|
||||||
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<?php if (is_array($clients) && !empty($clients)): ?>
|
<?php if (is_array($clients) && !empty($clients)): ?>
|
||||||
<h3>Hosts</h3>
|
|
||||||
<form method="get" id="driveHealthForm">
|
|
||||||
<input type="hidden" name="mode" value="drive_health">
|
|
||||||
<ul>
|
|
||||||
<?php foreach ($clients as $c): ?>
|
|
||||||
<?php $id = $c['short_id']; ?>
|
|
||||||
<li>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="hosts[]" value="<?= h($id) ?>"
|
|
||||||
<?= in_array($id, $selectedHosts, true) ? 'checked' : '' ?>>
|
|
||||||
<?= h($c['name']) ?>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul><p>
|
|
||||||
<button type="submit" name="action" value="apply">Apply</button><p>
|
|
||||||
<button type="submit" name="action" value="all">Select All</button><br>
|
|
||||||
<button type="submit" name="action" value="none">Select None</button><p>
|
|
||||||
</form>
|
|
||||||
<!-- Remove host button (POST) -->
|
|
||||||
<form method="post" id="removeForm">
|
|
||||||
<input type="hidden" name="mode" value="drive_health">
|
|
||||||
<input type="hidden" name="action" value="remove">
|
|
||||||
<input type="hidden" name="remove_hosts" id="remove_hosts_input" value="">
|
|
||||||
<button type="submit">Remove</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($mode == 'cosmostat'): ?>
|
<h3>Hosts</h3>
|
||||||
|
<form method="get" id="driveHealthForm">
|
||||||
<input type="hidden" name="host" value="<?= h($selectedId) ?>">
|
<input type="hidden" name="mode" value="drive_health">
|
||||||
<h3>Endpoints</h3>
|
|
||||||
<ol id="endpointList"></ol>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($mode == 'gali'): ?>
|
|
||||||
|
|
||||||
<h3>Shuttle Gali</h3>
|
|
||||||
<?php endif; ?>
|
|
||||||
<!--
|
|
||||||
<?php if (!empty($remove_hosts)): ?>
|
|
||||||
<h4>Remove Selected:</h4>
|
|
||||||
<ul>
|
<ul>
|
||||||
<?php foreach ($remove_hosts as $sid): ?>
|
<?php foreach ($clients as $c): ?>
|
||||||
<li><?= h($sid) ?></li>
|
<?php $id = $c['short_id']; ?>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="hosts[]" value="<?= h($id) ?>" <?= in_array($id, $selectedHosts, true) ? 'checked' : '' ?>><?= h($c['name']) ?>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul><p>
|
||||||
<?php endif; ?>
|
<button class="btn btn-primary" type="submit" name="action" value="apply">Apply</button><p>
|
||||||
-->
|
<button class="btn btn-primary" type="submit" name="action" value="all">Select All</button><p>
|
||||||
|
<button class="btn btn-primary" type="submit" name="action" value="none">Select None</button><p>
|
||||||
|
</form>
|
||||||
|
<!-- Remove host button (POST) -->
|
||||||
|
<form method="post" id="removeForm">
|
||||||
|
<input type="hidden" name="mode" value="drive_health">
|
||||||
|
<input type="hidden" name="action" value="remove">
|
||||||
|
<input type="hidden" name="remove_hosts" id="remove_hosts_input" value="">
|
||||||
|
<button class="btn btn-primary" type="submit">Remove</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($mode == 'gali'): ?>
|
||||||
|
<h3>Shuttle Gali</h3>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@ -344,7 +321,8 @@ function renderMainContent(string $mode){
|
|||||||
echo '<div class="card"><p>No hosts selected.</p></div>';
|
echo '<div class="card"><p>No hosts selected.</p></div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
echo '<div class="storage_client">';
|
echo '
|
||||||
|
<div class="storage_client">';
|
||||||
foreach ($selectedHosts as $sid) {
|
foreach ($selectedHosts as $sid) {
|
||||||
// Find the client that matches this short_id
|
// Find the client that matches this short_id
|
||||||
$c = null;
|
$c = null;
|
||||||
@ -357,41 +335,49 @@ function renderMainContent(string $mode){
|
|||||||
if ($c === null) continue; // safety
|
if ($c === null) continue; // safety
|
||||||
|
|
||||||
$hostname = $c['name'] ?? 'Unknown';
|
$hostname = $c['name'] ?? 'Unknown';
|
||||||
echo '<div class="card">
|
echo '
|
||||||
';
|
<div class="card">
|
||||||
echo '<h2>Drive Health - ' . h($hostname) . '</h2>
|
<h2>Drive Health - ' . h($hostname) . '</h2>
|
||||||
<h3>IP: '.h($c['ip']).'<br>
|
<h3>IP: '.h($c['ip']).'<br>
|
||||||
Timestamp: '.date('F j, Y g:i a', (int) $c['timestamp']).' </h3>
|
Timestamp: '.date('F j, Y g:i a', (int) $c['timestamp']).' </h3>
|
||||||
';
|
';
|
||||||
|
|
||||||
if (isset($c['drives']) && is_array($c['drives']) && count($c['drives']) > 0) {
|
if (isset($c['drives']) && is_array($c['drives']) && count($c['drives']) > 0) {
|
||||||
echo '<table id="host_metrics_table">';
|
echo '
|
||||||
echo '<thead><tr><th>Drive Letter</th><th>Disk ID</th><th>Health Status</th><th>Model</th><th>Capacity</th><th>Power On Hours</th><th>Host Writes</th><th>Wear Level</th></tr></thead>
|
<table id="host_metrics_table">
|
||||||
';
|
<thead><tr>
|
||||||
echo '<tbody>
|
<th>Drive Letter</th><th>Disk ID</th><th>Health Metrics</th><th>Model</th><th>Serial</th><th>Capacity</th>
|
||||||
';
|
</tr></thead>
|
||||||
|
<tbody>';
|
||||||
foreach ($c['drives'] as $drive) {
|
foreach ($c['drives'] as $drive) {
|
||||||
echo '<tr>
|
$wear_gb = '';
|
||||||
';
|
if (isset($drive['host_writes'])){
|
||||||
echo '<td>' . h($drive['drive_letter'] ?? '') . '</td>';
|
$wear_gb = $drive['host_writes'].' wear';
|
||||||
echo '<td>' . h("".$drive['disk_id'] ?? '') . '</td>';
|
}
|
||||||
echo '<td>' . h($drive['health_status'] ?? '') . '</td>';
|
echo '
|
||||||
echo '<td>' . h($drive['model'] ?? '') . '</td>';
|
<tr>
|
||||||
echo '<td>' . h($drive['capacity'] ?? '') . '</td>';
|
<td>' . h($drive['drive_letter'] ?? '') . '</td>
|
||||||
echo '<td>' . h($drive['power_on_hours'] ?? '') . '</td>';
|
<td>' . h("".$drive['disk_id'] ?? '') . '</td>
|
||||||
echo '<td>' . h($drive['host_writes'] ?? '') . '</td>';
|
<td>' .
|
||||||
echo '<td>' . h($drive['wear_level'] ?? '') . '</td>';
|
h($drive['health_status'] ?? '').'<br>'.
|
||||||
echo '</tr>
|
h($drive['power_on_hours'] ?? '').'<br>'.
|
||||||
';
|
h($wear_gb ?? '').
|
||||||
|
'</td>
|
||||||
|
<td>' . h($drive['model'] ?? '') . '</td>
|
||||||
|
<td>' . h($drive['serial'] ?? '') . '</td>
|
||||||
|
<td>' . h($drive['capacity'] ?? '') . '</td>
|
||||||
|
</tr>';
|
||||||
}
|
}
|
||||||
echo '</tbody></table>
|
echo '
|
||||||
';
|
</tbody></table> ';
|
||||||
} else {
|
} else {
|
||||||
echo '<p>No drive data available for this host.</p>';
|
echo '<p>No drive data available for this host.</p>';
|
||||||
}
|
}
|
||||||
echo '</div>';
|
echo '
|
||||||
|
</div>';
|
||||||
}
|
}
|
||||||
echo '</div>';
|
echo '
|
||||||
|
</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@ -477,10 +463,12 @@ function renderMainContent(string $mode){
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<!-- Sidebar -->
|
|
||||||
<?php renderSidebar($mode); ?>
|
<!-- Sidebar -->
|
||||||
<!-- Main content -->
|
<?php renderSidebar($mode); ?>
|
||||||
<?php renderMainContent($mode); ?>
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<?php renderMainContent($mode); ?>
|
||||||
|
|
||||||
|
|
||||||
</div> <!-- /wrapper -->
|
</div> <!-- /wrapper -->
|
||||||
@ -507,11 +495,11 @@ function renderMainContent(string $mode){
|
|||||||
<script>
|
<script>
|
||||||
// Removal Handler
|
// Removal Handler
|
||||||
document.getElementById('removeForm').addEventListener('submit', function (e) {
|
document.getElementById('removeForm').addEventListener('submit', function (e) {
|
||||||
// Grab all checked checkboxes from the Drive‑Health form
|
// Grab all checked checkboxes from the Drive-Health form
|
||||||
const checked = document.querySelectorAll('#driveHealthForm input[type="checkbox"][name="hosts[]"]:checked');
|
const checked = document.querySelectorAll('#driveHealthForm input[type="checkbox"][name="hosts[]"]:checked');
|
||||||
const ids = Array.from(checked).map(cb => cb.value);
|
const ids = Array.from(checked).map(cb => cb.value);
|
||||||
|
|
||||||
// Pass the comma‑separated list to the hidden input
|
// Pass the comma-separated list to the hidden input
|
||||||
document.getElementById('remove_hosts_input').value = ids.join(',');
|
document.getElementById('remove_hosts_input').value = ids.join(',');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -6,12 +6,25 @@
|
|||||||
--bg-body: #2c3e50; /* main page background */
|
--bg-body: #2c3e50; /* main page background */
|
||||||
--bg-card: #34495e; /* card / panel background */
|
--bg-card: #34495e; /* card / panel background */
|
||||||
--bg-sidebar: #3d566e; /* sidebar background (slightly lighter) */
|
--bg-sidebar: #3d566e; /* sidebar background (slightly lighter) */
|
||||||
/* Accent / link colour */
|
/* Accent / link color */
|
||||||
--clr-accent: #3498db; /* blue accent for links */
|
--clr-accent: #3498db; /* blue accent for links */
|
||||||
/* Text colour */
|
/* Text color */
|
||||||
--clr-text: #ecf0f1; /* light whiteish text */
|
--clr-text: #ecf0f1; /* light whiteish text */
|
||||||
/* Borders / accents */
|
/* 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; }
|
* { box-sizing: border-box; }
|
||||||
@ -84,7 +97,7 @@ table, th, td {
|
|||||||
}
|
}
|
||||||
th, td { padding: 10px; }
|
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) {
|
#host_metrics_table tbody tr td:nth-of-type(even) {
|
||||||
background: #3e5c78; /* slight contrast */
|
background: #3e5c78; /* slight contrast */
|
||||||
}
|
}
|
||||||
@ -154,7 +167,7 @@ li { margin-bottom: 10px; color: var(--clr-text); }
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background: #808080; /* default – unknown / no timestamp */
|
background: #808080; /* default - unknown / no timestamp */
|
||||||
transition: background-color 1s linear; /* smooth fade */
|
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 <select> element not suck*/
|
||||||
|
|
||||||
|
/* Base <select> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <option> styling - works in most browsers */
|
||||||
|
.select-dark option {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--clr-select-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional - make the arrow bigger on hover for visibility */
|
||||||
|
.select-dark:hover {
|
||||||
|
background-position:
|
||||||
|
calc(100% - 18px) calc(50% + 2px),
|
||||||
|
calc(100% - 13px) calc(50% + 2px),
|
||||||
|
calc(100% - 3px) 50%;
|
||||||
|
}
|
||||||
@ -1,477 +0,0 @@
|
|||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/* ---------- Global constants ---------- */
|
|
||||||
define('API_KEY', 'deadbeef'); // API key used for the Remove button
|
|
||||||
|
|
||||||
/* ---------- Utility functions ---------- */
|
|
||||||
date_default_timezone_set('America/Los_Angeles');
|
|
||||||
/**
|
|
||||||
* Escape HTML
|
|
||||||
*/
|
|
||||||
function h(string $s): string
|
|
||||||
{
|
|
||||||
return htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Load simple key/value pairs from a YAML file.
|
|
||||||
* Lines starting with '#' are ignored.
|
|
||||||
* The function returns an associative array.
|
|
||||||
*/
|
|
||||||
function loadYaml(string $path): array
|
|
||||||
{
|
|
||||||
if (!file_exists($path)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
||||||
$data = [];
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
$line = trim($line);
|
|
||||||
if ($line === '' || $line[0] === '#') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$pos = strpos($line, ':');
|
|
||||||
if ($pos === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$key = trim(substr($line, 0, $pos));
|
|
||||||
$value = trim(substr($line, $pos + 1));
|
|
||||||
$value = trim($value, "\"'"); // remove surrounding quotes
|
|
||||||
if ($value === '') {
|
|
||||||
$value = null;
|
|
||||||
}
|
|
||||||
$data[$key] = $value;
|
|
||||||
}
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
/* ---------- Load settings ---------- */
|
|
||||||
$settingsPath = '/app/cosmostat_settings.yaml';
|
|
||||||
$settings = loadYaml($settingsPath);
|
|
||||||
|
|
||||||
/* ---------- Page mode handling ---------- */
|
|
||||||
$mode = $_GET['mode'] ?? 'cosmostat'; // default mode
|
|
||||||
$validModes = ['cosmostat', 'drive_health']; // extend as needed
|
|
||||||
if (!in_array($mode, $validModes, true)) {
|
|
||||||
$mode = 'cosmostat';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ---------- Process a POST “remove” request ---------- */
|
|
||||||
/* ---------- Helper: remove client details ---------- */
|
|
||||||
function removeClient($clientList): array
|
|
||||||
{
|
|
||||||
$bindIp = "172.25.1.18";
|
|
||||||
$port = "5001";
|
|
||||||
$path = "/storage_client_delete";
|
|
||||||
// Build the request payload.
|
|
||||||
$payload = [
|
|
||||||
'bind_ip' => $bindIp,
|
|
||||||
'port' => $port,
|
|
||||||
'path' => $path,
|
|
||||||
'API_KEY' => API_KEY,
|
|
||||||
'remove_hosts' => $clientList,
|
|
||||||
];
|
|
||||||
$jsonPayload = json_encode($payload);
|
|
||||||
if ($jsonPayload === false) {
|
|
||||||
// Encoding failed – return a clear error.
|
|
||||||
return [
|
|
||||||
'status' => 'error',
|
|
||||||
'code' => 0,
|
|
||||||
'message' => 'Failed to encode request payload: ' . json_last_error_msg(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the HTTP context for a POST request.
|
|
||||||
$headers = [
|
|
||||||
"Content-Type: application/json",
|
|
||||||
"Content-Length: " . strlen($jsonPayload),
|
|
||||||
"User-Agent: PHP/" . PHP_VERSION,
|
|
||||||
];
|
|
||||||
|
|
||||||
$ctxOptions = [
|
|
||||||
'http' => [
|
|
||||||
'method' => 'POST',
|
|
||||||
'header' => implode("\r\n", $headers),
|
|
||||||
'content' => $jsonPayload,
|
|
||||||
'timeout' => 5,
|
|
||||||
'ignore_errors' => true, // Still receive body for 4xx/5xx
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
$ctx = stream_context_create($ctxOptions);
|
|
||||||
|
|
||||||
|
|
||||||
$url = "http://172.25.1.18:5001/storage_client_delete";
|
|
||||||
$ctx = stream_context_create([
|
|
||||||
'http' => [
|
|
||||||
'timeout' => 5,
|
|
||||||
'header' => "User-Agent: PHP/" . PHP_VERSION . "\r\n"
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$json = @file_get_contents($url, false, $ctx);
|
|
||||||
if ($json === false) {
|
|
||||||
return []; // caller will handle empty case
|
|
||||||
}
|
|
||||||
$data = json_decode($json, true);
|
|
||||||
if (!is_array($data)) {
|
|
||||||
return ['fail'];
|
|
||||||
}
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
|
|
||||||
$removedHostsMsg = '';
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$raw = file_get_contents('php://input');
|
|
||||||
$data = json_decode($raw, true);
|
|
||||||
if (json_last_error() === JSON_ERROR_NONE
|
|
||||||
&& isset($data['API_KEY'], $data['remove_hosts'])
|
|
||||||
&& $data['API_KEY'] === API_KEY
|
|
||||||
) {
|
|
||||||
/* ---- For demo purposes we just echo a message. In a real
|
|
||||||
system you would perform the removal logic here. ---- */
|
|
||||||
$removedHostsMsg = '<div class="card"><p>Removed hosts: '
|
|
||||||
. h(implode(', ', $data['remove_hosts']))
|
|
||||||
. '</p></div>';
|
|
||||||
echo "Time to remove ".$data['remove_hosts']."<br";
|
|
||||||
// removeClient($clientList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- API configuration per mode ---------- */
|
|
||||||
$apiConfig = [
|
|
||||||
'cosmostat' => ['bind' => '10.200.27.20', 'port' => '5000'],
|
|
||||||
'drive_health' => ['bind' => '172.25.1.18', 'port' => '5001'], // new API
|
|
||||||
];
|
|
||||||
|
|
||||||
/* ---------- Helper: fetch client details ---------- */
|
|
||||||
function fetchClientDetails(string $bindIp, string $port, string $path = '/client_details'): array
|
|
||||||
{
|
|
||||||
$url = "http://{$bindIp}:{$port}{$path}";
|
|
||||||
$ctx = stream_context_create([
|
|
||||||
'http' => [
|
|
||||||
'timeout' => 5,
|
|
||||||
'header' => "User-Agent: PHP/" . PHP_VERSION . "\r\n"
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
$json = @file_get_contents($url, false, $ctx);
|
|
||||||
if ($json === false) {
|
|
||||||
return []; // caller will handle empty case
|
|
||||||
}
|
|
||||||
$data = json_decode($json, true);
|
|
||||||
if (!is_array($data)) {
|
|
||||||
return ['fail'];
|
|
||||||
}
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
/* ---------- Fetch client details ---------- */
|
|
||||||
$apiInfo = $apiConfig[$mode];
|
|
||||||
$clients = fetchClientDetails($apiInfo['bind'], $apiInfo['port']);
|
|
||||||
|
|
||||||
/* ---------- Ensure each client has a short_id ---------- */
|
|
||||||
foreach ($clients as &$client) {
|
|
||||||
if (!isset($client['short_id'])) {
|
|
||||||
$client['short_id'] = substr($client['uuid'] ?? '', 0, 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unset($client);
|
|
||||||
/* ---------- Determine selected hosts (Drive Health only) ---------- */
|
|
||||||
$selectedHosts = $_GET['hosts'] ?? [];
|
|
||||||
if ($mode === 'drive_health') {
|
|
||||||
if (isset($_GET['action'])) {
|
|
||||||
switch ($_GET['action']) {
|
|
||||||
case 'all':
|
|
||||||
$selectedHosts = array_column($clients, 'short_id');
|
|
||||||
break;
|
|
||||||
case 'none':
|
|
||||||
$selectedHosts = [];
|
|
||||||
break;
|
|
||||||
// 'apply' – nothing to do; $selectedHosts already contains the posted hosts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* ---- Determine selected host ---- */
|
|
||||||
$selectedId = $_GET['host'] ?? '';
|
|
||||||
$selectedIdx = null;
|
|
||||||
foreach ($clients as $idx => $client) {
|
|
||||||
if (isset($client['short_id']) && $client['short_id'] === $selectedId) {
|
|
||||||
$selectedIdx = $idx;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($selectedIdx === null) {
|
|
||||||
// Default to the first client (if any)
|
|
||||||
$selectedIdx = 0;
|
|
||||||
$selectedId = $clients[$selectedIdx]['short_id'] ?? '';
|
|
||||||
}
|
|
||||||
$client = $clients[$selectedIdx] ?? null;
|
|
||||||
$properties = $client['client_properties'][0] ?? [];
|
|
||||||
$systemProperties = $properties['system_properties'] ?? [];
|
|
||||||
$systemComponents = $properties['system_components'] ?? [];
|
|
||||||
$selectedHost = $clients[$selectedIdx]['hostname'] ?? 'Unknown';
|
|
||||||
|
|
||||||
/* ---- ---- */
|
|
||||||
/* ---- Sidebar Renderer ---- */
|
|
||||||
function renderSidebar(string $mode){
|
|
||||||
global $clients, $client, $properties, $systemProperties, $systemComponents, $selectedHost, $selectedHosts, $selectedId, $selectedIdx, $removedHostsMsg;
|
|
||||||
/* Show a removal‑success message if we just processed a POST */
|
|
||||||
if ($removedHostsMsg) {
|
|
||||||
echo $removedHostsMsg;
|
|
||||||
}
|
|
||||||
$modes = [
|
|
||||||
'cosmostat' => 'Cosmostat',
|
|
||||||
'drive_health' => 'Drive Health',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
<nav class="sidebar">
|
|
||||||
<form method="get" id="modeForm">
|
|
||||||
<label for="modeSelect">Mode:</label>
|
|
||||||
<select name="mode" id="modeSelect" onchange="this.form.submit()">
|
|
||||||
<?php foreach ($modes as $key => $label): ?>
|
|
||||||
<option value="<?= h($key) ?>" <?= $mode === $key ? 'selected' : '' ?>>
|
|
||||||
<?= h($label) ?>
|
|
||||||
</option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php if ($mode === 'drive_health'): ?>
|
|
||||||
<?php
|
|
||||||
if ( !is_array($clients) || empty($clients) ) {
|
|
||||||
echo '<p style="margin:1rem 0; font-style:italic;">'
|
|
||||||
. 'No hosts are available to manage at this time.'
|
|
||||||
. '</p>';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php if (is_array($clients) && !empty($clients)): ?>
|
|
||||||
<h3>Hosts</h3>
|
|
||||||
<form method="get" id="driveHealthForm">
|
|
||||||
<input type="hidden" name="mode" value="drive_health">
|
|
||||||
<ul>
|
|
||||||
<?php foreach ($clients as $c): ?>
|
|
||||||
<?php $id = $c['short_id']; ?>
|
|
||||||
<li>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" name="hosts[]" value="<?= h($id) ?>"
|
|
||||||
<?= in_array($id, $selectedHosts, true) ? 'checked' : '' ?>>
|
|
||||||
<?= h($c['name']) ?>
|
|
||||||
</label>
|
|
||||||
</li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul><p>
|
|
||||||
<button type="submit" name="action" value="apply">Apply</button><p>
|
|
||||||
<button type="submit" name="action" value="all">Select All</button><br>
|
|
||||||
<button type="submit" name="action" value="none">Select None</button><p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Remove button (POST → same page) -->
|
|
||||||
<form id="removeHostsForm" method="post" action="">
|
|
||||||
<!-- API key is added by JS; we keep this form so the button can be inside it -->
|
|
||||||
<button type="button" id="removeButton">Remove Selected Hosts</button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($mode == 'cosmostat'): ?>
|
|
||||||
<input type="hidden" name="host" value="<?= h($selectedId) ?>">
|
|
||||||
<h3>Endpoints</h3>
|
|
||||||
<ol id="endpointList"></ol>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($mode == 'gali'): ?>
|
|
||||||
<h3>Shuttle Gali</h3>
|
|
||||||
<?php endif; ?>
|
|
||||||
</nav>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Main Content Renderer ---- */
|
|
||||||
function renderMainContent(string $mode){
|
|
||||||
global $clients, $client, $properties, $systemProperties, $systemComponents, $selectedHost, $selectedHosts, $selectedId, $selectedIdx;
|
|
||||||
/* ---------- Handle empty / error ---------- */
|
|
||||||
if ($clients === ['fail']) {
|
|
||||||
die('<div class="card"><p style="color:red;">Could not retrieve data from the API for mode "' . h($mode) . '".</p></div>');
|
|
||||||
}
|
|
||||||
if ($mode === 'drive_health') {
|
|
||||||
// If nothing is selected, show a friendly message
|
|
||||||
if (empty($selectedHosts)) {
|
|
||||||
echo '<div class="card"><p>No hosts selected.</p></div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
echo '<div class="storage_client">';
|
|
||||||
foreach ($selectedHosts as $sid) {
|
|
||||||
// Find the client that matches this short_id
|
|
||||||
$c = null;
|
|
||||||
foreach ($clients as $cl) {
|
|
||||||
if ($cl['short_id'] === $sid) {
|
|
||||||
$c = $cl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($c === null) continue; // safety
|
|
||||||
$hostname = $c['name'] ?? 'Unknown';
|
|
||||||
echo '<div class="card">';
|
|
||||||
echo '<h2>Drive Health - ' . h($hostname) . '</h2>';
|
|
||||||
echo '<h3>IP: ' . h($c['ip']) . '<br>';
|
|
||||||
echo 'Timestamp: ' . date('F j, Y g:i a', (int) $c['timestamp']) . '</h3>';
|
|
||||||
if (isset($c['drives']) && is_array($c['drives']) && count($c['drives']) > 0) {
|
|
||||||
echo '<table id="host_metrics_table">';
|
|
||||||
echo '<thead><tr><th>Drive Letter</th><th>Model</th><th>Capacity</th><th>Power On Hours</th><th>Host Writes</th><th>Wear Level</th></tr></thead>';
|
|
||||||
echo '<tbody>';
|
|
||||||
foreach ($c['drives'] as $drive) {
|
|
||||||
echo '<tr>';
|
|
||||||
echo '<td>' . h($drive['drive_letter'] ?? '') . '</td>';
|
|
||||||
echo '<td>' . h($drive['model'] ?? '') . '</td>';
|
|
||||||
echo '<td>' . h($drive['capacity'] ?? '') . '</td>';
|
|
||||||
echo '<td>' . h($drive['power_on_hours'] ?? '') . '</td>';
|
|
||||||
echo '<td>' . h($drive['host_writes'] ?? '') . '</td>';
|
|
||||||
echo '<td>' . h($drive['wear_level'] ?? '') . '</td>';
|
|
||||||
echo '</tr>';
|
|
||||||
}
|
|
||||||
echo '</tbody></table>';
|
|
||||||
} else {
|
|
||||||
echo '<p>No drive data available for this host.</p>';
|
|
||||||
}
|
|
||||||
echo '</div>';
|
|
||||||
}
|
|
||||||
echo '</div>';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<div class="main">
|
|
||||||
<?php if ($mode == 'cosmostat'): ?><!-- Header Card -->
|
|
||||||
<div class="card">
|
|
||||||
<h2>Matt-Cloud Cosmostat Dashboard</h2>
|
|
||||||
<p>This dashboard shows the local Matt-Cloud system stats.</p>
|
|
||||||
<div class="help-link" id="helpToggle">API</div>
|
|
||||||
</div> <!-- / Header Card -->
|
|
||||||
<!-- Hidden API Card -->
|
|
||||||
<div id="helpText" class="card">
|
|
||||||
<strong>Component Desriptor</strong>
|
|
||||||
<p>To view the component descriptor, you may <br>
|
|
||||||
<code>curl -s https://<?= h($_SERVER['SERVER_NAME']) ?>/descriptor</code></p>
|
|
||||||
This will return the entire JSON descriptor variable.<br>
|
|
||||||
The agent then reports back to the Cosmostat Server with all the data found in the descriptor.<br>
|
|
||||||
Full Source Code can be found at its <a target="_blank" rel="noopener noreferrer" href="https://gitea.matt-cloud.com/matt/cosmoserver">Gitea</a> page.
|
|
||||||
</div> <!-- / Header Card -->
|
|
||||||
<!-- summary card -->
|
|
||||||
<div class="card">
|
|
||||||
<?php if (!empty($systemProperties)): ?>
|
|
||||||
<h2>System Properties</h2>
|
|
||||||
<table><tr>
|
|
||||||
<td>
|
|
||||||
<ul class="system-list">
|
|
||||||
<?php foreach ($systemProperties as $prop): ?>
|
|
||||||
<li><?= h($prop['Property']) ?></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<h2>Live System Metrics</h2>
|
|
||||||
<div id="host_metrics" class="column">Connecting...</div>
|
|
||||||
</td>
|
|
||||||
</tr></table>
|
|
||||||
<?php endif; ?><br>
|
|
||||||
<div class="componentDetail-link" id="componentDetailToggle">Toggle Component Details</div>
|
|
||||||
</div> <!--/summary card -->
|
|
||||||
|
|
||||||
<!-- hidden detail card -->
|
|
||||||
<div id="componentDetailText" class="card">
|
|
||||||
<?php if (!empty($systemComponents)): ?>
|
|
||||||
<h2>Components</h2>
|
|
||||||
<div class="components">
|
|
||||||
<?php foreach ($systemComponents as $comp): ?>
|
|
||||||
<div class="component">
|
|
||||||
<h3><?= h($comp['component_name']) ?></h3>
|
|
||||||
<ul class="info-list">
|
|
||||||
<?php foreach ($comp['info_strings'] as $info): ?>
|
|
||||||
<li><?= h($info) ?></li>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div> <!--/hidden detail card -->
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($mode == 'gali'): ?>
|
|
||||||
<h3>Shuttle Gali</h3>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</div> <!-- /main -->
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- Render server dashboard ---- */
|
|
||||||
?>
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Cosmostat - <?= h($selectedHost) ?></title>
|
|
||||||
<link rel="stylesheet" href="src/styles.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="wrapper">
|
|
||||||
<!-- Sidebar -->
|
|
||||||
<?php renderSidebar($mode); ?>
|
|
||||||
<!-- Main content -->
|
|
||||||
<?php renderMainContent($mode); ?>
|
|
||||||
|
|
||||||
</div> <!-- /wrapper -->
|
|
||||||
<script src="socket.io/socket.io.js"></script>
|
|
||||||
<script src="src/system_metrics.js"></script>
|
|
||||||
<script>
|
|
||||||
/* Help / detail toggles */
|
|
||||||
document.getElementById('helpToggle')?.addEventListener('click', function () {
|
|
||||||
const help = document.getElementById('helpText');
|
|
||||||
help.style.display = help.style.display === 'none' || help.style.display === '' ? 'block' : 'none';
|
|
||||||
});
|
|
||||||
document.getElementById('componentDetailToggle')?.addEventListener('click', function () {
|
|
||||||
const help = document.getElementById('componentDetailText');
|
|
||||||
help.style.display = help.style.display === 'none' || help.style.display === '' ? 'block' : 'none';
|
|
||||||
});
|
|
||||||
|
|
||||||
/* --- Remove button logic --- */
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const removeBtn = document.getElementById('removeButton');
|
|
||||||
if (!removeBtn) return; // not in drive_health mode
|
|
||||||
|
|
||||||
removeBtn.addEventListener('click', function () {
|
|
||||||
// Grab all checked hosts from the drive‑health form
|
|
||||||
const checked = document.querySelectorAll('input[name="hosts[]"]:checked');
|
|
||||||
const hosts = Array.from(checked).map(cb => cb.value);
|
|
||||||
|
|
||||||
if (hosts.length === 0) {
|
|
||||||
alert('No hosts selected.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
API_KEY: '<?= API_KEY ?>',
|
|
||||||
remove_hosts: hosts
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch('', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
})
|
|
||||||
.then(resp => {
|
|
||||||
if (!resp.ok) throw new Error('Network error');
|
|
||||||
return resp.text(); // we just reload on any response
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// reload the same index page
|
|
||||||
window.location.reload();
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
alert('Error removing hosts.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# this was some weird testing stuff
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Grab the list of IPs
|
# Grab the list of IPs
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user