Merge pull request #6 from BlackRoad-OS/codex/update-blackroad-os-prism-console
Enhance console layout and health handling
This commit is contained in:
14
Dockerfile
14
Dockerfile
@@ -1,14 +1,22 @@
|
|||||||
FROM node:18-alpine AS base
|
FROM node:18-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:18-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
COPY --from=builder /app/.next ./.next
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/next.config.* ./
|
||||||
|
|
||||||
ENV PORT=8080
|
ENV PORT=8080
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD ["npm", "start"]
|
CMD ["npm", "start"]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ReactNode } from 'react';
|
|||||||
import AppShell from '@/components/layout/AppShell';
|
import AppShell from '@/components/layout/AppShell';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Prism Console',
|
title: 'BlackRoad OS – Prism Console',
|
||||||
description: 'Operational console for BlackRoad OS'
|
description: 'Operational console for BlackRoad OS'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,11 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="card" style={{ gridColumn: 'span 2' }}>
|
||||||
|
<h2>System Status</h2>
|
||||||
|
<p className="muted">Live and static readiness signals for the Prism Console.</p>
|
||||||
|
<div className="grid">
|
||||||
<LiveHealthCard />
|
<LiveHealthCard />
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h3>Configuration Snapshot</h3>
|
<h3>Configuration Snapshot</h3>
|
||||||
<p className="muted">Resolved URLs from server/public configuration.</p>
|
<p className="muted">Resolved URLs from server/public configuration.</p>
|
||||||
@@ -50,6 +53,23 @@ export default function Home() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<h3>Dependency Checklist</h3>
|
||||||
|
<p className="muted">Configuration readiness across Prism Console dependencies.</p>
|
||||||
|
<ul>
|
||||||
|
{serviceHealth.map((service) => (
|
||||||
|
<li key={service.key}>
|
||||||
|
{service.name}:{' '}
|
||||||
|
<span className={service.configured ? 'status-ok' : 'status-bad'}>
|
||||||
|
{service.configured ? 'Configured' : 'Missing'}
|
||||||
|
</span>{' '}
|
||||||
|
<span className="muted">{service.url || 'not set'}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="card" style={{ gridColumn: 'span 2' }}>
|
<div className="card" style={{ gridColumn: 'span 2' }}>
|
||||||
<h3>Services</h3>
|
<h3>Services</h3>
|
||||||
@@ -66,22 +86,6 @@ export default function Home() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="card">
|
|
||||||
<h3>System Status</h3>
|
|
||||||
<p className="muted">Configuration readiness across Prism Console dependencies.</p>
|
|
||||||
<ul>
|
|
||||||
{serviceHealth.map((service) => (
|
|
||||||
<li key={service.key}>
|
|
||||||
{service.name}:{' '}
|
|
||||||
<span className={service.configured ? 'status-ok' : 'status-bad'}>
|
|
||||||
{service.configured ? 'Configured' : 'Missing'}
|
|
||||||
</span>{' '}
|
|
||||||
<span className="muted">{service.url || 'not set'}</span>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h3>Operator Queue</h3>
|
<h3>Operator Queue</h3>
|
||||||
<p className="muted">Placeholder for pending operator tasks, incidents, or approvals.</p>
|
<p className="muted">Placeholder for pending operator tasks, incidents, or approvals.</p>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ type HealthResponse = {
|
|||||||
export function LiveHealthCard() {
|
export function LiveHealthCard() {
|
||||||
const [health, setHealth] = useState<HealthResponse | null>(null);
|
const [health, setHealth] = useState<HealthResponse | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [lastChecked, setLastChecked] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
@@ -31,10 +33,14 @@ export function LiveHealthCard() {
|
|||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setHealth(payload);
|
setHealth(payload);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setLastChecked(payload.ts);
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setError(err instanceof Error ? err.message : 'Unable to load health');
|
setError(err instanceof Error ? err.message : 'Unable to load health');
|
||||||
|
setLastChecked(new Date().toISOString());
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -47,14 +53,16 @@ export function LiveHealthCard() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const statusText = health?.ok ? 'ONLINE' : 'OFFLINE';
|
const statusText = loading ? 'CHECKING' : health?.ok ? 'ONLINE' : 'OFFLINE';
|
||||||
|
const statusClass = loading ? 'badge' : health?.ok ? 'badge' : 'status-bad';
|
||||||
|
const checkedAt = lastChecked;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h3>System Status</h3>
|
<h3>System Status</h3>
|
||||||
<p className="muted">Live ping against <code>/api/health</code> for {serviceConfig.SERVICE_NAME}.</p>
|
<p className="muted">Live ping against <code>/api/health</code> for {serviceConfig.SERVICE_NAME}.</p>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
<span className={health?.ok ? 'badge' : 'status-bad'}>Status: {statusText}</span>
|
<span className={statusClass}>Status: {statusText}</span>
|
||||||
<span className="muted">Service: {serviceConfig.SERVICE_ID}</span>
|
<span className="muted">Service: {serviceConfig.SERVICE_ID}</span>
|
||||||
</div>
|
</div>
|
||||||
{health && (
|
{health && (
|
||||||
@@ -64,6 +72,10 @@ export function LiveHealthCard() {
|
|||||||
<div className="muted">Last checked: {new Date(health.ts).toLocaleString()}</div>
|
<div className="muted">Last checked: {new Date(health.ts).toLocaleString()}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!health && loading && <p className="muted">Requesting live health...</p>}
|
||||||
|
{checkedAt && !health && !loading && (
|
||||||
|
<p className="muted">Last checked: {new Date(checkedAt).toLocaleString()}</p>
|
||||||
|
)}
|
||||||
{error && <p className="status-bad">{error}</p>}
|
{error && <p className="status-bad">{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user