diff --git a/docker-compose.yml b/docker-compose.yml index 077dc48..85c8fe3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,29 +77,14 @@ services: # DB migrate: applies packages/server/migrations # ============================================================ db-migrate: - image: postgres:16-alpine + build: + context: ./packages/server + dockerfile: Dockerfile + command: ["node", "dist/migrate.js"] + env_file: .env depends_on: postgres: condition: service_healthy - environment: - PGPASSWORD: ${POSTGRES_PASSWORD:-elydora-secret-change-me} - volumes: - - ./packages/server/migrations:/migrations:ro,z - entrypoint: ["sh", "-c"] - command: - - | - set -e - PSQL="psql -h postgres -U ${POSTGRES_USER:-elydora} -d ${POSTGRES_DB:-elydora}" - for file in /migrations/*.sql; do - version=$$(basename "$$file" | cut -d_ -f1 | sed 's/^0*//') - applied=$$($$PSQL -tAX -c "SELECT 1 FROM schema_versions WHERE version = $$version" 2>/dev/null || true) - if [ "$$applied" = "1" ]; then - echo "Skipping $$file (already applied)" - continue - fi - echo "Applying $$file" - $$PSQL -v ON_ERROR_STOP=1 -f "$$file" - done api: build: diff --git a/helm/elydora/Chart.yaml b/helm/elydora/Chart.yaml index 6697167..bed5cdc 100644 --- a/helm/elydora/Chart.yaml +++ b/helm/elydora/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: elydora -description: Elydora — Open-source AI accountability and compliance platform +description: Open-source AI accountability and compliance platform for Elydora type: application version: 0.1.0 appVersion: "0.1.0" diff --git a/packages/console/README.md b/packages/console/README.md index eafdbd3..3fc50da 100644 --- a/packages/console/README.md +++ b/packages/console/README.md @@ -8,7 +8,8 @@ Web dashboard for the Elydora Responsibility Protocol. Provides agent management - **Language:** TypeScript 5.7 - **UI:** React 19, Tailwind CSS 4 - **Data Fetching:** SWR 2.3 -- **Deployment:** Cloudflare Pages (static export) +- **i18n:** i18next with English and Simplified Chinese +- **Deployment:** Docker image running the Next.js standalone server ## Project Structure @@ -16,7 +17,7 @@ Web dashboard for the Elydora Responsibility Protocol. Provides agent management src/ ├── app/ │ ├── layout.tsx # Root layout with AppShell -│ ├── page.tsx # Dashboard — stats, recent operations, quick actions +│ ├── page.tsx # Dashboard: stats, recent operations, quick actions │ ├── globals.css # Theme variables, global styles │ ├── agents/ │ │ ├── page.tsx # Agent list, search, registration @@ -33,6 +34,8 @@ src/ │ │ └── page.tsx # Audit log query builder │ ├── login/page.tsx # Login form │ ├── register/page.tsx # Registration form +│ ├── settings/ # Organization settings +│ ├── global-error.tsx # Root error boundary │ └── jwks/page.tsx # Public key display ├── components/ │ ├── AppShell.tsx # Main layout wrapper with sidebar @@ -43,6 +46,8 @@ src/ │ ├── EpochDetailClient.tsx # Epoch proof and verification │ ├── ChainVisualization.tsx # Merkle chain visualization │ ├── VerificationChecklist.tsx # Proof verification UI +│ ├── I18nProvider.tsx # Locale provider +│ ├── agent-registration/ # Registration flow components │ └── ui/ │ ├── PageHeader.tsx # Page title and breadcrumbs │ ├── DataTable.tsx # Sortable, paginated table @@ -50,12 +55,17 @@ src/ │ ├── SearchInput.tsx # Search/filter input │ ├── StatusBadge.tsx # Status indicator │ ├── CopyButton.tsx # Copy-to-clipboard +│ ├── LanguageDropdown.tsx # Locale switcher │ └── Sidebar.tsx # Navigation sidebar +├── features/ +│ └── agent-registration/ # Install instructions and credential handling +├── i18n/ # en and zh message catalogs ├── lib/ │ ├── api.ts # Typed REST client for all endpoints │ ├── auth.tsx # Auth context and hooks │ └── hooks.ts # SWR data-fetching hooks └── shared/ # Shared types (@elydora/shared) +e2e/ # Playwright specs ``` ## Getting Started @@ -90,20 +100,17 @@ Runs at `http://localhost:3000` with Turbopack. npm run build ``` -Produces a static export in `out/`. A post-build script removes stale `_redirects` files. +Produces a Next.js standalone server in `.next/standalone`. ### Deployment -```bash -npm run deploy -``` +The root `docker-compose.yml` builds this package with `packages/console/Dockerfile` and serves it on port 3000. The image runs `node server.js` from the standalone output. -Deploys the `out/` directory to Cloudflare Pages. - -### Type Checking +### Checks ```bash -npm run typecheck +npm run typecheck # tsc --noEmit +npm run test:e2e # Playwright specs in e2e/ ``` ## Pages @@ -122,3 +129,4 @@ npm run typecheck | `/login` | Login | | `/register` | Register account and organization | | `/jwks` | Display server public keys in JWK format | +| `/settings` | Organization settings | diff --git a/packages/console/src/components/OperationDetailCard.tsx b/packages/console/src/components/OperationDetailCard.tsx index 71a0977..cf1ac76 100644 --- a/packages/console/src/components/OperationDetailCard.tsx +++ b/packages/console/src/components/OperationDetailCard.tsx @@ -93,7 +93,7 @@ export default function OperationDetailCard({ operation, receipt, payload }: Ope )} - {/* Payload — what the operation actually did */} + {/* Payload: what the operation did */} {payload && (
diff --git a/packages/console/src/components/ui/Modal.tsx b/packages/console/src/components/ui/Modal.tsx index 3cbc862..3176e19 100644 --- a/packages/console/src/components/ui/Modal.tsx +++ b/packages/console/src/components/ui/Modal.tsx @@ -45,7 +45,7 @@ export default function Modal({ }; }, [isOpen]); - // Keydown listener — uses ref so it never changes identity + // Keydown listener uses a ref for stable identity useEffect(() => { if (!isOpen) return; diff --git a/packages/console/src/components/ui/Sidebar.tsx b/packages/console/src/components/ui/Sidebar.tsx index c803e42..12c5996 100644 --- a/packages/console/src/components/ui/Sidebar.tsx +++ b/packages/console/src/components/ui/Sidebar.tsx @@ -135,7 +135,7 @@ export default function Sidebar({ isOpen, onClose }: SidebarProps) { return ( <> - {/* Backdrop overlay — mobile only */} + {/* Mobile backdrop overlay */} {isOpen && (
> { + const exists = await client.query("SELECT to_regclass('schema_versions') AS name"); + if (!exists.rows[0]?.name) return new Set(); + const rows = await client.query<{ version: string }>('SELECT version FROM schema_versions'); + return new Set(rows.rows.map((row) => Number(row.version))); +} + +async function main(): Promise { + const connectionString = process.env.DATABASE_URL; + if (!connectionString) throw new Error('DATABASE_URL is required.'); + + const files = (await readdir(MIGRATIONS_DIR)) + .filter((file) => file.endsWith('.sql')) + .sort((a, b) => versionOf(a) - versionOf(b)); + + const client = new pg.Client({ connectionString }); + await client.connect(); + try { + const applied = await appliedVersions(client); + for (const file of files) { + if (applied.has(versionOf(file))) { + console.log(`Skipping ${file} (already applied)`); + continue; + } + console.log(`Applying ${file}`); + await client.query(await readFile(join(MIGRATIONS_DIR, file), 'utf8')); + } + } finally { + await client.end(); + } +} + +main().catch((error) => { + console.error('migration failed:', error); + process.exit(1); +});