lupine.api 1.1.58 → 1.1.60

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/README.md +3 -3
  2. package/admin/admin-about.tsx +12 -16
  3. package/admin/admin-config.tsx +47 -44
  4. package/admin/admin-css.tsx +3 -3
  5. package/admin/admin-db.tsx +75 -75
  6. package/admin/admin-frame-helper.tsx +364 -364
  7. package/admin/admin-frame.tsx +164 -164
  8. package/admin/admin-index.tsx +65 -65
  9. package/admin/admin-login.tsx +111 -111
  10. package/admin/admin-menu-edit.tsx +637 -637
  11. package/admin/admin-menu-list.tsx +87 -87
  12. package/admin/admin-page-edit.tsx +564 -564
  13. package/admin/admin-page-list.tsx +83 -83
  14. package/admin/admin-performance.tsx +28 -28
  15. package/admin/admin-release.tsx +427 -426
  16. package/admin/admin-resources.tsx +382 -382
  17. package/admin/admin-shell.tsx +89 -89
  18. package/admin/admin-table-data.tsx +146 -146
  19. package/admin/admin-table-list.tsx +230 -230
  20. package/admin/admin-test-animations.tsx +395 -395
  21. package/admin/admin-test-component.tsx +823 -808
  22. package/admin/admin-test-edit.tsx +319 -319
  23. package/admin/admin-test-themes.tsx +56 -56
  24. package/admin/admin-tokens.tsx +338 -338
  25. package/admin/design/admin-design.tsx +174 -174
  26. package/admin/design/block-grid.tsx +36 -36
  27. package/admin/design/block-grid1.tsx +21 -21
  28. package/admin/design/block-paragraph.tsx +19 -19
  29. package/admin/design/block-title.tsx +19 -19
  30. package/admin/design/design-block-box.tsx +140 -140
  31. package/admin/design/drag-data.tsx +24 -24
  32. package/admin/index.ts +9 -9
  33. package/admin/package.json +15 -15
  34. package/admin/tsconfig.json +127 -127
  35. package/dev/copy-folder.js +32 -32
  36. package/dev/cp-index-html.js +69 -69
  37. package/dev/file-utils.js +12 -12
  38. package/dev/index.js +18 -19
  39. package/dev/package.json +12 -12
  40. package/dev/plugin-ifelse.js +168 -168
  41. package/dev/plugin-ifelse.test.js +37 -37
  42. package/dev/run-cmd.js +14 -14
  43. package/dev/send-request.js +12 -12
  44. package/package.json +55 -55
  45. package/src/admin-api/admin-api-helper.ts +210 -205
  46. package/src/admin-api/admin-api.ts +65 -65
  47. package/src/admin-api/admin-auth.ts +152 -146
  48. package/src/admin-api/admin-config.ts +94 -84
  49. package/src/admin-api/admin-csv.ts +94 -94
  50. package/src/admin-api/admin-db.ts +269 -269
  51. package/src/admin-api/admin-menu.ts +135 -135
  52. package/src/admin-api/admin-page.ts +135 -135
  53. package/src/admin-api/admin-performance.ts +128 -128
  54. package/src/admin-api/admin-release.ts +706 -700
  55. package/src/admin-api/admin-resources.ts +318 -318
  56. package/src/admin-api/admin-token-helper.ts +82 -79
  57. package/src/admin-api/admin-tokens.ts +90 -90
  58. package/src/admin-api/index.ts +2 -2
  59. package/src/admin-api/web-config-api.ts +19 -19
  60. package/src/api/api-cache.ts +103 -103
  61. package/src/api/api-helper.ts +44 -44
  62. package/src/api/api-module.ts +67 -60
  63. package/src/api/api-router.ts +177 -177
  64. package/src/api/api-shared-storage.ts +64 -64
  65. package/src/api/async-storage.ts +5 -5
  66. package/src/api/debug-service.ts +56 -56
  67. package/src/api/encode-html.ts +27 -27
  68. package/src/api/handle-status.ts +75 -75
  69. package/src/api/index.ts +15 -16
  70. package/src/api/mini-web-socket.ts +270 -270
  71. package/src/api/server-content-type.ts +82 -82
  72. package/src/api/server-render.ts +235 -215
  73. package/src/api/shell-service.ts +74 -74
  74. package/src/api/simple-storage.ts +80 -80
  75. package/src/api/static-server.ts +128 -125
  76. package/src/api/to-client-delivery.ts +26 -26
  77. package/src/app/app-cache.ts +55 -55
  78. package/src/app/app-helper.ts +62 -62
  79. package/src/app/app-message.ts +109 -109
  80. package/src/app/app-shared-storage.ts +363 -363
  81. package/src/app/app-start.ts +136 -136
  82. package/src/app/cleanup-exit.ts +16 -16
  83. package/src/app/host-to-path.ts +38 -38
  84. package/src/app/index.ts +11 -11
  85. package/src/app/process-dev-requests.ts +130 -130
  86. package/src/app/web-listener.ts +294 -294
  87. package/src/app/web-processor.ts +47 -42
  88. package/src/app/web-server.ts +100 -100
  89. package/src/common-js/web-env.js +104 -104
  90. package/src/index.ts +7 -7
  91. package/src/lang/api-lang-en.ts +26 -26
  92. package/src/lang/api-lang-zh-cn.ts +27 -27
  93. package/src/lang/index.ts +2 -2
  94. package/src/lang/lang-helper.ts +76 -76
  95. package/src/lang/lang-props.ts +6 -6
  96. package/src/lib/db/db-helper.ts +23 -23
  97. package/src/lib/db/db-mysql.ts +249 -250
  98. package/src/lib/db/db-sqlite.ts +101 -101
  99. package/src/lib/db/db.spec.ts +28 -28
  100. package/src/lib/db/db.ts +325 -325
  101. package/src/lib/db/index.ts +5 -5
  102. package/src/lib/index.ts +3 -3
  103. package/src/lib/logger.spec.ts +214 -214
  104. package/src/lib/logger.ts +281 -281
  105. package/src/lib/runtime-require.ts +37 -37
  106. package/src/lib/utils/cookie-util.ts +34 -34
  107. package/src/lib/utils/crypto.ts +58 -58
  108. package/src/lib/utils/date-utils.ts +317 -317
  109. package/src/lib/utils/deep-merge.ts +37 -37
  110. package/src/lib/utils/delay.ts +12 -12
  111. package/src/lib/utils/file-setting.ts +55 -55
  112. package/src/lib/utils/format-bytes.ts +11 -11
  113. package/src/lib/utils/fs-utils.ts +158 -158
  114. package/src/lib/utils/get-env.ts +27 -27
  115. package/src/lib/utils/index.ts +12 -12
  116. package/src/lib/utils/is-type.ts +48 -48
  117. package/src/lib/utils/load-env.ts +14 -14
  118. package/src/lib/utils/pad.ts +6 -6
  119. package/src/models/api-base.ts +5 -5
  120. package/src/models/api-module-props.ts +10 -11
  121. package/src/models/api-router-props.ts +26 -26
  122. package/src/models/app-cache-props.ts +33 -33
  123. package/src/models/app-data-props.ts +10 -10
  124. package/src/models/app-helper-props.ts +6 -6
  125. package/src/models/app-shared-storage-props.ts +38 -38
  126. package/src/models/app-start-props.ts +18 -18
  127. package/src/models/async-storage-props.ts +13 -13
  128. package/src/models/db-config.ts +30 -30
  129. package/src/models/host-to-path-props.ts +12 -12
  130. package/src/models/index.ts +16 -16
  131. package/src/models/json-object.ts +8 -8
  132. package/src/models/locals-props.ts +36 -36
  133. package/src/models/logger-props.ts +84 -84
  134. package/src/models/simple-storage-props.ts +13 -14
  135. package/src/models/to-client-delivery-props.ts +6 -6
  136. package/tsconfig.json +115 -115
  137. package/dev/plugin-gen-versions.js +0 -20
