11 Commits
Author SHA1 Message Date
jhoogie ec63906cb2 Merge pull request 'cve fixes' (#17) from update-1.0.11 into main
Reviewed-on: #17
2026-09-18 15:03:54 +02:00
jhoogie 2d7e67e315 Merge pull request 'bugfix dashboard' (#16) from update-1.0.10 into main
Reviewed-on: #16
2026-09-17 17:28:11 +02:00
jhoogie 539b42dde7 Merge pull request 'bugfix dashboard' (#15) from update-1.0.9 into main
Reviewed-on: #15
2026-09-17 17:07:24 +02:00
jhoogie 73ce30c5b0 Merge pull request 'Bugfixes' (#14) from update-1.0.8 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/14
2026-09-15 14:06:45 +02:00
jhoogie 4915a090a3 Merge pull request 'dashboard tooltip update' (#13) from update-1.0.7 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/13
2026-09-11 17:13:03 +02:00
jhoogie 6d1b114b78 Merge pull request 'dashboard update' (#12) from update-1.0.6 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/12
2026-09-11 15:22:34 +02:00
jhoogie a9b6203dec Merge pull request 'bugfix' (#11) from update-1.0.5 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/11
2026-09-11 14:10:57 +02:00
jhoogie 36f49b216f Merge pull request 'bugfix' (#10) from update-1.0.5 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/10
2026-09-10 21:38:12 +02:00
jhoogie 796245fefe Merge pull request 'bugfix' (#9) from update-1.0.4 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/9
2026-09-10 21:28:05 +02:00
jhoogie 0bbb166b32 Merge pull request 'bugfix' (#8) from update-1.0.3 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/8
2026-09-10 16:42:38 +02:00
jhoogie fe6b94aa7f Merge pull request 'bugfix' (#7) from update-1.0.2 into main
Reviewed-on: http://10.0.3.108:3000/Vibe/VibeFinance/pulls/7
2026-09-10 15:43:57 +02:00
13 changed files with 11 additions and 139 deletions
-34
View File
@@ -335,40 +335,6 @@ dan is het antwoord `no configuration file provided: not found`. Met
Pullen zelf werkt anoniem zolang de organisatie public is — zie Pullen zelf werkt anoniem zolang de organisatie public is — zie
[Pullen op een deploy-machine](#pullen-op-een-deploy-machine). [Pullen op een deploy-machine](#pullen-op-een-deploy-machine).
### De scan meldt 46 CVE's op postgres:18-alpine
Dat klopt, en het blijft zo. Ze zitten allemaal in één bestand:
```bash
# Alle bevindingen komen uit /usr/local/bin/gosu, niets uit de Alpine-pakketten
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image --scanners vuln postgres:18-alpine
```
`gosu` is een klein Go-programma dat het postgres-entrypoint gebruikt om van
root naar de `postgres`-gebruiker te schakelen. Upstream compileert het met Go
1.24.6, en alle meldingen zijn CVE's in die Go-standaardbibliotheek. Je kunt er
niets aan verhelpen met een `pull`: dit ís de nieuwste versie.
Wat wél is gedaan: beide compose-bestanden starten de container met
`user: "postgres"`. Het entrypoint roept gosu alleen aan als het als root start
(`if [ "$(id -u)" = '0' ]`), dus die tak wordt overgeslagen en de binary draait
nooit. De CVE's zijn daarmee onbereikbaar — de scanner kijkt naar wat aanwezig
is, niet naar wat uitgevoerd wordt.
> Dit werkt ook op een volume dat ooit als root is aangemaakt: de
> data-directory is dan al eigendom van postgres. Getest met een bestaande
> database; die start en de data blijft intact.
**Waarom geen eigen image zonder gosu.** Dat kan (`FROM postgres:18-alpine` +
`RUN rm -f /usr/local/bin/gosu`) en zet de scan op nul, maar je ruilt een
onbereikbaar risico voor een echt risico: je moet dat image dan zelf herbouwen
bij elke postgres-patch. Precies die achterstand was het probleem toen het image
vier maanden op 18.4 stond met 143 bevindingen. Met het officiële image haalt
`docker compose pull` de patches vanzelf op, en zodra upstream gosu met een
nieuwere Go herbouwt is het gratis opgelost. `test.ps1` waarschuwt daarom bij
stap 4 als `postgres:18-alpine` in de registry nieuwer is dan wat je lokaal hebt.
### PostgreSQL versie-upgrade ### PostgreSQL versie-upgrade
Twee soorten, met een belangrijk verschil. Kijk eerst welke je voor je hebt: Twee soorten, met een belangrijk verschil. Kijk eerst welke je voor je hebt:
-47
View File
@@ -2,53 +2,6 @@
## [Niet vrijgegeven] ## [Niet vrijgegeven]
### Opgelost
- **De kaart "Rendement Eigen Vermogen" op Voortgang stond leeg.** Bedrag en
percentage waren sinds de eerste commit hardcoded als `—` in de JSX; er zat
geen berekening achter en `evRows` werd niet eens uit de context gehaald. Het
oogde als een datafout — lege database, mislukte API-call — maar de kaart was
nooit afgebouwd. Hij rekent nu met dezelfde formule als het blok "Rendement"
op Eigen vermogen: huidige waarde min geïnvesteerde fiat, gedeeld door die
inleg. Groen bij winst, rood bij verlies, en `—` zolang er niets is ingelegd,
zodat er niet door nul wordt gedeeld
- Beide kaarten tonen daarmee hetzelfde totaal. Dat is bewust: twee plekken die
"rendement" noemen horen niet twee verschillende getallen te geven
### Gewijzigd
- De achtergrond achter een popup is lichter: `rgba(0,0,0,0.75)` naar `0.45` in
de gedeelde `Overlay`. Op 0.75 was van het scherm eronder vrijwel niets meer
te zien, terwijl je bij het invullen van een modal juist wilt kunnen nakijken
waar je vandaan kwam. Gedimd genoeg om de modal de aandacht te laten houden
- De popups van NavBar, Instellingen en Gebruikersbeheer gebruiken de gedeelde
`Overlay` niet maar hadden hun eigen backdrop op `rgba(0,0,0,0.55)` met
`backdropFilter: blur(4px)`. Alle drie staan nu op dezelfde 0.45, en de blur
is eruit: die maakte de achtergrond onscherp in plaats van zichtbaar, en
zonder dat weg te halen bleven ze alsnog afwijken van de rest. Vier
popup-backdrops, één waarde
## [1.0.12] - 2026-09-18
### Beveiliging
- **De postgres-container draait niet meer als root.** Beide compose-bestanden
starten hem met `user: "postgres"`. Alle 46 bevindingen op `postgres:18-alpine`
zitten in één bestand — `/usr/local/bin/gosu`, dat upstream met Go 1.24.6
compileert — en het entrypoint roept gosu alleen aan als het als root start.
Non-root starten slaat die tak over, dus de binary draait nooit meer. De
Alpine-pakketten in dat image zijn zelf schoon
- Getest op een bestaande database die ooit als root was aangemaakt: die start
als non-root en de data blijft intact. Ook een verse database initialiseert
gewoon
- Bewust géén eigen postgres-image gebouwd om die binary te verwijderen. Dat zou
de scan op nul zetten maar je verplichten het image zelf te herbouwen bij elke
postgres-patch — precies de achterstand die het image vier maanden op 18.4 met
143 bevindingen hield. De afweging staat in `BEHEER.md`, zodat de volgende
scan niet opnieuw tot dezelfde vraag leidt
- `test.ps1` waarschuwt bij stap 4 wanneer `postgres:18-alpine` in de registry
nieuwer is dan wat er lokaal staat. Die achterstand is het werkelijke risico;
het getal in de scan is dat niet
## [1.0.11] - 2026-09-18
### Beveiliging ### Beveiliging
- **PostgreSQL 18.4 → 18.6.** Het `postgres:18-alpine` image dat in gebruik was - **PostgreSQL 18.4 → 18.6.** Het `postgres:18-alpine` image dat in gebruik was
dateerde van vier maanden geleden. Dat is een patch binnen dezelfde major, dus dateerde van vier maanden geleden. Dat is een patch binnen dezelfde major, dus
+2 -9
View File
@@ -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) ![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) ![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) ![Docker](https://img.shields.io/badge/Docker_Compose-2496ED?style=flat&logo=docker&logoColor=white)
![Version](https://img.shields.io/badge/versie-1.0.12-8b5cf6?style=flat) ![Version](https://img.shields.io/badge/versie-1.0.10-8b5cf6?style=flat)
![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white) ![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white)
<img src="docs/screenshot-dashboard.png" alt="VibeFinance dashboard" width="820"> <img src="docs/screenshot-dashboard.png" alt="VibeFinance dashboard" width="820">
@@ -190,7 +190,7 @@ build — anders zou de release met een placeholder als notes online komen.
| 📊 **Dashboard** | KPI's over inleg, vermogen, winst en ROI, de voortgang naar je doel, en de grafiek met vermogen tegen inleg | | 📊 **Dashboard** | KPI's over inleg, vermogen, winst en ROI, de voortgang naar je doel, en de grafiek met vermogen tegen inleg |
| 💰 **Eigen vermogen** | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek | | 💰 **Eigen vermogen** | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek |
| 📉 **Schuld** | Schulden bijhouden per blok met afbetalingsoverzicht | | 📉 **Schuld** | Schulden bijhouden per blok met afbetalingsoverzicht |
| 📈 **Voortgang Schuld & EV** | KPI-kaarten met de verdeling van schuld en EV, het rendement en de netto waarde, plus grafieken en trends van EV en schuld over tijd (apart bij te houden) | | 📈 **Voortgang Schuld & EV** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) |
| 👥 **Gebruikers** | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) | | 👥 **Gebruikers** | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) |
| ⚙️ **Instellingen** | Kleurthema, licht/donker, backup downloaden, backup terugzetten en export naar HTML | | ⚙️ **Instellingen** | Kleurthema, licht/donker, backup downloaden, backup terugzetten en export naar HTML |
@@ -202,13 +202,6 @@ inleglijn is per definitie gelijk aan de KPI "Totaal geïnvesteerde fiat" — be
rekenen het oudste voortgangsmoment als startinleg en tellen daar de stortingen rekenen het oudste voortgangsmoment als startinleg en tellen daar de stortingen
per rij bij op. per rij bij op.
Rendement betekent overal hetzelfde: huidige waarde min geïnvesteerde fiat,
gedeeld door die inleg. De kaart "Rendement Eigen Vermogen" op Voortgang en het
blok "Rendement" op Eigen vermogen tonen daarom hetzelfde totaal — dat laatste
splitst het alleen nog uit per categorie. Zolang er geen inleg is vastgelegd
staat er `—` in plaats van een percentage, want zonder noemer is er geen
rendement te berekenen.
De x-as noemt maanden (`sep '26`), niet losse datums: de grafiek rekent uit De x-as noemt maanden (`sep '26`), niet losse datums: de grafiek rekent uit
hoeveel labels er naast elkaar passen en toont er zoveel, terugtellend vanaf het hoeveel labels er naast elkaar passen en toont er zoveel, terugtellend vanaf het
laatste punt zodat dat er altijd bij staat. Bij een langere reeks dunt hij laatste punt zodat dat er altijd bij staat. Bij een langere reeks dunt hij
-3
View File
@@ -15,9 +15,6 @@ services:
postgres: postgres:
image: postgres:18-alpine image: postgres:18-alpine
container_name: vibefinance-postgres-dev container_name: vibefinance-postgres-dev
# Zie docker-compose.yml: non-root starten omzeilt gosu, waar alle CVE's
# van dit image in zitten.
user: "postgres"
environment: environment:
POSTGRES_DB: ${POSTGRES_DB:-vibefinance} POSTGRES_DB: ${POSTGRES_DB:-vibefinance}
POSTGRES_USER: ${POSTGRES_USER:-vibefinance} POSTGRES_USER: ${POSTGRES_USER:-vibefinance}
-7
View File
@@ -16,13 +16,6 @@ services:
image: postgres:18-alpine image: postgres:18-alpine
container_name: vibefinance_postgres container_name: vibefinance_postgres
restart: unless-stopped restart: unless-stopped
# Meteen als postgres draaien in plaats van als root. Het entrypoint schakelt
# anders zelf terug met `gosu`, en juist die binary bevat alle 46 CVE's van
# dit image (Go stdlib 1.24.6, meegecompileerd door upstream). Start de
# container al als postgres, dan slaat het entrypoint die tak over en wordt
# gosu nooit uitgevoerd. Werkt ook op een volume dat ooit als root is
# aangemaakt — de data-directory is dan al van postgres.
user: "postgres"
environment: environment:
POSTGRES_DB: ${POSTGRES_DB:-vibefinance} POSTGRES_DB: ${POSTGRES_DB:-vibefinance}
POSTGRES_USER: ${POSTGRES_USER:-vibefinance} POSTGRES_USER: ${POSTGRES_USER:-vibefinance}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "vibefinance-frontend", "name": "vibefinance-frontend",
"version": "1.0.13-dev", "version": "1.0.11-dev",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+1 -1
View File
@@ -172,7 +172,7 @@ export default function GebruikersBeheer({ onClose }) {
onClick={onClose} onClick={onClose}
style={{ style={{
position: "fixed", inset: 0, zIndex: 400, position: "fixed", inset: 0, zIndex: 400,
background: "rgba(0,0,0,0.45)", background: "rgba(0,0,0,0.55)", backdropFilter: "blur(4px)",
display: "flex", alignItems: "center", justifyContent: "center", display: "flex", alignItems: "center", justifyContent: "center",
}} }}
> >
@@ -49,7 +49,7 @@ export default function InstellingenModal({ onClose }) {
onClick={onClose} onClick={onClose}
style={{ style={{
position: "fixed", inset: 0, zIndex: 400, position: "fixed", inset: 0, zIndex: 400,
background: "rgba(0,0,0,0.45)", background: "rgba(0,0,0,0.55)", backdropFilter: "blur(4px)",
display: "flex", alignItems: "center", justifyContent: "center", display: "flex", alignItems: "center", justifyContent: "center",
}} }}
> >
+1 -1
View File
@@ -116,7 +116,7 @@ function OverPopup({ onClose, nieuweVersie, T }) {
{/* Overlay */} {/* Overlay */}
<div onClick={onClose} style={{ <div onClick={onClose} style={{
position: "fixed", inset: 0, zIndex: 490, position: "fixed", inset: 0, zIndex: 490,
background: "rgba(0,0,0,0.45)", background: "rgba(0,0,0,0.55)", backdropFilter: "blur(4px)",
display: "flex", alignItems: "center", justifyContent: "center", display: "flex", alignItems: "center", justifyContent: "center",
}}> }}>
{/* Modal */} {/* Modal */}
+1 -1
View File
@@ -319,7 +319,7 @@ export function Overlay({ children, onClose, zIndex = 400 }) {
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }} onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
style={{ style={{
position: "fixed", inset: 0, position: "fixed", inset: 0,
background: "rgba(0,0,0,0.45)", background: "rgba(0,0,0,0.75)",
zIndex, zIndex,
}} }}
> >
+3 -16
View File
@@ -237,22 +237,13 @@ export default function VoortgangTab() {
const [showAdd, setShowAdd] = useState(false); const [showAdd, setShowAdd] = useState(false);
const [editRow, setEditRow] = useState(null); const [editRow, setEditRow] = useState(null);
const { const {
totEV, totSchuld, cats, catTotals, evRows, schuldBlokken, schuldHistory, totEV, totSchuld, catTotals, schuldBlokken, schuldHistory,
evVoortgangHistory, setEvVoortgangHistory, evVoortgangHistory, setEvVoortgangHistory,
data, setData, schuld, locked, darkMode, T, data, setData, schuld, locked, darkMode, T,
} = useApp(); } = useApp();
const nettoWaarde = totEV - totSchuld; const nettoWaarde = totEV - totSchuld;
// Rendement t.o.v. de geinvesteerde fiat, zelfde formule als op de EV-tab.
const totGeinvesteerd = cats.reduce(
(a, c) => a + evRows.reduce((b, r) => b + (r[c.id] || 0), 0),
0
);
const rendementBedrag = totGeinvesteerd > 0 ? totEV - totGeinvesteerd : null;
const rendementPct = totGeinvesteerd > 0 ? (rendementBedrag / totGeinvesteerd) * 100 : null;
const rendementKleur = rendementBedrag === null ? T.muted : rendementBedrag >= 0 ? GREEN : RED;
const schuldCatsDash = schuldBlokken.map((b) => ({ const schuldCatsDash = schuldBlokken.map((b) => ({
naam: b.naam, color: b.color, naam: b.naam, color: b.color,
subtot: b.items.reduce((a, i) => a + (i.waarde || 0), 0), subtot: b.items.reduce((a, i) => a + (i.waarde || 0), 0),
@@ -386,15 +377,11 @@ export default function VoortgangTab() {
<div style={{ fontWeight: 700, fontSize: 12, letterSpacing: "0.08em", textTransform: "uppercase", color: "#f59e0b", marginBottom: 12 }}>Rendement Eigen Vermogen</div> <div style={{ fontWeight: 700, fontSize: 12, letterSpacing: "0.08em", textTransform: "uppercase", color: "#f59e0b", marginBottom: 12 }}>Rendement Eigen Vermogen</div>
<div style={{ padding: "10px 0", borderBottom: `1px solid ${T.border}` }}> <div style={{ padding: "10px 0", borderBottom: `1px solid ${T.border}` }}>
<div style={{ fontSize: 11, color: T.muted, marginBottom: 4 }}>Bedrag</div> <div style={{ fontSize: 11, color: T.muted, marginBottom: 4 }}>Bedrag</div>
<div style={{ fontSize: 15, fontWeight: 700, color: rendementKleur }}> <div style={{ fontSize: 15, fontWeight: 700, color: T.muted }}>—</div>
{rendementBedrag === null ? "—" : fmt(rendementBedrag)}
</div>
</div> </div>
<div style={{ padding: "10px 0" }}> <div style={{ padding: "10px 0" }}>
<div style={{ fontSize: 11, color: T.muted, marginBottom: 4 }}>Percentage</div> <div style={{ fontSize: 11, color: T.muted, marginBottom: 4 }}>Percentage</div>
<div style={{ fontSize: 15, fontWeight: 700, color: rendementKleur }}> <div style={{ fontSize: 15, fontWeight: 700, color: T.muted }}>—</div>
{rendementPct === null ? "—" : `${rendementPct >= 0 ? "+" : ""}${rendementPct.toFixed(1)}%`}
</div>
</div> </div>
</VCard> </VCard>
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "vibefinance", "name": "vibefinance",
"version": "1.0.13", "version": "1.0.11",
"description": "VibeFinance — persoonlijk vermogensbeheer", "description": "VibeFinance — persoonlijk vermogensbeheer",
"private": true, "private": true,
"repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance", "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance",
-17
View File
@@ -124,23 +124,6 @@ if (-not $SkipScan) {
exit 1 exit 1
} }
Write-Host "Imagescan schoon ($ernst)" -ForegroundColor Green Write-Host "Imagescan schoon ($ernst)" -ForegroundColor Green
# Het postgres-image scannen we niet als gate: alle bevindingen daarin zitten
# in /usr/local/bin/gosu, en die binary wordt nooit uitgevoerd omdat de
# compose-bestanden de container al als `postgres` starten (zie BEHEER.md).
# Wat wél misgaat is achterstand: het image stond vier maanden op 18.4 met
# 143 bevindingen omdat niemand pullde. Daar waarschuwen we voor — falen zou
# onterecht zijn, want een release van de app hangt er niet van af.
$pgTag = "postgres:18-alpine"
$lokaal = (docker image inspect $pgTag --format '{{index .RepoDigests 0}}' 2>$null)
if ($lokaal) {
$remote = (docker buildx imagetools inspect $pgTag --format '{{.Manifest.Digest}}' 2>$null)
if ($remote -and $lokaal -notlike "*$remote*") {
Write-Host "Let op: $pgTag is nieuwer in de registry — draai 'docker compose pull postgres'." -ForegroundColor Yellow
} else {
Write-Host "$pgTag is actueel" -ForegroundColor Green
}
}
} else { } else {
Write-Step "[4/5] Imagescan overgeslagen (-SkipScan)" Write-Step "[4/5] Imagescan overgeslagen (-SkipScan)"
} }