productionalizing?

This commit is contained in:
2026-02-11 17:12:00 -06:00
parent 99bf0f6c5f
commit a7635c62d3
6 changed files with 328 additions and 359 deletions

View File

@@ -3,7 +3,7 @@ from flask_minify import Minify
import json
import werkzeug.exceptions as HTTPerror
from config import *
from monitor import monitor
from monitor import monitor, SERVICES
app = flask.Flask(__name__)
@@ -43,6 +43,7 @@ pages['projects']['skillList'] = skillList
pages['projects']['projects'] = proj
pages['home']['books'] = books
pages['books']['books'] = books
pages['status']['services'] = SERVICES
@app.route('/api/status')
def api_status():

View File

@@ -30,15 +30,9 @@ SERVICES = [
'timeout': 10
},
{
'id': 'pass',
'name': 'pass.asimonson.com',
'url': 'https://pass.asimonson.com',
'timeout': 10
},
{
'id': 'ssh',
'name': 'ssh.asimonson.com',
'url': 'https://ssh.asimonson.com',
'id': 'balls',
'name': 'balls.asimonson.com',
'url': 'https://balls.asimonson.com',
'timeout': 10
}
]
@@ -182,8 +176,22 @@ class ServiceMonitor:
if datetime.fromisoformat(c['timestamp']) > cutoff
]
if not checks:
return None
if not checks:
return None
# Require minimum data coverage for the time period
# Calculate expected number of checks for this period
expected_checks = (hours * 3600) / CHECK_INTERVAL
# Require at least 50% of expected checks to show this metric
minimum_checks = max(3, expected_checks * 0.5)
if len(checks) < minimum_checks:
return None
else:
# For all-time, require at least 3 checks
if len(checks) < 3:
return None
online_count = sum(1 for c in checks if c['status'] == 'online')
uptime = (online_count / len(checks)) * 100

View File

