lupine.api 1.1.58 → 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.
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 +703 -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,338 +1,338 @@
1
- import {
2
- CssProps,
3
- getRenderPageProps,
4
- RefProps,
5
- EditableLabel,
6
- HtmlVar,
7
- ModalWindow,
8
- NotificationColor,
9
- NotificationMessage,
10
- PagingLink,
11
- } from 'lupine.components';
12
-
13
- export type TokenProps = {
14
- token: string;
15
- description: string;
16
- timestamp?: number;
17
- };
18
-
19
- const getTokenData = async (pageIndex = 0, searchText: string = '') => {
20
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/list', {
21
- q: searchText,
22
- });
23
- const dataResponse = await response.json;
24
- let pageLimit = 15;
25
- let tokenList: TokenProps[] = [];
26
- if (dataResponse && dataResponse.status === 'ok') {
27
- tokenList = dataResponse.result;
28
- pageLimit = dataResponse.pageLimit;
29
- tokenList;
30
- }
31
-
32
- const itemsCount = tokenList.length;
33
- let maxPages = Math.floor(itemsCount / pageLimit);
34
- if (itemsCount % pageLimit !== 0) {
35
- maxPages++;
36
- }
37
- if (pageIndex > maxPages - 1) {
38
- pageIndex = maxPages - 1;
39
- }
40
- const offset = pageIndex * pageLimit;
41
- return {
42
- status: 'ok',
43
- itemsCount,
44
- pageIndex,
45
- pageLimit,
46
- result: tokenList.slice(offset, offset + pageLimit),
47
- };
48
- };
49
- const generateToken = async () => {
50
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/generate');
51
- const dataResponse = await response.json;
52
- if (dataResponse && dataResponse.status === 'ok') {
53
- NotificationMessage.sendMessage(dataResponse.message || 'Token generated successfully', NotificationColor.Success);
54
- } else {
55
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to generate token', NotificationColor.Error);
56
- }
57
- return dataResponse.result;
58
- };
59
- const addTokenData = async (item: TokenProps) => {
60
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/add', { ...item });
61
- const dataResponse = await response.json;
62
- if (dataResponse && dataResponse.status === 'ok') {
63
- NotificationMessage.sendMessage(dataResponse.message || 'Token added successfully', NotificationColor.Success);
64
- } else {
65
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to add token', NotificationColor.Error);
66
- }
67
- return { ...item };
68
- };
69
-
70
- const removeTokenData = async (token: string) => {
71
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/remove', { token });
72
- const dataResponse = await response.json;
73
- if (dataResponse && dataResponse.status === 'ok') {
74
- NotificationMessage.sendMessage(dataResponse.message || 'Token removed successfully', NotificationColor.Success);
75
- } else {
76
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to remove token', NotificationColor.Error);
77
- }
78
- };
79
- const updateTokenData = async (item: TokenProps) => {
80
- const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/update', { ...item });
81
- const dataResponse = await response.json;
82
- if (dataResponse && dataResponse.status === 'ok') {
83
- NotificationMessage.sendMessage(dataResponse.message || 'Token updated successfully', NotificationColor.Success);
84
- } else {
85
- NotificationMessage.sendMessage(dataResponse.message || 'Failed to update token', NotificationColor.Error);
86
- }
87
- return { ...item };
88
- };
89
-
90
- type TokenDataUpdateProps = {
91
- save?: (isNew: boolean) => Promise<TokenProps | null>;
92
- cancel?: () => void;
93
- };
94
- // show dialog to edit one item, and call update when save the result
95
- const showTokenEditItem = async (item: TokenProps, update: (item: TokenProps) => void, isNew: boolean) => {
96
- const handleClicked = async (index: number) => {
97
- if (index === 1) {
98
- updateFn.cancel?.();
99
- modalClose();
100
- }
101
- if (index === 0) {
102
- const newItem = await updateFn.save?.(isNew);
103
- if (newItem) {
104
- update(newItem);
105
- modalClose();
106
- }
107
- }
108
- };
109
- const updateFn: TokenDataUpdateProps = {};
110
- const modalClose = await ModalWindow.show({
111
- title: 'Edit Token Data',
112
- buttons: ['Save', 'Cancel'],
113
- // contentMaxHeight: '400px',
114
- handleClicked,
115
- children: <TokenEditItem item={item} update={updateFn}></TokenEditItem>,
116
- closeWhenClickOutside: false,
117
- });
118
- };
119
-
120
- // edit one item
121
- export const TokenEditItem = (props: { item: TokenProps; update: TokenDataUpdateProps }) => {
122
- const ref: RefProps = { id: '' };
123
- const css: CssProps = {
124
- padding: '10px',
125
- border: 'solid 1px gray',
126
- margin: '1px',
127
- position: 'relative',
128
- '.lable': {
129
- width: '90px',
130
- },
131
- };
132
- props.update.save = async (isNew: boolean) => {
133
- const token = ref.$('input.token').value;
134
- const description = ref.$('input.description').value;
135
- if (token && description) {
136
- const newItem = isNew
137
- ? await addTokenData({ token, description })
138
- : await updateTokenData({ token, description });
139
- props.item.token = newItem.token;
140
- props.item.description = newItem.description;
141
- return newItem;
142
- }
143
- NotificationMessage.sendMessage('Please input token and description', NotificationColor.Error);
144
- return null;
145
- };
146
- return (
147
- <div ref={ref} css={css} class='sample-data'>
148
- <div class='row-box mt-m'>
149
- <div class='lable'>Token: </div>
150
- <div>
151
- <input type='text' class='input-base token' value={props.item.token} readonly={true} />
152
- </div>
153
- </div>
154
- <div class='row-box'>
155
- <div class='lable'>Description: </div>
156
- <div>
157
- <input type='text' class='input-base description' value={props.item.description} />
158
- </div>
159
- </div>
160
- </div>
161
- );
162
- };
163
-
164
- // show one item
165
- export const TokenShowItem = (props: { item: TokenProps }) => {
166
- const ref: RefProps = {};
167
- const css: CssProps = {
168
- padding: '10px',
169
- border: 'solid 1px gray',
170
- margin: '1px',
171
- position: 'relative',
172
- '.control-box': {
173
- display: 'none',
174
- position: 'absolute',
175
- right: '10px',
176
- top: '10px',
177
- },
178
- '&:hover .control-box': {
179
- display: 'block',
180
- },
181
- '.lable': {
182
- width: '85px',
183
- },
184
- '.token': {
185
- lineBreak: 'anywhere',
186
- wordBreak: 'break-all',
187
- },
188
- };
189
- const onEdit = (ev: any) => {
190
- const update = (item: TokenProps) => {
191
- dom.value = makeDom(item);
192
- };
193
- showTokenEditItem(props.item, update, false);
194
- };
195
- const onRemove = async (ev: any) => {
196
- if (!confirm('Are you sure you want to delete this token?')) {
197
- return;
198
- }
199
- await removeTokenData(props.item.token);
200
- ref.current.remove();
201
- };
202
-
203
- const makeDom = (item: TokenProps) => {
204
- const saveText = (text: string) => {
205
- item.description = text;
206
- updateTokenData(item);
207
- dom.value = makeDom(item);
208
- };
209
- return (
210
- <div>
211
- <div class='control-box'>
212
- <button class='button-base button-ss' onClick={onEdit}>
213
- Edit
214
- </button>
215
- <button class='button-base button-ss' onClick={onRemove}>
216
- Delete
217
- </button>
218
- </div>
219
- <div class='row-box'>
220
- <div class='lable'>Token: </div>
221
- <div class='token'>{item.token}</div>
222
- </div>
223
- <div class='row-box'>
224
- <div class='lable'>Description: </div>
225
- <div class='flex-1'>
226
- <EditableLabel text={item.description} save={saveText} type='text' />
227
- </div>
228
- </div>
229
- <div class='row-box'>
230
- <div class='lable'>Timestamp: </div>
231
- <div>{new Date(item.timestamp || 0).toLocaleString()}</div>
232
- </div>
233
- </div>
234
- );
235
- };
236
-
237
- const dom = new HtmlVar(makeDom(props.item));
238
- return (
239
- <div ref={ref} css={css} class='sample-data'>
240
- {dom.node}
241
- </div>
242
- );
243
- };
244
-
245
- // show the list
246
- const TokenList = () => {
247
- let currentIndex = 0;
248
- let searchText = '';
249
- let maxPages = 0;
250
- const ref: RefProps = {
251
- onLoad: async (self: Element) => {
252
- await makeList(currentIndex);
253
- },
254
- };
255
- const onAdd = async () => {
256
- const update = async (item: TokenProps) => {
257
- // new item is added at the list end, so update the last page
258
- await makeList(maxPages + 1);
259
- };
260
- showTokenEditItem(
261
- {
262
- token: await generateToken(),
263
- description: '',
264
- },
265
- update,
266
- true
267
- );
268
- };
269
- const onSearch = async () => {
270
- searchText = ref.$('input.search').value.trim();
271
- await makeList(currentIndex);
272
- };
273
- const makeList = async (pageIndex: number) => {
274
- const onLinkClick = async (index: number) => {
275
- currentIndex = index;
276
- await makeList(currentIndex);
277
- };
278
- const items = await getTokenData(pageIndex, searchText);
279
- maxPages = Math.floor((items.itemsCount + 1) / items.pageLimit);
280
- listDom.value = (
281
- <div>
282
- <PagingLink
283
- itemsCount={items.itemsCount}
284
- pageIndex={pageIndex}
285
- pageLimit={items.pageLimit}
286
- onClick={onLinkClick}
287
- baseLink=''
288
- ></PagingLink>
289
- {items.result.map((item) => (
290
- <TokenShowItem item={item}></TokenShowItem>
291
- ))}
292
- <PagingLink
293
- itemsCount={items.itemsCount}
294
- pageIndex={pageIndex}
295
- pageLimit={items.pageLimit}
296
- onClick={onLinkClick}
297
- baseLink=''
298
- ></PagingLink>
299
- </div>
300
- );
301
- };
302
- const listDom = new HtmlVar(<div></div>);
303
- const css: CssProps = {
304
- display: 'flex',
305
- flexDirection: 'column',
306
- '.label': {
307
- width: '70px',
308
- },
309
- };
310
- return (
311
- <div ref={ref} css={css}>
312
- <div>
313
- <div class='row-box'>
314
- <div class='label'>Search: </div>
315
- <input type='text' class='input-base search' />
316
- <button class='button-base mr-s' onClick={onSearch}>
317
- Search
318
- </button>
319
- <button class='button-base' onClick={onAdd}>
320
- Add
321
- </button>
322
- </div>
323
- </div>
324
- <div class='list'>{listDom.node}</div>
325
- </div>
326
- );
327
- };
328
-
329
- export const AdminTokensPage = () => {
330
- return (
331
- <div>
332
- <div>
333
- <div>Tokens management</div>
334
- <TokenList />
335
- </div>
336
- </div>
337
- );
338
- };
1
+ import {
2
+ CssProps,
3
+ getRenderPageProps,
4
+ RefProps,
5
+ EditableLabel,
6
+ HtmlVar,
7
+ ModalWindow,
8
+ NotificationColor,
9
+ NotificationMessage,
10
+ PagingLink,
11
+ } from 'lupine.components';
12
+
13
+ export type TokenProps = {
14
+ token: string;
15
+ description: string;
16
+ timestamp?: number;
17
+ };
18
+
19
+ const getTokenData = async (pageIndex = 0, searchText: string = '') => {
20
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/list', {
21
+ q: searchText,
22
+ });
23
+ const dataResponse = await response.json;
24
+ let pageLimit = 15;
25
+ let tokenList: TokenProps[] = [];
26
+ if (dataResponse && dataResponse.status === 'ok') {
27
+ tokenList = dataResponse.result;
28
+ pageLimit = dataResponse.pageLimit;
29
+ tokenList;
30
+ }
31
+
32
+ const itemsCount = tokenList.length;
33
+ let maxPages = Math.floor(itemsCount / pageLimit);
34
+ if (itemsCount % pageLimit !== 0) {
35
+ maxPages++;
36
+ }
37
+ if (pageIndex > maxPages - 1) {
38
+ pageIndex = maxPages - 1;
39
+ }
40
+ const offset = pageIndex * pageLimit;
41
+ return {
42
+ status: 'ok',
43
+ itemsCount,
44
+ pageIndex,
45
+ pageLimit,
46
+ result: tokenList.slice(offset, offset + pageLimit),
47
+ };
48
+ };
49
+ const generateToken = async () => {
50
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/generate');
51
+ const dataResponse = await response.json;
52
+ if (dataResponse && dataResponse.status === 'ok') {
53
+ NotificationMessage.sendMessage(dataResponse.message || 'Token generated successfully', NotificationColor.Success);
54
+ } else {
55
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to generate token', NotificationColor.Error);
56
+ }
57
+ return dataResponse.result;
58
+ };
59
+ const addTokenData = async (item: TokenProps) => {
60
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/add', { ...item });
61
+ const dataResponse = await response.json;
62
+ if (dataResponse && dataResponse.status === 'ok') {
63
+ NotificationMessage.sendMessage(dataResponse.message || 'Token added successfully', NotificationColor.Success);
64
+ } else {
65
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to add token', NotificationColor.Error);
66
+ }
67
+ return { ...item };
68
+ };
69
+
70
+ const removeTokenData = async (token: string) => {
71
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/remove', { token });
72
+ const dataResponse = await response.json;
73
+ if (dataResponse && dataResponse.status === 'ok') {
74
+ NotificationMessage.sendMessage(dataResponse.message || 'Token removed successfully', NotificationColor.Success);
75
+ } else {
76
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to remove token', NotificationColor.Error);
77
+ }
78
+ };
79
+ const updateTokenData = async (item: TokenProps) => {
80
+ const response = await getRenderPageProps().renderPageFunctions.fetchData('/api/admin/tokens/update', { ...item });
81
+ const dataResponse = await response.json;
82
+ if (dataResponse && dataResponse.status === 'ok') {
83
+ NotificationMessage.sendMessage(dataResponse.message || 'Token updated successfully', NotificationColor.Success);
84
+ } else {
85
+ NotificationMessage.sendMessage(dataResponse.message || 'Failed to update token', NotificationColor.Error);
86
+ }
87
+ return { ...item };
88
+ };
89
+
90
+ type TokenDataUpdateProps = {
91
+ save?: (isNew: boolean) => Promise<TokenProps | null>;
92
+ cancel?: () => void;
93
+ };
94
+ // show dialog to edit one item, and call update when save the result
95
+ const showTokenEditItem = async (item: TokenProps, update: (item: TokenProps) => void, isNew: boolean) => {
96
+ const handleClicked = async (index: number) => {
97
+ if (index === 1) {
98
+ updateFn.cancel?.();
99
+ modalClose();
100
+ }
101
+ if (index === 0) {
102
+ const newItem = await updateFn.save?.(isNew);
103
+ if (newItem) {
104
+ update(newItem);
105
+ modalClose();
106
+ }
107
+ }
108
+ };
109
+ const updateFn: TokenDataUpdateProps = {};
110
+ const modalClose = await ModalWindow.show({
111
+ title: 'Edit Token Data',
112
+ buttons: ['Save', 'Cancel'],
113
+ // contentMaxHeight: '400px',
114
+ handleClicked,
115
+ children: <TokenEditItem item={item} update={updateFn}></TokenEditItem>,
116
+ closeWhenClickOutside: false,
117
+ });
118
+ };
119
+
120
+ // edit one item
121
+ export const TokenEditItem = (props: { item: TokenProps; update: TokenDataUpdateProps }) => {
122
+ const ref: RefProps = { id: '' };
123
+ const css: CssProps = {
124
+ padding: '10px',
125
+ border: 'solid 1px gray',
126
+ margin: '1px',
127
+ position: 'relative',
128
+ '.lable': {
129
+ width: '90px',
130
+ },
131
+ };
132
+ props.update.save = async (isNew: boolean) => {
133
+ const token = ref.$('input.token').value;
134
+ const description = ref.$('input.description').value;
135
+ if (token && description) {
136
+ const newItem = isNew
137
+ ? await addTokenData({ token, description })
138
+ : await updateTokenData({ token, description });
139
+ props.item.token = newItem.token;
140
+ props.item.description = newItem.description;
141
+ return newItem;
142
+ }
143
+ NotificationMessage.sendMessage('Please input token and description', NotificationColor.Error);
144
+ return null;
145
+ };
146
+ return (
147
+ <div ref={ref} css={css} class='sample-data'>
148
+ <div class='row-box mt-m'>
149
+ <div class='lable'>Token: </div>
150
+ <div>
151
+ <input type='text' class='input-base token' value={props.item.token} readonly={true} />
152
+ </div>
153
+ </div>
154
+ <div class='row-box'>
155
+ <div class='lable'>Description: </div>
156
+ <div>
157
+ <input type='text' class='input-base description' value={props.item.description} />
158
+ </div>
159
+ </div>
160
+ </div>
161
+ );
162
+ };
163
+
164
+ // show one item
165
+ export const TokenShowItem = (props: { item: TokenProps }) => {
166
+ const ref: RefProps = {};
167
+ const css: CssProps = {
168
+ padding: '10px',
169
+ border: 'solid 1px gray',
170
+ margin: '1px',
171
+ position: 'relative',
172
+ '.control-box': {
173
+ display: 'none',
174
+ position: 'absolute',
175
+ right: '10px',
176
+ top: '10px',
177
+ },
178
+ '&:hover .control-box': {
179
+ display: 'block',
180
+ },
181
+ '.lable': {
182
+ width: '85px',
183
+ },
184
+ '.token': {
185
+ lineBreak: 'anywhere',
186
+ wordBreak: 'break-all',
187
+ },
188
+ };
189
+ const onEdit = (ev: any) => {
190
+ const update = (item: TokenProps) => {
191
+ dom.value = makeDom(item);
192
+ };
193
+ showTokenEditItem(props.item, update, false);
194
+ };
195
+ const onRemove = async (ev: any) => {
196
+ if (!confirm('Are you sure you want to delete this token?')) {
197
+ return;
198
+ }
199
+ await removeTokenData(props.item.token);
200
+ ref.current.remove();
201
+ };
202
+
203
+ const makeDom = (item: TokenProps) => {
204
+ const saveText = (text: string) => {
205
+ item.description = text;
206
+ updateTokenData(item);
207
+ dom.value = makeDom(item);
208
+ };
209
+ return (
210
+ <div>
211
+ <div class='control-box'>
212
+ <button class='button-base button-ss' onClick={onEdit}>
213
+ Edit
214
+ </button>
215
+ <button class='button-base button-ss' onClick={onRemove}>
216
+ Delete
217
+ </button>
218
+ </div>
219
+ <div class='row-box'>
220
+ <div class='lable'>Token: </div>
221
+ <div class='token'>{item.token}</div>
222
+ </div>
223
+ <div class='row-box'>
224
+ <div class='lable'>Description: </div>
225
+ <div class='flex-1'>
226
+ <EditableLabel text={item.description} save={saveText} type='text' />
227
+ </div>
228
+ </div>
229
+ <div class='row-box'>
230
+ <div class='lable'>Timestamp: </div>
231
+ <div>{new Date(item.timestamp || 0).toLocaleString()}</div>
232
+ </div>
233
+ </div>
234
+ );
235
+ };
236
+
237
+ const dom = new HtmlVar(makeDom(props.item));
238
+ return (
239
+ <div ref={ref} css={css} class='sample-data'>
240
+ {dom.node}
241
+ </div>
242
+ );
243
+ };
244
+
245
+ // show the list
246
+ const TokenList = () => {
247
+ let currentIndex = 0;
248
+ let searchText = '';
249
+ let maxPages = 0;
250
+ const ref: RefProps = {
251
+ onLoad: async (self: Element) => {
252
+ await makeList(currentIndex);
253
+ },
254
+ };
255
+ const onAdd = async () => {
256
+ const update = async (item: TokenProps) => {
257
+ // new item is added at the list end, so update the last page
258
+ await makeList(maxPages + 1);
259
+ };
260
+ showTokenEditItem(
261
+ {
262
+ token: await generateToken(),
263
+ description: '',
264
+ },
265
+ update,
266
+ true
267
+ );
268
+ };
269
+ const onSearch = async () => {
270
+ searchText = ref.$('input.search').value.trim();
271
+ await makeList(currentIndex);
272
+ };
273
+ const makeList = async (pageIndex: number) => {
274
+ const onLinkClick = async (index: number) => {
275
+ currentIndex = index;
276
+ await makeList(currentIndex);
277
+ };
278
+ const items = await getTokenData(pageIndex, searchText);
279
+ maxPages = Math.floor((items.itemsCount + 1) / items.pageLimit);
280
+ listDom.value = (
281
+ <div>
282
+ <PagingLink
283
+ itemsCount={items.itemsCount}
284
+ pageIndex={pageIndex}
285
+ pageLimit={items.pageLimit}
286
+ onClick={onLinkClick}
287
+ baseLink=''
288
+ ></PagingLink>
289
+ {items.result.map((item) => (
290
+ <TokenShowItem item={item}></TokenShowItem>
291
+ ))}
292
+ <PagingLink
293
+ itemsCount={items.itemsCount}
294
+ pageIndex={pageIndex}
295
+ pageLimit={items.pageLimit}
296
+ onClick={onLinkClick}
297
+ baseLink=''
298
+ ></PagingLink>
299
+ </div>
300
+ );
301
+ };
302
+ const listDom = new HtmlVar(<div></div>);
303
+ const css: CssProps = {
304
+ display: 'flex',
305
+ flexDirection: 'column',
306
+ '.label': {
307
+ width: '70px',
308
+ },
309
+ };
310
+ return (
311
+ <div ref={ref} css={css}>
312
+ <div>
313
+ <div class='row-box'>
314
+ <div class='label'>Search: </div>
315
+ <input type='text' class='input-base search' />
316
+ <button class='button-base mr-s' onClick={onSearch}>
317
+ Search
318
+ </button>
319
+ <button class='button-base' onClick={onAdd}>
320
+ Add
321
+ </button>
322
+ </div>
323
+ </div>
324
+ <div class='list'>{listDom.node}</div>
325
+ </div>
326
+ );
327
+ };
328
+
329
+ export const AdminTokensPage = () => {
330
+ return (
331
+ <div>
332
+ <div>
333
+ <div>Tokens management</div>
334
+ <TokenList />
335
+ </div>
336
+ </div>
337
+ );
338
+ };