# Remote Client Shares V1.1 Design

## Doel

Een gebruiker van WebManager moet naast de bestaande server-side storage-roots ook een beperkte set lokale mappen van zijn eigen client-Mac kunnen benaderen, zonder de hele homefolder bloot te geven.

Voorbeelden van toegestane client-shares:

- `Downloads`
- `Movies`
- `Pictures`

De oplossing moet:

- simpel blijven
- veilig blijven
- de bestaande storage-workflow niet breken
- WebManager niet laten vastlopen als een remote helper-agent offline is

---

## Kernbeslissingen voor V1.1

Deze beslissingen liggen in V1.1 vast.

- Remote client shares worden niet opgenomen in `root_aliases`.
- `/Clients` wordt een aparte virtuele bron naast `/Volumes`.
- Remote paden lopen niet door de bestaande lokale filesystem-resolutie.
- `client_id` is intern de enige leidende identiteit.
- `display_name` is alleen voor UI-weergave.
- De agent werkt alleen met `share key + relatief pad`.
- Alle agent-calls vereisen authenticatie, niet alleen registratie.
- Offline agents mogen alleen hun eigen subtree beïnvloeden, nooit de rest van de app.
- V1 blijft read-mostly: registry, browse, info, preview, download.

---

## Waarom niet als gewone root alias

De huidige backend gaat uit van server-side whitelisted filesystem roots.

Dat model werkt voor:

- `/Volumes/...`
- gemounte server storage
- container-side toegankelijke paden

Dat model werkt niet goed voor:

- de lokale schijf van de browsergebruiker
- een remote Mac die buiten de server draait
- clients die offline kunnen zijn
- clients die dynamische IP-adressen hebben

Daarom mogen remote client shares niet in hetzelfde model worden gestopt als `root_aliases`.

---

## Scope V1.1

### In scope

- beperkte client-shares: `Downloads`, `Movies`, `Pictures`
- lokale helper-agent op macOS
- agent registratie in WebManager
- heartbeat/status tracking
- virtuele `Clients` bron in de WebUI
- browse van remote shares
- bestand-info
- tekstpreview
- image preview waar triviaal
- download van bestanden
- nette offline-afhandeling

### Expliciet niet in V1.1

- hele homefolder
- willekeurige custom paths buiten de toegestane sharelijst
- shell/subprocess execution
- rename
- mkdir
- delete
- upload naar remote share
- bookmarks voor `/Clients/...`
- startup paths voor `/Clients/...`
- cross-source copy of move
- complete taakrunner-integratie zoals server copy/move tasks
- automatische LAN discovery
- multi-user auth met OS user mapping

---

## Gewenste gebruikerservaring

In de WebUI komt naast server-storage een extra virtuele bron:

- `/Volumes`
- `/Clients`

Onder `/Clients` ziet de gebruiker geregistreerde clients, bijvoorbeeld:

- `MacBook Pro van Jan`
- `iMac Woonkamer`

Onder een client ziet de gebruiker alleen de toegestane shares:

- `Downloads`
- `Movies`
- `Pictures`

Voor de gebruiker kan dat eruitzien als:

- `/Clients/MacBook-Pro-van-Jan/Downloads`
- `/Clients/MacBook-Pro-van-Jan/Movies`
- `/Clients/MacBook-Pro-van-Jan/Pictures`

Maar intern mag routing niet op `display_name` leunen.

Intern moet WebManager werken met een stabiele client-identiteit en een mappinglaag:

- `client_id` voor routing en opslag
- `display_name` voor weergave
- optioneel een afgeleide slug voor browse-url-presentatie

---

## Architectuuroverzicht

Er zijn drie componenten.

### 1. WebManager backend

Verantwoordelijk voor:

- registry van bekende remote clients
- status- en heartbeat-tracking
- virtuele browse-root `Clients`
- proxying van requests naar agents
- timeouts en foutafhandeling
- scheiding tussen local-source en remote-source afhandeling

### 2. WebUI frontend

Verantwoordelijk voor:

- tonen van `Clients` als extra bron
- navigeren binnen client/share paden
- offline status tonen
- requests afvuren naar gewone WebManager backend-routes

### 3. Remote helper-agent op macOS

Verantwoordelijk voor:

- toegang tot vaste lokale shares
- strikte padvalidatie binnen die shares
- simpele browse/info/read/download endpoints
- zichzelf registreren bij WebManager
- heartbeat sturen
- auth afdwingen op alle agent-endpoints

---

## Bereikbaarheidsmodel

Dit is de eerste harde productbeslissing.

