saltcorn-samba 0.3.0
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 +82 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/filemanager-view.js +249 -0
- package/index.js +622 -0
- package/package.json +56 -0
- package/pdf-view.js +163 -0
- package/public/samba-filemanager.js +736 -0
- package/public/samba-tree.js +292 -0
- package/public/samba.css +69 -0
- package/smb-client.js +279 -0
- package/tree-view.js +212 -0
package/tree-view.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View template: `samba_tree`
|
|
3
|
+
*
|
|
4
|
+
* Renders a lazy-loading directory tree of a Samba share. Each directory can
|
|
5
|
+
* be expanded via AJAX; clicking a file opens the PDF viewer route (for PDFs
|
|
6
|
+
* and images) or triggers an "open in external app" link (smb://) for
|
|
7
|
+
* everything else.
|
|
8
|
+
*
|
|
9
|
+
* The view has two operating modes:
|
|
10
|
+
* - "static" – always show the same base_path from the config
|
|
11
|
+
* - "from_field" – append the value of a row field to base_path
|
|
12
|
+
* (only meaningful when embedded in a Show view)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { div, a, i, span, script, button } = require("@saltcorn/markup/tags");
|
|
16
|
+
const Workflow = require("@saltcorn/data/models/workflow");
|
|
17
|
+
const Form = require("@saltcorn/data/models/form");
|
|
18
|
+
const Field = require("@saltcorn/data/models/field");
|
|
19
|
+
const Table = require("@saltcorn/data/models/table");
|
|
20
|
+
|
|
21
|
+
const { withClient, toSmbUrl, sanitizeRelativePath } = require("./smb-client");
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Configuration workflow
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const configuration_workflow = () =>
|
|
28
|
+
new Workflow({
|
|
29
|
+
steps: [
|
|
30
|
+
{
|
|
31
|
+
name: "Tree options",
|
|
32
|
+
form: async (context) => {
|
|
33
|
+
const table = await Table.findOne({ id: context.table_id });
|
|
34
|
+
const fields = table ? await table.getFields() : [];
|
|
35
|
+
const stringFieldOptions = fields
|
|
36
|
+
.filter(
|
|
37
|
+
(f) =>
|
|
38
|
+
f.type &&
|
|
39
|
+
(f.type.name === "String" || f.type === "String" || f.type.name === "Text")
|
|
40
|
+
)
|
|
41
|
+
.map((f) => f.name);
|
|
42
|
+
return new Form({
|
|
43
|
+
fields: [
|
|
44
|
+
new Field({
|
|
45
|
+
name: "mode",
|
|
46
|
+
label: "Path mode",
|
|
47
|
+
type: "String",
|
|
48
|
+
required: true,
|
|
49
|
+
attributes: {
|
|
50
|
+
options: ["static", "from_field"],
|
|
51
|
+
},
|
|
52
|
+
sublabel:
|
|
53
|
+
"static = always show the plugin base_path; from_field = append a row field value",
|
|
54
|
+
}),
|
|
55
|
+
new Field({
|
|
56
|
+
name: "path_field",
|
|
57
|
+
label: "Row field with sub-path",
|
|
58
|
+
type: "String",
|
|
59
|
+
attributes: { options: stringFieldOptions },
|
|
60
|
+
showIf: { mode: "from_field" },
|
|
61
|
+
}),
|
|
62
|
+
new Field({
|
|
63
|
+
name: "extra_subpath",
|
|
64
|
+
label: "Extra sub-path (appended)",
|
|
65
|
+
type: "String",
|
|
66
|
+
sublabel:
|
|
67
|
+
"Optional. Static suffix appended after the field value. Example: 'invoices'",
|
|
68
|
+
}),
|
|
69
|
+
new Field({
|
|
70
|
+
name: "show_hidden",
|
|
71
|
+
label: "Show hidden files",
|
|
72
|
+
type: "Bool",
|
|
73
|
+
}),
|
|
74
|
+
new Field({
|
|
75
|
+
name: "pdf_inline",
|
|
76
|
+
label: "Open PDFs inline",
|
|
77
|
+
type: "Bool",
|
|
78
|
+
default: true,
|
|
79
|
+
}),
|
|
80
|
+
new Field({
|
|
81
|
+
name: "expose_smb_link",
|
|
82
|
+
label: "Show 'Open in file manager' link (smb://)",
|
|
83
|
+
type: "Bool",
|
|
84
|
+
default: true,
|
|
85
|
+
sublabel:
|
|
86
|
+
"Adds a button that opens the file/folder in Nemo, Nautilus, Dolphin, Explorer etc.",
|
|
87
|
+
}),
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Helpers
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
async function get_state_fields(table_id, viewname, { mode, path_field }) {
|
|
100
|
+
if (mode === "from_field" && !path_field) return [];
|
|
101
|
+
// The tree view can be embedded in a Show view and does not require any
|
|
102
|
+
// state fields on its own – we always operate on the current row.
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Given the view configuration + a Saltcorn row, compute the effective
|
|
108
|
+
* relative path (below base_path) that the tree should start from.
|
|
109
|
+
*/
|
|
110
|
+
function computeStartPath(configuration, row) {
|
|
111
|
+
const parts = [];
|
|
112
|
+
if (configuration.mode === "from_field" && configuration.path_field && row) {
|
|
113
|
+
const v = row[configuration.path_field];
|
|
114
|
+
if (v) parts.push(String(v));
|
|
115
|
+
}
|
|
116
|
+
if (configuration.extra_subpath) parts.push(String(configuration.extra_subpath));
|
|
117
|
+
const joined = parts.join("/");
|
|
118
|
+
// sanitizeRelativePath throws on traversal – we let the caller handle it.
|
|
119
|
+
return sanitizeRelativePath(joined);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Render – client-side JS is served from /public/samba-tree.js
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
function renderShell(viewname, startPath, configuration) {
|
|
127
|
+
const treeId = `samba-tree-${Math.random().toString(36).slice(2, 10)}`;
|
|
128
|
+
const pluginVersion = configuration.__pluginVersion || "0.2.0";
|
|
129
|
+
const opts = {
|
|
130
|
+
viewname,
|
|
131
|
+
startPath,
|
|
132
|
+
pdfInline: !!configuration.pdf_inline,
|
|
133
|
+
exposeSmbLink: configuration.expose_smb_link !== false,
|
|
134
|
+
showHidden: !!configuration.show_hidden,
|
|
135
|
+
};
|
|
136
|
+
return (
|
|
137
|
+
div(
|
|
138
|
+
{ class: "samba-tree-container card p-2" },
|
|
139
|
+
div(
|
|
140
|
+
{ class: "samba-tree-toolbar d-flex align-items-center mb-2" },
|
|
141
|
+
span({ class: "text-muted small me-2" }, "Path:"),
|
|
142
|
+
span({ class: "samba-tree-breadcrumb small fw-bold" }, startPath || "/")
|
|
143
|
+
),
|
|
144
|
+
div({ id: treeId, class: "samba-tree", "data-opts": JSON.stringify(opts) }),
|
|
145
|
+
div({ id: treeId + "-viewer", class: "samba-viewer mt-3" })
|
|
146
|
+
) +
|
|
147
|
+
script(
|
|
148
|
+
`(function(){
|
|
149
|
+
function boot(){window.saltcornSambaMount && window.saltcornSambaMount(${JSON.stringify(
|
|
150
|
+
treeId
|
|
151
|
+
)});}
|
|
152
|
+
if(window.saltcornSambaMount){boot();return;}
|
|
153
|
+
var s=document.createElement('script');
|
|
154
|
+
s.src='/plugins/public/saltcorn-samba@${pluginVersion}/samba-tree.js';
|
|
155
|
+
s.onload=boot;
|
|
156
|
+
document.head.appendChild(s);
|
|
157
|
+
})();`
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// View: run (single row) and runMany
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
async function run(table_id, viewname, configuration, state, extra) {
|
|
167
|
+
// "state" typically contains the primary key of the row when the view is
|
|
168
|
+
// opened directly. If we are embedded in a Show view, `extra.row` is set.
|
|
169
|
+
let row = extra && extra.row;
|
|
170
|
+
if (!row && configuration.mode === "from_field") {
|
|
171
|
+
const table = await Table.findOne({ id: table_id });
|
|
172
|
+
if (table && state && Object.keys(state).length) {
|
|
173
|
+
row = await table.getRow(state);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
let startPath = "";
|
|
177
|
+
try {
|
|
178
|
+
startPath = computeStartPath(configuration, row || {});
|
|
179
|
+
} catch (e) {
|
|
180
|
+
return div({ class: "alert alert-danger" }, "Samba tree: " + e.message);
|
|
181
|
+
}
|
|
182
|
+
return renderShell(viewname, startPath, configuration);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runMany(table_id, viewname, configuration, state, extra) {
|
|
186
|
+
const rows = extra && extra.rows ? extra.rows : [];
|
|
187
|
+
return rows.map((row) => ({
|
|
188
|
+
html: renderShell(
|
|
189
|
+
viewname,
|
|
190
|
+
(() => {
|
|
191
|
+
try {
|
|
192
|
+
return computeStartPath(configuration, row);
|
|
193
|
+
} catch {
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
})(),
|
|
197
|
+
configuration
|
|
198
|
+
),
|
|
199
|
+
row,
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
name: "SambaTree",
|
|
205
|
+
display_state_form: false,
|
|
206
|
+
get_state_fields,
|
|
207
|
+
configuration_workflow,
|
|
208
|
+
run,
|
|
209
|
+
runMany,
|
|
210
|
+
description:
|
|
211
|
+
"Browse a Samba/CIFS share as a lazy-loading directory tree. Files open inline (PDF) or in the OS file manager.",
|
|
212
|
+
};
|