chore(oss): audit the console package and the Helm chart
Add the dist/migrate.js entrypoint the Helm job already invoked and point Compose at the same runner, so one implementation applies packages/server/migrations in both paths. Rewrite the console README to the standalone Docker build it actually uses and fix the chart description. Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
This commit is contained in:
1 parent
bc6983a62e
commit
f69350d709
7 files changed
+76
-34
No files matched your search
+5
-20
@@ -77,29 +77,14 @@ services:
|
|||||||
# DB migrate: applies packages/server/migrations
|
# DB migrate: applies packages/server/migrations
|
||||||
# ============================================================
|
# ============================================================
|
||||||
db-migrate:
|
db-migrate:
|
||||||
image: postgres:16-alpine
|
build:
|
||||||
|
context: ./packages/server
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
command: ["node", "dist/migrate.js"]
|
||||||
|
env_file: .env
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_healthy
|
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:
|
api:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: elydora
|
name: elydora
|
||||||
description: Elydora — Open-source AI accountability and compliance platform
|
description: Open-source AI accountability and compliance platform for Elydora
|
||||||
type: application
|
type: application
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
appVersion: "0.1.0"
|
appVersion: "0.1.0"
|
||||||
+18
-10
@@ -8,7 +8,8 @@ Web dashboard for the Elydora Responsibility Protocol. Provides agent management
|
|||||||
- **Language:** TypeScript 5.7
|
- **Language:** TypeScript 5.7
|
||||||
- **UI:** React 19, Tailwind CSS 4
|
- **UI:** React 19, Tailwind CSS 4
|
||||||
- **Data Fetching:** SWR 2.3
|
- **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
|
## Project Structure
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ Web dashboard for the Elydora Responsibility Protocol. Provides agent management
|
|||||||
src/
|
src/
|
||||||
├── app/
|
├── app/
|
||||||
│ ├── layout.tsx # Root layout with AppShell
|
│ ├── 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
|
│ ├── globals.css # Theme variables, global styles
|
||||||
│ ├── agents/
|
│ ├── agents/
|
||||||
│ │ ├── page.tsx # Agent list, search, registration
|
│ │ ├── page.tsx # Agent list, search, registration
|
||||||
@@ -33,6 +34,8 @@ src/
|
|||||||
│ │ └── page.tsx # Audit log query builder
|
│ │ └── page.tsx # Audit log query builder
|
||||||
│ ├── login/page.tsx # Login form
|
│ ├── login/page.tsx # Login form
|
||||||
│ ├── register/page.tsx # Registration form
|
│ ├── register/page.tsx # Registration form
|
||||||
|
│ ├── settings/ # Organization settings
|
||||||
|
│ ├── global-error.tsx # Root error boundary
|
||||||
│ └── jwks/page.tsx # Public key display
|
│ └── jwks/page.tsx # Public key display
|
||||||
├── components/
|
├── components/
|
||||||
│ ├── AppShell.tsx # Main layout wrapper with sidebar
|
│ ├── AppShell.tsx # Main layout wrapper with sidebar
|
||||||
@@ -43,6 +46,8 @@ src/
|
|||||||
│ ├── EpochDetailClient.tsx # Epoch proof and verification
|
│ ├── EpochDetailClient.tsx # Epoch proof and verification
|
||||||
│ ├── ChainVisualization.tsx # Merkle chain visualization
|
│ ├── ChainVisualization.tsx # Merkle chain visualization
|
||||||
│ ├── VerificationChecklist.tsx # Proof verification UI
|
│ ├── VerificationChecklist.tsx # Proof verification UI
|
||||||
|
│ ├── I18nProvider.tsx # Locale provider
|
||||||
|
│ ├── agent-registration/ # Registration flow components
|
||||||
│ └── ui/
|
│ └── ui/
|
||||||
│ ├── PageHeader.tsx # Page title and breadcrumbs
|
│ ├── PageHeader.tsx # Page title and breadcrumbs
|
||||||
│ ├── DataTable.tsx # Sortable, paginated table
|
│ ├── DataTable.tsx # Sortable, paginated table
|
||||||
@@ -50,12 +55,17 @@ src/
|
|||||||
│ ├── SearchInput.tsx # Search/filter input
|
│ ├── SearchInput.tsx # Search/filter input
|
||||||
│ ├── StatusBadge.tsx # Status indicator
|
│ ├── StatusBadge.tsx # Status indicator
|
||||||
│ ├── CopyButton.tsx # Copy-to-clipboard
|
│ ├── CopyButton.tsx # Copy-to-clipboard
|
||||||
|
│ ├── LanguageDropdown.tsx # Locale switcher
|
||||||
│ └── Sidebar.tsx # Navigation sidebar
|
│ └── Sidebar.tsx # Navigation sidebar
|
||||||
|
├── features/
|
||||||
|
│ └── agent-registration/ # Install instructions and credential handling
|
||||||
|
├── i18n/ # en and zh message catalogs
|
||||||
├── lib/
|
├── lib/
|
||||||
│ ├── api.ts # Typed REST client for all endpoints
|
│ ├── api.ts # Typed REST client for all endpoints
|
||||||
│ ├── auth.tsx # Auth context and hooks
|
│ ├── auth.tsx # Auth context and hooks
|
||||||
│ └── hooks.ts # SWR data-fetching hooks
|
│ └── hooks.ts # SWR data-fetching hooks
|
||||||
└── shared/ # Shared types (@elydora/shared)
|
└── shared/ # Shared types (@elydora/shared)
|
||||||
|
e2e/ # Playwright specs
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
@@ -90,20 +100,17 @@ Runs at `http://localhost:3000` with Turbopack.
|
|||||||
npm run build
|
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
|
### Deployment
|
||||||
|
|
||||||
```bash
|
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.
|
||||||
npm run deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Deploys the `out/` directory to Cloudflare Pages.
|
### Checks
|
||||||
|
|
||||||
### Type Checking
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run typecheck
|
npm run typecheck # tsc --noEmit
|
||||||
|
npm run test:e2e # Playwright specs in e2e/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Pages
|
## Pages
|
||||||
@@ -122,3 +129,4 @@ npm run typecheck
|
|||||||
| `/login` | Login |
|
| `/login` | Login |
|
||||||
| `/register` | Register account and organization |
|
| `/register` | Register account and organization |
|
||||||
| `/jwks` | Display server public keys in JWK format |
|
| `/jwks` | Display server public keys in JWK format |
|
||||||
|
| `/settings` | Organization settings |
|
||||||
@@ -93,7 +93,7 @@ export default function OperationDetailCard({ operation, receipt, payload }: Ope
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Payload — what the operation actually did */}
|
{/* Payload: what the operation did */}
|
||||||
{payload && (
|
{payload && (
|
||||||
<div className="border border-border bg-surface p-4 sm:p-6 relative">
|
<div className="border border-border bg-surface p-4 sm:p-6 relative">
|
||||||
<span className="crosshair ch-tl" />
|
<span className="crosshair ch-tl" />
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function Modal({
|
|||||||
};
|
};
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
// Keydown listener — uses ref so it never changes identity
|
// Keydown listener uses a ref for stable identity
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) return;
|
if (!isOpen) return;
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export default function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop overlay — mobile only */}
|
{/* Mobile backdrop overlay */}
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
className="fixed inset-0 bg-black/50 z-40 md:hidden"
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { readdir, readFile } from 'node:fs/promises';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import pg from 'pg';
|
||||||
|
|
||||||
|
const MIGRATIONS_DIR = process.env.MIGRATIONS_DIR ?? join(process.cwd(), 'migrations');
|
||||||
|
|
||||||
|
/** Leading digits of a migration filename, e.g. 004_x.sql becomes 4. */
|
||||||
|
function versionOf(file: string): number {
|
||||||
|
const match = /^(\d+)_/.exec(file);
|
||||||
|
if (!match) throw new Error(`Migration ${file} does not start with a version number.`);
|
||||||
|
return Number(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appliedVersions(client: pg.Client): Promise<Set<number>> {
|
||||||
|
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<void> {
|
||||||
|
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);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user