From b30829c844be4c7bb7168139df753da6238f912b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BF=AE=E9=9B=A8?= Date: Tue, 11 Aug 2026 16:12:15 +0800 Subject: [PATCH] feat: add observable Ant Design workbench --- .github/workflows/ci.yml | 58 +- Dockerfile | 2 +- README.md | 117 +- admin-dashboard/README.md | 54 +- admin-dashboard/package.json | 7 +- admin-dashboard/pnpm-lock.yaml | 796 +++++++- admin-dashboard/src/pages/_app.tsx | 5 +- admin-dashboard/src/pages/_document.tsx | 42 + .../src/pages/api/waitqueue/[...path].ts | 2 + admin-dashboard/src/pages/index.tsx | 1350 ++++++++------ .../src/style/dashboard.module.css | 1620 ++++++----------- .../src/theme/control-room-theme.tsx | 118 ++ compose.yaml | 2 +- wait-queue/src/app.ts | 52 +- wait-queue/src/common/logger.ts | 7 + wait-queue/src/lib/task_manager.ts | 38 +- wait-queue/src/lib/task_run.ts | 30 +- wait-queue/src/middleware/security.ts | 16 +- wait-queue/src/observability/metrics.ts | 188 ++ .../src/observability/runtime_snapshot.ts | 170 ++ wait-queue/src/routes/admin.ts | 44 +- wait-queue/src/service/admin.ts | 85 +- wait-queue/src/types/api.ts | 21 + wait-queue/test/admin.test.js | 115 +- wait-queue/test/metrics.test.js | 187 ++ .../test/observability.integration.test.js | 62 + wait-queue/test/readiness.test.js | 28 + wait-queue/test/security.test.js | 26 + wait-queue/test/task_manager.test.js | 45 +- wait-queue/test/task_run.test.js | 29 + 30 files changed, 3597 insertions(+), 1719 deletions(-) create mode 100644 admin-dashboard/src/pages/_document.tsx create mode 100644 admin-dashboard/src/theme/control-room-theme.tsx create mode 100644 wait-queue/src/observability/metrics.ts create mode 100644 wait-queue/src/observability/runtime_snapshot.ts create mode 100644 wait-queue/test/metrics.test.js create mode 100644 wait-queue/test/observability.integration.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61c8c07..df9c84e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -279,12 +279,46 @@ jobs: '.code == 0 and .data.status == "ok"' \ <<< "$health_json" > /dev/null + live_json="$(curl --fail --silent --show-error \ + "http://127.0.0.1:${API_PORT}/health/live")" + jq --exit-status \ + '.code == 0 and .data.status == "ok"' \ + <<< "$live_json" > /dev/null + ready_json="$(curl --fail --silent --show-error --retry 10 --retry-all-errors --retry-delay 2 \ "http://127.0.0.1:${API_PORT}/waitqueue/ready")" jq --exit-status \ '.code == 0 and .data.status == "ready" and .data.dependencies.mysql == "ok" and .data.dependencies.redis == "ok"' \ <<< "$ready_json" > /dev/null + ready_alias_json="$(curl --fail --silent --show-error \ + "http://127.0.0.1:${API_PORT}/health/ready")" + jq --exit-status \ + '.code == 0 and .data.status == "ready" and .data.dependencies.mysql == "ok" and .data.dependencies.redis == "ok"' \ + <<< "$ready_alias_json" > /dev/null + + unauthenticated_metrics_status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \ + "http://127.0.0.1:${API_PORT}/metrics")" + test "$unauthenticated_metrics_status" = "401" + + unauthenticated_metrics_slash_status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \ + "http://127.0.0.1:${API_PORT}/metrics/")" + test "$unauthenticated_metrics_slash_status" = "401" + + wrong_token_metrics_slash_status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \ + --header 'Authorization: Bearer definitely-wrong' \ + "http://127.0.0.1:${API_PORT}/metrics/")" + test "$wrong_token_metrics_slash_status" = "401" + + metrics_body="$(curl --fail --silent --show-error \ + --header "Authorization: Bearer ${WAITQUEUE_API_TOKEN}" \ + "http://127.0.0.1:${API_PORT}/metrics")" + grep --fixed-strings --quiet '# TYPE waitqueue_queue_waiting_tasks gauge' <<< "$metrics_body" + grep --fixed-strings --quiet '# TYPE waitqueue_callback_attempts_total counter' <<< "$metrics_body" + if grep --extended-regexp --quiet 'namespace=|task_id=|taskId=|token=|hook_url=|hookUrl=' <<< "$metrics_body"; then + exit 1 + fi + unauthenticated_status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \ "http://127.0.0.1:${API_PORT}/waitqueue/admin/overview")" test "$unauthenticated_status" = "401" @@ -297,7 +331,8 @@ jobs: authenticated_json="$(curl --fail --silent --show-error \ --header "Authorization: Bearer ${WAITQUEUE_API_TOKEN}" \ "http://127.0.0.1:${API_PORT}/waitqueue/admin/overview")" - jq --exit-status '.code == 0 and .data.summary.queueCount == 0' \ + jq --exit-status \ + '.code == 0 and .data.summary.queueCount == 0 and .data.summary.retrying == 0 and .data.summary.deadLetters == 0 and .data.summary.oldestWaitingAt == null and .data.summary.oldestWaitingAgeSeconds == null and (.data.metricsStartedAt | type) == "string"' \ <<< "$authenticated_json" > /dev/null private_hook_status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \ @@ -320,7 +355,26 @@ jobs: dashboard_html="$(curl --fail --silent --show-error --retry 10 --retry-all-errors --retry-delay 2 \ "http://127.0.0.1:${DASHBOARD_PORT}/")" - grep --fixed-strings --quiet 'WaitQueue Control Room' <<< "$dashboard_html" + grep --fixed-strings --quiet 'WaitQueue Workbench' <<< "$dashboard_html" + node -e ' + let html = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => { html += chunk; }); + process.stdin.on("end", () => { + const css = html.match(/