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
39 changes: 39 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Frontend CI

on:
push:
paths:
- 'frontend/**'
- '.github/workflows/frontend.yml'
pull_request:
paths:
- 'frontend/**'
- '.github/workflows/frontend.yml'

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Lint
run: npm run lint

- name: Test
run: npm run test

- name: Build
run: npm run build
24 changes: 24 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
8 changes: 8 additions & 0 deletions frontend/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": ["warn", { "allowConstantExport": true }]
}
}
8 changes: 8 additions & 0 deletions frontend/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always"
}
80 changes: 80 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# spanLedger Frontend

Dark, calm, surgical UI for the spanLedger reliability pipeline auditor.

## Prerequisites

- Node.js ≥ 18
- Python ≥ 3.11 (to run the backend)

## Development

### 1. Start the backend

```sh
# From the repo root
python -m spanledger run --config demo/spanledger.demo.yaml
```

The backend serves the API at `http://localhost:8231`.

### 2. Start the frontend dev server

```sh
# From this directory (frontend/)
npm install
npm run dev
```

The dev server runs at `http://localhost:5173` and proxies all `/api`, `/status`, `/findings`, and `/healthz` requests to the backend — no CORS configuration required.

## Available Scripts

| Command | Description |
|---------|-------------|
| `npm run dev` | Start Vite dev server with HMR |
| `npm run build` | TypeScript check + production bundle |
| `npm run preview` | Serve the production build locally |
| `npm run test` | Run all unit tests (vitest) |
| `npm run lint` | ESLint + Prettier check |
| `npm run lint:fix` | Auto-fix lint and format issues |

## Simulated Data Mode

To run without a backend (e.g., for demo rehearsal):

```sh
npm run dev -- -- --mode mock
# or navigate to: http://localhost:5173/?data=sim
```

A **SIMULATED DATA** badge will appear in the top bar. The UI never presents simulated data without this label.

## Architecture

See [`docs/frontend/FRONTEND_ARCHITECTURE.md`](../docs/frontend/FRONTEND_ARCHITECTURE.md) for the full design.

Key points:
- **Framework**: Vite 5 + React 18 + TypeScript (strict)
- **Styling**: Tailwind CSS v3 + CSS custom properties (design tokens)
- **Server state**: TanStack Query v5
- **Routing**: react-router-dom v6
- **Charts**: Recharts
- **Icons**: lucide-react

## Folder Structure

```
src/
api/ API client, types, normalizer, query hooks, mock client
components/
layout/ RootLayout, Sidebar, TopBar, ConnectionBanner, PageHeader
ui/ Card, Badge, Button, Select, Table, Skeleton, Toast, ...
slo/ BudgetGauge, BurnRateBars, SliStat, ...
events/ EventRow, EventClassBadge, GapRunChart, ...
charts/ SliHistoryChart, TimelineAxis, ...
lib/ Pure utilities: time, format, slo, signoz-links, settings
pages/ One directory per route, page-local components
providers/ UiProvider, DemoProvider
theme/ tokens.css — the single source of truth for all design tokens
```
75 changes: 75 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import js from '@eslint/js'
import tsParser from '@typescript-eslint/parser'
import tsPlugin from '@typescript-eslint/eslint-plugin'
import reactPlugin from 'eslint-plugin-react'
import reactHooksPlugin from 'eslint-plugin-react-hooks'

export default [
js.configs.recommended,
{
files: ['src/**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
globals: {
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
localStorage: 'readonly',
fetch: 'readonly',
URL: 'readonly',
AbortController: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
clearInterval: 'readonly',
Response: 'readonly',
MouseEvent: 'readonly',
KeyboardEvent: 'readonly',
Node: 'readonly',
HTMLDivElement: 'readonly',
HTMLButtonElement: 'readonly',
HTMLInputElement: 'readonly',
HTMLTextAreaElement: 'readonly',
SVGPathElement: 'readonly',
MediaQueryListEvent: 'readonly',
URLSearchParams: 'readonly',
React: 'readonly',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
react: reactPlugin,
'react-hooks': reactHooksPlugin,
},
rules: {
...tsPlugin.configs.recommended.rules,
...reactPlugin.configs.recommended.rules,
...reactHooksPlugin.configs.recommended.rules,
// No hex colors in src/ outside theme/ (design system rule)
'no-restricted-syntax': [
'error',
{
selector: 'Literal[value=/#[0-9a-fA-F]{3,8}/]',
message:
'No hardcoded hex colors in src/ outside theme/tokens.css. Use CSS variables.',
},
],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
},
settings: {
react: { version: 'detect' },
},
},
{
ignores: ['dist/', 'node_modules/', '*.config.js', '*.config.ts'],
},
]
24 changes: 24 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>spanLedger — Reliability Pipeline Auditor</title>
<meta
name="description"
content="spanLedger monitors your OpenTelemetry pipeline for probe delivery, budget burn, and SLO health — with forensic-grade incident reporting."
/>
<!-- Preload fonts (self-hosted woff2 — no CDN dependency) -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading