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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/admin/admin-about.tsx +16 -0
- package/admin/admin-config.tsx +44 -0
- package/admin/admin-css.tsx +3 -0
- package/admin/admin-db.tsx +74 -0
- package/admin/admin-frame-props.tsx +9 -0
- package/admin/admin-frame.tsx +466 -0
- package/admin/admin-index.tsx +66 -0
- package/admin/admin-login.tsx +99 -0
- package/admin/admin-menu-edit.tsx +637 -0
- package/admin/admin-menu-list.tsx +87 -0
- package/admin/admin-page-edit.tsx +564 -0
- package/admin/admin-page-list.tsx +83 -0
- package/admin/admin-performance.tsx +28 -0
- package/admin/admin-release.tsx +320 -0
- package/admin/admin-resources.tsx +385 -0
- package/admin/admin-shell.tsx +89 -0
- package/admin/admin-table-data.tsx +146 -0
- package/admin/admin-table-list.tsx +231 -0
- package/admin/admin-test-animations.tsx +379 -0
- package/admin/admin-test-component.tsx +808 -0
- package/admin/admin-test-edit.tsx +319 -0
- package/admin/admin-test-themes.tsx +56 -0
- package/admin/admin-tokens.tsx +338 -0
- package/admin/design/admin-design.tsx +174 -0
- package/admin/design/block-grid.tsx +36 -0
- package/admin/design/block-grid1.tsx +21 -0
- package/admin/design/block-paragraph.tsx +19 -0
- package/admin/design/block-title.tsx +19 -0
- package/admin/design/design-block-box.tsx +140 -0
- package/admin/design/drag-data.tsx +24 -0
- package/admin/index.ts +6 -0
- package/admin/package.json +15 -0
- package/admin/tsconfig.json +127 -0
- package/dev/copy-folder.js +32 -0
- package/dev/cp-index-html.js +69 -0
- package/dev/file-utils.js +12 -0
- package/dev/index.js +19 -0
- package/dev/package.json +12 -0
- package/dev/plugin-gen-versions.js +20 -0
- package/dev/plugin-ifelse.js +155 -0
- package/dev/plugin-ifelse.test.js +37 -0
- package/dev/run-cmd.js +14 -0
- package/dev/send-request.js +12 -0
- package/package.json +55 -0
- package/src/admin-api/admin-api.ts +59 -0
- package/src/admin-api/admin-auth.ts +87 -0
- package/src/admin-api/admin-config.ts +93 -0
- package/src/admin-api/admin-csv.ts +81 -0
- package/src/admin-api/admin-db.ts +269 -0
- package/src/admin-api/admin-helper.ts +111 -0
- package/src/admin-api/admin-menu.ts +135 -0
- package/src/admin-api/admin-page.ts +135 -0
- package/src/admin-api/admin-performance.ts +128 -0
- package/src/admin-api/admin-release.ts +498 -0
- package/src/admin-api/admin-resources.ts +318 -0
- package/src/admin-api/admin-token-helper.ts +79 -0
- package/src/admin-api/admin-tokens.ts +90 -0
- package/src/admin-api/index.ts +2 -0
- package/src/api/api-cache.ts +103 -0
- package/src/api/api-helper.ts +44 -0
- package/src/api/api-module.ts +60 -0
- package/src/api/api-router.ts +177 -0
- package/src/api/api-shared-storage.ts +64 -0
- package/src/api/async-storage.ts +5 -0
- package/src/api/debug-service.ts +56 -0
- package/src/api/encode-html.ts +27 -0
- package/src/api/handle-status.ts +71 -0
- package/src/api/index.ts +16 -0
- package/src/api/mini-web-socket.ts +270 -0
- package/src/api/server-content-type.ts +82 -0
- package/src/api/server-render.ts +216 -0
- package/src/api/shell-service.ts +66 -0
- package/src/api/simple-storage.ts +80 -0
- package/src/api/static-server.ts +125 -0
- package/src/api/to-client-delivery.ts +26 -0
- package/src/app/app-cache.ts +55 -0
- package/src/app/app-loader.ts +62 -0
- package/src/app/app-message.ts +60 -0
- package/src/app/app-shared-storage.ts +317 -0
- package/src/app/app-start.ts +117 -0
- package/src/app/cleanup-exit.ts +12 -0
- package/src/app/host-to-path.ts +38 -0
- package/src/app/index.ts +11 -0
- package/src/app/process-dev-requests.ts +90 -0
- package/src/app/web-listener.ts +230 -0
- package/src/app/web-processor.ts +42 -0
- package/src/app/web-server.ts +86 -0
- package/src/common-js/web-env.js +104 -0
- package/src/index.ts +7 -0
- package/src/lang/api-lang-en.ts +27 -0
- package/src/lang/api-lang-zh-cn.ts +28 -0
- package/src/lang/index.ts +2 -0
- package/src/lang/lang-helper.ts +76 -0
- package/src/lang/lang-props.ts +6 -0
- package/src/lib/db/db-helper.ts +23 -0
- package/src/lib/db/db-mysql.ts +250 -0
- package/src/lib/db/db-sqlite.ts +101 -0
- package/src/lib/db/db.spec.ts +28 -0
- package/src/lib/db/db.ts +304 -0
- package/src/lib/db/index.ts +5 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/logger.spec.ts +214 -0
- package/src/lib/logger.ts +274 -0
- package/src/lib/runtime-require.ts +37 -0
- package/src/lib/utils/cookie-util.ts +34 -0
- package/src/lib/utils/crypto.ts +58 -0
- package/src/lib/utils/date-utils.ts +317 -0
- package/src/lib/utils/deep-merge.ts +37 -0
- package/src/lib/utils/delay.ts +12 -0
- package/src/lib/utils/file-setting.ts +55 -0
- package/src/lib/utils/format-bytes.ts +11 -0
- package/src/lib/utils/fs-utils.ts +144 -0
- package/src/lib/utils/get-env.ts +27 -0
- package/src/lib/utils/index.ts +12 -0
- package/src/lib/utils/is-type.ts +48 -0
- package/src/lib/utils/load-env.ts +14 -0
- package/src/lib/utils/pad.ts +6 -0
- package/src/models/api-base.ts +5 -0
- package/src/models/api-module-props.ts +11 -0
- package/src/models/api-router-props.ts +26 -0
- package/src/models/app-cache-props.ts +33 -0
- package/src/models/app-data-props.ts +10 -0
- package/src/models/app-loader-props.ts +6 -0
- package/src/models/app-shared-storage-props.ts +37 -0
- package/src/models/app-start-props.ts +18 -0
- package/src/models/async-storage-props.ts +13 -0
- package/src/models/db-config.ts +30 -0
- package/src/models/host-to-path-props.ts +12 -0
- package/src/models/index.ts +16 -0
- package/src/models/json-object.ts +8 -0
- package/src/models/locals-props.ts +36 -0
- package/src/models/logger-props.ts +84 -0
- package/src/models/simple-storage-props.ts +14 -0
- package/src/models/to-client-delivery-props.ts +6 -0
- package/tsconfig.json +115 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CssProps,
|
|
3
|
+
RefProps,
|
|
4
|
+
EditableLabel,
|
|
5
|
+
HtmlVar,
|
|
6
|
+
ModalWindow,
|
|
7
|
+
NotificationColor,
|
|
8
|
+
NotificationMessage,
|
|
9
|
+
PagingLink,
|
|
10
|
+
ToggleBaseHookProps,
|
|
11
|
+
ToggleSwitch,
|
|
12
|
+
ToggleSwitchSize,
|
|
13
|
+
} from 'lupine.components';
|
|
14
|
+
|
|
15
|
+
type SampleDataProps = {
|
|
16
|
+
id: number;
|
|
17
|
+
name: string;
|
|
18
|
+
info: string;
|
|
19
|
+
checked: boolean;
|
|
20
|
+
};
|
|
21
|
+
const _DEFAULT_PAGE_LIMIT = 10;
|
|
22
|
+
const _DEFAULT_ITEM_COUNT = 101;
|
|
23
|
+
const sampleData: SampleDataProps[] = Array.from({ length: _DEFAULT_ITEM_COUNT }, (_, i) => ({
|
|
24
|
+
id: i + 1,
|
|
25
|
+
name: `Book Name ${i + 1}`,
|
|
26
|
+
info: `This is the info field ${i + 1}`,
|
|
27
|
+
checked: true,
|
|
28
|
+
}));
|
|
29
|
+
const getSampleData = (pageIndex = 0, searchTexts: string[] = []) => {
|
|
30
|
+
const filterItems = sampleData.filter((item) =>
|
|
31
|
+
searchTexts.every(
|
|
32
|
+
(text) =>
|
|
33
|
+
item.name.toLowerCase().includes(text.toLowerCase()) || item.info.toLowerCase().includes(text.toLowerCase())
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
const pageLimit = _DEFAULT_PAGE_LIMIT;
|
|
37
|
+
const itemsCount = filterItems.length;
|
|
38
|
+
let maxPages = Math.floor(itemsCount / pageLimit);
|
|
39
|
+
if (itemsCount % pageLimit !== 0) {
|
|
40
|
+
maxPages++;
|
|
41
|
+
}
|
|
42
|
+
if (pageIndex > maxPages) {
|
|
43
|
+
pageIndex = maxPages - 1;
|
|
44
|
+
}
|
|
45
|
+
const offset = pageIndex * pageLimit;
|
|
46
|
+
return {
|
|
47
|
+
status: 'ok',
|
|
48
|
+
itemsCount,
|
|
49
|
+
pageIndex,
|
|
50
|
+
result: filterItems.slice(offset, offset + pageLimit),
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const removeSampleData = (itemId: number) => {
|
|
54
|
+
const index = sampleData.findIndex((item) => item.id === itemId);
|
|
55
|
+
if (index !== -1) {
|
|
56
|
+
sampleData.splice(index, 1);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const updateSampleData = (item: SampleDataProps) => {
|
|
60
|
+
const index = sampleData.findIndex((i) => i.id === item.id);
|
|
61
|
+
if (index !== -1) {
|
|
62
|
+
sampleData[index] = { ...item };
|
|
63
|
+
} else {
|
|
64
|
+
// it's a new record
|
|
65
|
+
let newId = sampleData.length + 1;
|
|
66
|
+
while (sampleData.some((item) => item.id === newId)) {
|
|
67
|
+
newId++;
|
|
68
|
+
}
|
|
69
|
+
item.id = newId;
|
|
70
|
+
sampleData.push({ ...item });
|
|
71
|
+
}
|
|
72
|
+
return item;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
type SampleDataUpdateProps = {
|
|
76
|
+
save?: () => SampleDataProps | null;
|
|
77
|
+
cancel?: () => void;
|
|
78
|
+
};
|
|
79
|
+
// show dialog to edit one item, and call update when save the result
|
|
80
|
+
const showBookEditItem = async (item: SampleDataProps, update: (item: SampleDataProps) => void) => {
|
|
81
|
+
const handleClicked = async (index: number) => {
|
|
82
|
+
if (index === 1) {
|
|
83
|
+
updateFn.cancel?.();
|
|
84
|
+
modalClose();
|
|
85
|
+
}
|
|
86
|
+
if (index === 0) {
|
|
87
|
+
const newItem = updateFn.save?.();
|
|
88
|
+
if (newItem) {
|
|
89
|
+
update(newItem);
|
|
90
|
+
modalClose();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const updateFn: SampleDataUpdateProps = {};
|
|
95
|
+
const modalClose = await ModalWindow.show({
|
|
96
|
+
title: 'Edit Sample Data',
|
|
97
|
+
buttons: ['Save', 'Cancel'],
|
|
98
|
+
// contentMaxHeight: '400px',
|
|
99
|
+
handleClicked,
|
|
100
|
+
children: <BookEditItem item={item} update={updateFn}></BookEditItem>,
|
|
101
|
+
closeWhenClickOutside: false,
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// edit one item
|
|
106
|
+
export const BookEditItem = (props: { item: SampleDataProps; update: SampleDataUpdateProps }) => {
|
|
107
|
+
const ref: RefProps = { id: '' };
|
|
108
|
+
const css: CssProps = {
|
|
109
|
+
padding: '10px',
|
|
110
|
+
border: 'solid 1px gray',
|
|
111
|
+
margin: '1px',
|
|
112
|
+
position: 'relative',
|
|
113
|
+
'.lable': {
|
|
114
|
+
width: '70px',
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
props.update.save = () => {
|
|
118
|
+
const name = ref.$('input.name').value;
|
|
119
|
+
const info = ref.$('input.info').value;
|
|
120
|
+
if (name && info) {
|
|
121
|
+
const newItem = updateSampleData({ id: props.item.id, name, info, checked: switchUpdate.getChecked!() });
|
|
122
|
+
props.item.name = newItem.name;
|
|
123
|
+
props.item.info = newItem.info;
|
|
124
|
+
props.item.checked = newItem.checked;
|
|
125
|
+
props.item.id = newItem.id;
|
|
126
|
+
return newItem;
|
|
127
|
+
}
|
|
128
|
+
NotificationMessage.sendMessage('Please input name and info', NotificationColor.Error);
|
|
129
|
+
return null;
|
|
130
|
+
};
|
|
131
|
+
const switchUpdate: ToggleBaseHookProps = {};
|
|
132
|
+
return (
|
|
133
|
+
<div ref={ref} css={css} class='sample-data'>
|
|
134
|
+
<div class='row-box'>
|
|
135
|
+
<div class='lable'>Name: </div>
|
|
136
|
+
<div>
|
|
137
|
+
<input type='text' class='input-base name' value={props.item.name} />
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div class='row-box mt-m'>
|
|
141
|
+
<div class='lable'>Info: </div>
|
|
142
|
+
<div>
|
|
143
|
+
<input type='text' class='input-base info' value={props.item.info} />
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class='row-box mt-m'>
|
|
147
|
+
<div class='lable'>Checked: </div>
|
|
148
|
+
<ToggleSwitch size={ToggleSwitchSize.Small} hook={switchUpdate} checked={props.item.checked} />
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// show one item
|
|
155
|
+
export const BookShowItem = (props: { item: SampleDataProps }) => {
|
|
156
|
+
const ref: RefProps = { id: '' };
|
|
157
|
+
const css: CssProps = {
|
|
158
|
+
padding: '10px',
|
|
159
|
+
border: 'solid 1px gray',
|
|
160
|
+
margin: '1px',
|
|
161
|
+
position: 'relative',
|
|
162
|
+
'.control-box': {
|
|
163
|
+
display: 'none',
|
|
164
|
+
position: 'absolute',
|
|
165
|
+
right: '10px',
|
|
166
|
+
top: '10px',
|
|
167
|
+
},
|
|
168
|
+
'&:hover .control-box': {
|
|
169
|
+
display: 'block',
|
|
170
|
+
},
|
|
171
|
+
'.lable': {
|
|
172
|
+
width: '70px',
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
const onEdit = (ev: any) => {
|
|
176
|
+
const update = (item: SampleDataProps) => {
|
|
177
|
+
dom.value = makeDom(item);
|
|
178
|
+
};
|
|
179
|
+
showBookEditItem(props.item, update);
|
|
180
|
+
};
|
|
181
|
+
const onRemove = (ev: any) => {
|
|
182
|
+
removeSampleData(props.item.id);
|
|
183
|
+
ref.current.remove();
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const makeDom = (item: SampleDataProps) => {
|
|
187
|
+
const saveText = (text: string) => {
|
|
188
|
+
item.name = text;
|
|
189
|
+
updateSampleData(item);
|
|
190
|
+
dom.value = makeDom(item);
|
|
191
|
+
};
|
|
192
|
+
return (
|
|
193
|
+
<>
|
|
194
|
+
<div class='control-box'>
|
|
195
|
+
<button class='button-base button-ss' onClick={onEdit}>
|
|
196
|
+
Edit
|
|
197
|
+
</button>
|
|
198
|
+
<button class='button-base button-ss' onClick={onRemove}>
|
|
199
|
+
Delete
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
<div class='row-box'>
|
|
203
|
+
<div class='lable'>Name: </div>
|
|
204
|
+
<div>{item.name}</div>
|
|
205
|
+
<div class='px-m'>Double Click to edit: </div>
|
|
206
|
+
<EditableLabel text={item.name} save={saveText} type='text' />
|
|
207
|
+
</div>
|
|
208
|
+
<div class='row-box'>
|
|
209
|
+
<div class='lable'>Info: </div>
|
|
210
|
+
<div>{item.info}</div>
|
|
211
|
+
</div>
|
|
212
|
+
<div class='row-box'>
|
|
213
|
+
<div class='lable'>Checked: </div>
|
|
214
|
+
<div>{item.checked ? 'Yes' : 'No'}</div>
|
|
215
|
+
</div>
|
|
216
|
+
</>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const dom = HtmlVar(makeDom(props.item));
|
|
221
|
+
return (
|
|
222
|
+
<div ref={ref} css={css} class='sample-data'>
|
|
223
|
+
{dom.node}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// show the list
|
|
229
|
+
export const BookList = () => {
|
|
230
|
+
let currentIndex = 0;
|
|
231
|
+
let searchTexts: string[] = [];
|
|
232
|
+
let items = getSampleData(currentIndex, searchTexts);
|
|
233
|
+
const ref: RefProps = { onLoad: async (self: Element) => {} };
|
|
234
|
+
const onAdd = async () => {
|
|
235
|
+
const update = (item: SampleDataProps) => {
|
|
236
|
+
// new item is added at the list end, so update the last page
|
|
237
|
+
currentIndex = Math.floor((items.itemsCount + 1) / _DEFAULT_PAGE_LIMIT);
|
|
238
|
+
listDom.value = makeList(currentIndex);
|
|
239
|
+
};
|
|
240
|
+
showBookEditItem(
|
|
241
|
+
{
|
|
242
|
+
id: -1,
|
|
243
|
+
name: '',
|
|
244
|
+
info: '',
|
|
245
|
+
checked: false,
|
|
246
|
+
},
|
|
247
|
+
update
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
const onSearch = () => {
|
|
251
|
+
searchTexts = ref.$('input.search').value.trim().split(' ');
|
|
252
|
+
items = getSampleData(currentIndex, searchTexts);
|
|
253
|
+
listDom.value = makeList(currentIndex);
|
|
254
|
+
};
|
|
255
|
+
const makeList = (pageIndex: number) => {
|
|
256
|
+
const onLinkClick = (index: number) => {
|
|
257
|
+
currentIndex = index;
|
|
258
|
+
listDom.value = makeList(currentIndex);
|
|
259
|
+
};
|
|
260
|
+
const items = getSampleData(pageIndex, searchTexts);
|
|
261
|
+
return (
|
|
262
|
+
<div>
|
|
263
|
+
<PagingLink
|
|
264
|
+
itemsCount={items.itemsCount}
|
|
265
|
+
pageIndex={pageIndex}
|
|
266
|
+
pageLimit={_DEFAULT_PAGE_LIMIT}
|
|
267
|
+
onClick={onLinkClick}
|
|
268
|
+
baseLink=''
|
|
269
|
+
></PagingLink>
|
|
270
|
+
{items.result.map((item) => (
|
|
271
|
+
<BookShowItem item={item}></BookShowItem>
|
|
272
|
+
))}
|
|
273
|
+
<PagingLink
|
|
274
|
+
itemsCount={items.itemsCount}
|
|
275
|
+
pageIndex={pageIndex}
|
|
276
|
+
pageLimit={_DEFAULT_PAGE_LIMIT}
|
|
277
|
+
onClick={onLinkClick}
|
|
278
|
+
baseLink=''
|
|
279
|
+
></PagingLink>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
};
|
|
283
|
+
const listDom = HtmlVar(makeList(currentIndex));
|
|
284
|
+
const css: CssProps = {
|
|
285
|
+
display: 'flex',
|
|
286
|
+
flexDirection: 'column',
|
|
287
|
+
'.label': {
|
|
288
|
+
width: '70px',
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
return (
|
|
292
|
+
<div ref={ref} css={css}>
|
|
293
|
+
<div>
|
|
294
|
+
<div class='row-box'>
|
|
295
|
+
<div class='label'>Search: </div>
|
|
296
|
+
<input type='text' class='input-base search' />
|
|
297
|
+
<button class='button-base mr-s' onClick={onSearch}>
|
|
298
|
+
Search
|
|
299
|
+
</button>
|
|
300
|
+
<button class='button-base' onClick={onAdd}>
|
|
301
|
+
Add
|
|
302
|
+
</button>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
<div class='list'>{listDom.node}</div>
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const AdminTestEditPage = () => {
|
|
311
|
+
return (
|
|
312
|
+
<div>
|
|
313
|
+
<div>
|
|
314
|
+
<div>Test editing.</div>
|
|
315
|
+
<BookList />
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CssProps, darkThemes, lightThemes } from 'lupine.components';
|
|
2
|
+
|
|
3
|
+
const TestThemes = () => {
|
|
4
|
+
const lightNames = Object.keys(lightThemes);
|
|
5
|
+
const css: CssProps = {
|
|
6
|
+
'.theme-title': {
|
|
7
|
+
width: '200px',
|
|
8
|
+
},
|
|
9
|
+
'.theme-item1': {
|
|
10
|
+
width: '10%',
|
|
11
|
+
},
|
|
12
|
+
'.theme-item2': {
|
|
13
|
+
width: '10%',
|
|
14
|
+
},
|
|
15
|
+
'.theme-item3': {
|
|
16
|
+
width: '10%',
|
|
17
|
+
},
|
|
18
|
+
'.theme-item4': {
|
|
19
|
+
width: '10%',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
return (
|
|
23
|
+
<div css={css}>
|
|
24
|
+
<div class='row-box mb-s'>
|
|
25
|
+
<div class='theme-title bold mb-s'>Theme attributes</div>
|
|
26
|
+
<div class='theme-item1 mb-s'>Light</div>
|
|
27
|
+
<div class='theme-item2 mb-s'></div>
|
|
28
|
+
<div class='theme-item3 mb-s'>Dark</div>
|
|
29
|
+
<div class='theme-item4 mb-s'></div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{lightNames.map((themeName) => (
|
|
33
|
+
<div key={themeName} class='row-box mb-s'>
|
|
34
|
+
<div class='theme-title bold mb-s'>{themeName}</div>
|
|
35
|
+
<div class='theme-item1 mb-s' style={{ color: lightThemes[themeName] }}>
|
|
36
|
+
{lightThemes[themeName]}
|
|
37
|
+
</div>
|
|
38
|
+
<div class='theme-item2 mb-s' style={{ background: lightThemes[themeName] }}>
|
|
39
|
+
{lightThemes[themeName]}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class='theme-item3 mb-s' style={{ color: darkThemes[themeName] }}>
|
|
43
|
+
{darkThemes[themeName]}
|
|
44
|
+
</div>
|
|
45
|
+
<div class='theme-item4 mb-s' style={{ background: darkThemes[themeName] }}>
|
|
46
|
+
{darkThemes[themeName]}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const TestThemesPage = () => {
|
|
55
|
+
return <TestThemes />;
|
|
56
|
+
};
|
|
@@ -0,0 +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 = 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 = 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
|
+
};
|