feat(admin): apix-admin UI at admin.api-index.org
Deploy to Production / deploy (push) Failing after 47s

Internal maintainer dashboard: global stats, sandbox list with
tier-filter and pagination, sandbox drill-down with D3 world map,
tier-promotion form. API-key auth via HttpOnly cookie. Port 8084,
rolling a/b deploy alongside the existing service pairs.

- apix-common: AdminSandboxSummary, AdminSandboxListResponse, AdminStatsResponse DTOs
- apix-registry: AdminResource (4 endpoints), SandboxService.listAllSandboxes + getGlobalStats
- apix-registry: SandboxResource GET /{uuid}/dashboard endpoint (was missing — fixes portal 500)
- apix-portal: RegistryClient path corrected to /dashboard
- apix-admin: new Quarkus module — auth filter, registry REST client, 3 resources, 6 Qute templates, admin.js
- infra: Dockerfile.admin, docker-compose admin-a/b services, Caddyfile admin.api-index.org block
- scripts: deploy-bluegreen.sh extended for admin-a/b

Co-Authored-By: Mira Rehfeld <noreply@anthropic.com>
This commit is contained in:
Carsten Rehfeld
2026-05-16 21:18:48 +02:00
parent e385d05ce1
commit 82a379e536
32 changed files with 1300 additions and 32 deletions
+5 -2
View File
@@ -59,24 +59,27 @@ docker build -f infra/Dockerfile.registry -t apix-registry:latest . -q
docker build -f infra/Dockerfile.portal -t apix-portal:latest . -q
docker build -f infra/Dockerfile.demo -t apix-demo:latest . -q
docker build -f infra/Dockerfile.spider -t apix-spider:latest . -q
docker build -f infra/Dockerfile.admin -t apix-admin:latest . -q
# ── 4. Rolling restart: a-stack (registry-a → portal-a + demo-a) ─────────────
log "Rolling restart: a-stack..."
$COMPOSE up -d --no-deps --force-recreate registry-a
wait_healthy "registry-a"
$COMPOSE up -d --no-deps --force-recreate portal-a demo-a
$COMPOSE up -d --no-deps --force-recreate portal-a demo-a admin-a
wait_healthy "portal-a"
wait_healthy "demo-a"
wait_healthy "admin-a"
# ── 5. Rolling restart: b-stack ───────────────────────────────────────────────
log "Rolling restart: b-stack..."
$COMPOSE up -d --no-deps --force-recreate registry-b
wait_healthy "registry-b"
$COMPOSE up -d --no-deps --force-recreate portal-b demo-b
$COMPOSE up -d --no-deps --force-recreate portal-b demo-b admin-b
wait_healthy "portal-b"
wait_healthy "demo-b"
wait_healthy "admin-b"
# ── 6. Spider (cron job — restart acceptable) ─────────────────────────────────
log "Restarting spider..."
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# promote-sandbox.sh — Promote a sandbox tier + fetch demo ecosystem UUID
# Run on the server: bash promote-sandbox.sh
set -euo pipefail
ENV_FILE="/opt/apix/.env"
REGISTRY="https://api-index.org"
# ── Adjust these ───────────────────────────────────────────────────────────────
SANDBOX_ID="25321050-4707-4e85-bd9f-877ab04e4a55"
NEW_TIER="COMMUNITY"
# ──────────────────────────────────────────────────────────────────────────────
ADMIN_KEY=$(grep -oP '(?<=APIX_API_KEY=).+' "$ENV_FILE" | tr -d '[:space:]')
if [[ -z "$ADMIN_KEY" ]]; then
echo "ERROR: APIX_API_KEY not found in $ENV_FILE" >&2
exit 1
fi
echo "==> Promoting sandbox $SANDBOX_ID to $NEW_TIER ..."
curl -s -X PATCH "$REGISTRY/sandbox/admin/$SANDBOX_ID/tier" \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d "{\"tier\":\"$NEW_TIER\"}" | python3 -m json.tool
echo ""
echo "==> Demo ecosystem sandbox UUID ..."
docker exec "$(docker ps -qf name=demo-a)" \
psql -U apix -d apix -t -A \
-c "SELECT value FROM demo_config WHERE key='sandbox.id';"
+8 -9
View File
@@ -11,6 +11,12 @@ info() { echo -e "${GREEN}[apix]${NC} $*"; }
warn() { echo -e "${YELLOW}[warn]${NC} $*"; }
die() { echo -e "${RED}[fail]${NC} $*" >&2; exit 1; }
_on_exit() {
echo ""
read -rp "Press Enter to close…" _
}
trap _on_exit EXIT
warn "This will DROP and recreate the local 'apix' database."
read -rp "Continue? [y/N] " confirm
[[ "${confirm,,}" == "y" ]] || { echo "Aborted."; exit 0; }
@@ -58,12 +64,5 @@ for i in $(seq 1 30); do
sleep 1
done
info "Running Liquibase migrations"
mvn -q liquibase:update -pl apix-registry \
-Dliquibase.url="jdbc:postgresql://localhost:${DB_PORT}/${DB_NAME}" \
-Dliquibase.username="$DB_USER" \
-Dliquibase.password="$DB_PASS"
info "Migrations applied"
info "Reset complete — starting dev servers"
"$SCRIPT_DIR/dev.sh"
info "Reset complete — starting dev servers (Quarkus applies migrations at startup)"
exec "$SCRIPT_DIR/dev.sh"
+3
View File
@@ -13,6 +13,9 @@ TARGET="${1:-all}"
GREEN='\033[0;32m'; NC='\033[0m'
info() { echo -e "${GREEN}[apix]${NC} $*"; }
_on_exit() { echo ""; read -rp "Press Enter to close…" _; }
trap _on_exit EXIT
# ── tmux mode ────────────────────────────────────────────────────────────────
if command -v tmux &>/dev/null && tmux has-session -t apix-dev 2>/dev/null; then
_restart_window() {
+6 -20
View File
@@ -116,27 +116,13 @@ for i in $(seq 1 30); do
sleep 1
done
# ── 6. Liquibase migrations ───────────────────────────────────────────────────
# ── 6. Note on migrations ─────────────────────────────────────────────────────
step "Database migrations"
JDBC_URL="jdbc:postgresql://localhost:${DB_PORT}/${DB_NAME}"
if [[ ! -f "$PROJECT_ROOT/pom.xml" ]]; then
warn "No pom.xml found — Maven project not scaffolded yet (WORKLOG Block 1 / C-00)."
warn "Skipping Liquibase migrations. Run this script again after completing Block 1."
else
# apix-registry depends on apix-common and apix-verification; install them
# to the local repository first so Maven can resolve them during the
# liquibase:update goal (no source files yet — this completes in seconds).
info "Installing shared modules to local repository…"
mvn -q install -pl apix-common,apix-verification -DskipTests
info "Running Liquibase migrations on $JDBC_URL"
mvn -q liquibase:update -pl apix-registry \
-Dliquibase.url="$JDBC_URL" \
-Dliquibase.username="$DB_USER" \
-Dliquibase.password="$DB_PASS"
info "Migrations applied"
fi
# Migrations are applied automatically by Quarkus at startup via
# quarkus.liquibase.migrate-at-start=true. Running them separately via the
# Maven Liquibase plugin records a different classpath prefix in DATABASECHANGELOG
# than Quarkus uses, causing a "relation already exists" conflict on next start.
info "Migrations will run automatically when Quarkus starts (migrate-at-start=true)."
# ── Done ──────────────────────────────────────────────────────────────────────
echo ""