saltcorn-samba 0.3.3 → 0.3.5
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 +38 -0
- package/README.md +43 -11
- package/index.js +316 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ 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.3.5] – 2026-07-05
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Testverbindung im Konfigurations-Wizard.**
|
|
11
|
+
Auf Schritt 1 „Samba-Server“ gibt es jetzt einen Button
|
|
12
|
+
„→ Verbindung jetzt testen“. Er öffnet eine SMB-Sitzung mit den
|
|
13
|
+
aktuell im Formular stehenden Werten (ohne zu speichern), meldet den
|
|
14
|
+
Benutzer an und listet die Wurzel bzw. den Basispfad des Shares auf.
|
|
15
|
+
Bei Erfolg werden Dauer, Anzahl Einträge und die ersten 20 Namen
|
|
16
|
+
angezeigt; bei Fehlschlag der genaue SMB-Fehler plus konkreter
|
|
17
|
+
Handlungshinweis auf Deutsch (z. B. DNS, ECONNREFUSED, LOGON_FAILURE,
|
|
18
|
+
BAD_NETWORK_NAME, SMBv1).
|
|
19
|
+
- **Neue Route `POST /sambatest` (nur Admin).** Nimmt die Serverdaten
|
|
20
|
+
als JSON entgegen, gibt strukturiertes Ergebnis mit `ok`, `error`,
|
|
21
|
+
`code`, `hint`, `attempted` zurück. Verändert keine Konfiguration.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Alle Feld-Beschreibungen in Schritt 1 komplett neu, ausführlich
|
|
25
|
+
auf Deutsch.** Jedes Feld (Server, Freigabe, Domäne, Benutzer,
|
|
26
|
+
Passwort, Basispfad, Port) erklärt Format, Docker-Fallstricke,
|
|
27
|
+
Standardwerte und typische Fehlerquellen. Schritt 2 ebenfalls
|
|
28
|
+
vollständig lokalisiert.
|
|
29
|
+
- Schritt-Namen: „Samba server“ → „Samba-Server“, „Access & permissions“
|
|
30
|
+
→ „Zugriff & Berechtigungen“.
|
|
31
|
+
|
|
32
|
+
## [0.3.4] – 2026-07-05
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- **`function is not iterable` beim Installieren behoben.**
|
|
36
|
+
In v0.3.3 hatte ich überkompensiert: der Manifest-Key `dependencies`
|
|
37
|
+
wurde in eine Factory-Funktion umgewandelt — Saltcorn liest `dependencies`
|
|
38
|
+
jedoch NICHT über `withCfg`, sondern iteriert es direkt mit `for..of`
|
|
39
|
+
([`saltcorn-data/models/plugin.ts` Zeile 638](https://github.com/saltcorn/saltcorn/blob/master/packages/saltcorn-data/models/plugin.ts)).
|
|
40
|
+
Deshalb muss `dependencies` ein statisches Array bleiben. Alle anderen
|
|
41
|
+
Keys (viewtemplates, routes, headers) werden weiterhin über `withCfg`
|
|
42
|
+
gelesen und bleiben Factory-Funktionen. Ein ausführlicher Header-Kommentar
|
|
43
|
+
über `module.exports` dokumentiert nun beide Zugriffsmuster.
|
|
44
|
+
|
|
7
45
|
## [0.3.3] – 2026-07-05
|
|
8
46
|
|
|
9
47
|
### Fixed
|
package/README.md
CHANGED
|
@@ -98,20 +98,22 @@ docker exec -it <saltcorn-container> \
|
|
|
98
98
|
Nach der Installation unter *Einstellungen → Plugins → saltcorn-samba →
|
|
99
99
|
Configure* ausfüllen. Die Konfiguration ist zweistufig:
|
|
100
100
|
|
|
101
|
-
### Schritt 1 — *Samba
|
|
101
|
+
### Schritt 1 — *Samba-Server*
|
|
102
102
|
|
|
103
103
|
| Feld | Beispiel | Beschreibung |
|
|
104
104
|
|---|---|---|
|
|
105
|
-
| Server | `192.168.1.
|
|
106
|
-
| Share
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
| Port | `445` | Standard
|
|
112
|
-
| SMB host visible to clients | `fileserver.lan` | *optional* — Host, der in `smb://`-Links auftaucht (nützlich in Docker) |
|
|
105
|
+
| **Server** | `192.168.1.20`, `nas01`, `fileserver.local` | Hostname oder IP des Samba-Servers, **ohne** `smb://`-Präfix und **ohne** Backslashes. Muss aus Sicht des Saltcorn-Prozesses auflösbar sein. **In Docker:** nicht `localhost` verwenden – stattdessen LAN-IP des Hosts oder Container ins passende Netzwerk hängen. |
|
|
106
|
+
| **Freigabe / Share-Name** | `daten`, `public`, `projekte` | Name der SMB-Freigabe **ohne** Slashes. Auf dem Server als `[NAME]`-Abschnitt in `smb.conf` bzw. unter Windows als Freigabename sichtbar. Nicht die Ordner-Bezeichnung. |
|
|
107
|
+
| **Domäne / Arbeitsgruppe** | `WORKGROUP`, `CONTOSO` | Meist `WORKGROUP` (Standard). Für Active Directory: NetBIOS-Name der Domäne, nicht der FQDN. |
|
|
108
|
+
| **Benutzername** | `saltcorn` | Samba-/AD-Benutzer, **nicht** im Format `DOMAIN\user` (Domäne gehört in das eigene Feld). Leer lassen für anonymen Zugriff (nur bei `guest ok = yes`). |
|
|
109
|
+
| **Passwort** | *(secret)* | Samba nutzt ein eigenes Passwort (`smbpasswd`), nicht zwingend das Linux-Login. Moderne Server lehnen leere Passwörter ab. |
|
|
110
|
+
| **Basispfad** | `projekte/2026` | Optional. Relativ, mit Slashes, **ohne** führenden `/`. Beschränkt jeden Zugriff auf dieses Unterverzeichnis der Freigabe. `..` und absolute Pfade werden abgelehnt. |
|
|
111
|
+
| **TCP-Port** | `445` | Standard SMB2/3 über TCP. **SMBv1 (139) wird nicht unterstützt** – auf dem Server `min protocol = SMB2` setzen. |
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
> **Tipp:** Bevor Sie speichern, klicken Sie auf **„→ Verbindung jetzt
|
|
114
|
+
> testen“** – siehe Abschnitt [Verbindung testen](#verbindung-testen).
|
|
115
|
+
|
|
116
|
+
### Schritt 2 — *Zugriff & Berechtigungen*
|
|
115
117
|
|
|
116
118
|
| Feld | Default | Beschreibung |
|
|
117
119
|
|---|---|---|
|
|
@@ -129,7 +131,37 @@ Configure* ausfüllen. Die Konfiguration ist zweistufig:
|
|
|
129
131
|
- Nur die Features aktivieren, die wirklich gebraucht werden.
|
|
130
132
|
- Auf dem Samba-Server einen separaten Nutzer mit passenden Rechten anlegen
|
|
131
133
|
(read-only wenn nur gelesen werden soll).
|
|
132
|
-
- Zusätzlich mit *
|
|
134
|
+
- Zusätzlich mit *Basispfad* den Zugriff auf ein Unterverzeichnis begrenzen.
|
|
135
|
+
|
|
136
|
+
### Verbindung testen
|
|
137
|
+
|
|
138
|
+
Direkt im Konfigurations-Wizard (Schritt 1) gibt es den Button
|
|
139
|
+
**„→ Verbindung jetzt testen“**. Er sendet die aktuell im Formular
|
|
140
|
+
stehenden Werte an die interne Route `POST /sambatest` (nur für Admins),
|
|
141
|
+
baut eine SMB-Verbindung auf und listet den Basispfad bzw. die
|
|
142
|
+
Share-Wurzel auf. Es werden dabei **keine** Daten gespeichert oder
|
|
143
|
+
geschrieben.
|
|
144
|
+
|
|
145
|
+
- **Erfolg (grün):** Dauer der Verbindung, Anzahl gefundener Einträge
|
|
146
|
+
und die ersten 20 Namen (Datei/Ordner) werden angezeigt.
|
|
147
|
+
- **Fehler (rot):** Der SMB-/Netzwerk-Fehler wird im Klartext gezeigt,
|
|
148
|
+
zusammen mit einem konkreten Handlungshinweis auf Deutsch, z. B.:
|
|
149
|
+
- `ECONNREFUSED` → Samba läuft nicht oder Firewall/Docker blockt 445
|
|
150
|
+
- `ETIMEDOUT` → Host nicht erreichbar (Ping/`nc -vz`)
|
|
151
|
+
- `ENOTFOUND` → DNS-Auflösung fehlgeschlagen (IP verwenden)
|
|
152
|
+
- `LOGON_FAILURE` / `ACCESS_DENIED` → Benutzer, Passwort oder Domäne falsch
|
|
153
|
+
- `BAD_NETWORK_NAME` → Share-Name existiert so nicht auf dem Server
|
|
154
|
+
- `SMB1 / protocol` → Server bietet nur SMBv1 an (nicht unterstützt)
|
|
155
|
+
|
|
156
|
+
Die Route kann zusätzlich auch von Skripten aufgerufen werden:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
curl -X POST -H 'Content-Type: application/json' \
|
|
160
|
+
-H "X-CSRF-Token: $CSRF" \
|
|
161
|
+
--cookie "$COOKIES" \
|
|
162
|
+
-d '{"server":"192.168.1.20","share":"daten","username":"u","password":"p"}' \
|
|
163
|
+
https://saltcorn.example.com/sambatest
|
|
164
|
+
```
|
|
133
165
|
|
|
134
166
|
---
|
|
135
167
|
|
package/index.js
CHANGED
|
@@ -54,103 +54,262 @@ const DEFAULT_DENIED_EXT = [
|
|
|
54
54
|
// Plugin configuration
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
|
|
57
|
+
// HTML block injected as first "field" of step 1 — Saltcorn renders fields
|
|
58
|
+
// with type="String" and input_type="custom_html" as raw HTML. This gives
|
|
59
|
+
// admins a Test-Verbindung button next to the form itself.
|
|
60
|
+
const CONNECTION_TEST_HTML = `
|
|
61
|
+
<div class="card mb-3" style="border-left:4px solid #0d6efd">
|
|
62
|
+
<div class="card-body">
|
|
63
|
+
<h5 class="card-title" style="margin-top:0">Verbindung testen</h5>
|
|
64
|
+
<p class="card-text" style="margin-bottom:.6rem">
|
|
65
|
+
Prüfen Sie die eingegebenen Zugangsdaten <b>bevor</b> Sie speichern.
|
|
66
|
+
Der Test öffnet eine SMB-Verbindung zum Server, meldet den angegebenen
|
|
67
|
+
Benutzer an und listet das Wurzel-Verzeichnis des Shares (bzw. den
|
|
68
|
+
Basispfad, falls angegeben) auf. Es werden keine Daten geschrieben.
|
|
69
|
+
</p>
|
|
70
|
+
<button type="button" class="btn btn-primary" onclick="sambaTestConn(this)">
|
|
71
|
+
→ Verbindung jetzt testen
|
|
72
|
+
</button>
|
|
73
|
+
<div id="sambaTestOut" style="margin-top:.8rem"></div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<script>
|
|
77
|
+
window.sambaTestConn = async function(btn) {
|
|
78
|
+
var out = document.getElementById('sambaTestOut');
|
|
79
|
+
var form = btn.closest('form');
|
|
80
|
+
if (!form) { out.innerHTML = '<div class="alert alert-danger">Formular nicht gefunden.</div>'; return; }
|
|
81
|
+
function v(n){ var el = form.querySelector('[name="'+n+'"]'); return el ? el.value : ''; }
|
|
82
|
+
var payload = {
|
|
83
|
+
server: v('server'),
|
|
84
|
+
share: v('share'),
|
|
85
|
+
domain: v('domain'),
|
|
86
|
+
username: v('username'),
|
|
87
|
+
password: v('password'),
|
|
88
|
+
base_path: v('base_path'),
|
|
89
|
+
port: v('port')
|
|
90
|
+
};
|
|
91
|
+
if (!payload.server || !payload.share) {
|
|
92
|
+
out.innerHTML = '<div class="alert alert-warning">Bitte mindestens <b>Server</b> und <b>Share</b> ausfüllen.</div>';
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
var csrf = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
|
|
96
|
+
btn.disabled = true;
|
|
97
|
+
var oldTxt = btn.textContent;
|
|
98
|
+
btn.textContent = 'Teste …';
|
|
99
|
+
out.innerHTML = '<div class="text-muted">Verbindung wird aufgebaut … (bis zu 30 s)</div>';
|
|
100
|
+
try {
|
|
101
|
+
var r = await fetch('/sambatest', {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
credentials: 'same-origin',
|
|
104
|
+
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'CSRF-Token': csrf },
|
|
105
|
+
body: JSON.stringify(payload)
|
|
106
|
+
});
|
|
107
|
+
var data = await r.json();
|
|
108
|
+
if (data && data.ok) {
|
|
109
|
+
var rows = (data.entries||[]).map(function(e){
|
|
110
|
+
return '<li>'+ (e.isDirectory?'📁 ':'📄 ') + String(e.name).replace(/[<>&]/g,'?') +'</li>';
|
|
111
|
+
}).join('');
|
|
112
|
+
out.innerHTML =
|
|
113
|
+
'<div class="alert alert-success">' +
|
|
114
|
+
'<b>✓ Verbindung erfolgreich</b> (' + data.duration_ms + ' ms)<br>' +
|
|
115
|
+
'Server: <code>' + data.server + ':' + data.port + '</code>, Share: <code>' + data.share + '</code>, ' +
|
|
116
|
+
'Basispfad: <code>' + data.base_path + '</code>, Benutzer: <code>' + data.username + '</code><br>' +
|
|
117
|
+
'Einträge gefunden: <b>' + data.entry_count + '</b>' + (data.truncated ? ' (erste 20 unten)' : '') +
|
|
118
|
+
(rows ? '<ul style="margin:.5rem 0 0 1rem">' + rows + '</ul>' : '') +
|
|
119
|
+
'</div>';
|
|
120
|
+
} else {
|
|
121
|
+
var a = data && data.attempted || {};
|
|
122
|
+
out.innerHTML =
|
|
123
|
+
'<div class="alert alert-danger">' +
|
|
124
|
+
'<b>✗ Verbindung fehlgeschlagen</b><br>' +
|
|
125
|
+
'Fehler: <code>' + String(data && data.error || 'Unbekannt').replace(/[<>&]/g,'?') + '</code>' +
|
|
126
|
+
(data && data.code ? ' <span class="text-muted">(' + data.code + ')</span>' : '') + '<br>' +
|
|
127
|
+
(data && data.hint ? '<div style="margin-top:.4rem"><b>Hinweis:</b> ' + String(data.hint).replace(/[<>&]/g,'?') + '</div>' : '') +
|
|
128
|
+
'<details style="margin-top:.4rem"><summary>Versuchte Verbindungsdaten</summary>' +
|
|
129
|
+
'<table class="table table-sm" style="margin-top:.4rem">' +
|
|
130
|
+
'<tr><td>Server</td><td><code>' + (a.server||'') + ':' + (a.port||'') + '</code></td></tr>' +
|
|
131
|
+
'<tr><td>Share</td><td><code>' + (a.share||'') + '</code></td></tr>' +
|
|
132
|
+
'<tr><td>Basispfad</td><td><code>' + (a.base_path||'') + '</code></td></tr>' +
|
|
133
|
+
'<tr><td>Domäne</td><td><code>' + (a.domain||'') + '</code></td></tr>' +
|
|
134
|
+
'<tr><td>Benutzer</td><td><code>' + (a.username||'') + '</code></td></tr>' +
|
|
135
|
+
'</table></details>' +
|
|
136
|
+
'</div>';
|
|
137
|
+
}
|
|
138
|
+
} catch (e) {
|
|
139
|
+
out.innerHTML = '<div class="alert alert-danger">Fehler beim Aufruf von /sambatest: ' +
|
|
140
|
+
String(e && e.message || e).replace(/[<>&]/g,'?') + '</div>';
|
|
141
|
+
} finally {
|
|
142
|
+
btn.disabled = false;
|
|
143
|
+
btn.textContent = oldTxt;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
</script>
|
|
147
|
+
`;
|
|
148
|
+
|
|
57
149
|
const configuration_workflow = () =>
|
|
58
150
|
new Workflow({
|
|
59
151
|
steps: [
|
|
60
152
|
{
|
|
61
|
-
name: "Samba
|
|
153
|
+
name: "Samba-Server",
|
|
62
154
|
form: () =>
|
|
63
155
|
new Form({
|
|
64
156
|
fields: [
|
|
65
157
|
new Field({
|
|
66
158
|
name: "server",
|
|
67
|
-
label: "Server",
|
|
68
|
-
sublabel:
|
|
159
|
+
label: "Server (Hostname oder IP)",
|
|
160
|
+
sublabel:
|
|
161
|
+
"Adresse des Samba-Servers <b>ohne</b> smb://-Präfix und ohne Backslashes. " +
|
|
162
|
+
"Beispiele: <code>192.168.1.20</code>, <code>fileserver.local</code>, <code>nas01</code>. " +
|
|
163
|
+
"Läuft Saltcorn in Docker, ist <code>localhost</code> → der Container selbst; nutzen Sie die LAN-IP " +
|
|
164
|
+
"des Host-Systems (nicht 127.0.0.1) oder verbinden Sie den Container ins gleiche Bridge/Host-Netzwerk. " +
|
|
165
|
+
"Der Hostname muss <b>aus Sicht des Saltcorn-Prozesses</b> auflösbar sein.",
|
|
69
166
|
type: "String",
|
|
70
167
|
required: true,
|
|
71
168
|
}),
|
|
72
169
|
new Field({
|
|
73
170
|
name: "share",
|
|
74
|
-
label: "Share
|
|
75
|
-
sublabel:
|
|
171
|
+
label: "Freigabe / Share-Name",
|
|
172
|
+
sublabel:
|
|
173
|
+
"Name der SMB-Freigabe <b>ohne</b> Schrägstriche. Auf dem Server sichtbar als <code>[NAME]</code>-Abschnitt " +
|
|
174
|
+
"in <code>/etc/samba/smb.conf</code> oder unter Windows als Freigabe-Name. " +
|
|
175
|
+
"Beispiele: <code>daten</code>, <code>public</code>, <code>projekte</code>. " +
|
|
176
|
+
"Nicht die Ordner-Bezeichnung, sondern der Freigabe-Name eingeben.",
|
|
76
177
|
type: "String",
|
|
77
178
|
required: true,
|
|
78
179
|
}),
|
|
79
|
-
new Field({
|
|
80
|
-
|
|
81
|
-
|
|
180
|
+
new Field({
|
|
181
|
+
name: "domain",
|
|
182
|
+
label: "Domäne / Arbeitsgruppe",
|
|
183
|
+
sublabel:
|
|
184
|
+
"Windows-Domäne oder Arbeitsgruppe des Benutzers. " +
|
|
185
|
+
"Für klassische Samba-Server im Heim-/Firmennetz meist <code>WORKGROUP</code> (Standard). " +
|
|
186
|
+
"Für Active Directory: NetBIOS-Name der Domäne, z. B. <code>CONTOSO</code>. Nicht der FQDN.",
|
|
187
|
+
type: "String",
|
|
188
|
+
default: "WORKGROUP",
|
|
189
|
+
}),
|
|
190
|
+
new Field({
|
|
191
|
+
name: "username",
|
|
192
|
+
label: "Benutzername",
|
|
193
|
+
sublabel:
|
|
194
|
+
"Samba-/AD-Benutzer, unter dem gelesen und (je nach Rechten) geschrieben wird. " +
|
|
195
|
+
"<b>Nicht</b> im Format <code>DOMÄNE\\user</code> – die Domäne gehört oben ins eigene Feld. " +
|
|
196
|
+
"Leer lassen für anonymen Zugriff (nur möglich, wenn der Share <code>guest ok = yes</code> erlaubt).",
|
|
197
|
+
type: "String",
|
|
198
|
+
}),
|
|
199
|
+
new Field({
|
|
200
|
+
name: "password",
|
|
201
|
+
label: "Passwort",
|
|
202
|
+
sublabel:
|
|
203
|
+
"Passwort des Samba-Benutzers. Wird verschlüsselt in der Saltcorn-Datenbank abgelegt. " +
|
|
204
|
+
"<b>Wichtig:</b> Samba nutzt ein eigenes Passwort (<code>smbpasswd</code>), nicht zwingend das Linux-Login-Passwort. " +
|
|
205
|
+
"Moderne Samba-Server lehnen leere Passwörter ab.",
|
|
206
|
+
type: "String",
|
|
207
|
+
input_type: "password",
|
|
208
|
+
}),
|
|
82
209
|
new Field({
|
|
83
210
|
name: "base_path",
|
|
84
|
-
label: "
|
|
85
|
-
sublabel:
|
|
211
|
+
label: "Basispfad (optional)",
|
|
212
|
+
sublabel:
|
|
213
|
+
"Beschränkt sämtlichen Zugriff auf ein Unterverzeichnis der Freigabe. " +
|
|
214
|
+
"Relativ, mit Schrägstrichen, <b>ohne</b> führenden Slash. " +
|
|
215
|
+
"Beispiel: <code>projekte/2026</code> → der File-Manager sieht nur diesen Unterordner und alles darin. " +
|
|
216
|
+
"Leer lassen für die komplette Share-Wurzel. <code>..</code> und absolute Pfade werden abgelehnt.",
|
|
86
217
|
type: "String",
|
|
87
218
|
}),
|
|
88
|
-
new Field({
|
|
219
|
+
new Field({
|
|
220
|
+
name: "port",
|
|
221
|
+
label: "TCP-Port",
|
|
222
|
+
sublabel:
|
|
223
|
+
"SMB-Port des Servers. Standard: <code>445</code> (SMB2/3 über TCP). " +
|
|
224
|
+
"Nur ändern, wenn Ihr Server bewusst auf einem anderen Port läuft. " +
|
|
225
|
+
"<b>SMBv1 (Port 139/NetBIOS) wird nicht unterstützt</b> – aktivieren Sie SMB2+ auf dem Server " +
|
|
226
|
+
"(<code>min protocol = SMB2</code> in smb.conf).",
|
|
227
|
+
type: "Integer",
|
|
228
|
+
default: 445,
|
|
229
|
+
}),
|
|
230
|
+
new Field({
|
|
231
|
+
name: "_test_html",
|
|
232
|
+
label: " ",
|
|
233
|
+
input_type: "custom_html",
|
|
234
|
+
attributes: { html: CONNECTION_TEST_HTML },
|
|
235
|
+
}),
|
|
89
236
|
],
|
|
90
237
|
}),
|
|
91
238
|
},
|
|
92
239
|
{
|
|
93
|
-
name: "
|
|
240
|
+
name: "Zugriff & Berechtigungen",
|
|
94
241
|
form: () =>
|
|
95
242
|
new Form({
|
|
96
243
|
fields: [
|
|
97
244
|
new Field({
|
|
98
245
|
name: "min_role_read",
|
|
99
|
-
label: "
|
|
100
|
-
sublabel:
|
|
246
|
+
label: "Mindestrolle für Lesen",
|
|
247
|
+
sublabel:
|
|
248
|
+
"Saltcorn-Rolle, die mindestens nötig ist, um Dateien und Verzeichnisse anzusehen. " +
|
|
249
|
+
"Werte: <code>1</code>=Admin, <code>40</code>=Staff, <code>80</code>=User, <code>100</code>=Public. " +
|
|
250
|
+
"Kleinere Zahl = höhere Rolle. Standard: <code>80</code> (alle angemeldeten Benutzer).",
|
|
101
251
|
type: "Integer",
|
|
102
252
|
default: 80,
|
|
103
253
|
}),
|
|
104
254
|
new Field({
|
|
105
255
|
name: "min_role_write",
|
|
106
|
-
label: "
|
|
256
|
+
label: "Mindestrolle für Upload / Löschen / Umbenennen",
|
|
107
257
|
sublabel:
|
|
108
|
-
"
|
|
258
|
+
"Setzen Sie auf <code>100</code>, um <b>alle</b> Schreibaktionen komplett zu deaktivieren – auch wenn die " +
|
|
259
|
+
"Checkboxen unten aktiv sind. Standard: <code>40</code> (nur Staff und Admin).",
|
|
109
260
|
type: "Integer",
|
|
110
261
|
default: 40,
|
|
111
262
|
}),
|
|
112
263
|
new Field({
|
|
113
264
|
name: "allow_upload",
|
|
114
|
-
label: "
|
|
265
|
+
label: "Datei-Upload erlauben",
|
|
266
|
+
sublabel: "Zeigt den Upload-Button und aktiviert POST /sambaupload.",
|
|
115
267
|
type: "Bool",
|
|
116
268
|
default: false,
|
|
117
269
|
}),
|
|
118
270
|
new Field({
|
|
119
271
|
name: "allow_delete",
|
|
120
|
-
label: "
|
|
272
|
+
label: "Löschen erlauben",
|
|
273
|
+
sublabel: "Erlaubt das Löschen von Dateien und leeren Verzeichnissen.",
|
|
121
274
|
type: "Bool",
|
|
122
275
|
default: false,
|
|
123
276
|
}),
|
|
124
277
|
new Field({
|
|
125
278
|
name: "allow_rename",
|
|
126
|
-
label: "
|
|
279
|
+
label: "Umbenennen / Verschieben erlauben",
|
|
280
|
+
sublabel: "Erlaubt Umbenennen und Verschieben innerhalb des Basispfads.",
|
|
127
281
|
type: "Bool",
|
|
128
282
|
default: false,
|
|
129
283
|
}),
|
|
130
284
|
new Field({
|
|
131
285
|
name: "allow_mkdir",
|
|
132
|
-
label: "
|
|
286
|
+
label: "Neue Verzeichnisse anlegen erlauben",
|
|
287
|
+
sublabel: "Zeigt den „Neuer Ordner“-Button in der File-Manager-Ansicht.",
|
|
133
288
|
type: "Bool",
|
|
134
289
|
default: false,
|
|
135
290
|
}),
|
|
136
291
|
new Field({
|
|
137
292
|
name: "max_upload_mb",
|
|
138
|
-
label: "Max
|
|
293
|
+
label: "Max. Upload-Größe pro Datei (MiB)",
|
|
294
|
+
sublabel: "Serverseitige Obergrenze pro Datei. Standard: 50 MiB.",
|
|
139
295
|
type: "Integer",
|
|
140
296
|
default: 50,
|
|
141
297
|
}),
|
|
142
298
|
new Field({
|
|
143
299
|
name: "denied_extensions",
|
|
144
|
-
label: "
|
|
300
|
+
label: "Gesperrte Datei-Endungen (komma-getrennt, ohne Punkte)",
|
|
145
301
|
sublabel:
|
|
146
|
-
"
|
|
302
|
+
"Standard: <code>exe,bat,cmd,com,msi,scr,vbs,js,jse,wsf,wsh,ps1,ps1xml,psm1,sh,bash,zsh</code>. " +
|
|
303
|
+
"Leer lassen für die Standard-Liste.",
|
|
147
304
|
type: "String",
|
|
148
305
|
}),
|
|
149
306
|
new Field({
|
|
150
307
|
name: "public_smb_host",
|
|
151
|
-
label: "
|
|
308
|
+
label: "Für Clients sichtbarer SMB-Hostname (optional)",
|
|
152
309
|
sublabel:
|
|
153
|
-
"
|
|
310
|
+
"Wird in erzeugten <code>smb://</code>-Links verwendet. Standard: der Wert aus dem Feld <b>Server</b>. " +
|
|
311
|
+
"Nützlich, wenn Saltcorn in Docker läuft (Server = interne IP), Clients aber den LAN-Namen sehen sollen " +
|
|
312
|
+
"(z. B. <code>fileserver.local</code>).",
|
|
154
313
|
type: "String",
|
|
155
314
|
}),
|
|
156
315
|
],
|
|
@@ -369,6 +528,107 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
|
|
|
369
528
|
},
|
|
370
529
|
},
|
|
371
530
|
|
|
531
|
+
// ---- Connection test (admin only) --------------------------------------
|
|
532
|
+
//
|
|
533
|
+
// POST /sambatest
|
|
534
|
+
// Body (JSON): { server, share, domain, username, password, base_path, port }
|
|
535
|
+
// Returns: { ok, server, share, base_path, entries?: [{name,isDirectory}],
|
|
536
|
+
// error?, code?, hint? }
|
|
537
|
+
//
|
|
538
|
+
// The route accepts values straight from the config form so admins can
|
|
539
|
+
// verify the connection BEFORE saving. It never touches the persisted
|
|
540
|
+
// configuration and is restricted to role_id === 1 (admin).
|
|
541
|
+
{
|
|
542
|
+
url: "/sambatest",
|
|
543
|
+
method: "post",
|
|
544
|
+
callback: async ({ req, res }) => {
|
|
545
|
+
if (roleOf(req) !== 1) {
|
|
546
|
+
return jsonError(res, 403, "Only admins can test the connection.");
|
|
547
|
+
}
|
|
548
|
+
const body = (req.body && typeof req.body === "object") ? req.body : {};
|
|
549
|
+
const testCfg = {
|
|
550
|
+
server: String(body.server || "").trim(),
|
|
551
|
+
share: String(body.share || "").trim(),
|
|
552
|
+
domain: String(body.domain || "").trim() || "WORKGROUP",
|
|
553
|
+
username: String(body.username || "").trim(),
|
|
554
|
+
password: String(body.password || ""),
|
|
555
|
+
base_path: String(body.base_path || "").trim(),
|
|
556
|
+
port: Number(body.port) || 445,
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
if (!testCfg.server) return jsonError(res, 400, "Please enter a Server (hostname or IP).");
|
|
560
|
+
if (!testCfg.share) return jsonError(res, 400, "Please enter a Share name.");
|
|
561
|
+
|
|
562
|
+
const started = Date.now();
|
|
563
|
+
try {
|
|
564
|
+
const listing = await withClient(testCfg, async (client) => {
|
|
565
|
+
// If base_path is set, list it — that also verifies traversal.
|
|
566
|
+
const rel = testCfg.base_path ? sanitizeRelativePath(testCfg.base_path) : "";
|
|
567
|
+
return await client.readdir(rel);
|
|
568
|
+
});
|
|
569
|
+
const took = Date.now() - started;
|
|
570
|
+
return res.json({
|
|
571
|
+
ok: true,
|
|
572
|
+
server: testCfg.server,
|
|
573
|
+
share: testCfg.share,
|
|
574
|
+
base_path: testCfg.base_path || "(share root)",
|
|
575
|
+
port: testCfg.port,
|
|
576
|
+
domain: testCfg.domain,
|
|
577
|
+
username: testCfg.username || "(anonymous)",
|
|
578
|
+
duration_ms: took,
|
|
579
|
+
entry_count: Array.isArray(listing) ? listing.length : 0,
|
|
580
|
+
entries: (Array.isArray(listing) ? listing : []).slice(0, 20).map((e) => ({
|
|
581
|
+
name: e && (e.name || e),
|
|
582
|
+
isDirectory: !!(e && (e.isDirectory === true || (e.stats && e.stats.isDirectory && e.stats.isDirectory()))),
|
|
583
|
+
})),
|
|
584
|
+
truncated: Array.isArray(listing) && listing.length > 20,
|
|
585
|
+
});
|
|
586
|
+
} catch (err) {
|
|
587
|
+
// Turn opaque SMB / socket errors into actionable hints.
|
|
588
|
+
const msg = String((err && err.message) || err || "Unknown error");
|
|
589
|
+
const code = (err && (err.code || err.errno)) || null;
|
|
590
|
+
let hint = null;
|
|
591
|
+
const m = msg.toLowerCase();
|
|
592
|
+
if (code === "ECONNREFUSED" || m.includes("econnrefused"))
|
|
593
|
+
hint = "The server refused the connection on port " + testCfg.port +
|
|
594
|
+
". Check that Samba is running and that a firewall (or Docker) does not block TCP/445.";
|
|
595
|
+
else if (code === "ETIMEDOUT" || m.includes("etimedout") || m.includes("timed out"))
|
|
596
|
+
hint = "Timeout — the host did not respond. Verify Server address and that it is reachable from the Saltcorn host (try: ping / nc -vz " + testCfg.server + " " + testCfg.port + ").";
|
|
597
|
+
else if (code === "ENOTFOUND" || code === "EAI_AGAIN" || m.includes("getaddrinfo"))
|
|
598
|
+
hint = "DNS lookup failed for '" + testCfg.server + "'. Use an IP address or make sure the hostname resolves from inside the Saltcorn container.";
|
|
599
|
+
else if (code === "EHOSTUNREACH" || m.includes("ehostunreach"))
|
|
600
|
+
hint = "No route to host. Check network / VPN / bridged Docker network.";
|
|
601
|
+
else if (m.includes("logon_failure") || m.includes("nt_status_logon_failure") || m.includes("access_denied") || m.includes("nt_status_access_denied"))
|
|
602
|
+
hint = "Login rejected. Check Username, Password and Domain / Workgroup. On modern Samba servers, empty passwords are usually disabled.";
|
|
603
|
+
else if (m.includes("bad_network_name") || m.includes("nt_status_bad_network_name"))
|
|
604
|
+
hint = "The share '" + testCfg.share + "' does not exist on " + testCfg.server + ". Check spelling and the [share] section in smb.conf.";
|
|
605
|
+
else if (m.includes("account_disabled") || m.includes("account_locked"))
|
|
606
|
+
hint = "The user account is disabled or locked on the Samba server.";
|
|
607
|
+
else if (m.includes("password_expired") || m.includes("password_must_change"))
|
|
608
|
+
hint = "The password must be changed before this account can be used.";
|
|
609
|
+
else if (m.includes("smb1") || m.includes("protocol"))
|
|
610
|
+
hint = "The server may be offering only SMBv1 which this plugin does not support. Enable SMB2 / SMB3 on the Samba server (min protocol = SMB2 in smb.conf).";
|
|
611
|
+
else if (m.includes("traversal") || m.includes("path"))
|
|
612
|
+
hint = "The Base path could not be validated. It must be a relative sub-directory (e.g. 'projects/2026'), never start with / or \\, and must not contain '..'.";
|
|
613
|
+
|
|
614
|
+
return res.status(200).json({
|
|
615
|
+
ok: false,
|
|
616
|
+
error: msg,
|
|
617
|
+
code,
|
|
618
|
+
hint,
|
|
619
|
+
attempted: {
|
|
620
|
+
server: testCfg.server,
|
|
621
|
+
share: testCfg.share,
|
|
622
|
+
port: testCfg.port,
|
|
623
|
+
domain: testCfg.domain,
|
|
624
|
+
username: testCfg.username || "(anonymous)",
|
|
625
|
+
base_path: testCfg.base_path || "(share root)",
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
|
|
372
632
|
// ---- Write routes (v0.3.0) --------------------------------------------
|
|
373
633
|
|
|
374
634
|
{
|
|
@@ -608,27 +868,48 @@ function wrapView(v) {
|
|
|
608
868
|
// ---------------------------------------------------------------------------
|
|
609
869
|
|
|
610
870
|
// ---------------------------------------------------------------------------
|
|
611
|
-
// IMPORTANT: how Saltcorn
|
|
871
|
+
// IMPORTANT: how Saltcorn reads the manifest
|
|
612
872
|
// ---------------------------------------------------------------------------
|
|
613
|
-
//
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
//
|
|
873
|
+
// Two access patterns coexist in Saltcorn's plugin loader:
|
|
874
|
+
//
|
|
875
|
+
// 1. `withCfg(key)` in `saltcorn-data/db/state.ts` — when a plugin exports
|
|
876
|
+
// `configuration_workflow`, this helper calls `plugin[key](cfg)` and then
|
|
877
|
+
// iterates the return value. Any key it consults MUST therefore be a
|
|
878
|
+
// factory function that returns the real value.
|
|
879
|
+
// Keys consumed via `withCfg`: types, viewtemplates, functions,
|
|
880
|
+
// modelpatterns, fileviews, actions, eventTypes, fonts, icons,
|
|
881
|
+
// table_providers, authentication, exchange, copilot_skills,
|
|
882
|
+
// external_tables, headers, routes, capacitor_plugins.
|
|
883
|
+
//
|
|
884
|
+
// 2. Direct property access in `saltcorn-data/models/plugin.ts` — the
|
|
885
|
+
// loader reads a handful of keys straight off the module, WITHOUT going
|
|
886
|
+
// through `withCfg`. Those must be the raw value (not a function).
|
|
887
|
+
// Direct-access keys: dependencies (for..of loop), onLoad (called as fn),
|
|
888
|
+
// authentication (truthy check + later withCfg), user_config_form,
|
|
889
|
+
// plugin_name (string), layout / types / functions / viewtemplates
|
|
890
|
+
// (truthy checks in the plugin-store info card).
|
|
891
|
+
//
|
|
892
|
+
// The intersection is where confusion lives:
|
|
893
|
+
// - `dependencies` is read *directly* as an iterable and MUST be an array.
|
|
894
|
+
// - `viewtemplates`, `routes`, `headers` etc. are read via `withCfg` and
|
|
895
|
+
// MUST be functions when `configuration_workflow` is present.
|
|
896
|
+
//
|
|
897
|
+
// See:
|
|
898
|
+
// https://github.com/saltcorn/saltcorn/blob/master/packages/saltcorn-data/db/state.ts
|
|
899
|
+
// https://github.com/saltcorn/saltcorn/blob/master/packages/saltcorn-data/models/plugin.ts
|
|
621
900
|
// ---------------------------------------------------------------------------
|
|
622
901
|
|
|
623
902
|
module.exports = {
|
|
624
903
|
sc_plugin_api_version: 1,
|
|
625
904
|
configuration_workflow,
|
|
905
|
+
// The four keys below are consumed via `withCfg` → must be factory fns.
|
|
626
906
|
viewtemplates: () => [wrapView(fileManagerView), wrapView(treeView)],
|
|
627
907
|
routes: () => routes,
|
|
628
908
|
headers: () => [
|
|
629
909
|
{ css: `/plugins/public/${PLUGIN_NAME}/samba.css` },
|
|
630
910
|
],
|
|
631
|
-
dependencies
|
|
911
|
+
// `dependencies` is iterated directly (for..of) → must be a raw array.
|
|
912
|
+
dependencies: [],
|
|
632
913
|
};
|
|
633
914
|
|
|
634
915
|
// Note: the `samba_pdf` fieldview shipped in v0.1–0.3.1 has been removed from
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saltcorn-samba",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share. File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|