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,83 @@
1
+ import { CssProps, RefProps, getRenderPageProps, NotificationMessage } from 'lupine.components';
2
+ import { AdminTableData } from './admin-table-data';
3
+ import { adminFrameProps } from './admin-frame-props';
4
+ import { AdminPageEditPage } from './admin-page-edit';
5
+
6
+ const tableName = '$__s_page';
7
+ const fetchTableDelete = async (tableName: string, id: string) => {
8
+ const data = await getRenderPageProps().renderPageFunctions.fetchData(`/api/admin/menus/delete/${id}`);
9
+ return data.json;
10
+ };
11
+
12
+ export const AdminPagePage = () => {
13
+ const refUpdate = adminFrameProps.tabsHook;
14
+ const onDelete = async (rowData: any) => {
15
+ if (!confirm(`Are you really Deleting ${rowData['menuid']}?`)) {
16
+ return;
17
+ }
18
+ const json = await fetchTableDelete(tableName, rowData['menuid']);
19
+ console.log('====fetchTableDelete', json);
20
+ NotificationMessage.sendMessage('Deleted: ' + rowData['menuid']);
21
+ updateList.refreshRef();
22
+ };
23
+
24
+ const onEdit = async (rowData: any) => {
25
+ if (refUpdate?.getCount && refUpdate.getCount() > adminFrameProps.maxTabsCount) {
26
+ alert('You are opening too many pages');
27
+ return;
28
+ }
29
+ if (refUpdate?.findAndActivate && refUpdate.findAndActivate('Menu: ' + rowData.menuid)) {
30
+ return;
31
+ }
32
+ refUpdate?.newPage && (await refUpdate.newPage('Menu: ' + rowData.menuid, AdminPageEditPage(rowData.menuid)));
33
+ };
34
+
35
+ const css: CssProps = {
36
+ '.admin-table-list > div > div': {
37
+ backgroundColor: '#95c998',
38
+ marginBottom: '16px',
39
+ },
40
+ '.admin-table-list .row-1': {
41
+ backgroundColor: '#e8f8e8', //#95c998
42
+ },
43
+ };
44
+ const updateList = { refreshRef: Function, onDelete, onEdit };
45
+ const refInput: RefProps = {};
46
+ let timeoutID: NodeJS.Timeout | undefined = undefined;
47
+ const onInput = () => {
48
+ if (timeoutID) {
49
+ clearTimeout(timeoutID);
50
+ }
51
+ timeoutID = setTimeout(() => {
52
+ updateList.refreshRef();
53
+ }, 500);
54
+ };
55
+ const onNewPage = async () => {
56
+ if (refUpdate?.getCount && refUpdate.getCount() > adminFrameProps.maxTabsCount) {
57
+ alert('You are opening too many pages');
58
+ return;
59
+ }
60
+ if (refUpdate?.findAndActivate && refUpdate.findAndActivate('Page: ' + tableName)) {
61
+ return;
62
+ }
63
+ refUpdate?.newPage && (await refUpdate.newPage('Page: ' + tableName, AdminPageEditPage('')));
64
+ };
65
+ const onSearch = () => {};
66
+ return (
67
+ <div css={css}>
68
+ <div class='admin-sub-title'>Users List</div>
69
+ <div class='row-box mt1 mb1'>
70
+ <div>
71
+ <input type='text' ref={refInput} class='input-base' placeholder='Search' onInput={onInput} />
72
+ </div>
73
+ <button onClick={onSearch} class='button-base'>
74
+ Search
75
+ </button>
76
+ <button onClick={onNewPage} class='button-base'>
77
+ New Page
78
+ </button>
79
+ </div>
80
+ <AdminTableData tableName={'$__s_page'} update={updateList}></AdminTableData>
81
+ </div>
82
+ );
83
+ };
@@ -0,0 +1,28 @@
1
+ import { getRenderPageProps, RefProps, HtmlVar } from 'lupine.components';
2
+
3
+ export const AdminPerformancePage = () => {
4
+ const fetchData = async () => {
5
+ const data = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/performance/data');
6
+ return data.json;
7
+ };
8
+ const onRefresh = async () => {
9
+ const data = await fetchData();
10
+ console.log(data);
11
+ dom.value = <pre>{JSON.stringify(data, null, 2)}</pre>;
12
+ };
13
+
14
+ const ref: RefProps = {
15
+ onLoad: async () => {
16
+ onRefresh();
17
+ },
18
+ };
19
+ const dom = HtmlVar('');
20
+ return (
21
+ <div ref={ref}>
22
+ <button onClick={onRefresh} class='button-base'>
23
+ Refresh
24
+ </button>
25
+ {dom.node}
26
+ </div>
27
+ );
28
+ };
@@ -0,0 +1,320 @@
1
+ import {
2
+ CssProps,
3
+ getRenderPageProps,
4
+ RefProps,
5
+ DomUtils,
6
+ HtmlVar,
7
+ NotificationColor,
8
+ NotificationMessage,
9
+ formatBytes,
10
+ downloadStream,
11
+ } from 'lupine.components';
12
+
13
+ interface DirProps {
14
+ name: string;
15
+ time: string;
16
+ size: number;
17
+ dir: boolean;
18
+ items?: DirProps[];
19
+ }
20
+ interface ReleaseListProps {
21
+ result: any;
22
+ onUpdate: () => void;
23
+ onLogClick: (logName: string) => Promise<void>;
24
+ }
25
+ const ReleaseList = (props: ReleaseListProps) => {
26
+ const ref: RefProps = {
27
+ onLoad: async () => {
28
+ const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
29
+ DomUtils.setValue('.from-list', data.fromList || '');
30
+ },
31
+ };
32
+ return (
33
+ <div ref={ref}>
34
+ <div class='row-box mt-m'>
35
+ <label class='label mr-m release-label'>From:</label>
36
+ <div class='w-50p'>
37
+ <select type='text' class='input-base w-100p from-list'>
38
+ {props.result.appsFrom.map((app: string) => (
39
+ <option key={app} value={app}>
40
+ {app}
41
+ </option>
42
+ ))}
43
+ </select>
44
+ </div>
45
+ </div>
46
+ <div class='row-box mt-m'>
47
+ <label class='label mr-m release-label'>To:</label>
48
+ <div class='w-50p'>
49
+ <select type='text' class='input-base w-100p to-list'>
50
+ {props.result.apps.map((app: string) => (
51
+ <option key={app} value={app}>
52
+ {app}
53
+ </option>
54
+ ))}
55
+ </select>
56
+ </div>
57
+ </div>
58
+ <div class='row-box mt-m'>
59
+ <label class='label mr-m release-label'>Release:</label>
60
+ <label class='label mr-m' for='chk-server'>
61
+ Server:
62
+ </label>
63
+ <div class='mr-l'>
64
+ <input type='checkbox' class='base-css chk-server' id='chk-server' />
65
+ </div>
66
+ <label class='label mr-m' for='chk-api'>
67
+ Api:
68
+ </label>
69
+ <div class='mr-l'>
70
+ <input type='checkbox' class='base-css chk-api' id='chk-api' />
71
+ </div>
72
+ <label class='label mr-m' for='chk-web'>
73
+ Web:
74
+ </label>
75
+ <div class='mr-l'>
76
+ <input type='checkbox' class='base-css chk-web' id='chk-web' />
77
+ </div>
78
+
79
+ <label class='label mr-m' for='chk-env'>
80
+ Env:
81
+ </label>
82
+ <div class='mr-l'>
83
+ <input type='checkbox' class='base-css chk-env' id='chk-env' />
84
+ </div>
85
+ <label class='label mr-m' for='chk-backup'>
86
+ ( Backup:
87
+ </label>
88
+ <div class=''>
89
+ <input type='checkbox' class='base-css chk-backup' id='chk-backup' /> )
90
+ </div>
91
+ </div>
92
+ <div class='row-box mt-m'>
93
+ <label class='label mr-m release-label'>Web Sub-folder:</label>
94
+ <div class='w-50p mr-l'>
95
+ {/* <input type='text' class='input-base w-100p input-web-sub' placeholder='The Sub-folder you want to update' /> */}
96
+
97
+ {props.result.webSub.map((folder: string) => (
98
+ <div>
99
+ <label>
100
+ <input type='checkbox' class={'chk-web-sub input-' + folder} value={folder} /> {folder}
101
+ </label>
102
+ </div>
103
+ ))}
104
+ <label class='label mr-m release-label'>(Only update index.js file)</label>
105
+ </div>
106
+ </div>
107
+ <LogList logs={props.result.logs} onLogClick={props.onLogClick} />
108
+ <div class='row-box mt-m'>
109
+ <button onClick={props.onUpdate} class='button-base release-update-btn'>
110
+ Update
111
+ </button>
112
+ </div>
113
+ </div>
114
+ );
115
+ };
116
+
117
+ const LogList = (props: {
118
+ logs: { name: string; size: number; time: string }[];
119
+ onLogClick: (logName: string) => Promise<void>;
120
+ }) => {
121
+ return (
122
+ <div>
123
+ <div class='row-box mt-m'>
124
+ <label class='label mr-m release-label'>Logs:</label>
125
+ <div type='text'>
126
+ {props.logs && props.logs.map((log: { name: string; size: number; time: string }) => (
127
+ <div>
128
+ <label class='release-log' onClick={() => props.onLogClick(log.name)}>{`${log.name}`}</label> ({log.time};{' '}
129
+ {formatBytes(log.size)}){' '}
130
+ </div>
131
+ ))}
132
+ </div>
133
+ </div>
134
+ </div>
135
+ );
136
+ };
137
+
138
+ export const AdminReleasePage = () => {
139
+ const fetchData = async (options: { targetUrl: string; accessToken: string; log?: boolean }) => {
140
+ const data = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/check', options);
141
+ console.log('AdminRelease', data);
142
+ return data.json;
143
+ };
144
+ const css: CssProps = {
145
+ '.release-label': {
146
+ width: '130px',
147
+ },
148
+ '.release-log': {
149
+ cursor: 'pointer',
150
+ textDecoration: 'underline',
151
+ },
152
+ };
153
+ const domLog = HtmlVar('');
154
+ const domUpdate = HtmlVar('');
155
+ const getDomData = () => {
156
+ const dataOld = JSON.parse(localStorage.getItem('admin-release') || '{}');
157
+ const data = {
158
+ targetUrl: DomUtils.getValue('.target-url'),
159
+ accessToken: DomUtils.getValue('.access-token'),
160
+ fromList: DomUtils.getValue('.from-list') || dataOld.fromList,
161
+ };
162
+ localStorage.setItem('admin-release', JSON.stringify(data));
163
+ return data;
164
+ };
165
+
166
+ const onUpdate = async () => {
167
+ const data = getDomData();
168
+ if (!data.targetUrl || !data.accessToken) {
169
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
170
+ return;
171
+ }
172
+
173
+ const fromList = DomUtils.getValue('.from-list');
174
+ const toList = DomUtils.getValue('.to-list');
175
+ const chkServer = DomUtils.getChecked('.chk-server');
176
+ const chkApi = DomUtils.getChecked('.chk-api');
177
+ const chkWeb = DomUtils.getChecked('.chk-web');
178
+ const webSub = DomUtils.getValue('.input-web-sub');
179
+ const webSubs = document.querySelectorAll<HTMLInputElement>('.chk-web-sub');
180
+ const webSubsChecked = Array.from(webSubs)
181
+ .filter((input) => input.checked)
182
+ .map((input) => input.value);
183
+ const chkEnv = DomUtils.getChecked('.chk-env');
184
+ const chkBackup = DomUtils.getChecked('.chk-backup');
185
+ if (!chkServer && !chkApi && !chkWeb && !chkEnv) {
186
+ NotificationMessage.sendMessage('Please select the release options', NotificationColor.Error);
187
+ return;
188
+ }
189
+
190
+ if (fromList !== toList && !confirm('The From and To are not the same, are you sure?')) {
191
+ return;
192
+ }
193
+ if (!confirm('Are you sure you want to update the release? (Assets are not copied, so it may cause issues)')) {
194
+ return;
195
+ }
196
+ const releaseUpdateBtn = document.querySelector('.release-update-btn') as HTMLButtonElement;
197
+ releaseUpdateBtn.disabled = true;
198
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/update', {
199
+ ...data,
200
+ fromList,
201
+ toList,
202
+ chkServer,
203
+ chkApi,
204
+ chkWeb,
205
+ webSub, // will be deprecated
206
+ webSubs: webSubsChecked,
207
+ chkEnv,
208
+ chkBackup,
209
+ });
210
+ const dataResponse = await response.json;
211
+ console.log('AdminRelease', dataResponse);
212
+ releaseUpdateBtn.disabled = false;
213
+ if (!dataResponse || dataResponse.status !== 'ok') {
214
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to update release', NotificationColor.Error);
215
+ return;
216
+ }
217
+ NotificationMessage.sendMessage('Release updated successfully', NotificationColor.Success);
218
+ };
219
+
220
+ const onLogClick = async (logName: string) => {
221
+ const data = getDomData();
222
+ if (!data.targetUrl || !data.accessToken) {
223
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
224
+ return;
225
+ }
226
+
227
+ const responseText = await getRenderPageProps().renderPageFunctions.fetchData(
228
+ '/api/admin/release/view-log',
229
+ {
230
+ ...data,
231
+ logName,
232
+ },
233
+ true
234
+ );
235
+ const blob = await responseText.blob();
236
+ downloadStream(blob, logName);
237
+ };
238
+ const onCheck = async () => {
239
+ const data = getDomData();
240
+ if (!data.targetUrl || !data.accessToken) {
241
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
242
+ return;
243
+ }
244
+ const result = await fetchData(data);
245
+ if (!result || result.status !== 'ok') {
246
+ NotificationMessage.sendMessage(result.message || 'Failed to get release list', NotificationColor.Error);
247
+ return;
248
+ }
249
+ console.log(result);
250
+
251
+ domUpdate.value = <ReleaseList result={result} onUpdate={onUpdate} onLogClick={onLogClick} />;
252
+ domLog.value = <pre>{JSON.stringify(result, null, 2)}</pre>;
253
+ };
254
+
255
+ const onRefreshCacheLocal = async () => {
256
+ return onRefreshCache(true);
257
+ };
258
+ const onRefreshCacheRemote = async () => {
259
+ return onRefreshCache(false);
260
+ };
261
+ const onRefreshCache = async (isLocal?: boolean) => {
262
+ const data = getDomData();
263
+ if (!isLocal) {
264
+ if (!data.targetUrl || !data.accessToken) {
265
+ NotificationMessage.sendMessage('Please fill in all fields', NotificationColor.Error);
266
+ return;
267
+ }
268
+ }
269
+
270
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/release/refresh-cache', {
271
+ ...data,
272
+ isLocal,
273
+ });
274
+ const dataResponse = await response.json;
275
+ console.log('AdminRelease', dataResponse);
276
+ if (!dataResponse || dataResponse.status !== 'ok') {
277
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to refresh cache', NotificationColor.Error);
278
+ return;
279
+ }
280
+ domLog.value = <pre>{JSON.stringify(dataResponse, null, 2)}</pre>;
281
+ NotificationMessage.sendMessage('Cache refreshed successfully', NotificationColor.Success);
282
+ };
283
+
284
+ const ref: RefProps = {
285
+ onLoad: async () => {
286
+ const data = JSON.parse(localStorage.getItem('admin-release') || '{}');
287
+ DomUtils.setValue('.target-url', data.targetUrl || '');
288
+ DomUtils.setValue('.access-token', data.accessToken || '');
289
+ },
290
+ };
291
+ return (
292
+ <div ref={ref} css={css} class='admin-release-top'>
293
+ <div class='row-box mt1 mb1'>
294
+ <label class='label mr-m release-label'>Target Url:</label>
295
+ <div class='w-50p'>
296
+ <input type='text' class='input-base w-100p target-url' placeholder='Target Url' />
297
+ </div>
298
+ </div>
299
+ <div class='row-box mt1 mb1'>
300
+ <label class='label mr-m release-label'>Access token:</label>
301
+ <div class='w-50p'>
302
+ <input type='text' class='input-base w-100p access-token' placeholder='Access token' />
303
+ </div>
304
+ </div>
305
+ <div class='row-box mt1 mb1'>
306
+ <button onClick={onCheck} class='button-base mr-m'>
307
+ Check
308
+ </button>
309
+ <button onClick={onRefreshCacheRemote} class='button-base mr-m'>
310
+ Refresh Cache (Remote)
311
+ </button>
312
+ <button onClick={onRefreshCacheLocal} class='button-base'>
313
+ Refresh Cache (Local)
314
+ </button>
315
+ </div>
316
+ {domUpdate.node}
317
+ {domLog.node}
318
+ </div>
319
+ );
320
+ };