Compare commits
1 Commits
step35/151
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 60e8b62a79 |
@@ -238,6 +238,21 @@
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.env-badge {
|
||||
font-size: 9px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
margin-left: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
}
|
||||
.env-badge.env_production { background: rgba(255, 68, 102, 0.15); color: var(--color-danger); }
|
||||
.env-badge.env_staging { background: rgba(255, 170, 34, 0.15); color: var(--color-warning); }
|
||||
.env-badge.env_local { background: rgba(74, 240, 192, 0.15); color: var(--color-primary); }
|
||||
.env-badge.env_unknown { background: rgba(138, 154, 184, 0.15); color: var(--color-text-muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -251,16 +266,11 @@
|
||||
<span style="font-size:11px;color:var(--color-text-muted)">LIVE</span>
|
||||
</div>
|
||||
|
||||
<div class="portal-grid">
|
||||
<div class="portal-grid" id="portal-grid">
|
||||
<!-- Populated dynamically from portals.json -->
|
||||
|
||||
<!-- Portal: Hermes -->
|
||||
<div class="portal-card status-online">
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">Hermes</div>
|
||||
<div class="portal-id">portal://hermes.nexus</div>
|
||||
</div>
|
||||
<span class="status-badge online">online</span>
|
||||
<span class="status-badge online">online</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
@@ -285,13 +295,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Portal: Archive -->
|
||||
<div class="portal-card status-online">
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">Archive</div>
|
||||
<div class="portal-id">portal://archive.nexus</div>
|
||||
</div>
|
||||
<span class="status-badge online">online</span>
|
||||
<span class="status-badge online">online</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
@@ -316,13 +320,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Portal: Satflow -->
|
||||
<div class="portal-card status-warning">
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">Satflow</div>
|
||||
<div class="portal-id">portal://satflow.nexus</div>
|
||||
</div>
|
||||
<span class="status-badge warning">degraded</span>
|
||||
<span class="status-badge warning">degraded</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
@@ -347,13 +345,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Portal: Evennia -->
|
||||
<div class="portal-card status-online">
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">Evennia</div>
|
||||
<div class="portal-id">portal://evennia.nexus</div>
|
||||
</div>
|
||||
<span class="status-badge online">online</span>
|
||||
<span class="status-badge online">online</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
@@ -378,13 +370,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Portal: Bannerlord -->
|
||||
<div class="portal-card status-offline">
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">Bannerlord</div>
|
||||
<div class="portal-id">portal://bannerlord.nexus</div>
|
||||
</div>
|
||||
<span class="status-badge offline">offline</span>
|
||||
<span class="status-badge offline">offline</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
@@ -409,13 +395,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Portal: OpenClaw -->
|
||||
<div class="portal-card status-locked">
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">OpenClaw</div>
|
||||
<div class="portal-id">portal://openclaw.nexus</div>
|
||||
</div>
|
||||
<span class="status-badge locked">locked</span>
|
||||
<span class="status-badge locked">locked</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
@@ -445,34 +425,156 @@
|
||||
<div class="summary-bar">
|
||||
<div class="summary-item">
|
||||
<div>
|
||||
<div class="summary-count" style="color:var(--color-primary)">4</div>
|
||||
<div class="summary-count online" data-default="0">0</div>
|
||||
<div class="summary-label">Online</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div>
|
||||
<div class="summary-count" style="color:var(--color-warning)">1</div>
|
||||
<div class="summary-count degraded" data-default="0">0</div>
|
||||
<div class="summary-label">Degraded</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div>
|
||||
<div class="summary-count" style="color:var(--color-danger)">1</div>
|
||||
<div class="summary-count offline" data-default="0">0</div>
|
||||
<div class="summary-label">Offline</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<div>
|
||||
<div class="summary-count" style="color:var(--color-secondary)">1</div>
|
||||
<div class="summary-count locked" data-default="0">0</div>
|
||||
<div class="summary-label">Locked</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left:auto;align-self:center;font-size:10px;color:var(--color-text-muted)">
|
||||
LAST SYNC: <span style="color:var(--color-text)">04:20:07 UTC</span>
|
||||
LAST SYNC: <span class="last-sync-time" style="color:var(--color-text)">--:--:-- UTC</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Portal Status Wall — dynamic renderer backed by portals.json
|
||||
// Fetches real portal data and renders environment-aware status cards.
|
||||
// Ref: #714
|
||||
|
||||
(async function() {
|
||||
const grid = document.querySelector('.portal-grid');
|
||||
if (!grid) {
|
||||
console.error('Portal grid container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('../portals.json');
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const portals = await resp.json();
|
||||
|
||||
// Clear any placeholder cards
|
||||
grid.innerHTML = '';
|
||||
|
||||
// Environment label mapping
|
||||
const envLabels = {production: 'PROD', staging: 'STAGING', local: 'LOCAL'};
|
||||
|
||||
// Status to CSS class and display label
|
||||
const statusConfig = {
|
||||
online: {cls: 'online', label: 'ONLINE'},
|
||||
offline: {cls: 'offline', label: 'OFFLINE'},
|
||||
warning: {cls: 'warning', label: 'DEGRADED'},
|
||||
locked: {cls: 'locked', label: 'LOCKED'},
|
||||
standby: {cls: 'warning', label: 'STANDBY'}
|
||||
};
|
||||
|
||||
// Render portal cards from portals.json
|
||||
for (const p of portals) {
|
||||
const env = (p.environment || 'unknown').toLowerCase();
|
||||
const statusKey = (p.status || 'offline').toLowerCase();
|
||||
const cfg = statusConfig[statusKey] || {cls: 'offline', label: statusKey.toUpperCase()};
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = `portal-card status-${cfg.cls}`;
|
||||
card.innerHTML = `
|
||||
<div class="portal-header">
|
||||
<div>
|
||||
<div class="portal-name">${escHtml(p.name || p.id)}</div>
|
||||
<div class="portal-id">portal://${escHtml(p.id)}</div>
|
||||
</div>
|
||||
<span class="status-badge ${cfg.cls}">${cfg.label}</span>
|
||||
</div>
|
||||
<div class="portal-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Type</span>
|
||||
<span class="meta-value">${escHtml(p.portal_type || 'world')}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Category</span>
|
||||
<span class="meta-value">${escHtml(p.world_category || '-')}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Env</span>
|
||||
<span class="meta-value env-badge env_${env}">${envLabels[env] || env.toUpperCase()}</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">Readiness</span>
|
||||
<span class="meta-value">${escHtml(p.readiness_state || 'unknown')}</span>
|
||||
</div>
|
||||
${p.blocked_reason ? `
|
||||
<div class="meta-row" style="color:var(--color-warning)">
|
||||
<span class="meta-label">Blocked</span>
|
||||
<span class="meta-value">${escHtml(p.blocked_reason)}</span>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
}
|
||||
|
||||
// Update summary bar counts
|
||||
updateSummary(portals);
|
||||
|
||||
// Update last sync time
|
||||
const timeEl = document.querySelector('.last-sync-time');
|
||||
if (timeEl) {
|
||||
const now = new Date();
|
||||
timeEl.textContent = now.toISOString().slice(11,19) + ' UTC';
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error('Failed to load portal status:', e);
|
||||
grid.innerHTML = '<div style="color:var(--color-danger);padding:12px;">Portal registry offline — unable to load portals.json</div>';
|
||||
}
|
||||
|
||||
// Helper: escape HTML
|
||||
function escHtml(s) {
|
||||
return String(s || '').replace(/[&<>"']/g, c => ({
|
||||
'&':'&', '<':'<', '>':'>', '"':'"', "'":'''
|
||||
})[c]);
|
||||
}
|
||||
|
||||
// Helper: update summary counts
|
||||
function updateSummary(portals) {
|
||||
const counts = {online:0, degraded:0, offline:0, locked:0};
|
||||
for (const p of portals) {
|
||||
const st = (p.status || '').toLowerCase();
|
||||
if (st === 'online') counts.online++;
|
||||
else if (st === 'offline') counts.offline++;
|
||||
else if (st === 'warning' || st === 'degraded') counts.degraded++;
|
||||
else if (st === 'locked') counts.locked++;
|
||||
else counts.offline++;
|
||||
}
|
||||
const map = {
|
||||
'online': '.summary-count.online',
|
||||
'degraded': '.summary-count.degraded',
|
||||
'offline': '.summary-count.offline',
|
||||
'locked': '.summary-count.locked'
|
||||
};
|
||||
for (const [key, selector] of Object.entries(map)) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.textContent = counts[key];
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user