sample status page

This commit is contained in:
2026-02-11 14:54:45 -06:00
parent 46fc66971d
commit b1e75bd91f
7 changed files with 1180 additions and 31 deletions

View File

@@ -1190,4 +1190,267 @@ tr {
display: none;
height: 0px;
}
}
/* Status Page Styles */
.status-subtitle {
text-align: center;
color: #a8a8a8;
margin-top: -10px;
margin-bottom: 2em;
font-size: 1rem;
}
.status-info {
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);
}
.status-info span {
color: #a8a8a8;
font-size: 0.9rem;
}
#refreshBtn {
background: rgba(156, 49, 45, 0.8);
color: #ecebeb;
border: none;
padding: 0.5em 1.5em;
border-radius: 0.3em;
cursor: pointer;
font-size: 0.9rem;
transition: 0.3s;
}
#refreshBtn:hover:not(:disabled) {
background: rgba(156, 49, 45, 1);
}
#refreshBtn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.status-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5em;
margin-bottom: 2em;
}
.status-card {
background: rgba(24, 24, 24, 0.85);
border-radius: 0.5em;
padding: 1.5em;
border-top: solid 4px rgba(139, 36, 36, 0.5);
transition: 0.3s;
}
.status-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.status-card.online {
border-top-color: rgba(76, 175, 80, 0.8);
}
.status-card.degraded {
border-top-color: rgba(255, 193, 7, 0.8);
}
.status-card.offline {
border-top-color: rgba(244, 67, 54, 0.8);
}
.status-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1em;
padding-bottom: 0.5em;
border-bottom: 1px solid rgba(168, 168, 168, 0.2);
}
.status-header h3 {
margin: 0;
font-size: 1.1rem;
color: #ecebeb;
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5em;
}
.status-text {
font-size: 0.85rem;
color: #a8a8a8;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
}
.status-dot.online {
background: #4caf50;
box-shadow: 0 0 10px #4caf50;
}
.status-dot.degraded {
background: #ffc107;
box-shadow: 0 0 10px #ffc107;
}
.status-dot.offline {
background: #f44336;
box-shadow: 0 0 10px #f44336;
}
.status-dot.loading {
background: #a8a8a8;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.status-details {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.status-metric {
display: flex;
justify-content: space-between;
padding: 0.3em 0;
}
.metric-label {
color: #a8a8a8;
font-size: 0.9rem;
}
.metric-value {
color: #ecebeb;
font-size: 0.9rem;
font-weight: bold;
}
.status-legend {
background: rgba(24, 24, 24, 0.85);
border-radius: 0.5em;
padding: 1.5em;
border: solid 2px rgba(139, 36, 36, 0.5);
margin-bottom: 2em;
}
.status-legend h4 {
margin-top: 0;
color: #ecebeb;
}
.legend-items {
display: flex;
gap: 2em;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 0.5em;
color: #a8a8a8;
font-size: 0.9rem;
}
.status-note {
text-align: center;
font-size: 0.85rem;
color: #a8a8a8;
font-style: italic;
}
.status-uptime {
margin-top: 1em;
padding-top: 0.8em;
border-top: 1px solid rgba(168, 168, 168, 0.2);
}
.uptime-label {
color: #a8a8a8;
font-size: 0.85rem;
margin-bottom: 0.3em;
font-weight: bold;
}
.uptime-values {
color: #ecebeb;
font-size: 0.85rem;
line-height: 1.5;
}
.uptime-values strong {
color: #4caf50;
}
.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;
}
.status-info-box h4 {
margin-top: 0;
color: #ecebeb;
}
.status-info-box ul {
margin: 0;
padding-left: 1.5em;
color: #a8a8a8;
}
.status-info-box li {
margin-bottom: 0.5em;
color: #a8a8a8;
}
.status-info-box strong {
color: #ecebeb;
}
@media screen and (max-width: 1400px) {
.status-container {
grid-template-columns: 1fr;
}
.status-info {
flex-direction: column;
gap: 1em;
}
.legend-items {
flex-direction: column;
gap: 0.5em;
}
}

180
src/static/js/status.js Normal file
View File

