Port van de iOS GardenKeeper app naar SvelteKit + Konva.js, uitsluitend via Docker containers.
| Onderdeel | Technologie | Port |
|---|---|---|
| Reverse proxy | nginx:alpine | 80 |
| Web app | SvelteKit + Konva.js | 3000 (intern) |
| Backend | PocketBase | 8090 (intern) |
gardenkeeper-web/
├── docker-compose.yml
├── docker-compose.dev.yml ← dev mode met live reload (./app:/app mount)
├── nginx.conf
├── .env ← CLAUDE_API_KEY (niet committen!)
└── app/ ← SvelteKit project
├── Dockerfile
└── src/
├── lib/
│ ├── types.ts ← alle models + helpers
│ ├── stores/
│ │ ├── app.ts ← state (gardens, zones, plants, placements)
│ │ └── logs.ts ← AI-actie logging
│ └── services/
│ ├── pocketbase.ts ← PocketBase client + pbUrl
│ └── claude.ts ← AI service (Claude API)
└── routes/
├── +layout.svelte ← nav + auth guard
├── +page.svelte ← tuinoverzicht
├── api/
│ ├── ai/+server.ts ← Claude proxy endpoint
│ └── photo/+server.ts ← Wikipedia foto endpoint
├── auth/login/ ← login + registratie
└── garden/[id]/
├── +page.svelte ← tuin detail (tabs: Canvas / Planten / Zones)
├── GardenCanvas.svelte ← Konva canvas met zones + planttokens
├── PlantDetail.svelte ← plant detail sidebar
└── ZoneDetail.svelte ← zone detail (sidebar of modal)
- Pan (sleep) en zoom (scroll/knop) over het tuincanvas
- Rasteroverlay (25px minor, 1m major gridlines)
- Optionele plattegrond als achtergrondafbeelding
- Zones: teken nieuw (⬜+), sleep om te verplaatsen, resize via hoekhandvat
- Enkele klik = selecteren; dubbelklik = zone detail openen als modal
- Plant tokens: sleepbaar, toont foto + naam + gestippelde groeiradius
- Foto uploaden of automatisch ophalen van Wikipedia
- AI-data ophalen via Claude (hoogte, zon, water, onderhoud, snoeien, bloeitijd)
- Plantkalender: bloei- en snoeimalanden visueel gemarkeerd
- Zones toewijzen / verwijderen via chip-UI
- Plant direct zichtbaar na aanmaken
- Mini zone-map: visuele weergave van de zone met meterraster
- Plant tokens sleepbaar voor herpositionering (opgeslagen in PocketBase)
- Groeiradius ring op basis van plantdiameter
- Zone bewerken (naam, kleur, afmeting)
- Planten toevoegen uit plantenlijst (naam + variëteit zichtbaar)
- Planten verwijderen uit zone
- Overzicht van alle zones met kleur, afmeting en plantencount
- "Geen zone"-rij toont planten nog niet aan een zone toegewezen
- Klik op zone opent zone detail als modal
Container Station op QNAP ondersteunt Docker Compose. Hieronder stap voor stap hoe je de app op je NAS krijgt.
- QNAP NAS met Container Station 3.x geïnstalleerd
- SSH-toegang tot de NAS (of File Station om bestanden te uploaden)
- Git beschikbaar op de NAS (optioneel, anders handmatig uploaden)
Via SSH:
ssh admin@JOUW-NAS-IP
# Maak een map aan in de shared folder
mkdir -p /share/Container/gardenkeeper
cd /share/Container/gardenkeeper
# Optie A: via git
git clone https://github.com/JOUW-REPO/gardenkeeper-web.git .
# Optie B: upload de map via File Station naar
# /share/Container/gardenkeeper/cd /share/Container/gardenkeeper
cp .env.example .env
vi .env # vul CLAUDE_API_KEY=sk-ant-... in- Open Container Station in het QNAP-beheerportaal
- Klik op Maken → Maak via Docker Compose
- Geef het project een naam, bv.
gardenkeeper - Plak de inhoud van
docker-compose.ymlin het tekstveld — of wijs het pad/share/Container/gardenkeeper/docker-compose.ymlaan - Voeg onder Omgevingsvariabelen toe:
CLAUDE_API_KEY= jouw Claude API key - Klik Maken — Container Station bouwt de images en start de containers
Tip: Als je liever via SSH werkt:
cd /share/Container/gardenkeeper docker-compose up -d --buildQNAP gebruikt Docker Compose v1 — gebruik
docker-compose(met koppelteken), nietdocker compose.
- Ga naar
http://JOUW-NAS-IP/_/ - Maak een admin-account aan
- Maak de collections aan (zie tabel verderop in deze README)
http://JOUW-NAS-IP/ — via de nginx reverse proxy op poort 80.
Als poort 80 al in gebruik is door een andere container of de QNAP-webserver, pas dan de poortmapping aan in docker-compose.yml:
ports:
- "8080:80" # bereikbaar op poort 8080De PocketBase-data zit in Docker named volumes (pb_data, pb_migrations). Deze blijven behouden bij docker-compose down en NAS-reboots. Om een backup te maken:
# Op de NAS via SSH
docker run --rm \
-v gardenkeeper_pb_data:/data \
-v /share/Backup:/backup \
alpine tar czf /backup/pb_data_$(date +%Y%m%d).tar.gz -C /data .cd /share/Container/gardenkeeper
git pull
docker-compose up -d --build gardenkeeperdocker-compose.yml wordt niet bijgehouden in git (om API keys uit de geschiedenis te houden). Pas het bestand rechtstreeks aan op de NAS als er iets gewijzigd moet worden:
# Controleer de huidige waarde
grep PUBLIC_PB_URL docker-compose.yml
# Pas een waarde aan (voorbeeld: PocketBase URL)
sed -i 's|PUBLIC_PB_URL=http://pocketbase:8090|PUBLIC_PB_URL=${PUBLIC_PB_URL:-http://192.168.1.99:8090}|' docker-compose.ymlAls je docker-compose.yml hebt aangepast maar de container gedraagt zich nog als voorheen, is de container niet opnieuw aangemaakt. Gebruik --force-recreate:
docker-compose up -d --force-recreate gardenkeeperControleer achteraf welke waarden de draaiende container heeft:
docker inspect gardenkeeper_gardenkeeper_1 | grep PUBLIC_PB_URL# Bekijk huidige .env
cat .env
# Pas een waarde aan via sed
sed -i 's/OUDE_WAARDE/NIEUWE_WAARDE/' .env
# Of bewerk handmatig
vi .envDe .env moet minimaal bevatten:
CLAUDE_API_KEY=sk-ant-...
PUBLIC_PB_URL hoeft niet in .env — de standaardwaarde in docker-compose.yml wordt dan gebruikt.
docker-compose ps
docker-compose logs gardenkeeper --tail=50
docker-compose logs nginx --tail=20Controleer of de pocketbase-hostnaam oplosbaar is vanuit de nginx-container. Als PocketBase buiten dit Compose-project draait, gebruik dan het NAS IP-adres rechtstreeks in nginx.conf (bv. http://192.168.1.99:8090) in plaats van http://pocketbase:8090.
Op QNAP vervangt git pull bestanden met een nieuw inode. nginx's reload (nginx -s reload) herlaadt de configuratie maar houdt de file descriptor van het oude inode vast — de nieuwe inhoud wordt dan niet opgepikt.
Gebruik na elke git pull die nginx.conf wijzigt een volledige container-restart in plaats van enkel reload:
docker-compose restart nginxControleer daarna of de nieuwe config actief is:
docker exec gardenkeeper_nginx_1 cat /etc/nginx/conf.d/default.conf | grep -A3 "api/photo"# 1. API key instellen
cp .env.example .env
nano .env # vul CLAUDE_API_KEY in
# 2. Bouwen en starten
docker compose up -d --build
# 3. PocketBase admin instellen
# Ga naar http://JOUW-IP/_/
# Maak admin account aan
# Maak de collections aan (zie hieronder)
# 4. App bereikbaar op http://JOUW-IP/docker compose -f docker-compose.dev.yml --env-file .env upDe app/ map wordt als volume gemount — wijzigingen in app/src/ zijn direct zichtbaar.
git pull
docker compose up -d --build gardenkeeper| Collection | Velden |
|---|---|
users |
standaard auth |
gardens |
owner, name, width_m, height_m, floorplan_photo, notes |
garden_zones |
garden, name, color, x_pos, y_pos, width_m, height_m, notes |
plants |
garden, name, variety, photo, category, size_m, ai_* |
placed_plants |
garden, plant, zone (optioneel), x_pos, y_pos, rotation, scale |
vegetable_patches |
owner, name, width_m, height_m, floorplan_photo, notes |
vegetables |
patch, name, status, photo, planned_count, ai_*, size_m |
placed_vegetables |
patch, vegetable, x_pos, y_pos, rotation |
ai_height, ai_sun, ai_water, ai_maintenance, ai_pruning, ai_pruning_instruction, ai_pruning_months, ai_bloom_season, ai_bloom_months, ai_diameter_m, ai_category, ai_winterhard, ai_tip, ai_fetched_at
iOS → Web mapping:
| iOS | Web |
|---|---|
UIScrollView zoom/pan |
Konva.Stage (draggable + wheel zoom) |
ZoneUIView (UIKit) |
Konva.Rect in Konva.Group (draggable) |
PlantToken (SwiftUI) |
Konva.Group met Circle + Image + label |
UIPanGestureRecognizer |
Konva drag events |
@Published / AppState |
Svelte writable stores |
Konva verwerkt alle gesture-conflicten automatisch.