From b99ec0c4f4a2827bfa90ae13aef06215a564c004 Mon Sep 17 00:00:00 2001 From: Gleb Korotkiy Date: Sun, 21 Jun 2026 11:08:30 +0300 Subject: [PATCH] chore: add deployment infrastructure --- .dockerignore | 24 +++++++++++ .gitea/workflows/ci-pr.yml | 84 +++++++++++++++++++++++++++++++++++++ Dockerfile | 39 +++++++++++++++++ docker-compose.yml | 17 ++++++++ docker/docker-entrypoint.sh | 13 ++++++ docker/nginx.conf.template | 61 +++++++++++++++++++++++++++ src/style.css | 31 +++++++++----- 7 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/ci-pr.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100755 docker/docker-entrypoint.sh create mode 100644 docker/nginx.conf.template diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d3f5e37 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +.git +.gitignore +.gitea +.vscode +.idea +.fleet + +node_modules +dist +dist-ssr +coverage +playwright-report +test-results + +.env +.env.* +!.env.example +!.env.*.example + +*.log +*.swp +*.swo +.DS_Store +Thumbs.db diff --git a/.gitea/workflows/ci-pr.yml b/.gitea/workflows/ci-pr.yml new file mode 100644 index 0000000..ecdef5e --- /dev/null +++ b/.gitea/workflows/ci-pr.yml @@ -0,0 +1,84 @@ +name: ci-pr + +on: + pull_request: + branches: + - dev + workflow_dispatch: + +concurrency: + group: ci-pr-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + gate: + runs-on: [frontend, ubuntu-latest] + timeout-minutes: 120 + env: + PLAYWRIGHT_BROWSERS_PATH: /root/.cache/ms-playwright + + steps: + - name: Checkout + uses: actions/checkout@v4.2.2 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.9 + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: | + /root/.bun/install/cache + node_modules + key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }} + restore-keys: | + ${{ runner.os }}-${{ runner.arch }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Generate API clients + run: bun run apigen + + - name: Lint check + run: bun run lint + + - name: Type check + run: bun run typecheck + + - name: Unit tests + run: bun run test:unit + + - name: Build + run: bun run build + + - name: Resolve Playwright version + id: playwright-version + run: | + version="$(bunx playwright --version | awk '{ print $2 }')" + echo "version=${version}" >> "${GITHUB_OUTPUT}" + + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: /root/.cache/ms-playwright + key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }} + + - name: Install Playwright system deps + run: bunx playwright install-deps chromium + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: bunx playwright install chromium + + - name: E2E + env: + CI: '1' + PLAYWRIGHT_HTML_OPEN: never + run: bun run test:e2e -- --reporter=line + + - name: Docker build + run: docker build --pull -t analytical-panel-frontend:ci . diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fb91074 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1.7 + +FROM oven/bun:1.3.9 AS builder + +WORKDIR /app + +ENV HUSKY=0 + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +COPY package.json bun.lock ./ + +RUN bun install --frozen-lockfile + +COPY . . + +RUN bun run apigen +RUN bun run build + + +FROM nginx:1.29-alpine AS runner + +RUN apk add --no-cache gettext + +COPY docker/nginx.conf.template /etc/nginx/templates/default.conf.template +COPY docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +COPY --from=builder /app/dist/ /usr/share/nginx/html/ + +EXPOSE 80 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -q -O /dev/null http://127.0.0.1/ || exit 1 + +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9905356 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +services: + frontend: + container_name: analytical-panel-frontend + build: + context: . + dockerfile: Dockerfile + environment: + BACKEND_UPSTREAM: '${BACKEND_UPSTREAM:-https://anal-back.dev.nii-ecos.ru}' + ports: + - '${FRONTEND_PORT:-8080}:80' + restart: unless-stopped + networks: + - analytical_panel + +networks: + analytical_panel: + name: analytical_panel_network diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100755 index 0000000..a6b4a24 --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh +set -eu + +: "${BACKEND_UPSTREAM:?BACKEND_UPSTREAM is required (example: http://analytical-panel-backend:8000)}" + +BACKEND_UPSTREAM="${BACKEND_UPSTREAM%/}" +export BACKEND_UPSTREAM + +envsubst '${BACKEND_UPSTREAM}' \ + < /etc/nginx/templates/default.conf.template \ + > /etc/nginx/conf.d/default.conf + +exec "$@" diff --git a/docker/nginx.conf.template b/docker/nginx.conf.template new file mode 100644 index 0000000..c842baf --- /dev/null +++ b/docker/nginx.conf.template @@ -0,0 +1,61 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + proxy_ssl_server_name on; + send_timeout 300s; + + location = /index.html { + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + add_header Pragma "no-cache" always; + add_header Expires "0" always; + try_files /index.html =404; + } + + location ~* \.(?:js|mjs|css|png|jpg|jpeg|gif|svg|ico|webp|avif|woff2?|ttf|otf|map)$ { + add_header Cache-Control "public, max-age=31536000, immutable" always; + try_files $uri =404; + } + + location = /api { + proxy_pass ${BACKEND_UPSTREAM}/api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /api/ { + proxy_pass ${BACKEND_UPSTREAM}/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location = /health { + proxy_pass ${BACKEND_UPSTREAM}/health/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health/ { + proxy_pass ${BACKEND_UPSTREAM}/health/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/src/style.css b/src/style.css index 6df4ee7..9b7bd14 100644 --- a/src/style.css +++ b/src/style.css @@ -1031,9 +1031,13 @@ select { .analytics-table th:first-child, .analytics-table td:first-child { - width: 28px; + width: 44px; + min-width: 44px; + max-width: 44px; + padding-right: 0; + padding-left: 0; color: rgb(2 36 86 / 0.5); - text-align: right; + text-align: center; } .analytics-table th:last-child, @@ -1057,11 +1061,6 @@ select { white-space: normal !important; } -.technopark-area-table th:first-child, -.technopark-area-table td:first-child { - width: 30px; -} - .sez-priority-table th:nth-child(3), .sez-priority-table td:nth-child(3) { width: 140px; @@ -1183,7 +1182,7 @@ select { } .ranking-table__rank-col { - width: 36px; + width: 44px; } .ranking-table__region-col { @@ -1229,8 +1228,13 @@ select { .ranking-table th:first-child, .ranking-table td:first-child { + width: 44px; + min-width: 44px; + max-width: 44px; + padding-right: 0; + padding-left: 0; color: rgb(2 36 86 / 0.56); - text-align: right; + text-align: center; } .ranking-table th:nth-child(n + 3), @@ -1662,7 +1666,14 @@ select { } .ranking-table__rank-col { - width: 24px; + width: 36px; + } + + .ranking-table th:first-child, + .ranking-table td:first-child { + width: 36px; + min-width: 36px; + max-width: 36px; } .ranking-table__metric-col {