### V1.1-keuze

V1.1 gaat uit van een omgeving waarin WebManager de agent rechtstreeks kan bereiken.

Dat betekent praktisch:

- dezelfde LAN
- of een expliciet configureerbaar agent-endpoint
- of een deployment waar server en client netwerkmatig direct verbonden zijn

### Waarom deze keuze

Dit is het simpelste model dat functioneel klopt zonder reverse tunnels, websockets als transportlaag, of extra relay-infrastructuur.

### Wat V1.1 niet probeert op te lossen

Deze versie garandeert niet dat een agent achter willekeurige NAT/firewall altijd bereikbaar is.

Dus:

- self-registration blijft het discoverymodel
- direct bereikbare agent-endpoint blijft het V1-transportmodel
- reverse-connect of tunnelmodellen zijn uitgesteld

### Fallback

Een handmatige endpoint override blijft toegestaan als operationele fallback, bijvoorbeeld:

- `http://192.168.1.25:8765`

Maar dat is geen hoofdmodel en geen productbelofte.

---

## Hoe de remote agent bekend wordt in WebManager

### Gekozen model: agent registreert zichzelf

De agent meldt zichzelf actief aan bij WebManager. Niet andersom.

Dat betekent:

- geen handmatig client-IP nodig als hoofdmodel
- geen server-naar-client discovery nodig
- geen afhankelijkheid van LAN-broadcasting
- geen probleem als het client-IP wisselt, zolang het geregistreerde endpoint actueel is

### Registratiestroom

Bij starten van de agent:

1. de agent leest lokale config
2. de agent bepaalt:
   - `client_id`
   - `display_name`
   - `shares`
   - `endpoint`
3. de agent registreert zich bij WebManager
4. WebManager slaat client-record op of werkt het bij
5. de agent stuurt periodieke heartbeats

### Benodigde velden bij registratie

Voorstel:

```json
{
  "client_id": "f4b2c8f8-2b1b-4d89-9ed2-8d6d7b1f3abc",
  "display_name": "MacBook Pro van Jan",
  "platform": "macos",
  "agent_version": "1.1.0",
  "endpoint": "http://192.168.1.25:8765",
  "shares": [
    { "key": "downloads", "label": "Downloads" },
    { "key": "movies", "label": "Movies" },
    { "key": "pictures", "label": "Pictures" }
  ]
}
```

### Backend bewaart per client

- `client_id`
- `display_name`
- `platform`
- `agent_version`
- `endpoint`
- `shares`
- `last_seen`
- `status`
- `last_error`
- `reachable_at`
- eventueel `registration_token_id`

### Heartbeat

De agent stuurt elke 15-30 seconden een heartbeat.

Bijvoorbeeld:

```json
{
  "client_id": "f4b2c8f8-2b1b-4d89-9ed2-8d6d7b1f3abc",
  "agent_version": "1.1.0"
}
```

### Statusmodel

Deze velden moeten logisch gescheiden blijven:

- `last_seen`
  Laatste succesvolle heartbeat van de agent.
- `status`
  Afgeleide UI-status, bijvoorbeeld `online` of `offline`.
- `last_error`
  Laatste connect- of browsefout richting agent.
- `reachable_at`
  Laatste moment waarop een directe agent-call echt succesvol was.

Belangrijk:

- een heartbeat bepaalt niet automatisch dat elke browse-call werkt
- een enkele browse-timeout mag niet blind `last_seen` overschrijven
- status mag niet gaan flappen op basis van één los incident

### Aanbevolen statusregels

- `online` als `last_seen` recent is
- `offline` als heartbeat-timeout overschreden is
- extra foutdetails via `last_error`
- optioneel UI-label zoals `online with recent errors` later, maar niet nodig in V1.1

---

## Authenticatie en beveiliging

### Backend registratie-auth

Registratie vereist een bearer token.

Bijvoorbeeld:

- `Authorization: Bearer <registration-token>`

### Agent endpoint-auth

Alle agent-calls vereisen authenticatie. Niet alleen registratie.

Dus ook:

- `/health`
- `/api/list`
- `/api/info`
- `/api/read`
- `/api/download`

moeten beschermd zijn.

### V1.1 minimum

Voor V1.1 volstaat een eenvoudige gedeelde agent-token, bijvoorbeeld:

- WebManager bewaart een secret per client of per installatie
- backend stuurt dat token mee op elke agent-call
- agent weigert requests zonder geldig token

Voorbeeld:

- `Authorization: Bearer <agent-access-token>`

### Niet doen in V1.1