@@ -0,0 +1,180 @@
// Fetch and display service status from API
/**
* Fetch status data from server
*/
async function fetchStatus() {
try {
const response = await fetch('/api/status');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
updateStatusDisplay(data);
} catch (error) {
console.error('Error fetching status:', error);
showError('Failed to fetch service status. Please try again later.');
}
}
/**
* Update the status display with fetched data
*/
function updateStatusDisplay(data) {
// Update last check time
if (data.last_check) {
const lastCheck = new Date(data.last_check);
const timeString = lastCheck.toLocaleString();
document.getElementById('lastUpdate').textContent = `Last checked: ${timeString}`;
}
// Update next check time
if (data.next_check) {
const nextCheck = new Date(data.next_check);
const timeString = nextCheck.toLocaleString();
const nextCheckEl = document.getElementById('nextUpdate');
if (nextCheckEl) {
nextCheckEl.textContent = `Next check: ${timeString}`;
}
}
// Update each service
data.services.forEach(service => {
updateServiceCard(service);
});
// Re-enable refresh button
const refreshBtn = document.getElementById('refreshBtn');
if (refreshBtn) {
refreshBtn.disabled = false;
refreshBtn.textContent = 'Refresh Now';
}
}
/**
* Update a single service card
*/
function updateServiceCard(service) {
const card = document.getElementById(`status-${service.id}`);
if (!card) return;
const statusDot = card.querySelector('.status-dot');
const statusText = card.querySelector('.status-text');
const timeDisplay = document.getElementById(`time-${service.id}`);
const codeDisplay = document.getElementById(`code-${service.id}`);
const uptimeDisplay = document.getElementById(`uptime-${service.id}`);
const checksDisplay = document.getElementById(`checks-${service.id}`);
// Update response time
if (service.response_time !== null) {
timeDisplay.textContent = `${service.response_time}ms`;
} else {
timeDisplay.textContent = '--';
}
// Update status code
if (service.status_code !== null) {
codeDisplay.textContent = service.status_code;
} else {
codeDisplay.textContent = service.status === 'unknown' ? 'Unknown' : 'Error';
}
// Update status indicator
card.classList.remove('online', 'degraded', 'offline', 'unknown');
switch (service.status) {
case 'online':
statusDot.className = 'status-dot online';
statusText.textContent = 'Operational';
card.classList.add('online');
break;
case 'degraded':
case 'timeout':
statusDot.className = 'status-dot degraded';
statusText.textContent = service.status === 'timeout' ? 'Timeout' : 'Degraded';
card.classList.add('degraded');
break;
case 'offline':
statusDot.className = 'status-dot offline';
statusText.textContent = 'Offline';
card.classList.add('offline');
break;
default:
statusDot.className = 'status-dot loading';
statusText.textContent = 'Unknown';
card.classList.add('unknown');
}
// Update uptime statistics
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>`);
}
if (uptimeHTML.length > 0) {
uptimeDisplay.innerHTML = uptimeHTML.join(' | ');
} else {
uptimeDisplay.textContent = 'No data yet';
}
}
// Update total checks
if (checksDisplay && service.total_checks !== undefined) {
checksDisplay.textContent = service.total_checks;
}
}
/**
* Show error message
*/
function showError(message) {
const errorDiv = document.createElement('div');
errorDiv.className = 'status-error';
errorDiv.textContent = message;
errorDiv.style.cssText = 'background: rgba(244, 67, 54, 0.2); color: #f44336; padding: 1em; margin: 1em 0; border-radius: 0.5em; text-align: center;';
const container = document.querySelector('.foregroundContent');
if (container) {
container.insertBefore(errorDiv, container.firstChild);
setTimeout(() => errorDiv.remove(), 5000);
}
}
/**
* Manual refresh
*/
function refreshStatus() {
const refreshBtn = document.getElementById('refreshBtn');
if (refreshBtn) {
refreshBtn.disabled = true;
refreshBtn.textContent = 'Checking...';
}
fetchStatus();
}
/**
* Initialize on page load
*/
function initStatusPage() {
fetchStatus();
// Auto-refresh every 5 minutes to get latest data
setInterval(fetchStatus, 300000);
}
// Start when page loads
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initStatusPage);
} else {
initStatusPage();
}

View File

@@ -0,0 +1,145 @@
{
"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
}
]
}
}
}