Merge branch origin/codex/make-prism-console-deployable-on-railway into main

This commit is contained in:
Alexa Amundson
2025-11-22 12:29:24 -06:00
8 changed files with 34 additions and 241 deletions

View File

@@ -18,7 +18,9 @@ COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
# Railway provides PORT dynamically, but we can set a default # Railway provides PORT dynamically, but we can set a default
ENV PORT=3000 ENV PORT=8080
ENV HOST=0.0.0.0
ENV HOSTNAME=0.0.0.0
EXPOSE $PORT EXPOSE $PORT
CMD ["node", "server.js"] CMD ["node", "server.js"]

153
README.md
View File

@@ -1,148 +1,29 @@
# BlackRoad OS Prism Console # BlackRoad OS Prism Console
Operator / admin console for BlackRoad OS services. This frontend surfaces system health, environment metadata, and operator workflows for the broader BlackRoad OS platform. Operator / admin console for BlackRoad OS services. The app is a Next.js (App Router) frontend that surfaces environment metadata, service health, and operator workflows.
## Tech Stack ## Deployment Quick Reference (Railway)
- Next.js (App Router) - **Build**: `npm run build`
- React - **Start**: `npm start` (runs the standalone Next.js server with `HOST=0.0.0.0` and `PORT=${PORT:-8080}`)
- TypeScript - **Health**: `GET /health`
```json
## Getting Started {
Install dependencies and run the development server: "status": "ok",
"service": "prism-console"
```bash }
# blackroad-os-prism-console
Prism console for BlackRoad OS — environments, deployments, observability, admin views.
## Project Structure
```
blackroad-os-prism-console/
├── frontend/ # Next.js application
│ ├── app/ # Next.js App Router pages
│ │ ├── environments/ # Environments page
│ │ ├── deployments/ # Deployments page
│ │ └── logs/ # Logs page
│ └── components/ # Reusable React components
├── backend/ # Placeholder for future API services
└── infra/ # Infrastructure and environment configs
└── env.example # Environment configuration template
``` ```
## Getting Started ## Local Development
Install dependencies and run the dev server:
### Frontend Development
```bash ```bash
cd frontend
npm install npm install
npm run dev npm run dev
``` ```
Visit http://localhost:3000 by default. Visit http://localhost:3000 during development.
## Build & Start
Production build and runtime (default port 8080 for deployment targets like Railway):
```bash
npm run build
npm start
```
## Environment Variables
See `.env.example` for available variables. Key values:
- `OS_ROOT` base BlackRoad OS root URL
- `SERVICE_BASE_URL` public URL for this console
- `CORE_API_URL`, `AGENTS_API_URL` optional upstream APIs
## Health & Info
- `/api/health` health payload including service id and readiness
- `/api/info` static metadata about the Prism Console service
- `/api/version` version and environment snapshot
- `/api/debug-env` safe environment surface for troubleshooting
## Deployment (Railway)
This repository is configured for deployment on [Railway](https://railway.app) with the following setup:
### Automatic Configuration
The repository includes Railway configuration files:
- `railway.toml` Railway deployment configuration (recommended)
- `railway.json` Legacy Railway configuration (deprecated but supported)
- `nixpacks.toml` Nixpacks build configuration
### Required Environment Variables
Set these in your Railway service settings:
- `NODE_ENV=production` (automatically set by Railway)
- `PORT` (automatically provided by Railway)
### Optional Environment Variables
For full functionality, configure:
- `SERVICE_BASE_URL` Public URL of this console (e.g., `https://console.blackroad.systems`)
- `OS_ROOT` Base BlackRoad OS root URL (e.g., `https://blackroad.systems`)
- `CORE_API_URL` Core API endpoint (optional)
- `AGENTS_API_URL` Agents API endpoint (optional)
- `PUBLIC_CONSOLE_URL` Public console URL (optional)
- `NEXT_PUBLIC_OS_ROOT` Client-side OS root URL
- `NEXT_PUBLIC_SERVICE_ID=console`
- `NEXT_PUBLIC_SERVICE_NAME="BlackRoad OS Prism Console"`
### Deployment Details
- **Build Command**: Automatically detected from `package.json` (`npm run build`)
- **Start Command**: Automatically detected from `package.json` (`npm start`)
- **Health Check**: `/api/health` (configured in `railway.toml`)
- **Port**: Dynamically assigned by Railway via `$PORT` environment variable
### Manual Deployment
If deploying manually or with custom settings:
1. Build: `npm install && npm run build`
2. Start: `npm start` (uses standalone Next.js server)
3. Ensure `PORT` environment variable is set
### Healthcheck Response
The `/api/health` endpoint returns:
```json
{
"ok": true,
"service": "console",
"status": "ok",
"environment": "production",
"version": "1.0.0"
}
```
## Additional Notes
- Base URL: https://console.blackroad.systems
- OS Root: https://blackroad.systems
The application will be available at `http://localhost:3000`.
### Features
- **Environments**: Manage deployment environments
- **Deployments**: Track and control application deployments
- **Logs**: Monitor and analyze system logs
## Deployment
This project is designed to be deployed on:
- **Frontend**: Railway or Cloudflare Pages
- **Backend**: Railway (when implemented)
See `infra/env.example` for required environment variables.
## Development Roadmap
- [x] Basic Next.js frontend structure
- [x] Sidebar navigation
- [x] Placeholder pages for main sections
- [ ] Backend API implementation
- [ ] Database integration
- [ ] Authentication
- [ ] Deployment automation
- [ ] Cloudflare/Railway integration
## License
See [LICENSE](LICENSE) file for details.
## Project Notes
- Next.js 16 with the App Router and TypeScript.
- Production build outputs a standalone server (`.next/standalone`) suitable for Railway or container runtimes.
- Additional informational endpoints live under `/api` (e.g., `/api/health`, `/api/info`, `/api/version`).