- open agent-HTTP API zonder auth
- browse/download endpoints publiek bereikbaar maken op het LAN

---

## Virtueel padmodel

Remote client shares krijgen een aparte namespace.

Voorstel voor de gebruikersweergave:

- `/Clients`
- `/Clients/<client-display>`
- `/Clients/<client-display>/<share-label>`
- `/Clients/<client-display>/<share-label>/subdir/file.ext`

Intern moet de backend dit mappen naar:

- `client_id`
- `share_key`
- relatief share-pad

Belangrijk:

- dit zijn logische WebManager-paden
- het zijn geen echte lokale backend filesystem-paden
- ze mogen niet door de bestaande lokale `PathGuard` resolved worden

### Consequentie voor de codebasis

`/Clients/...` moet vroeg in routing worden onderschept door een aparte browse- of source-facade.

Dus:

- niet de lokale `PathGuard` uitbreiden tot remote sources
- niet overal `if remote` in bestaande lokale services strooien
- wel een duidelijke scheiding tussen local source en remote source

---

## Share-validatie in de agent

De agent werkt niet met vrije absolute paden.

De agent heeft een vaste share-map, bijvoorbeeld:

```json
{
  "downloads": "/Users/jan/Downloads",
  "movies": "/Users/jan/Movies",
  "pictures": "/Users/jan/Pictures"
}
```

Een request bevat dan:

- `share = downloads`
- `path = Some/Subdir/file.txt`

Niet:

- `/Users/jan/...`

### Validatieregels

- onbekende `share` weigeren
- `..` weigeren
- pad resolven binnen de gekozen share-root
- symlink escape blokkeren
- alleen toegestane bestandshandelingen toestaan

---

## Read, preview en download limieten

V1.1 moet resource-grenzen expliciet vastleggen.

### Tekstpreview

- maximum grootte voor tekstpreview vastleggen
- voorstel: zelfde orde als huidige server-side preview/edit-limieten, of kleiner
- grote tekstbestanden niet volledig in memory laden voor preview

### Binary versus text

- agent moet tekstpreview alleen teruggeven voor ondersteunde teksttypes
- binaire content mag niet per ongeluk als tekst in JSON-responses worden gepusht

### Download

- downloads moeten gestreamd worden
- geen volledige bestand-buffering in memory

### Image preview

- alleen triviale image preview in V1.1
- geen zware thumbnail-pipeline in deze fase

---

## Offline gedrag

Dit is een harde eis.

WebManager mag niet vastlopen als de agent niet draait.

### Backendregels

- alle agent-calls krijgen korte timeouts, bijvoorbeeld 1-3 seconden
- connect- of timeoutfouten worden vertaald naar nette app-fouten
- offline agent blokkeert nooit globale pagina-initialisatie
- browse- en file-fouten blijven lokaal tot betreffende request

### Frontendregels

- `/Clients` mag laden, ook als sommige clients offline zijn
- offline clients mogen zichtbaar blijven in de lijst
- browsen in offline subtree toont foutmelding
- andere panes blijven bruikbaar
- geen endless spinner

---

## API-ontwerp

## 1. Backend registry endpoints

### `POST /api/clients/register`

Registreert of update een remote agent.

### `POST /api/clients/heartbeat`

Werkt `last_seen` bij.

### `GET /api/clients`

Geeft bekende clients terug met:

- `client_id`
- `display_name`
- `status`
- `last_seen`
- `last_error`
- `shares`

---

## 2. Backend browse facade voor UI

De frontend blijft praten met gewone WebManager-routes.

### `GET /api/browse?path=/Clients`

Geeft alle bekende clients terug als directories.

### `GET /api/browse?path=/Clients/<client>/`

Geeft shares van die client terug als directories.

### `GET /api/browse?path=/Clients/<client>/<share>/...`

Backend vertaalt dit naar een agent-call.

Belangrijk:

- browse facade bepaalt eerst of pad onder `/Clients` valt
- alleen niet-remote paden mogen daarna naar bestaande lokale browse-paths

---

## 3. Agent endpoints

Eenvoudig houden. Geen shell.

### `GET /health`

Gezondheidscheck met auth.

### `GET /api/list?share=downloads&path=subdir`

Directory-inhoud binnen een share.

### `GET /api/info?share=downloads&path=file.txt`

Metadata.

### `GET /api/read?share=downloads&path=file.txt`

Tekstpreview.

### `GET /api/download?share=downloads&path=file.txt`

Gestreamde download.

---

## Haalbaarheid

## Goed haalbaar in V1.1

