kempo 4.2.10 → 4.2.11
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/app-public/.config.json +2 -5
- package/bin/cli.js +2 -8
- package/dist/admin/accounts/groups/[name]/index.html +518 -0
- package/dist/admin/accounts/groups/index.html +378 -0
- package/dist/admin/accounts/permissions/index.html +397 -0
- package/dist/admin/accounts/users/[id]/index.html +541 -0
- package/dist/admin/accounts/users/index.html +396 -0
- package/dist/admin/content/fragments/edit/index.html +132 -0
- package/dist/admin/content/fragments/index.html +375 -0
- package/dist/admin/content/globals/edit/index.html +132 -0
- package/dist/admin/content/globals/index.html +358 -0
- package/dist/admin/content/pages/edit/index.html +132 -0
- package/dist/admin/content/pages/index.html +509 -0
- package/dist/admin/content/templates/edit/index.html +132 -0
- package/dist/admin/content/templates/index.html +429 -0
- package/dist/admin/extensions/index.html +222 -0
- package/dist/admin/index.html +130 -0
- package/dist/admin/index.page.html +1 -0
- package/dist/admin/settings/[owner]/[name]/index.html +449 -0
- package/dist/admin/settings/index.html +410 -0
- package/middleware/{extension-scope-router.js → kempo.js} +40 -25
- package/package.json +3 -3
- package/middleware/kempo-auth.js +0 -26
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
owner: system
|
|
3
|
+
locked: true
|
|
4
|
+
name:
|
|
5
|
+
author:
|
|
6
|
+
created:
|
|
7
|
+
updated:
|
|
8
|
+
-->
|
|
9
|
+
<!DOCTYPE html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="UTF-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
+
<link rel="icon" type="image/png" sizes="48x48" href="/media/icon48.png">
|
|
15
|
+
<link rel="manifest" href="/manifest.json">
|
|
16
|
+
<title>Groups - Admin</title>
|
|
17
|
+
<link rel="stylesheet" href="/kempo-css/kempo.min.css">
|
|
18
|
+
<script src="/admin/init.js"></script>
|
|
19
|
+
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<!--
|
|
23
|
+
locked: true
|
|
24
|
+
-->
|
|
25
|
+
|
|
26
|
+
<k-aside
|
|
27
|
+
main="push"
|
|
28
|
+
state="expanded"
|
|
29
|
+
style="--aside_padding: 0;"
|
|
30
|
+
persistent-id="admin-nav"
|
|
31
|
+
>
|
|
32
|
+
<k-aside-toggle>
|
|
33
|
+
<h3 class="m0">Kempo Admin</h3>
|
|
34
|
+
</k-aside-toggle>
|
|
35
|
+
<k-aside-item
|
|
36
|
+
icon="preview"
|
|
37
|
+
href="/"
|
|
38
|
+
no-expand
|
|
39
|
+
>Site</k-aside-item>
|
|
40
|
+
<k-aside-item
|
|
41
|
+
icon="cards"
|
|
42
|
+
href="/admin"
|
|
43
|
+
no-expand
|
|
44
|
+
>Dashboard</k-aside-item>
|
|
45
|
+
<k-aside-menu
|
|
46
|
+
icon="manage-accounts"
|
|
47
|
+
label="Accounts"
|
|
48
|
+
>
|
|
49
|
+
<k-aside-item
|
|
50
|
+
icon="person"
|
|
51
|
+
href="/admin/accounts/users"
|
|
52
|
+
no-expand
|
|
53
|
+
>Users</k-aside-item>
|
|
54
|
+
<k-aside-item
|
|
55
|
+
icon="group"
|
|
56
|
+
href="/admin/accounts/groups"
|
|
57
|
+
no-expand
|
|
58
|
+
>Groups</k-aside-item>
|
|
59
|
+
<k-aside-item
|
|
60
|
+
icon="lock"
|
|
61
|
+
href="/admin/accounts/permissions"
|
|
62
|
+
no-expand
|
|
63
|
+
>Permissions</k-aside-item>
|
|
64
|
+
</k-aside-menu>
|
|
65
|
+
<k-aside-menu
|
|
66
|
+
icon="cards_stack"
|
|
67
|
+
label="Content"
|
|
68
|
+
>
|
|
69
|
+
<k-aside-item
|
|
70
|
+
icon="browse"
|
|
71
|
+
href="/admin/content/templates"
|
|
72
|
+
>Templates</k-aside-item>
|
|
73
|
+
<k-aside-item
|
|
74
|
+
icon="docs"
|
|
75
|
+
href="/admin/content/pages"
|
|
76
|
+
>Pages</k-aside-item>
|
|
77
|
+
<k-aside-item
|
|
78
|
+
icon="content"
|
|
79
|
+
href="/admin/content/fragments"
|
|
80
|
+
>Fragments</k-aside-item>
|
|
81
|
+
<k-aside-item
|
|
82
|
+
icon="globe"
|
|
83
|
+
href="/admin/content/globals"
|
|
84
|
+
>Global Content</k-aside-item>
|
|
85
|
+
</k-aside-menu>
|
|
86
|
+
<k-aside-item
|
|
87
|
+
icon="settings"
|
|
88
|
+
href="/admin/settings"
|
|
89
|
+
no-expand
|
|
90
|
+
>Settings</k-aside-item>
|
|
91
|
+
<k-aside-item
|
|
92
|
+
icon="extension"
|
|
93
|
+
href="/admin/extensions"
|
|
94
|
+
no-expand
|
|
95
|
+
>Extensions</k-aside-item>
|
|
96
|
+
<hr>
|
|
97
|
+
<k-aside-spacer></k-aside-spacer>
|
|
98
|
+
<k-theme-switcher
|
|
99
|
+
labels
|
|
100
|
+
style="--padding:var(--spacer_h);margin:var(--spacer_h) auto"
|
|
101
|
+
></k-theme-switcher>
|
|
102
|
+
</k-aside>
|
|
103
|
+
<script type="module">
|
|
104
|
+
import '/kempo-ui/components/ThemeSwitcher.js';
|
|
105
|
+
const items = document.querySelectorAll('k-aside-item');
|
|
106
|
+
const currentPath = window.location.pathname;
|
|
107
|
+
|
|
108
|
+
items.forEach(item => {
|
|
109
|
+
const href = item.getAttribute('href');
|
|
110
|
+
if (!href) return;
|
|
111
|
+
const isExact = href === '/admin' || href === '/';
|
|
112
|
+
const match = isExact
|
|
113
|
+
? (currentPath === href || currentPath === href + '/')
|
|
114
|
+
: (currentPath === href || currentPath.startsWith(href + '/'));
|
|
115
|
+
if (match) {
|
|
116
|
+
item.setAttribute('active', '');
|
|
117
|
+
const menu = item.closest('k-aside-menu');
|
|
118
|
+
if (menu) menu.setAttribute('open', '');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<k-main>
|
|
124
|
+
|
|
125
|
+
<script type="module" src="/kempo-ui/components/Table.js"></script>
|
|
126
|
+
<script type="module" src="/kempo-ui/components/tableControls/FirstPage.js"></script>
|
|
127
|
+
<script type="module" src="/kempo-ui/components/tableControls/PrevPage.js"></script>
|
|
128
|
+
<script type="module" src="/kempo-ui/components/tableControls/NextPage.js"></script>
|
|
129
|
+
<script type="module" src="/kempo-ui/components/tableControls/LastPage.js"></script>
|
|
130
|
+
<script type="module" src="/kempo-ui/components/tableControls/PageSelect.js"></script>
|
|
131
|
+
<script type="module" src="/kempo-ui/components/tableControls/PageSize.js"></script>
|
|
132
|
+
<script type="module" src="/kempo-ui/components/tableControls/Edit.js"></script>
|
|
133
|
+
<script type="module" src="/kempo-ui/components/tableControls/DeleteRecord.js"></script>
|
|
134
|
+
<script type="module" src="/kempo-ui/components/tableControls/DeleteSelected.js"></script>
|
|
135
|
+
<script type="module" src="/admin/components/TableRowLink.js"></script>
|
|
136
|
+
<script type="module" src="/kempo/components/Permission.js"></script>
|
|
137
|
+
|
|
138
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
139
|
+
<h1 class="flex mb0">Groups</h1>
|
|
140
|
+
<k-permission has="system:group:create">
|
|
141
|
+
<button id="createGroupBtn" class="primary">Create Group</button>
|
|
142
|
+
</k-permission>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<h2>Custom Groups</h2>
|
|
146
|
+
<k-table id="customTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No custom groups have been created yet.">
|
|
147
|
+
<admin-table-row-link slot="before" url-pattern="/admin/accounts/groups/{name}" field-name="name"></admin-table-row-link>
|
|
148
|
+
<div slot="top" class="d-f full" style="align-items: center;">
|
|
149
|
+
<div>
|
|
150
|
+
<k-permission has="system:group:delete">
|
|
151
|
+
<k-tc-delete-selected></k-tc-delete-selected>
|
|
152
|
+
</k-permission>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="d-f full" style="align-items: center;">
|
|
156
|
+
<div>
|
|
157
|
+
<k-tc-first-page></k-tc-first-page>
|
|
158
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
159
|
+
<k-tc-next-page></k-tc-next-page>
|
|
160
|
+
<k-tc-last-page></k-tc-last-page>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="flex"></div>
|
|
163
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
164
|
+
<div class="flex"></div>
|
|
165
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
166
|
+
</div>
|
|
167
|
+
</k-table>
|
|
168
|
+
|
|
169
|
+
<h2>Extension Groups</h2>
|
|
170
|
+
<k-table id="extensionTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No extension groups found.">
|
|
171
|
+
<admin-table-row-link slot="before" url-pattern="/admin/accounts/groups/{name}" field-name="name"></admin-table-row-link>
|
|
172
|
+
<div class="d-f full" style="align-items: center;">
|
|
173
|
+
<div>
|
|
174
|
+
<k-tc-first-page></k-tc-first-page>
|
|
175
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
176
|
+
<k-tc-next-page></k-tc-next-page>
|
|
177
|
+
<k-tc-last-page></k-tc-last-page>
|
|
178
|
+
</div>
|
|
179
|
+
<div class="flex"></div>
|
|
180
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
181
|
+
<div class="flex"></div>
|
|
182
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
183
|
+
</div>
|
|
184
|
+
</k-table>
|
|
185
|
+
|
|
186
|
+
<h2>System Groups</h2>
|
|
187
|
+
<k-table id="systemTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No system groups found.">
|
|
188
|
+
<admin-table-row-link slot="before" url-pattern="/admin/accounts/groups/{name}" field-name="name"></admin-table-row-link>
|
|
189
|
+
<div class="d-f full" style="align-items: center;">
|
|
190
|
+
<div>
|
|
191
|
+
<k-tc-first-page></k-tc-first-page>
|
|
192
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
193
|
+
<k-tc-next-page></k-tc-next-page>
|
|
194
|
+
<k-tc-last-page></k-tc-last-page>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="flex"></div>
|
|
197
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
198
|
+
<div class="flex"></div>
|
|
199
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
200
|
+
</div>
|
|
201
|
+
</k-table>
|
|
202
|
+
|
|
203
|
+
<script type="module">
|
|
204
|
+
import { listGroups, createGroup, updateGroup, deleteGroups, getAllCurrentUserPermissions } from '/kempo/sdk.js';
|
|
205
|
+
import { html } from '/kempo-ui/lit-all.min.js';
|
|
206
|
+
import Toast from '/kempo-ui/components/Toast.js';
|
|
207
|
+
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
208
|
+
|
|
209
|
+
await window.customElements.whenDefined('k-table');
|
|
210
|
+
|
|
211
|
+
const [, permData] = await getAllCurrentUserPermissions();
|
|
212
|
+
const myPerms = new Set(permData?.permissions ?? []);
|
|
213
|
+
const canEdit = myPerms.has('system:group:update');
|
|
214
|
+
const canDelete = myPerms.has('system:group:delete');
|
|
215
|
+
|
|
216
|
+
const fields = [
|
|
217
|
+
{
|
|
218
|
+
name: 'groupName',
|
|
219
|
+
label: 'Name',
|
|
220
|
+
size: 300,
|
|
221
|
+
editable: false,
|
|
222
|
+
calculator: r => r.name.includes(':') ? r.name.split(':').slice(1).join(':') : r.name
|
|
223
|
+
},
|
|
224
|
+
{ name: 'description', label: 'Description', editable: canEdit },
|
|
225
|
+
{
|
|
226
|
+
name: 'createdAt',
|
|
227
|
+
label: 'Created',
|
|
228
|
+
formatter: v => new Date(v).toLocaleString(),
|
|
229
|
+
size: 208,
|
|
230
|
+
editable: false
|
|
231
|
+
}
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
const customFields = [
|
|
235
|
+
{ name: 'groupName', label: 'Name', size: 300, editable: canEdit },
|
|
236
|
+
{ name: 'description', label: 'Description', editable: canEdit },
|
|
237
|
+
{
|
|
238
|
+
name: 'createdAt',
|
|
239
|
+
label: 'Created',
|
|
240
|
+
formatter: v => new Date(v).toLocaleString(),
|
|
241
|
+
size: 208,
|
|
242
|
+
editable: false
|
|
243
|
+
}
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const addGroupName = r => ({ ...r, groupName: r.name.includes(':') ? r.name.split(':').slice(1).join(':') : r.name });
|
|
247
|
+
|
|
248
|
+
const setupTable = ($table, owner, loadFn) => {
|
|
249
|
+
if(canEdit && owner !== 'system') { // Prevent editor control for system groups
|
|
250
|
+
$table.setAttribute('request-edit', '');
|
|
251
|
+
const edit = document.createElement('k-tc-edit');
|
|
252
|
+
edit.setAttribute('slot', 'after');
|
|
253
|
+
$table.appendChild(edit);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if(canDelete && owner === 'custom'){
|
|
257
|
+
$table.setAttribute('request-delete', '');
|
|
258
|
+
$table.setAttribute('enable-selection', '');
|
|
259
|
+
const del = document.createElement('k-tc-delete-record');
|
|
260
|
+
del.setAttribute('slot', 'after');
|
|
261
|
+
$table.appendChild(del);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
$table.addEventListener('requestSave', async ({ detail: { record, newData, approve, reject } }) => {
|
|
265
|
+
const patch = { name: record.name, description: newData.description };
|
|
266
|
+
if(owner === 'custom' && newData.groupName !== undefined && newData.groupName !== record.groupName){
|
|
267
|
+
patch.newName = `custom:${newData.groupName}`;
|
|
268
|
+
}
|
|
269
|
+
const [error] = await updateGroup(patch);
|
|
270
|
+
if(error){
|
|
271
|
+
reject();
|
|
272
|
+
Toast.error(error.msg || 'Failed to update group');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
approve();
|
|
276
|
+
if(patch.newName) loadFn();
|
|
277
|
+
Toast.success('Group updated');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if(owner === 'custom'){
|
|
281
|
+
$table.addEventListener('requestDelete', ({ detail: { records, approve, reject } }) => {
|
|
282
|
+
const displayName = r => r.name.includes(':') ? r.name.split(':').slice(1).join(':') : r.name;
|
|
283
|
+
const count = records.length;
|
|
284
|
+
const msg = count === 1
|
|
285
|
+
? `<div class="p"><p>Delete "${displayName(records[0])}"? This action cannot be undone.</p></div>`
|
|
286
|
+
: `<div class="p"><p>Delete ${count} groups? This action cannot be undone.</p></div>`;
|
|
287
|
+
Dialog.confirm(msg, async (confirmed) => {
|
|
288
|
+
if(!confirmed){ reject(); return; }
|
|
289
|
+
const [error] = await deleteGroups(records.map(r => r.name));
|
|
290
|
+
if(error){
|
|
291
|
+
reject();
|
|
292
|
+
Toast.error(error.msg || 'Failed to delete');
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
approve();
|
|
296
|
+
Toast.success(count === 1 ? 'Group deleted' : `${count} groups deleted`);
|
|
297
|
+
loadFn();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const loadTable = async ($table, owner) => {
|
|
304
|
+
const [error, data] = await listGroups({ owner });
|
|
305
|
+
if(error){
|
|
306
|
+
Toast.error(error.msg || 'Failed to load groups');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const isCustom = owner === 'custom';
|
|
310
|
+
const records = isCustom ? data.groups.map(addGroupName) : data.groups;
|
|
311
|
+
$table.setData({ records, fields: isCustom ? customFields : fields });
|
|
312
|
+
$table.setupFetchRecords(data.total, async (start, count) => {
|
|
313
|
+
const [err, d] = await listGroups({ limit: count, offset: start, owner });
|
|
314
|
+
if(err) return [];
|
|
315
|
+
return isCustom ? d.groups.map(addGroupName) : d.groups;
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const $customTable = document.getElementById('customTable');
|
|
320
|
+
const $extensionTable = document.getElementById('extensionTable');
|
|
321
|
+
const $systemTable = document.getElementById('systemTable');
|
|
322
|
+
|
|
323
|
+
setupTable($customTable, 'custom', () => loadTable($customTable, 'custom'));
|
|
324
|
+
setupTable($extensionTable, 'extension', () => loadTable($extensionTable, 'extension'));
|
|
325
|
+
setupTable($systemTable, 'system', () => loadTable($systemTable, 'system'));
|
|
326
|
+
|
|
327
|
+
await Promise.all([
|
|
328
|
+
loadTable($customTable, 'custom'),
|
|
329
|
+
loadTable($extensionTable, 'extension'),
|
|
330
|
+
loadTable($systemTable, 'system')
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
if(myPerms.has('system:group:create')){
|
|
334
|
+
document.getElementById('createGroupBtn').addEventListener('click', () => {
|
|
335
|
+
const $dialog = Dialog.create(html`
|
|
336
|
+
<div class="p">
|
|
337
|
+
<div class="mb">
|
|
338
|
+
<label class="d-b mb-sm">Name</label>
|
|
339
|
+
<input type="text" id="dlg-name" class="full" required>
|
|
340
|
+
</div>
|
|
341
|
+
<div>
|
|
342
|
+
<label class="d-b mb-sm">Description</label>
|
|
343
|
+
<input type="text" id="dlg-description" class="full">
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
`, {
|
|
347
|
+
title: 'Create Group',
|
|
348
|
+
confirmText: 'Create',
|
|
349
|
+
confirmAction: async () => {
|
|
350
|
+
const name = $dialog.querySelector('#dlg-name').value.trim();
|
|
351
|
+
const description = $dialog.querySelector('#dlg-description').value.trim();
|
|
352
|
+
if(!name){
|
|
353
|
+
Toast.error('Group name is required');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if(name.includes(':')){
|
|
357
|
+
Toast.error('Group name cannot contain ":"');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const [error] = await createGroup({ name, description });
|
|
361
|
+
if(error){
|
|
362
|
+
Toast.error(error.msg || 'Failed to create group');
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
Toast.success('Group created');
|
|
366
|
+
loadTable($customTable, 'custom');
|
|
367
|
+
},
|
|
368
|
+
cancelText: 'Cancel'
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
</script>
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
</k-main>
|
|
376
|
+
|
|
377
|
+
</body>
|
|
378
|
+
</html>
|