From 1b14553417353dc24b7f3ec36a44f8816c31ce7b Mon Sep 17 00:00:00 2001 From: Jordy Hoogeveen Date: Thu, 10 Sep 2026 14:03:34 +0200 Subject: [PATCH 1/4] bugfixes --- .env.example | 7 +- BEHEER.md | 98 ++++++++++++++++++------- Changelog.md | 29 ++++++++ README.md | 19 +++-- docker-compose.dev.yml | 5 ++ frontend/package.json | 2 +- frontend/src/App.css | 34 +++++++++ frontend/src/hooks/useKolomBreedtes.js | 78 ++++++++++++++++++++ frontend/src/pages/EigenVermogenTab.jsx | 43 ++++++++--- frontend/src/pages/VoortgangTab.jsx | 38 +++++++--- frontend/vite.config.js | 2 +- package.json | 2 +- 12 files changed, 299 insertions(+), 58 deletions(-) create mode 100644 frontend/src/hooks/useKolomBreedtes.js diff --git a/.env.example b/.env.example index 94375c6..86357f3 100644 --- a/.env.example +++ b/.env.example @@ -17,6 +17,9 @@ CORS_ORIGIN=http://localhost:3400 # Update-notificaties (leeg = melding uit) UPDATE_CHECK_URL=https://vibehoogie.duckdns.org/api/v1/repos/vibe/VibeFinance/releases/latest -# Nodig zolang de Gitea-repo niet publiek leesbaar is; scope read:repository. -# Zonder token geeft releases/latest 404 en blijft de update-melding leeg. +# De Gitea-organisatie is publiek, dus de check werkt anoniem — laat dit staan. +# Alleen nodig als de organisatie weer privé wordt; scope read:repository. +# Dan geeft releases/latest zonder token 404 en blijft de melding leeg. +# Let op: een ongeldig token geeft 401, ook op een publieke repo. Bij twijfel +# de regel uitgecommentarieerd laten in plaats van een token te gokken. # UPDATE_CHECK_TOKEN= diff --git a/BEHEER.md b/BEHEER.md index fb30e79..ceaea8e 100644 --- a/BEHEER.md +++ b/BEHEER.md @@ -32,6 +32,25 @@ Vite proxyt `/api` naar `localhost:3001` binnen dezelfde container. > ⚠️ Alleen `.\dev.ps1 down` wist de development-database (`docker compose down -v`). > `.\dev.ps1 up` doet een gewone `down` en laat het volume staan. +### Welke versie de dev-app toont + +De versie in "Over VibeFinance" komt uit `VITE_APP_VERSION`. `dev.ps1` leest die +uit de **root** `package.json`, zet hem in de omgeving en synct hem naar +`frontend/package.json`. In de dev-container is het een *runtime*-variabele +(`docker-compose.dev.yml`, sectie `environment`) en geen build-arg: Vite bakt +`__APP_VERSION__` in bij het **starten** van de dev-server, niet bij het bouwen +van het image. Is hij leeg, dan valt `vite.config.js` terug op +`frontend/package.json`. + +Ziet de app een oude versie, dan draait de dev-server nog van vóór de bump — +`.\dev.ps1 up` hercreëert de container en start Vite opnieuw. Start je de stack +handmatig met `docker compose -f docker-compose.dev.yml up`, dan staat +`VITE_APP_VERSION` niet in je shell en zie je de versie uit +`frontend/package.json`. + +> Een versie met een suffix (`1.0.1-dev`) onderdrukt de update-melding +> permanent — zie [Update-notificaties](#update-notificaties). + --- ## Testen @@ -77,27 +96,35 @@ docker compose -f docker-compose.dev.yml exec -w /app/frontend app npm run lint ## Tokens +> **De `Vibe`-organisatie staat op public.** Alles wat alleen *leest* heeft +> daardoor geen token meer nodig: `git clone`, `docker compose pull` en de +> update-check van de app werken anoniem. Het host-token hieronder is dus +> optioneel geworden. *Schrijven* blijft geauthenticeerd — een publieke +> organisatie verandert daar niets aan. + Twee tokens, gesplitst op machine en op read/write. De grens die iets betekent ligt tussen werkstation en server, niet tussen push en release: op één machine kan wie het ene token kan lezen, het andere ook. -| Token | Machine | Scopes | Gebruikt voor | -|---|---|---|---| -| `vibefinance-workstation` | dit werkstation | `write:package`, `write:repository` | `docker push` (`release.ps1`, `dev.ps1 push`) en het aanmaken van de Gitea-release | -| `vibefinance-host` | de Docker-host | `read:package`, `read:repository` | `docker compose pull` en de update-check van de app | +| Token | Machine | Scopes | Gebruikt voor | Nog nodig? | +|---|---|---|---|---| +| `vibefinance-workstation` | dit werkstation | `write:package`, `write:repository` | `docker push` (`release.ps1`, `dev.ps1 push`) en het aanmaken van de Gitea-release | **Ja** | +| `vibefinance-host` | de Docker-host | `read:package`, `read:repository` | `docker compose pull` en de update-check van de app | Nee, zolang de organisatie public is | Aanmaken: Gitea → Settings → Applications → Generate New Token. Doe dat onder een -account dat lid is van de `Vibe`-organisatie — die is privé, dus een token van -een buitenstaander ziet de repo niet. +account dat lid is van de `Vibe`-organisatie: voor schrijven volstaat +organisatie-lidmaatschap niet automatisch, het account moet ook push-rechten op de +repo hebben. **Waar ze terechtkomen** | | Werkstation | Docker-host | |---|---|---| -| Registry | `docker login` (credential store), of `REGISTRY_USER` + `REGISTRY_TOKEN` in `.env.release` | `docker login` als de gebruiker die deployt | -| Gitea-API | `GITEA_TOKEN` in `.env.release` | `UPDATE_CHECK_TOKEN` in `.env` | +| Registry | `docker login` (credential store), of `REGISTRY_USER` + `REGISTRY_TOKEN` in `.env.release` | niets nodig voor een pull; anders `docker login` als de gebruiker die deployt | +| Gitea-API | `GITEA_TOKEN` in `.env.release` | niets nodig; optioneel `UPDATE_CHECK_TOKEN` in `.env` | -**Waarom het host-token read-only blijft.** Dat staat in platte tekst in `.env` op +**Waarom een host-token read-only blijft** — voor het geval de organisatie weer +privé gaat en je er alsnog één neerzet. Dat staat in platte tekst in `.env` op een server die de app op poort 3400 publiceert. Met schrijfrechten kan wie bij dat bestand komt — via de container, een shell op die host, of een backup — een eigen image als `latest` naar de registry pushen, en die host pullt `latest` zelf. @@ -109,7 +136,7 @@ Read-only houdt je eigen deploy-pad uit de aanvalsketen. |---|---| | 401 | Token ongeldig of verlopen. Op de registry: helemaal niet ingelogd. | | 403 | Het token mag de repo wél zien, maar mist de **scope** voor dit endpoint. Gitea noemt de vereiste scope in de response-body. | -| 404 | Het token mag de repo niet zien. Gitea geeft geen 403, om niet te verklappen dat iets bestaat: verkeerd account, of geen toegang tot de organisatie. | +| 404 | Het token mag de repo niet zien. Gitea geeft geen 403, om niet te verklappen dat iets bestaat: verkeerd account, of geen toegang tot de organisatie. Nu de organisatie public is, wijst een 404 op een leesactie eerder op een verkeerde URL dan op rechten. | Scopes van een bestaand token zijn in Gitea niet aan te passen. Bij een 403 maak je dus een nieuw token aan; bijwerken kan niet. @@ -126,9 +153,21 @@ Vanuit de app-container op de host valt de body van een fout anders weg — busybox-`wget` gooit die bij een foutstatus weg, en Node zit in het image: ```bash -docker exec vibefinance_app node -e 'fetch(process.env.UPDATE_CHECK_URL,{headers:{Accept:"application/json",Authorization:`token ${process.env.UPDATE_CHECK_TOKEN}`}}).then(async r=>console.log(r.status, (await r.text()).slice(0,300)))' +# Anoniem, zoals de app het doet zonder UPDATE_CHECK_TOKEN +docker exec vibefinance_app node -e 'fetch(process.env.UPDATE_CHECK_URL,{headers:{Accept:"application/json"}}).then(async r=>console.log(r.status, (await r.text()).slice(0,300)))' + +# Mét token, precies zoals routes/update.js de header opbouwt +docker exec vibefinance_app node -e 'const t=process.env.UPDATE_CHECK_TOKEN; fetch(process.env.UPDATE_CHECK_URL,{headers:{Accept:"application/json",...(t&&{Authorization:`token ${t}`})}}).then(async r=>console.log(r.status, (await r.text()).slice(0,300)))' ``` +> Let op: een **ongeldig** token geeft `401`, ook op deze publieke repo — Gitea +> valt bij een verkeerd token niet terug op anonieme toegang. Krijg je 401 +> terwijl anoniem `200` geeft, haal `UPDATE_CHECK_TOKEN` dan uit `.env` in plaats +> van hem te vervangen. Een *leeg* token is onschuldig: `Authorization: token ` +> negeert Gitea (`200`), en `config/index.js` zet met `|| null` bij een lege +> waarde helemaal geen header. Nagemeten op 2026-09-10: +> geen header `200`, `token ongeldig123` `401`, leeg `200`. + ### Tokens opruimen De lijst onder Settings → Applications heeft een kolom **Last used**. Zet eerst @@ -406,20 +445,8 @@ docker-credential-desktop list ### Pullen op een deploy-machine -Het package is privé, dus ook een **pull** vereist een login — anoniem geeft -`401 Unauthorized` (`docker compose pull` meldt dan "no basic auth credentials" -of "pull access denied"). Eenmalig op de Docker-host: - -```bash -docker login vibehoogie.duckdns.org -# gebruikersnaam: je Gitea-account -# wachtwoord: het token van deze host (vibefinance-host, read:package) -``` - -Daarna werkt `docker compose pull && docker compose up -d`. De login komt in -`~/.docker/config.json` van de gebruiker die het commando uitvoert — doe het dus -als dezelfde gebruiker (bij `sudo docker` is dat root). Log in op -`vibehoogie.duckdns.org` **zonder** `:3000`. +De organisatie is public, dus een **pull** werkt anoniem — `docker compose pull && +docker compose up -d` en klaar, zonder `docker login`. Controleren zonder te pullen: @@ -427,8 +454,23 @@ Controleren zonder te pullen: docker manifest inspect vibehoogie.duckdns.org/vibe/vibefinance-app:latest ``` -Ditzelfde token gaat als `UPDATE_CHECK_TOKEN` in de `.env` op deze host, voor de -update-check van de app — één token voor beide, zie [Tokens](#tokens). +Krijg je `401 Unauthorized`, "no basic auth credentials" of "pull access denied", +dan is het package niet publiek leesbaar (organisatie weer op privé, of de +package-eigenaar wijkt af van de organisatie). Dan alsnog eenmalig op de host: + +```bash +docker login vibehoogie.duckdns.org +# gebruikersnaam: je Gitea-account +# wachtwoord: een token met read:package +``` + +De login komt in `~/.docker/config.json` van de gebruiker die het commando +uitvoert — doe het dus als dezelfde gebruiker (bij `sudo docker` is dat root). +Log in op `vibehoogie.duckdns.org` **zonder** `:3000`. + +Moet je terug naar een token, dan kan datzelfde token ook als +`UPDATE_CHECK_TOKEN` in de `.env` op deze host — één token voor beide, zie +[Tokens](#tokens). --- @@ -464,7 +506,7 @@ uit `.env` (of de default in `docker-compose.yml`). Beschikbare backend-variabel | Variabele | Betekenis | |---|---| | `UPDATE_CHECK_URL` | Release-API die gepolld wordt. Leeg = melding uit. | -| `UPDATE_CHECK_TOKEN` | Nodig zolang de `Vibe`-organisatie in Gitea privé is: het token van de host (`read:repository`, zie [Tokens](#tokens)). Zonder token geeft `releases/latest` 404, met een verkeerde scope 403 — in beide gevallen blijft de melding leeg, met alleen een `WARN` in het containerlog. | +| `UPDATE_CHECK_TOKEN` | Optioneel. De `Vibe`-organisatie is public, dus `releases/latest` antwoordt anoniem met `200` en de check heeft geen token nodig. Gaat de organisatie weer privé, dan is het host-token nodig (`read:repository`, zie [Tokens](#tokens)): zonder geeft `releases/latest` dan 404, met een verkeerde scope 403 — in beide gevallen blijft de melding leeg, met alleen een `WARN` in het containerlog. | **Een release publiceren** diff --git a/Changelog.md b/Changelog.md index 920eeee..5405180 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,11 +2,40 @@ ## [Niet vrijgegeven] +### Toegevoegd +- Kolommen in de tabellen op Eigen Vermogen ("Geïnvesteerde Fiat") en Voortgang + zijn versleepbaar: pak de rechterrand van een kolomkop. De breedtes gaan per + tabel naar localStorage, dus ze overleven een tabwissel en een herlaad; + dubbelklik op de greep zet één kolom terug op zijn standaardbreedte. De logica + staat in de nieuwe hook `frontend/src/hooks/useKolomBreedtes.js`, de greep zelf + is `.kolom-grip` in `App.css` + ### Gewijzigd +- De `Vibe`-organisatie in Gitea staat op **public**, dus lezen vereist geen + token meer: `git clone`, `docker compose pull` en de update-check werken + anoniem. `UPDATE_CHECK_TOKEN` in `.env` is daarmee optioneel — de code + ondersteunt hem nog als vangnet voor het geval de organisatie weer privé + wordt. Schrijven verandert niet: `docker push` en het aanmaken van een release + hebben nog steeds het werkstation-token nodig +- De tabellen op Eigen Vermogen en Voortgang staan op `table-layout: fixed`, + nodig om ingestelde kolombreedtes te respecteren. Gevolg: lange tekst rekt een + kolom niet meer op maar wordt afgekapt met een ellips. De Opmerkingen-cellen + hebben een `title`, dus hover geeft de volledige tekst - Het label bij een beschikbare update in "Over VibeFinance" is "Nieuwe versie" (was "Nieuwste versie" in 1.0.0) ### Opgelost +- De sticky kolomkop van die tabellen was in dark mode maar 30% dekkend: hij + gebruikte `T.card` (`rgba(22,23,27,0.30)`), waardoor de rijen tijdens het + scrollen zichtbaar door de koptekst heen liepen. Nu `T.popupBg`, de + ondoorzichtige variant die al in het thema stond +- In dev bakte Vite de versie uit `frontend/package.json` in in plaats van de + versie uit de root `package.json`. `VITE_APP_VERSION` kwam alleen als + build-arg mee, maar de `development`-stage in de `Dockerfile` declareert die + `ARG` niet — en Vite bakt de constante in bij het *starten* van de dev-server, + niet bij het bouwen van het image. De variabele staat nu in de + runtime-omgeving van de dev-container; `vite.config.js` valt met `||` (was + `??`) terug op `package.json` als hij leeg is - API-antwoorden sturen nu `Cache-Control: no-store`. Ze hadden alleen een `ETag` en geen cache-beleid, waardoor de browser zelf mocht bepalen hoe lang hij ze hergebruikte — per browser en profiel verschillend. Gevolg: de diff --git a/README.md b/README.md index bd0d0f1..58b26ae 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,17 @@ openssl rand -hex 64 ```bash cp .env.example .env # en vul de waarden in -# Het app-image staat in een privé-registry, dus eerst inloggen: -docker login vibehoogie.duckdns.org # wachtwoord = Gitea-token met read:package - docker compose pull && docker compose up -d ``` +De `Vibe`-organisatie in Gitea is publiek, dus een pull werkt anoniem — geen +`docker login` nodig. Krijg je toch `401 Unauthorized` of "pull access denied", +dan staat de organisatie weer op privé: + +```bash +docker login vibehoogie.duckdns.org # wachtwoord = Gitea-token met read:package +``` + Navigeer naar `http://:3400` en registreer je account. De **eerste** geregistreerde gebruiker krijgt automatisch de Admin-rol. De container luistert intern op poort 3001 en serveert daar zowel de frontend @@ -135,6 +140,10 @@ pusht naar de registry, zet de Git-tag en publiceert de Gitea-release. | 📈 **Voortgang** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) | | 👥 **Gebruikers** | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) | +De kolommen in de tabellen op Eigen vermogen en Voortgang zijn versleepbaar: pak +de rechterrand van een kolomkop. De breedtes worden per tabel in de browser +bewaard; dubbelklik op de greep voor de standaardbreedte. + --- ## 🏗️ Architectuur @@ -165,7 +174,7 @@ vibefinance/ │ ├── version.js # Draaiende versie (__APP_VERSION__ uit de build) │ ├── constants/ # Kleuren, tabs, begindata │ ├── utils/ # Formatters, helpers -│ ├── hooks/ # useTheme, useUpdateCheck +│ ├── hooks/ # useTheme, useUpdateCheck, useKolomBreedtes │ ├── context/ # AppContext (global state + API sync) │ ├── components/ │ │ ├── ui/ # Primitieve UI (VCard, DonutChart…) @@ -259,7 +268,7 @@ Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via | `PORT` | `3001` | Poort binnen de container | | `STATIC_DIR` | `/app/public` | Map met de frontend-build | | `UPDATE_CHECK_URL` | Gitea releases-API | Bron update-melding (leeg = uit) | -| `UPDATE_CHECK_TOKEN` | — | Nodig zolang de Gitea-repo privé is; scope `read:repository` | +| `UPDATE_CHECK_TOKEN` | — | Optioneel: de Gitea-repo is publiek, dus de check werkt anoniem. Alleen nodig als de organisatie weer privé wordt; scope `read:repository` | --- diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 77159d7..9c8cefc 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -49,6 +49,11 @@ services: environment: NODE_ENV: development PORT: 3001 + # Vite bakt de versie in bij het STARTEN van de dev-server, dus die moet + # in de runtime-omgeving staan; de build-arg hierboven bereikt alleen de + # frontend-build-stage en doet niets voor target: development. + # Leeg = vite.config.js valt terug op frontend/package.json. + VITE_APP_VERSION: ${VITE_APP_VERSION:-} JWT_SECRET: dev-secret-change-in-production JWT_EXPIRES: 7d CORS_ORIGIN: http://localhost diff --git a/frontend/package.json b/frontend/package.json index 0eed82e..3de1039 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance-frontend", - "version": "1.0.0", + "version": "1.0.1-dev", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/App.css b/frontend/src/App.css index f75a672..731aefb 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -26,3 +26,37 @@ input[type="number"]::-webkit-inner-spin-button { @keyframes spin { to { transform: rotate(360deg); } } + +/* Sleepvlak op de rechterrand van een tabelkop — zie hooks/useKolomBreedtes.js. + Blijft binnen de cel (geen negatieve offset), anders duwt de laatste kolom + een horizontale scrollbar tevoorschijn. */ +.kolom-grip { + position: absolute; + top: 0; + right: 0; + width: 6px; + height: 100%; + cursor: col-resize; + user-select: none; + touch-action: none; +} + +/* Het streepje erft de kleur van de kop, dus per categorie de accentkleur. */ +.kolom-grip::after { + content: ""; + position: absolute; + top: 25%; + right: 2px; + width: 1px; + height: 50%; + background: currentColor; + opacity: 0.3; +} + +.kolom-grip:hover::after, +.kolom-grip:active::after { + top: 0; + height: 100%; + width: 2px; + opacity: 1; +} diff --git a/frontend/src/hooks/useKolomBreedtes.js b/frontend/src/hooks/useKolomBreedtes.js new file mode 100644 index 0000000..eef3ea6 --- /dev/null +++ b/frontend/src/hooks/useKolomBreedtes.js @@ -0,0 +1,78 @@ +import { useRef, useState } from "react"; +import { loadFromStorage, saveToStorage } from "../utils/storage.js"; + +// Smaller dan dit wordt een kolom onleesbaar; de greep blijft wel pakbaar. +const MIN_BREEDTE = 60; + +/** + * Versleepbare kolombreedtes voor een tabel. + * + * `kolommen` is een array van { id, breedte } in weergave-volgorde, waarbij + * `breedte` de standaard in pixels is. De aanpassingen van de gebruiker gaan + * per tabel naar localStorage, zodat ze een tabwissel of herlaad overleven. + * Kolommen die later verschijnen (een categorie die de gebruiker toevoegt) + * krijgen hun standaardbreedte; opgeslagen breedtes van verdwenen kolommen + * blijven ongebruikt staan en storen niets. + * + * De tabel moet `tableLayout: "fixed"` krijgen met een , en als + * `width` de teruggegeven `totaal`. Met `width: "100%"` verdeelt de browser de + * ruimte namelijk proportioneel en levert slepen niet exact de gesleepte + * pixels op. + * + * Geeft terug: + * breedtes px per kolom-id, standaard plus aanpassing + * totaal som van de breedtes, voor de `width` van de tabel + * grip props voor het sleepvlak in de van die kolom + * herstel alle kolommen terug naar hun standaardbreedte + */ +export function useKolomBreedtes(opslagKey, kolommen) { + const [aangepast, setAangepast] = useState(() => loadFromStorage(opslagKey) ?? {}); + // Spiegelt `aangepast` zodat het slepen en het opslaan bij het loslaten niet + // van de state-updater afhangen (die mag geen bijwerkingen hebben). + const laatste = useRef(aangepast); + const sleep = useRef(null); + + const breedtes = {}; + for (const k of kolommen) breedtes[k.id] = aangepast[k.id] ?? k.breedte; + const totaal = kolommen.reduce((som, k) => som + breedtes[k.id], 0); + + const zet = (volgende) => { laatste.current = volgende; setAangepast(volgende); }; + + const grip = (id) => ({ + className: "kolom-grip", + role: "separator", + title: "Sleep om de kolom breder of smaller te maken — dubbelklik voor de standaardbreedte", + onPointerDown: (e) => { + // Pointer capture: het slepen loopt door buiten de greep, ook boven de + // rijen of naast de tabel, zonder losse listeners op window. + e.preventDefault(); + e.currentTarget.setPointerCapture(e.pointerId); + sleep.current = { id, startX: e.clientX, startBreedte: breedtes[id] }; + }, + onPointerMove: (e) => { + if (!sleep.current) return; + const { id: kolomId, startX, startBreedte } = sleep.current; + const nieuw = Math.max(MIN_BREEDTE, Math.round(startBreedte + e.clientX - startX)); + if (laatste.current[kolomId] === nieuw) return; + zet({ ...laatste.current, [kolomId]: nieuw }); + }, + onPointerUp: () => { + if (!sleep.current) return; + sleep.current = null; + // Pas bij het loslaten opslaan; tijdens het slepen zou elke pixel een + // localStorage-write kosten. + saveToStorage(opslagKey, laatste.current); + }, + onDoubleClick: () => { + // Zonder destructuring-omit: die laat een ongebruikte variabele achter. + const rest = { ...laatste.current }; + delete rest[id]; + zet(rest); + saveToStorage(opslagKey, rest); + }, + }); + + const herstel = () => { zet({}); saveToStorage(opslagKey, {}); }; + + return { breedtes, totaal, grip, herstel }; +} diff --git a/frontend/src/pages/EigenVermogenTab.jsx b/frontend/src/pages/EigenVermogenTab.jsx index fc5acdf..3d4a706 100644 --- a/frontend/src/pages/EigenVermogenTab.jsx +++ b/frontend/src/pages/EigenVermogenTab.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { fmt, fmtDatum } from "../utils/format.js"; +import { useKolomBreedtes } from "../hooks/useKolomBreedtes.js"; import { PURPLE, PURPLE_LIGHT, GREEN, RED, CAT_COLORS } from "../constants/index.js"; import { VCard, DonutChart, Overlay, ModalBox, ModalHeader, ModalFooter, @@ -250,8 +251,22 @@ function EVVoortgangTabel() { const removeRow = (datum) => setEvRows(evRows.filter((r) => r.datum !== datum)); - const th = { padding: "8px 10px", fontSize: 11, color: T.muted, fontWeight: 600, textAlign: "left", whiteSpace: "nowrap", background: T.card, position: "sticky", top: 0, zIndex: 1, borderBottom: `1px solid ${T.border}` }; - const td = { padding: "7px 10px", fontSize: 12, color: T.text, borderBottom: `1px solid ${T.border}` }; + // Kolommen in weergave-volgorde, met hun standaardbreedte. De categorieën + // zijn dynamisch, dus de lijst wordt per render opgebouwd. + const kolommen = [ + { id: "datum", breedte: 100 }, + ...cats.map((c) => ({ id: c.id, breedte: 100 })), + { id: "nettoWaarde", breedte: 115 }, + { id: "verschil", breedte: 105 }, + { id: "opmerkingen", breedte: 150 }, + ...(!locked ? [{ id: "acties", breedte: 75 }] : []), + ]; + const { breedtes, totaal, grip } = useKolomBreedtes("ev_kolom_breedtes", kolommen); + + // overflow/ellipsis horen bij tableLayout "fixed": de inhoud rekt de kolom + // niet meer op, dus zonder afkappen loopt lange tekst de buurcel binnen. + const th = { padding: "8px 10px", fontSize: 11, color: T.muted, fontWeight: 600, textAlign: "left", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", background: T.popupBg, position: "sticky", top: 0, zIndex: 1, borderBottom: `1px solid ${T.border}` }; + const td = { padding: "7px 10px", fontSize: 12, color: T.text, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", borderBottom: `1px solid ${T.border}` }; return (
@@ -326,15 +341,22 @@ function EVVoortgangTabel() { {evRows.length > 0 ? (
- +
+ + {kolommen.map((k) => )} + - - {cats.map((c) => )} - - - - {!locked && } + + {cats.map((c) => ( + + ))} + + + + {!locked && } @@ -349,7 +371,8 @@ function EVVoortgangTabel() { {cats.map((c) => )} - + {/* title: bij een smalle kolom is de tekst afgekapt */} + {!locked && (
Datum{c.naam}Netto WaardeVerschilOpmerkingenDatum + {c.naam} + Netto WaardeVerschilOpmerkingen
{fmt(row[c.id] || 0)}{fmt(row.nettoWaarde || 0)} {vLabel}{row.opmerkingen || "—"}{row.opmerkingen || "—"}
diff --git a/frontend/src/pages/VoortgangTab.jsx b/frontend/src/pages/VoortgangTab.jsx index 06c2004..5001975 100644 --- a/frontend/src/pages/VoortgangTab.jsx +++ b/frontend/src/pages/VoortgangTab.jsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { fmt, fmtDatum } from "../utils/format.js"; +import { useKolomBreedtes } from "../hooks/useKolomBreedtes.js"; import { PURPLE, PURPLE_LIGHT, RED, GREEN } from "../constants/index.js"; import { VCard, DonutChart, Overlay, ModalBox, ModalHeader, ModalFooter } from "../components/ui/index.jsx"; @@ -261,8 +262,21 @@ export default function VoortgangTab() { setData({ ...data, schuld: { ...schuld, schuldHistory: schuldHistory.filter((s) => s.datum !== datum) } }); }; - const th = { padding: "8px 10px", fontSize: 11, color: T.muted, fontWeight: 600, textAlign: "left", whiteSpace: "nowrap", background: T.card, position: "sticky", top: 0, zIndex: 1, borderBottom: `1px solid ${T.border}` }; - const td = { padding: "7px 10px", fontSize: 12, color: T.text, borderBottom: `1px solid ${T.border}` }; + // Kolommen in weergave-volgorde, met hun standaardbreedte. + const kolommen = [ + { id: "datum", breedte: 120 }, + { id: "ev", breedte: 175 }, + { id: "schuld", breedte: 160 }, + { id: "netto", breedte: 175 }, + { id: "opmerkingen", breedte: 340 }, + ...(!locked ? [{ id: "acties", breedte: 90 }] : []), + ]; + const { breedtes, totaal, grip } = useKolomBreedtes("voortgang_kolom_breedtes", kolommen); + + // overflow/ellipsis horen bij tableLayout "fixed": de inhoud rekt de kolom + // niet meer op, dus zonder afkappen loopt lange tekst de buurcel binnen. + const th = { padding: "8px 10px", fontSize: 11, color: T.muted, fontWeight: 600, textAlign: "left", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", background: T.popupBg, position: "sticky", top: 0, zIndex: 1, borderBottom: `1px solid ${T.border}` }; + const td = { padding: "7px 10px", fontSize: 12, color: T.text, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", borderBottom: `1px solid ${T.border}` }; return (
@@ -396,15 +410,18 @@ export default function VoortgangTab() { )} {voortgangRows.length > 0 ? (
- +
+ + {kolommen.map((k) => )} + - - - - - - {!locked && } + + + + + + {!locked && } @@ -414,7 +431,8 @@ export default function VoortgangTab() { - + {/* title: bij een smalle kolom is de tekst afgekapt */} + {!locked && (
DatumEigen VermogenSchuldNettowaardeOpmerkingenDatumEigen VermogenSchuldNettowaardeOpmerkingen
{row.ev !== null ? fmt(row.ev) : "—"} {row.schuld !== null ? fmt(row.schuld) : "—"} = 0 ? GREEN : RED) : T.muted }}>{row.netto !== null ? fmt(row.netto) : "—"}{row.opmerkingen || "—"}{row.opmerkingen || "—"} diff --git a/frontend/vite.config.js b/frontend/vite.config.js index ce46b5e..ff8615f 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -41,6 +41,6 @@ export default defineConfig({ define: { // VITE_API_URL wordt ingebakken tijdens build __API_URL__: JSON.stringify(process.env.VITE_API_URL ?? "/api"), - __APP_VERSION__: JSON.stringify(process.env.VITE_APP_VERSION ?? pkg.version), + __APP_VERSION__: JSON.stringify(process.env.VITE_APP_VERSION || pkg.version), }, }); diff --git a/package.json b/package.json index 88e575a..ba17b36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance", - "version": "1.0.0", + "version": "1.0.1", "description": "VibeFinance — persoonlijk vermogensbeheer", "private": true, "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance", -- 2.47.3 From 6fd4f4766b26f03f1d0be8a831c398a2d328bfdb Mon Sep 17 00:00:00 2001 From: Jordy Hoogeveen Date: Thu, 10 Sep 2026 14:16:09 +0200 Subject: [PATCH 2/4] Versie 1.0.1 in README-badge en frontend, changelog vrijgegeven Co-Authored-By: Claude Opus 5 (1M context) --- Changelog.md | 2 +- README.md | 2 +- frontend/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index 5405180..0710583 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,6 @@ # Changelog -## [Niet vrijgegeven] +## [1.0.1] - 2026-09-10 ### Toegevoegd - Kolommen in de tabellen op Eigen Vermogen ("Geïnvesteerde Fiat") en Voortgang diff --git a/README.md b/README.md index 58b26ae..d194193 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer ![Node.js](https://img.shields.io/badge/Node.js_22-339933?style=flat&logo=node.js&logoColor=white) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL_18-4169E1?style=flat&logo=postgresql&logoColor=white) ![Docker](https://img.shields.io/badge/Docker_Compose-2496ED?style=flat&logo=docker&logoColor=white) -![Version](https://img.shields.io/badge/versie-1.0.0-8b5cf6?style=flat) +![Version](https://img.shields.io/badge/versie-1.0.1-8b5cf6?style=flat) ![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white) VibeFinance dashboard diff --git a/frontend/package.json b/frontend/package.json index 3de1039..2864ae1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance-frontend", - "version": "1.0.1-dev", + "version": "1.0.1", "private": true, "type": "module", "scripts": { -- 2.47.3 From 857322a490f29dfd5114d7e2cc49ee488d5c8f97 Mon Sep 17 00:00:00 2001 From: Jordy Hoogeveen Date: Thu, 10 Sep 2026 14:30:18 +0200 Subject: [PATCH 3/4] =?UTF-8?q?Tokendocumentatie=20terug=20naar=20=C3=A9?= =?UTF-8?q?=C3=A9n=20werkstation-token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- BEHEER.md | 98 ++++++++++++++++++++++++++++++---------------------- Changelog.md | 5 +++ README.md | 8 +++-- 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/BEHEER.md b/BEHEER.md index ceaea8e..24a95db 100644 --- a/BEHEER.md +++ b/BEHEER.md @@ -96,39 +96,48 @@ docker compose -f docker-compose.dev.yml exec -w /app/frontend app npm run lint ## Tokens -> **De `Vibe`-organisatie staat op public.** Alles wat alleen *leest* heeft -> daardoor geen token meer nodig: `git clone`, `docker compose pull` en de -> update-check van de app werken anoniem. Het host-token hieronder is dus -> optioneel geworden. *Schrijven* blijft geauthenticeerd — een publieke -> organisatie verandert daar niets aan. +**Eén token, op het werkstation.** Dat is genoeg omdat de `Vibe`-organisatie op +public staat: alles wat alleen *leest* heeft geen token meer nodig — `git clone`, +`docker compose pull` en de update-check van de app werken anoniem. Wat overblijft +is schrijven, en dat gebeurt allemaal vanaf deze ene machine. -Twee tokens, gesplitst op machine en op read/write. De grens die iets betekent -ligt tussen werkstation en server, niet tussen push en release: op één machine -kan wie het ene token kan lezen, het andere ook. +| Token | Machine | Scopes | Gebruikt voor | +|---|---|---|---| +| `vibefinance-workstation` | dit werkstation | `write:package`, `write:repository` | `docker push` (`release.ps1`, `dev.ps1 push`) én het aanmaken van de Gitea-release | -| Token | Machine | Scopes | Gebruikt voor | Nog nodig? | -|---|---|---|---|---| -| `vibefinance-workstation` | dit werkstation | `write:package`, `write:repository` | `docker push` (`release.ps1`, `dev.ps1 push`) en het aanmaken van de Gitea-release | **Ja** | -| `vibefinance-host` | de Docker-host | `read:package`, `read:repository` | `docker compose pull` en de update-check van de app | Nee, zolang de organisatie public is | +Er is geen apart host-token en geen apart registry-token: -Aanmaken: Gitea → Settings → Applications → Generate New Token. Doe dat onder een -account dat lid is van de `Vibe`-organisatie: voor schrijven volstaat -organisatie-lidmaatschap niet automatisch, het account moet ook push-rechten op de -repo hebben. +- **`git push`** gaat over SSH (`gitea@…:Vibe/VibeFinance.git`), dus die gebruikt + je SSH-sleutel en niet dit token. +- **De registry** heeft geen eigen variabele nodig: `release.ps1` en `dev.ps1` + vallen bij een ontbrekende `REGISTRY_TOKEN` terug op `GITEA_TOKEN`. Zet alleen + `REGISTRY_USER` erbij, zodat het script zelf kan inloggen als de credential + store leeg is. +- **De Docker-host** hoeft niets meer te hebben: pull en update-check zijn anoniem. -**Waar ze terechtkomen** +Aanmaken: Gitea → Settings → Applications → Generate New Token, met **beide** +scopes aangevinkt. Doe dat onder een account dat push-rechten op de repo heeft — +lid zijn van de organisatie is daarvoor niet automatisch genoeg. Scopes van een +bestaand token zijn later niet aan te passen, dus vink ze in één keer goed aan. -| | Werkstation | Docker-host | -|---|---|---| -| Registry | `docker login` (credential store), of `REGISTRY_USER` + `REGISTRY_TOKEN` in `.env.release` | niets nodig voor een pull; anders `docker login` als de gebruiker die deployt | -| Gitea-API | `GITEA_TOKEN` in `.env.release` | niets nodig; optioneel `UPDATE_CHECK_TOKEN` in `.env` | +**Waar hij terechtkomt** — `.env.release` naast de scripts, niet in git +(`.gitignore` pakt `.env.*`): -**Waarom een host-token read-only blijft** — voor het geval de organisatie weer -privé gaat en je er alsnog één neerzet. Dat staat in platte tekst in `.env` op -een server die de app op poort 3400 publiceert. Met schrijfrechten kan wie bij dat -bestand komt — via de container, een shell op die host, of een backup — een eigen -image als `latest` naar de registry pushen, en die host pullt `latest` zelf. -Read-only houdt je eigen deploy-pad uit de aanvalsketen. +```ini +GITEA_TOKEN= +REGISTRY_USER= +``` + +Een variabele die al in je shell staat wint, zodat je per run kunt afwijken. Een +lege waarde in het bestand wordt overgeslagen. + +**Als de organisatie weer privé gaat** heb je er wél een tweede bij nodig: een +read-only token op de Docker-host, voor `docker compose pull` en +`UPDATE_CHECK_TOKEN`. Houd dat token dan read-only. Het staat in platte tekst in +`.env` op een server die de app op poort 3400 publiceert; met schrijfrechten kan +wie bij dat bestand komt — via de container, een shell op die host, of een +backup — een eigen image als `latest` naar de registry pushen, en die host pullt +`latest` zelf. Read-only houdt je eigen deploy-pad uit de aanvalsketen. ### Wat een foutcode betekent @@ -168,14 +177,21 @@ docker exec vibefinance_app node -e 'const t=process.env.UPDATE_CHECK_TOKEN; fet > waarde helemaal geen header. Nagemeten op 2026-09-10: > geen header `200`, `token ongeldig123` `401`, leeg `200`. -### Tokens opruimen +### Oude tokens opruimen -De lijst onder Settings → Applications heeft een kolom **Last used**. Zet eerst -beide machines om, draai daarna een release en een pull, en kijk een dag later -welke tokens niet meer meebewegen — die kun je intrekken. Betrouwbaarder dan uit -de naam afleiden wat waar in gebruik is. Trek niets in vóór die controle: het -token achter je `docker login` op dit werkstation laat een release op de push -falen. +Nu er één token overblijft, kunnen de rest weg — maar niet op de gok. De lijst +onder Settings → Applications heeft een kolom **Last used**. Werkwijze: + +1. Zet het nieuwe token in `.env.release` en draai een release die helemaal + doorloopt (inclusief `docker push` en stap 5). +2. Kijk in de lijst welk token net **Last used** heeft gekregen. +3. Wacht een dag en trek de tokens in die niet meer meebewegen. + +Betrouwbaarder dan uit de naam afleiden wat waar in gebruik is. Trek vooral niets +in vóór die controle: het token achter je `docker login` zit in de credential +store en niet in een bestand, dus je ziet aan niets welke het is — trek je die +in, dan valt de push in stap 3 om. Loopt dat toch mis, dan is +`docker login vibehoogie.duckdns.org` met het nieuwe token genoeg. --- @@ -222,16 +238,15 @@ in de volgende commit. Staat er een `.env.release` naast `release.ps1`, dan leest het script die in voordat er iets gebeurt, en hoeft stap 3 niet meer met de hand: -``` +```ini # .env.release — staat in .gitignore, hoort alleen op de release-machine GITEA_TOKEN= REGISTRY_USER= -REGISTRY_TOKEN= ``` -Beide tokenvariabelen krijgen hetzelfde token; zie [Tokens](#tokens) voor waarom -dat op deze machine één token mag zijn, en voor wat een 401, 403 of 404 van Gitea -betekent. +`REGISTRY_TOKEN` hoeft er niet in: beide scripts vallen bij een ontbrekende +`REGISTRY_TOKEN` terug op `GITEA_TOKEN`. Zie [Tokens](#tokens) voor de scopes en +voor wat een 401, 403 of 404 van Gitea betekent. Een variabele die al in je shell staat wint, zodat je per run kunt afwijken zonder het bestand aan te passen. Het script logt alleen welke namen het heeft geladen, @@ -430,8 +445,9 @@ machine — `vibefinance-workstation`, zie [Tokens](#tokens). Zonder login faalt een push met `401 Unauthorized`; `release.ps1` en `dev.ps1 push` controleren dit vóór de build en stoppen dan meteen. -Liever niet handmatig inloggen? Zet `REGISTRY_USER` en `REGISTRY_TOKEN` in -`.env.release`, dan logt het script zelf in. +Liever niet handmatig inloggen? Zet `REGISTRY_USER` in `.env.release`, dan logt +het script zelf in met `GITEA_TOKEN` — een aparte `REGISTRY_TOKEN` is alleen +nodig als je daar bewust een ánder token voor wilt gebruiken. Twee valkuilen: log in op `vibehoogie.duckdns.org` **zonder** `:3000` (dat is een aparte sleutel in de credential store), en doe het als dezelfde Windows-gebruiker diff --git a/Changelog.md b/Changelog.md index 0710583..73fd30c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -17,6 +17,11 @@ ondersteunt hem nog als vangnet voor het geval de organisatie weer privé wordt. Schrijven verandert niet: `docker push` en het aanmaken van een release hebben nog steeds het werkstation-token nodig +- Het tweetoken-model in `BEHEER.md` is teruggebracht tot **één** Gitea-token op + het werkstation, met `write:package` en `write:repository`. Het host-token is + niet meer nodig (pull en update-check zijn anoniem), `git push` gaat over SSH, + en `REGISTRY_TOKEN` hoeft niet apart: `release.ps1` en `dev.ps1` vielen daar al + terug op `GITEA_TOKEN`. Geen code-wijziging, alleen minder te beheren - De tabellen op Eigen Vermogen en Voortgang staan op `table-layout: fixed`, nodig om ingestelde kolombreedtes te respecteren. Gevolg: lange tekst rekt een kolom niet meer op maar wordt afgekapt met een ellips. De Opmerkingen-cellen diff --git a/README.md b/README.md index d194193..322f079 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,8 @@ backend/package.json (15 dependencies) ```powershell # token: staat in .env.release, of eenmalig in de shell -$env:GITEA_TOKEN = "" +# één Gitea-token met write:package (push) én write:repository (release) +$env:GITEA_TOKEN = "" .\release.ps1 ``` @@ -298,8 +299,9 @@ Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via ## 🛠️ Beheer Zie `BEHEER.md` voor commando's voor lokale ontwikkeling, releases, logs en -database-toegang — en voor de sectie **Tokens**: welk Gitea-token op welke -machine hoort, met welke scopes, en wat een 401, 403 of 404 van Gitea betekent. +database-toegang — en voor de sectie **Tokens**: waarom één Gitea-token op het +werkstation genoeg is, welke scopes het nodig heeft, en wat een 401, 403 of 404 +van Gitea betekent. --- -- 2.47.3 From 4e923fa998c63dc83beb79cab631e41122d95129 Mon Sep 17 00:00:00 2001 From: Jordy Hoogeveen Date: Thu, 10 Sep 2026 14:44:24 +0200 Subject: [PATCH 4/4] Versienummer naar 1.0.2-dev voor de volgende cyclus Co-Authored-By: Claude Opus 5 (1M context) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba17b36..14d3770 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance", - "version": "1.0.1", + "version": "1.0.2-dev", "description": "VibeFinance — persoonlijk vermogensbeheer", "private": true, "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance", -- 2.47.3