Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# ── .NET ──────────────────────────────────────────────────────────────────────
**/bin/
**/obj/
**/.vs/
**/*.user
**/logs/
**/*.log

# ── Frontend ──────────────────────────────────────────────────────────────────
frontend/node_modules/
frontend/dist/
frontend/.cache/

# ── Git / CI ──────────────────────────────────────────────────────────────────
.git/
.github/
.gitignore

# ── Documentação / Exemplos (não necessários na imagem) ───────────────────────
README.md
AGENTS.md
examples/
database/

# ── Variáveis de ambiente (nunca incluir segredos na imagem) ──────────────────
.env
.env.*
!.env.example
136 changes: 136 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: CI/CD Pipeline

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

env:
REGISTRY: ghcr.io
API_IMAGE: ${{ github.repository }}/api
FRONTEND_IMAGE: ${{ github.repository }}/frontend

jobs:
# ============================================================================
# JOB 1 — Build & Lint da API .NET
# ============================================================================
build-api:
name: Build API (.NET 9)
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "9.0.x"

- name: Restore dependencies
run: dotnet restore EtlMonitoring.sln

- name: Build
run: dotnet build EtlMonitoring.sln --no-restore -c Release

# ============================================================================
# JOB 2 — Lint & Build do Frontend React
# ============================================================================
build-frontend:
name: Build Frontend (Node 22)
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
working-directory: frontend
run: npm ci

- name: Lint
working-directory: frontend
run: npm run lint

- name: Build
working-directory: frontend
run: npm run build

# ============================================================================
# JOB 3 — Publicar imagens Docker no GHCR (apenas na branch main)
# ============================================================================
publish:
name: Publish Docker Images
runs-on: ubuntu-latest
needs: [build-api, build-frontend]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Login no GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3

# ── API ────────────────────────────────────────────────────────────────
- name: Metadata da imagem (API)
id: meta-api
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.API_IMAGE }}
tags: |
type=sha,prefix=sha-
type=raw,value=latest

- name: Build e push da imagem (API)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
tags: ${{ steps.meta-api.outputs.tags }}
labels: ${{ steps.meta-api.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# ── Frontend ──────────────────────────────────────────────────────────
- name: Metadata da imagem (Frontend)
id: meta-frontend
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE }}
tags: |
type=sha,prefix=sha-
type=raw,value=latest

- name: Build e push da imagem (Frontend)
uses: docker/build-push-action@v6
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: ${{ steps.meta-frontend.outputs.tags }}
labels: ${{ steps.meta-frontend.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VITE_API_URL=/api
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,5 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml
.env
.env.example
48 changes: 48 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# =============================================================================
# Stage 1: Build
# =============================================================================
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src

# Copiar arquivos de projeto primeiro para aproveitar cache de camadas
COPY EtlMonitoring.sln .
COPY src/EtlMonitoring.Api/EtlMonitoring.Api.csproj src/EtlMonitoring.Api/
COPY src/EtlMonitoring.Core/EtlMonitoring.Core.csproj src/EtlMonitoring.Core/
COPY src/EtlMonitoring.Infrastructure/EtlMonitoring.Infrastructure.csproj src/EtlMonitoring.Infrastructure/

RUN dotnet restore EtlMonitoring.sln

# Copiar restante do código-fonte
COPY src/ src/

RUN dotnet publish src/EtlMonitoring.Api/EtlMonitoring.Api.csproj \
-c Release \
-o /app/publish \
--no-restore

# =============================================================================
# Stage 2: Runtime
# =============================================================================
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
WORKDIR /app

# Criar usuário não-root para segurança
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

# Instalar curl para healthcheck
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*

COPY --from=build /app/publish .

# Criar diretório de logs com permissões corretas
RUN mkdir -p logs && chown -R appuser:appgroup /app

USER appuser

EXPOSE 8080

ENV ASPNETCORE_URLS=http://+:8080 \
ASPNETCORE_ENVIRONMENT=Production

ENTRYPOINT ["dotnet", "EtlMonitoring.Api.dll"]
67 changes: 67 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: datapulsecm

services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "${SA_PASSWORD:-DataPulseCM@2024!}"
MSSQL_PID: Express
ports:
- "1433:1433"
volumes:
- sqlserver_data:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"${SA_PASSWORD:-DataPulseCM@2024!}\" -Q 'SELECT 1' -No"]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
restart: unless-stopped

# ---------------------------------------------------------------------------
# API .NET 9
# ---------------------------------------------------------------------------
api:
build:
context: .
dockerfile: Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__DefaultConnection: "Server=sqlserver,1433;Database=DataPulseCM;User Id=sa;Password=${SA_PASSWORD:-DataPulseCM@2024!};TrustServerCertificate=True;"
SEQ_URL: "${SEQ_URL:-}"
SEQ_API_KEY: "${SEQ_API_KEY:-}"
ports:
- "5105:8080"
depends_on:
sqlserver:
condition: service_healthy
volumes:
- api_logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health/live || exit 1"]
interval: 30s
timeout: 30s
retries: 5
start_period: 30s

# ---------------------------------------------------------------------------
# Frontend React (nginx)
# ---------------------------------------------------------------------------
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
args:
VITE_API_URL: /api # nginx faz proxy /api → api:8080
ports:
- "80:80"
depends_on:
api:
condition: service_healthy
restart: unless-stopped

volumes:
sqlserver_data:
api_logs:
29 changes: 29 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# =============================================================================
# Stage 1: Build
# =============================================================================
FROM node:22-alpine AS build
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts

COPY . .

# VITE_API_URL é resolvido em build-time pelo Vite
# Em docker-compose, passe --build-arg VITE_API_URL=/api (nginx faz proxy)
ARG VITE_API_URL=/api
ENV VITE_API_URL=$VITE_API_URL

RUN npm run build

# =============================================================================
# Stage 2: Serve com nginx
# =============================================================================
FROM nginx:1.27-alpine AS final

COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
39 changes: 39 additions & 0 deletions frontend/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

# SPA fallback — React Router funciona corretamente
location / {
try_files $uri $uri/ /index.html;
}

# Proxy reverso para a API dentro do docker-compose
location /api/ {
proxy_pass http://api:8080/api/;
proxy_http_version 1.1;
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;
}

# Proxy para o Swagger
location /swagger/ {
proxy_pass http://api:8080/swagger/;
proxy_http_version 1.1;
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;
}

# Health check endpoint (evita log excessivo do nginx)
location /health {
proxy_pass http://api:8080/health;
proxy_http_version 1.1;
access_log off;
}
}
3 changes: 2 additions & 1 deletion frontend/src/components/Common/StatusBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { ReactElement } from 'react';
import { Chip } from '@mui/material';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import ErrorIcon from '@mui/icons-material/Error';
Expand All @@ -12,7 +13,7 @@ interface StatusBadgeProps {
export default function StatusBadge({ status }: StatusBadgeProps) {
const normalizedStatus = status?.toUpperCase() || '';

let config: { color: 'success' | 'error' | 'warning' | 'info' | 'default'; icon: JSX.Element; label: string };
let config: { color: 'success' | 'error' | 'warning' | 'info' | 'default'; icon: ReactElement; label: string };

if (normalizedStatus.includes('SUCESSO') || normalizedStatus === 'SUCCESS') {
config = { color: 'success', icon: <CheckCircleIcon fontSize="small" />, label: 'Sucesso' };
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/DashBoard/StatusChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ export default function StatusChart({ byStatus }: StatusChartProps) {
<XAxis dataKey="status" tick={{ fontSize: 13 }} />
<YAxis allowDecimals={false} tick={{ fontSize: 13 }} />
<Tooltip
formatter={(value: number) => [value, 'Execuções']}
labelFormatter={(label: string) => `Status: ${label}`}
formatter={(value: number | undefined) => [value ?? 0, 'Execuções'] as [number, string]}
labelFormatter={(label: unknown) => `Status: ${String(label)}`}
/>
<Bar dataKey="count" radius={[4, 4, 0, 0]}>
{chartData.map((entry, index) => (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/services/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';
import type { JobExecution, Statistics, JobExecutionDetail } from '../types/job.types';

const API_BASE = 'http://localhost:5105/api';
const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:5105/api';

const api = axios.create({
baseURL: API_BASE,
Expand Down
Loading
Loading