saltcorn-samba 0.4.14 → 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +147 -0
- package/README.md +75 -5
- package/filemanager-view.js +93 -17
- package/i18n/de.json +93 -0
- package/i18n/en.json +93 -0
- package/i18n.js +155 -0
- package/index.js +45 -15
- package/package.json +4 -2
- package/pdf-view.js +8 -5
- package/public/samba-common.js +175 -0
- package/public/samba-filemanager.js +373 -381
- package/public/samba-tree.js +126 -137
- package/tree-view.js +88 -20
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,153 @@ All notable changes to `saltcorn-samba` are documented here.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.4.16] – 2026-07-08
|
|
8
|
+
|
|
9
|
+
### Added – Internationalisierung (Deutsch + Englisch)
|
|
10
|
+
|
|
11
|
+
Alle sichtbaren Texte im File-Manager, im Tree, im PDF-Fieldview und in den
|
|
12
|
+
Dialogen (Upload, Delete, Rename, Mkdir) sind jetzt übersetzbar.
|
|
13
|
+
|
|
14
|
+
- Neue Kataloge unter `i18n/de.json` und `i18n/en.json` (~90 Schlüssel,
|
|
15
|
+
Punkt-Notation nach Bereich `ui.*`, `fm.*`, `tree.*`, `pdf.*`).
|
|
16
|
+
- Neues Server-Modul `i18n.js` – keine externe Dependency, JSON-Kataloge
|
|
17
|
+
werden gelesen und im Prozess gecacht. Placeholder-Interpolation `{name}`.
|
|
18
|
+
API: `t(key, {locale, ...params})`, `tFor(locale)`, `catalogFor(locale)`,
|
|
19
|
+
`resolveLocaleFromReq(req, explicit)`.
|
|
20
|
+
- Locale-Auflösung: expliziter Wert → `req.getLocale()` → Query `?locale=xx`
|
|
21
|
+
→ `Accept-Language`-Header → Fallback `en`. Unbekannte Locales fallen
|
|
22
|
+
automatisch auf die Sprachbasis oder `en` zurück.
|
|
23
|
+
- Neue Diagnose-Route `GET /samba-i18n.json?locale=xx` liefert den Katalog
|
|
24
|
+
als JSON (kein Auth, enthält keine Konfiguration).
|
|
25
|
+
- Client bekommt den Katalog **inline** in die View-Shell injiziert – kein
|
|
26
|
+
zusätzlicher HTTP-Roundtrip beim ersten Rendern, keine englischen Keys, die
|
|
27
|
+
kurz aufblitzen. `SambaCommon.setCatalog(…)` wird aufgerufen, bevor die
|
|
28
|
+
view-spezifische JS-Datei geladen wird.
|
|
29
|
+
- Fehlt ein Key: automatischer Fallback auf Englisch, dann auf den Key
|
|
30
|
+
selbst – nichts bricht.
|
|
31
|
+
|
|
32
|
+
### Changed – Client-JS: Gemeinsame Utilities in `public/samba-common.js`
|
|
33
|
+
|
|
34
|
+
- Neues Modul `public/samba-common.js` bündelt: `iconFor()`, `extOf()`,
|
|
35
|
+
`mediaTypeFor()`, `isViewable()`, `fmtSize()`, `fmtDate()`, `joinPath()`,
|
|
36
|
+
`parentOf()`, plus die i18n-Funktionen `t()`, `setCatalog()`, `loadCatalog()`.
|
|
37
|
+
- **`iconFor()` war bisher in `samba-filemanager.js` und `samba-tree.js`
|
|
38
|
+
identisch dupliziert** – jetzt einzige Quelle in `samba-common.js`.
|
|
39
|
+
Änderungen am Icon-Mapping (neue Extensions etc.) müssen nur noch an einer
|
|
40
|
+
Stelle gemacht werden.
|
|
41
|
+
- `samba-filemanager.js` und `samba-tree.js` konsumieren die Utilities über
|
|
42
|
+
`window.SambaCommon`. Die lokalen Kopien von `iconFor`, `extOf`,
|
|
43
|
+
`mediaTypeFor`, `isViewable`, `fmtSize`, `fmtDate`, `joinPath`, `parentOf`
|
|
44
|
+
sind entfernt.
|
|
45
|
+
- Alle sichtbaren Strings in beiden Client-JS gehen über `SambaCommon.t(...)`.
|
|
46
|
+
|
|
47
|
+
### Changed – Bootstrap der View-Shells: zweistufig
|
|
48
|
+
|
|
49
|
+
`filemanager-view.js` und `tree-view.js` laden jetzt zuerst `samba-common.js`
|
|
50
|
+
(setzt `window.SambaCommon`, wendet den inline gelieferten i18n-Katalog an)
|
|
51
|
+
und erst danach die view-spezifische JS-Datei. Sind beide bereits geladen,
|
|
52
|
+
wird nur re-mounted – kein doppeltes `<script>`-Injecten. `pdf-view.js`
|
|
53
|
+
nutzt serverseitig `tFor(resolveLocaleFromReq(req))`.
|
|
54
|
+
|
|
55
|
+
### Changed – Namens-Cleanup und Kommentare in beiden Client-JS
|
|
56
|
+
|
|
57
|
+
Goal: die Client-Dateien sollen sich wie ordentliche Node-ähnliche Module
|
|
58
|
+
lesen, nicht wie ein Minifier-Output.
|
|
59
|
+
|
|
60
|
+
- `samba-tree.js`: `h`→`element`, `fetchDir`→`fetchDirectory`,
|
|
61
|
+
`renderList`→`renderLevel`, `openDir`→`toggleDirectory`, `li`→`lineItem`
|
|
62
|
+
u.ä. Jede exportierte / interne Funktion hat einen JSDoc-Kommentar.
|
|
63
|
+
- `samba-filemanager.js`: `h`→`element`, `modal`→`openModal`, `m`→`dialog`,
|
|
64
|
+
`r`→`response`, `fd`→`formData`, `overwriteCb`→`overwriteCheckbox`,
|
|
65
|
+
`picked`→`pickedList`, `uploadBtn`→`uploadButton`. Kommentare ergänzt.
|
|
66
|
+
|
|
67
|
+
### Added – README: Sicherheits-Abschnitt „Client-/Server-Trennung“
|
|
68
|
+
|
|
69
|
+
Neuer Unterabschnitt in `## Sicherheit` erklärt explizit:
|
|
70
|
+
|
|
71
|
+
- Welche Dateien serverseitig laufen (SMB-Credentials, `base_path`,
|
|
72
|
+
`view_base_path`, Sanitizer, Routen) und welche clientseitig (reine UI).
|
|
73
|
+
- Wo genau Sicherheit durchgesetzt wird: CSRF (Saltcorn-csurf), Auth/Rolle
|
|
74
|
+
(Saltcorn + `min_role_read`/`min_role_write`), `sanitizeRelativePath`,
|
|
75
|
+
Base-Path-Enforcement, SMB-Session mit serverseitig konfiguriertem User.
|
|
76
|
+
- Wichtiger Hinweis: `view_base_path` ist **keine** Sicherheitsgrenze
|
|
77
|
+
zwischen Usern; für Mandantentrennung entweder Rollen-gated Views oder
|
|
78
|
+
eigene Plugin-Instanzen mit separaten SMB-Usern.
|
|
79
|
+
- Kurzfassung: JavaScript in `public/` ist reine Kosmetik; jeder Client (auch
|
|
80
|
+
ein manipulierter) bekommt dieselben 403/400-Antworten wie ein regulärer.
|
|
81
|
+
|
|
82
|
+
Zusätzlich neuer Abschnitt `## Internationalisierung (i18n)` mit
|
|
83
|
+
Entwickler-Doku (Kataloge ergänzen, API, Locale-Auflösung, Diagnose-Route).
|
|
84
|
+
|
|
85
|
+
### Packaging
|
|
86
|
+
|
|
87
|
+
- `package.json`: Version 0.4.15 → 0.4.16. `files` ergänzt um `i18n.js` und
|
|
88
|
+
`i18n/`. `lint`-Script prüft jetzt auch `i18n.js` und `public/samba-common.js`.
|
|
89
|
+
- Keine neuen Runtime-Dependencies. Fortlaufend: nur `smb3-client ^0.2.0`.
|
|
90
|
+
|
|
91
|
+
### Compatibility
|
|
92
|
+
|
|
93
|
+
Rückwärtskompatibel:
|
|
94
|
+
|
|
95
|
+
- Alle bestehenden Routen (`/sambadir`, `/sambafile`, `/sambalink`,
|
|
96
|
+
`/sambaupload`, `/sambadelete`, `/sambarename`, `/sambamkdir`, `/sambatest`)
|
|
97
|
+
unverändert in Signatur und Verhalten.
|
|
98
|
+
- View-Konfigurationsschema unverändert; existierende Views laufen weiter.
|
|
99
|
+
- Wer die Client-JS direkt einbindet, muss zusätzlich `samba-common.js` vor
|
|
100
|
+
`samba-filemanager.js` / `samba-tree.js` laden (das übernimmt die
|
|
101
|
+
View-Shell automatisch).
|
|
102
|
+
|
|
103
|
+
## [0.4.15] – 2026-07-07
|
|
104
|
+
|
|
105
|
+
### Fixed – CSRF-Fehlermeldung bei Upload / Ordner anlegen / Rename / Delete
|
|
106
|
+
|
|
107
|
+
Bei jedem schreibenden Klick in der File-Manager-View („Ordner anlegen“,
|
|
108
|
+
„Datei hochladen“, „umbenennen“, „löschen“) erschien der Fehler
|
|
109
|
+
`Invalid CSRF token`, obwohl der Client den Token korrekt im Header
|
|
110
|
+
`X-CSRF-Token` mitgeschickt hat.
|
|
111
|
+
|
|
112
|
+
**Root Cause:** Saltcorns globaler `csurf`-Middleware validiert bereits
|
|
113
|
+
alle POST-Routen der Plugins gegen das Session-Secret. Unser Plugin hatte
|
|
114
|
+
zusätzlich eine manuelle `checkCsrf(req)`-Funktion, die den vom Client
|
|
115
|
+
geschickten Token mit `req.csrfToken()` verglich. `req.csrfToken()` gibt
|
|
116
|
+
aber bei jedem Aufruf einen **frisch gesalzenen** Token zurück – der
|
|
117
|
+
Stringvergleich `provided !== req.csrfToken()` schlug daher immer fehl,
|
|
118
|
+
auch bei völlig gültigen Requests.
|
|
119
|
+
|
|
120
|
+
**Fix:** `checkCsrf` ist jetzt ein dokumentierter No-Op. Die eigentliche
|
|
121
|
+
CSRF-Prüfung übernimmt weiterhin Saltcorns Middleware (kein Sicherheits-
|
|
122
|
+
abbau). Betroffene Write-Routen (Upload, Mkdir, Rename, Delete) in
|
|
123
|
+
`index.js` sind unverändert und funktionieren jetzt korrekt.
|
|
124
|
+
|
|
125
|
+
### Added – View-Basispfad (`view_base_path`) für File-Manager und Tree
|
|
126
|
+
|
|
127
|
+
Beide Views (SambaFileManager und SambaTree) haben ein neues optionales
|
|
128
|
+
Konfigurationsfeld **„View-Basispfad (relativ zum Plugin-Basispfad)“**.
|
|
129
|
+
Damit lässt sich der Pfad zweistufig konfigurieren:
|
|
130
|
+
|
|
131
|
+
1. **Plugin-Basispfad** (weiterhin Pflicht) – z. B. `static` – legt fest,
|
|
132
|
+
in welchem Unterverzeichnis der Freigabe der Plugin-Root liegt.
|
|
133
|
+
2. **View-Basispfad** (neu, optional) – z. B. `projekte/2026` – ein
|
|
134
|
+
statischer Präfix, der nur für diese eine View gilt.
|
|
135
|
+
|
|
136
|
+
Beispiel: Plugin-Basispfad = `static`, View-Basispfad = `projekte/2026`
|
|
137
|
+
→ die View listet den effektiven Pfad `static/projekte/2026`.
|
|
138
|
+
|
|
139
|
+
Der bisherige `extra_subpath` in beiden Views ist jetzt konsistent auf
|
|
140
|
+
den `from_field`-Modus beschränkt (dort weiterhin: Suffix hinter dem
|
|
141
|
+
Feldwert der aktuellen Zeile). Im `static`-Modus zählt nur
|
|
142
|
+
`view_base_path`.
|
|
143
|
+
|
|
144
|
+
Der zusammengesetzte Pfad wird pro Segment slash-getrimmt und
|
|
145
|
+
durchläuft `sanitizeRelativePath`, so dass `..`-Traversal, absolute
|
|
146
|
+
Pfade, UNC-Präfixe usw. weiterhin abgelehnt werden.
|
|
147
|
+
|
|
148
|
+
### Tests
|
|
149
|
+
|
|
150
|
+
`test/sanitize.test.js` deckt jetzt zusätzlich die Pfad-Komposition
|
|
151
|
+
(`view_base_path` + Feldwert + `extra_subpath`) inklusive Slash-
|
|
152
|
+
Normalisierung, Modus-Regeln und Traversal-Ablehnung ab.
|
|
153
|
+
|
|
7
154
|
## [0.4.14] – 2026-07-06
|
|
8
155
|
|
|
9
156
|
### Changed – Code-Review / Aufräumen (keine Verhaltensänderung)
|
package/README.md
CHANGED
|
@@ -194,9 +194,10 @@ Optionen:
|
|
|
194
194
|
|
|
195
195
|
| Option | Zweck |
|
|
196
196
|
|---|---|
|
|
197
|
-
| **
|
|
198
|
-
| **Row
|
|
199
|
-
| **
|
|
197
|
+
| **View-Basispfad (relativ zum Plugin-Basispfad)** | Optionaler statischer Präfix nur für diese View. Beispiel: Plugin-Basispfad = `static`, View-Basispfad = `projekte/2026` → View listet `static/projekte/2026`. Zweistufige Konfiguration: einmal serverweit im Plugin, einmal pro View. |
|
|
198
|
+
| **Row-Modus** | `static` = nur der View-Basispfad zählt, `from_field` = zusätzlich wird ein DB-Feldwert der aktuellen Zeile angehängt (z. B. in einer Show-View) |
|
|
199
|
+
| **Feld mit Unterpfad** | Feld der Tabelle mit dem relativen Ordnernamen (nur bei `from_field`) |
|
|
200
|
+
| **Zusätzlicher Suffix (nach dem Feldwert)** | statischer Suffix, z. B. `invoices` (nur bei `from_field`) |
|
|
200
201
|
| **Show hidden files** | `.dotfiles` einblenden |
|
|
201
202
|
| **Allow navigating up** | Up-Button aktivieren (nur bis zum Root, kein Ausbruch) |
|
|
202
203
|
| **Open PDFs / images inline** | Klick öffnet die Datei im integrierten Viewer |
|
|
@@ -204,6 +205,10 @@ Optionen:
|
|
|
204
205
|
| **Page size** | Einträge pro Seite (0 = alle) |
|
|
205
206
|
| **Panel title** | Text im Karten-Header |
|
|
206
207
|
|
|
208
|
+
Der effektive Pfad wird zusammengesetzt aus (jeweils falls gesetzt):
|
|
209
|
+
`Plugin-Basispfad / View-Basispfad / Feldwert / Zusätzlicher Suffix`.
|
|
210
|
+
`..`-Traversal, absolute Pfade und UNC-Präfixe werden abgelehnt.
|
|
211
|
+
|
|
207
212
|
Beispiel-Datenmodell für kunden-spezifische Ordner:
|
|
208
213
|
|
|
209
214
|
```
|
|
@@ -212,12 +217,13 @@ Felder: name (String), akte_dir (String, z. B. "kunde_42/2026")
|
|
|
212
217
|
```
|
|
213
218
|
|
|
214
219
|
Dann eine Show-View von `kunden` bauen und die `SambaFileManager`-View mit
|
|
215
|
-
|
|
220
|
+
Row-Modus = `from_field`, Feld = `akte_dir` einbetten.
|
|
216
221
|
|
|
217
222
|
### View: `SambaTree`
|
|
218
223
|
|
|
219
224
|
Kompaktere Alternative – lazy-loading Baum, ideal in einer Sidebar oder
|
|
220
|
-
neben Formularen. Gleiche Path-
|
|
225
|
+
neben Formularen. Gleiche Path-Optionen wie beim File-Manager (inkl.
|
|
226
|
+
**View-Basispfad**, **Row-Modus**, **Feld**, **Zusätzlicher Suffix**).
|
|
221
227
|
|
|
222
228
|
### Fieldview `samba_pdf` (deaktiviert seit 0.3.2)
|
|
223
229
|
|
|
@@ -298,6 +304,70 @@ führende/abschließende Punkte oder Windows-Reserved-Names enthalten.
|
|
|
298
304
|
|
|
299
305
|
Unit-Tests zum Path- und Filename-Sanitizer laufen in CI (`npm test`).
|
|
300
306
|
|
|
307
|
+
### Client-/Server-Trennung
|
|
308
|
+
|
|
309
|
+
Damit klar ist, wo Sicherheit tatsächlich durchgesetzt wird: das Plugin
|
|
310
|
+
läuft in zwei streng getrennten Welten.
|
|
311
|
+
|
|
312
|
+
- **Serverseitig (Node.js im Saltcorn-Prozess):**
|
|
313
|
+
`index.js` (HTTP-Routen), `smb-client.js` (SMB3-Verbindung, Auth, Signing,
|
|
314
|
+
Encryption), `readdir-compat.js` (Verzeichnis-Listing),
|
|
315
|
+
`filemanager-view.js` / `tree-view.js` / `pdf-view.js`
|
|
316
|
+
(configuration_workflow, run, computeStartPath, sanitizeRelativePath),
|
|
317
|
+
`i18n.js` (Übersetzungskatalog) und `tools/diag-*.js` (CLI-Diagnose).
|
|
318
|
+
**SMB-Credentials (User, Domain, Passwort), Plugin-`base_path` und pro-View
|
|
319
|
+
`view_base_path` liegen ausschließlich serverseitig in der Saltcorn-DB.**
|
|
320
|
+
Der Browser sieht davon nichts.
|
|
321
|
+
|
|
322
|
+
- **Clientseitig (Browser, unter `public/`):**
|
|
323
|
+
`samba-filemanager.js`, `samba-tree.js`, `samba-common.js` (gemeinsame
|
|
324
|
+
Utilities + i18n) und `samba.css`. Reine UI. Alle Zugriffe laufen über
|
|
325
|
+
`fetch()` mit `X-CSRF-Token`-Header.
|
|
326
|
+
|
|
327
|
+
- **Enforcement-Punkte serverseitig — in jeder Request-Bearbeitung:**
|
|
328
|
+
1. **CSRF** – Saltcorns globale csurf-Middleware. Ohne Token: 403.
|
|
329
|
+
2. **Auth/Rolle** – Saltcorn prüft Login und View/Table-Rechte vor `run(…)`.
|
|
330
|
+
Zusätzlich prüft jede Route `min_role_read` bzw. `min_role_write`.
|
|
331
|
+
3. **Pfad-Sanitizing** – `sanitizeRelativePath` lehnt `..`, absolute Pfade,
|
|
332
|
+
UNC-Präfixe, Laufwerksbuchstaben und NUL-Bytes ab.
|
|
333
|
+
4. **Base-Path-Enforcement** – der effektive Pfad ist immer relativ zum
|
|
334
|
+
serverseitig konfigurierten `base_path`; `view_base_path` stammt aus der
|
|
335
|
+
View-Konfig, nie aus dem Request.
|
|
336
|
+
5. **SMB-Session** läuft mit dem serverseitig konfigurierten SMB-User; der
|
|
337
|
+
Client kann keine anderen Credentials injizieren.
|
|
338
|
+
|
|
339
|
+
- **Wichtige Hinweise:**
|
|
340
|
+
`view_base_path` ist **keine** Sicherheitsgrenze zwischen Usern – nur
|
|
341
|
+
Bequemlichkeit. Für Mandantentrennung entweder eigene Views pro Mandant
|
|
342
|
+
(mit Rollen-Steuerung), oder mehrere Plugin-Instanzen mit unterschiedlichen
|
|
343
|
+
SMB-Usern. Produktions-Empfehlung: dedizierter SMB-Service-Account mit
|
|
344
|
+
minimalen Rechten; die Fileserver-ACL limitiert dann zusätzlich.
|
|
345
|
+
|
|
346
|
+
- **Kurzfassung:** JavaScript in `public/` ist reine Kosmetik. Alle
|
|
347
|
+
Zugriffsrechte werden serverseitig durchgesetzt — ein manipulierter Client
|
|
348
|
+
bekommt genau die gleichen 403/400-Antworten wie ein regulärer.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Internationalisierung (i18n)
|
|
353
|
+
|
|
354
|
+
Ab v0.4.16 sind alle sichtbaren Texte übersetzbar. Ausgeliefert werden **de**
|
|
355
|
+
und **en**.
|
|
356
|
+
|
|
357
|
+
- Kataloge liegen als JSON unter `i18n/<locale>.json`.
|
|
358
|
+
- Serverseitige Nutzung: `const { t, tFor } = require("./i18n");`
|
|
359
|
+
→ `t("fm.upload.button", { locale: "de" })` bzw.
|
|
360
|
+
`const _t = tFor("de"); _t("fm.upload.button")`.
|
|
361
|
+
- Clientseitig: `window.SambaCommon.t("fm.upload.button")`. Der Katalog wird
|
|
362
|
+
von der View-Shell inline in die Seite injiziert (kein Extra-Request).
|
|
363
|
+
- Locale-Auflösung: explizit übergebener Wert → `req.getLocale()` →
|
|
364
|
+
`?locale=xx` → `Accept-Language` → Fallback `en`.
|
|
365
|
+
- Neue Sprache ergänzen: `i18n/<locale>.json` anlegen, `availableLocales` in
|
|
366
|
+
`i18n.js` erweitern, PR aufmachen. Fehlt ein Key, greift automatisch der
|
|
367
|
+
englische Fallback — nichts bricht.
|
|
368
|
+
- Diagnose-Endpoint: `GET /samba-i18n.json?locale=de` liefert den kompletten
|
|
369
|
+
Katalog als JSON (ohne Auth, enthält keine Konfiguration).
|
|
370
|
+
|
|
301
371
|
---
|
|
302
372
|
|
|
303
373
|
## Entwicklung
|
package/filemanager-view.js
CHANGED
|
@@ -18,6 +18,7 @@ const Field = require("@saltcorn/data/models/field");
|
|
|
18
18
|
const Table = require("@saltcorn/data/models/table");
|
|
19
19
|
|
|
20
20
|
const { sanitizeRelativePath } = require("./smb-client");
|
|
21
|
+
const { catalogFor, resolveLocaleFromReq } = require("./i18n");
|
|
21
22
|
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
// Configuration workflow
|
|
@@ -44,31 +45,42 @@ const configuration_workflow = () =>
|
|
|
44
45
|
.map((f) => f.name);
|
|
45
46
|
return new Form({
|
|
46
47
|
fields: [
|
|
48
|
+
new Field({
|
|
49
|
+
name: "view_base_path",
|
|
50
|
+
label: "View-Basispfad (relativ zum Plugin-Basispfad)",
|
|
51
|
+
type: "String",
|
|
52
|
+
sublabel:
|
|
53
|
+
"Optional. Statischer Pfad relativ zum Plugin-Basispfad, der f\u00fcr diese View gilt. " +
|
|
54
|
+
"Beispiel: Plugin-Basispfad = 'static', View-Basispfad = 'projekte/2026' \u2192 View listet 'static/projekte/2026'. " +
|
|
55
|
+
"Keine f\u00fchrenden/abschlie\u00dfenden Slashes n\u00f6tig. Traversal (\u201e..\u201c) wird abgelehnt.",
|
|
56
|
+
}),
|
|
47
57
|
new Field({
|
|
48
58
|
name: "mode",
|
|
49
|
-
label: "
|
|
59
|
+
label: "Row-Modus",
|
|
50
60
|
type: "String",
|
|
51
61
|
required: true,
|
|
52
62
|
attributes: {
|
|
53
63
|
options: ["static", "from_field"],
|
|
54
64
|
},
|
|
55
65
|
sublabel:
|
|
56
|
-
"static =
|
|
66
|
+
"static = immer nur der View-Basispfad. from_field = h\u00e4nge einen Feldwert der aktuellen Zeile an " +
|
|
67
|
+
"(nur sinnvoll, wenn die View in einer Show-View eingebettet ist).",
|
|
57
68
|
default: "static",
|
|
58
69
|
}),
|
|
59
70
|
new Field({
|
|
60
71
|
name: "path_field",
|
|
61
|
-
label: "
|
|
72
|
+
label: "Feld mit Unterpfad",
|
|
62
73
|
type: "String",
|
|
63
74
|
attributes: { options: stringFieldOptions },
|
|
64
75
|
showIf: { mode: "from_field" },
|
|
65
76
|
}),
|
|
66
77
|
new Field({
|
|
67
78
|
name: "extra_subpath",
|
|
68
|
-
label: "
|
|
79
|
+
label: "Zus\u00e4tzlicher Suffix (nach dem Feldwert)",
|
|
69
80
|
type: "String",
|
|
70
81
|
sublabel:
|
|
71
|
-
"Optional.
|
|
82
|
+
"Optional. Statischer Suffix, der nach dem Feldwert angeh\u00e4ngt wird. Beispiel: 'invoices'.",
|
|
83
|
+
showIf: { mode: "from_field" },
|
|
72
84
|
}),
|
|
73
85
|
new Field({
|
|
74
86
|
name: "show_hidden",
|
|
@@ -122,22 +134,58 @@ async function get_state_fields() {
|
|
|
122
134
|
return [];
|
|
123
135
|
}
|
|
124
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Compose the start path (relative to the plugin base_path) from three
|
|
139
|
+
* optional parts:
|
|
140
|
+
* 1. `view_base_path` — static, per-view prefix
|
|
141
|
+
* 2. row field value — only in "from_field" mode
|
|
142
|
+
* 3. `extra_subpath` — static suffix, only in "from_field" mode
|
|
143
|
+
*
|
|
144
|
+
* Empty parts are dropped. The final joined path is passed through
|
|
145
|
+
* sanitizeRelativePath so traversal / absolute paths / backslashes are
|
|
146
|
+
* rejected consistently.
|
|
147
|
+
*/
|
|
125
148
|
function computeStartPath(configuration, row) {
|
|
126
149
|
const parts = [];
|
|
150
|
+
if (configuration.view_base_path) {
|
|
151
|
+
parts.push(String(configuration.view_base_path));
|
|
152
|
+
}
|
|
127
153
|
if (configuration.mode === "from_field" && configuration.path_field && row) {
|
|
128
154
|
const v = row[configuration.path_field];
|
|
129
155
|
if (v) parts.push(String(v));
|
|
130
156
|
}
|
|
131
|
-
if (
|
|
132
|
-
|
|
157
|
+
if (
|
|
158
|
+
configuration.mode === "from_field" &&
|
|
159
|
+
configuration.extra_subpath
|
|
160
|
+
) {
|
|
161
|
+
parts.push(String(configuration.extra_subpath));
|
|
162
|
+
}
|
|
163
|
+
const joined = parts
|
|
164
|
+
.map((p) => p.replace(/^\/+|\/+$/g, ""))
|
|
165
|
+
.filter(Boolean)
|
|
166
|
+
.join("/");
|
|
167
|
+
return sanitizeRelativePath(joined);
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
// ---------------------------------------------------------------------------
|
|
136
171
|
// Shell renderer
|
|
137
172
|
// ---------------------------------------------------------------------------
|
|
138
173
|
|
|
139
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Serialisiert den i18n-Katalog inline in die Seite. Alternative w\u00e4re ein
|
|
176
|
+
* zus\u00e4tzlicher HTTP-Roundtrip auf /samba-i18n.json — inline spart einen
|
|
177
|
+
* Request und rendert die Oberfl\u00e4che ohne Aufblitzen englischer Keys.
|
|
178
|
+
* `</script>` wird escaped, damit das JSON-Blob den HTML-Parser nicht t\u00f6tet.
|
|
179
|
+
*/
|
|
180
|
+
function serialiseCatalog(catalog) {
|
|
181
|
+
return JSON.stringify(catalog || {}).replace(/</g, "\\u003c");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function renderShell(startPath, configuration, pluginVersion, csrfToken, locale, catalog) {
|
|
140
185
|
const id = "samba-fm-" + Math.random().toString(36).slice(2, 10);
|
|
186
|
+
// Panel-Titel: wenn der User keinen eigenen Titel gesetzt hat, greift die
|
|
187
|
+
// \u00fcbersetzte Default-Bezeichnung aus dem i18n-Katalog.
|
|
188
|
+
const defaultTitle = (catalog && catalog["fm.title_default"]) || "Samba files";
|
|
141
189
|
const opts = {
|
|
142
190
|
startPath,
|
|
143
191
|
showHidden: !!configuration.show_hidden,
|
|
@@ -145,10 +193,16 @@ function renderShell(startPath, configuration, pluginVersion, csrfToken) {
|
|
|
145
193
|
pdfInline: configuration.pdf_inline !== false,
|
|
146
194
|
exposeSmbLink: configuration.expose_smb_link !== false,
|
|
147
195
|
pageSize: Number(configuration.page_size) || 0,
|
|
148
|
-
title: configuration.title ||
|
|
196
|
+
title: configuration.title || defaultTitle,
|
|
149
197
|
csrfToken: csrfToken || "",
|
|
198
|
+
locale: locale || "en",
|
|
150
199
|
};
|
|
151
200
|
|
|
201
|
+
// Zwei-Stufen-Bootstrap:
|
|
202
|
+
// 1. samba-common.js laden (SambaCommon global) und Katalog setzen
|
|
203
|
+
// 2. samba-filemanager.js laden und mounten
|
|
204
|
+
// Wenn beide Skripte bereits geladen sind, wird nur remounted.
|
|
205
|
+
const catalogJson = serialiseCatalog(catalog);
|
|
152
206
|
return (
|
|
153
207
|
div(
|
|
154
208
|
{ class: "samba-fm card" },
|
|
@@ -167,14 +221,23 @@ function renderShell(startPath, configuration, pluginVersion, csrfToken) {
|
|
|
167
221
|
) +
|
|
168
222
|
script(
|
|
169
223
|
`(function(){
|
|
170
|
-
|
|
224
|
+
var CATALOG=${catalogJson};
|
|
225
|
+
function applyCatalog(){ if(window.SambaCommon && window.SambaCommon.setCatalog) window.SambaCommon.setCatalog(CATALOG); }
|
|
226
|
+
function mount(){window.saltcornSambaMountFM && window.saltcornSambaMountFM(${JSON.stringify(
|
|
171
227
|
id
|
|
172
228
|
)}, ${JSON.stringify(opts)});}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
229
|
+
function loadFM(){
|
|
230
|
+
if(window.saltcornSambaMountFM){ mount(); return; }
|
|
231
|
+
var s=document.createElement('script');
|
|
232
|
+
s.src='/plugins/public/saltcorn-samba@${pluginVersion}/samba-filemanager.js';
|
|
233
|
+
s.onload=mount;
|
|
234
|
+
document.head.appendChild(s);
|
|
235
|
+
}
|
|
236
|
+
if(window.SambaCommon){ applyCatalog(); loadFM(); return; }
|
|
237
|
+
var c=document.createElement('script');
|
|
238
|
+
c.src='/plugins/public/saltcorn-samba@${pluginVersion}/samba-common.js';
|
|
239
|
+
c.onload=function(){ applyCatalog(); loadFM(); };
|
|
240
|
+
document.head.appendChild(c);
|
|
178
241
|
})();`
|
|
179
242
|
)
|
|
180
243
|
);
|
|
@@ -196,9 +259,21 @@ function extractCsrf(extra) {
|
|
|
196
259
|
return "";
|
|
197
260
|
}
|
|
198
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Liest die vom Nutzer bevorzugte Sprache aus dem Request (Saltcorn setzt
|
|
264
|
+
* das aus Login-Cookie / Accept-Language) und liefert Locale + Katalog. Bei
|
|
265
|
+
* Ausfall greift `en` als Notnagel – hardgecodete Strings werden nie leer.
|
|
266
|
+
*/
|
|
267
|
+
function resolveLocaleAndCatalog(extra) {
|
|
268
|
+
const req = extra && extra.req;
|
|
269
|
+
const locale = resolveLocaleFromReq(req);
|
|
270
|
+
return { locale, catalog: catalogFor(locale) };
|
|
271
|
+
}
|
|
272
|
+
|
|
199
273
|
async function run(table_id, viewname, configuration, state, extra) {
|
|
200
274
|
const pluginVersion = configuration.__pluginVersion || "0.3.0";
|
|
201
275
|
const csrf = extractCsrf(extra);
|
|
276
|
+
const { locale, catalog } = resolveLocaleAndCatalog(extra);
|
|
202
277
|
let row = extra && extra.row;
|
|
203
278
|
if (!row && configuration.mode === "from_field" && table_id) {
|
|
204
279
|
const table = await Table.findOne({ id: table_id });
|
|
@@ -216,12 +291,13 @@ async function run(table_id, viewname, configuration, state, extra) {
|
|
|
216
291
|
} catch (e) {
|
|
217
292
|
return div({ class: "alert alert-danger" }, "Samba file manager: " + e.message);
|
|
218
293
|
}
|
|
219
|
-
return renderShell(startPath, configuration, pluginVersion, csrf);
|
|
294
|
+
return renderShell(startPath, configuration, pluginVersion, csrf, locale, catalog);
|
|
220
295
|
}
|
|
221
296
|
|
|
222
297
|
async function runMany(table_id, viewname, configuration, state, extra) {
|
|
223
298
|
const pluginVersion = configuration.__pluginVersion || "0.3.0";
|
|
224
299
|
const csrf = extractCsrf(extra);
|
|
300
|
+
const { locale, catalog } = resolveLocaleAndCatalog(extra);
|
|
225
301
|
const rows = (extra && extra.rows) || [];
|
|
226
302
|
return rows.map((row) => {
|
|
227
303
|
let startPath = "";
|
|
@@ -231,7 +307,7 @@ async function runMany(table_id, viewname, configuration, state, extra) {
|
|
|
231
307
|
startPath = "";
|
|
232
308
|
}
|
|
233
309
|
return {
|
|
234
|
-
html: renderShell(startPath, configuration, pluginVersion, csrf),
|
|
310
|
+
html: renderShell(startPath, configuration, pluginVersion, csrf, locale, catalog),
|
|
235
311
|
row,
|
|
236
312
|
};
|
|
237
313
|
});
|
package/i18n/de.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$meta": {
|
|
3
|
+
"language": "Deutsch",
|
|
4
|
+
"locale": "de",
|
|
5
|
+
"note": "Übersetzungskatalog für saltcorn-samba. Punkt-Notation gruppiert nach Bereich."
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
"ui.loading": "Lädt …",
|
|
9
|
+
"ui.close": "Schließen",
|
|
10
|
+
"ui.cancel": "Abbrechen",
|
|
11
|
+
"ui.confirm": "OK",
|
|
12
|
+
"ui.error": "Fehler",
|
|
13
|
+
"ui.empty": "(leeres Verzeichnis)",
|
|
14
|
+
"ui.empty_short": "(leer)",
|
|
15
|
+
"ui.path": "Pfad:",
|
|
16
|
+
"ui.show_hidden": "Versteckte Dateien anzeigen",
|
|
17
|
+
"ui.up": "⬆ Übergeordnet",
|
|
18
|
+
"ui.up_title": "Ein Verzeichnis nach oben",
|
|
19
|
+
"ui.home_title": "Zur Wurzel springen",
|
|
20
|
+
"ui.refresh_title": "Aktualisieren",
|
|
21
|
+
"ui.page_of": "Seite {page} / {total}",
|
|
22
|
+
"ui.prev_page": "‹",
|
|
23
|
+
"ui.next_page": "›",
|
|
24
|
+
|
|
25
|
+
"fm.title_default": "Samba-Dateien",
|
|
26
|
+
"fm.col.filename": "Dateiname",
|
|
27
|
+
"fm.col.media_type": "MIME-Typ",
|
|
28
|
+
"fm.col.size": "Größe",
|
|
29
|
+
"fm.col.modified": "Geändert",
|
|
30
|
+
"fm.col.actions": "Aktionen",
|
|
31
|
+
"fm.counter": "{dirs} Ordner · {files} Dateien",
|
|
32
|
+
"fm.open": "Öffnen",
|
|
33
|
+
"fm.open_new_tab": "In neuem Tab öffnen",
|
|
34
|
+
"fm.view": "Anzeigen",
|
|
35
|
+
"fm.download": "Herunterladen",
|
|
36
|
+
"fm.rename": "Umbenennen",
|
|
37
|
+
"fm.rename_title": "Umbenennen",
|
|
38
|
+
"fm.delete": "Löschen",
|
|
39
|
+
"fm.delete_title": "Löschen",
|
|
40
|
+
"fm.smb_link_title": "Im Datei-Manager öffnen (Nemo/Nautilus/Explorer)",
|
|
41
|
+
"fm.upload": "⬆ Hochladen",
|
|
42
|
+
"fm.upload_title": "Dateien in dieses Verzeichnis hochladen",
|
|
43
|
+
"fm.new_folder": "+ Ordner",
|
|
44
|
+
"fm.new_folder_title": "Neuen Ordner anlegen",
|
|
45
|
+
|
|
46
|
+
"fm.confirm_delete.title": "Löschen bestätigen",
|
|
47
|
+
"fm.confirm_delete.prompt_prefix": "„",
|
|
48
|
+
"fm.confirm_delete.prompt_suffix": "\" wirklich löschen?",
|
|
49
|
+
"fm.confirm_delete.dir_hint": "Klappt nur, wenn das Verzeichnis leer ist.",
|
|
50
|
+
"fm.confirm_delete.file_hint": "Dieser Vorgang kann nicht rückgängig gemacht werden.",
|
|
51
|
+
"fm.deleted_ok": "„{name}\" gelöscht",
|
|
52
|
+
"fm.deleted_fail": "Löschen fehlgeschlagen: {msg}",
|
|
53
|
+
|
|
54
|
+
"fm.rename.title": "„{name}\" umbenennen",
|
|
55
|
+
"fm.rename.label": "Neuer Name",
|
|
56
|
+
"fm.rename.hint": "Slashes sind nicht erlaubt. Zum Verschieben stattdessen die ↗-Funktion nutzen.",
|
|
57
|
+
"fm.renamed_ok": "Umbenannt in „{name}\"",
|
|
58
|
+
"fm.rename_fail": "Umbenennen fehlgeschlagen: {msg}",
|
|
59
|
+
|
|
60
|
+
"fm.mkdir.title": "Ordner anlegen in {path}",
|
|
61
|
+
"fm.mkdir.label": "Ordnername",
|
|
62
|
+
"fm.mkdir.placeholder": "Neuer Ordnername",
|
|
63
|
+
"fm.mkdir.create": "Anlegen",
|
|
64
|
+
"fm.mkdir_ok": "Ordner „{name}\" angelegt",
|
|
65
|
+
"fm.mkdir_fail": "Ordner anlegen fehlgeschlagen: {msg}",
|
|
66
|
+
|
|
67
|
+
"fm.upload.title": "Dateien hochladen",
|
|
68
|
+
"fm.upload.dropzone": "Dateien hierher ziehen oder klicken zum Auswählen",
|
|
69
|
+
"fm.upload.into_prefix": "Ziel: ",
|
|
70
|
+
"fm.upload.limit": ". Max. {mb} MiB pro Datei.",
|
|
71
|
+
"fm.upload.or_pick": "…oder Dateien auswählen",
|
|
72
|
+
"fm.upload.overwrite": "Vorhandene Dateien überschreiben",
|
|
73
|
+
"fm.upload.button": "Hochladen",
|
|
74
|
+
"fm.upload.uploading": "Wird hochgeladen …",
|
|
75
|
+
"fm.upload.no_files": "Keine Dateien ausgewählt",
|
|
76
|
+
"fm.upload.ok": "{n} Datei(en) hochgeladen",
|
|
77
|
+
"fm.upload.partial_summary": "{ok} von {total} Dateien hochgeladen. Fehler:",
|
|
78
|
+
"fm.upload.some_failed": "Einige Uploads fehlgeschlagen",
|
|
79
|
+
"fm.upload.failed": "Upload fehlgeschlagen: {msg}",
|
|
80
|
+
"fm.upload.failed_short": "Upload fehlgeschlagen",
|
|
81
|
+
|
|
82
|
+
"tree.open_in_fm": "Im Datei-Manager öffnen",
|
|
83
|
+
"tree.open_in_fm_title": "Im Datei-Manager öffnen",
|
|
84
|
+
"tree.error_prefix": "Fehler: ",
|
|
85
|
+
"tree.samba_prefix": "Samba: ",
|
|
86
|
+
"tree.open_new_tab": "In neuem Tab öffnen",
|
|
87
|
+
|
|
88
|
+
"pdf.download": "Herunterladen",
|
|
89
|
+
"pdf.open_in_fm": "Im Datei-Manager öffnen",
|
|
90
|
+
"pdf.open_in_fm_title": "In Nemo/Nautilus/Explorer öffnen",
|
|
91
|
+
"pdf.open_new_tab": "In neuem Tab öffnen",
|
|
92
|
+
"pdf.empty": "—"
|
|
93
|
+
}
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$meta": {
|
|
3
|
+
"language": "English",
|
|
4
|
+
"locale": "en",
|
|
5
|
+
"note": "Translation catalogue for saltcorn-samba. Dot notation groups keys by area."
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
"ui.loading": "Loading…",
|
|
9
|
+
"ui.close": "Close",
|
|
10
|
+
"ui.cancel": "Cancel",
|
|
11
|
+
"ui.confirm": "OK",
|
|
12
|
+
"ui.error": "Error",
|
|
13
|
+
"ui.empty": "(empty directory)",
|
|
14
|
+
"ui.empty_short": "(empty)",
|
|
15
|
+
"ui.path": "Path:",
|
|
16
|
+
"ui.show_hidden": "Show hidden",
|
|
17
|
+
"ui.up": "⬆ Up",
|
|
18
|
+
"ui.up_title": "Up one directory",
|
|
19
|
+
"ui.home_title": "Go to root",
|
|
20
|
+
"ui.refresh_title": "Refresh",
|
|
21
|
+
"ui.page_of": "Page {page} / {total}",
|
|
22
|
+
"ui.prev_page": "‹",
|
|
23
|
+
"ui.next_page": "›",
|
|
24
|
+
|
|
25
|
+
"fm.title_default": "Samba files",
|
|
26
|
+
"fm.col.filename": "Filename",
|
|
27
|
+
"fm.col.media_type": "Media type",
|
|
28
|
+
"fm.col.size": "Size",
|
|
29
|
+
"fm.col.modified": "Modified",
|
|
30
|
+
"fm.col.actions": "Actions",
|
|
31
|
+
"fm.counter": "{dirs} folders · {files} files",
|
|
32
|
+
"fm.open": "Open",
|
|
33
|
+
"fm.open_new_tab": "Open in new tab",
|
|
34
|
+
"fm.view": "View",
|
|
35
|
+
"fm.download": "Download",
|
|
36
|
+
"fm.rename": "Rename",
|
|
37
|
+
"fm.rename_title": "Rename",
|
|
38
|
+
"fm.delete": "Delete",
|
|
39
|
+
"fm.delete_title": "Delete",
|
|
40
|
+
"fm.smb_link_title": "Open in file manager (Nemo/Nautilus/Explorer)",
|
|
41
|
+
"fm.upload": "⬆ Upload",
|
|
42
|
+
"fm.upload_title": "Upload files to this directory",
|
|
43
|
+
"fm.new_folder": "+ Folder",
|
|
44
|
+
"fm.new_folder_title": "Create new folder",
|
|
45
|
+
|
|
46
|
+
"fm.confirm_delete.title": "Confirm delete",
|
|
47
|
+
"fm.confirm_delete.prompt_prefix": "Delete \"",
|
|
48
|
+
"fm.confirm_delete.prompt_suffix": "\"?",
|
|
49
|
+
"fm.confirm_delete.dir_hint": "This will only succeed if the directory is empty.",
|
|
50
|
+
"fm.confirm_delete.file_hint": "This action cannot be undone.",
|
|
51
|
+
"fm.deleted_ok": "Deleted {name}",
|
|
52
|
+
"fm.deleted_fail": "Delete failed: {msg}",
|
|
53
|
+
|
|
54
|
+
"fm.rename.title": "Rename \"{name}\"",
|
|
55
|
+
"fm.rename.label": "New name",
|
|
56
|
+
"fm.rename.hint": "Slashes are not allowed. To move, use the ↗ file-manager link instead.",
|
|
57
|
+
"fm.renamed_ok": "Renamed to {name}",
|
|
58
|
+
"fm.rename_fail": "Rename failed: {msg}",
|
|
59
|
+
|
|
60
|
+
"fm.mkdir.title": "Create folder in {path}",
|
|
61
|
+
"fm.mkdir.label": "Folder name",
|
|
62
|
+
"fm.mkdir.placeholder": "New folder name",
|
|
63
|
+
"fm.mkdir.create": "Create",
|
|
64
|
+
"fm.mkdir_ok": "Created folder {name}",
|
|
65
|
+
"fm.mkdir_fail": "Create folder failed: {msg}",
|
|
66
|
+
|
|
67
|
+
"fm.upload.title": "Upload files",
|
|
68
|
+
"fm.upload.dropzone": "Drop files here or click to select",
|
|
69
|
+
"fm.upload.into_prefix": "Uploading to ",
|
|
70
|
+
"fm.upload.limit": ". Max {mb} MiB per file.",
|
|
71
|
+
"fm.upload.or_pick": "…or pick files",
|
|
72
|
+
"fm.upload.overwrite": "Overwrite existing files",
|
|
73
|
+
"fm.upload.button": "Upload",
|
|
74
|
+
"fm.upload.uploading": "Uploading…",
|
|
75
|
+
"fm.upload.no_files": "No files selected",
|
|
76
|
+
"fm.upload.ok": "Uploaded {n} file(s)",
|
|
77
|
+
"fm.upload.partial_summary": "Uploaded {ok} / {total} files. Failures:",
|
|
78
|
+
"fm.upload.some_failed": "Some uploads failed",
|
|
79
|
+
"fm.upload.failed": "Upload failed: {msg}",
|
|
80
|
+
"fm.upload.failed_short": "Upload failed",
|
|
81
|
+
|
|
82
|
+
"tree.open_in_fm": "Open in file manager",
|
|
83
|
+
"tree.open_in_fm_title": "Open in file manager",
|
|
84
|
+
"tree.error_prefix": "Error: ",
|
|
85
|
+
"tree.samba_prefix": "Samba: ",
|
|
86
|
+
"tree.open_new_tab": "Open in new tab",
|
|
87
|
+
|
|
88
|
+
"pdf.download": "Download",
|
|
89
|
+
"pdf.open_in_fm": "Open in file manager",
|
|
90
|
+
"pdf.open_in_fm_title": "Open in Nemo/Nautilus/Explorer",
|
|
91
|
+
"pdf.open_new_tab": "Open in new tab",
|
|
92
|
+
"pdf.empty": "—"
|
|
93
|
+
}
|