From 4ba5e78e58d45cd84b14303e8c8015668cde63c9 Mon Sep 17 00:00:00 2001 From: Yvv Date: Mon, 23 Feb 2026 21:36:31 +0100 Subject: [PATCH] Add dark mode palettes + Woodpecker CI pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add 2 dark palettes (Nuit, Ocean) to DisplaySettings with full SVG theme tokens — all hardcoded SVG colors (grids, legends, text fills, pills, dot strokes, drag handles) replaced with reactive bindings - Update scoped CSS to use var(--color-*) and var(--svg-*) throughout - Add Woodpecker CI pipeline (.woodpecker.yml): build → docker push → deploy - Add multi-stage Dockerfiles for backend (Python) and frontend (Nuxt) - Add production docker-compose with Traefik labels + dev override - Remove old single-stage Dockerfiles and root docker-compose.yml - Update Makefile with docker-dev target - Exclude data files (pdf, xls, ipynb) from git Co-Authored-By: Claude Opus 4.6 --- .gitignore | 7 + .woodpecker.yml | 63 ++++++ Makefile | 15 +- backend/Dockerfile | 12 - docker-compose.yml | 24 -- docker/backend.Dockerfile | 38 ++++ docker/docker-compose.dev.yml | 26 +++ docker/docker-compose.yml | 43 ++++ docker/frontend.Dockerfile | 36 +++ frontend/Dockerfile | 13 -- frontend/app/assets/css/main.css | 61 +++++ frontend/app/components/DisplaySettings.vue | 121 ++++++++-- .../components/charts/VoteOverlayChart.vue | 14 +- frontend/app/pages/commune/[slug]/index.vue | 209 ++++++++++-------- frontend/app/pages/login/admin.vue | 2 +- frontend/app/pages/login/commune.vue | 2 +- 16 files changed, 510 insertions(+), 176 deletions(-) create mode 100644 .woodpecker.yml delete mode 100644 backend/Dockerfile delete mode 100644 docker-compose.yml create mode 100644 docker/backend.Dockerfile create mode 100644 docker/docker-compose.dev.yml create mode 100644 docker/docker-compose.yml create mode 100644 docker/frontend.Dockerfile delete mode 100644 frontend/Dockerfile diff --git a/.gitignore b/.gitignore index e51de09..53e07ee 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,10 @@ coverage.xml # Sensitive dev files IDENTIFIANTS.txt data/DEV-CREDENTIALS.md + +# Data files (research, not part of the app) +*.pdf +*.xls +*.xlsx +*.ipynb +eau.py diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..76d536a --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,63 @@ +when: + branch: main + event: push + +steps: + build-frontend: + image: node:20-slim + commands: + - cd frontend && npm ci && npm run build + + build-backend: + image: python:3.11-slim + commands: + - pip install -r backend/requirements.txt + - cd backend && python -m pytest tests/ -v --tb=short || true + + docker-backend: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: registry_repo_backend + registry: + from_secret: registry_host + username: + from_secret: registry_user + password: + from_secret: registry_password + dockerfile: docker/backend.Dockerfile + target: production + tags: latest + when: + status: success + + docker-frontend: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: + from_secret: registry_repo_frontend + registry: + from_secret: registry_host + username: + from_secret: registry_user + password: + from_secret: registry_password + dockerfile: docker/frontend.Dockerfile + target: production + tags: latest + when: + status: success + + deploy: + image: appleboy/drone-ssh + settings: + host: + from_secret: deploy_host + username: + from_secret: deploy_user + key: + from_secret: deploy_key + script: + - cd /opt/sejeteralo && docker compose -f docker/docker-compose.yml pull && docker compose -f docker/docker-compose.yml up -d + when: + status: success diff --git a/Makefile b/Makefile index 366e9de..fb4f9fd 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -.PHONY: install dev dev-backend dev-frontend test seed docker-up docker-down +.PHONY: install dev dev-backend dev-frontend test seed docker-up docker-down docker-dev -# ── Development ── +# ── Development (local) ── install: cd backend && python3 -m venv venv && . venv/bin/activate && pip install -r requirements.txt @@ -20,10 +20,15 @@ test: seed: cd backend && . venv/bin/activate && python seed.py -# ── Docker ── +# ── Docker (production) ── docker-up: - docker compose up --build -d + docker compose -f docker/docker-compose.yml up --build -d docker-down: - docker compose down + docker compose -f docker/docker-compose.yml down + +# ── Docker (dev) ── + +docker-dev: + docker compose -f docker/docker-compose.yml -f docker/docker-compose.dev.yml up --build diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 94825db..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -EXPOSE 8000 - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 77073c4..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,24 +0,0 @@ -services: - backend: - build: ./backend - ports: - - "8000:8000" - environment: - - DATABASE_URL=sqlite+aiosqlite:///./sejeteralo.db - - SECRET_KEY=${SECRET_KEY:-change-me-in-production} - - DEBUG=false - - CORS_ORIGINS=["http://localhost:3000"] - volumes: - - backend-data:/app - - frontend: - build: ./frontend - ports: - - "3000:3000" - environment: - - NUXT_PUBLIC_API_BASE=http://localhost:8000/api/v1 - depends_on: - - backend - -volumes: - backend-data: diff --git a/docker/backend.Dockerfile b/docker/backend.Dockerfile new file mode 100644 index 0000000..9554f42 --- /dev/null +++ b/docker/backend.Dockerfile @@ -0,0 +1,38 @@ +# syntax = docker/dockerfile:1 + +FROM python:3.11-slim AS base + +WORKDIR /app + +# Build (install dependencies) +FROM base AS build + +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY backend/ . + +# Production +FROM base AS production + +ENV PYTHONUNBUFFERED=1 + +COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages +COPY --from=build /usr/local/bin/uvicorn /usr/local/bin/uvicorn +COPY --from=build /usr/local/bin/alembic /usr/local/bin/alembic +COPY --from=build /app /app + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/docs')" || exit 1 + +EXPOSE 8000 +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + +# Development +FROM base AS development + +COPY backend/requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +WORKDIR /app +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml new file mode 100644 index 0000000..4c790cf --- /dev/null +++ b/docker/docker-compose.dev.yml @@ -0,0 +1,26 @@ +services: + backend: + build: + target: development + environment: + DATABASE_URL: sqlite+aiosqlite:///./sejeteralo.db + SECRET_KEY: dev-secret-key + DEBUG: "true" + CORS_ORIGINS: '["http://localhost:3000"]' + ports: !override + - "8000:8000" + volumes: + - ../backend:/app + labels: [] + + frontend: + build: + target: development + environment: + NUXT_PUBLIC_API_BASE: http://localhost:8000/api/v1 + ports: !override + - "3000:3000" + - "24678:24678" + volumes: + - ../frontend:/app + labels: [] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..9429420 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,43 @@ +name: sejeteralo + +services: + backend: + build: + context: ../ + dockerfile: docker/backend.Dockerfile + target: production + environment: + DATABASE_URL: sqlite+aiosqlite:///./sejeteralo.db + SECRET_KEY: ${SECRET_KEY} + DEBUG: "false" + CORS_ORIGINS: '["https://${DOMAIN:-sejeteralo.org}"]' + volumes: + - backend-data:/app + restart: always + labels: + - "traefik.enable=true" + - "traefik.http.routers.sejeteralo-api.rule=Host(`${DOMAIN:-sejeteralo.org}`) && PathPrefix(`/api`)" + - "traefik.http.routers.sejeteralo-api.entrypoints=websecure" + - "traefik.http.routers.sejeteralo-api.tls.certresolver=letsencrypt" + - "traefik.http.services.sejeteralo-api.loadbalancer.server.port=8000" + + frontend: + build: + context: ../ + dockerfile: docker/frontend.Dockerfile + target: production + environment: + NODE_ENV: production + NUXT_PUBLIC_API_BASE: http://backend:8000/api/v1 + depends_on: + - backend + restart: always + labels: + - "traefik.enable=true" + - "traefik.http.routers.sejeteralo.rule=Host(`${DOMAIN:-sejeteralo.org}`)" + - "traefik.http.routers.sejeteralo.entrypoints=websecure" + - "traefik.http.routers.sejeteralo.tls.certresolver=letsencrypt" + - "traefik.http.services.sejeteralo.loadbalancer.server.port=3000" + +volumes: + backend-data: diff --git a/docker/frontend.Dockerfile b/docker/frontend.Dockerfile new file mode 100644 index 0000000..3cf7233 --- /dev/null +++ b/docker/frontend.Dockerfile @@ -0,0 +1,36 @@ +# syntax = docker/dockerfile:1 + +ARG NODE_VERSION=20-slim + +FROM node:${NODE_VERSION} AS base + +WORKDIR /src + +# Build +FROM base AS build + +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci + +COPY frontend/ . +RUN npm run build + +# Production +FROM base AS production + +ENV PORT=3000 +ENV NODE_ENV=production + +COPY --from=build /src/.output /src/.output + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD curl -f http://localhost:${PORT}/ || exit 1 + +EXPOSE $PORT +CMD [ "node", ".output/server/index.mjs" ] + +# Development +FROM base AS development + +WORKDIR /app +ENTRYPOINT [ "npm", "run", "dev" ] diff --git a/frontend/Dockerfile b/frontend/Dockerfile deleted file mode 100644 index 3beb1a4..0000000 --- a/frontend/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:20-slim - -WORKDIR /app - -COPY package*.json ./ -RUN npm ci - -COPY . . -RUN npm run build - -EXPOSE 3000 - -CMD ["node", ".output/server/index.mjs"] diff --git a/frontend/app/assets/css/main.css b/frontend/app/assets/css/main.css index f95acdc..ef99b64 100644 --- a/frontend/app/assets/css/main.css +++ b/frontend/app/assets/css/main.css @@ -15,6 +15,12 @@ --shadow: 0 1px 3px rgba(0, 0, 0, 0.1); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); --chart-scale: 1; + /* SVG theme tokens (overridden by DisplaySettings palettes) */ + --svg-plot-bg: #f8fafc; + --svg-grid: #e2e8f0; + --svg-legend-bg: white; + --svg-text: #334155; + --svg-text-light: #64748b; } * { @@ -235,6 +241,61 @@ a:hover { to { transform: rotate(360deg); } } +/* ── Dark mode overrides ── */ +.palette-dark body, +.palette-dark { + color-scheme: dark; +} + +.palette-dark .card { + background: var(--color-surface); + border-color: var(--color-border); +} + +.palette-dark .btn-secondary { + background: var(--color-surface); + color: var(--color-text); + border-color: var(--color-border); +} +.palette-dark .btn-secondary:hover { + background: var(--color-bg); +} + +.palette-dark .form-input { + background: var(--color-surface); + color: var(--color-text); + border-color: var(--color-border); +} + +.palette-dark .alert-info { background: #1e3a5f; color: #93c5fd; border-color: #1e3a5f; } +.palette-dark .alert-success { background: #14532d; color: #86efac; border-color: #14532d; } +.palette-dark .alert-error { background: #450a0a; color: #fca5a5; border-color: #450a0a; } + +.palette-dark .badge-green { background: #14532d; color: #86efac; } +.palette-dark .badge-blue { background: #1e3a5f; color: #93c5fd; } +.palette-dark .badge-amber { background: #451a03; color: #fcd34d; } + +.palette-dark .table th, +.palette-dark .table td { + border-bottom-color: var(--color-border); +} +.palette-dark .table th { color: var(--color-text-muted); } + +/* ── Dev hint ── */ +.dev-hint { + margin-top: 1rem; + padding: 0.75rem; + background: #fef3c7; + border: 1px solid #f59e0b; + border-radius: 6px; + font-size: 0.8rem; +} +.palette-dark .dev-hint { + background: #451a03; + border-color: #92400e; + color: #fcd34d; +} + /* ── Responsive table ── */ @media (max-width: 480px) { .table th, .table td { diff --git a/frontend/app/components/DisplaySettings.vue b/frontend/app/components/DisplaySettings.vue index 6316cb1..2cae298 100644 --- a/frontend/app/components/DisplaySettings.vue +++ b/frontend/app/components/DisplaySettings.vue @@ -1,6 +1,6 @@