@@ -104,11 +104,30 @@ strong {
color: #ecebeb;
}
p, li {
ul {
margin: 0;
padding-left: 1.5em;
color: #a8a8a8;
}
li {
margin-bottom: 0.5em;
color: #a8a8a8;
}
strong {
color: #ecebeb;
}
p, li, span {
color: rgb(212, 212, 212);
font-size: 1rem;
}
span {
font-size: .8rem;
}
a, a p {
color: #a0a0a0a0;
text-decoration: none;
@@ -1297,20 +1316,116 @@ tr {
font-size: 1rem;
}
.status-info {
/* Overall Status Bar */
/* Overall Status Bar */
.overall-status-bar {
background: rgba(24, 24, 24, 0.95);
border-radius: 0.5em;
padding: 2em;
margin-bottom: 2em;
border-top: solid 4px rgba(139, 36, 36, 0.5);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transition: border-color 0.5s ease;
}
.overall-status-bar.all-operational {
border-top-color: rgba(76, 175, 80, 0.8);
}
.overall-status-bar.partial-outage {
border-top-color: rgba(255, 193, 7, 0.8);
}
.overall-status-bar.major-outage {
border-top-color: rgba(244, 67, 54, 0.8);
}
.overall-status-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2em;
padding: 1em;
background: rgba(24, 24, 24, 0.85);
border-radius: 0.5em;
border: solid 2px rgba(139, 36, 36, 0.5);
flex-wrap: wrap;
gap: 2em;
}
.status-info span {
.overall-status-indicator {
display: flex;
align-items: center;
gap: 1.5em;
}
.overall-status-icon {
font-size: 3rem;
line-height: 1;
animation: spin 2s linear infinite;
}
.overall-status-icon.operational {
color: #4caf50;
animation: none;
}
.overall-status-icon.partial {
color: #ffc107;
animation: pulse-icon 1.5s ease-in-out infinite;
}
.overall-status-icon.major {
color: #f44336;
animation: none;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse-icon {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.overall-status-title {
margin: 0;
font-size: 1.5rem;
color: #ecebeb;
font-weight: 600;
}
.overall-status-subtitle {
margin: 0.3em 0 0 0;
font-size: 1rem;
color: #a8a8a8;
font-size: 0.9rem;
}
.overall-status-metrics {
display: flex;
gap: 2em;
}
.metric-box {
display: flex;
flex-direction: column;
align-items: center;
padding: 1em 1.5em;
background: rgba(0, 0, 0, 0.3);
border-radius: 0.5em;
min-width: 80px;
}
.metric-number {
font-size: 2rem;
font-weight: bold;
color: #ecebeb;
line-height: 1;
}
.metric-label {
font-size: 0.85rem;
color: #a8a8a8;
margin-top: 0.3em;
text-transform: uppercase;
letter-spacing: 0.5px;
}
#refreshBtn {
@@ -1504,35 +1619,28 @@ tr {
}
.uptime-values strong {
color: #4caf50;
font-weight: 600;
}
.status-info-box {
background: rgba(24, 24, 24, 0.85);
border-radius: 0.5em;
padding: 1.5em;
border: solid 2px rgba(139, 36, 36, 0.5);
margin-top: 2em;
/* Uptime color coding based on percentage */
.uptime-excellent {
color: #4caf50 !important; /* 99%+ - Green */
}
.status-info-box h4 {
margin-top: 0;
color: #ecebeb;
.uptime-good {
color: #8bc34a !important; /* 95-99% - Light green */
}
.status-info-box ul {
margin: 0;
padding-left: 1.5em;
color: #a8a8a8;
.uptime-fair {
color: #ffc107 !important; /* 90-95% - Yellow */
}
.status-info-box li {
margin-bottom: 0.5em;
color: #a8a8a8;
.uptime-poor {
color: #f44336 !important; /* <90% - Red */
}
.status-info-box strong {
color: #ecebeb;
.uptime-none {
color: #888 !important; /* No data - Gray */
}
@media screen and (max-width: 1400px) {
@@ -1549,4 +1657,19 @@ tr {
flex-direction: column;
gap: 0.5em;
}
.overall-status-content {
flex-direction: column;
text-align: center;
}
.overall-status-indicator {
flex-direction: column;
text-align: center;
}
.overall-status-metrics {
width: 100%;
justify-content: center;
}
}

View File

@@ -43,6 +43,9 @@ function updateStatusDisplay(data) {
updateServiceCard(service);
});
// Update overall status
updateOverallStatus(data.services);
// Re-enable refresh button
const refreshBtn = document.getElementById('refreshBtn');
if (refreshBtn) {
@@ -109,24 +112,29 @@ function updateServiceCard(service) {
if (uptimeDisplay && service.uptime) {
const uptimeHTML = [];
if (service.uptime['24h'] !== null) {
uptimeHTML.push(`24h: <strong>${service.uptime['24h']}%</strong>`);
}
if (service.uptime['7d'] !== null) {
uptimeHTML.push(`7d: <strong>${service.uptime['7d']}%</strong>`);
}
if (service.uptime['30d'] !== null) {
uptimeHTML.push(`30d: <strong>${service.uptime['30d']}%</strong>`);
}
if (service.uptime.all_time !== null) {
uptimeHTML.push(`All: <strong>${service.uptime.all_time}%</strong>`);
}
// Helper function to get color class based on uptime percentage
const getUptimeClass = (value) => {
if (value === null) return 'uptime-none';
if (value >= 99) return 'uptime-excellent';
if (value >= 95) return 'uptime-good';
if (value >= 90) return 'uptime-fair';
return 'uptime-poor';
};
if (uptimeHTML.length > 0) {
uptimeDisplay.innerHTML = uptimeHTML.join(' | ');
} else {
uptimeDisplay.textContent = 'No data yet';
}
// Helper function to format uptime value
const formatUptime = (value, label) => {
const display = value !== null ? `${value}%` : '--';
const colorClass = getUptimeClass(value);
return `${label}: <strong class="${colorClass}">${display}</strong>`;
};
// Add all uptime metrics
uptimeHTML.push(formatUptime(service.uptime['24h'], '24h'));
uptimeHTML.push(formatUptime(service.uptime['7d'], '7d'));
uptimeHTML.push(formatUptime(service.uptime['30d'], '30d'));
uptimeHTML.push(formatUptime(service.uptime.all_time, 'All'));
uptimeDisplay.innerHTML = uptimeHTML.join(' | ');
}
// Update total checks
@@ -135,6 +143,68 @@ function updateServiceCard(service) {
}
}
/**
* Update overall status bar
*/
function updateOverallStatus(services) {
const overallBar = document.getElementById('overallStatus');
const icon = overallBar.querySelector('.overall-status-icon');
const title = overallBar.querySelector('.overall-status-title');
const subtitle = overallBar.querySelector('.overall-status-subtitle');
const onlineCount = document.getElementById('onlineCount');
const totalCount = document.getElementById('totalCount');
// Count service statuses
const total = services.length;
const online = services.filter(s => s.status === 'online').length;
const degraded = services.filter(s => s.status === 'degraded' || s.status === 'timeout').length;
const offline = services.filter(s => s.status === 'offline').length;
// Update counts
onlineCount.textContent = online;
totalCount.textContent = total;
// Remove all status classes
overallBar.classList.remove('all-operational', 'partial-outage', 'major-outage');
icon.classList.remove('operational', 'partial', 'major', 'loading');
// Determine overall status
if (online === total) {
// All systems operational
overallBar.classList.add('all-operational');
icon.classList.add('operational');
icon.textContent = '✓';
title.textContent = 'All Systems Operational';
subtitle.textContent = `All ${total} services are running normally`;
} else if (offline >= Math.ceil(total / 2)) {
// Major outage (50% or more offline)
overallBar.classList.add('major-outage');
icon.classList.add('major');
icon.textContent = '✕';
title.textContent = 'Major Outage';
subtitle.textContent = `${offline} service${offline !== 1 ? 's' : ''} offline, ${degraded} degraded`;
} else if (offline > 0 || degraded > 0) {
// Partial outage
overallBar.classList.add('partial-outage');
icon.classList.add('partial');
icon.textContent = '⚠';
title.textContent = 'Partial Outage';
if (offline > 0 && degraded > 0) {
subtitle.textContent = `${offline} offline, ${degraded} degraded`;
} else if (offline > 0) {
subtitle.textContent = `${offline} service${offline !== 1 ? 's' : ''} offline`;
} else {
subtitle.textContent = `${degraded} service${degraded !== 1 ? 's' : ''} degraded`;
}
} else {
// Unknown state
icon.classList.add('loading');
icon.textContent = '◐';
title.textContent = 'Status Unknown';
subtitle.textContent = 'Waiting for service data';
}
}
/**
* Show error message
*/

View File

@@ -1,145 +0,0 @@
{
"last_check": "2026-02-11T14:53:09.045018",
"services": {
"main": {
"name": "asimonson.com",
"url": "https://asimonson.com",
"status": "online",
"response_time": 170,
"status_code": 200,
"last_online": "2026-02-11T14:53:08.089141",
"checks": [
{
"timestamp": "2026-02-11T14:45:35.008804",
"status": "online",
"response_time": 171,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:51:02.673733",
"status": "online",
"response_time": 138,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:53:08.089141",
"status": "online",
"response_time": 170,
"status_code": 200
}
]
},
"files": {
"name": "files.asimonson.com",
"url": "https://files.asimonson.com",
"status": "online",
"response_time": 215,
"status_code": 200,
"last_online": "2026-02-11T14:53:08.259522",
"checks": [
{
"timestamp": "2026-02-11T14:45:35.180195",
"status": "online",
"response_time": 285,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:51:02.812769",
"status": "online",
"response_time": 259,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:53:08.259522",
"status": "online",
"response_time": 215,
"status_code": 200
}
]
},
"git": {
"name": "git.asimonson.com",
"url": "https://git.asimonson.com",
"status": "online",
"response_time": 145,
"status_code": 200,
"last_online": "2026-02-11T14:53:08.475376",
"checks": [
{
"timestamp": "2026-02-11T14:45:35.465748",
"status": "online",
"response_time": 297,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:51:03.072293",
"status": "online",
"response_time": 293,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:53:08.475376",
"status": "online",
"response_time": 145,
"status_code": 200
}
]
},
"pass": {
"name": "pass.asimonson.com",
"url": "https://pass.asimonson.com",
"status": "online",
"response_time": 160,
"status_code": 200,
"last_online": "2026-02-11T14:53:08.621016",
"checks": [
{
"timestamp": "2026-02-11T14:45:35.763775",
"status": "online",
"response_time": 253,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:51:03.365544",
"status": "online",
"response_time": 228,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:53:08.621016",
"status": "online",
"response_time": 160,
"status_code": 200
}
]
},
"ssh": {
"name": "ssh.asimonson.com",
"url": "https://ssh.asimonson.com",
"status": "online",
"response_time": 263,
"status_code": 200,
"last_online": "2026-02-11T14:53:08.781704",
"checks": [
{
"timestamp": "2026-02-11T14:45:36.017180",
"status": "online",
"response_time": 378,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:51:03.594307",
"status": "online",
"response_time": 411,
"status_code": 200
},
{
"timestamp": "2026-02-11T14:53:08.781704",
"status": "online",
"response_time": 263,
"status_code": 200
}
]
}
}
}

View File

@@ -2,160 +2,18 @@
<div class="foreground"></div>
<div class="foregroundContent">
<h2 class='concentratedHead'>Service Status Monitor</h2>
<p class="status-subtitle">Automated monitoring of asimonson.com services</p>
<div class="status-info">
<div>
<span id="lastUpdate">Last checked: Loading...</span>
<br>
<span id="nextUpdate" style="font-size: 0.85em; color: #888;">Next check: --</span>
</div>
<button id="refreshBtn" onclick="refreshStatus()">Refresh Now</button>
</div>
<div class="status-container">
<div class="status-card" id="status-main">
<div class="status-header">
<h3>asimonson.com</h3>
<div class="status-indicator">
<span class="status-dot loading"></span>
<span class="status-text">Loading...</span>
</div>
</div>
<div class="status-details">
<div class="status-metric">
<span class="metric-label">Response Time:</span>
<span class="metric-value" id="time-main">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Status Code:</span>
<span class="metric-value" id="code-main">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Total Checks:</span>
<span class="metric-value" id="checks-main">--</span>
</div>
</div>
<div class="status-uptime">
<div class="uptime-label">Uptime:</div>
<div class="uptime-values" id="uptime-main">Loading...</div>
</div>
</div>
<div class="status-card" id="status-files">
<div class="status-header">
<h3>files.asimonson.com</h3>
<div class="status-indicator">
<span class="status-dot loading"></span>
<span class="status-text">Loading...</span>
</div>
</div>
<div class="status-details">
<div class="status-metric">
<span class="metric-label">Response Time:</span>
<span class="metric-value" id="time-files">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Status Code:</span>
<span class="metric-value" id="code-files">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Total Checks:</span>
<span class="metric-value" id="checks-files">--</span>
</div>
</div>
<div class="status-uptime">
<div class="uptime-label">Uptime:</div>
<div class="uptime-values" id="uptime-files">Loading...</div>
</div>
</div>
<div class="status-card" id="status-git">
<div class="status-header">
<h3>git.asimonson.com</h3>
<div class="status-indicator">
<span class="status-dot loading"></span>
<span class="status-text">Loading...</span>
</div>
</div>
<div class="status-details">
<div class="status-metric">
<span class="metric-label">Response Time:</span>
<span class="metric-value" id="time-git">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Status Code:</span>
<span class="metric-value" id="code-git">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Total Checks:</span>
<span class="metric-value" id="checks-git">--</span>
</div>
</div>
<div class="status-uptime">
<div class="uptime-label">Uptime:</div>
<div class="uptime-values" id="uptime-git">Loading...</div>
</div>
</div>
<div class="status-card" id="status-pass">
<div class="status-header">
<h3>pass.asimonson.com</h3>
<div class="status-indicator">
<span class="status-dot loading"></span>
<span class="status-text">Loading...</span>
</div>
</div>
<div class="status-details">
<div class="status-metric">
<span class="metric-label">Response Time:</span>
<span class="metric-value" id="time-pass">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Status Code:</span>
<span class="metric-value" id="code-pass">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Total Checks:</span>
<span class="metric-value" id="checks-pass">--</span>
</div>
</div>
<div class="status-uptime">
<div class="uptime-label">Uptime:</div>
<div class="uptime-values" id="uptime-pass">Loading...</div>
</div>
</div>
<div class="status-card" id="status-ssh">
<div class="status-header">
<h3>ssh.asimonson.com</h3>
<div class="status-indicator">
<span class="status-dot loading"></span>
<span class="status-text">Loading...</span>
</div>
</div>
<div class="status-details">
<div class="status-metric">
<span class="metric-label">Response Time:</span>
<span class="metric-value" id="time-ssh">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Status Code:</span>
<span class="metric-value" id="code-ssh">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Total Checks:</span>
<span class="metric-value" id="checks-ssh">--</span>
</div>
</div>
<div class="status-uptime">
<div class="uptime-label">Uptime:</div>
<div class="uptime-values" id="uptime-ssh">Loading...</div>
</div>
</div>
</div>
<div class="status-legend">
<div class='flex spaceBetween'>
<div>
<span id="lastUpdate">Last checked: Loading...</span>
<br />
<span id="nextUpdate">Next check: --</span>
</div>
<button id="refreshBtn" onclick="refreshStatus()">Refresh Now</button>
</div>
<br/>
<h4>Status Legend</h4>
<div class="legend-items">
<div class="legend-item">
@@ -173,13 +31,67 @@
</div>
</div>
<div class="status-info-box">
<!-- Overall Status Bar -->
<div class="overall-status-bar" id="overallStatus">
<div class="overall-status-content">
<div class="overall-status-indicator">
<span class="overall-status-icon loading"></span>
<div>
<h3 class="overall-status-title">Checking Systems...</h3>
<p class="overall-status-subtitle">Loading service status</p>
</div>
</div>
<div class="overall-status-metrics">
<div class="metric-box">
<span class="metric-number" id="onlineCount">--</span>
<span class="metric-label">Online</span>
</div>
<div class="metric-box">
<span class="metric-number" id="totalCount">--</span>
<span class="metric-label">Total</span>
</div>
</div>
</div>
</div>
<div class="status-container">
{% for service in var.services %}
<a href="https://{{ service.url }}">
<div class="status-card" id="status-{{ service.id }}">
<div class="status-header">
<h3>{{ service.name }}</h3>
<div class="status-indicator">
<span class="status-dot loading"></span>
<span class="status-text">Loading...</span>
</div>
</div>
<div class="status-details">
<div class="status-metric">
<span class="metric-label">Response Time:</span>
<span class="metric-value" id="time-{{ service.id }}">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Status Code:</span>
<span class="metric-value" id="code-{{ service.id }}">--</span>
</div>
<div class="status-metric">
<span class="metric-label">Total Checks:</span>
<span class="metric-value" id="checks-{{ service.id }}">--</span>
</div>
</div>
<div class="status-uptime">
<div class="uptime-label">Uptime:</div>
<div class="uptime-values" id="uptime-{{ service.id }}">Loading...</div>
</div>
</div>
</a>
{% endfor %}
</div>
<div class="status-legend">
<h4>About This Monitor</h4>
<ul>
<li><strong>Check Frequency:</strong> Services are checked automatically every 2 hours from the server</li>
<li><strong>Uptime Calculation:</strong> Based on historical check data (24h, 7d, 30d, and all-time)</li>
<li><strong>Response Time:</strong> Time taken to receive a response from the service</li>
<li><strong>Status Code:</strong> HTTP response code from the service</li>
<li><strong>Check Frequency:</strong> Services are checked automatically every 30 minutes from the server</li>
<li><strong>Page Refresh:</strong> This page auto-refreshes every 5 minutes to show latest data</li>
</ul>
</div>