cosmostat working
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
1. Global settings & color palette
|
||||
------------------------------------------------- */
|
||||
:root {
|
||||
/* Dark theme – body & card backgrounds */
|
||||
/* Dark theme - body & card backgrounds */
|
||||
--bg-body: #2c3e50; /* main page background */
|
||||
--bg-card: #34495e; /* card / panel background */
|
||||
--bg-sidebar: #3d566e; /* sidebar background (slightly lighter) */
|
||||
@ -30,14 +30,23 @@ a { color: var(--clr-accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* -------------------------------------------------
|
||||
2. Layout – wrapper, sidebar, main
|
||||
2. Layout - wrapper, sidebar, main
|
||||
------------------------------------------------- */
|
||||
.wrapper { display: flex; min-height: 100vh; }
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background: var(--bg-sidebar);
|
||||
position: fixed; /* keep sidebar visible during scroll */
|
||||
top: 0; /* stick to the top of the viewport */
|
||||
left: 0; /* align to the left edge */
|
||||
height: 100vh; /* full viewport height */
|
||||
/* ---- size & spacing ------------------------------------------- */
|
||||
width: 200px; /* same as before */
|
||||
padding: 1rem;
|
||||
overflow-y: auto; /* allow sidebar content to scroll if needed */
|
||||
/* ---- look ------------------------------------------------------- */
|
||||
background: var(--bg-sidebar);
|
||||
/* optional: keep it above other content */
|
||||
z-index: 1000;
|
||||
}
|
||||
.sidebar h3 { margin: 0 0 .4rem 0; font-size: 1.1rem; }
|
||||
.sidebar ul { list-style: none; padding: 0; margin: 0; }
|
||||
@ -46,8 +55,13 @@ a:hover { text-decoration: underline; }
|
||||
.sidebar a { color: var(--clr-accent); }
|
||||
.sidebar a.active { font-weight: bold; }
|
||||
|
||||
.main { flex: 1; padding: 1rem; }
|
||||
|
||||
.main{
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
padding-left: 200px; /* space for the fixed sidebar */
|
||||
/* optional: avoid accidental horizontal overflow */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
/* -------------------------------------------------
|
||||
3. Card component
|
||||
------------------------------------------------- */
|
||||
@ -105,7 +119,7 @@ li { margin-bottom: 10px; color: var(--clr-text); }
|
||||
.component h3 { margin: 0 0 5px; }
|
||||
|
||||
/* -------------------------------------------------
|
||||
7. Help toggle / modal
|
||||
7. Panel toggles / modal
|
||||
------------------------------------------------- */
|
||||
.help-link {
|
||||
cursor: pointer;
|
||||
@ -116,8 +130,117 @@ li { margin-bottom: 10px; color: var(--clr-text); }
|
||||
.help-link:hover { text-decoration: underline; }
|
||||
#helpText { display: none; }
|
||||
|
||||
.componentDetail-link {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
color: var(--clr-accent);
|
||||
text-align: left;
|
||||
}
|
||||
.componentDetail-link:hover { text-decoration: underline; }
|
||||
#componentDetailText { display: none; }
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
8. Misc helpers
|
||||
------------------------------------------------- */
|
||||
/* Hide numeric markers in metric columns (if any) */
|
||||
#host_metrics_column td { list-style: none; padding-left: 0; margin-left: 0; }
|
||||
#host_metrics_column td { list-style: none; padding-left: 0; margin-left: 0; }
|
||||
|
||||
.host-status {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-left: 6px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
background: #808080; /* default – unknown / no timestamp */
|
||||
transition: background-color 1s linear; /* smooth fade */
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------------------------------
|
||||
9. Mobile adjustments
|
||||
------------------------------------------------- */
|
||||
@media (max-width: 768px) {
|
||||
/* 1. Make the whole page a column */
|
||||
.wrapper {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 2. Hide the sidebar initially */
|
||||
.sidebar {
|
||||
position: relative; /* take it out of the flow */
|
||||
width: 100%;
|
||||
max-height: 0; /* collapsed */
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out;
|
||||
background: var(--bg-sidebar);
|
||||
padding: 0; /* remove padding */
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
max-height: 500px; /* enough for all items */
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* 3. Move the toggle button into the header */
|
||||
.mobile-toggle {
|
||||
display: block;
|
||||
font-size: 1.5rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--clr-accent);
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* 4. Main content no left padding */
|
||||
.main {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
/* 5. Table scroll on small screens */
|
||||
.card table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.card table thead,
|
||||
.card table tbody,
|
||||
.card table tr,
|
||||
.card table td,
|
||||
.card table th {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card table tbody {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card table td,
|
||||
.card table th {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
/* 6. Adjust the host status dot positioning */
|
||||
.host-status {
|
||||
margin-left: 2px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Optional: style the drawer indicator */
|
||||
.sidebar.show::before {
|
||||
content: "✕ Close";
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--bg-sidebar);
|
||||
color: var(--clr-accent);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
@ -61,65 +61,66 @@ io.on('connection', async socket => {
|
||||
/* ---------- 3. Serve static files ----------------------------------- */
|
||||
/* --------------------------------------------------------------------- */
|
||||
app.use(express.static('public'));
|
||||
/* --- 4. Redis subscriber (patched) --------------------------------- */
|
||||
const redisClient = createClient({
|
||||
url: 'redis://192.168.37.1:6379',
|
||||
socket: { keepAlive: 60000, // 60 s TCP keep-alive
|
||||
reconnectStrategy: attempts => Math.min(attempts * 100, 3000) } // back-off
|
||||
});
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
/* ---------- 4. Redis subscriber ------------------------------------- */
|
||||
/* --------------------------------------------------------------------- */
|
||||
const redisClient = createClient({ url: 'redis://192.168.37.1:6379' });
|
||||
redisClient.on('error', err => console.error('Redis error', err));
|
||||
|
||||
(async () => {
|
||||
await redisClient.connect();
|
||||
const sub = redisClient.duplicate(); // duplicate to keep separate pub/sub
|
||||
|
||||
const sub = redisClient.duplicate();
|
||||
await sub.connect();
|
||||
|
||||
// Subscribe to the channel that sends host stats
|
||||
await sub.subscribe(
|
||||
['host_metrics'],
|
||||
(message, channel) => {
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(message); // message is a JSON string
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse ${channel}`, e);
|
||||
return;
|
||||
}
|
||||
io.emit(channel, payload);
|
||||
// --------------------------------------------------------------------
|
||||
// Helper that re-subscribes to a channel (and re-sends the handler)
|
||||
// --------------------------------------------------------------------
|
||||
async function safeSubscribe(channel, handler) {
|
||||
try {
|
||||
await sub.subscribe(channel, handler);
|
||||
console.log(`Subscribed to ${channel}`);
|
||||
} catch (e) {
|
||||
console.error(`Failed to subscribe to ${channel}`, e);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Subscribe to the channel that sends host stats
|
||||
await sub.subscribe(
|
||||
['client_summary'],
|
||||
(message, channel) => {
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(message); // message is a JSON string
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse ${channel}`, e);
|
||||
return;
|
||||
}
|
||||
// ---------------------------------------------------------------
|
||||
// Subscribe to all required channels
|
||||
// ---------------------------------------------------------------
|
||||
await safeSubscribe('host_metrics', (msg) => forward('host_metrics', msg));
|
||||
await safeSubscribe('client_summary', (msg) => forward('client_summary', msg));
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Forward messages to Socket.io
|
||||
// ---------------------------------------------------------------
|
||||
function forward(channel, message) {
|
||||
try {
|
||||
const payload = JSON.parse(message);
|
||||
io.emit(channel, payload);
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse message from ${channel}`, e);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Subscribe to the channel that sends host stats
|
||||
await sub.subscribe(
|
||||
['client_hostnames'],
|
||||
(message, channel) => {
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(message); // message is a JSON string
|
||||
} catch (e) {
|
||||
console.error(`Failed to parse ${channel}`, e);
|
||||
return;
|
||||
}
|
||||
io.emit(channel, payload);
|
||||
}
|
||||
);
|
||||
// ----------------------------------------------------------------
|
||||
// Re-subscribe automatically when the Redis connection reconnects
|
||||
// ----------------------------------------------------------------
|
||||
sub.on('reconnecting', () => console.log('Redis reconnecting…'));
|
||||
sub.on('ready', async () => {
|
||||
console.log('Redis ready - re-subscribing to all channels');
|
||||
await safeSubscribe('host_metrics', (msg) => forward('host_metrics', msg));
|
||||
await safeSubscribe('client_summary', (msg) => forward('client_summary', msg));
|
||||
});
|
||||
|
||||
|
||||
sub.on('error', err => console.error('Subscriber error', err));
|
||||
// Optional: if the connection ends for any reason, close the process
|
||||
sub.on('end', () => {
|
||||
console.error('Redis connection closed - exiting');
|
||||
process.exit(1);
|
||||
});
|
||||
})();
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
@ -11,7 +11,7 @@ listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# ---------------------------------------
|
||||
# The API – only /descriptor
|
||||
# API Endpoints
|
||||
# ---------------------------------------
|
||||
location = /descriptor {
|
||||
proxy_pass http://192.168.37.1:5000/descriptor;
|
||||
@ -20,6 +20,20 @@ server_name localhost;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
location = /update_client {
|
||||
proxy_pass http://192.168.37.1:5000/update_client;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
location = /create_client {
|
||||
proxy_pass http://192.168.37.1:5000/create_client;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# ---------------------------------------
|
||||
# WebSocket endpoint
|
||||
|
||||
Reference in New Issue
Block a user