sync for dev 033026
This commit is contained in:
@ -56,6 +56,7 @@ REAL_API_KEY: "{{ lookup('password', '/dev/null length=64 chars=ascii_letters,di
|
|||||||
|
|
||||||
# dashboard vars
|
# dashboard vars
|
||||||
service_control_web_folder: "{{ service_folder }}/web"
|
service_control_web_folder: "{{ service_folder }}/web"
|
||||||
|
service_control_docker_folder: "{{ service_folder }}/docker"
|
||||||
public_dashboard: true
|
public_dashboard: true
|
||||||
custom_port: "80"
|
custom_port: "80"
|
||||||
web_src: "/web"
|
web_src: "/web"
|
||||||
@ -63,19 +64,21 @@ web_src: "/web"
|
|||||||
# other vars
|
# other vars
|
||||||
quick_refresh: false
|
quick_refresh: false
|
||||||
x64_arch: true
|
x64_arch: true
|
||||||
|
refresh_special: false
|
||||||
|
special_server: "none"
|
||||||
|
|
||||||
# cosmostat_settings, will be for special_server defaults
|
# cosmostat_settings, will be for special_server defaults
|
||||||
noisy_test: false
|
noisy_test: false
|
||||||
debug_output: true
|
debug_output: false
|
||||||
secure_api: true
|
secure_api: true
|
||||||
push_redis: false
|
push_redis: false
|
||||||
run_background : true
|
run_background : true
|
||||||
log_output: true
|
log_output: true
|
||||||
update_frequency: "1"
|
update_frequency: "1"
|
||||||
cosmostat_server: true
|
cosmostat_server: false
|
||||||
cosmostat_server_api: "https://cosmostat.testy-cal.com/"
|
cosmostat_server_api: "https://cosmostat.matt-cloud.com/"
|
||||||
local_api_address: "http://{{ cosmostat_server_ip }}:{{ custom_api_port }}/"
|
local_api_address: "http://{{ cosmostat_server_ip }}:{{ custom_api_port }}/"
|
||||||
cosmostat_server_reporter: false
|
cosmostat_server_reporter: true
|
||||||
# setting this to true for default install
|
# setting this to true for default install
|
||||||
disable_local_api: true
|
disable_local_api: true
|
||||||
...
|
...
|
||||||
@ -21,7 +21,8 @@ app_settings = {
|
|||||||
"REAL_API_KEY": ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(256)),
|
"REAL_API_KEY": ''.join(secrets.choice(string.ascii_letters + string.digits) for _ in range(256)),
|
||||||
"disable_local_api": False,
|
"disable_local_api": False,
|
||||||
"local_api_address": "http://10.200.27.20:5000/",
|
"local_api_address": "http://10.200.27.20:5000/",
|
||||||
"cosmostat_server_ip": "10.200.27.20"
|
"cosmostat_server_ip": "10.200.27.20",
|
||||||
|
"api_bind_ip": "192.168.37.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
with open('cosmostat_settings.yaml', 'r') as f:
|
with open('cosmostat_settings.yaml', 'r') as f:
|
||||||
@ -71,10 +72,7 @@ def service_gateway_ip():
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def redis_gateway_ip():
|
def redis_gateway_ip():
|
||||||
if cosmostat_settings["secure_api"]:
|
|
||||||
return cosmostat_bind_ip()
|
return cosmostat_bind_ip()
|
||||||
else:
|
|
||||||
return "0.0.0.0"
|
|
||||||
|
|
||||||
def cosmostat_server_api():
|
def cosmostat_server_api():
|
||||||
return cosmostat_settings["cosmostat_server_api"]
|
return cosmostat_settings["cosmostat_server_api"]
|
||||||
|
|||||||
@ -170,11 +170,10 @@ def get_php_summary():
|
|||||||
system_components.append(this_component)
|
system_components.append(this_component)
|
||||||
|
|
||||||
if run_cosmostat_server():
|
if run_cosmostat_server():
|
||||||
print(cosmostat_client.name)
|
|
||||||
client_uuid = cosmostat_server.get_uuid_from_hostname(cosmostat_client.name)
|
client_uuid = cosmostat_server.get_uuid_from_hostname(cosmostat_client.name)
|
||||||
print(client_uuid)
|
|
||||||
data_timestamp = cosmostat_server.get_system(client_uuid)
|
data_timestamp = cosmostat_server.get_system(client_uuid)
|
||||||
print(data_timestamp)
|
log_string = f"Cosmostat Client Name: {cosmostat_client.name} - UUID: {client_uuid}- Timestamp: {data_timestamp}"
|
||||||
|
log_data(log_output = log_string, log_level = "log_output")
|
||||||
component_age = {
|
component_age = {
|
||||||
"component_name": "Data Timestamp",
|
"component_name": "Data Timestamp",
|
||||||
"info_strings": f"Data is {data_timestamp} seconds old"
|
"info_strings": f"Data is {data_timestamp} seconds old"
|
||||||
@ -221,7 +220,10 @@ def create_client():
|
|||||||
if not cosmostat_server.check_uuid(payload["uuid"]):
|
if not cosmostat_server.check_uuid(payload["uuid"]):
|
||||||
result = run_create_client(payload)
|
result = run_create_client(payload)
|
||||||
else:
|
else:
|
||||||
result = {"message": "object already exists, skipping creation"}
|
result = {
|
||||||
|
"message": "object already exists, skipping creation",
|
||||||
|
"system_exists": "True"
|
||||||
|
}
|
||||||
return jsonify(result), 200
|
return jsonify(result), 200
|
||||||
|
|
||||||
# api to validate Cosmostat Class
|
# api to validate Cosmostat Class
|
||||||
@ -280,6 +282,7 @@ def run_update_client(this_client):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"status": update_status,
|
"status": update_status,
|
||||||
|
"client_updated": "True",
|
||||||
"uuid": this_client["uuid"],
|
"uuid": this_client["uuid"],
|
||||||
"redis_data": this_client,
|
"redis_data": this_client,
|
||||||
"timestamp_update": timestamp_update
|
"timestamp_update": timestamp_update
|
||||||
@ -296,6 +299,7 @@ def run_create_client(this_client):
|
|||||||
update_status = f'created client {this_client["short_id"]}'
|
update_status = f'created client {this_client["short_id"]}'
|
||||||
return {
|
return {
|
||||||
"status": update_status,
|
"status": update_status,
|
||||||
|
"client_created": "True",
|
||||||
"uuid": this_client["uuid"],
|
"uuid": this_client["uuid"],
|
||||||
"client_properties": this_client,
|
"client_properties": this_client,
|
||||||
"timestamp_update": timestamp_update
|
"timestamp_update": timestamp_update
|
||||||
@ -372,13 +376,15 @@ def get_client_details():
|
|||||||
# Cosmostat Client Reporter
|
# Cosmostat Client Reporter
|
||||||
def client_update():
|
def client_update():
|
||||||
api_url = f"{cosmostat_server_api()}update_client"
|
api_url = f"{cosmostat_server_api()}update_client"
|
||||||
print(api_url)
|
|
||||||
payload = get_client_payload(get_client_redis_data(human_readable = False), "redis_data")
|
payload = get_client_payload(get_client_redis_data(human_readable = False), "redis_data")
|
||||||
|
log_data(log_output = f"API Update Called - {api_url}", log_level = "debug_output")
|
||||||
log_data(log_output = "client_update - redis data from local client:", log_level = "noisy_test")
|
log_data(log_output = "client_update - redis data from local client:", log_level = "noisy_test")
|
||||||
log_data(log_output = payload, log_level = "noisy_test")
|
log_data(log_output = payload, log_level = "noisy_test")
|
||||||
# execute API call
|
# execute API call
|
||||||
result = client_submission_handler(api_url, payload)
|
result = client_submission_handler(api_url, payload)
|
||||||
client_api_initialize()
|
if not result or not result.get('client_updated'):
|
||||||
|
log_data(log_output = f"Client not updated, initializing", log_level = "log_output")
|
||||||
|
result = client_api_initialize()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Cosmostat Client Initializer
|
# Cosmostat Client Initializer
|
||||||
@ -434,6 +440,7 @@ if __name__ == '__main__':
|
|||||||
######################################
|
######################################
|
||||||
### Main Functions
|
### Main Functions
|
||||||
######################################
|
######################################
|
||||||
|
log_data(log_output = f"Main Function Start", log_level = "log_output")
|
||||||
|
|
||||||
# instantiate and return the Client System object
|
# instantiate and return the Client System object
|
||||||
def new_cosmos_client():
|
def new_cosmos_client():
|
||||||
@ -480,9 +487,13 @@ if __name__ == '__main__':
|
|||||||
######################################
|
######################################
|
||||||
|
|
||||||
# local client System Class Object
|
# local client System Class Object
|
||||||
|
log_data(log_output = f"Cosmostat Client Start", log_level = "log_output")
|
||||||
cosmostat_client = new_cosmos_client()
|
cosmostat_client = new_cosmos_client()
|
||||||
|
cosmostat_client.update_system_state()
|
||||||
|
|
||||||
# remote client reporter
|
# remote client reporter
|
||||||
if app_settings["cosmostat_server_reporter"] and not app_settings["cosmostat_server"]:
|
if app_settings["cosmostat_server_reporter"] and not app_settings["cosmostat_server"]:
|
||||||
|
log_data(log_output = f"Initialize Client Reporter", log_level = "log_output")
|
||||||
client_api_initialize()
|
client_api_initialize()
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
@ -491,15 +502,19 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
cosmostat_server = None
|
cosmostat_server = None
|
||||||
if run_cosmostat_server():
|
if run_cosmostat_server():
|
||||||
|
log_data(log_output = f"Cosmostat Server Start", log_level = "log_output")
|
||||||
cosmostat_server = new_cosmostat_server()
|
cosmostat_server = new_cosmostat_server()
|
||||||
this_client = get_client_payload(get_php_summary(), "client_properties")
|
this_client = get_client_payload(get_php_summary(), "client_properties")
|
||||||
timestamp_update = cosmostat_server.add_system(system_dictionary = this_client)
|
timestamp_update = cosmostat_server.add_system(system_dictionary = this_client)
|
||||||
|
# moving this here so all the bits exist
|
||||||
|
client_update()
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
# send initial stats update to redis
|
# send initial stats update to redis
|
||||||
######################################
|
######################################
|
||||||
|
|
||||||
if app_settings["push_redis"] and not app_settings["disable_local_api"]:
|
if app_settings["push_redis"] and not app_settings["disable_local_api"]:
|
||||||
|
log_data(log_output = f"Initial Redis Push", log_level = "log_output")
|
||||||
update_redis_server()
|
update_redis_server()
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
@ -507,6 +522,7 @@ if __name__ == '__main__':
|
|||||||
######################################
|
######################################
|
||||||
|
|
||||||
if app_settings["run_background"] and not app_settings["disable_local_api"]:
|
if app_settings["run_background"] and not app_settings["disable_local_api"]:
|
||||||
|
log_data(log_output = f"Background Function Initializing", log_level = "log_output")
|
||||||
log_data(log_output = "Loading flask background subroutine...", log_level = "log_output")
|
log_data(log_output = "Loading flask background subroutine...", log_level = "log_output")
|
||||||
|
|
||||||
scheduler.add_job(id='background_loop',
|
scheduler.add_job(id='background_loop',
|
||||||
@ -523,16 +539,18 @@ if __name__ == '__main__':
|
|||||||
######################################
|
######################################
|
||||||
# Flask API
|
# Flask API
|
||||||
######################################
|
######################################
|
||||||
print(f"gateway: {service_gateway_ip()} - port: {service_api_port()}")
|
log_data(log_output = f"gateway: {service_gateway_ip()} - port: {service_api_port()}", log_level = "log_output")
|
||||||
if not app_settings["disable_local_api"]:
|
if not app_settings["disable_local_api"]:
|
||||||
|
log_data(log_output = f"Main API Start", log_level = "log_output")
|
||||||
app.run(debug=False, host=service_gateway_ip(), port=service_api_port())
|
app.run(debug=False, host=service_gateway_ip(), port=service_api_port())
|
||||||
else:
|
else:
|
||||||
# if local API disabled, phone home if configured
|
# if local API disabled, phone home if configured
|
||||||
print("Internal API Disabled.")
|
log_data(log_output = f"Internal API Disabled", log_level = "log_output")
|
||||||
while True:
|
|
||||||
if int(time.time()) % 5 == 0 and not cosmostat_client.check_system_timer():
|
|
||||||
cosmostat_client.update_system_state()
|
cosmostat_client.update_system_state()
|
||||||
client_update()
|
client_update()
|
||||||
time.sleep(0.5)
|
while True:
|
||||||
|
cosmostat_client.update_system_state()
|
||||||
|
client_update()
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
@ -91,7 +91,8 @@
|
|||||||
"Clock Speed"
|
"Clock Speed"
|
||||||
],
|
],
|
||||||
"php_extra" :[
|
"php_extra" :[
|
||||||
"CPU Model"
|
"CPU Model",
|
||||||
|
"Core Count"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -242,6 +243,7 @@
|
|||||||
"Device Path": "lsblk -d -o NAME,SIZE | grep -v -e 0B -e NAME | awk '{{print \"/dev/\"$1}}' | grep {this_device}",
|
"Device Path": "lsblk -d -o NAME,SIZE | grep -v -e 0B -e NAME | awk '{{print \"/dev/\"$1}}' | grep {this_device}",
|
||||||
"Total Capacity": "lsblk -d -o NAME,SIZE | grep {this_device} | awk '{{print $2}}'"
|
"Total Capacity": "lsblk -d -o NAME,SIZE | grep {this_device} | awk '{{print $2}}'"
|
||||||
},
|
},
|
||||||
|
"precheck": "lspci | grep sr0 | wc -l",
|
||||||
"metrics": {
|
"metrics": {
|
||||||
"placeholder": ""
|
"placeholder": ""
|
||||||
}
|
}
|
||||||
|
|||||||
91
files/docker/Dockerfile
Normal file
91
files/docker/Dockerfile
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 1. Base image
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# We use a slim Debian base so we can use apt‑get to pull every
|
||||||
|
# component in one go. Debian Bookworm contains all the
|
||||||
|
# packages we need (nodejs 18, redis, nginx, php8‑fpm, etc.).
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
FROM php:8.0-apache
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 2. Build arguments – handy if you want to change the port numbers
|
||||||
|
# without touching the Dockerfile
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
ARG REDIS_PORT=6379
|
||||||
|
ARG NODE_PORT=3000
|
||||||
|
ARG PHP_PORT=8080
|
||||||
|
ARG NGX_PORT=80
|
||||||
|
|
||||||
|
ENV REDIS_PORT=${REDIS_PORT}
|
||||||
|
ENV NODE_PORT=${NODE_PORT}
|
||||||
|
ENV PHP_PORT=${PHP_PORT}
|
||||||
|
ENV NGX_PORT=${NGX_PORT}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 3. Install all the system packages we need
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
RUN apt-get update && \
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||||
|
curl gnupg ca-certificates \
|
||||||
|
nodejs npm \
|
||||||
|
redis-server \
|
||||||
|
nginx \
|
||||||
|
supervisor \
|
||||||
|
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4. Prepare the working directories
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Node app
|
||||||
|
WORKDIR /app
|
||||||
|
COPY web/node_server/package.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY web/node_server/ ./
|
||||||
|
|
||||||
|
# Web‑dashboard static files
|
||||||
|
COPY web/html /var/www/html/
|
||||||
|
|
||||||
|
# API settings file
|
||||||
|
COPY cosmostat_settings.yaml /app/cosmostat_settings.yaml
|
||||||
|
|
||||||
|
# Nginx config – you can keep the same file you used for the
|
||||||
|
# proxy service in the compose file. It will proxy 3000 (WS)
|
||||||
|
# and 8080 (PHP) to the local container.
|
||||||
|
COPY web/proxy/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 5. Supervisord configuration
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Create a minimal supervisord.conf that will launch the four
|
||||||
|
# services from the same container.
|
||||||
|
RUN mkdir -p /etc/supervisor/conf.d && \
|
||||||
|
cat > /etc/supervisor/conf.d/supervisord.conf <<EOF
|
||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
|
||||||
|
[program:redis]
|
||||||
|
command=/usr/bin/redis-server --port ${REDIS_PORT}
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=root
|
||||||
|
|
||||||
|
[program:node]
|
||||||
|
command=npm run start
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=root
|
||||||
|
|
||||||
|
[program:nginx]
|
||||||
|
command=/usr/sbin/nginx -g 'daemon off;'
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
user=root
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 6. Expose the ports
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
EXPOSE ${REDIS_PORT} ${NGX_PORT}
|
||||||
|
|
||||||
|
# 7. Default command – start supervisord
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
48
files/docker/Dockerfile-1
Normal file
48
files/docker/Dockerfile-1
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
FROM php:8.0-apache
|
||||||
|
|
||||||
|
RUN set -eux; \
|
||||||
|
apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
gnupg \
|
||||||
|
lsb-release \
|
||||||
|
wget \
|
||||||
|
curl \
|
||||||
|
sudo \
|
||||||
|
redis-server \
|
||||||
|
nginx \
|
||||||
|
supervisor \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Node application
|
||||||
|
COPY web/node_server/ .
|
||||||
|
RUN npm install --only=production
|
||||||
|
|
||||||
|
#RUN npm ci --production
|
||||||
|
|
||||||
|
# PHP static files (public web root)
|
||||||
|
COPY web/html/ /var/www/html/
|
||||||
|
|
||||||
|
# NGINX config (overwrites the default)
|
||||||
|
COPY web/proxy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Shared settings file (read‑only)
|
||||||
|
COPY cosmostat_settings.yaml /app/cosmostat_settings.yaml
|
||||||
|
|
||||||
|
# Supervisor configuration
|
||||||
|
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||||
|
|
||||||
|
# Entrypoint script
|
||||||
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 6379
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||||
|
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
|
|
||||||
|
|
||||||
6
files/docker/entrypoint.sh
Normal file
6
files/docker/entrypoint.sh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Ensure the shared config file is readable
|
||||||
|
chmod 644 /app/cosmostat_settings.yaml
|
||||||
|
|
||||||
|
# Let Supervisor do the heavy lifting
|
||||||
|
exec "$@"
|
||||||
22
files/docker/supervisord.conf
Normal file
22
files/docker/supervisord.conf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
|
||||||
|
[program:redis]
|
||||||
|
command=/usr/bin/redis-server
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
|
||||||
|
[program:node]
|
||||||
|
command=sh -c "cd /app && node server.js"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
|
||||||
|
[program:apache]
|
||||||
|
command=/usr/sbin/httpd -DFOREGROUND
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
|
||||||
|
[program:nginx]
|
||||||
|
command=nginx -g "daemon off;"
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
@ -6,7 +6,7 @@ function h(string $s): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load API data
|
// Load API data
|
||||||
$raw_api_settings = file('/opt/api_settings/cosmostat_settings.yaml',
|
$raw_api_settings = file('/app/cosmostat_settings.yaml',
|
||||||
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
|
|
||||||
$api_settings = [];
|
$api_settings = [];
|
||||||
|
|||||||
@ -15,25 +15,20 @@
|
|||||||
pingTimeout: 5000,
|
pingTimeout: 5000,
|
||||||
pingInterval: 25000,
|
pingInterval: 25000,
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Color constants
|
Color constants
|
||||||
========================================================== */
|
========================================================== */
|
||||||
const GREEN = [ 39, 174, 96]; // #27ae60
|
const GREEN = [ 39, 174, 96]; // #27ae60
|
||||||
const YELLOW = [243, 156, 18]; // #f39c12
|
const YELLOW = [243, 156, 18]; // #f39c12
|
||||||
const RED = [192, 57, 43]; // #c0392b
|
const RED = [192, 57, 43]; // #c0392b
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Helpers
|
Helpers
|
||||||
========================================================== */
|
========================================================== */
|
||||||
const hostTimestamps = {}; // keyed by short_id
|
const hostTimestamps = {}; // keyed by short_id
|
||||||
|
|
||||||
const toRgb = (r, g, b) => `rgb(${r},${g},${b})`;
|
const toRgb = (r, g, b) => `rgb(${r},${g},${b})`;
|
||||||
|
|
||||||
const T20 = 20 * 1000;
|
const T20 = 20 * 1000;
|
||||||
const T40 = 40 * 1000;
|
const T40 = 40 * 1000;
|
||||||
const T60 = 60 * 1000;
|
const T60 = 60 * 1000;
|
||||||
|
|
||||||
function getFreshnessColor(ageMs) {
|
function getFreshnessColor(ageMs) {
|
||||||
if (ageMs <= T20) {
|
if (ageMs <= T20) {
|
||||||
return toRgb(...GREEN);
|
return toRgb(...GREEN);
|
||||||
@ -54,19 +49,16 @@
|
|||||||
}
|
}
|
||||||
return toRgb(...RED);
|
return toRgb(...RED);
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeSetText(id, txt) {
|
function safeSetText(id, txt) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.textContent = txt;
|
if (el) el.textContent = txt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Get the short_id from the query string
|
Get the short_id from the query string
|
||||||
========================================================== */
|
========================================================== */
|
||||||
function getSelectedId() {
|
function getSelectedId() {
|
||||||
return new URLSearchParams(window.location.search).get('host') || '';
|
return new URLSearchParams(window.location.search).get('host') || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Sidebar building - uses short_id for status key
|
Sidebar building - uses short_id for status key
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -75,30 +67,24 @@
|
|||||||
const current = Array.from(ul.children).map(li => li.dataset.id);
|
const current = Array.from(ul.children).map(li => li.dataset.id);
|
||||||
const newIds = systemList.map(s => s.short_id);
|
const newIds = systemList.map(s => s.short_id);
|
||||||
if (arraysEqual(current, newIds)) return;
|
if (arraysEqual(current, newIds)) return;
|
||||||
|
|
||||||
const selected = getSelectedId().toLowerCase();
|
const selected = getSelectedId().toLowerCase();
|
||||||
ul.innerHTML = ''; // reset list
|
ul.innerHTML = ''; // reset list
|
||||||
|
|
||||||
systemList.forEach(item => {
|
systemList.forEach(item => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
|
|
||||||
// status dot - keyed by short_id
|
// status dot - keyed by short_id
|
||||||
const status = document.createElement('span');
|
const status = document.createElement('span');
|
||||||
status.className = 'host-status';
|
status.className = 'host-status';
|
||||||
status.dataset.id = item.short_id;
|
status.dataset.id = item.short_id;
|
||||||
|
|
||||||
// link - display hostname, encode short_id in URL
|
// link - display hostname, encode short_id in URL
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = '?host=' + encodeURIComponent(item.short_id);
|
a.href = '?host=' + encodeURIComponent(item.short_id);
|
||||||
a.textContent = item.hostname;
|
a.textContent = item.hostname;
|
||||||
if (item.short_id.toLowerCase() === selected) a.classList.add('active');
|
if (item.short_id.toLowerCase() === selected) a.classList.add('active');
|
||||||
|
|
||||||
li.appendChild(status);
|
li.appendChild(status);
|
||||||
li.appendChild(a);
|
li.appendChild(a);
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Update status colors every second
|
Update status colors every second
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -113,9 +99,7 @@
|
|||||||
if (span) span.style.backgroundColor = color;
|
if (span) span.style.backgroundColor = color;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(updateStatusColors, 1000);
|
setInterval(updateStatusColors, 1000);
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Utility helpers
|
Utility helpers
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -124,7 +108,6 @@
|
|||||||
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderGenericTable(containerId, data, emptyMsg) {
|
function renderGenericTable(containerId, data, emptyMsg) {
|
||||||
const container = document.getElementById(containerId);
|
const container = document.getElementById(containerId);
|
||||||
if (!Array.isArray(data) || !data.length) {
|
if (!Array.isArray(data) || !data.length) {
|
||||||
@ -138,7 +121,6 @@
|
|||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
container.appendChild(table);
|
container.appendChild(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeRowsByName(rows) {
|
function mergeRowsByName(rows) {
|
||||||
const groups = {}; // { Source: { Metric: [], Data: [] } }
|
const groups = {}; // { Source: { Metric: [], Data: [] } }
|
||||||
rows.forEach(r => {
|
rows.forEach(r => {
|
||||||
@ -156,7 +138,6 @@
|
|||||||
Data: g.Data,
|
Data: g.Data,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function orderRows(rows) {
|
function orderRows(rows) {
|
||||||
const priority = ['System', 'CPU', 'RAM'];
|
const priority = ['System', 'CPU', 'RAM'];
|
||||||
const map = {};
|
const map = {};
|
||||||
@ -167,7 +148,6 @@
|
|||||||
return ai - bi;
|
return ai - bi;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTable(rows) {
|
function buildTable(rows) {
|
||||||
const cols = ['Source', 'Metric', 'Data'];
|
const cols = ['Source', 'Metric', 'Data'];
|
||||||
const table = document.createElement('table');
|
const table = document.createElement('table');
|
||||||
@ -200,12 +180,10 @@
|
|||||||
});
|
});
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Handle incoming data
|
Handle incoming data
|
||||||
========================================================== */
|
========================================================== */
|
||||||
let lastUpdate = Date.now();
|
let lastUpdate = Date.now();
|
||||||
|
|
||||||
function handleSummary(raw) {
|
function handleSummary(raw) {
|
||||||
lastUpdate = Date.now(); // reset watchdog
|
lastUpdate = Date.now(); // reset watchdog
|
||||||
let payload;
|
let payload;
|
||||||
@ -215,25 +193,20 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else payload = raw;
|
} else payload = raw;
|
||||||
|
|
||||||
if (!Array.isArray(payload) || !payload.length) {
|
if (!Array.isArray(payload) || !payload.length) {
|
||||||
safeSetText('client_summary', 'No data available');
|
safeSetText('client_summary', 'No data available');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the list first (so <span> elements exist)
|
// Build the list first (so <span> elements exist)
|
||||||
buildList(payload);
|
buildList(payload);
|
||||||
|
|
||||||
// Store the timestamp for every short_id
|
// Store the timestamp for every short_id
|
||||||
payload.forEach(hostObj => {
|
payload.forEach(hostObj => {
|
||||||
if (hostObj.short_id && hostObj.data_timestamp) {
|
if (hostObj.short_id && hostObj.data_timestamp) {
|
||||||
hostTimestamps[hostObj.short_id] = hostObj.data_timestamp; // seconds
|
hostTimestamps[hostObj.short_id] = hostObj.data_timestamp; // seconds
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Immediately update colors for the current view
|
// Immediately update colors for the current view
|
||||||
updateStatusColors();
|
updateStatusColors();
|
||||||
|
|
||||||
// Metric table for selected host
|
// Metric table for selected host
|
||||||
const selectedId = getSelectedId();
|
const selectedId = getSelectedId();
|
||||||
const hostObj = payload.find(h => h.short_id === selectedId) || payload[0];
|
const hostObj = payload.find(h => h.short_id === selectedId) || payload[0];
|
||||||
@ -242,7 +215,6 @@
|
|||||||
: [];
|
: [];
|
||||||
renderGenericTable('host_metrics', hostData, 'No Stats available');
|
renderGenericTable('host_metrics', hostData, 'No Stats available');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Socket event wiring
|
Socket event wiring
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -258,7 +230,6 @@
|
|||||||
safeSetText('client_summary', `Re-connected (attempt ${attempt})`);
|
safeSetText('client_summary', `Re-connected (attempt ${attempt})`);
|
||||||
requestSummary();
|
requestSummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Request logic
|
Request logic
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -266,7 +237,6 @@
|
|||||||
if (!socket.connected) return; // guard against stale emits
|
if (!socket.connected) return; // guard against stale emits
|
||||||
socket.emit('get_client_summary'); // server will reply via client_summary
|
socket.emit('get_client_summary'); // server will reply via client_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Recursive polling
|
Recursive polling
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -279,7 +249,6 @@
|
|||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
if (!pollTimer) pollLoop();
|
if (!pollTimer) pollLoop();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Watchdog - force reconnect if no data for 15 s
|
Watchdog - force reconnect if no data for 15 s
|
||||||
========================================================== */
|
========================================================== */
|
||||||
@ -291,7 +260,6 @@
|
|||||||
setTimeout(watchdog, 5000);
|
setTimeout(watchdog, 5000);
|
||||||
}
|
}
|
||||||
watchdog();
|
watchdog();
|
||||||
|
|
||||||
/* ==========================================================
|
/* ==========================================================
|
||||||
Keep the 'active' link in sync when the URL changes
|
Keep the 'active' link in sync when the URL changes
|
||||||
========================================================== */
|
========================================================== */
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
<?php
|
<?php
|
||||||
# load API settings, this requires a simple yaml file
|
# load API settings, this requires a simple yaml file
|
||||||
$raw_api_settings = file('/opt/api_settings/cosmostat_settings.yaml', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
$raw_api_settings = file('/app/cosmostat_settings.yaml', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
$api_settings = [];
|
$api_settings = [];
|
||||||
foreach ($raw_api_settings as $line) {
|
foreach ($raw_api_settings as $line) {
|
||||||
if ($line[0] === '#') {
|
if ($line[0] === '#') {
|
||||||
|
|||||||
@ -13,6 +13,15 @@
|
|||||||
state: stopped
|
state: stopped
|
||||||
scope: user
|
scope: user
|
||||||
|
|
||||||
|
# create service working folder
|
||||||
|
- name: Cosmostat - API - create cosmos user service folder
|
||||||
|
file:
|
||||||
|
path: "{{ user_service_folder }}"
|
||||||
|
state: directory
|
||||||
|
owner: "{{ service_user }}"
|
||||||
|
group: "{{ service_user }}"
|
||||||
|
mode: '0755'
|
||||||
|
|
||||||
- name: Cosmostat - API - copy api files
|
- name: Cosmostat - API - copy api files
|
||||||
copy:
|
copy:
|
||||||
src: api/
|
src: api/
|
||||||
|
|||||||
@ -59,6 +59,14 @@
|
|||||||
# allow user services to "linger"
|
# allow user services to "linger"
|
||||||
- name: Cosmostat - Init - cosmos user enable linger
|
- name: Cosmostat - Init - cosmos user enable linger
|
||||||
shell: "loginctl enable-linger {{ service_user }}"
|
shell: "loginctl enable-linger {{ service_user }}"
|
||||||
|
register: user_linger
|
||||||
|
|
||||||
|
# - name: Reboot target after linger change
|
||||||
|
# reboot:
|
||||||
|
# msg: "Cosmostat - Init - Rebooting target for linger enable"
|
||||||
|
# pre_reboot_delay: 10
|
||||||
|
# reboot_timeout: 600
|
||||||
|
# when: user_linger.changed
|
||||||
|
|
||||||
# create service working folder
|
# create service working folder
|
||||||
- name: Cosmostat - Init - create cosmostat service folder
|
- name: Cosmostat - Init - create cosmostat service folder
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
|
# refresh when refresh
|
||||||
|
#- name: Quick refresh
|
||||||
|
# when: refresh_special | bool
|
||||||
|
# set_fact:
|
||||||
|
# quick_refresh: true
|
||||||
|
|
||||||
# initializa environment
|
# initializa environment
|
||||||
- name: Initialize Environment
|
- name: Initialize Environment
|
||||||
when: not quick_refresh | bool
|
when: not quick_refresh | bool
|
||||||
@ -7,6 +13,7 @@
|
|||||||
|
|
||||||
# set up API
|
# set up API
|
||||||
- name: Build API
|
- name: Build API
|
||||||
|
when: false
|
||||||
include_tasks: api.yaml
|
include_tasks: api.yaml
|
||||||
|
|
||||||
# set up web stack
|
# set up web stack
|
||||||
|
|||||||
@ -3,45 +3,64 @@
|
|||||||
# This part sets up cosmostat web dashboard
|
# This part sets up cosmostat web dashboard
|
||||||
###############################################
|
###############################################
|
||||||
|
|
||||||
- name: Cosmostat - Web - stop containers
|
#- name: Cosmostat - Web - stop containers
|
||||||
when: not quick_refresh | bool
|
# community.docker.docker_compose_v2:
|
||||||
shell: "docker-compose -f {{ service_control_web_folder }}/docker-compose.yaml down"
|
# project_src: "{{ service_control_docker_folder }}"
|
||||||
ignore_errors: yes
|
# state: stopped
|
||||||
|
# ignore_errors: yes
|
||||||
|
|
||||||
# Create web Folder
|
# Create web Folder
|
||||||
- name: "Cosmostat - Web - create {{ service_control_web_folder }}"
|
#- name: "Cosmostat - Web - create {{ service_control_web_folder }}"
|
||||||
file:
|
# file:
|
||||||
path: "{{ service_control_web_folder }}"
|
# path: "{{ service_control_web_folder }}"
|
||||||
state: directory
|
# state: directory
|
||||||
mode: '0755'
|
# mode: '0755'
|
||||||
owner: "{{ service_user }}"
|
# owner: "{{ service_user }}"
|
||||||
group: "{{ service_user }}"
|
# group: "{{ service_user }}"
|
||||||
|
|
||||||
- name: Cosmostat - Web - copy docker files
|
- name: Cosmostat - Web - copy docker files
|
||||||
copy:
|
copy:
|
||||||
src: "web/"
|
src: "docker/"
|
||||||
dest: "{{ service_control_web_folder }}"
|
dest: "{{ service_control_docker_folder }}"
|
||||||
mode: 0755
|
mode: 0755
|
||||||
owner: "{{ service_user }}"
|
owner: "{{ service_user }}"
|
||||||
group: "{{ service_user }}"
|
group: "{{ service_user }}"
|
||||||
|
|
||||||
|
- name: Cosmostat - Web - copy web files
|
||||||
|
copy:
|
||||||
|
src: "web/"
|
||||||
|
dest: "{{ service_control_docker_folder }}/web"
|
||||||
|
mode: 0755
|
||||||
|
owner: "{{ service_user }}"
|
||||||
|
group: "{{ service_user }}"
|
||||||
|
|
||||||
|
- name: Cosmostat - Web - template docker-compose.yaml
|
||||||
|
template:
|
||||||
|
src: docker-compose-single.yaml
|
||||||
|
dest: "{{ service_control_docker_folder }}/docker-compose.yaml"
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
|
- name: "Cosmostat - Web - template cosmostat_settings.yaml"
|
||||||
|
template:
|
||||||
|
src: cosmostat_settings.yaml
|
||||||
|
dest: "{{ service_control_docker_folder }}/cosmostat_settings.yaml"
|
||||||
|
owner: "{{ service_user }}"
|
||||||
|
group: "{{ service_user }}"
|
||||||
|
mode: 0644
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
# configure as server
|
# configure as server
|
||||||
- name: Cosmostat - Web - Configure Server Dashboard
|
- name: Cosmostat - Web - Configure Server Dashboard
|
||||||
when: cosmostat_server | bool
|
when: cosmostat_server | bool
|
||||||
include_tasks: server.yaml
|
include_tasks: server.yaml
|
||||||
|
|
||||||
- name: Cosmostat - Web - template docker-compose.yaml
|
#- name: Cosmostat - Web - Start containers
|
||||||
template:
|
# community.docker.docker_compose_v2:
|
||||||
src: docker-compose-php.yaml
|
# project_src: "{{ service_control_web_folder }}"
|
||||||
dest: "{{ service_control_web_folder }}/docker-compose.yaml"
|
# state: present
|
||||||
mode: 0644
|
# register: docker_output
|
||||||
|
#- debug: |
|
||||||
|
# msg="{{ docker_output.actions }}"
|
||||||
|
|
||||||
- name: Cosmostat - Web - Start containers
|
|
||||||
shell: "docker-compose -f {{ service_control_web_folder }}/docker-compose.yaml up -d"
|
|
||||||
register: docker_output
|
|
||||||
- debug: |
|
|
||||||
msg="{{ docker_output.stdout_lines }}"
|
|
||||||
msg="{{ docker_output.stderr_lines }}"
|
|
||||||
|
|
||||||
...
|
...
|
||||||
@ -34,12 +34,11 @@ services:
|
|||||||
- "{{ docker_gateway }}:8080:80"
|
- "{{ docker_gateway }}:8080:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./html:/var/www/html/
|
- ./html:/var/www/html/
|
||||||
- "{{ api_service_folder }}/cosmostat_settings.yaml:/opt/api_settings/cosmostat_settings.yaml:ro"
|
- "{{ api_service_folder }}/cosmostat_settings.yaml:/app/cosmostat_settings.yaml:ro"
|
||||||
networks:
|
networks:
|
||||||
- cosmostat_net
|
- cosmostat_net
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
# public_dashboard: {{ public_dashboard }}
|
|
||||||
cosmostat_nginx_proxy:
|
cosmostat_nginx_proxy:
|
||||||
container_name: cosmostat_nginx_proxy
|
container_name: cosmostat_nginx_proxy
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
|
|||||||
35
templates/docker-compose-single.yaml
Normal file
35
templates/docker-compose-single.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
services:
|
||||||
|
# cosmostat:
|
||||||
|
# container_name: cosmostat
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: Dockerfile
|
||||||
|
# ports:
|
||||||
|
# - "{{ docker_gateway }}:6379:6379"
|
||||||
|
# - "{{ (docker_gateway + ':') if not public_dashboard | bool else '' }}{{ custom_port }}:80"
|
||||||
|
# networks:
|
||||||
|
# - cosmostat_net
|
||||||
|
# restart: always
|
||||||
|
|
||||||
|
cosmostat_all:
|
||||||
|
container_name: cosmostat_all
|
||||||
|
image: cosmostat-all:latest
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
networks:
|
||||||
|
- cosmostat_net
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "{{ docker_gateway }}:6379:6379"
|
||||||
|
- "{{ (docker_gateway + ':') if not public_dashboard | bool else '' }}{{ custom_port }}:80"
|
||||||
|
volumes:
|
||||||
|
- "/opt/cosmostat/docker/web/html:/var/www/html"
|
||||||
|
- "/opt/cosmostat/docker/web/node_server:/app"
|
||||||
|
- "/opt/cosmostat/cosmostat_settings.yaml:/app/cosmostat_settings.yaml:ro"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cosmostat_net:
|
||||||
|
external: true
|
||||||
@ -11,4 +11,4 @@ Restart=always
|
|||||||
{{ extra_service_options }}
|
{{ extra_service_options }}
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=default.target
|
||||||
|
|||||||
Reference in New Issue
Block a user