mirror of
https://github.com/asimonson1125/asimonson1125.github.io.git
synced 2026-02-25 05:09:49 -06:00
sample status page
This commit is contained in:
@@ -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
180
src/static/js/status.js
Normal 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();
|
||||
}
|
||||
145
src/static/json/status_history.json
Normal file
145
src/static/json/status_history.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user