diff --git a/.env.example b/.env.example index b339e15..0c62165 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ -ORBITALWARD_ENV=development -ORBITALWARD_SECRET_KEY=change-me -DATABASE_URL=postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward +ORBITWARD_ENV=development +ORBITWARD_SECRET_KEY=change-me +DATABASE_URL=postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward REDIS_URL=redis://redis:6379/0 FRONTEND_URL=http://localhost:5173 BACKEND_URL=http://localhost:8000 diff --git a/AGENTS.md b/AGENTS.md index c61c43d..f98f310 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ -# OrbitalWard Agent Notes +# OrbitWard Agent Notes -OrbitalWard should be built incrementally. Keep the first release focused on a secure monitoring appliance with guided setup, website monitoring, alerts, and notifications. +OrbitWard should be built incrementally. Keep the first release focused on a secure monitoring appliance with guided setup, website monitoring, alerts, and notifications. ## Product Guardrails diff --git a/LICENSE b/LICENSE index 668210d..fd3efe7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 OrbitalWard contributors +Copyright (c) 2026 OrbitWard contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 03909b1..32ddbc7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# OrbitalWard +# OrbitWard Beautiful, self-hosted infrastructure monitoring without the enterprise-tool headache. -OrbitalWard is an open-source monitoring appliance for homelabs, small businesses, and internal IT teams. The first target is a secure and attractive monitoring foundation with guided setup, website checks, alerts, notifications, and clean dashboards. It is not trying to be a full Zabbix or LibreNMS replacement in the first release. +OrbitWard is an open-source monitoring appliance for homelabs, small businesses, and internal IT teams. The first target is a secure and attractive monitoring foundation with guided setup, website checks, alerts, notifications, and clean dashboards. It is not trying to be a full Zabbix or LibreNMS replacement in the first release. ## Current Status @@ -57,7 +57,7 @@ Default local admin credentials come from `.env`: - `INITIAL_ADMIN_EMAIL=admin@example.com` - `INITIAL_ADMIN_PASSWORD=change-me` -Change these values before using OrbitalWard anywhere beyond local development. +Change these values before using OrbitWard anywhere beyond local development. ## Project Structure diff --git a/backend/alembic.ini b/backend/alembic.ini index 6d59d28..ad19032 100644 --- a/backend/alembic.ini +++ b/backend/alembic.ini @@ -2,7 +2,7 @@ script_location = alembic prepend_sys_path = . -sqlalchemy.url = postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward +sqlalchemy.url = postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward [loggers] keys = root,sqlalchemy,alembic diff --git a/backend/alembic/versions/20260522_0001_initial_schema.py b/backend/alembic/versions/20260522_0001_initial_schema.py index 64897f6..f7c8c0c 100644 --- a/backend/alembic/versions/20260522_0001_initial_schema.py +++ b/backend/alembic/versions/20260522_0001_initial_schema.py @@ -1,4 +1,4 @@ -"""Initial OrbitalWard schema. +"""Initial OrbitWard schema. Revision ID: 20260522_0001 Revises: diff --git a/backend/app/__init__.py b/backend/app/__init__.py index f399ca0..79435b7 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1 +1 @@ -"""OrbitalWard backend package.""" +"""OrbitWard backend package.""" diff --git a/backend/app/api/health.py b/backend/app/api/health.py index 5aaafd9..f23ad6b 100644 --- a/backend/app/api/health.py +++ b/backend/app/api/health.py @@ -5,4 +5,4 @@ router = APIRouter(tags=["health"]) @router.get("/health") def health() -> dict[str, str]: - return {"status": "ok", "service": "orbitalward-backend"} + return {"status": "ok", "service": "orbitward-backend"} diff --git a/backend/app/api/notifications.py b/backend/app/api/notifications.py index 4866741..1cf4fe7 100644 --- a/backend/app/api/notifications.py +++ b/backend/app/api/notifications.py @@ -14,7 +14,7 @@ router = APIRouter(prefix="/notifications/channels", tags=["notifications"]) def _channel_to_read(channel: NotificationChannel) -> NotificationChannelRead: settings = dict(channel.settings or {}) - settings.setdefault("username", "OrbitalWard") + settings.setdefault("username", "OrbitWard") return NotificationChannelRead( id=channel.id, name=channel.name, @@ -40,7 +40,7 @@ def create_channel( db: Session = Depends(get_db), ) -> NotificationChannelRead: channel_settings = dict(payload.settings or {}) - channel_settings.setdefault("username", "OrbitalWard") + channel_settings.setdefault("username", "OrbitWard") channel = NotificationChannel( name=payload.name, channel_type=payload.channel_type, @@ -70,8 +70,8 @@ def test_channel( response = httpx.post( url, json={ - "username": (channel.settings or {}).get("username") or "OrbitalWard", - "text": f"OrbitalWard test notification for {channel.name}", + "username": (channel.settings or {}).get("username") or "OrbitWard", + "text": f"OrbitWard test notification for {channel.name}", }, timeout=10, ) diff --git a/backend/app/auth/security.py b/backend/app/auth/security.py index e3d714b..c9c3691 100644 --- a/backend/app/auth/security.py +++ b/backend/app/auth/security.py @@ -20,12 +20,12 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: def create_access_token(subject: str) -> str: expires_at = datetime.now(UTC) + timedelta(minutes=settings.access_token_expire_minutes) payload = {"sub": subject, "exp": expires_at} - return jwt.encode(payload, settings.orbitalward_secret_key, algorithm=ALGORITHM) + return jwt.encode(payload, settings.orbitward_secret_key, algorithm=ALGORITHM) def decode_access_token(token: str) -> str | None: try: - payload = jwt.decode(token, settings.orbitalward_secret_key, algorithms=[ALGORITHM]) + payload = jwt.decode(token, settings.orbitward_secret_key, algorithms=[ALGORITHM]) return payload.get("sub") except JWTError: return None diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 70b125d..01729af 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -1,15 +1,19 @@ from functools import lru_cache -from pydantic import AnyHttpUrl, Field +from pydantic import AliasChoices, AnyHttpUrl, Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") - orbitalward_env: str = "development" - orbitalward_secret_key: str = Field(default="change-me", min_length=8) - database_url: str = "postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward" + orbitward_env: str = Field(default="development", validation_alias=AliasChoices("ORBITWARD_ENV", "ORBITALWARD_ENV")) + orbitward_secret_key: str = Field( + default="change-me", + min_length=8, + validation_alias=AliasChoices("ORBITWARD_SECRET_KEY", "ORBITALWARD_SECRET_KEY"), + ) + database_url: str = "postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward" redis_url: str = "redis://redis:6379/0" frontend_url: AnyHttpUrl | str = "http://localhost:5173" backend_url: AnyHttpUrl | str = "http://localhost:8000" diff --git a/backend/app/core/secrets.py b/backend/app/core/secrets.py index faf668d..92cd7a0 100644 --- a/backend/app/core/secrets.py +++ b/backend/app/core/secrets.py @@ -7,7 +7,7 @@ from app.core.config import settings def _fernet() -> Fernet: - digest = hashlib.sha256(settings.orbitalward_secret_key.encode("utf-8")).digest() + digest = hashlib.sha256(settings.orbitward_secret_key.encode("utf-8")).digest() return Fernet(base64.urlsafe_b64encode(digest)) diff --git a/backend/app/main.py b/backend/app/main.py index d08a4e0..a7a719a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -25,7 +25,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: app = FastAPI( - title="OrbitalWard API", + title="OrbitWard API", version="0.1.0", description="Self-hosted infrastructure monitoring API", lifespan=lifespan, diff --git a/backend/pyproject.toml b/backend/pyproject.toml index a1ec77d..254cea3 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "orbitalward-backend" +name = "orbitward-backend" version = "0.1.0" -description = "OrbitalWard FastAPI backend" +description = "OrbitWard FastAPI backend" requires-python = ">=3.12" dependencies = [ "alembic>=1.13.3", diff --git a/backend/tests/test_notifications.py b/backend/tests/test_notifications.py index 4e50e97..4595a4d 100644 --- a/backend/tests/test_notifications.py +++ b/backend/tests/test_notifications.py @@ -11,8 +11,8 @@ def test_notification_channel_does_not_return_saved_secret(client: TestClient, d json={ "name": "Operations Webhook", "channel_type": "generic_webhook", - "settings": {"username": "OrbitalWard"}, - "secret": "https://hooks.example.test/orbitalward", + "settings": {"username": "OrbitWard"}, + "secret": "https://hooks.example.test/orbitward", "is_enabled": True, }, ) @@ -25,8 +25,8 @@ def test_notification_channel_does_not_return_saved_secret(client: TestClient, d channel = db_session.get(NotificationChannel, body["id"]) assert channel is not None - assert channel.encrypted_secret != "https://hooks.example.test/orbitalward" - assert decrypt_secret(channel.encrypted_secret) == "https://hooks.example.test/orbitalward" + assert channel.encrypted_secret != "https://hooks.example.test/orbitward" + assert decrypt_secret(channel.encrypted_secret) == "https://hooks.example.test/orbitward" list_response = client.get("/notifications/channels") assert list_response.status_code == 200 @@ -42,7 +42,7 @@ def test_notification_channel_update_without_secret_preserves_existing_secret(cl json={ "name": "Mattermost", "channel_type": "mattermost", - "settings": {"username": "OrbitalWard"}, + "settings": {"username": "OrbitWard"}, "secret": "https://hooks.example.test/mattermost", "is_enabled": True, }, @@ -54,7 +54,7 @@ def test_notification_channel_update_without_secret_preserves_existing_secret(cl f"/notifications/channels/{channel_id}", json={ "name": "Mattermost Alerts", - "settings": {"username": "OrbitalWard Alerts"}, + "settings": {"username": "OrbitWard Alerts"}, "is_enabled": False, }, ) @@ -62,7 +62,7 @@ def test_notification_channel_update_without_secret_preserves_existing_secret(cl assert update_response.status_code == 200 body = update_response.json() assert body["name"] == "Mattermost Alerts" - assert body["settings"]["username"] == "OrbitalWard Alerts" + assert body["settings"]["username"] == "OrbitWard Alerts" assert body["is_enabled"] is False assert body["has_secret"] is True assert "secret" not in body diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 2a6350d..8bda8ec 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,14 +1,16 @@ +name: orbitward + services: postgres: image: postgres:16-alpine environment: - POSTGRES_USER: orbitalward - POSTGRES_PASSWORD: orbitalward - POSTGRES_DB: orbitalward + POSTGRES_USER: orbitward + POSTGRES_PASSWORD: orbitward + POSTGRES_DB: orbitward volumes: - postgres-dev-data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U orbitalward -d orbitalward"] + test: ["CMD-SHELL", "pg_isready -U orbitward -d orbitward"] interval: 10s timeout: 5s retries: 5 diff --git a/docker-compose.yml b/docker-compose.yml index 067d26a..c69de60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,16 @@ +name: orbitward + services: postgres: image: postgres:16-alpine environment: - POSTGRES_USER: orbitalward - POSTGRES_PASSWORD: orbitalward - POSTGRES_DB: orbitalward + POSTGRES_USER: orbitward + POSTGRES_PASSWORD: orbitward + POSTGRES_DB: orbitward volumes: - postgres-data:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U orbitalward -d orbitalward"] + test: ["CMD-SHELL", "pg_isready -U orbitward -d orbitward"] interval: 10s timeout: 5s retries: 5 diff --git a/docs/agent-handoff.md b/docs/agent-handoff.md index 2928552..2293dd5 100644 --- a/docs/agent-handoff.md +++ b/docs/agent-handoff.md @@ -4,9 +4,9 @@ Last updated: 2026-05-26 ## Current Identity -- Product name: OrbitalWard -- Local repository path: `/home/ksmith/projects/OrbitalWard` -- Git remote: `https://git.firebugit.com/ksmith/OrbitalWard.git` +- Product name: OrbitWard +- Local repository path: `/home/ksmith/projects/OrbitalWard` until the working directory is moved +- Git remote: `https://git.firebugit.com/ksmith/OrbitWard.git` - Main branch: `main` - Latest pushed commit: check `origin/main` with `git log -1 --oneline origin/main` @@ -15,14 +15,14 @@ The project was previously named InfraPulse. Do not reintroduce the old name in ## Gitea Access - Gitea API base: `https://git.firebugit.com/api/v1` -- Repository API path: `/repos/ksmith/OrbitalWard` +- Repository API path: `/repos/ksmith/OrbitWard` - Access token file: `/home/ksmith/.codex_security/gitea_token` Never print the token value. Read it only inside commands that call the Gitea API. ## Current Product State -OrbitalWard is a secure monitoring appliance focused on the v0.1 vertical slice: +OrbitWard is a secure monitoring appliance focused on the v0.1 vertical slice: - Authenticated FastAPI backend with SQLAlchemy, Alembic, Pydantic, and JWT auth. - React, TypeScript, Vite, and Tailwind frontend. @@ -55,18 +55,18 @@ Recent Docker checks: Earlier rename and monitor work also verified: - `docker compose -f docker-compose.dev.yml up -d --build` -- Backend health returned `{"status":"ok","service":"orbitalward-backend"}`. +- Backend health returned `{"status":"ok","service":"orbitward-backend"}`. - Direct worker probes for TCP and ICMP ping checks passed inside the Docker network. - API probe created and deleted one ping monitor and one TCP monitor successfully. -The final Compose project uses `orbitalward-*` containers, images, network, and volumes. +The final Compose project uses `orbitward-*` containers, images, network, and volumes. ## Important Implementation Notes -- `ORBITALWARD_SECRET_KEY` is the encryption/JWT secret environment variable. -- `DATABASE_URL` now defaults to the `orbitalward` database/user in Compose. -- The frontend local storage key is `orbitalward_token`. -- Notification default username is `OrbitalWard`. +- `ORBITWARD_SECRET_KEY` is the encryption/JWT secret environment variable. +- `DATABASE_URL` now defaults to the `orbitward` database/user in Compose. +- The frontend local storage key is `orbitward_token`. +- Notification default username is `OrbitWard`. - The TLS expiry check lives in `worker/app/collectors/website.py` and is enabled per monitor through JSON config fields: - `check_tls_expiry` - `tls_warning_days` @@ -75,11 +75,11 @@ The final Compose project uses `orbitalward-*` containers, images, network, and Use the Gitea API with the token file above. Useful endpoints: -- List issues: `GET /repos/ksmith/OrbitalWard/issues?state=all` -- Create issue: `POST /repos/ksmith/OrbitalWard/issues` -- Update issue: `PATCH /repos/ksmith/OrbitalWard/issues/{index}` -- List milestones: `GET /repos/ksmith/OrbitalWard/milestones` -- List labels: `GET /repos/ksmith/OrbitalWard/labels` +- List issues: `GET /repos/ksmith/OrbitWard/issues?state=all` +- Create issue: `POST /repos/ksmith/OrbitWard/issues` +- Update issue: `PATCH /repos/ksmith/OrbitWard/issues/{index}` +- List milestones: `GET /repos/ksmith/OrbitWard/milestones` +- List labels: `GET /repos/ksmith/OrbitWard/labels` Issue source docs: diff --git a/docs/alerting-design.md b/docs/alerting-design.md index 95ae0c0..cd861ef 100644 --- a/docs/alerting-design.md +++ b/docs/alerting-design.md @@ -26,4 +26,4 @@ Initial channels: - Zoom Team Chat incoming webhook - Generic webhook -Alert messages should be human-readable and include asset, check, status, duration, timestamps, and a link back to OrbitalWard. +Alert messages should be human-readable and include asset, check, status, duration, timestamps, and a link back to OrbitWard. diff --git a/docs/architecture.md b/docs/architecture.md index 0ab5688..0887884 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,6 +1,6 @@ # Architecture -OrbitalWard is a monorepo with four main areas: +OrbitWard is a monorepo with four main areas: - `backend`: FastAPI service exposing REST endpoints and owning database access. - `worker`: Background scheduler and collectors for checks and alert evaluation. diff --git a/docs/discovery-design.md b/docs/discovery-design.md index 49e39ae..9835d56 100644 --- a/docs/discovery-design.md +++ b/docs/discovery-design.md @@ -1,6 +1,6 @@ # Discovery Design -Guided discovery is a core OrbitalWard workflow. +Guided discovery is a core OrbitWard workflow. ```text Add target @@ -16,7 +16,7 @@ Create monitors and optional alert rules ## Monitor vs Alert Separation -OrbitalWard must allow monitoring without alerting. Every discovered item should eventually support separate choices: +OrbitWard must allow monitoring without alerting. Every discovered item should eventually support separate choices: - Collect metric - Graph metric diff --git a/docs/gitea-issues.md b/docs/gitea-issues.md index 908738e..59eddfd 100644 --- a/docs/gitea-issues.md +++ b/docs/gitea-issues.md @@ -58,7 +58,7 @@ 44. Show and graph SNMP interface throughput 45. Build asset detail UI for monitors, metrics, and context 46. Refine metric-only monitor status semantics -47. Rename product from OrbitalWard to OrbitWard +47. Rename product to OrbitWard ## Current Implementation Snapshot diff --git a/docs/plugin-design.md b/docs/plugin-design.md index 5b92e76..0abab38 100644 --- a/docs/plugin-design.md +++ b/docs/plugin-design.md @@ -1,11 +1,11 @@ # Plugin Design -Plugins will let OrbitalWard add collectors and discovery logic without hard-coding every integration into the core API. +Plugins will let OrbitWard add collectors and discovery logic without hard-coding every integration into the core API. Target shape: ```python -class OrbitalWardPlugin: +class OrbitWardPlugin: name: str display_name: str diff --git a/docs/progress.md b/docs/progress.md index b4b24cf..429f188 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -1,10 +1,10 @@ -# OrbitalWard Progress +# OrbitWard Progress Last updated: 2026-05-26 ## Current State -OrbitalWard has a working Docker Compose development stack with PostgreSQL, Redis, FastAPI backend, Python worker, and React/Vite frontend. +OrbitWard has a working Docker Compose development stack with PostgreSQL, Redis, FastAPI backend, Python worker, and React/Vite frontend. Implemented foundation: @@ -35,7 +35,7 @@ Implemented notification slice: - Create, edit, test, and delete notification channels from the UI. - Generic webhook, Mattermost, and Zoom Team Chat channel types. -- Webhook URLs encrypted at rest using `ORBITALWARD_SECRET_KEY`. +- Webhook URLs encrypted at rest using `ORBITWARD_SECRET_KEY`. - Saved webhook URLs are not returned to the UI. - Configurable post username per notification channel. - Worker sends incident open and recovery notifications. @@ -148,4 +148,4 @@ Default local login comes from `.env`: - `INITIAL_ADMIN_EMAIL=admin@example.com` - `INITIAL_ADMIN_PASSWORD=change-me` -Change these values before using OrbitalWard outside local development. +Change these values before using OrbitWard outside local development. diff --git a/docs/security.md b/docs/security.md index 0ecb9df..81edf17 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1,6 +1,6 @@ # Security -OrbitalWard must be secure from the beginning because it will store infrastructure credentials. +OrbitWard must be secure from the beginning because it will store infrastructure credentials. ## Authentication @@ -19,7 +19,7 @@ Credential records are modeled separately from monitors and assets. Secret field Rules: -- Use `ORBITALWARD_SECRET_KEY` from the environment. +- Use `ORBITWARD_SECRET_KEY` from the environment. - Never log secrets. - Mask saved secrets in the UI. - Audit credential create, update, and delete events. diff --git a/docs/vision.md b/docs/vision.md index d78e4ea..1d6c056 100644 --- a/docs/vision.md +++ b/docs/vision.md @@ -1,12 +1,12 @@ -# OrbitalWard Vision +# OrbitWard Vision -OrbitalWard is a secure, self-hosted monitoring platform for homelabs, small businesses, and internal IT teams. +OrbitWard is a secure, self-hosted monitoring platform for homelabs, small businesses, and internal IT teams. The v0.1 product should feel like a polished appliance, not a pile of raw monitoring configuration. Users should be guided through adding targets, testing connections, discovering useful items, choosing what to monitor, and separately choosing what should alert. ## Design Philosophy -OrbitalWard exposes intent, not implementation details. +OrbitWard exposes intent, not implementation details. Raw SNMP OIDs, probe internals, and collector details belong behind friendly profiles and advanced tools. The normal UI should say things like "Port 5 outbound traffic", "Graph this port", and "Alert if port goes down". diff --git a/frontend/index.html b/frontend/index.html index de63a13..46fa01d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,7 +3,7 @@ - OrbitalWard + OrbitWard
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6a980b8..13ca30c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,11 +1,11 @@ { - "name": "orbitalward-frontend", + "name": "orbitward-frontend", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "orbitalward-frontend", + "name": "orbitward-frontend", "version": "0.1.0", "dependencies": { "@vitejs/plugin-react": "^5.0.0", diff --git a/frontend/package.json b/frontend/package.json index e275fd7..4d3fac7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "orbitalward-frontend", + "name": "orbitward-frontend", "version": "0.1.0", "private": true, "type": "module", diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index 9583743..bea54df 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -65,7 +65,7 @@ export function App() { } if (auth.loading) { - return
Loading OrbitalWard...
; + return
Loading OrbitWard...
; } if (!auth.user || !auth.token) { diff --git a/frontend/src/components/Shell.tsx b/frontend/src/components/Shell.tsx index 40c9d60..064bbd7 100644 --- a/frontend/src/components/Shell.tsx +++ b/frontend/src/components/Shell.tsx @@ -49,7 +49,7 @@ export function Shell({ children, currentPage, onPageChange, onSignOut, user }:
-
OrbitalWard
+
OrbitWard
Monitoring appliance
diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index e2bec77..793ac50 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -3,10 +3,20 @@ import { useEffect, useMemo, useState } from "react"; import { api, login } from "../api/client"; import type { User } from "../types/api"; -const TOKEN_KEY = "orbitalward_token"; +const TOKEN_KEY = "orbitward_token"; +const LEGACY_TOKEN_KEY = "orbitalward_token"; export function useAuth() { - const [token, setToken] = useState(() => localStorage.getItem(TOKEN_KEY)); + const [token, setToken] = useState(() => { + const currentToken = localStorage.getItem(TOKEN_KEY); + if (currentToken) return currentToken; + const legacyToken = localStorage.getItem(LEGACY_TOKEN_KEY); + if (legacyToken) { + localStorage.setItem(TOKEN_KEY, legacyToken); + localStorage.removeItem(LEGACY_TOKEN_KEY); + } + return legacyToken; + }); const [user, setUser] = useState(null); const [loading, setLoading] = useState(Boolean(token)); @@ -26,6 +36,7 @@ export function useAuth() { }) .catch(() => { localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(LEGACY_TOKEN_KEY); if (!cancelled) { setToken(null); setUser(null); @@ -52,6 +63,7 @@ export function useAuth() { }, signOut: () => { localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(LEGACY_TOKEN_KEY); setToken(null); setUser(null); }, diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index d727771..0e4547d 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -33,7 +33,7 @@ export function LoginPage({ onLogin }: LoginPageProps) {
-
OrbitalWard
+
OrbitWard

diff --git a/frontend/src/pages/NotificationsPage.tsx b/frontend/src/pages/NotificationsPage.tsx index d96ebbd..9d57565 100644 --- a/frontend/src/pages/NotificationsPage.tsx +++ b/frontend/src/pages/NotificationsPage.tsx @@ -14,7 +14,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) { const [name, setName] = useState(""); const [channelType, setChannelType] = useState("generic_webhook"); const [url, setUrl] = useState(""); - const [username, setUsername] = useState("OrbitalWard"); + const [username, setUsername] = useState("OrbitWard"); const [enabled, setEnabled] = useState(true); const [editingChannelId, setEditingChannelId] = useState(null); const [busyId, setBusyId] = useState(null); @@ -38,7 +38,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) { await api.updateNotificationChannel(token, editingChannelId, { name, channel_type: channelType, - settings: { username: username.trim() || "OrbitalWard" }, + settings: { username: username.trim() || "OrbitWard" }, secret: url.trim() ? url.trim() : undefined, is_enabled: enabled, }); @@ -46,7 +46,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) { await api.createNotificationChannel(token, { name, channel_type: channelType, - settings: { username: username.trim() || "OrbitalWard" }, + settings: { username: username.trim() || "OrbitWard" }, secret: url, is_enabled: enabled, }); @@ -65,7 +65,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) { setName(channel.name); setChannelType(channel.channel_type); setUrl(""); - setUsername(String(channel.settings.username || "OrbitalWard")); + setUsername(String(channel.settings.username || "OrbitWard")); setEnabled(channel.is_enabled); setMessage(null); } @@ -75,7 +75,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) { setName(""); setChannelType("generic_webhook"); setUrl(""); - setUsername("OrbitalWard"); + setUsername("OrbitWard"); setEnabled(true); } @@ -180,7 +180,7 @@ export function NotificationsPage({ token }: NotificationsPageProps) {
{channel.name}
-
{String(channel.settings.username || "OrbitalWard")}
+
{String(channel.settings.username || "OrbitWard")}
{channel.has_secret ? "Secret stored" : "No secret"}
{channel.channel_type}
diff --git a/worker/app/__init__.py b/worker/app/__init__.py index 5f55d65..e4dc89a 100644 --- a/worker/app/__init__.py +++ b/worker/app/__init__.py @@ -1 +1 @@ -"""OrbitalWard worker package.""" +"""OrbitWard worker package.""" diff --git a/worker/app/collectors/network.py b/worker/app/collectors/network.py index f51948d..20cd3f3 100644 --- a/worker/app/collectors/network.py +++ b/worker/app/collectors/network.py @@ -78,7 +78,7 @@ def _resolve_ipv4(host: str) -> str: def _build_icmp_echo_request(identifier: int, sequence: int) -> bytes: - payload = b"OrbitalWard ping" + payload = b"OrbitWard ping" header = struct.pack("!BBHHH", 8, 0, 0, identifier, sequence) checksum = _icmp_checksum(header + payload) header = struct.pack("!BBHHH", 8, 0, checksum, identifier, sequence) diff --git a/worker/app/config.py b/worker/app/config.py index 0fc8222..2d2c84f 100644 --- a/worker/app/config.py +++ b/worker/app/config.py @@ -1,14 +1,18 @@ from functools import lru_cache +from pydantic import AliasChoices, Field from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") - orbitalward_env: str = "development" - orbitalward_secret_key: str = "change-me" - database_url: str = "postgresql+psycopg://orbitalward:orbitalward@postgres:5432/orbitalward" + orbitward_env: str = Field(default="development", validation_alias=AliasChoices("ORBITWARD_ENV", "ORBITALWARD_ENV")) + orbitward_secret_key: str = Field( + default="change-me", + validation_alias=AliasChoices("ORBITWARD_SECRET_KEY", "ORBITALWARD_SECRET_KEY"), + ) + database_url: str = "postgresql+psycopg://orbitward:orbitward@postgres:5432/orbitward" redis_url: str = "redis://redis:6379/0" frontend_url: str = "http://localhost:5173" backend_url: str = "http://localhost:8000" diff --git a/worker/app/scheduler.py b/worker/app/scheduler.py index 7d62a24..5e7d019 100644 --- a/worker/app/scheduler.py +++ b/worker/app/scheduler.py @@ -24,7 +24,7 @@ class Scheduler: self._stopped = asyncio.Event() async def run(self) -> None: - logger.info("OrbitalWard worker started for %s", settings.orbitalward_env) + logger.info("OrbitWard worker started for %s", settings.orbitward_env) while not self._stopped.is_set(): await self.tick() try: @@ -249,7 +249,7 @@ class Scheduler: await self._post_webhook( url, self._format_incident_message(incident, monitor, event_type), - str((channel.settings or {}).get("username") or "OrbitalWard"), + str((channel.settings or {}).get("username") or "OrbitWard"), ) except httpx.HTTPError: logger.exception("Notification delivery failed for channel %s", channel.id) @@ -296,5 +296,5 @@ class Scheduler: last_message = (incident.details or {}).get("last_message") if last_message: body.append(f"Last response: {last_message}") - body.extend(["", f"View in OrbitalWard: {settings.frontend_url}/incidents/{incident.id}"]) + body.extend(["", f"View in OrbitWard: {settings.frontend_url}/incidents/{incident.id}"]) return "\n".join(str(line) for line in body) diff --git a/worker/app/secrets.py b/worker/app/secrets.py index 4586f94..20ac034 100644 --- a/worker/app/secrets.py +++ b/worker/app/secrets.py @@ -7,7 +7,7 @@ from app.config import settings def _fernet() -> Fernet: - digest = hashlib.sha256(settings.orbitalward_secret_key.encode("utf-8")).digest() + digest = hashlib.sha256(settings.orbitward_secret_key.encode("utf-8")).digest() return Fernet(base64.urlsafe_b64encode(digest)) diff --git a/worker/pyproject.toml b/worker/pyproject.toml index 4e09d4f..eaaca04 100644 --- a/worker/pyproject.toml +++ b/worker/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "orbitalward-worker" +name = "orbitward-worker" version = "0.1.0" -description = "OrbitalWard background worker" +description = "OrbitWard background worker" requires-python = ">=3.12" dependencies = [ "cryptography>=48.0.0", diff --git a/worker/tests/test_scheduler.py b/worker/tests/test_scheduler.py index 3280455..4f2d019 100644 --- a/worker/tests/test_scheduler.py +++ b/worker/tests/test_scheduler.py @@ -16,7 +16,7 @@ from app.scheduler import Scheduler def encrypt_secret(value: str) -> str: - digest = hashlib.sha256(settings.orbitalward_secret_key.encode("utf-8")).digest() + digest = hashlib.sha256(settings.orbitward_secret_key.encode("utf-8")).digest() return Fernet(base64.urlsafe_b64encode(digest)).encrypt(value.encode("utf-8")).decode("utf-8") @@ -102,8 +102,8 @@ class SchedulerTestCase(unittest.IsolatedAsyncioTestCase): channel = NotificationChannel( name="Ops Webhook", channel_type="generic_webhook", - settings={"username": "OrbitalWard"}, - encrypted_secret=encrypt_secret("https://hooks.example.test/orbitalward"), + settings={"username": "OrbitWard"}, + encrypted_secret=encrypt_secret("https://hooks.example.test/orbitward"), is_enabled=True, ) self.db.add(channel) @@ -121,8 +121,8 @@ class SchedulerTestCase(unittest.IsolatedAsyncioTestCase): assert incident is not None assert incident.status == "open" assert len(scheduler.posts) == 1 - assert scheduler.posts[0]["url"] == "https://hooks.example.test/orbitalward" - assert scheduler.posts[0]["username"] == "OrbitalWard" + assert scheduler.posts[0]["url"] == "https://hooks.example.test/orbitward" + assert scheduler.posts[0]["username"] == "OrbitWard" assert incident.details["notification_history"][0]["event"] == "opened" await scheduler._send_incident_notifications(self.db, incident, monitor, "opened", datetime.now(UTC))