- client registry
- heartbeat online/offline
- virtuele `Clients` root
- browse
- file info
- tekstpreview
- eenvoudige image preview
- gestreamde download

## Bewust uitgesteld

- rename
- mkdir
- delete
- upload
- bookmarks/startup paths
- cross-source copy
- cross-source move
- unified history
- task-runner integratie

---

## Veranderingen per gebied

## Backend

Nieuwe onderdelen:

- client registry repository
- client registry service
- routes voor register/heartbeat/list
- browse/source facade voor `Clients/...`
- agent HTTP client met harde timeouts en auth

Bestaande onderdelen die waarschijnlijk geraakt worden:

- [routes_browse.py](/workspace/webmanager-mvp/webui/backend/app/api/routes_browse.py)
  Om `/Clients` vroeg te routeren.
- [dependencies.py](/workspace/webmanager-mvp/webui/backend/app/dependencies.py)
  Voor nieuwe registry- en agent-services.
- [app/main.py](/workspace/webmanager-mvp/webui/backend/app/main.py)
  Voor nieuwe routers.

Liever niet verbreden:

- [path_guard.py](/workspace/webmanager-mvp/webui/backend/app/security/path_guard.py)
  Deze hoort lokaal filesystemgericht te blijven.
- [file_ops_service.py](/workspace/webmanager-mvp/webui/backend/app/services/file_ops_service.py)
  Deze service is nu server-filesystemgericht en moet niet vervuild raken met remote transportlogica.

## Frontend

Waarschijnlijk aanpassen:

- [app.js](/workspace/webmanager-mvp/webui/html/app.js)
  Voor:
  - extra virtuele root
  - render van clients en shares
  - offline status
  - source-aware browse/view/download/info flows
- [index.html](/workspace/webmanager-mvp/webui/html/index.html)
  Alleen als extra statuslabels of clientindicatoren nodig zijn

## Remote agent

Te baseren op:

- [finder_commander/app/main.py](/workspace/webmanager-mvp/finder_commander/app/main.py)
- [finder_commander/run-local.sh](/workspace/webmanager-mvp/finder_commander/run-local.sh)
- [finder_commander/requirements.txt](/workspace/webmanager-mvp/finder_commander/requirements.txt)

Maar vereenvoudigd:

- geen shell command endpoint
- geen hele home-root
- alleen `share key + relatief pad`
- registratie en heartbeat toevoegen
- auth afdwingen op alle endpoints

---

## Minimale agent-config

Voorstel lokaal configbestand:

```json
{
  "webmanager_base_url": "https://webmanager.example.com",
  "registration_token": "registration-secret",
  "agent_access_token": "agent-secret",
  "client_id": "f4b2c8f8-2b1b-4d89-9ed2-8d6d7b1f3abc",
  "display_name": "MacBook Pro van Jan",
  "shares": {
    "downloads": "/Users/jan/Downloads",
    "movies": "/Users/jan/Movies",
    "pictures": "/Users/jan/Pictures"
  },
  "listen_host": "0.0.0.0",
  "listen_port": 8765,
  "public_endpoint": "http://192.168.1.25:8765"
}
```

Opmerking:

- `public_endpoint` is het endpoint dat WebManager gebruikt
- `listen_host` en `public_endpoint` hoeven niet identiek te zijn

---

## Open keuzes die bewust zijn uitgesteld

Deze keuzes zijn echt later werk, niet meer V1.1:

- reverse-connect of tunnelmodel
- cross-source copy
- cross-source move
- bookmarks/startup paths voor `/Clients/...`
- write-acties op remote shares
- sterkere pairing of key rotation

---

## Beslisadvies

Aanbevolen implementatievolgorde voor V1.1:

1. agent registry + heartbeat
2. virtuele `Clients` root in browse
3. online/offline status met gescheiden statusvelden
4. browse/info/preview/download voor remote shares

Niet in V1.1:

5. write-acties
6. bookmarks/startup paths
7. cross-source flows

---

## Samenvatting

De juiste V1.1-richting is:

- geen hele homefolder
- wel beperkte shares zoals `Downloads`, `Movies`, `Pictures`
- remote helper-agent op macOS
- agent registreert zichzelf bij WebManager
- WebManager bewaart `client_id`-geleide registry en status
- `/Clients` wordt een aparte virtuele bron
- remote paden blijven buiten lokale filesystem services
- alle agent-calls vereisen auth
- offline agents mogen nooit de rest van WebManager verstoren

Dit model is haalbaar, beperkt in scope, en houdt de bestaande lokale storage-architectuur schoon.