@@ -1,426 +1,427 @@
1
- import {
2
- CssProps,
3
- getRenderPageProps,
4
- RefProps,
5
- DomUtils,
6
- HtmlVar,
7
- NotificationColor,
8
- NotificationMessage,
9
- formatBytes,
10
- downloadStream,
11
- ActionSheetSelectPromise,
12
- encodeHtml,
13
- } from 'lupine.components';
14
-
15
- interface ReleaseListProps {
16
- result: any;
17
- onUpdate: () => void;
18
- onLogClick: (logName: string) => Promise<void>;
19
- }
20
- const ReleaseList = (props: ReleaseListProps) => {
21
- const ref: RefProps = {
22
- onLoad: async () => {
23
- const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
24
- DomUtils.setValue('.from-list', data.fromList || '');
25
- },
26
- };
27
- return (
28
- <div ref={ref}>
29
- <div class='row-box mt-m'>
30
- <label class='label mr-m release-label'>From:</label>
31
- <div class='w-50p'>
32
- <select type='text' class='input-base w-100p from-list'>
33
- {props.result.appsFrom.map((app: string) => (
34
- <option key={app} value={app}>
35
- {app}
36
- </option>
37
- ))}
38
- </select>
39
- </div>
40
- </div>
41
- <div class='row-box mt-m'>
42
- <label class='label mr-m release-label'>To:</label>
43
- <div class='w-50p'>
44
- <select type='text' class='input-base w-100p to-list'>
45
- {props.result.apps.map((app: string) => (
46
- <option key={app} value={app}>
47
- {app}
48
- </option>
49
- ))}
50
- </select>
51
- </div>
52
- </div>
53
- <div class='row-box mt-m'>
54
- <label class='label mr-m release-label'>Release:</label>
55
- <label class='label mr-m' for='chk-server'>
56
- Server:
57
- </label>
58
- <div class='mr-l'>
59
- <input type='checkbox' class='base-css chk-server' id='chk-server' />
60
- </div>
61
- <label class='label mr-m' for='chk-api'>
62
- Api:
63
- </label>
64
- <div class='mr-l'>
65
- <input type='checkbox' class='base-css chk-api' id='chk-api' />
66
- </div>
67
- <label class='label mr-m' for='chk-web'>
68
- Web:
69
- </label>
70
- <div class='mr-l'>
71
- <input type='checkbox' class='base-css chk-web' id='chk-web' />
72
- </div>
73
-
74
- <label class='label mr-m' for='chk-env'>
75
- Env:
76
- </label>
77
- <div class='mr-l'>
78
- <input type='checkbox' class='base-css chk-env' id='chk-env' />
79
- </div>
80
- <label class='label mr-m' for='chk-backup'>
81
- ( Backup:
82
- </label>
83
- <div class=''>
84
- <input type='checkbox' class='base-css chk-backup' id='chk-backup' /> )
85
- </div>
86
- </div>
87
- <div class='row-box mt-m'>
88
- <label class='label mr-m release-label'>Web Sub-folder:</label>
89
- <div class='w-50p mr-l'>
90
- {/* <input type='text' class='input-base w-100p input-web-sub' placeholder='The Sub-folder you want to update' /> */}
91
-
92
- {props.result.webSub.map((folder: string) => (
93
- <div>
94
- <label>
95
- <input type='checkbox' class={'chk-web-sub input-' + folder} value={folder} /> {folder}
96
- </label>
97
- </div>
98
- ))}
99
- <label class='label mr-m release-label'>(Skip *.js.map files)</label>
100
- </div>
101
- </div>
102
- <LogList logs={props.result.logs} onLogClick={props.onLogClick} />
103
- <div class='row-box mt-m'>
104
- <button onClick={props.onUpdate} class='button-base release-update-btn'>
105
- Update
106
- </button>
107
- </div>
108
- </div>
109
- );
110
- };
111
-
112
- const LogList = (props: {
113
- logs: { name: string; size: number; time: string }[];
114
- onLogClick: (logName: string) => Promise<void>;
115
- }) => {
116
- return (
117
- <div>
118
- <div class='row-box mt-m'>
119
- <label class='label mr-m release-label'>Logs:</label>
120
- <div type='text'>
121
- {props.logs &&
122
- props.logs.map((log: { name: string; size: number; time: string }) => (
123
- <div>
124
- <label class='release-log' onClick={() => props.onLogClick(log.name)}>{`${log.name}`}</label> (
125
- {log.time}; {formatBytes(log.size)}){' '}
126
- </div>
127
- ))}
128
- </div>
129
- </div>
130
- </div>
131
- );
132
- };
133
-
134
- export const AdminReleasePage = () => {
135
- const fetchData = async (options: { targetUrl: string; accessToken: string; log?: boolean }) => {
136
- const data = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/check', options);
137
- console.log('release/check', data);
138
- return data.json;
139
- };
140
- const css: CssProps = {
141
- '.release-label': {
142
- width: '130px',
143
- },
144
- '.release-log': {
145
- cursor: 'pointer',
146
- textDecoration: 'underline',
147
- },
148
- };
149
- const domLog = new HtmlVar('');
150
- const domUpdate = new HtmlVar('');
151
- const getDomData = () => {
152
- const domFromList = ref.$('.from-list');
153
- let fromValue = '';
154
- if (!domFromList) {
155
- const dataOld = JSON.parse(localStorage.getItem('admin-release') || '{}');
156
- fromValue = dataOld.fromList;
157
- } else {
158
- fromValue = domFromList.value;
159
- }
160
- const data = {
161
- targetUrl: ref.$('.target-url').value,
162
- accessToken: ref.$('.access-token').value,
163
- fromList: fromValue,
164
- };
165
- localStorage.setItem('admin-release', JSON.stringify(data));
166
- return data;
167
- };
168
-
169
- const onUpdate = async () => {
170
- const data = getDomData();
171
- if (!data.targetUrl || !data.accessToken) {
172
- NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
173
- return;
174
- }
175
-
176
- const fromList = ref.$('.from-list').value;
177
- const toList = ref.$('.to-list').value;
178
- const chkServer = ref.$('.chk-server').checked;
179
- const chkApi = ref.$('.chk-api').checked;
180
- const chkWeb = ref.$('.chk-web').checked;
181
- // const webSub = ref.$('.input-web-sub').value;
182
- const webSubs = ref.$all('.chk-web-sub') as HTMLInputElement[];
183
- const webSubsChecked = Array.from(webSubs)
184
- .filter((input) => input.checked)
185
- .map((input) => input.value);
186
- const wrongWebSubs = webSubsChecked.filter((s) => !s.startsWith(fromList + '_web/'));
187
- if (wrongWebSubs.length > 0) {
188
- NotificationMessage.sendMessage(`Some web sub folder is not under ${fromList}`, NotificationColor.Error);
189
- return;
190
- }
191
- const chkEnv = ref.$('.chk-env').checked;
192
- const chkBackup = ref.$('.chk-backup').checked;
193
- if (!chkServer && !chkApi && !chkWeb && !chkEnv) {
194
- NotificationMessage.sendMessage('Please select the release options', NotificationColor.Error);
195
- return;
196
- }
197
-
198
- if (fromList !== toList && !confirm('The From and To are not the same, are you sure?')) {
199
- return;
200
- }
201
- if (!confirm('Are you sure you want to update the release? (Assets are not copied, so it may cause issues)')) {
202
- return;
203
- }
204
- const releaseUpdateBtn = document.querySelector('.release-update-btn') as HTMLButtonElement;
205
- releaseUpdateBtn.disabled = true;
206
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/update', {
207
- ...data,
208
- fromList,
209
- toList,
210
- chkServer,
211
- chkApi,
212
- chkWeb,
213
- // webSub, // will be deprecated
214
- webSubs: webSubsChecked,
215
- chkEnv,
216
- chkBackup,
217
- });
218
- const dataResponse = await response.json;
219
- console.log('release/update', dataResponse);
220
- releaseUpdateBtn.disabled = false;
221
- if (!dataResponse || dataResponse.status !== 'ok') {
222
- NotificationMessage.sendMessage(
223
- dataResponse.message || 'Failed to update release (timeout, possibly backend is runing, please wait and click Check!)',
224
- NotificationColor.Error
225
- );
226
- return;
227
- }
228
- NotificationMessage.sendMessage('Release updated successfully', NotificationColor.Success);
229
- };
230
-
231
- const onLogClick = async (logName: string) => {
232
- const data = getDomData();
233
- if (!data.targetUrl || !data.accessToken) {
234
- NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
235
- return;
236
- }
237
-
238
- const responseText = await getRenderPageProps().renderPageFunctions.fetchData(
239
- '/api/admin/release/view-log',
240
- {
241
- ...data,
242
- logName,
243
- },
244
- true
245
- );
246
- const blob = await responseText.blob();
247
- downloadStream(blob, logName);
248
- };
249
- const onCheck = async () => {
250
- const data = getDomData();
251
- if (!data.targetUrl || !data.accessToken) {
252
- NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
253
- return;
254
- }
255
- const result = await fetchData(data);
256
- if (!result || result.status !== 'ok') {
257
- NotificationMessage.sendMessage(result.message || 'Failed to get release list', NotificationColor.Error);
258
- return;
259
- }
260
- console.log(result);
261
-
262
- domUpdate.value = <ReleaseList result={result} onUpdate={onUpdate} onLogClick={onLogClick} />;
263
- domLog.value = <pre>{JSON.stringify(result, null, 2)}</pre>;
264
- if (result.releaseProgress) {
265
- NotificationMessage.sendMessage('Release progress: ' + result.releaseProgress, NotificationColor.Warning);
266
- }
267
- };
268
-
269
- const onRefreshCacheLocal = async () => {
270
- return onRefreshCache(true);
271
- };
272
- const onRefreshCacheRemote = async () => {
273
- return onRefreshCache(false);
274
- };
275
- const onRefreshCache = async (isLocal?: boolean) => {
276
- const data = getDomData();
277
- if (!isLocal) {
278
- if (!data.targetUrl || !data.accessToken) {
279
- NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
280
- return;
281
- }
282
- }
283
-
284
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/refresh-cache', {
285
- ...data,
286
- isLocal,
287
- });
288
- const dataResponse = await response.json;
289
- console.log('refresh-cache', dataResponse);
290
- if (!dataResponse || dataResponse.status !== 'ok') {
291
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to refresh cache', NotificationColor.Error);
292
- return;
293
- }
294
- domLog.value = <pre>{encodeHtml(JSON.stringify(dataResponse, null, 2))}</pre>;
295
- NotificationMessage.sendMessage('Cache refreshed successfully', NotificationColor.Success);
296
- };
297
-
298
- const onRestartAppLocal = async () => {
299
- return onRestartApp(true);
300
- };
301
- const onRestartAppRemote = async () => {
302
- return onRestartApp(false);
303
- };
304
- const onRestartApp = async (isLocal?: boolean) => {
305
- const data = getDomData();
306
- if (!isLocal) {
307
- if (!data.targetUrl || !data.accessToken) {
308
- NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
309
- return;
310
- }
311
- }
312
-
313
- const index = await ActionSheetSelectPromise({
314
- title: 'Restart App (users may get disconnected errors) ?',
315
- options: ['OK'],
316
- cancelButtonText: 'Cancel',
317
- });
318
- if (index !== 0) {
319
- return;
320
- }
321
-
322
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/restart-app', {
323
- ...data,
324
- isLocal,
325
- });
326
- const dataResponse = await response.json;
327
- console.log('restart-app', dataResponse);
328
- if (!dataResponse || dataResponse.status !== 'ok') {
329
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to Restart App', NotificationColor.Error);
330
- return;
331
- }
332
- domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
333
- NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
334
- };
335
-
336
- const onShellLocal = async () => {
337
- return onShell(true);
338
- };
339
- const onShellRemote = async () => {
340
- return onShell(false);
341
- };
342
- const onShell = async (isLocal?: boolean) => {
343
- const data = getDomData();
344
- if (!isLocal) {
345
- if (!data.targetUrl || !data.accessToken) {
346
- NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
347
- return;
348
- }
349
- }
350
-
351
- const index = await ActionSheetSelectPromise({
352
- title: 'Run Cmd ?',
353
- options: ['OK'],
354
- cancelButtonText: 'Cancel',
355
- });
356
- if (index !== 0) {
357
- return;
358
- }
359
-
360
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/shell', {
361
- ...data,
362
- isLocal,
363
- cmd: ref.$('.release-cmd').value,
364
- });
365
- const dataResponse = await response.json;
366
- console.log('shell', dataResponse);
367
- if (!dataResponse || dataResponse.status !== 'ok') {
368
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to run cmd', NotificationColor.Error);
369
- return;
370
- }
371
- domLog.value = <pre>{encodeHtml(dataResponse.message) + '\r\n<br>' + encodeHtml(dataResponse.result)}</pre>;
372
- NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
373
- };
374
-
375
- const ref: RefProps = {
376
- onLoad: async () => {
377
- const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
378
- DomUtils.setValue('.target-url', data.targetUrl || '');
379
- DomUtils.setValue('.access-token', data.accessToken || '');
380
- },
381
- };
382
- return (
383
- <div ref={ref} css={css} class='admin-release-top'>
384
- <div class='row-box mt-m'>
385
- <label class='label mr-m release-label'>Target Url:</label>
386
- <div class='w-50p'>
387
- <input type='text' class='input-base w-100p target-url' placeholder='Target Url' />
388
- </div>
389
- </div>
390
- <div class='row-box mt-m'>
391
- <label class='label mr-m release-label'>Access token:</label>
392
- <div class='w-50p'>
393
- <input type='text' class='input-base w-100p access-token' placeholder='Access token' />
394
- </div>
395
- </div>
396
- <div class='row-box mt-m'>
397
- <button onClick={onCheck} class='button-base mr-m'>
398
- Check
399
- </button>
400
- <button onClick={onRefreshCacheRemote} class='button-base mr-m'>
401
- Refresh Cache (Remote)
402
- </button>
403
- <button onClick={onRestartAppRemote} class='button-base mr-m color-red'>
404
- Restart App (Remote)
405
- </button>
406
- <button onClick={onRefreshCacheLocal} class='button-base mr-m'>
407
- Refresh Cache (Local)
408
- </button>
409
- <button onClick={onRestartAppLocal} class='button-base color-red'>
410
- Restart App (Local)
411
- </button>
412
- </div>
413
- <div class='row-box mt-m mb-m'>
414
- <input type='text' class='input-base w-50p release-cmd mr-m' placeholder='Command' />
415
- <button onClick={onShellRemote} class='button-base color-red mr-m'>
416
- Run Cmd (Remote)
417
- </button>
418
- <button onClick={onShellLocal} class='button-base'>
419
- Run Cmd (Local)
420
- </button>
421
- </div>
422
- {domUpdate.node}
423
- {domLog.node}
424
- </div>
425
- );
426
- };
1
+ import {
2
+ CssProps,
3
+ getRenderPageProps,
4
+ RefProps,
5
+ DomUtils,
6
+ HtmlVar,
7
+ NotificationColor,
8
+ NotificationMessage,
9
+ formatBytes,
10
+ downloadStream,
11
+ ActionSheetSelectPromise,
12
+ encodeHtml,
13
+ } from 'lupine.components';
14
+
15
+ interface ReleaseListProps {
16
+ result: any;
17
+ onUpdate: () => void;
18
+ onLogClick: (logName: string) => Promise<void>;
19
+ }
20
+ const ReleaseList = (props: ReleaseListProps) => {
21
+ const ref: RefProps = {
22
+ onLoad: async () => {
23
+ const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
24
+ DomUtils.setValue('.from-list', data.fromList || '');
25
+ },
26
+ };
27
+ return (
28
+ <div ref={ref}>
29
+ <div class='row-box mt-m'>
30
+ <label class='label mr-m release-label'>From:</label>
31
+ <div class='w-50p'>
32
+ <select type='text' class='input-base w-100p from-list'>
33
+ {props.result.appsFrom.map((app: string) => (
34
+ <option key={app} value={app}>
35
+ {app}
36
+ </option>
37
+ ))}
38
+ </select>
39
+ </div>
40
+ </div>
41
+ <div class='row-box mt-m'>
42
+ <label class='label mr-m release-label'>To:</label>
43
+ <div class='w-50p'>
44
+ <select type='text' class='input-base w-100p to-list'>
45
+ {props.result.apps.map((app: string) => (
46
+ <option key={app} value={app}>
47
+ {app}
48
+ </option>
49
+ ))}
50
+ </select>
51
+ </div>
52
+ </div>
53
+ <div class='row-box mt-m'>
54
+ <label class='label mr-m release-label'>Release:</label>
55
+ <label class='label mr-m' for='chk-server'>
56
+ Server:
57
+ </label>
58
+ <div class='mr-l'>
59
+ <input type='checkbox' class='base-css chk-server' id='chk-server' />
60
+ </div>
61
+ <label class='label mr-m' for='chk-api'>
62
+ Api:
63
+ </label>
64
+ <div class='mr-l'>
65
+ <input type='checkbox' class='base-css chk-api' id='chk-api' />
66
+ </div>
67
+ {/* <label class='label mr-m' for='chk-web'>
68
+ Web:
69
+ </label>
70
+ <div class='mr-l'>
71
+ <input type='checkbox' class='base-css chk-web' id='chk-web' />
72
+ </div> */}
73
+
74
+ <label class='label mr-m' for='chk-env'>
75
+ Env:
76
+ </label>
77
+ <div class='mr-l'>
78
+ <input type='checkbox' class='base-css chk-env' id='chk-env' />
79
+ </div>
80
+ <label class='label mr-m' for='chk-backup'>
81
+ ( Backup:
82
+ </label>
83
+ <div class=''>
84
+ <input type='checkbox' class='base-css chk-backup' id='chk-backup' /> )
85
+ </div>
86
+ </div>
87
+ <div class='row-box mt-m'>
88
+ <label class='label mr-m release-label'>Web Sub-folder:</label>
89
+ <div class='w-50p mr-l'>
90
+ {/* <input type='text' class='input-base w-100p input-web-sub' placeholder='The Sub-folder you want to update' /> */}
91
+
92
+ {props.result.webSub.map((folder: string) => (
93
+ <div>
94
+ <label>
95
+ <input type='checkbox' class={'chk-web-sub input-' + folder} value={folder} /> {folder}
96
+ </label>
97
+ </div>
98
+ ))}
99
+ <label class='label mr-m release-label'>(Skip *.js.map, *.css.map files)</label>
100
+ </div>
101
+ </div>
102
+ <LogList logs={props.result.logs} onLogClick={props.onLogClick} />
103
+ <div class='row-box mt-m'>
104
+ <button onClick={props.onUpdate} class='button-base release-update-btn'>
105
+ Update
106
+ </button>
107
+ </div>
108
+ </div>
109
+ );
110
+ };
111
+
112
+ const LogList = (props: {
113
+ logs: { name: string; size: number; time: string }[];
114
+ onLogClick: (logName: string) => Promise<void>;
115
+ }) => {
116
+ return (
117
+ <div>
118
+ <div class='row-box mt-m'>
119
+ <label class='label mr-m release-label'>Logs:</label>
120
+ <div type='text'>
121
+ {props.logs &&
122
+ props.logs.map((log: { name: string; size: number; time: string }) => (
123
+ <div>
124
+ <label class='release-log' onClick={() => props.onLogClick(log.name)}>{`${log.name}`}</label> (
125
+ {log.time}; {formatBytes(log.size)}){' '}
126
+ </div>
127
+ ))}
128
+ </div>
129
+ </div>
130
+ </div>
131
+ );
132
+ };
133
+
134
+ export const AdminReleasePage = () => {
135
+ const fetchData = async (options: { targetUrl: string; accessToken: string; log?: boolean }) => {
136
+ const data = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/check', options);
137
+ console.log('release/check', data);
138
+ return data.json;
139
+ };
140
+ const css: CssProps = {
141
+ '.release-label': {
142
+ width: '130px',
143
+ },
144
+ '.release-log': {
145
+ cursor: 'pointer',
146
+ textDecoration: 'underline',
147
+ },
148
+ };
149
+ const domLog = new HtmlVar('');
150
+ const domUpdate = new HtmlVar('');
151
+ const getDomData = () => {
152
+ const domFromList = ref.$('.from-list');
153
+ let fromValue = '';
154
+ if (!domFromList) {
155
+ const dataOld = JSON.parse(localStorage.getItem('admin-release') || '{}');
156
+ fromValue = dataOld.fromList;
157
+ } else {
158
+ fromValue = domFromList.value;
159
+ }
160
+ const data = {
161
+ targetUrl: ref.$('.target-url').value,
162
+ accessToken: ref.$('.access-token').value,
163
+ fromList: fromValue,
164
+ };
165
+ localStorage.setItem('admin-release', JSON.stringify(data));
166
+ return data;
167
+ };
168
+
169
+ const onUpdate = async () => {
170
+ const data = getDomData();
171
+ if (!data.targetUrl || !data.accessToken) {
172
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
173
+ return;
174
+ }
175
+
176
+ const fromList = ref.$('.from-list').value;
177
+ const toList = ref.$('.to-list').value;
178
+ const chkServer = ref.$('.chk-server').checked;
179
+ const chkApi = ref.$('.chk-api').checked;
180
+ const chkWeb = true; //ref.$('.chk-web').checked;
181
+ // const webSub = ref.$('.input-web-sub').value;
182
+ const webSubs = ref.$all('.chk-web-sub') as HTMLInputElement[];
183
+ const webSubsChecked = Array.from(webSubs)
184
+ .filter((input) => input.checked)
185
+ .map((input) => input.value);
186
+ const wrongWebSubs = webSubsChecked.filter((s) => !s.startsWith(fromList + '_web/'));
187
+ if (wrongWebSubs.length > 0) {
188
+ NotificationMessage.sendMessage(`Some web sub folder is not under ${fromList}`, NotificationColor.Error);
189
+ return;
190
+ }
191
+ const chkEnv = ref.$('.chk-env').checked;
192
+ const chkBackup = ref.$('.chk-backup').checked;
193
+ if (!chkServer && !chkApi && webSubsChecked.length < 1 && !chkEnv) {
194
+ NotificationMessage.sendMessage('Please select the release options', NotificationColor.Error);
195
+ return;
196
+ }
197
+
198
+ if (fromList !== toList && !confirm('The From and To are not the same, are you sure?')) {
199
+ return;
200
+ }
201
+ if (!confirm('Are you sure you want to update the release? (Assets are not copied, so it may cause issues)')) {
202
+ return;
203
+ }
204
+ const releaseUpdateBtn = document.querySelector('.release-update-btn') as HTMLButtonElement;
205
+ releaseUpdateBtn.disabled = true;
206
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/update', {
207
+ ...data,
208
+ fromList,
209
+ toList,
210
+ chkServer,
211
+ chkApi,
212
+ chkWeb,
213
+ // webSub, // will be deprecated
214
+ webSubs: webSubsChecked,
215
+ chkEnv,
216
+ chkBackup,
217
+ });
218
+ const dataResponse = await response.json;
219
+ console.log('release/update', dataResponse);
220
+ releaseUpdateBtn.disabled = false;
221
+ if (!dataResponse || dataResponse.status !== 'ok') {
222
+ NotificationMessage.sendMessage(
223
+ dataResponse.message ||
224
+ 'Failed to update release (timeout, possibly backend is runing, please wait and click Check!)',
225
+ NotificationColor.Error
226
+ );
227
+ return;
228
+ }
229
+ NotificationMessage.sendMessage('Release updated successfully', NotificationColor.Success);
230
+ };
231
+
232
+ const onLogClick = async (logName: string) => {
233
+ const data = getDomData();
234
+ if (!data.targetUrl || !data.accessToken) {
235
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
236
+ return;
237
+ }
238
+
239
+ const responseText = await getRenderPageProps().renderPageFunctions.fetchData(
240
+ '/api/admin/release/view-log',
241
+ {
242
+ ...data,
243
+ logName,
244
+ },
245
+ true
246
+ );
247
+ const blob = await responseText.blob();
248
+ downloadStream(blob, logName);
249
+ };
250
+ const onCheck = async () => {
251
+ const data = getDomData();
252
+ if (!data.targetUrl || !data.accessToken) {
253
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
254
+ return;
255
+ }
256
+ const result = await fetchData(data);
257
+ if (!result || result.status !== 'ok') {
258
+ NotificationMessage.sendMessage(result.message || 'Failed to get release list', NotificationColor.Error);
259
+ return;
260
+ }
261
+ console.log(result);
262
+
263
+ domUpdate.value = <ReleaseList result={result} onUpdate={onUpdate} onLogClick={onLogClick} />;
264
+ domLog.value = <pre>{JSON.stringify(result, null, 2)}</pre>;
265
+ if (result.releaseProgress) {
266
+ NotificationMessage.sendMessage('Release progress: ' + result.releaseProgress, NotificationColor.Warning);
267
+ }
268
+ };
269
+
270
+ const onRefreshCacheLocal = async () => {
271
+ return onRefreshCache(true);
272
+ };
273
+ const onRefreshCacheRemote = async () => {
274
+ return onRefreshCache(false);
275
+ };
276
+ const onRefreshCache = async (isLocal?: boolean) => {
277
+ const data = getDomData();
278
+ if (!isLocal) {
279
+ if (!data.targetUrl || !data.accessToken) {
280
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
281
+ return;
282
+ }
283
+ }
284
+
285
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/refresh-cache', {
286
+ ...data,
287
+ isLocal,
288
+ });
289
+ const dataResponse = await response.json;
290
+ console.log('refresh-cache', dataResponse);
291
+ if (!dataResponse || dataResponse.status !== 'ok') {
292
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to refresh cache', NotificationColor.Error);
293
+ return;
294
+ }
295
+ domLog.value = <pre>{encodeHtml(JSON.stringify(dataResponse, null, 2))}</pre>;
296
+ NotificationMessage.sendMessage('Cache refreshed successfully', NotificationColor.Success);
297
+ };
298
+
299
+ const onRestartAppLocal = async () => {
300
+ return onRestartApp(true);
301
+ };
302
+ const onRestartAppRemote = async () => {
303
+ return onRestartApp(false);
304
+ };
305
+ const onRestartApp = async (isLocal?: boolean) => {
306
+ const data = getDomData();
307
+ if (!isLocal) {
308
+ if (!data.targetUrl || !data.accessToken) {
309
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
310
+ return;
311
+ }
312
+ }
313
+
314
+ const index = await ActionSheetSelectPromise({
315
+ title: 'Restart App (users may get disconnected errors) ?',
316
+ options: ['OK'],
317
+ cancelButtonText: 'Cancel',
318
+ });
319
+ if (index !== 0) {
320
+ return;
321
+ }
322
+
323
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/restart-app', {
324
+ ...data,
325
+ isLocal,
326
+ });
327
+ const dataResponse = await response.json;
328
+ console.log('restart-app', dataResponse);
329
+ if (!dataResponse || dataResponse.status !== 'ok') {
330
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to Restart App', NotificationColor.Error);
331
+ return;
332
+ }
333
+ domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
334
+ NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
335
+ };
336
+
337
+ const onShellLocal = async () => {
338
+ return onShell(true);
339
+ };
340
+ const onShellRemote = async () => {
341
+ return onShell(false);
342
+ };
343
+ const onShell = async (isLocal?: boolean) => {
344
+ const data = getDomData();
345
+ if (!isLocal) {
346
+ if (!data.targetUrl || !data.accessToken) {
347
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
348
+ return;
349
+ }
350
+ }
351
+
352
+ const index = await ActionSheetSelectPromise({
353
+ title: 'Run Cmd ?',
354
+ options: ['OK'],
355
+ cancelButtonText: 'Cancel',
356
+ });
357
+ if (index !== 0) {
358
+ return;
359
+ }
360
+
361
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/shell', {
362
+ ...data,
363
+ isLocal,
364
+ cmd: ref.$('.release-cmd').value,
365
+ });
366
+ const dataResponse = await response.json;
367
+ console.log('shell', dataResponse);
368
+ if (!dataResponse || dataResponse.status !== 'ok') {
369
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to run cmd', NotificationColor.Error);
370
+ return;
371
+ }
372
+ domLog.value = <pre>{encodeHtml(dataResponse.message) + '\r\n<br>' + encodeHtml(dataResponse.result)}</pre>;
373
+ NotificationMessage.sendMessage('Restart App successfully', NotificationColor.Success);
374
+ };
375
+
376
+ const ref: RefProps = {
377
+ onLoad: async () => {
378
+ const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
379
+ DomUtils.setValue('.target-url', data.targetUrl || '');
380
+ DomUtils.setValue('.access-token', data.accessToken || '');
381
+ },
382
+ };
383
+ return (
384
+ <div ref={ref} css={css} class='admin-release-top'>
385
+ <div class='row-box mt-m'>
386
+ <label class='label mr-m release-label'>Target Url:</label>
387
+ <div class='w-50p'>
388
+ <input type='text' class='input-base w-100p target-url' placeholder='Target Url' />
389
+ </div>
390
+ </div>
391
+ <div class='row-box mt-m'>
392
+ <label class='label mr-m release-label'>Access token:</label>
393
+ <div class='w-50p'>
394
+ <input type='text' class='input-base w-100p access-token' placeholder='Access token' />
395
+ </div>
396
+ </div>
397
+ <div class='row-box mt-m'>
398
+ <button onClick={onCheck} class='button-base mr-m'>
399
+ Check
400
+ </button>
401
+ <button onClick={onRefreshCacheRemote} class='button-base mr-m'>
402
+ Refresh Cache (Remote)
403
+ </button>
404
+ <button onClick={onRestartAppRemote} class='button-base mr-m color-red'>
405
+ Restart App (Remote)
406
+ </button>
407
+ <button onClick={onRefreshCacheLocal} class='button-base mr-m'>
408
+ Refresh Cache (Local)
409
+ </button>
410
+ <button onClick={onRestartAppLocal} class='button-base color-red'>
411
+ Restart App (Local)
412
+ </button>
413
+ </div>
414
+ <div class='row-box mt-m mb-m'>
415
+ <input type='text' class='input-base w-50p release-cmd mr-m' placeholder='Command' />
416
+ <button onClick={onShellRemote} class='button-base color-red mr-m'>
417
+ Run Cmd (Remote)
418
+ </button>
419
+ <button onClick={onShellLocal} class='button-base'>
420
+ Run Cmd (Local)
421
+ </button>
422
+ </div>
423
+ {domUpdate.node}
424
+ {domLog.node}
425
+ </div>
426
+ );
427
+ };