@jfvilas/plugin-kwirth-fileman 0.13.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/README.md +206 -0
- package/dist/api/KwirthFilemanClient.esm.js +30 -0
- package/dist/api/KwirthFilemanClient.esm.js.map +1 -0
- package/dist/api/types.esm.js +8 -0
- package/dist/api/types.esm.js.map +1 -0
- package/dist/assets/kwirthfileman-logo.svg +8 -0
- package/dist/components/EntityKwirthFilemanContent.esm.js +585 -0
- package/dist/components/EntityKwirthFilemanContent.esm.js.map +1 -0
- package/dist/components/custom-fm.module.css.esm.js +8 -0
- package/dist/components/custom-fm.module.css.esm.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.esm.js +4 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/node_modules/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/plugin.esm.js +33 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/dist/version.esm.js +4 -0
- package/dist/version.esm.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
3
|
+
import { Progress, WarningPanel } from '@backstage/core-components';
|
|
4
|
+
import { useApi, alertApiRef } from '@backstage/core-plugin-api';
|
|
5
|
+
import { isKwirthAvailable, ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR, getPodList, getContainerList } from '@jfvilas/plugin-kwirth-common';
|
|
6
|
+
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
|
|
7
|
+
import { kwirthFilemanApiRef } from '../api/types.esm.js';
|
|
8
|
+
import { SignalMessageLevelEnum, accessKeySerialize, InstanceMessageTypeEnum, InstanceMessageActionEnum, InstanceMessageFlowEnum, InstanceConfigViewEnum, InstanceConfigObjectEnum, SignalMessageEventEnum } from '@jfvilas/kwirth-common';
|
|
9
|
+
import { ComponentNotFound, ErrorType, ClusterList, KwirthNews, StatusLog } from '@jfvilas/plugin-kwirth-frontend';
|
|
10
|
+
import { Box, Grid, Card, CardHeader, IconButton, Typography } from '@material-ui/core';
|
|
11
|
+
import PlayIcon from '@material-ui/icons/PlayArrow';
|
|
12
|
+
import PauseIcon from '@material-ui/icons/Pause';
|
|
13
|
+
import StopIcon from '@material-ui/icons/Stop';
|
|
14
|
+
import InfoIcon from '@material-ui/icons/Info';
|
|
15
|
+
import WarningIcon from '@material-ui/icons/Warning';
|
|
16
|
+
import ErrorIcon from '@material-ui/icons/Error';
|
|
17
|
+
import KwirthFilemanLogo from '../plugins/plugin-kwirth-fileman/src/assets/kwirthfileman-logo.svg';
|
|
18
|
+
import { v4 } from 'uuid';
|
|
19
|
+
import { FileManager } from '@jfvilas/react-file-manager';
|
|
20
|
+
import '@jfvilas/react-file-manager/dist/style.css';
|
|
21
|
+
import styles from './custom-fm.module.css.esm.js';
|
|
22
|
+
import { VERSION } from '../version.esm.js';
|
|
23
|
+
|
|
24
|
+
const EntityKwirthFilemanContent = (props) => {
|
|
25
|
+
const { entity } = useEntity();
|
|
26
|
+
const kwirthFilemanApi = useApi(kwirthFilemanApiRef);
|
|
27
|
+
const alertApi = useApi(alertApiRef);
|
|
28
|
+
const [validClusters, setResources] = useState([]);
|
|
29
|
+
const [selectedClusterName, setSelectedClusterName] = useState("");
|
|
30
|
+
const [selectedNamespaces, setSelectedNamespaces] = useState([]);
|
|
31
|
+
const [selectedPodNames, setSelectedPodNames] = useState([]);
|
|
32
|
+
const [selectedContainerNames, setSelectedContainerNames] = useState([]);
|
|
33
|
+
const [started, setStarted] = useState(false);
|
|
34
|
+
const [stopped, setStopped] = useState(true);
|
|
35
|
+
const paused = useRef(false);
|
|
36
|
+
const [statusMessages, setStatusMessages] = useState([]);
|
|
37
|
+
const [webSocket, setWebSocket] = useState();
|
|
38
|
+
const [showStatusDialog, setShowStatusDialog] = useState(false);
|
|
39
|
+
const [statusLevel, setStatusLevel] = useState(SignalMessageLevelEnum.INFO);
|
|
40
|
+
const [backendVersion, setBackendVersion] = useState("");
|
|
41
|
+
const [backendInfo, setBackendInfo] = useState();
|
|
42
|
+
const instance = useRef();
|
|
43
|
+
const [stateFiles, setStateFiles] = useState([]);
|
|
44
|
+
const files = useRef([]);
|
|
45
|
+
const [currentPath, setCurrentPath] = useState("");
|
|
46
|
+
const { loading, error } = useAsync(async () => {
|
|
47
|
+
if (backendVersion === "") setBackendVersion(await kwirthFilemanApi.getVersion());
|
|
48
|
+
if (!backendInfo) setBackendInfo(await kwirthFilemanApi.getInfo());
|
|
49
|
+
let reqScopes = ["fileman$read"];
|
|
50
|
+
let data = await kwirthFilemanApi.requestAccess(entity, "fileman", reqScopes);
|
|
51
|
+
setResources(data);
|
|
52
|
+
});
|
|
53
|
+
const filemanBoxRef = useRef(null);
|
|
54
|
+
const [filemanBoxTop, setFilemanBoxTop] = useState(0);
|
|
55
|
+
let permissions = {
|
|
56
|
+
create: true,
|
|
57
|
+
delete: true,
|
|
58
|
+
download: true,
|
|
59
|
+
copy: true,
|
|
60
|
+
move: true,
|
|
61
|
+
rename: true,
|
|
62
|
+
upload: true
|
|
63
|
+
};
|
|
64
|
+
let fileUploadConfig = {
|
|
65
|
+
url: ""
|
|
66
|
+
};
|
|
67
|
+
let cluster = validClusters.find((cluster2) => cluster2.name === selectedClusterName);
|
|
68
|
+
if (cluster) {
|
|
69
|
+
let accessKey = cluster.accessKeys.get("fileman$read");
|
|
70
|
+
if (accessKey) {
|
|
71
|
+
fileUploadConfig = {
|
|
72
|
+
url: `${cluster.url}/channel/fileman/upload?key=${instance.current}`,
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: {
|
|
75
|
+
"Authorization": "Bearer " + accessKeySerialize(accessKey)
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let level = currentPath.split("/").length - 1;
|
|
81
|
+
if (level < 3) {
|
|
82
|
+
permissions = {
|
|
83
|
+
create: false,
|
|
84
|
+
delete: false,
|
|
85
|
+
download: false,
|
|
86
|
+
copy: false,
|
|
87
|
+
move: false,
|
|
88
|
+
rename: false,
|
|
89
|
+
upload: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (filemanBoxRef.current) setFilemanBoxTop(filemanBoxRef.current.getBoundingClientRect().top);
|
|
94
|
+
});
|
|
95
|
+
const clickStart = () => {
|
|
96
|
+
if (!paused.current) {
|
|
97
|
+
setStarted(true);
|
|
98
|
+
paused.current = false;
|
|
99
|
+
setStopped(false);
|
|
100
|
+
startFilemanViewer();
|
|
101
|
+
} else {
|
|
102
|
+
paused.current = false;
|
|
103
|
+
setStarted(true);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const onClickPause = () => {
|
|
107
|
+
setStarted(false);
|
|
108
|
+
paused.current = true;
|
|
109
|
+
};
|
|
110
|
+
const onClickStop = () => {
|
|
111
|
+
setStarted(false);
|
|
112
|
+
setStopped(true);
|
|
113
|
+
paused.current = false;
|
|
114
|
+
stopFilemanViewer();
|
|
115
|
+
};
|
|
116
|
+
const onSelectCluster = (clusterName) => {
|
|
117
|
+
if (started) onClickStop();
|
|
118
|
+
if (clusterName) {
|
|
119
|
+
setSelectedClusterName(clusterName);
|
|
120
|
+
setSelectedPodNames([]);
|
|
121
|
+
setSelectedContainerNames([]);
|
|
122
|
+
setStatusMessages([]);
|
|
123
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === clusterName);
|
|
124
|
+
if (cluster2 && cluster2.pods) {
|
|
125
|
+
let validNamespaces = Array.from(new Set(cluster2.pods.map((pod) => pod.namespace)));
|
|
126
|
+
if (validNamespaces.length === 1) {
|
|
127
|
+
setSelectedNamespaces(validNamespaces);
|
|
128
|
+
let podList = getPodList(cluster2.pods, validNamespaces);
|
|
129
|
+
setSelectedPodNames(podList.map((pod) => pod.name));
|
|
130
|
+
setSelectedContainerNames(getContainerList(cluster2.pods, validNamespaces, podList.map((pod) => pod.name), props.excludeContainers || []));
|
|
131
|
+
} else {
|
|
132
|
+
setSelectedNamespaces([]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
let FilemanCommandEnum;
|
|
138
|
+
((FilemanCommandEnum2) => {
|
|
139
|
+
FilemanCommandEnum2["HOME"] = "home";
|
|
140
|
+
FilemanCommandEnum2["DIR"] = "dir";
|
|
141
|
+
FilemanCommandEnum2["CREATE"] = "create";
|
|
142
|
+
FilemanCommandEnum2["RENAME"] = "rename";
|
|
143
|
+
FilemanCommandEnum2["DELETE"] = "delete";
|
|
144
|
+
FilemanCommandEnum2["MOVE"] = "move";
|
|
145
|
+
FilemanCommandEnum2["COPY"] = "copy";
|
|
146
|
+
FilemanCommandEnum2["UPLOAD"] = "upload";
|
|
147
|
+
FilemanCommandEnum2["DOWNLOAD"] = "download";
|
|
148
|
+
})(FilemanCommandEnum || (FilemanCommandEnum = {}));
|
|
149
|
+
const processFilemanMessage = (wsEvent) => {
|
|
150
|
+
let msg = JSON.parse(wsEvent.data);
|
|
151
|
+
switch (msg.type) {
|
|
152
|
+
case InstanceMessageTypeEnum.DATA: {
|
|
153
|
+
let response = JSON.parse(wsEvent.data);
|
|
154
|
+
switch (response.action) {
|
|
155
|
+
case InstanceMessageActionEnum.COMMAND:
|
|
156
|
+
{
|
|
157
|
+
switch (response.command) {
|
|
158
|
+
case "home" /* HOME */:
|
|
159
|
+
let data = response.data;
|
|
160
|
+
let nss = Array.from(new Set(data.map((n) => n.split("/")[0])));
|
|
161
|
+
nss.map((ns) => {
|
|
162
|
+
if (!files.current.some((f) => f.path === "/" + ns)) {
|
|
163
|
+
files.current.push({ name: ns, isDirectory: true, path: "/" + ns, class: "namespace" });
|
|
164
|
+
}
|
|
165
|
+
let podNames = Array.from(new Set(data.filter((a) => a.split("/")[0] === ns).map((o) => o.split("/")[1])));
|
|
166
|
+
podNames.map((p) => {
|
|
167
|
+
if (!files.current.some((f) => f.path === "/" + ns + "/" + p)) {
|
|
168
|
+
files.current.push({ name: p, isDirectory: true, path: "/" + ns + "/" + p, class: "pod" });
|
|
169
|
+
}
|
|
170
|
+
let conts = Array.from(new Set(data.filter((a) => a.split("/")[0] === ns && a.split("/")[1] === p).map((o) => o.split("/")[2])));
|
|
171
|
+
conts.map((c) => {
|
|
172
|
+
if (!files.current.some((f) => f.path === "/" + ns + "/" + p + "/" + c)) {
|
|
173
|
+
files.current.push({ name: c, isDirectory: true, path: "/" + ns + "/" + p + "/" + c, class: "container" });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
setStateFiles([...files.current]);
|
|
179
|
+
break;
|
|
180
|
+
case "dir" /* DIR */:
|
|
181
|
+
let content = JSON.parse(response.data);
|
|
182
|
+
if (content.status === "Success") {
|
|
183
|
+
for (let o of content.metadata.object) {
|
|
184
|
+
let name = o.name.split("/")[o.name.split("/").length - 1];
|
|
185
|
+
let e = {
|
|
186
|
+
name,
|
|
187
|
+
isDirectory: o.type === 1,
|
|
188
|
+
path: o.name,
|
|
189
|
+
updatedAt: (/* @__PURE__ */ new Date(+o.time)).toISOString(),
|
|
190
|
+
size: +o.size,
|
|
191
|
+
...o.type === 0 ? { class: "file" } : {}
|
|
192
|
+
};
|
|
193
|
+
let i = files.current.findIndex((f) => f.path === e.path);
|
|
194
|
+
if (i >= 0)
|
|
195
|
+
files.current[i] = e;
|
|
196
|
+
else
|
|
197
|
+
files.current.push(e);
|
|
198
|
+
}
|
|
199
|
+
setStateFiles([...files.current]);
|
|
200
|
+
} else {
|
|
201
|
+
addMessage(SignalMessageLevelEnum.ERROR, content.text || content.message);
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
case "rename" /* RENAME */:
|
|
205
|
+
{
|
|
206
|
+
let content2 = JSON.parse(response.data);
|
|
207
|
+
if (content2.status !== "Success") addMessage(SignalMessageLevelEnum.ERROR, content2.text || content2.message);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
case "delete" /* DELETE */: {
|
|
211
|
+
let content2 = JSON.parse(response.data);
|
|
212
|
+
if (content2.status === "Success") {
|
|
213
|
+
let fname = content2.metadata.object;
|
|
214
|
+
files.current = files.current.filter((f) => f.path !== fname);
|
|
215
|
+
files.current = files.current.filter((f) => !f.path.startsWith(fname + "/"));
|
|
216
|
+
setStateFiles([...files.current]);
|
|
217
|
+
} else {
|
|
218
|
+
addMessage(SignalMessageLevelEnum.ERROR, content2.text || content2.message);
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "move" /* MOVE */:
|
|
223
|
+
case "copy" /* COPY */:
|
|
224
|
+
case "create" /* CREATE */: {
|
|
225
|
+
let content2 = JSON.parse(response.data);
|
|
226
|
+
if (content2.status === "Success") {
|
|
227
|
+
let f = {
|
|
228
|
+
name: content2.metadata.object.split("/").slice(-1)[0],
|
|
229
|
+
isDirectory: content2.metadata.type === 1,
|
|
230
|
+
path: content2.metadata.object,
|
|
231
|
+
updatedAt: (/* @__PURE__ */ new Date(+content2.metadata.time)).toISOString(),
|
|
232
|
+
size: +content2.metadata.size,
|
|
233
|
+
...content2.metadata.type.type === 0 ? { class: "file" } : {}
|
|
234
|
+
};
|
|
235
|
+
files.current.push(f);
|
|
236
|
+
setStateFiles([...files.current]);
|
|
237
|
+
} else {
|
|
238
|
+
addMessage(SignalMessageLevelEnum.ERROR, content2.text || content2.message);
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
case InstanceMessageTypeEnum.SIGNAL:
|
|
249
|
+
let signalMessage = JSON.parse(wsEvent.data);
|
|
250
|
+
if (signalMessage.flow === InstanceMessageFlowEnum.RESPONSE) {
|
|
251
|
+
if (signalMessage.action === InstanceMessageActionEnum.START) {
|
|
252
|
+
if (signalMessage.text) addMessage(SignalMessageLevelEnum.INFO, signalMessage.text);
|
|
253
|
+
instance.current = signalMessage.instance;
|
|
254
|
+
} else {
|
|
255
|
+
addMessage(SignalMessageLevelEnum.ERROR, wsEvent.data);
|
|
256
|
+
}
|
|
257
|
+
} else if (signalMessage.flow === InstanceMessageFlowEnum.UNSOLICITED) {
|
|
258
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
259
|
+
if (cluster2) {
|
|
260
|
+
let accessKey = cluster2.accessKeys.get("fileman$read");
|
|
261
|
+
if (accessKey && instance?.current) {
|
|
262
|
+
if (signalMessage.event === SignalMessageEventEnum.ADD) {
|
|
263
|
+
let filemanMessage = {
|
|
264
|
+
flow: InstanceMessageFlowEnum.REQUEST,
|
|
265
|
+
action: InstanceMessageActionEnum.COMMAND,
|
|
266
|
+
channel: "fileman",
|
|
267
|
+
type: InstanceMessageTypeEnum.DATA,
|
|
268
|
+
accessKey: accessKeySerialize(accessKey),
|
|
269
|
+
instance: instance.current,
|
|
270
|
+
id: v4(),
|
|
271
|
+
command: "home" /* HOME */,
|
|
272
|
+
namespace: signalMessage.namespace,
|
|
273
|
+
group: "",
|
|
274
|
+
pod: signalMessage.pod,
|
|
275
|
+
container: signalMessage.container,
|
|
276
|
+
params: [],
|
|
277
|
+
msgtype: "filemanmessage"
|
|
278
|
+
};
|
|
279
|
+
let payload = JSON.stringify(filemanMessage);
|
|
280
|
+
wsEvent.target.send(payload);
|
|
281
|
+
if (signalMessage.text) addMessage(SignalMessageLevelEnum.INFO, signalMessage.text);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
addMessage(SignalMessageLevelEnum.INFO, "Have no instance/accessKey");
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
addMessage(SignalMessageLevelEnum.INFO, "Have no cluster");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
|
+
default:
|
|
292
|
+
console.log(`Invalid message type ${msg.type}`);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const addMessage = (level2, text) => {
|
|
297
|
+
alertApi.post({ message: text, severity: level2, display: "transient" });
|
|
298
|
+
setStatusMessages((prev) => [...prev, {
|
|
299
|
+
level: level2,
|
|
300
|
+
text,
|
|
301
|
+
type: InstanceMessageTypeEnum.SIGNAL
|
|
302
|
+
}]);
|
|
303
|
+
};
|
|
304
|
+
const websocketOnMessage = (wsEvent) => {
|
|
305
|
+
let instanceMessage;
|
|
306
|
+
try {
|
|
307
|
+
instanceMessage = JSON.parse(wsEvent.data);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.log(err);
|
|
310
|
+
console.log(wsEvent.data);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
switch (instanceMessage.channel) {
|
|
314
|
+
case "fileman":
|
|
315
|
+
processFilemanMessage(wsEvent);
|
|
316
|
+
break;
|
|
317
|
+
default:
|
|
318
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Invalid channel in message: " + instanceMessage.channel);
|
|
319
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Invalid message: " + JSON.stringify(instanceMessage));
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
const websocketOnOpen = (ws) => {
|
|
324
|
+
setWebSocket(ws);
|
|
325
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
326
|
+
if (!cluster2) {
|
|
327
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
let pods = cluster2.pods.filter((p2) => selectedNamespaces.includes(p2.namespace));
|
|
331
|
+
if (!pods) {
|
|
332
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No pods found");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
console.log(`WS connected`);
|
|
336
|
+
let accessKey = cluster2.accessKeys.get("fileman$read");
|
|
337
|
+
if (accessKey) {
|
|
338
|
+
let containers = [];
|
|
339
|
+
if (selectedContainerNames.length > 0) {
|
|
340
|
+
for (var p of selectedPodNames) {
|
|
341
|
+
for (var c of selectedContainerNames) {
|
|
342
|
+
containers.push(p + "+" + c);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
let iConfig = {
|
|
347
|
+
channel: "fileman",
|
|
348
|
+
objects: InstanceConfigObjectEnum.PODS,
|
|
349
|
+
action: InstanceMessageActionEnum.START,
|
|
350
|
+
flow: InstanceMessageFlowEnum.REQUEST,
|
|
351
|
+
instance: "",
|
|
352
|
+
accessKey: accessKeySerialize(accessKey),
|
|
353
|
+
scope: "fileman$read",
|
|
354
|
+
view: selectedContainerNames.length > 0 ? InstanceConfigViewEnum.CONTAINER : InstanceConfigViewEnum.POD,
|
|
355
|
+
namespace: selectedNamespaces.join(","),
|
|
356
|
+
group: "",
|
|
357
|
+
pod: selectedPodNames.map((p2) => p2).join(","),
|
|
358
|
+
container: containers.join(","),
|
|
359
|
+
data: {},
|
|
360
|
+
type: InstanceMessageTypeEnum.SIGNAL
|
|
361
|
+
};
|
|
362
|
+
ws.send(JSON.stringify(iConfig));
|
|
363
|
+
} else {
|
|
364
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No accessKey for starting fileman streaming");
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const startFilemanViewer = () => {
|
|
369
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
370
|
+
if (!cluster2) {
|
|
371
|
+
addMessage(SignalMessageLevelEnum.ERROR, "No cluster selected");
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
let ws = new WebSocket(cluster2.url);
|
|
376
|
+
ws.onopen = () => websocketOnOpen(ws);
|
|
377
|
+
ws.onmessage = (event) => websocketOnMessage(event);
|
|
378
|
+
ws.onclose = (event) => websocketOnClose(event);
|
|
379
|
+
setWebSocket(ws);
|
|
380
|
+
} catch (err) {
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const websocketOnClose = (_event) => {
|
|
384
|
+
console.log(`WS disconnected`);
|
|
385
|
+
setStarted(false);
|
|
386
|
+
paused.current = false;
|
|
387
|
+
setStopped(true);
|
|
388
|
+
};
|
|
389
|
+
const stopFilemanViewer = () => {
|
|
390
|
+
webSocket?.close();
|
|
391
|
+
};
|
|
392
|
+
const actionButtons = () => {
|
|
393
|
+
let hasKey = false;
|
|
394
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
395
|
+
if (cluster2) {
|
|
396
|
+
hasKey = Boolean(cluster2.accessKeys.get("fileman$read"));
|
|
397
|
+
}
|
|
398
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(IconButton, { onClick: () => clickStart(), title: "Play", disabled: started || !paused || selectedPodNames.length === 0 || !hasKey }, /* @__PURE__ */ React.createElement(PlayIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: onClickPause, title: "Pause", disabled: !(started && !paused.current && selectedPodNames.length > 0) }, /* @__PURE__ */ React.createElement(PauseIcon, null)), /* @__PURE__ */ React.createElement(IconButton, { onClick: onClickStop, title: "Stop", disabled: stopped || selectedPodNames.length === 0 }, /* @__PURE__ */ React.createElement(StopIcon, null)));
|
|
399
|
+
};
|
|
400
|
+
const statusButtons = (title) => {
|
|
401
|
+
const show = (level2) => {
|
|
402
|
+
setShowStatusDialog(true);
|
|
403
|
+
setStatusLevel(level2);
|
|
404
|
+
};
|
|
405
|
+
const prepareText = (txt) => {
|
|
406
|
+
return txt ? txt.length > 25 ? txt.substring(0, 25) + "..." : txt : "N/A";
|
|
407
|
+
};
|
|
408
|
+
return /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "row" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Typography, { variant: "h5" }, prepareText(title))), /* @__PURE__ */ React.createElement(Grid, { item: true, style: { marginTop: "-8px" } }, /* @__PURE__ */ React.createElement(IconButton, { title: "info", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO), onClick: () => show(SignalMessageLevelEnum.INFO) }, /* @__PURE__ */ React.createElement(InfoIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.INFO) ? "blue" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "warning", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING), onClick: () => show(SignalMessageLevelEnum.WARNING), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(WarningIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.WARNING) ? "orange" : "#BDBDBD" } })), /* @__PURE__ */ React.createElement(IconButton, { title: "error", disabled: !statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR), onClick: () => show(SignalMessageLevelEnum.ERROR), style: { marginLeft: "-16px" } }, /* @__PURE__ */ React.createElement(ErrorIcon, { style: { color: statusMessages.some((m) => m.type === InstanceMessageTypeEnum.SIGNAL && m.level === SignalMessageLevelEnum.ERROR) ? "red" : "#BDBDBD" } }))));
|
|
409
|
+
};
|
|
410
|
+
const statusClear = (level2) => {
|
|
411
|
+
setStatusMessages(statusMessages.filter((m) => m.level !== level2));
|
|
412
|
+
setShowStatusDialog(false);
|
|
413
|
+
};
|
|
414
|
+
const onError = (error2, _file) => {
|
|
415
|
+
addMessage(SignalMessageLevelEnum.ERROR, error2.message);
|
|
416
|
+
};
|
|
417
|
+
const onRename = (file, newName) => {
|
|
418
|
+
let [namespace, pod, container] = file.path.split("/").slice(1);
|
|
419
|
+
files.current = files.current.filter((f) => f.path !== file.path);
|
|
420
|
+
setStateFiles([...files.current]);
|
|
421
|
+
sendCommand("rename" /* RENAME */, namespace, pod, container, [file.path, newName]);
|
|
422
|
+
};
|
|
423
|
+
const onRefresh = () => {
|
|
424
|
+
if (level >= 3) {
|
|
425
|
+
files.current = files.current.filter((f) => !f.path.startsWith(currentPath + "/"));
|
|
426
|
+
getLocalDir(currentPath + "/");
|
|
427
|
+
} else {
|
|
428
|
+
sendCommand("home" /* HOME */, "", "", "", []);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
const getLocalDir = (folder) => {
|
|
432
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
433
|
+
if (cluster2) {
|
|
434
|
+
let accessKey = cluster2.accessKeys.get("fileman$read");
|
|
435
|
+
if (accessKey && instance?.current && webSocket) {
|
|
436
|
+
let [namespace, pod, container] = folder.split("/").slice(1);
|
|
437
|
+
let filemanMessage = {
|
|
438
|
+
flow: InstanceMessageFlowEnum.REQUEST,
|
|
439
|
+
action: InstanceMessageActionEnum.COMMAND,
|
|
440
|
+
channel: "fileman",
|
|
441
|
+
type: InstanceMessageTypeEnum.DATA,
|
|
442
|
+
accessKey: accessKeySerialize(accessKey),
|
|
443
|
+
instance: instance.current,
|
|
444
|
+
id: v4(),
|
|
445
|
+
command: "dir" /* DIR */,
|
|
446
|
+
namespace,
|
|
447
|
+
group: "",
|
|
448
|
+
pod,
|
|
449
|
+
container,
|
|
450
|
+
params: [folder],
|
|
451
|
+
msgtype: "filemanmessage"
|
|
452
|
+
};
|
|
453
|
+
let payload = JSON.stringify(filemanMessage);
|
|
454
|
+
webSocket.send(payload);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
const sendCommand = (command, namespace, pod, container, params) => {
|
|
459
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
460
|
+
if (cluster2) {
|
|
461
|
+
let accessKey = cluster2.accessKeys.get("fileman$read");
|
|
462
|
+
if (accessKey && instance?.current && webSocket) {
|
|
463
|
+
let filemanMessage = {
|
|
464
|
+
flow: InstanceMessageFlowEnum.REQUEST,
|
|
465
|
+
action: InstanceMessageActionEnum.COMMAND,
|
|
466
|
+
channel: "fileman",
|
|
467
|
+
type: InstanceMessageTypeEnum.DATA,
|
|
468
|
+
accessKey: accessKeySerialize(accessKey),
|
|
469
|
+
instance: instance.current,
|
|
470
|
+
id: v4(),
|
|
471
|
+
command,
|
|
472
|
+
namespace,
|
|
473
|
+
group: "",
|
|
474
|
+
pod,
|
|
475
|
+
container,
|
|
476
|
+
params,
|
|
477
|
+
msgtype: "filemanmessage"
|
|
478
|
+
};
|
|
479
|
+
let payload = JSON.stringify(filemanMessage);
|
|
480
|
+
webSocket.send(payload);
|
|
481
|
+
} else {
|
|
482
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Have no instance/accessKey");
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Have no cluster");
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
const onDelete = async (filesToDelete) => {
|
|
489
|
+
for (let file of filesToDelete) {
|
|
490
|
+
let [namespace, pod, container] = file.path.split("/").slice(1);
|
|
491
|
+
sendCommand("delete" /* DELETE */, namespace, pod, container, [file.path]);
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
const onCreateFolder = async (name, parentFolder) => {
|
|
495
|
+
let [namespace, pod, container] = parentFolder.path.split("/").slice(1);
|
|
496
|
+
sendCommand("create" /* CREATE */, namespace, pod, container, [parentFolder.path + "/" + name]);
|
|
497
|
+
};
|
|
498
|
+
const onDownload = async (filesToDownload) => {
|
|
499
|
+
let cluster2 = validClusters.find((cluster3) => cluster3.name === selectedClusterName);
|
|
500
|
+
if (cluster2) {
|
|
501
|
+
let accessKey = cluster2.accessKeys.get("fileman$read");
|
|
502
|
+
if (accessKey) {
|
|
503
|
+
for (let file of filesToDownload) {
|
|
504
|
+
const url = `${cluster2.url}/channel/fileman/download?key=${instance.current}&filename=${file.path}`;
|
|
505
|
+
try {
|
|
506
|
+
const response = await fetch(url, { headers: { "Authorization": "Bearer " + accessKeySerialize(accessKey) } });
|
|
507
|
+
if (response.ok) {
|
|
508
|
+
const blob = await response.blob();
|
|
509
|
+
const link = document.createElement("a");
|
|
510
|
+
link.href = URL.createObjectURL(blob);
|
|
511
|
+
link.download = file.path.split("/").slice(-1)[0];
|
|
512
|
+
if (file.isDirectory) link.download += ".tar.gz";
|
|
513
|
+
document.body.appendChild(link);
|
|
514
|
+
link.click();
|
|
515
|
+
document.body.removeChild(link);
|
|
516
|
+
URL.revokeObjectURL(link.href);
|
|
517
|
+
} else {
|
|
518
|
+
console.error(`Error downloading file: ${file.path}`);
|
|
519
|
+
addMessage(SignalMessageLevelEnum.ERROR, `Error downloading file ${file.path}: (${response.status}) ${await response.text()}`);
|
|
520
|
+
}
|
|
521
|
+
} catch (error2) {
|
|
522
|
+
console.error(`Error downloading file: ${file.path}`, error2);
|
|
523
|
+
addMessage(SignalMessageLevelEnum.ERROR, `Error downloading file ${file.path}: ${error2}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Have no instance/accessKey");
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
addMessage(SignalMessageLevelEnum.ERROR, "Have no cluster");
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const onPaste = (filesToPaste, destFolder, operation) => {
|
|
534
|
+
let command = operation === "move" ? "move" /* MOVE */ : "copy" /* COPY */;
|
|
535
|
+
for (let file of filesToPaste) {
|
|
536
|
+
let [namespace, pod, container] = file.path.split("/").slice(1);
|
|
537
|
+
sendCommand(command, namespace, pod, container, [file.path, destFolder.path]);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
const onFolderChange = (folder) => {
|
|
541
|
+
setCurrentPath(folder);
|
|
542
|
+
folder += "/";
|
|
543
|
+
let level2 = folder.split("/").length - 1;
|
|
544
|
+
if (level2 > 3) getLocalDir(folder);
|
|
545
|
+
};
|
|
546
|
+
const onFileUploading = (file, _parentFolder) => {
|
|
547
|
+
return { filename: currentPath + "/" + file.name };
|
|
548
|
+
};
|
|
549
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), !isKwirthAvailable(entity) && !loading && error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "An error has ocurred while obtaining data from kuebernetes clusters.", message: error?.message }), !isKwirthAvailable(entity) && !loading && /* @__PURE__ */ React.createElement(MissingAnnotationEmptyState, { readMoreUrl: "https://github.com/jfvilas/plugin-kwirth-fileman", annotation: [ANNOTATION_BACKSTAGE_KUBERNETES_LABELID, ANNOTATION_BACKSTAGE_KUBERNETES_LABELSELECTOR] }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_CLUSTERS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster2) => sum + cluster2.pods.length, 0) === 0 && /* @__PURE__ */ React.createElement(ComponentNotFound, { error: ErrorType.NO_PODS, entity }), isKwirthAvailable(entity) && !loading && validClusters && validClusters.length > 0 && validClusters.reduce((sum, cluster2) => sum + cluster2.pods.length, 0) > 0 && /* @__PURE__ */ React.createElement(Box, { sx: { display: "flex", height: "100%" } }, /* @__PURE__ */ React.createElement(Box, { sx: { width: "200px", maxWidth: "200px" } }, /* @__PURE__ */ React.createElement(Grid, { container: true, direction: "column" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(ClusterList, { resources: validClusters, selectedClusterName, onSelect: onSelectCluster }))), !props.hideVersion && /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(KwirthNews, { latestVersions: backendInfo, backendVersion, ownVersion: VERSION }))))), /* @__PURE__ */ React.createElement(Box, { sx: { flexGrow: 1, flex: 1, overflow: "hidden", p: 1, marginLeft: "8px" } }, !selectedClusterName && /* @__PURE__ */ React.createElement("img", { src: KwirthFilemanLogo, alt: "No cluster selected", style: { left: "40%", marginTop: "10%", width: "20%", position: "relative" } }), selectedClusterName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { style: { marginTop: -8, marginBottom: "8px" } }, /* @__PURE__ */ React.createElement(
|
|
550
|
+
CardHeader,
|
|
551
|
+
{
|
|
552
|
+
title: statusButtons(selectedClusterName),
|
|
553
|
+
style: { marginTop: -4, marginBottom: 4, flexShrink: 0 },
|
|
554
|
+
action: actionButtons()
|
|
555
|
+
}
|
|
556
|
+
)), started && /* @__PURE__ */ React.createElement(Grid, { ref: filemanBoxRef, style: { height: `calc(100vh - ${filemanBoxTop}px - 35px)` } }, /* @__PURE__ */ React.createElement(
|
|
557
|
+
FileManager,
|
|
558
|
+
{
|
|
559
|
+
className: styles.customFm,
|
|
560
|
+
files: stateFiles,
|
|
561
|
+
filePreviewPath: "http://avoid-console-error",
|
|
562
|
+
primaryColor: "#1976d2",
|
|
563
|
+
fontFamily: '"Helvetica Neue", Helvetica, Roboto, Arial, sans-serif',
|
|
564
|
+
height: "100%",
|
|
565
|
+
actions: /* @__PURE__ */ new Map(),
|
|
566
|
+
icons: /* @__PURE__ */ new Map(),
|
|
567
|
+
fileUploadConfig,
|
|
568
|
+
onCreateFolder,
|
|
569
|
+
onError,
|
|
570
|
+
onRename,
|
|
571
|
+
onPaste,
|
|
572
|
+
onDelete,
|
|
573
|
+
onFolderChange,
|
|
574
|
+
onRefresh,
|
|
575
|
+
onFileUploading,
|
|
576
|
+
onDownload,
|
|
577
|
+
enableFilePreview: false,
|
|
578
|
+
initialPath: "",
|
|
579
|
+
permissions
|
|
580
|
+
}
|
|
581
|
+
))))), showStatusDialog && /* @__PURE__ */ React.createElement(StatusLog, { level: statusLevel, onClose: () => setShowStatusDialog(false), statusMessages, onClear: statusClear }));
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
export { EntityKwirthFilemanContent };
|
|
585
|
+
//# sourceMappingURL=EntityKwirthFilemanContent.esm.js.map
|