feat(mcp): manage dashboards and reports (#454) #771
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docker Build and Push | |
| on: | |
| workflow_dispatch: | |
| push: | |
| paths-ignore: | |
| # README and docs | |
| - "**/README*" | |
| - "**/readme*" | |
| - "**/*.md" | |
| - "**/docs/**" | |
| - "**/CHANGELOG*" | |
| - "**/LICENSE*" | |
| # Test files | |
| - "**/*.test.*" | |
| - "**/*.spec.*" | |
| - "**/__tests__/**" | |
| - "**/tests/**" | |
| # SDKs (published separately) | |
| - "packages/sdks/**" | |
| # Public app (docs/marketing, not part of Docker deploy) | |
| - "apps/public/**" | |
| # Dev / tooling | |
| - "**/.vscode/**" | |
| - "**/.cursor/**" | |
| - "**/.env.example" | |
| - "**/.env.*.example" | |
| - "**/.gitignore" | |
| - "**/.eslintignore" | |
| - "**/.prettierignore" | |
| env: | |
| repo_owner: "openpanel-dev" | |
| jobs: | |
| lint-and-test: | |
| runs-on: ubuntu-latest | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| ports: | |
| - 5432:5432 | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: postgres | |
| options: >- | |
| --health-cmd "pg_isready -U postgres" | |
| --health-interval 5s | |
| --health-timeout 3s | |
| --health-retries 20 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping || exit 1" | |
| --health-interval 5s | |
| --health-timeout 3s | |
| --health-retries 20 | |
| clickhouse: | |
| image: clickhouse/clickhouse-server:26.1.3.52 | |
| ports: | |
| - 8123:8123 | |
| env: | |
| CLICKHOUSE_DB: openpanel | |
| CLICKHOUSE_USER: default | |
| CLICKHOUSE_PASSWORD: "" | |
| CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1 | |
| options: >- | |
| --health-cmd "wget -qO- http://localhost:8123/ping || exit 1" | |
| --health-interval 5s | |
| --health-timeout 3s | |
| --health-retries 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| # .nvmrc is the source of truth; keep ARG NODE_VERSION in | |
| # apps/*/Dockerfile in sync with it. Must stay >=22.19.0: | |
| # undici@8 fails at import on older Node with | |
| # "webidl.util.markAsUncloneable is not a function". | |
| node-version-file: ".nvmrc" | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v3 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Codegen | |
| run: pnpm codegen | |
| - name: Migrate database | |
| run: pnpm migrate:deploy | |
| env: | |
| DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres?schema=public | |
| DATABASE_URL_DIRECT: postgresql://postgres:postgres@localhost:5432/postgres?schema=public | |
| CLICKHOUSE_URL: http://localhost:8123/openpanel | |
| SELF_HOSTED: "true" | |
| - name: Run tests | |
| run: pnpm test | |
| env: | |
| DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres?schema=public | |
| CLICKHOUSE_URL: http://localhost:8123/openpanel | |
| REDIS_URL: redis://localhost:6379 | |
| # - name: Run Biome | |
| # run: pnpm lint | |
| # - name: Run TypeScript checks | |
| # run: pnpm typecheck | |
| build-api: | |
| permissions: | |
| packages: write | |
| contents: read | |
| needs: lint-and-test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Prepare platform pair | |
| run: | | |
| platform=${{ matrix.platform }} | |
| echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: apps/api/Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| cache-from: type=gha,scope=api-${{ env.PLATFORM_PAIR }} | |
| cache-to: type=gha,mode=max,scope=api-${{ env.PLATFORM_PAIR }} | |
| outputs: type=image,name=ghcr.io/${{ env.repo_owner }}/api,push-by-digest=true,name-canonical=true,push=true | |
| build-args: | | |
| DATABASE_URL=postgresql://dummy:dummy@localhost:5432/dummy | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-api-${{ env.PLATFORM_PAIR }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| # Boots the images this run produced and asserts they actually serve traffic. | |
| # Gates every merge-* job below, so a broken image never gets a pullable tag. | |
| smoke: | |
| permissions: | |
| packages: read | |
| contents: read | |
| needs: [build-api, build-worker, build-dashboard] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # build-* pushed by digest but has not tagged anything yet, so pull the | |
| # exact amd64 digests this run produced. | |
| - name: Download amd64 digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/smoke-digests | |
| pattern: digests-*-linux-amd64 | |
| - name: Resolve images | |
| run: | | |
| set -euo pipefail | |
| for app in api worker dashboard; do | |
| dir="/tmp/smoke-digests/digests-${app}-linux-amd64" | |
| digest="$(ls "$dir" | head -1)" | |
| if [ -z "$digest" ]; then | |
| echo "::error::no digest artifact for $app" | |
| exit 1 | |
| fi | |
| image="ghcr.io/${{ env.repo_owner }}/${app}@sha256:${digest}" | |
| echo "$app -> $image" | |
| echo "OP_$(echo "$app" | tr '[:lower:]' '[:upper:]')_IMAGE=$image" >> "$GITHUB_ENV" | |
| done | |
| - name: Smoke test the stack | |
| run: .github/smoke/smoke.sh | |
| - name: Tear down | |
| if: always() | |
| run: docker compose -f .github/smoke/docker-compose.yml down -v || true | |
| merge-api: | |
| permissions: | |
| packages: write | |
| contents: write | |
| needs: [build-api, smoke] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-api-* | |
| merge-multiple: true | |
| - name: Generate tags | |
| id: tags | |
| run: | | |
| BRANCH_NAME=$(echo "${{ github.ref_name }}" | sed 's/\//-/g') | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-4) | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| docker buildx imagetools create \ | |
| -t ghcr.io/${{ env.repo_owner }}/api:${{ steps.tags.outputs.branch_name }}-${{ steps.tags.outputs.short_sha }} \ | |
| $(printf 'ghcr.io/${{ env.repo_owner }}/api@sha256:%s ' *) | |
| - name: Create/Update API tag | |
| if: github.ref == 'refs/heads/main' | |
| run: | | |
| # Delete existing tag if it exists | |
| if git tag -l "api" | grep -q "api"; then | |
| git tag -d "api" | |
| echo "Deleted local tag: api" | |
| fi | |
| # Create new tag | |
| git tag "api" "${{ github.sha }}" | |
| echo "Created tag: api" | |
| # Push tag to remote | |
| git push origin "api" --force | |
| echo "Pushed tag: api" | |
| build-worker: | |
| permissions: | |
| packages: write | |
| contents: read | |
| needs: lint-and-test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Prepare platform pair | |
| run: | | |
| platform=${{ matrix.platform }} | |
| echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: apps/worker/Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| cache-from: type=gha,scope=worker-${{ env.PLATFORM_PAIR }} | |
| cache-to: type=gha,mode=max,scope=worker-${{ env.PLATFORM_PAIR }} | |
| outputs: type=image,name=ghcr.io/${{ env.repo_owner }}/worker,push-by-digest=true,name-canonical=true,push=true | |
| build-args: | | |
| DATABASE_URL=postgresql://dummy:dummy@localhost:5432/dummy | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-worker-${{ env.PLATFORM_PAIR }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| merge-worker: | |
| permissions: | |
| packages: write | |
| contents: write | |
| needs: [build-worker, smoke] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-worker-* | |
| merge-multiple: true | |
| - name: Generate tags | |
| id: tags | |
| run: | | |
| BRANCH_NAME=$(echo "${{ github.ref_name }}" | sed 's/\//-/g') | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-4) | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| docker buildx imagetools create \ | |
| -t ghcr.io/${{ env.repo_owner }}/worker:${{ steps.tags.outputs.branch_name }}-${{ steps.tags.outputs.short_sha }} \ | |
| $(printf 'ghcr.io/${{ env.repo_owner }}/worker@sha256:%s ' *) | |
| - name: Create/Update Worker tag | |
| if: github.ref == 'refs/heads/main' | |
| run: | | |
| # Delete existing tag if it exists | |
| if git tag -l "worker" | grep -q "worker"; then | |
| git tag -d "worker" | |
| echo "Deleted local tag: worker" | |
| fi | |
| # Create new tag | |
| git tag "worker" "${{ github.sha }}" | |
| echo "Created tag: worker" | |
| # Push tag to remote | |
| git push origin "worker" --force | |
| echo "Pushed tag: worker" | |
| build-dashboard: | |
| permissions: | |
| packages: write | |
| contents: read | |
| needs: lint-and-test | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux/amd64 | |
| runner: ubuntu-latest | |
| - platform: linux/arm64 | |
| runner: ubuntu-24.04-arm | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Prepare platform pair | |
| run: | | |
| platform=${{ matrix.platform }} | |
| echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push by digest | |
| id: build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| file: apps/start/Dockerfile | |
| platforms: ${{ matrix.platform }} | |
| cache-from: type=gha,scope=dashboard-${{ env.PLATFORM_PAIR }} | |
| cache-to: type=gha,mode=max,scope=dashboard-${{ env.PLATFORM_PAIR }} | |
| outputs: type=image,name=ghcr.io/${{ env.repo_owner }}/dashboard,push-by-digest=true,name-canonical=true,push=true | |
| build-args: | | |
| NO_CLOUDFLARE=1 | |
| - name: Export digest | |
| run: | | |
| mkdir -p /tmp/digests | |
| digest="${{ steps.build.outputs.digest }}" | |
| touch "/tmp/digests/${digest#sha256:}" | |
| - name: Upload digest | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: digests-dashboard-${{ env.PLATFORM_PAIR }} | |
| path: /tmp/digests/* | |
| if-no-files-found: error | |
| retention-days: 1 | |
| merge-dashboard: | |
| permissions: | |
| packages: write | |
| contents: write | |
| needs: [build-dashboard, smoke] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download digests | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: /tmp/digests | |
| pattern: digests-dashboard-* | |
| merge-multiple: true | |
| - name: Generate tags | |
| id: tags | |
| run: | | |
| BRANCH_NAME=$(echo "${{ github.ref_name }}" | sed 's/\//-/g') | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-4) | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create manifest list and push | |
| working-directory: /tmp/digests | |
| run: | | |
| docker buildx imagetools create \ | |
| -t ghcr.io/${{ env.repo_owner }}/dashboard:${{ steps.tags.outputs.branch_name }}-${{ steps.tags.outputs.short_sha }} \ | |
| $(printf 'ghcr.io/${{ env.repo_owner }}/dashboard@sha256:%s ' *) | |
| - name: Create/Update Dashboard tag | |
| if: github.ref == 'refs/heads/main' | |
| run: | | |
| # Delete existing tag if it exists | |
| if git tag -l "dashboard" | grep -q "dashboard"; then | |
| git tag -d "dashboard" | |
| echo "Deleted local tag: dashboard" | |
| fi | |
| # Create new tag | |
| git tag "dashboard" "${{ github.sha }}" | |
| echo "Created tag: dashboard" | |
| # Push tag to remote | |
| git push origin "dashboard" --force | |
| echo "Pushed tag: dashboard" |