diff --git a/files/api/Components.py b/files/api/Components.py
index 81120cb..511a9a8 100644
--- a/files/api/Components.py
+++ b/files/api/Components.py
@@ -1,10 +1,16 @@
-# this class file is for the cosmostat service
+#################################################################
+#################################################################
+### Cosmostat Component and System Class
+#################################################################
+#################################################################
+
import subprocess
import json
import time
import weakref
import base64, hashlib
from typing import Dict, Any, List
+# Import Cosmos Settings
from Cosmos_Settings import *
# Global Class Vars
@@ -33,7 +39,13 @@ for entry in component_class_tree:
#################################################################
#################################################################
-# Component Class
+### Component Class
+### Each Component type is defined by the descriptor and built
+### as part of the System Class Instantiation
+### Each Component Object contains static and dynamic data
+### The static data is declared once at instantiation
+### THe dynamic data is periodically updated by the application
+### where the System Class Object is instantiated
#################################################################
#################################################################
@@ -42,8 +54,7 @@ class Component:
############################################################
# instantiate new component
# this_device is set when the component has multiple instances
- ############################################################
-
+ ############################################################
def __init__(self, name: str, comp_type: str, parent_system, this_device=None):
# begin init
@@ -246,7 +257,7 @@ class Component:
return result
########################################################
- # random data functions
+ # various data functions
########################################################
# complex data type return
@@ -301,7 +312,15 @@ class Component:
############################################################
############################################################
-# System Class
+### System Class
+### The System Class uses the Descriptor to build a List
+### of Components and interact with the data in a
+### useful manner. The System Object is similar to a
+### Component Object in that it has Static and Dynamic
+### properties, which are populated in a similar manner
+### to the Components. In fact, this is designed for the
+### System object to update Component Dymanic Metrics
+### as part of the same subroutine that updates its own
############################################################
############################################################
diff --git a/files/api/Cosmostat.py b/files/api/Cosmostat.py
index 1744dcf..5e5e5bd 100644
--- a/files/api/Cosmostat.py
+++ b/files/api/Cosmostat.py
@@ -1,9 +1,16 @@
-# This will be a class definitation for the cosmostat server
-# On the server, there will be a Cosmostat Class Object
-# This will have an array of System Class Objects
-# These will be created based on API input from remote systems
-# The remote systems will submit a json of their state to a private API
-# this will define the System Class
+
+#################################################################
+#################################################################
+### Cosmostat Classes
+### The Cosmostat Server is a Class for the API running on the
+### dashboard. This keeps track of all active systems that are
+### actively reporting back to the Cosmostat Server Dashboard
+### The static and active data is maintained in a single
+### Cosmostat Server Object, and this Object contains a List
+### of Cosmostat Client Objects. This is where the data actually
+### lives
+#################################################################
+#################################################################
import subprocess
import json
@@ -11,14 +18,15 @@ import time
import weakref
import base64, hashlib
from typing import Dict, Any, List
+# Import Cosmos Settings
from Cosmos_Settings import *
-
-
-#################################################################
-#################################################################
-# Cosmostat Class
#################################################################
+### Cosmostat Server Class
+### This Class is for maintaining a list of Client Objects.
+### Each Object is a remote System reporting back
+### The Class Functions are for the main application to interact
+### with client Object data.
#################################################################
class CosmostatServer:
@@ -106,22 +114,30 @@ class CosmostatServer:
return result
def get_client_hostnames(self, send_age = False):
+ self.purge_stale_hostnames()
+ result = []
+ for system in self.systems:
+ if send_age:
+ result.append({"hostname": system.hostname, "data_age": age})
+ else:
+ result.append(system.hostname)
+ return result
+
+ def purge_stale_hostnames(self):
now = time.time()
fresh_systems = []
- result = []
-
for system in self.systems:
age = now - system.data_timestamp
if age <= 60: # keep only fresh servers
fresh_systems.append(system)
- if send_age:
- result.append({"hostname": system.hostname, "data_age": age})
- else:
- result.append(system.hostname)
-
- self.systems = fresh_systems # replace the old list
- return result
+ self.systems = fresh_systems # replace the old list
+#################################################################
+### Cosmostat Client Class
+### Each Class Object contains static and active data as well as
+### the hostname and uuid/short_id.
+### The timestamp is for removing stale Clients
+#################################################################
class CosmostatClient:
diff --git a/files/api/app.py b/files/api/app.py
index cad0222..cb72b04 100644
--- a/files/api/app.py
+++ b/files/api/app.py
@@ -1,16 +1,21 @@
+#######################################################################
+### app.py
+### cosmostat service handler
+#######################################################################
+
from flask import Flask, jsonify, request, Response
from flask_apscheduler import APScheduler
from typing import Dict, Union
-
import json, time, redis, yaml
-import base64, hashlib
import secrets, string
-
import requests
from requests import RequestException, Response
-from Components import *
+# Import Cosmos Settings
from Cosmos_Settings import *
+# System and Component Classes
+from Components import *
+# Cosmostat server Classes
from Cosmostat import *
# declare flask apps
@@ -373,11 +378,11 @@ def client_update():
log_data(log_output = payload, log_level = "noisy_test")
# execute API call
result = client_submission_handler(api_url, payload)
- client_initialize()
+ client_api_initialize()
return result
# Cosmostat Client Initializer
-def client_initialize():
+def client_api_initialize():
api_url = f"{cosmostat_server_api()}create_client"
# generate payload
payload = get_client_payload(get_php_summary(), "client_properties")
@@ -446,34 +451,39 @@ if __name__ == '__main__':
# Background Loop Function
def background_loop():
- # Update all data on the System object unless this is the server
- if cosmostat_client.check_system_timer() and not run_cosmostat_server():
+ # Update all data on the local System object
+ if cosmostat_client.check_system_timer() or run_cosmostat_server():
cosmostat_client.update_system_state()
+ # publish to redis if the web dashboard is active locally
if app_settings["push_redis"] and not app_settings["disable_local_api"]:
update_redis_server()
+ # report data to the server if configured
if run_cosmostat_reporter():
if int(time.time()) % 5 == 0 and not cosmostat_client.check_system_timer():
cosmostat_client.update_system_state()
client_update()
+ # if this is the server, do this stuff
if run_cosmostat_server():
- # update the client state since that was skipped
- cosmostat_client.update_system_state()
- this_client = get_client_payload(get_client_redis_data(human_readable = False), "redis_data")
- if app_settings["noisy_test"]:
- print(this_client)
- run_update_client(this_client)
+ # purge stale client systems
+ cosmostat_server.purge_stale_hostnames()
+ # report the server's own client object to itself
+ run_update_client(get_client_payload(get_client_redis_data(human_readable = False), "redis_data"))
+ log_data(log_output = f"{this_client}", log_level = "noisy_test")
time.sleep(0.5)
######################################
- # instantiate client
+ # instantiate client
######################################
+
+ # local client System Class Object
cosmostat_client = new_cosmos_client()
+ # remote client reporter
if app_settings["cosmostat_server_reporter"] and not app_settings["cosmostat_server"]:
- client_initialize()
+ client_api_initialize()
######################################
# instantiate server
@@ -517,6 +527,7 @@ if __name__ == '__main__':
if not app_settings["disable_local_api"]:
app.run(debug=False, host=service_gateway_ip(), port=service_api_port())
else:
+ # if local API disabled, phone home if configured
print("Internal API Disabled.")
while True:
if int(time.time()) % 5 == 0 and not cosmostat_client.check_system_timer():
diff --git a/files/api/new_descriptors.json b/files/api/new_descriptors.json
index 4bbcd7d..42d5543 100644
--- a/files/api/new_descriptors.json
+++ b/files/api/new_descriptors.json
@@ -1,4 +1,7 @@
[
+ {
+ "notes:": "this is both a scratch file and a reference for new component descriptors"
+ },
{
"name": "",
"description": "",
@@ -37,88 +40,6 @@
"which have variance"
]
},
- {
- "static_key_variables": [
- {"name": "Hostname", "command": "hostname"},
- {"name": "Virtual Machine", "command": "echo $( [ \"$(systemd-detect-virt)\" = none ] && echo False || echo True )", "req_check": "False"},
- {"name": "CPU Architecture", "command": "lscpu --json | jq -r '.lscpu[] | select(.field==\"Architecture:\") | .data'"},
- {"name": "OS Kernel", "command": "uname -r"},
- {"name": "OS Name", "command": "cat /etc/os-release | grep PRETTY | cut -d\\\" -f2"},
- {"name": "Manufacturer", "command": "sudo dmidecode --type 1 | grep Manufacturer: | cut -d: -f2 | sed -e 's/^[ \\t]*//'"},
- {"name": "Product Name", "command": "sudo dmidecode --type 2 | grep 'Product Name:' | cut -d: -f2 | sed -e 's/^[ \\t]*//'"},
- {"name": "Serial Number", "command": "sudo dmidecode --type 2 | grep 'Serial Number: '| cut -d: -f2 | sed -e 's/^[ \\t]*//'"}
- ],
- "dynamic_key_variables": [
- {"name": "System Uptime", "command": "uptime -p"},
- {"name": "Current Date", "command": "date '+%D %r'"}
- ],
- "virt_ignore": [
- "Product Name",
- "Serial Number"
- ]
- },
-{
- "name:": "System",
- "static_key_variables": [
- {
- "name": "Hostname",
- "command": "hostname"
- },
- {
- "name": "Virtual Machine",
- "command": "echo $( [ \"$(systemd-detect-virt)\" = none ] && echo False || echo True )",
- "req_check": "False"
- },
- {
- "name": "CPU Architecture",
- "command": "lscpu --json | jq -r '.lscpu[] | select(.field==\"Architecture:\") | .data'"
- },
- {
- "name": "OS Kernel",
- "command": "uname -r"
- },
- {
- "name": "OS Name",
- "command": "cat /etc/os-release | grep PRETTY | cut -d\\\" -f2"
- },
- {
- "name": "Manufacturer",
- "command":{
- "x86_64": "sudo dmidecode --type 1 | grep Manufacturer: | cut -d: -f2 | sed -e 's/^[ \\t]*//'"
- },
- "arch_check": "true"
- },
- {
- "name": "Product Name",
- "command": {
- "x86_64": "sudo dmidecode --type 2 | grep 'Product Name:' | cut -d: -f2 | sed -e 's/^[ \\t]*//'"
- },
- "arch_check": "true"
- },
- {
- "name": "Serial Number",
- "command": {
- "x86_64": "sudo dmidecode --type 2 | grep 'Serial Number: '| cut -d: -f2 | sed -e 's/^[ \\t]*//'"
- },
- "arch_check": "true"
- }
- ],
- "dynamic_key_variables": [
- {
- "name": "System Uptime",
- "command": "uptime -p"
- },
- {
- "name": "Current Date",
- "command": "date '+%D %r'"
- }
- ],
- "virt_ignore": [
- "Product Name",
- "Serial Number"
- ]
- },
-
{
"SATA GBW": "sudo /usr/sbin/smartctl -x --json /dev/{this_device} | jq -r '.physical_block_size as $block |.ata_device_statistics.pages[] | select(.name == \"General Statistics\") | .table[] | select(.name == \"Logical Sectors Written\") | .value as $sectors | ($sectors * $block) / 1073741824 ' | awk '{{printf \"%.2f GiB Written\\n\", $0}}' || true",
diff --git a/files/api/shrink.py b/files/api/shrink.py
deleted file mode 100644
index 4f7313e..0000000
--- a/files/api/shrink.py
+++ /dev/null
@@ -1,46 +0,0 @@
-
- def get_properties_keys(self, component = None):
- component_properties = []
- if component == None:
- component_properties = self._properties.items()
- else:
- component_properties = self.get_property(component)
- result = self.process_key_list(key_items = component_properties, key_name = "Property", return_type = "key" key_value = "Value")
- return result
-
- def get_metrics_keys(self):
- result = self.process_key_list(key_items = self._metrics.items(), key_name = "Metric", key_value = "Data", return_type = "key")
- return result
-
- def get_properties_strings(self, return_simple = True):
- result = self.process_key_list(key_items = self._properties.items(), key_name = "Property", return_type = "string", return_simple = return_simple)
- return result
-
- def get_metrics_strings(self, return_simple = True):
- result = self.process_key_list(key_items = self._metrics.items(), key_name = "Metric", return_type = "string", return_simple = return_simple)
- return result
-
- def process_key_list(self, key_items: str, key_name: str, return_type: str, key_value = "none"):
- result = []
- empty_value = ["", "null", None, []]
- for name, values in key_items:
- for value in (values if isinstance(values, list) else [values]):
- if value not in empty_value and name not in self.virt_ignore:
- this_key_string = f"{name}: {value}"
- if return_simple:
- result.append(this_key_string)
- elif return_keys:
- this_key_value = {
- "Source": self.name,
- key_name: name,
- key_value: value
- }
- result.append(this_key_value)
- else:
- complex_key_string = {
- "Source": self.name,
- key_name: this_key_string
- }
- result.append(complex_key_string)
-
- return result
\ No newline at end of file
diff --git a/files/server/server.php b/files/server/server.php
index adb2f4e..5a27b81 100644
--- a/files/server/server.php
+++ b/files/server/server.php
@@ -95,7 +95,10 @@ $selectedHost = $clients[$selectedIdx]['hostname'];
Component Desriptor
To view the component descriptor, you may curl -s https://= h($_SERVER['SERVER_NAME']) ?>/descriptor
-
This will return the entire JSON descriptor variable
+ This will return the entire JSON descriptor variable.
+ The endpoint agent uses this descriptor to build out its local System Object.
+ The agent then reports back to the Cosmostat Server with all the data found in the descriptor.
+ Full Source Code can be found at its Gitea page.
diff --git a/files/web/archive/redis-server.js b/files/web/archive/redis-server.js
deleted file mode 100644
index 37d8924..0000000
--- a/files/web/archive/redis-server.js
+++ /dev/null
@@ -1,218 +0,0 @@
-/* ------------------------------------------------------------------ */
-/* 1. Socket‑IO connection & helpers – unchanged */
-/* ------------------------------------------------------------------ */
-const socket = io();
-socket.on('connect_error', err => {
- safeSetText('status', `Could not connect to server - ${err.message}`);
-});
-socket.on('reconnect', attempt => {
- safeSetText('status', `Re-connected (attempt ${attempt})`);
-});
-function safeSetText(id, txt) {
- const el = document.getElementById(id);
- if (el) el.textContent = txt;
-}
-
-/* ------------------------------------------------------------------ */
-/* 2. Global state */
-/* ------------------------------------------------------------------ */
-let selectedHost = null; // hostname that is currently displayed
-const hostDataMap = {}; // hostname → client object (from CLIENT_LIST)
-
-/* ------------------------------------------------------------------ */
-/* 3. Build the host list once the page is ready */
-/* ------------------------------------------------------------------ */
-function initHostList() {
- const listEl = document.getElementById('host-list');
- listEl.innerHTML = ''; // clear any stray markup
-
- CLIENT_LIST.forEach(host => {
- hostDataMap[host.hostname] = host; // cache for quick lookup
- const item = document.createElement('div');
- item.textContent = host.hostname;
- item.className = 'host-item';
- item.dataset.hostname = host.hostname;
- item.addEventListener('click', () => selectHost(host.hostname));
- listEl.appendChild(item);
- });
-
- // auto‑select the first host (you could also stay on "Loading…" until the user clicks)
- if (CLIENT_LIST.length) selectHost(CLIENT_LIST[0].hostname);
-}
-
-/* ------------------------------------------------------------------ */
-/* 4. Handle host click – update UI and request live metrics */
-/* ------------------------------------------------------------------ */
-function selectHost(hostname) {
- if (selectedHost === hostname) return; // already selected
- selectedHost = hostname;
-
- // Update active styling in the list
- document.querySelectorAll('.host-item').forEach(el => {
- el.classList.toggle('active', el.dataset.hostname === hostname);
- });
-
- // Render the static part of the page for this host
- renderHostContent(hostDataMap[hostname]);
-
- // Now request the live metrics for this host
- // The server sends an array of all hosts – we’ll filter below
- // (If you have a dedicated endpoint you could request only the chosen host here)
-}
-
-/* ------------------------------------------------------------------ */
-/* 5. Render the static content (system properties + components) */
-/* ------------------------------------------------------------------ */
-function renderHostContent(host) {
- const main = document.getElementById('main-content');
- main.innerHTML = ''; // clear
-
- // 5a. System Properties
- if (host.client_properties?.[0]?.system_properties?.length) {
- const propSection = document.createElement('div');
- propSection.innerHTML = '