lupine.api 1.1.57 → 1.1.59
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 +3 -3
- package/admin/admin-about.tsx +12 -16
- package/admin/admin-config.tsx +47 -44
- package/admin/admin-css.tsx +3 -3
- package/admin/admin-db.tsx +75 -75
- package/admin/admin-frame-helper.tsx +364 -364
- package/admin/admin-frame.tsx +164 -164
- package/admin/admin-index.tsx +65 -65
- package/admin/admin-login.tsx +111 -111
- package/admin/admin-menu-edit.tsx +637 -637
- package/admin/admin-menu-list.tsx +87 -87
- package/admin/admin-page-edit.tsx +564 -564
- package/admin/admin-page-list.tsx +83 -83
- package/admin/admin-performance.tsx +28 -28
- package/admin/admin-release.tsx +427 -404
- package/admin/admin-resources.tsx +382 -382
- package/admin/admin-shell.tsx +89 -89
- package/admin/admin-table-data.tsx +146 -146
- package/admin/admin-table-list.tsx +230 -230
- package/admin/admin-test-animations.tsx +395 -395
- package/admin/admin-test-component.tsx +823 -808
- package/admin/admin-test-edit.tsx +319 -319
- package/admin/admin-test-themes.tsx +56 -56
- package/admin/admin-tokens.tsx +338 -338
- package/admin/design/admin-design.tsx +174 -174
- package/admin/design/block-grid.tsx +36 -36
- package/admin/design/block-grid1.tsx +21 -21
- package/admin/design/block-paragraph.tsx +19 -19
- package/admin/design/block-title.tsx +19 -19
- package/admin/design/design-block-box.tsx +140 -140
- package/admin/design/drag-data.tsx +24 -24
- package/admin/index.ts +9 -9
- package/admin/package.json +15 -15
- package/admin/tsconfig.json +127 -127
- package/dev/copy-folder.js +32 -32
- package/dev/cp-index-html.js +69 -69
- package/dev/file-utils.js +12 -12
- package/dev/index.js +18 -19
- package/dev/package.json +12 -12
- package/dev/plugin-ifelse.js +168 -168
- package/dev/plugin-ifelse.test.js +37 -37
- package/dev/run-cmd.js +14 -14
- package/dev/send-request.js +12 -12
- package/package.json +55 -55
- package/src/admin-api/admin-api-helper.ts +210 -205
- package/src/admin-api/admin-api.ts +65 -65
- package/src/admin-api/admin-auth.ts +152 -146
- package/src/admin-api/admin-config.ts +94 -84
- package/src/admin-api/admin-csv.ts +94 -94
- package/src/admin-api/admin-db.ts +269 -269
- package/src/admin-api/admin-menu.ts +135 -135
- package/src/admin-api/admin-page.ts +135 -135
- package/src/admin-api/admin-performance.ts +128 -128
- package/src/admin-api/admin-release.ts +703 -700
- package/src/admin-api/admin-resources.ts +318 -318
- package/src/admin-api/admin-token-helper.ts +82 -79
- package/src/admin-api/admin-tokens.ts +90 -90
- package/src/admin-api/index.ts +2 -2
- package/src/admin-api/web-config-api.ts +19 -19
- package/src/api/api-cache.ts +103 -103
- package/src/api/api-helper.ts +44 -44
- package/src/api/api-module.ts +67 -60
- package/src/api/api-router.ts +177 -177
- package/src/api/api-shared-storage.ts +64 -64
- package/src/api/async-storage.ts +5 -5
- package/src/api/debug-service.ts +56 -56
- package/src/api/encode-html.ts +27 -27
- package/src/api/handle-status.ts +75 -75
- package/src/api/index.ts +15 -16
- package/src/api/mini-web-socket.ts +270 -270
- package/src/api/server-content-type.ts +82 -82
- package/src/api/server-render.ts +235 -215
- package/src/api/shell-service.ts +74 -74
- package/src/api/simple-storage.ts +80 -80
- package/src/api/static-server.ts +128 -125
- package/src/api/to-client-delivery.ts +26 -26
- package/src/app/app-cache.ts +55 -55
- package/src/app/app-helper.ts +62 -62
- package/src/app/app-message.ts +109 -109
- package/src/app/app-shared-storage.ts +363 -363
- package/src/app/app-start.ts +136 -136
- package/src/app/cleanup-exit.ts +16 -16
- package/src/app/host-to-path.ts +38 -38
- package/src/app/index.ts +11 -11
- package/src/app/process-dev-requests.ts +130 -130
- package/src/app/web-listener.ts +294 -294
- package/src/app/web-processor.ts +47 -42
- package/src/app/web-server.ts +100 -100
- package/src/common-js/web-env.js +104 -104
- package/src/index.ts +7 -7
- package/src/lang/api-lang-en.ts +26 -26
- package/src/lang/api-lang-zh-cn.ts +27 -27
- package/src/lang/index.ts +2 -2
- package/src/lang/lang-helper.ts +76 -76
- package/src/lang/lang-props.ts +6 -6
- package/src/lib/db/db-helper.ts +23 -23
- package/src/lib/db/db-mysql.ts +249 -250
- package/src/lib/db/db-sqlite.ts +101 -101
- package/src/lib/db/db.spec.ts +28 -28
- package/src/lib/db/db.ts +325 -325
- package/src/lib/db/index.ts +5 -5
- package/src/lib/index.ts +3 -3
- package/src/lib/logger.spec.ts +214 -214
- package/src/lib/logger.ts +281 -281
- package/src/lib/runtime-require.ts +37 -37
- package/src/lib/utils/cookie-util.ts +34 -34
- package/src/lib/utils/crypto.ts +58 -58
- package/src/lib/utils/date-utils.ts +317 -317
- package/src/lib/utils/deep-merge.ts +37 -37
- package/src/lib/utils/delay.ts +12 -12
- package/src/lib/utils/file-setting.ts +55 -55
- package/src/lib/utils/format-bytes.ts +11 -11
- package/src/lib/utils/fs-utils.ts +158 -158
- package/src/lib/utils/get-env.ts +27 -27
- package/src/lib/utils/index.ts +12 -12
- package/src/lib/utils/is-type.ts +48 -48
- package/src/lib/utils/load-env.ts +14 -14
- package/src/lib/utils/pad.ts +6 -6
- package/src/models/api-base.ts +5 -5
- package/src/models/api-module-props.ts +10 -11
- package/src/models/api-router-props.ts +26 -26
- package/src/models/app-cache-props.ts +33 -33
- package/src/models/app-data-props.ts +10 -10
- package/src/models/app-helper-props.ts +6 -6
- package/src/models/app-shared-storage-props.ts +38 -38
- package/src/models/app-start-props.ts +18 -18
- package/src/models/async-storage-props.ts +13 -13
- package/src/models/db-config.ts +30 -30
- package/src/models/host-to-path-props.ts +12 -12
- package/src/models/index.ts +16 -16
- package/src/models/json-object.ts +8 -8
- package/src/models/locals-props.ts +36 -36
- package/src/models/logger-props.ts +84 -84
- package/src/models/simple-storage-props.ts +13 -14
- package/src/models/to-client-delivery-props.ts +6 -6
- package/tsconfig.json +115 -115
- package/dev/plugin-gen-versions.js +0 -20
|
@@ -1,382 +1,382 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CssProps,
|
|
3
|
-
getRenderPageProps,
|
|
4
|
-
DomUtils,
|
|
5
|
-
formatBytes,
|
|
6
|
-
HtmlVar,
|
|
7
|
-
InputWithTitle,
|
|
8
|
-
MessageBox,
|
|
9
|
-
MessageBoxButtonProps,
|
|
10
|
-
NotificationColor,
|
|
11
|
-
NotificationMessage,
|
|
12
|
-
Progress,
|
|
13
|
-
ProgressHookProps,
|
|
14
|
-
uploadFile,
|
|
15
|
-
downloadStream,
|
|
16
|
-
} from 'lupine.components';
|
|
17
|
-
|
|
18
|
-
// https://www.toptal.com/designers/htmlarrows/symbols/
|
|
19
|
-
const ResourcesList = (props: {
|
|
20
|
-
results: any;
|
|
21
|
-
onDownload: (name: string) => void;
|
|
22
|
-
onUploadLocal: (name: string) => void;
|
|
23
|
-
onNewFolder: (name: string) => void;
|
|
24
|
-
onRename: (fPath: string, fName: string) => void;
|
|
25
|
-
onRemove: (name: string) => void;
|
|
26
|
-
onRemoveDir: (name: string) => void;
|
|
27
|
-
onListFolder: (folder: string) => void;
|
|
28
|
-
resourcesFullPath: string;
|
|
29
|
-
}) => {
|
|
30
|
-
const listFolder = (results: any, parentFolder = '') => {
|
|
31
|
-
return results.map((result: any) => (
|
|
32
|
-
<div>
|
|
33
|
-
<div class={`row-box mt-m`}>
|
|
34
|
-
<label class='label mr-m f-name' onClick={() => props.onListFolder(parentFolder + result.name)}>
|
|
35
|
-
{result.name}
|
|
36
|
-
</label>
|
|
37
|
-
<label class='label mr-m f-time'>{result.time}</label>
|
|
38
|
-
<label class='label mr-m f-size'>{typeof result.size !== 'undefined' && formatBytes(result.size)}</label>
|
|
39
|
-
{typeof result.size !== 'undefined' && (
|
|
40
|
-
<div>
|
|
41
|
-
<button
|
|
42
|
-
title='Download remote file'
|
|
43
|
-
onClick={() => props.onDownload(parentFolder + result.name)}
|
|
44
|
-
class='button-base button-s'
|
|
45
|
-
>
|
|
46
|
-
⇩
|
|
47
|
-
</button>
|
|
48
|
-
<button
|
|
49
|
-
title='Rename remote file'
|
|
50
|
-
onClick={() => props.onRename(parentFolder + result.name, result.name)}
|
|
51
|
-
class='button-base button-s'
|
|
52
|
-
>
|
|
53
|
-
⇎
|
|
54
|
-
</button>
|
|
55
|
-
<button
|
|
56
|
-
title='Delete remote file'
|
|
57
|
-
onClick={() => props.onRemove(parentFolder + result.name)}
|
|
58
|
-
class='button-base button-s color-red'
|
|
59
|
-
>
|
|
60
|
-
✘
|
|
61
|
-
</button>
|
|
62
|
-
</div>
|
|
63
|
-
)}
|
|
64
|
-
{typeof result.size === 'undefined' && (
|
|
65
|
-
<div>
|
|
66
|
-
<button onClick={() => props.onListFolder(parentFolder + result.name)} class='button-base button-s'>
|
|
67
|
-
⇨
|
|
68
|
-
</button>
|
|
69
|
-
<button
|
|
70
|
-
title='Choose a local file to upload to remote'
|
|
71
|
-
onClick={() => props.onUploadLocal(parentFolder + result.name)}
|
|
72
|
-
class='button-base button-s mr-m'
|
|
73
|
-
>
|
|
74
|
-
⇮
|
|
75
|
-
</button>
|
|
76
|
-
|
|
77
|
-
<button
|
|
78
|
-
title='Rename remote file'
|
|
79
|
-
onClick={() => props.onRename(parentFolder + result.name, result.name)}
|
|
80
|
-
class='button-base button-s'
|
|
81
|
-
>
|
|
82
|
-
⇎
|
|
83
|
-
</button>
|
|
84
|
-
<button
|
|
85
|
-
title='Delete remote file'
|
|
86
|
-
onClick={() => props.onRemoveDir(parentFolder + result.name)}
|
|
87
|
-
class='button-base button-s color-red'
|
|
88
|
-
>
|
|
89
|
-
✘
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
)}
|
|
93
|
-
</div>
|
|
94
|
-
{result.items && <div class='pl-ll'>{listFolder(result.items, result.name + '/')}</div>}
|
|
95
|
-
</div>
|
|
96
|
-
));
|
|
97
|
-
};
|
|
98
|
-
const css: CssProps = {
|
|
99
|
-
'.f-name': {
|
|
100
|
-
textDecoration: 'underline',
|
|
101
|
-
width: '200px',
|
|
102
|
-
cursor: 'pointer',
|
|
103
|
-
},
|
|
104
|
-
'.f-size span, .f-time span': {
|
|
105
|
-
color: 'red',
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
return (
|
|
109
|
-
<div css={css}>
|
|
110
|
-
<div class='row-box mt-m mr-s'>
|
|
111
|
-
<button title='Go up' onClick={() => props.onListFolder('..')} class='button-base button-s mr-m'>
|
|
112
|
-
⇦
|
|
113
|
-
</button>
|
|
114
|
-
<button title='Refresh' onClick={() => props.onListFolder('')} class='button-base button-s'>
|
|
115
|
-
⇳
|
|
116
|
-
</button>
|
|
117
|
-
<button
|
|
118
|
-
title='Choose a local file to upload to remote'
|
|
119
|
-
onClick={() => props.onUploadLocal('')}
|
|
120
|
-
class='button-base button-s mr-m'
|
|
121
|
-
>
|
|
122
|
-
⇮
|
|
123
|
-
</button>
|
|
124
|
-
|
|
125
|
-
<button title='Create a sub folder' onClick={() => props.onNewFolder('')} class='button-base button-s'>
|
|
126
|
-
⚒
|
|
127
|
-
</button>
|
|
128
|
-
</div>
|
|
129
|
-
<div class='label mx-m f-name'>{props.resourcesFullPath}</div>
|
|
130
|
-
{listFolder(props.results)}
|
|
131
|
-
</div>
|
|
132
|
-
);
|
|
133
|
-
};
|
|
134
|
-
export const AdminResourcesPage = () => {
|
|
135
|
-
const domLog = new HtmlVar('');
|
|
136
|
-
const domUpdate = new HtmlVar('');
|
|
137
|
-
|
|
138
|
-
let resourcesFullPath = '';
|
|
139
|
-
const onResources = async () => {
|
|
140
|
-
loasResources('/');
|
|
141
|
-
};
|
|
142
|
-
const loasResources = async (folder: string) => {
|
|
143
|
-
const onListFolder = (folder: string) => {
|
|
144
|
-
loasResources(resourcesFullPath + folder);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
let pathForUpload = '';
|
|
148
|
-
const uploadFile = async (fPath: string) => {
|
|
149
|
-
pathForUpload = fPath;
|
|
150
|
-
const fDom = DomUtils.bySelector('.up-file') as HTMLInputElement;
|
|
151
|
-
fDom.click();
|
|
152
|
-
};
|
|
153
|
-
const onUploadLocal = (fPath: string) => {
|
|
154
|
-
// MessageBox.show({
|
|
155
|
-
// title: 'Override remote file',
|
|
156
|
-
// buttonType: MessageBoxButtonProps.YesNo,
|
|
157
|
-
// contentMinWidth: '300px',
|
|
158
|
-
// handleClicked: (index: number, close) => {
|
|
159
|
-
// if (index === 0) {
|
|
160
|
-
uploadFile(fPath);
|
|
161
|
-
// }
|
|
162
|
-
// close();
|
|
163
|
-
// },
|
|
164
|
-
// children: <div>Do you upload local files that may overwrite remote file [{fPath}]?</div>,
|
|
165
|
-
// });
|
|
166
|
-
};
|
|
167
|
-
const onNewFolder = async (fPath: string) => {
|
|
168
|
-
let newName = '';
|
|
169
|
-
const content = InputWithTitle('New folder name:', '', (value: string) => {
|
|
170
|
-
newName = value;
|
|
171
|
-
});
|
|
172
|
-
MessageBox.show({
|
|
173
|
-
title: 'Input new folder name',
|
|
174
|
-
buttonType: MessageBoxButtonProps.OkCancel,
|
|
175
|
-
contentMinWidth: '300px',
|
|
176
|
-
handleClicked: async (index: number, close) => {
|
|
177
|
-
if (index === 0) {
|
|
178
|
-
await newFolder(fPath, newName);
|
|
179
|
-
}
|
|
180
|
-
close();
|
|
181
|
-
},
|
|
182
|
-
children: content,
|
|
183
|
-
});
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const onDownload = async (name: string) => {
|
|
187
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData(
|
|
188
|
-
'/api/admin/resources/download',
|
|
189
|
-
{
|
|
190
|
-
resource: resourcesFullPath + name,
|
|
191
|
-
},
|
|
192
|
-
true
|
|
193
|
-
);
|
|
194
|
-
if (!response || !response.blob) {
|
|
195
|
-
NotificationMessage.sendMessage('Failed to get resource', NotificationColor.Error);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
downloadStream(await response.blob(), name);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const newFolder = async (fPath: string, fName: string) => {
|
|
203
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/newFolder', {
|
|
204
|
-
resource: resourcesFullPath + fPath,
|
|
205
|
-
newName: fName,
|
|
206
|
-
});
|
|
207
|
-
const dataResponse = await response.json;
|
|
208
|
-
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
209
|
-
NotificationMessage.sendMessage(dataResponse.message || 'Failed to create a folder', NotificationColor.Error);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
NotificationMessage.sendMessage(
|
|
213
|
-
'Folder created successfully, you need to refresh the page',
|
|
214
|
-
NotificationColor.Success
|
|
215
|
-
);
|
|
216
|
-
};
|
|
217
|
-
const renameFile = async (fPath: string, fName: string, fNewName: string) => {
|
|
218
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/rename', {
|
|
219
|
-
resource: resourcesFullPath + fPath,
|
|
220
|
-
oldName: fName,
|
|
221
|
-
newName: fNewName,
|
|
222
|
-
});
|
|
223
|
-
const dataResponse = await response.json;
|
|
224
|
-
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
225
|
-
NotificationMessage.sendMessage(dataResponse.message || 'Failed to rename resource', NotificationColor.Error);
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
NotificationMessage.sendMessage(
|
|
229
|
-
'Resource renamed successfully, you need to refresh the page',
|
|
230
|
-
NotificationColor.Success
|
|
231
|
-
);
|
|
232
|
-
};
|
|
233
|
-
const onRename = async (fPath: string, fName: string) => {
|
|
234
|
-
let newName = '';
|
|
235
|
-
const content = InputWithTitle('Reanme to:', fName, (value: string) => {
|
|
236
|
-
newName = value;
|
|
237
|
-
});
|
|
238
|
-
MessageBox.show({
|
|
239
|
-
title: 'Input new name',
|
|
240
|
-
buttonType: MessageBoxButtonProps.OkCancel,
|
|
241
|
-
contentMinWidth: '300px',
|
|
242
|
-
handleClicked: async (index: number, close) => {
|
|
243
|
-
if (index === 0) {
|
|
244
|
-
await renameFile(fPath, fName, newName);
|
|
245
|
-
}
|
|
246
|
-
close();
|
|
247
|
-
},
|
|
248
|
-
children: content,
|
|
249
|
-
});
|
|
250
|
-
};
|
|
251
|
-
const removeFile = async (name: string) => {
|
|
252
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/remove', {
|
|
253
|
-
resource: resourcesFullPath + name,
|
|
254
|
-
});
|
|
255
|
-
const dataResponse = await response.json;
|
|
256
|
-
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
257
|
-
NotificationMessage.sendMessage(dataResponse.message || 'Failed to remove resource', NotificationColor.Error);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
NotificationMessage.sendMessage(
|
|
261
|
-
'Resource removed successfully, you need to refresh the page',
|
|
262
|
-
NotificationColor.Success
|
|
263
|
-
);
|
|
264
|
-
};
|
|
265
|
-
const removeDir = async (name: string) => {
|
|
266
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/removeDir', {
|
|
267
|
-
resource: resourcesFullPath + name,
|
|
268
|
-
});
|
|
269
|
-
const dataResponse = await response.json;
|
|
270
|
-
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
271
|
-
NotificationMessage.sendMessage(dataResponse.message || 'Failed to remove resource', NotificationColor.Error);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
NotificationMessage.sendMessage(
|
|
275
|
-
'Resource removed successfully, you need to refresh the page',
|
|
276
|
-
NotificationColor.Success
|
|
277
|
-
);
|
|
278
|
-
};
|
|
279
|
-
const onRemove = async (name: string) => {
|
|
280
|
-
MessageBox.show({
|
|
281
|
-
title: 'Remove file',
|
|
282
|
-
buttonType: MessageBoxButtonProps.YesNo,
|
|
283
|
-
contentMinWidth: '300px',
|
|
284
|
-
handleClicked: (index: number, close) => {
|
|
285
|
-
if (index === 0) {
|
|
286
|
-
removeFile(name);
|
|
287
|
-
}
|
|
288
|
-
close();
|
|
289
|
-
},
|
|
290
|
-
children: <div>Do you really want to remove the remote file [{name}]?</div>,
|
|
291
|
-
});
|
|
292
|
-
};
|
|
293
|
-
const onRemoveDir = async (name: string) => {
|
|
294
|
-
MessageBox.show({
|
|
295
|
-
title: 'Remove directory',
|
|
296
|
-
buttonType: MessageBoxButtonProps.YesNo,
|
|
297
|
-
contentMinWidth: '300px',
|
|
298
|
-
handleClicked: (index: number, close) => {
|
|
299
|
-
if (index === 0) {
|
|
300
|
-
removeDir(name);
|
|
301
|
-
}
|
|
302
|
-
close();
|
|
303
|
-
},
|
|
304
|
-
children: <div>Do you really want to remove the remote directory [{name}]?</div>,
|
|
305
|
-
});
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/data', {
|
|
309
|
-
folder,
|
|
310
|
-
});
|
|
311
|
-
const dataResponse = await response.json;
|
|
312
|
-
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
313
|
-
NotificationMessage.sendMessage(dataResponse.message || 'Failed to get resources', NotificationColor.Error);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// dataResponse.results' first item has the fullPath
|
|
318
|
-
resourcesFullPath = dataResponse.results[0].fullPath;
|
|
319
|
-
if (!resourcesFullPath.endsWith('/') && !resourcesFullPath.endsWith('\\')) {
|
|
320
|
-
resourcesFullPath += '/';
|
|
321
|
-
}
|
|
322
|
-
domUpdate.value = (
|
|
323
|
-
<ResourcesList
|
|
324
|
-
results={dataResponse.results.slice(1)}
|
|
325
|
-
onDownload={onDownload}
|
|
326
|
-
onUploadLocal={onUploadLocal}
|
|
327
|
-
onNewFolder={onNewFolder}
|
|
328
|
-
onRename={onRename}
|
|
329
|
-
onRemove={onRemove}
|
|
330
|
-
onRemoveDir={onRemoveDir}
|
|
331
|
-
onListFolder={onListFolder}
|
|
332
|
-
resourcesFullPath={resourcesFullPath}
|
|
333
|
-
/>
|
|
334
|
-
);
|
|
335
|
-
domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
const progressUpdate: ProgressHookProps = {};
|
|
339
|
-
const onUploadProgress = (percentage: number, chunkNumber: number, totalChunks: number) => {
|
|
340
|
-
progressUpdate.onProgress?.(percentage, chunkNumber, totalChunks);
|
|
341
|
-
};
|
|
342
|
-
const onFileChange = async (e: Event) => {
|
|
343
|
-
const target = e.target as HTMLInputElement;
|
|
344
|
-
const file = target.files?.[0];
|
|
345
|
-
if (!file) return;
|
|
346
|
-
|
|
347
|
-
progressUpdate.onShow?.(true, 'Uploading resource');
|
|
348
|
-
progressUpdate.onProgress?.(0, 0, 100);
|
|
349
|
-
const uploadResult = await uploadFile(
|
|
350
|
-
file,
|
|
351
|
-
'/api/admin/resources/upload?n=' + file.name + '&p=' + resourcesFullPath,
|
|
352
|
-
onUploadProgress
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
setTimeout(() => {
|
|
356
|
-
progressUpdate.onShow?.(false);
|
|
357
|
-
}, 1000);
|
|
358
|
-
|
|
359
|
-
if (uploadResult !== true) {
|
|
360
|
-
NotificationMessage.sendMessage('Failed to upload resource', NotificationColor.Error);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
NotificationMessage.sendMessage(
|
|
364
|
-
'Resource uploaded successfully, you need to refresh the page',
|
|
365
|
-
NotificationColor.Success
|
|
366
|
-
);
|
|
367
|
-
};
|
|
368
|
-
const css: CssProps = {};
|
|
369
|
-
return (
|
|
370
|
-
<div css={css} class='admin-release-top'>
|
|
371
|
-
<div class='row-box mt1 mb1'>
|
|
372
|
-
<button onClick={onResources} class='button-base'>
|
|
373
|
-
Resources
|
|
374
|
-
</button>
|
|
375
|
-
</div>
|
|
376
|
-
{domUpdate.node}
|
|
377
|
-
{domLog.node}
|
|
378
|
-
<Progress hook={progressUpdate} />
|
|
379
|
-
<input type='file' class='d-none up-file' onChange={onFileChange} accept='.*' />
|
|
380
|
-
</div>
|
|
381
|
-
);
|
|
382
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
CssProps,
|
|
3
|
+
getRenderPageProps,
|
|
4
|
+
DomUtils,
|
|
5
|
+
formatBytes,
|
|
6
|
+
HtmlVar,
|
|
7
|
+
InputWithTitle,
|
|
8
|
+
MessageBox,
|
|
9
|
+
MessageBoxButtonProps,
|
|
10
|
+
NotificationColor,
|
|
11
|
+
NotificationMessage,
|
|
12
|
+
Progress,
|
|
13
|
+
ProgressHookProps,
|
|
14
|
+
uploadFile,
|
|
15
|
+
downloadStream,
|
|
16
|
+
} from 'lupine.components';
|
|
17
|
+
|
|
18
|
+
// https://www.toptal.com/designers/htmlarrows/symbols/
|
|
19
|
+
const ResourcesList = (props: {
|
|
20
|
+
results: any;
|
|
21
|
+
onDownload: (name: string) => void;
|
|
22
|
+
onUploadLocal: (name: string) => void;
|
|
23
|
+
onNewFolder: (name: string) => void;
|
|
24
|
+
onRename: (fPath: string, fName: string) => void;
|
|
25
|
+
onRemove: (name: string) => void;
|
|
26
|
+
onRemoveDir: (name: string) => void;
|
|
27
|
+
onListFolder: (folder: string) => void;
|
|
28
|
+
resourcesFullPath: string;
|
|
29
|
+
}) => {
|
|
30
|
+
const listFolder = (results: any, parentFolder = '') => {
|
|
31
|
+
return results.map((result: any) => (
|
|
32
|
+
<div>
|
|
33
|
+
<div class={`row-box mt-m`}>
|
|
34
|
+
<label class='label mr-m f-name' onClick={() => props.onListFolder(parentFolder + result.name)}>
|
|
35
|
+
{result.name}
|
|
36
|
+
</label>
|
|
37
|
+
<label class='label mr-m f-time'>{result.time}</label>
|
|
38
|
+
<label class='label mr-m f-size'>{typeof result.size !== 'undefined' && formatBytes(result.size)}</label>
|
|
39
|
+
{typeof result.size !== 'undefined' && (
|
|
40
|
+
<div>
|
|
41
|
+
<button
|
|
42
|
+
title='Download remote file'
|
|
43
|
+
onClick={() => props.onDownload(parentFolder + result.name)}
|
|
44
|
+
class='button-base button-s'
|
|
45
|
+
>
|
|
46
|
+
⇩
|
|
47
|
+
</button>
|
|
48
|
+
<button
|
|
49
|
+
title='Rename remote file'
|
|
50
|
+
onClick={() => props.onRename(parentFolder + result.name, result.name)}
|
|
51
|
+
class='button-base button-s'
|
|
52
|
+
>
|
|
53
|
+
⇎
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
title='Delete remote file'
|
|
57
|
+
onClick={() => props.onRemove(parentFolder + result.name)}
|
|
58
|
+
class='button-base button-s color-red'
|
|
59
|
+
>
|
|
60
|
+
✘
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
{typeof result.size === 'undefined' && (
|
|
65
|
+
<div>
|
|
66
|
+
<button onClick={() => props.onListFolder(parentFolder + result.name)} class='button-base button-s'>
|
|
67
|
+
⇨
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
title='Choose a local file to upload to remote'
|
|
71
|
+
onClick={() => props.onUploadLocal(parentFolder + result.name)}
|
|
72
|
+
class='button-base button-s mr-m'
|
|
73
|
+
>
|
|
74
|
+
⇮
|
|
75
|
+
</button>
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
title='Rename remote file'
|
|
79
|
+
onClick={() => props.onRename(parentFolder + result.name, result.name)}
|
|
80
|
+
class='button-base button-s'
|
|
81
|
+
>
|
|
82
|
+
⇎
|
|
83
|
+
</button>
|
|
84
|
+
<button
|
|
85
|
+
title='Delete remote file'
|
|
86
|
+
onClick={() => props.onRemoveDir(parentFolder + result.name)}
|
|
87
|
+
class='button-base button-s color-red'
|
|
88
|
+
>
|
|
89
|
+
✘
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
{result.items && <div class='pl-ll'>{listFolder(result.items, result.name + '/')}</div>}
|
|
95
|
+
</div>
|
|
96
|
+
));
|
|
97
|
+
};
|
|
98
|
+
const css: CssProps = {
|
|
99
|
+
'.f-name': {
|
|
100
|
+
textDecoration: 'underline',
|
|
101
|
+
width: '200px',
|
|
102
|
+
cursor: 'pointer',
|
|
103
|
+
},
|
|
104
|
+
'.f-size span, .f-time span': {
|
|
105
|
+
color: 'red',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
return (
|
|
109
|
+
<div css={css}>
|
|
110
|
+
<div class='row-box mt-m mr-s'>
|
|
111
|
+
<button title='Go up' onClick={() => props.onListFolder('..')} class='button-base button-s mr-m'>
|
|
112
|
+
⇦
|
|
113
|
+
</button>
|
|
114
|
+
<button title='Refresh' onClick={() => props.onListFolder('')} class='button-base button-s'>
|
|
115
|
+
⇳
|
|
116
|
+
</button>
|
|
117
|
+
<button
|
|
118
|
+
title='Choose a local file to upload to remote'
|
|
119
|
+
onClick={() => props.onUploadLocal('')}
|
|
120
|
+
class='button-base button-s mr-m'
|
|
121
|
+
>
|
|
122
|
+
⇮
|
|
123
|
+
</button>
|
|
124
|
+
|
|
125
|
+
<button title='Create a sub folder' onClick={() => props.onNewFolder('')} class='button-base button-s'>
|
|
126
|
+
⚒
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
<div class='label mx-m f-name'>{props.resourcesFullPath}</div>
|
|
130
|
+
{listFolder(props.results)}
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
export const AdminResourcesPage = () => {
|
|
135
|
+
const domLog = new HtmlVar('');
|
|
136
|
+
const domUpdate = new HtmlVar('');
|
|
137
|
+
|
|
138
|
+
let resourcesFullPath = '';
|
|
139
|
+
const onResources = async () => {
|
|
140
|
+
loasResources('/');
|
|
141
|
+
};
|
|
142
|
+
const loasResources = async (folder: string) => {
|
|
143
|
+
const onListFolder = (folder: string) => {
|
|
144
|
+
loasResources(resourcesFullPath + folder);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
let pathForUpload = '';
|
|
148
|
+
const uploadFile = async (fPath: string) => {
|
|
149
|
+
pathForUpload = fPath;
|
|
150
|
+
const fDom = DomUtils.bySelector('.up-file') as HTMLInputElement;
|
|
151
|
+
fDom.click();
|
|
152
|
+
};
|
|
153
|
+
const onUploadLocal = (fPath: string) => {
|
|
154
|
+
// MessageBox.show({
|
|
155
|
+
// title: 'Override remote file',
|
|
156
|
+
// buttonType: MessageBoxButtonProps.YesNo,
|
|
157
|
+
// contentMinWidth: '300px',
|
|
158
|
+
// handleClicked: (index: number, close) => {
|
|
159
|
+
// if (index === 0) {
|
|
160
|
+
uploadFile(fPath);
|
|
161
|
+
// }
|
|
162
|
+
// close();
|
|
163
|
+
// },
|
|
164
|
+
// children: <div>Do you upload local files that may overwrite remote file [{fPath}]?</div>,
|
|
165
|
+
// });
|
|
166
|
+
};
|
|
167
|
+
const onNewFolder = async (fPath: string) => {
|
|
168
|
+
let newName = '';
|
|
169
|
+
const content = InputWithTitle('New folder name:', '', (value: string) => {
|
|
170
|
+
newName = value;
|
|
171
|
+
});
|
|
172
|
+
MessageBox.show({
|
|
173
|
+
title: 'Input new folder name',
|
|
174
|
+
buttonType: MessageBoxButtonProps.OkCancel,
|
|
175
|
+
contentMinWidth: '300px',
|
|
176
|
+
handleClicked: async (index: number, close) => {
|
|
177
|
+
if (index === 0) {
|
|
178
|
+
await newFolder(fPath, newName);
|
|
179
|
+
}
|
|
180
|
+
close();
|
|
181
|
+
},
|
|
182
|
+
children: content,
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const onDownload = async (name: string) => {
|
|
187
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData(
|
|
188
|
+
'/api/admin/resources/download',
|
|
189
|
+
{
|
|
190
|
+
resource: resourcesFullPath + name,
|
|
191
|
+
},
|
|
192
|
+
true
|
|
193
|
+
);
|
|
194
|
+
if (!response || !response.blob) {
|
|
195
|
+
NotificationMessage.sendMessage('Failed to get resource', NotificationColor.Error);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
downloadStream(await response.blob(), name);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const newFolder = async (fPath: string, fName: string) => {
|
|
203
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/newFolder', {
|
|
204
|
+
resource: resourcesFullPath + fPath,
|
|
205
|
+
newName: fName,
|
|
206
|
+
});
|
|
207
|
+
const dataResponse = await response.json;
|
|
208
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
209
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to create a folder', NotificationColor.Error);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
NotificationMessage.sendMessage(
|
|
213
|
+
'Folder created successfully, you need to refresh the page',
|
|
214
|
+
NotificationColor.Success
|
|
215
|
+
);
|
|
216
|
+
};
|
|
217
|
+
const renameFile = async (fPath: string, fName: string, fNewName: string) => {
|
|
218
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/rename', {
|
|
219
|
+
resource: resourcesFullPath + fPath,
|
|
220
|
+
oldName: fName,
|
|
221
|
+
newName: fNewName,
|
|
222
|
+
});
|
|
223
|
+
const dataResponse = await response.json;
|
|
224
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
225
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to rename resource', NotificationColor.Error);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
NotificationMessage.sendMessage(
|
|
229
|
+
'Resource renamed successfully, you need to refresh the page',
|
|
230
|
+
NotificationColor.Success
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
const onRename = async (fPath: string, fName: string) => {
|
|
234
|
+
let newName = '';
|
|
235
|
+
const content = InputWithTitle('Reanme to:', fName, (value: string) => {
|
|
236
|
+
newName = value;
|
|
237
|
+
});
|
|
238
|
+
MessageBox.show({
|
|
239
|
+
title: 'Input new name',
|
|
240
|
+
buttonType: MessageBoxButtonProps.OkCancel,
|
|
241
|
+
contentMinWidth: '300px',
|
|
242
|
+
handleClicked: async (index: number, close) => {
|
|
243
|
+
if (index === 0) {
|
|
244
|
+
await renameFile(fPath, fName, newName);
|
|
245
|
+
}
|
|
246
|
+
close();
|
|
247
|
+
},
|
|
248
|
+
children: content,
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
const removeFile = async (name: string) => {
|
|
252
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/remove', {
|
|
253
|
+
resource: resourcesFullPath + name,
|
|
254
|
+
});
|
|
255
|
+
const dataResponse = await response.json;
|
|
256
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
257
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to remove resource', NotificationColor.Error);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
NotificationMessage.sendMessage(
|
|
261
|
+
'Resource removed successfully, you need to refresh the page',
|
|
262
|
+
NotificationColor.Success
|
|
263
|
+
);
|
|
264
|
+
};
|
|
265
|
+
const removeDir = async (name: string) => {
|
|
266
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/removeDir', {
|
|
267
|
+
resource: resourcesFullPath + name,
|
|
268
|
+
});
|
|
269
|
+
const dataResponse = await response.json;
|
|
270
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
271
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to remove resource', NotificationColor.Error);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
NotificationMessage.sendMessage(
|
|
275
|
+
'Resource removed successfully, you need to refresh the page',
|
|
276
|
+
NotificationColor.Success
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
const onRemove = async (name: string) => {
|
|
280
|
+
MessageBox.show({
|
|
281
|
+
title: 'Remove file',
|
|
282
|
+
buttonType: MessageBoxButtonProps.YesNo,
|
|
283
|
+
contentMinWidth: '300px',
|
|
284
|
+
handleClicked: (index: number, close) => {
|
|
285
|
+
if (index === 0) {
|
|
286
|
+
removeFile(name);
|
|
287
|
+
}
|
|
288
|
+
close();
|
|
289
|
+
},
|
|
290
|
+
children: <div>Do you really want to remove the remote file [{name}]?</div>,
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
const onRemoveDir = async (name: string) => {
|
|
294
|
+
MessageBox.show({
|
|
295
|
+
title: 'Remove directory',
|
|
296
|
+
buttonType: MessageBoxButtonProps.YesNo,
|
|
297
|
+
contentMinWidth: '300px',
|
|
298
|
+
handleClicked: (index: number, close) => {
|
|
299
|
+
if (index === 0) {
|
|
300
|
+
removeDir(name);
|
|
301
|
+
}
|
|
302
|
+
close();
|
|
303
|
+
},
|
|
304
|
+
children: <div>Do you really want to remove the remote directory [{name}]?</div>,
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/resources/data', {
|
|
309
|
+
folder,
|
|
310
|
+
});
|
|
311
|
+
const dataResponse = await response.json;
|
|
312
|
+
if (!dataResponse || dataResponse.status !== 'ok') {
|
|
313
|
+
NotificationMessage.sendMessage(dataResponse.message || 'Failed to get resources', NotificationColor.Error);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// dataResponse.results' first item has the fullPath
|
|
318
|
+
resourcesFullPath = dataResponse.results[0].fullPath;
|
|
319
|
+
if (!resourcesFullPath.endsWith('/') && !resourcesFullPath.endsWith('\\')) {
|
|
320
|
+
resourcesFullPath += '/';
|
|
321
|
+
}
|
|
322
|
+
domUpdate.value = (
|
|
323
|
+
<ResourcesList
|
|
324
|
+
results={dataResponse.results.slice(1)}
|
|
325
|
+
onDownload={onDownload}
|
|
326
|
+
onUploadLocal={onUploadLocal}
|
|
327
|
+
onNewFolder={onNewFolder}
|
|
328
|
+
onRename={onRename}
|
|
329
|
+
onRemove={onRemove}
|
|
330
|
+
onRemoveDir={onRemoveDir}
|
|
331
|
+
onListFolder={onListFolder}
|
|
332
|
+
resourcesFullPath={resourcesFullPath}
|
|
333
|
+
/>
|
|
334
|
+
);
|
|
335
|
+
domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const progressUpdate: ProgressHookProps = {};
|
|
339
|
+
const onUploadProgress = (percentage: number, chunkNumber: number, totalChunks: number) => {
|
|
340
|
+
progressUpdate.onProgress?.(percentage, chunkNumber, totalChunks);
|
|
341
|
+
};
|
|
342
|
+
const onFileChange = async (e: Event) => {
|
|
343
|
+
const target = e.target as HTMLInputElement;
|
|
344
|
+
const file = target.files?.[0];
|
|
345
|
+
if (!file) return;
|
|
346
|
+
|
|
347
|
+
progressUpdate.onShow?.(true, 'Uploading resource');
|
|
348
|
+
progressUpdate.onProgress?.(0, 0, 100);
|
|
349
|
+
const uploadResult = await uploadFile(
|
|
350
|
+
file,
|
|
351
|
+
'/api/admin/resources/upload?n=' + file.name + '&p=' + resourcesFullPath,
|
|
352
|
+
onUploadProgress
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
progressUpdate.onShow?.(false);
|
|
357
|
+
}, 1000);
|
|
358
|
+
|
|
359
|
+
if (uploadResult !== true) {
|
|
360
|
+
NotificationMessage.sendMessage('Failed to upload resource', NotificationColor.Error);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
NotificationMessage.sendMessage(
|
|
364
|
+
'Resource uploaded successfully, you need to refresh the page',
|
|
365
|
+
NotificationColor.Success
|
|
366
|
+
);
|
|
367
|
+
};
|
|
368
|
+
const css: CssProps = {};
|
|
369
|
+
return (
|
|
370
|
+
<div css={css} class='admin-release-top'>
|
|
371
|
+
<div class='row-box mt1 mb1'>
|
|
372
|
+
<button onClick={onResources} class='button-base'>
|
|
373
|
+
Resources
|
|
374
|
+
</button>
|
|
375
|
+
</div>
|
|
376
|
+
{domUpdate.node}
|
|
377
|
+
{domLog.node}
|
|
378
|
+
<Progress hook={progressUpdate} />
|
|
379
|
+
<input type='file' class='d-none up-file' onChange={onFileChange} accept='.*' />
|
|
380
|
+
</div>
|
|
381
|
+
);
|
|
382
|
+
};
|