diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5f1be45 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules +dist +.github +*.local diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4726eac --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Type check + run: npx tsc --noEmit + + - name: Build + run: npm run build + + - name: Upload dist + uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..258b515 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,34 @@ +name: Deploy to Vercel + +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Build + run: npm run build + env: + VITE_API_URL: ${{ secrets.VITE_API_URL }} + + - name: Deploy to Vercel + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + vercel-args: --prod + working-directory: . diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..310af66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# ── Stage 1: build ──────────────────────────────────────────────────────────── +FROM node:20-alpine AS builder + +WORKDIR /app + +RUN npm install -g npm@11 + +COPY package.json package-lock.json ./ +RUN npm ci --legacy-peer-deps + +COPY . . +RUN npm run build + +# ── Stage 2: serve ──────────────────────────────────────────────────────────── +FROM nginx:1.27-alpine + +COPY --from=builder /app/dist /usr/share/nginx/html + +# Single-page app: route all 404s back to index.html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..f9489a8 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,18 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + # Serve static assets with long-lived cache + location ~* \.(js|css|woff2?|ttf|svg|png|ico)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # SPA fallback: send all unmatched routes to index.html + location / { + try_files $uri $uri/ /index.html; + } +} diff --git a/src/api.ts b/src/api.ts index d583612..e12a6fa 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,4 @@ -const BASE_URL = 'http://192.168.254.31:8080' +const BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8080' export async function apiFetch(path: string, options: RequestInit = {}): Promise { const token = localStorage.getItem('auth_token') diff --git a/src/components/SassMetricsView.tsx b/src/components/SassMetricsView.tsx index 2ac5fb4..c6afe76 100644 --- a/src/components/SassMetricsView.tsx +++ b/src/components/SassMetricsView.tsx @@ -90,7 +90,7 @@ export default function SassMetricsView() { functionName: s.functionName ?? '', sourceFile: s.sourceFile ?? '', sourceLine: s.sourceLine ?? null, - tsNs: s.tsNs, + tsNs: s.tsNs ?? 0, }) } const entry = corrMap.get(pcOffset)! diff --git a/src/types.ts b/src/types.ts index c056a0b..9e437dd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -73,6 +73,7 @@ export interface HostMetricSample { time: string; tsNs: number; hostname: string; + ipAddr?: string; cpuPct: number; ramUsedMib: number; ramTotalMib: number; @@ -146,6 +147,11 @@ export interface ProfileSample { reasonName?: string; sampleCount: number; occurrenceCount: number; + corrId?: number; + scopeId?: string; + tsNs?: number; + metricName?: string; + metricValue?: number; } export interface InsightDto { diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..0d199b2 --- /dev/null +++ b/vercel.json @@ -0,0 +1,5 @@ +{ + "rewrites": [ + { "source": "/(.*)", "destination": "/index.html" } + ] +}