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
package/app-public/.config.json
CHANGED
|
@@ -4,14 +4,11 @@
|
|
|
4
4
|
"/kempo-ui/icons/**": "../node_modules/kempo-ui/icons/**",
|
|
5
5
|
"/kempo-ui/**": "../node_modules/kempo-ui/dist/**",
|
|
6
6
|
"/monaco-editor/**": "../node_modules/monaco-editor/min/**",
|
|
7
|
-
"/kempo/**": "../node_modules/kempo/dist/kempo/**"
|
|
8
|
-
"/admin": "../node_modules/kempo/dist/admin/",
|
|
9
|
-
"/admin/**": "../node_modules/kempo/dist/admin/**"
|
|
7
|
+
"/kempo/**": "../node_modules/kempo/dist/kempo/**"
|
|
10
8
|
},
|
|
11
9
|
"middleware": {
|
|
12
10
|
"custom": [
|
|
13
|
-
"../middleware/kempo
|
|
14
|
-
"../middleware/extension-scope-router.js"
|
|
11
|
+
"../node_modules/kempo/middleware/kempo.js"
|
|
15
12
|
]
|
|
16
13
|
},
|
|
17
14
|
"templating": {
|
package/bin/cli.js
CHANGED
|
@@ -117,13 +117,13 @@ if(existsSync(join(projectDir, 'public'))){
|
|
|
117
117
|
const { confirm } = await inquirer.prompt([{
|
|
118
118
|
type: 'confirm',
|
|
119
119
|
name: 'confirm',
|
|
120
|
-
message: 'This will delete public/, server/, templates/,
|
|
120
|
+
message: 'This will delete public/, server/, templates/, .env, drizzle.config.js, and docker-compose.yml. Are you sure?',
|
|
121
121
|
default: false
|
|
122
122
|
}]);
|
|
123
123
|
|
|
124
124
|
if(!confirm) process.exit(0);
|
|
125
125
|
|
|
126
|
-
for(const item of ['public', 'server', 'templates', '
|
|
126
|
+
for(const item of ['public', 'server', 'templates', '.env', 'docker-compose.yml', 'drizzle.config.js']){
|
|
127
127
|
const p = join(projectDir, item);
|
|
128
128
|
if(existsSync(p)) rmSync(p, { recursive: true, force: true });
|
|
129
129
|
}
|
|
@@ -312,12 +312,6 @@ if(!existsSync(templatesDir) && existsSync(templatesSource)){
|
|
|
312
312
|
console.log('Scaffolded templates/');
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
const extensionsDir = join(projectDir, 'extensions');
|
|
316
|
-
if(!existsSync(extensionsDir)){
|
|
317
|
-
mkdirSync(extensionsDir);
|
|
318
|
-
console.log('Created extensions/');
|
|
319
|
-
}
|
|
320
|
-
|
|
321
315
|
const drizzleConfigPath = join(projectDir, 'drizzle.config.js');
|
|
322
316
|
const scaffoldDrizzle = join(moduleRoot, 'app-drizzle.config.js');
|
|
323
317
|
if(!existsSync(drizzleConfigPath) && existsSync(scaffoldDrizzle)){
|
|
@@ -0,0 +1,518 @@
|
|
|
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>Group - 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/Icon.js"></script>
|
|
126
|
+
<script type="module" src="/kempo-ui/components/Table.js"></script>
|
|
127
|
+
<script type="module" src="/kempo-ui/components/tableControls/FirstPage.js"></script>
|
|
128
|
+
<script type="module" src="/kempo-ui/components/tableControls/PrevPage.js"></script>
|
|
129
|
+
<script type="module" src="/kempo-ui/components/tableControls/NextPage.js"></script>
|
|
130
|
+
<script type="module" src="/kempo-ui/components/tableControls/LastPage.js"></script>
|
|
131
|
+
<script type="module" src="/kempo-ui/components/tableControls/PageSelect.js"></script>
|
|
132
|
+
<script type="module" src="/kempo-ui/components/tableControls/PageSize.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/tableControls/AddMember.js"></script>
|
|
136
|
+
<script type="module" src="/admin/components/tableControls/AddPermission.js"></script>
|
|
137
|
+
<script type="module" src="/admin/components/TableRowLink.js"></script>
|
|
138
|
+
|
|
139
|
+
<div class="mb d-f" style="align-items: flex-start; justify-content: space-between;">
|
|
140
|
+
<div>
|
|
141
|
+
<a href="/admin/accounts/groups" class="no-link">
|
|
142
|
+
<button class="mbh"><k-icon name="arrow" direction="left"></k-icon> Groups</button>
|
|
143
|
+
</a>
|
|
144
|
+
<h1 class="mb0" id="pageTitle">Group</h1>
|
|
145
|
+
</div>
|
|
146
|
+
<button id="saveBtn" class="primary" disabled style="display:none;"><k-icon name="save"></k-icon> Save</button>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div id="loading">Loading group...</div>
|
|
150
|
+
<div id="notFound" style="display: none;">
|
|
151
|
+
<p>Group not found.</p>
|
|
152
|
+
<a href="/admin/accounts/groups">Back to Groups</a>
|
|
153
|
+
</div>
|
|
154
|
+
<div id="content" style="display: none;">
|
|
155
|
+
|
|
156
|
+
<!-- Group Information -->
|
|
157
|
+
<div class="card">
|
|
158
|
+
<h2>Information</h2>
|
|
159
|
+
<div class="d-f mb">
|
|
160
|
+
<label style="min-width: 120px;"><strong>Owner</strong></label>
|
|
161
|
+
<span id="groupOwner"></span>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="d-f mb">
|
|
164
|
+
<label for="groupNameInput" style="min-width: 120px;"><strong>Name</strong></label>
|
|
165
|
+
<input type="text" id="groupNameInput" style="max-width: 400px;">
|
|
166
|
+
</div>
|
|
167
|
+
<div class="d-f mb">
|
|
168
|
+
<label for="groupDescription" style="min-width: 120px;"><strong>Description</strong></label>
|
|
169
|
+
<input type="text" id="groupDescription" style="max-width: 400px;">
|
|
170
|
+
</div>
|
|
171
|
+
<div class="d-f mb">
|
|
172
|
+
<label style="min-width: 120px;"><strong>Created</strong></label>
|
|
173
|
+
<span id="groupCreatedAt"></span>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="d-f mb">
|
|
176
|
+
<label style="min-width: 120px;"><strong>Updated</strong></label>
|
|
177
|
+
<span id="groupUpdatedAt"></span>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<!-- Members -->
|
|
182
|
+
<div class="card">
|
|
183
|
+
<h2>Members</h2>
|
|
184
|
+
<p id="usersMembersMsg" style="display:none">All users are automatically members of this group.</p>
|
|
185
|
+
<k-table id="membersTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="This group has no members.">
|
|
186
|
+
<admin-table-row-link slot="before" url-pattern="/admin/accounts/users/{id}" field-name="id"></admin-table-row-link>
|
|
187
|
+
<k-tc-delete-selected id="tcDeleteSelected" slot="top" style="display:none;"></k-tc-delete-selected>
|
|
188
|
+
<admin-add-member id="tcAddMember" slot="top" style="display:none;"></admin-add-member>
|
|
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
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<!-- Permissions -->
|
|
205
|
+
<div class="card">
|
|
206
|
+
<h2>Permissions</h2>
|
|
207
|
+
<p id="adminPermsMsg" style="display:none">Members of this group automatically have all permissions.</p>
|
|
208
|
+
<k-table id="permissionsTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No permissions assigned to this group.">
|
|
209
|
+
<k-tc-delete-selected id="tcDeleteSelectedPerm" slot="top" style="display:none;"></k-tc-delete-selected>
|
|
210
|
+
<admin-add-permission id="tcAddPermission" slot="top" style="display:none;"></admin-add-permission>
|
|
211
|
+
<div class="d-f full" style="align-items: center;">
|
|
212
|
+
<div>
|
|
213
|
+
<k-tc-first-page></k-tc-first-page>
|
|
214
|
+
<k-tc-prev-page></k-tc-prev-page>
|
|
215
|
+
<k-tc-next-page></k-tc-next-page>
|
|
216
|
+
<k-tc-last-page></k-tc-last-page>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="flex"></div>
|
|
219
|
+
<div><k-tc-page-select></k-tc-page-select></div>
|
|
220
|
+
<div class="flex"></div>
|
|
221
|
+
<div><k-tc-page-size></k-tc-page-size></div>
|
|
222
|
+
</div>
|
|
223
|
+
</k-table>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<script type="module">
|
|
229
|
+
import {
|
|
230
|
+
getGroup, updateGroup, deleteGroups, getGroupMembers,
|
|
231
|
+
removeMemberFromGroup, getGroupPermissions, removePermissionFromGroup,
|
|
232
|
+
getAllCurrentUserPermissions
|
|
233
|
+
} from '/kempo/sdk.js';
|
|
234
|
+
import Toast from '/kempo-ui/components/Toast.js';
|
|
235
|
+
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
236
|
+
|
|
237
|
+
const groupName = decodeURIComponent(
|
|
238
|
+
window.location.pathname.split('/admin/accounts/groups/')[1]?.replace(/\/$/, '') || ''
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if(!groupName){
|
|
242
|
+
document.getElementById('loading').style.display = 'none';
|
|
243
|
+
document.getElementById('notFound').style.display = '';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/*
|
|
247
|
+
Permission checks
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
const [, permResult] = await getAllCurrentUserPermissions();
|
|
251
|
+
const myPerms = new Set(permResult?.permissions ?? []);
|
|
252
|
+
const canEdit = myPerms.has('system:group:update');
|
|
253
|
+
const canDelete = myPerms.has('system:group:delete');
|
|
254
|
+
const canReadPerms = myPerms.has('system:permissions:read');
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
Load group
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
const [groupError, groupData] = await getGroup(groupName);
|
|
261
|
+
|
|
262
|
+
document.getElementById('loading').style.display = 'none';
|
|
263
|
+
|
|
264
|
+
if(groupError){
|
|
265
|
+
document.getElementById('notFound').style.display = '';
|
|
266
|
+
} else {
|
|
267
|
+
document.getElementById('content').style.display = '';
|
|
268
|
+
const displayName = groupName.includes(':') ? groupName.split(':').slice(1).join(':') : groupName;
|
|
269
|
+
document.getElementById('pageTitle').textContent = `Group: ${displayName}`;
|
|
270
|
+
document.title = `${displayName} - Admin`;
|
|
271
|
+
|
|
272
|
+
const owner = groupName.includes(':') ? groupName.split(':')[0] : '';
|
|
273
|
+
const isAdmin = owner === 'admin' || !owner;
|
|
274
|
+
|
|
275
|
+
await customElements.whenDefined('k-table');
|
|
276
|
+
|
|
277
|
+
/*
|
|
278
|
+
Group Information
|
|
279
|
+
*/
|
|
280
|
+
|
|
281
|
+
const isCustom = owner === 'custom';
|
|
282
|
+
const $saveBtn = document.getElementById('saveBtn');
|
|
283
|
+
const $groupOwner = document.getElementById('groupOwner');
|
|
284
|
+
const $groupNameInput = document.getElementById('groupNameInput');
|
|
285
|
+
const $groupDescription = document.getElementById('groupDescription');
|
|
286
|
+
|
|
287
|
+
$groupOwner.textContent = owner || '(none)';
|
|
288
|
+
$groupNameInput.value = displayName;
|
|
289
|
+
$groupDescription.value = groupData.description || '';
|
|
290
|
+
document.getElementById('groupCreatedAt').textContent = new Date(groupData.createdAt).toLocaleString();
|
|
291
|
+
document.getElementById('groupUpdatedAt').textContent = groupData.updatedAt ? new Date(groupData.updatedAt).toLocaleString() : 'Never';
|
|
292
|
+
if(!canEdit || !isCustom){
|
|
293
|
+
$groupNameInput.readOnly = true;
|
|
294
|
+
$groupDescription.readOnly = true;
|
|
295
|
+
} else {
|
|
296
|
+
$saveBtn.style.display = '';
|
|
297
|
+
|
|
298
|
+
const origName = displayName;
|
|
299
|
+
const origDescription = groupData.description || '';
|
|
300
|
+
|
|
301
|
+
const handleBeforeUnload = e => { e.preventDefault(); e.returnValue = ''; };
|
|
302
|
+
|
|
303
|
+
const checkDirty = () => {
|
|
304
|
+
const dirty = $groupNameInput.value !== origName || $groupDescription.value !== origDescription;
|
|
305
|
+
$saveBtn.disabled = !dirty;
|
|
306
|
+
if(dirty){
|
|
307
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
308
|
+
} else {
|
|
309
|
+
window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
$groupNameInput.addEventListener('input', checkDirty);
|
|
314
|
+
$groupDescription.addEventListener('input', checkDirty);
|
|
315
|
+
|
|
316
|
+
$saveBtn.addEventListener('click', async () => {
|
|
317
|
+
const newDisplayName = $groupNameInput.value.trim();
|
|
318
|
+
if(!newDisplayName){
|
|
319
|
+
Toast.error('Name is required');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const newFullName = `${owner}:${newDisplayName}`;
|
|
323
|
+
const patch = { name: groupName, description: $groupDescription.value };
|
|
324
|
+
if(newFullName !== groupName) patch.newName = newFullName;
|
|
325
|
+
const [error] = await updateGroup(patch);
|
|
326
|
+
if(error){
|
|
327
|
+
Toast.error(error.msg || 'Failed to save group');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
331
|
+
Toast.success('Group saved');
|
|
332
|
+
if(newFullName !== groupName){
|
|
333
|
+
window.location.href = `/admin/accounts/groups/${encodeURIComponent(newFullName)}`;
|
|
334
|
+
} else {
|
|
335
|
+
$saveBtn.disabled = true;
|
|
336
|
+
document.getElementById('groupUpdatedAt').textContent = new Date().toLocaleString();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/*
|
|
342
|
+
Members
|
|
343
|
+
*/
|
|
344
|
+
|
|
345
|
+
const $membersTable = document.getElementById('membersTable');
|
|
346
|
+
|
|
347
|
+
if(groupName === 'system:Users'){
|
|
348
|
+
document.getElementById('usersMembersMsg').style.display = '';
|
|
349
|
+
$membersTable.style.display = 'none';
|
|
350
|
+
} else {
|
|
351
|
+
const $tcDeleteSelected = document.getElementById('tcDeleteSelected');
|
|
352
|
+
const $tcAddMember = document.getElementById('tcAddMember');
|
|
353
|
+
$tcAddMember.groupName = groupName;
|
|
354
|
+
|
|
355
|
+
if(canEdit){
|
|
356
|
+
$membersTable.setAttribute('request-delete', '');
|
|
357
|
+
$membersTable.setAttribute('enable-selection', '');
|
|
358
|
+
$tcDeleteSelected.style.display = '';
|
|
359
|
+
$tcAddMember.style.display = '';
|
|
360
|
+
|
|
361
|
+
const del = document.createElement('k-tc-delete-record');
|
|
362
|
+
del.setAttribute('slot', 'after');
|
|
363
|
+
$membersTable.appendChild(del);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
$membersTable.addEventListener('memberAdded', e => loadMembers());
|
|
367
|
+
|
|
368
|
+
$membersTable.addEventListener('requestDelete', ({ detail: { records, approve, reject } }) => {
|
|
369
|
+
const count = records.length;
|
|
370
|
+
const msg = count === 1
|
|
371
|
+
? `<div class="p"><p>Remove "${records[0].name}" from this group?</p></div>`
|
|
372
|
+
: `<div class="p"><p>Remove ${count} members from this group?</p></div>`;
|
|
373
|
+
Dialog.confirm(msg, async (confirmed) => {
|
|
374
|
+
if(!confirmed){ reject(); return; }
|
|
375
|
+
const errors = [];
|
|
376
|
+
for(const record of records){
|
|
377
|
+
const [error] = await removeMemberFromGroup(groupName, record.id);
|
|
378
|
+
if(error) errors.push(error.msg);
|
|
379
|
+
}
|
|
380
|
+
if(errors.length){
|
|
381
|
+
reject();
|
|
382
|
+
Toast.error(errors[0] || 'Failed to remove member');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
approve();
|
|
386
|
+
Toast.success(count === 1 ? 'Member removed' : `${count} members removed`);
|
|
387
|
+
loadMembers();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const loadMembers = async () => {
|
|
392
|
+
const [error, data] = await getGroupMembers(groupName);
|
|
393
|
+
if(error){
|
|
394
|
+
Toast.error(error.msg || 'Failed to load members');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
$membersTable.setData({
|
|
398
|
+
records: data.members,
|
|
399
|
+
fields: [
|
|
400
|
+
{ name: 'name', label: 'Name' },
|
|
401
|
+
{ name: 'email', label: 'Email' },
|
|
402
|
+
{
|
|
403
|
+
name: 'createdAt',
|
|
404
|
+
label: 'Joined',
|
|
405
|
+
formatter: v => new Date(v).toLocaleString()
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
});
|
|
409
|
+
$membersTable.setupFetchRecords(data.total, async (start, count) => {
|
|
410
|
+
const [err, d] = await getGroupMembers(groupName, { limit: count, offset: start });
|
|
411
|
+
if(err) return [];
|
|
412
|
+
return d.members;
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
loadMembers();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/*
|
|
420
|
+
Permissions
|
|
421
|
+
*/
|
|
422
|
+
|
|
423
|
+
const $permissionsTable = document.getElementById('permissionsTable');
|
|
424
|
+
|
|
425
|
+
if(groupName === 'system:Administrators'){
|
|
426
|
+
document.getElementById('adminPermsMsg').style.display = '';
|
|
427
|
+
$permissionsTable.style.display = 'none';
|
|
428
|
+
} else if(canReadPerms){
|
|
429
|
+
if(canEdit){
|
|
430
|
+
$permissionsTable.setAttribute('request-delete', '');
|
|
431
|
+
$permissionsTable.setAttribute('enable-selection', '');
|
|
432
|
+
|
|
433
|
+
const $tcDeleteSelectedPerm = document.getElementById('tcDeleteSelectedPerm');
|
|
434
|
+
$tcDeleteSelectedPerm.style.display = '';
|
|
435
|
+
|
|
436
|
+
const $tcAddPermission = document.getElementById('tcAddPermission');
|
|
437
|
+
$tcAddPermission.groupName = groupName;
|
|
438
|
+
$tcAddPermission.style.display = '';
|
|
439
|
+
|
|
440
|
+
const delPerm = document.createElement('k-tc-delete-record');
|
|
441
|
+
delPerm.setAttribute('slot', 'after');
|
|
442
|
+
$permissionsTable.appendChild(delPerm);
|
|
443
|
+
|
|
444
|
+
$permissionsTable.addEventListener('permissionAdded', e => loadPermissions());
|
|
445
|
+
|
|
446
|
+
$permissionsTable.addEventListener('requestDelete', ({ detail: { records, approve, reject } }) => {
|
|
447
|
+
const count = records.length;
|
|
448
|
+
const msg = count === 1
|
|
449
|
+
? `<div class="p"><p>Remove permission "${records[0].name}" from this group?</p></div>`
|
|
450
|
+
: `<div class="p"><p>Remove ${count} permissions from this group?</p></div>`;
|
|
451
|
+
Dialog.confirm(msg, async (confirmed) => {
|
|
452
|
+
if(!confirmed){ reject(); return; }
|
|
453
|
+
const errors = [];
|
|
454
|
+
for(const record of records){
|
|
455
|
+
const [error] = await removePermissionFromGroup(groupName, record.name);
|
|
456
|
+
if(error) errors.push(error.msg);
|
|
457
|
+
}
|
|
458
|
+
if(errors.length){
|
|
459
|
+
reject();
|
|
460
|
+
Toast.error(errors[0] || 'Failed to remove permission');
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
approve();
|
|
464
|
+
Toast.success(count === 1 ? 'Permission removed' : `${count} permissions removed`);
|
|
465
|
+
loadPermissions();
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const loadPermissions = async () => {
|
|
471
|
+
const [error, data] = await getGroupPermissions(groupName);
|
|
472
|
+
if(error){
|
|
473
|
+
Toast.error(error.msg || 'Failed to load permissions');
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
$permissionsTable.setData({
|
|
477
|
+
records: data.permissions.map(name => ({ name })),
|
|
478
|
+
fields: [
|
|
479
|
+
{
|
|
480
|
+
name: 'owner',
|
|
481
|
+
label: 'Owner',
|
|
482
|
+
size: 120,
|
|
483
|
+
calculator: r => r.name.includes(':') ? r.name.split(':')[0] : ''
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: 'resource',
|
|
487
|
+
label: 'Resource',
|
|
488
|
+
size: 180,
|
|
489
|
+
calculator: r => {
|
|
490
|
+
const parts = r.name.split(':');
|
|
491
|
+
return parts.length >= 3 ? parts[1] : parts.length === 2 ? parts[1] : '';
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: 'action',
|
|
496
|
+
label: 'Action',
|
|
497
|
+
size: 140,
|
|
498
|
+
calculator: r => {
|
|
499
|
+
const parts = r.name.split(':');
|
|
500
|
+
return parts.length >= 3 ? parts.slice(2).join(':') : '';
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
]
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
loadPermissions();
|
|
508
|
+
} else {
|
|
509
|
+
$permissionsTable.closest('.card').style.display = 'none';
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
</script>
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
</k-main>
|
|
516
|
+
|
|
517
|
+
</body>
|
|
518
|
+
</html>
|