Merge branch origin/codex/make-prism-console-deployable-on-railway into main
This commit is contained in:
@@ -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
153
README.md
@@ -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`).
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
8
src/app/health/route.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
return NextResponse.json({
|
||||||
|
status: 'ok',
|
||||||
|
service: 'prism-console'
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user