lupine.api 1.0.41

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