Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ This starts:
- See a broader runtime map when host tools are available, including systemd,
cron, PM2, tmux, listening sockets, Tailscale or Headscale, reverse-proxy
markers, and local DNS markers.
- Use the Runtime Map workspace to inspect provider nodes, diagnostics, and
cross-provider edges in one read-only view.
- Compare what Compose says should exist with what Docker is actually running.
- Use mock fallback data when Docker is not available.

Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Navigate, Route, Routes } from "react-router-dom";
import AppShell from "./components/AppShell";
import Home from "./screens/Home";
import MapScreen from "./screens/Map";
import RuntimeScreen from "./screens/Runtime";
import ServiceDetail from "./screens/ServiceDetail";
import Changes from "./screens/Changes";
import Copilot from "./screens/Copilot";
Expand All @@ -28,6 +29,7 @@ export function App() {
<Route element={<AppShell />}>
<Route index element={<Landing />} />
<Route path="map" element={<MapScreen />} />
<Route path="runtime" element={<RuntimeScreen />} />
<Route path="services/:name" element={<ServiceDetail />} />
<Route path="changes" element={<Changes />} />
<Route path="copilot" element={<Copilot />} />
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const SPACES: { heading: string; items: NavItem[] }[] = [
items: [
{ to: "/", label: "Home", icon: "home", end: true },
{ to: "/map", label: "Service Map", icon: "map" },
{ to: "/runtime", label: "Runtime", icon: "layers" },
{ to: "/changes", label: "Changes", icon: "history" },
{ to: "/copilot", label: "Copilot", icon: "spark" }
]
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/CommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function CommandPalette({
const nav: Command[] = [
{ id: "nav-home", label: "Home — Command Center", icon: "home", group: "Navigate", run: () => go("/") },
{ id: "nav-map", label: "Service Map", icon: "map", group: "Navigate", run: () => go("/map") },
{ id: "nav-runtime", label: "Runtime Map", icon: "layers", group: "Navigate", run: () => go("/runtime") },
{ id: "nav-changes", label: "Change Center", icon: "history", group: "Navigate", run: () => go("/changes") },
{ id: "nav-copilot", label: "Copilot", icon: "spark", group: "Navigate", run: () => go("/copilot") },
{ id: "nav-net", label: "Networking", icon: "network", group: "Navigate", run: () => go("/networking") },
Expand Down
16 changes: 8 additions & 8 deletions apps/web/src/hooks/useSystemModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo } from "react";
import type { DockerSnapshot, GraphResponse } from "@dockermap/contracts";
import type { DockerSnapshot, RuntimeMap } from "@dockermap/contracts";
import { buildModel, type SystemModel } from "../lib/model";
import { useApiResource } from "./useApiResource";

Expand All @@ -9,19 +9,19 @@ export interface SystemModelState {
error: string | null;
}

/** Fetches the snapshot + graph and composes them into the domain model. */
/** Fetches the Docker snapshot + runtime map and composes them into the domain model. */
export function useSystemModel(refreshTick = 0): SystemModelState {
const snapshot = useApiResource<DockerSnapshot>("/api/snapshot", refreshTick);
const graph = useApiResource<GraphResponse>("/api/graph", refreshTick);
const runtimeMap = useApiResource<RuntimeMap>("/api/runtime/map", refreshTick);

const model = useMemo(() => {
if (!snapshot.data || !graph.data) return null;
return buildModel(snapshot.data, graph.data);
}, [snapshot.data, graph.data]);
if (!snapshot.data || !runtimeMap.data) return null;
return buildModel(snapshot.data, runtimeMap.data);
}, [snapshot.data, runtimeMap.data]);

return {
model,
loading: snapshot.loading || graph.loading,
error: snapshot.error ?? graph.error
loading: snapshot.loading || runtimeMap.loading,
error: snapshot.error ?? runtimeMap.error
};
}
176 changes: 176 additions & 0 deletions apps/web/src/lib/demoData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
ImageRecord,
LogsResponse,
NetworkRecord,
RuntimeMap,
VolumeRecord
} from "@dockermap/contracts";

Expand Down Expand Up @@ -224,6 +225,180 @@ const demoComposeScan: ComposeScan = {
]
};