View File

@@ -6,7 +6,7 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "node .next/standalone/server.js", "start": "HOST=0.0.0.0 PORT=${PORT:-8080} node .next/standalone/server.js",
"start:dev": "next start -p ${PORT:-3000}", "start:dev": "next start -p ${PORT:-3000}",
"lint": "next lint" "lint": "next lint"
}, },

View File

@@ -5,8 +5,8 @@
"command": "npm ci && npm run build" "command": "npm ci && npm run build"
}, },
"deploy": { "deploy": {
"startCommand": "HOSTNAME=0.0.0.0 PORT=8080 npm start", "startCommand": "npm start",
"healthcheckPath": "/api/health", "healthcheckPath": "/health",
"healthcheckTimeout": 100, "healthcheckTimeout": 100,
"restartPolicyType": "ON_FAILURE", "restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10 "restartPolicyMaxRetries": 10

View File

@@ -3,8 +3,8 @@ builder = "NIXPACKS"
buildCommand = "npm ci && npm run build" buildCommand = "npm ci && npm run build"
[deploy] [deploy]
startCommand = "HOSTNAME=0.0.0.0 PORT=8080 npm start" startCommand = "npm start"
healthcheckPath = "/api/health" healthcheckPath = "/health"
healthcheckTimeout = 100 healthcheckTimeout = 100
restartPolicyType = "ON_FAILURE" restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 10 restartPolicyMaxRetries = 10

View File

@@ -1,97 +0,0 @@
'use client';
import { useEffect, useState } from 'react';
import type { ServiceStatus } from '@/lib/config';
import { serviceConfig } from '@/config/serviceConfig';
type HealthResponse = {
ok: boolean;
status: string;
ts: string;
environment: string;
version?: string;
services: ServiceStatus[];
};
function badgeClass(status: ServiceStatus['status']) {
if (status === 'healthy') return 'status-ok';
if (status === 'not_configured') return 'status-bad';
return 'status-bad';
}
export default function HealthPage() {
const [health, setHealth] = useState<HealthResponse | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let cancelled = false;
const load = async () => {
try {
const response = await fetch('/api/health', { cache: 'no-store' });
if (!response.ok) {
throw new Error(`Health endpoint returned ${response.status}`);
}
const payload = (await response.json()) as HealthResponse;
if (!cancelled) {
setHealth(payload);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err instanceof Error ? err.message : 'Unable to load health');
}
}
};
load();
const interval = setInterval(load, 15000);
return () => {
cancelled = true;
clearInterval(interval);
};
}, []);
return (
<div className="grid">
<div className="card">
<h1>Health Check</h1>
<p className="muted">Live status from <code>/api/health</code> for {serviceConfig.SERVICE_NAME}.</p>
{health && (
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
<span className={health.ok ? 'badge' : 'status-bad'}>{health.ok ? 'OK' : 'Degraded'}</span>
<span className="muted">Env: {health.environment}</span>
<span className="muted">Version: {health.version}</span>
<span className="muted">Updated: {new Date(health.ts).toLocaleString()}</span>
</div>
)}
{error && <p className="status-bad">{error}</p>}
</div>
<div className="card">
<h3>Services</h3>
<p className="muted">Core, API Gateway, Agents, and the operator console.</p>
<div style={{ display: 'grid', gap: 8 }}>
{health?.services.map((service) => (
<div
key={service.key}
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<div>
<div>{service.name}</div>
<div className="muted">{service.url || 'not set'}</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className={badgeClass(service.status)}>
{service.status === 'healthy' && 'Healthy'}
{service.status === 'not_configured' && 'Not configured'}
{service.status === 'unreachable' && 'Unreachable'}
</span>
<span className="muted">{service.latencyMs ? `${service.latencyMs} ms` : '—'}</span>
</div>
</div>
)) || <p className="muted">No services configured.</p>}
</div>
</div>
</div>
);
}

8
src/app/health/route.ts Normal file
View File

@@ -0,0 +1,8 @@
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({
status: 'ok',
service: 'prism-console'
});
}

View File

@@ -10,7 +10,6 @@ type Props = {
const navLinks: { href: Route; label: string }[] = [ const navLinks: { href: Route; label: string }[] = [
{ href: '/', label: 'Overview' }, { href: '/', label: 'Overview' },
{ href: '/status', label: 'Status' }, { href: '/status', label: 'Status' },
{ href: '/health', label: 'Health' },
{ href: '/agents', label: 'Agents' } { href: '/agents', label: 'Agents' }
]; ];