const demoRuntimeMap: RuntimeMap = {
nodes: [
{
id: "runtime_gateway",
provider: "reverse_proxy",
type: "reverse_proxy",
layer: "edge",
label: "gateway",
status: "running",
metadata: {
config: "nginx.conf",
tls: true
},
service: {
name: "gateway",
status: "running",
dependencies: ["api"],
dependents: ["review-browser"],
health: { state: "healthy", source: "nginx", message: "Serving edge traffic" },
logs: [{ id: "runtime_gateway_log", source: "nginx", level: "info" }],
events: [{ id: "runtime_gateway_event", kind: "reload", message: "Proxy config reloaded" }],
owner: { kind: "team", name: "platform" },
location: { kind: "host", value: "demo-host" }
}
},
{
id: "runtime_api",
provider: "docker",
type: "container",
layer: "container",
label: "api",
status: "running",
metadata: {
image: "python:3.11-slim",
role: "api"
},
service: {
name: "api",
status: "running",
dependencies: ["postgres", "redis"],
dependents: ["gateway", "worker"],
health: { state: "healthy", source: "http", message: "Responding in 42ms" },
logs: [{ id: "runtime_api_log", source: "docker logs api", level: "info" }],
events: [{ id: "runtime_api_event", kind: "deploy", message: "API revision promoted" }],
owner: { kind: "team", name: "product" },
location: { kind: "container", value: "application" }
}
},
{
id: "runtime_worker",
provider: "process",
type: "worker",
layer: "process",
label: "worker",
status: "running",
metadata: {
pid: 2412,
command: "python worker.py"
},
service: {
name: "worker",
status: "running",
dependencies: ["api", "postgres"],
dependents: [],
health: { state: "degraded", source: "heartbeat", message: "Queue lag above target" },
logs: [{ id: "runtime_worker_log", source: "worker", level: "warn" }],
events: [{ id: "runtime_worker_event", kind: "lag", message: "Queue delay crossed 30 seconds" }],
owner: { kind: "team", name: "ops" },
location: { kind: "host", value: "demo-host" }
}
},
{
id: "runtime_systemd_api",
provider: "systemd",
type: "systemd_service",
layer: "host",
label: "dockermap-api.service",
status: "running",
metadata: {
unit: "dockermap-api.service",
restart: "always"
},
service: {
name: "dockermap-api.service",
status: "running",
dependencies: ["docker.service"],
dependents: ["gateway"],
health: { state: "healthy", source: "systemd", message: "Unit is active" },
logs: [],
events: [{ id: "runtime_systemd_event", kind: "start", message: "systemd marked the unit active" }],
owner: { kind: "system", name: "systemd" },
location: { kind: "host", value: "demo-host" }
}
},
{
id: "runtime_npm_forge",
provider: "npm",
type: "node_application",
layer: "package",
label: "forge-ui",
status: "running",
metadata: {
framework: "vite",
path: "/srv/forge"
},
package: {
name: "forge-ui",
manager: "npm",
version: "0.4.0",
dependencies: ["react", "vite"],
dependents: [],
update: {
currentVersion: "0.4.0",
latestVersion: null,
available: false,
advisories: []
},
owner: { kind: "team", name: "frontend" },
location: { kind: "path", value: "/srv/forge" }
}
},
{
id: "runtime_postgres",
provider: "docker",
type: "database",
layer: "container",
label: "postgres",
status: "running",
metadata: {
image: "postgres:16-alpine",
role: "database"
},
service: {
name: "postgres",
status: "running",
dependencies: ["postgres_data"],
dependents: ["api", "worker"],
health: { state: "healthy", source: "postgres", message: "Primary is ready" },
logs: [],
events: [{ id: "runtime_postgres_event", kind: "backup", message: "Nightly backup completed" }],
owner: { kind: "team", name: "platform" },
location: { kind: "container", value: "data" }
}
},
{
id: "runtime_postgres_data",
provider: "docker",
type: "docker_volume",
layer: "storage",
label: "postgres_data",
status: "attached",
metadata: {
driver: "local"
}
}
],
edges: [
{ source: "runtime_gateway", target: "runtime_api", relationship: "proxies_to", metadata: { port: 80 } },
{ source: "runtime_api", target: "runtime_postgres", relationship: "depends_on", metadata: { source: "compose" } },
{ source: "runtime_worker", target: "runtime_api", relationship: "calls", metadata: { queue: "jobs" } },
{ source: "runtime_worker", target: "runtime_postgres", relationship: "depends_on", metadata: { source: "runtime" } },
{ source: "runtime_postgres", target: "runtime_postgres_data", relationship: "mounts", metadata: { path: "/var/lib/postgresql/data" } },
{ source: "runtime_systemd_api", target: "runtime_gateway", relationship: "exposes", metadata: { unit: "dockermap-api.service" } }
],
diagnostics: [
{
provider: "process",
severity: "warning",
message: "Worker heartbeat is stale enough to warrant inspection."
}
],
lastUpdated: Date.now()
};

function demoLogs(service: string | null): LogsResponse {
const containers = service ? demoContainers.filter((c) => c.name === service) : demoContainers;
const now = Date.now();
Expand Down Expand Up @@ -255,6 +430,7 @@ export function getDemoResponse<T>(path: string): T {

if (pathname === "/api/snapshot") return demoSnapshot as T;
if (pathname === "/api/graph") return demoGraph as T;
if (pathname === "/api/runtime/map") return demoRuntimeMap as T;
if (pathname === "/api/health") {
return {
node: { status: "ok", port: 4000 },
Expand Down
Loading
Loading