kempo 4.9.5 → 4.9.7
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/dist/admin/accounts/groups/[name]/index.page.html +5 -5
- package/dist/admin/accounts/groups/index.page.html +12 -10
- package/dist/admin/accounts/permissions/index.page.html +1 -1
- package/dist/admin/accounts/users/[id]/index.page.html +81 -63
- package/dist/admin/accounts/users/index.page.html +7 -9
- package/dist/admin/components/EmailTemplates.js +11 -19
- package/dist/admin/components/EmailTemplates.js.map +1 -1
- package/dist/admin/components/Emails.js +32 -29
- package/dist/admin/components/Emails.js.map +1 -1
- package/dist/admin/components/Updates.js +17 -22
- package/dist/admin/components/Updates.js.map +1 -1
- package/dist/admin/components/settings/SettingsPanel.js +1 -1
- package/dist/admin/components/settings/SettingsPanel.js.map +1 -1
- package/dist/admin/components/tableControls/EditEmailTemplate.js +2 -0
- package/dist/admin/components/tableControls/EditEmailTemplate.js.map +1 -0
- package/dist/admin/components/tableControls/UpdateItem.js +2 -0
- package/dist/admin/components/tableControls/UpdateItem.js.map +1 -0
- package/dist/admin/components/tableControls/UpdateSelected.js +2 -0
- package/dist/admin/components/tableControls/UpdateSelected.js.map +1 -0
- package/dist/admin/components/tableControls/ViewEmail.js +2 -0
- package/dist/admin/components/tableControls/ViewEmail.js.map +1 -0
- package/dist/admin/content/fragments/index.page.html +12 -12
- package/dist/admin/content/globals/index.page.html +12 -11
- package/dist/admin/content/pages/index.page.html +11 -23
- package/dist/admin/content/templates/index.page.html +13 -25
- package/dist/admin/emails/index.page.html +1 -1
- package/dist/admin/extensions/index.page.html +10 -9
- package/dist/admin/settings/index.page.html +13 -3
- package/middleware/kempo.js +1 -0
- package/package.json +2 -2
- package/server/utils/sessions/pruneExpiredSessions.js +32 -0
- package/server/utils/settings/settingRules.js +1 -0
- package/server/utils/settings/systemSettings.js +1 -0
- package/dist/admin/components/settings/AdminSettings.js +0 -7
- package/dist/admin/components/settings/AdminSettings.js.map +0 -1
- package/dist/admin/components/tableControls/ExtensionDetails.js +0 -2
- package/dist/admin/components/tableControls/ExtensionDetails.js.map +0 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteSelected.js"></script>
|
|
16
16
|
<script type="module" src="/admin/components/tableControls/AddMember.js"></script>
|
|
17
17
|
<script type="module" src="/admin/components/tableControls/AddPermission.js"></script>
|
|
18
|
-
<script type="module" src="/
|
|
18
|
+
<script type="module" src="/kempo-ui/components/controls/TcOpen.js"></script>
|
|
19
19
|
|
|
20
20
|
<div class="mb d-f" style="align-items: flex-start; justify-content: space-between;">
|
|
21
21
|
<div>
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
<div class="card">
|
|
64
64
|
<h2>Members</h2>
|
|
65
65
|
<p id="usersMembersMsg" style="display:none">All users are automatically members of this group.</p>
|
|
66
|
-
<k-table id="membersTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="This group has no members.">
|
|
67
|
-
<
|
|
66
|
+
<k-table id="membersTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="This group has no members." open-on-click>
|
|
67
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
68
68
|
<kc-tc-delete-selected id="tcDeleteSelected" slot="top" style="display:none;"></kc-tc-delete-selected>
|
|
69
69
|
<admin-add-member id="tcAddMember" slot="top" style="display:none;"></admin-add-member>
|
|
70
70
|
<div class="d-f full" style="align-items: center;">
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
return;
|
|
277
277
|
}
|
|
278
278
|
$membersTable.setData({
|
|
279
|
-
records: data.members,
|
|
279
|
+
records: data.members.map(m => ({ ...m, _url: `/admin/accounts/users/${m.id}` })),
|
|
280
280
|
fields: [
|
|
281
281
|
{ name: 'name', label: 'Name' },
|
|
282
282
|
{ name: 'email', label: 'Email' },
|
|
@@ -290,7 +290,7 @@
|
|
|
290
290
|
$membersTable.setupFetchRecords(data.total, async (start, count) => {
|
|
291
291
|
const [err, d] = await getGroupMembers(groupName, { limit: count, offset: start });
|
|
292
292
|
if(err) return [];
|
|
293
|
-
return d.members;
|
|
293
|
+
return d.members.map(m => ({ ...m, _url: `/admin/accounts/users/${m.id}` }));
|
|
294
294
|
});
|
|
295
295
|
};
|
|
296
296
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script type="module" src="/kempo-ui/components/controls/TcEdit.js"></script>
|
|
14
14
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteRecord.js"></script>
|
|
15
15
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteSelected.js"></script>
|
|
16
|
-
<script type="module" src="/
|
|
16
|
+
<script type="module" src="/kempo-ui/components/controls/TcOpen.js"></script>
|
|
17
17
|
<script type="module" src="/kempo/components/Permission.js"></script>
|
|
18
18
|
|
|
19
19
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
<h2>Custom Groups</h2>
|
|
27
|
-
<k-table id="customTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No custom groups have been created yet.">
|
|
28
|
-
<
|
|
27
|
+
<k-table id="customTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No custom groups have been created yet." open-on-click>
|
|
28
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
29
29
|
<div slot="top" class="d-f full" style="align-items: center;">
|
|
30
30
|
<div>
|
|
31
31
|
<k-permission has="system:group:delete">
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
</k-table>
|
|
49
49
|
|
|
50
50
|
<h2>Extension Groups</h2>
|
|
51
|
-
<k-table id="extensionTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No extension groups found.">
|
|
52
|
-
<
|
|
51
|
+
<k-table id="extensionTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No extension groups found." open-on-click>
|
|
52
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
53
53
|
<div class="d-f full" style="align-items: center;">
|
|
54
54
|
<div>
|
|
55
55
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
</k-table>
|
|
66
66
|
|
|
67
67
|
<h2>System Groups</h2>
|
|
68
|
-
<k-table id="systemTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No system groups found.">
|
|
69
|
-
<
|
|
68
|
+
<k-table id="systemTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No system groups found." open-on-click>
|
|
69
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
70
70
|
<div class="d-f full" style="align-items: center;">
|
|
71
71
|
<div>
|
|
72
72
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -126,6 +126,8 @@
|
|
|
126
126
|
|
|
127
127
|
const addGroupName = r => ({ ...r, groupName: r.name.includes(':') ? r.name.split(':').slice(1).join(':') : r.name });
|
|
128
128
|
|
|
129
|
+
const withUrl = r => ({ ...r, _url: `/admin/accounts/groups/${r.name}` });
|
|
130
|
+
|
|
129
131
|
const setupTable = ($table, owner, loadFn) => {
|
|
130
132
|
if(canEdit && owner !== 'system') { // Prevent editor control for system groups
|
|
131
133
|
$table.setAttribute('request-edit', '');
|
|
@@ -188,12 +190,12 @@
|
|
|
188
190
|
return;
|
|
189
191
|
}
|
|
190
192
|
const isCustom = owner === 'custom';
|
|
191
|
-
const
|
|
192
|
-
$table.setData({ records, fields: isCustom ? customFields : fields });
|
|
193
|
+
const toRecord = isCustom ? r => withUrl(addGroupName(r)) : withUrl;
|
|
194
|
+
$table.setData({ records: data.groups.map(toRecord), fields: isCustom ? customFields : fields });
|
|
193
195
|
$table.setupFetchRecords(data.total, async (start, count) => {
|
|
194
196
|
const [err, d] = await listGroups({ limit: count, offset: start, owner });
|
|
195
197
|
if(err) return [];
|
|
196
|
-
return
|
|
198
|
+
return d.groups.map(toRecord);
|
|
197
199
|
});
|
|
198
200
|
};
|
|
199
201
|
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
return parts.length >= 3 ? parts.slice(2).join(':') : '';
|
|
112
112
|
}
|
|
113
113
|
},
|
|
114
|
-
{ name: 'description', label: 'Description',
|
|
114
|
+
{ name: 'description', label: 'Description', editable: canManage },
|
|
115
115
|
{
|
|
116
116
|
name: 'createdAt',
|
|
117
117
|
label: 'Created',
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<content>
|
|
6
6
|
<script type="module" src="/kempo-ui/components/Toggle.js"></script>
|
|
7
7
|
<script type="module" src="/kempo-ui/components/Icon.js"></script>
|
|
8
|
+
<script type="module" src="/kempo-ui/components/Tabs.js"></script>
|
|
8
9
|
<script type="module" src="/kempo-ui/components/Table.js"></script>
|
|
9
10
|
<script type="module" src="/kempo-ui/components/controls/TcFirstPage.js"></script>
|
|
10
11
|
<script type="module" src="/kempo-ui/components/controls/TcPrevPage.js"></script>
|
|
@@ -17,7 +18,7 @@
|
|
|
17
18
|
<script type="module" src="/admin/components/GroupDeleteSelected.js"></script>
|
|
18
19
|
<script type="module" src="/admin/components/AddGroupControl.js"></script>
|
|
19
20
|
<script type="module" src="/admin/components/ClearExpiredSessionsControl.js"></script>
|
|
20
|
-
<script type="module" src="/
|
|
21
|
+
<script type="module" src="/kempo-ui/components/controls/TcOpen.js"></script>
|
|
21
22
|
<div class="d-f">
|
|
22
23
|
<div>
|
|
23
24
|
<a href="/admin/accounts/users" class="no-link mb">
|
|
@@ -38,69 +39,71 @@
|
|
|
38
39
|
<a href="/admin/accounts/users">Back to Users</a>
|
|
39
40
|
</div>
|
|
40
41
|
<div id="content" style="display: none;">
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<label for="userEmail" style="min-width: 120px;"><strong>Email</strong></label>
|
|
51
|
-
<input type="email" id="userEmail" style="max-width: 400px;">
|
|
52
|
-
</div>
|
|
53
|
-
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
54
|
-
<label style="min-width: 120px;"><strong>Verified</strong></label>
|
|
55
|
-
<k-toggle id="emailVerified"></k-toggle>
|
|
56
|
-
</div>
|
|
57
|
-
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
58
|
-
<label style="min-width: 120px;"><strong>Created</strong></label>
|
|
59
|
-
<span id="userCreatedAt"></span>
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
|
|
63
|
-
<!-- Groups -->
|
|
64
|
-
<div class="card">
|
|
65
|
-
<h2>Groups</h2>
|
|
66
|
-
<k-table id="groupsTable" request-delete placeholder="Not a member of any groups.">
|
|
67
|
-
<admin-table-row-link slot="before" url-pattern="/admin/accounts/groups/{name}" field-name="name"></admin-table-row-link>
|
|
68
|
-
<kc-tc-delete-record slot="after"></kc-tc-delete-record>
|
|
69
|
-
<div slot="top" id="groupsTableTop" class="d-f full" style="display:none; align-items: center;">
|
|
70
|
-
<div><admin-group-delete-selected></admin-group-delete-selected></div>
|
|
71
|
-
<div class="flex"></div>
|
|
72
|
-
<div><admin-add-group-control id="addGroupBtn"></admin-add-group-control></div>
|
|
42
|
+
<k-tabs id="userTabs" active="information" hash-routing>
|
|
43
|
+
<k-tab for="information">Information</k-tab>
|
|
44
|
+
<k-tab for="groups">Groups</k-tab>
|
|
45
|
+
<k-tab for="sessions">Sessions</k-tab>
|
|
46
|
+
|
|
47
|
+
<k-tab-content name="information">
|
|
48
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
49
|
+
<label for="userName" style="min-width: 120px;"><strong>Name</strong></label>
|
|
50
|
+
<input type="text" id="userName" style="max-width: 400px;">
|
|
73
51
|
</div>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<!-- Sessions -->
|
|
78
|
-
<div class="card">
|
|
79
|
-
<h2>Sessions</h2>
|
|
80
|
-
<k-table id="sessionsTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No active sessions.">
|
|
81
|
-
<kc-tc-delete-record slot="after"></kc-tc-delete-record>
|
|
82
|
-
<div slot="top" class="d-f full" style="align-items: center;">
|
|
83
|
-
<div><kc-tc-delete-selected></kc-tc-delete-selected></div>
|
|
84
|
-
<div class="flex"></div>
|
|
85
|
-
<div><admin-clear-expired-sessions id="clearExpiredBtn" style="display:none;"></admin-clear-expired-sessions></div>
|
|
52
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
53
|
+
<label for="userEmail" style="min-width: 120px;"><strong>Email</strong></label>
|
|
54
|
+
<input type="email" id="userEmail" style="max-width: 400px;">
|
|
86
55
|
</div>
|
|
87
|
-
<div class="d-f
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
<kc-tc-prev-page></kc-tc-prev-page>
|
|
91
|
-
<kc-tc-next-page></kc-tc-next-page>
|
|
92
|
-
<kc-tc-last-page></kc-tc-last-page>
|
|
93
|
-
</div>
|
|
94
|
-
<div class="flex"></div>
|
|
95
|
-
<div><kc-tc-page-select></kc-tc-page-select></div>
|
|
96
|
-
<div class="flex"></div>
|
|
97
|
-
<div><kc-tc-page-size></kc-tc-page-size></div>
|
|
56
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
57
|
+
<label style="min-width: 120px;"><strong>Verified</strong></label>
|
|
58
|
+
<k-toggle id="emailVerified"></k-toggle>
|
|
98
59
|
</div>
|
|
99
|
-
|
|
100
|
-
|
|
60
|
+
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
61
|
+
<label style="min-width: 120px;"><strong>Created</strong></label>
|
|
62
|
+
<span id="userCreatedAt"></span>
|
|
63
|
+
</div>
|
|
64
|
+
</k-tab-content>
|
|
65
|
+
|
|
66
|
+
<k-tab-content name="groups">
|
|
67
|
+
<k-table id="groupsTable" request-delete placeholder="Not a member of any groups." open-on-click>
|
|
68
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
69
|
+
<kc-tc-delete-record slot="after"></kc-tc-delete-record>
|
|
70
|
+
<div slot="top" id="groupsTableTop" class="d-f full" style="display:none; align-items: center;">
|
|
71
|
+
<div><admin-group-delete-selected></admin-group-delete-selected></div>
|
|
72
|
+
<div class="flex"></div>
|
|
73
|
+
<div><admin-add-group-control id="addGroupBtn"></admin-add-group-control></div>
|
|
74
|
+
</div>
|
|
75
|
+
</k-table>
|
|
76
|
+
</k-tab-content>
|
|
77
|
+
|
|
78
|
+
<k-tab-content name="sessions">
|
|
79
|
+
<k-table id="sessionsTable" enable-pages page-size="10" page-size-options="[10,25,50]" placeholder="No active sessions.">
|
|
80
|
+
<kc-tc-delete-record slot="after"></kc-tc-delete-record>
|
|
81
|
+
<div slot="top" class="d-f full" style="align-items: center;">
|
|
82
|
+
<div><kc-tc-delete-selected></kc-tc-delete-selected></div>
|
|
83
|
+
<div class="flex"></div>
|
|
84
|
+
<div><admin-clear-expired-sessions id="clearExpiredBtn" style="display:none;"></admin-clear-expired-sessions></div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="d-f full" style="align-items: center;">
|
|
87
|
+
<div>
|
|
88
|
+
<kc-tc-first-page></kc-tc-first-page>
|
|
89
|
+
<kc-tc-prev-page></kc-tc-prev-page>
|
|
90
|
+
<kc-tc-next-page></kc-tc-next-page>
|
|
91
|
+
<kc-tc-last-page></kc-tc-last-page>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="flex"></div>
|
|
94
|
+
<div><kc-tc-page-select></kc-tc-page-select></div>
|
|
95
|
+
<div class="flex"></div>
|
|
96
|
+
<div><kc-tc-page-size></kc-tc-page-size></div>
|
|
97
|
+
</div>
|
|
98
|
+
</k-table>
|
|
99
|
+
</k-tab-content>
|
|
101
100
|
|
|
101
|
+
<location name="admin-user-tabs" />
|
|
102
|
+
</k-tabs>
|
|
102
103
|
</div>
|
|
103
104
|
|
|
105
|
+
<location name="admin-user-scripts" />
|
|
106
|
+
|
|
104
107
|
<script type="module">
|
|
105
108
|
import {
|
|
106
109
|
getSession, getUser, updateUser, deleteUsers,
|
|
@@ -260,6 +263,7 @@
|
|
|
260
263
|
|
|
261
264
|
await customElements.whenDefined('k-table');
|
|
262
265
|
const $groupsTable = document.getElementById('groupsTable');
|
|
266
|
+
const toRecord = g => ({ ...g, _url: `/admin/accounts/groups/${g.name}` });
|
|
263
267
|
|
|
264
268
|
if(canEditGroups){
|
|
265
269
|
$groupsTable.setAttribute('enable-selection', '');
|
|
@@ -308,13 +312,13 @@
|
|
|
308
312
|
return;
|
|
309
313
|
}
|
|
310
314
|
$groupsTable.setData({
|
|
311
|
-
records: data.groups,
|
|
315
|
+
records: data.groups.map(toRecord),
|
|
312
316
|
fields: [{ name: 'name', label: 'Name' }]
|
|
313
317
|
});
|
|
314
318
|
$groupsTable.setupFetchRecords(data.total, async (start, count) => {
|
|
315
319
|
const [err, d] = await getUserGroups(userid, { limit: count, offset: start });
|
|
316
320
|
if(err) return [];
|
|
317
|
-
return d.groups;
|
|
321
|
+
return d.groups.map(toRecord);
|
|
318
322
|
});
|
|
319
323
|
};
|
|
320
324
|
|
|
@@ -347,8 +351,6 @@
|
|
|
347
351
|
});
|
|
348
352
|
});
|
|
349
353
|
|
|
350
|
-
loadGroups();
|
|
351
|
-
|
|
352
354
|
/*
|
|
353
355
|
Sessions
|
|
354
356
|
*/
|
|
@@ -420,7 +422,23 @@
|
|
|
420
422
|
});
|
|
421
423
|
};
|
|
422
424
|
|
|
423
|
-
|
|
425
|
+
/*
|
|
426
|
+
Each tab loads its data the first time it is opened, so a visit that never leaves Information makes no queries.
|
|
427
|
+
*/
|
|
428
|
+
|
|
429
|
+
const $userTabs = document.getElementById('userTabs');
|
|
430
|
+
const shownTabs = new Set();
|
|
431
|
+
const loaders = { groups: loadGroups, sessions: loadSessions };
|
|
432
|
+
|
|
433
|
+
$userTabs.addEventListener('tabfirstshown', ({ detail }) => {
|
|
434
|
+
shownTabs.add(detail.tab);
|
|
435
|
+
loaders[detail.tab]?.();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// The tab the address opened on was chosen before this listener existed, so it is loaded here.
|
|
439
|
+
await $userTabs.updateComplete;
|
|
440
|
+
const opening = $userTabs.active;
|
|
441
|
+
if(loaders[opening] && !shownTabs.has(opening)) loaders[opening]();
|
|
424
442
|
}
|
|
425
443
|
</script>
|
|
426
444
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
></script>
|
|
50
50
|
<script
|
|
51
51
|
type="module"
|
|
52
|
-
src="/
|
|
52
|
+
src="/kempo-ui/components/controls/TcOpen.js"
|
|
53
53
|
></script>
|
|
54
54
|
<script
|
|
55
55
|
type="module"
|
|
@@ -74,12 +74,9 @@
|
|
|
74
74
|
page-size-options="[10,25,50]"
|
|
75
75
|
placeholder="No users found."
|
|
76
76
|
filtered-placeholder="No users match your search."
|
|
77
|
+
open-on-click
|
|
77
78
|
>
|
|
78
|
-
<
|
|
79
|
-
slot="before"
|
|
80
|
-
url-pattern="/admin/accounts/users/{id}"
|
|
81
|
-
field-name="id"
|
|
82
|
-
></admin-table-row-link>
|
|
79
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
83
80
|
<div
|
|
84
81
|
slot="top"
|
|
85
82
|
class="d-f full"
|
|
@@ -118,6 +115,7 @@
|
|
|
118
115
|
import Dialog from '/kempo-ui/components/Dialog.js';
|
|
119
116
|
await window.customElements.whenDefined('k-table');
|
|
120
117
|
const $table = document.getElementById('usersTable');
|
|
118
|
+
const withUrl = u => ({ ...u, _url: `/admin/accounts/users/${u.id}` });
|
|
121
119
|
|
|
122
120
|
const [, permData] = await getAllCurrentUserPermissions();
|
|
123
121
|
const myPerms = new Set(permData?.permissions ?? []);
|
|
@@ -141,9 +139,9 @@
|
|
|
141
139
|
const [error, data] = await getUsers({ limit: 10, offset: 0 });
|
|
142
140
|
if (error) return;
|
|
143
141
|
$table.setData({
|
|
144
|
-
records: data.users,
|
|
142
|
+
records: data.users.map(withUrl),
|
|
145
143
|
fields: [
|
|
146
|
-
{ name: 'name', label: 'Name',
|
|
144
|
+
{ name: 'name', label: 'Name', editable: true },
|
|
147
145
|
{ name: 'email', label: 'Email', size: 352, editable: true },
|
|
148
146
|
{
|
|
149
147
|
name: 'emailVerified',
|
|
@@ -170,7 +168,7 @@
|
|
|
170
168
|
$table.setupFetchRecords(data.total, async (start, count) => {
|
|
171
169
|
const [err, d] = await getUsers({ limit: count, offset: start });
|
|
172
170
|
if (err) return [];
|
|
173
|
-
return d.users;
|
|
171
|
+
return d.users.map(withUrl);
|
|
174
172
|
});
|
|
175
173
|
};
|
|
176
174
|
|
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"/kempo-ui/components/ShadowComponent.js";import{html as t,nothing as i}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Spinner.js";import"/kempo-ui/components/Icon.js";import"/kempo-ui/components/Dialog.js";import"/kempo-ui/components/CodeEditor.js";import"/kempo-ui/components/Table.js";import"/kempo-ui/components/controls/TcOpen.js";import"/admin/components/tableControls/EditEmailTemplate.js";import s from"/kempo-ui/components/Toast.js";import{listEmailTemplates as a,getEmailTemplate as o,saveEmailTemplate as l,resetEmailTemplate as r}from"/kempo/sdk.js";const n=[{name:"title",label:"Email",editable:!1,calculator:e=>e.title||e.name},{name:"template",label:"Template",size:260,editable:!1,calculator:e=>e.template},{name:"status",label:"Status",size:130,editable:!1,calculator:e=>e.overridden?"Customized":"Default"}];export default class m extends e{static properties={owner:{type:String},previewUrl:{type:String,attribute:"preview-url"},templates:{state:!0},error:{state:!0},editing:{state:!0},saving:{state:!0}};constructor(){super(),this.owner="",this.previewUrl="/kempo/api/email-templates/preview",this.templates=null,this.error="",this.editing=null,this.saving=!1}connectedCallback(){super.connectedCallback();const e=this.closest("k-tab-content");!e||e.active?this.load():this.closest("k-tabs")?.addEventListener("tabfirstshown",({detail:t})=>{t.tab===e.name&&this.load()})}load=async()=>{const[e,t]=await a(this.owner);e?this.error=e.msg:this.templates=t.templates};edit=async e=>{const[t,i]=await o(e);t?s.error(t.msg):(this.editing=i,await this.updateComplete,this.renderRoot.querySelector("#emailDialog").open())};confirmSave=e=>{e.keepDialogOpen=!0,this.save()};save=async()=>{this.saving=!0;const[e]=await l({template:this.editing.template,source:this.renderRoot.querySelector("#emailEditor").value});this.saving=!1,e?s.error(e.msg):(this.renderRoot.querySelector("#emailDialog").close(),s.success("Email template saved"),await this.load())};reset=async()=>{this.saving=!0;const[e]=await r(this.editing.template);this.saving=!1,e?s.error(e.msg):(this.renderRoot.querySelector("#emailDialog").close(),s.success("Email template reset to the default"),await this.load())};renderDialog(){const e=this.editing;return e?t`
|
|
2
2
|
<k-dialog id="emailDialog" style="--width: 60rem;" confirm-text=${this.saving?"Saving…":"Save"} confirm-classes="primary ml" cancel-text="Cancel" .confirmAction=${this.confirmSave}>
|
|
3
|
-
<h2 slot="title" class="h5 pyh px m0">${
|
|
3
|
+
<h2 slot="title" class="h5 pyh px m0">${e.title||e.name}</h2>
|
|
4
4
|
<div class="p">
|
|
5
|
-
<p class="tc-muted mb">${
|
|
6
|
-
<k-code-editor id="emailEditor" class="r b mb" language="html" .value=${
|
|
7
|
-
${
|
|
5
|
+
<p class="tc-muted mb">${e.template}. Saving keeps your copy in <strong>templates/emails</strong>, which is used from the next email and is not changed by updates. A template that does not render is not saved.</p>
|
|
6
|
+
<k-code-editor id="emailEditor" class="r b mb" language="html" .value=${e.source} style="height: 55vh; min-height: 300px;"></k-code-editor>
|
|
7
|
+
${e.overridden?t`<button type="button" class="danger" ?disabled=${this.saving} @click=${this.reset}>Reset to default</button>`:i}
|
|
8
8
|
</div>
|
|
9
9
|
</k-dialog>
|
|
10
|
-
`:i}render(){return this.error?
|
|
11
|
-
<table class="mb">
|
|
12
|
-
<
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
<td class="tc-muted">${t.template}</td>
|
|
16
|
-
<td>${t.overridden?e`<span class="tc-warning">Customized</span>`:e`<span class="tc-muted">Default</span>`}</td>
|
|
17
|
-
<td class="ta-right">
|
|
18
|
-
<a class="btn" href=${`${this.previewUrl}?template=${encodeURIComponent(t.template)}`} target="email-preview">Preview</a>
|
|
19
|
-
<button aria-label="Edit" title="Edit" @click=${()=>this.edit(t.template)}><k-icon name="edit"></k-icon></button>
|
|
20
|
-
</td>
|
|
21
|
-
</tr>`)}</tbody>
|
|
22
|
-
</table>
|
|
10
|
+
`:i}updated(e){if(!e.has("templates")&&!e.has("previewUrl"))return;const t=this.renderRoot.querySelector("k-table");t&&this.templates&&t.setData({records:this.templates.map(e=>({...e,_url:`${this.previewUrl}?template=${encodeURIComponent(e.template)}`})),fields:n})}render(){return this.error?t`<p class="tc-danger" role="alert">${this.error}</p>`:this.templates?this.templates.length?t`
|
|
11
|
+
<k-table class="mb" placeholder="There are no email templates here." @editTemplate=${e=>this.edit(e.detail.record.template)}>
|
|
12
|
+
<kc-tc-open slot="after" target="_blank" title="Preview"></kc-tc-open>
|
|
13
|
+
<admin-edit-email-template slot="after"></admin-edit-email-template>
|
|
14
|
+
</k-table>
|
|
23
15
|
${this.renderDialog()}
|
|
24
|
-
`:
|
|
16
|
+
`:t`<p class="tc-muted">There are no email templates here.</p>`:t`<k-spinner></k-spinner>`}}customElements.define("k-email-templates",m);
|
|
25
17
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\EmailTemplates.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"src\\admin\\components\\EmailTemplates.js","names":["ShadowComponent","html","nothing","Toast","listEmailTemplates","getEmailTemplate","saveEmailTemplate","resetEmailTemplate","EmailTemplates","static","owner","type","String","previewUrl","attribute","templates","state","error","editing","saving","constructor","super","this","connectedCallback","load","async","data","msg","edit","found","template","updateComplete","renderRoot","querySelector","open","confirmSave","event","keepDialogOpen","save","source","value","close","success","reset","renderDialog","title","name","overridden","render","length","map","encodeURIComponent","customElements","define"],"sources":["0"],"sourcesContent":["import ShadowComponent from '/kempo-ui/components/ShadowComponent.js';\nimport { html, nothing } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Spinner.js';\nimport '/kempo-ui/components/Icon.js';\nimport '/kempo-ui/components/Dialog.js';\nimport '/kempo-ui/components/CodeEditor.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport { listEmailTemplates, getEmailTemplate, saveEmailTemplate, resetEmailTemplate } from '/kempo/sdk.js';\n\n/*\n The email templates of one extension, to edit, served at /admin/components/EmailTemplates.js:\n\n <script type=\"module\" src=\"/admin/components/EmailTemplates.js\"></script>\n <k-email-templates owner=\"kempo-commerce\"></k-email-templates>\n\n Without `owner` it lists kempo's own templates (verify-email, reset-password). Each row opens a dialog with the template in\n a code editor. Saving writes the site's own copy to `templates/emails/<owner>/<name>.page.html`, which is used from the next\n email on and survives updates of the extension; **Reset to default** removes it. The packaged template is never changed.\n\n **Preview** opens the template as it would be sent, in one named tab that each preview replaces. By default that is kempo's\n preview, which draws it with no data; an extension that can do better (its own orders as the data) serves its own and gives\n its address as `preview-url`:\n\n <k-email-templates owner=\"kempo-commerce\" preview-url=\"/commerce/api/emails/preview\"></k-email-templates>\n\n The preview is the saved template, not what is being typed in the editor.\n An extension puts this on its settings page; it needs no code of its own.\n*/\nexport default class EmailTemplates extends ShadowComponent {\n static properties = {\n owner: { type: String },\n previewUrl: { type: String, attribute: 'preview-url' },\n templates: { state: true },\n error: { state: true },\n editing: { state: true },\n saving: { state: true },\n };\n\n constructor(){\n super();\n this.owner = '';\n this.previewUrl = '/kempo/api/email-templates/preview';\n this.templates = null;\n this.error = '';\n this.editing = null;\n this.saving = false;\n }\n\n /*\n Lifecycle\n */\n connectedCallback(){\n super.connectedCallback();\n this.load();\n }\n\n /*\n Actions\n */\n load = async () => {\n const [error, data] = await listEmailTemplates(this.owner);\n if(error){\n this.error = error.msg;\n return;\n }\n this.templates = data.templates;\n };\n\n edit = async template => {\n const [error, found] = await getEmailTemplate(template);\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.editing = found;\n await this.updateComplete;\n this.renderRoot.querySelector('#emailDialog').open();\n };\n\n /* The dialog's Save button: it stays open until the template is saved, so a template that does not render is not lost. */\n confirmSave = event => {\n event.keepDialogOpen = true;\n this.save();\n };\n\n save = async () => {\n this.saving = true;\n const [error] = await saveEmailTemplate({ template: this.editing.template, source: this.renderRoot.querySelector('#emailEditor').value });\n this.saving = false;\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.renderRoot.querySelector('#emailDialog').close();\n Toast.success('Email template saved');\n await this.load();\n };\n\n reset = async () => {\n this.saving = true;\n const [error] = await resetEmailTemplate(this.editing.template);\n this.saving = false;\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.renderRoot.querySelector('#emailDialog').close();\n Toast.success('Email template reset to the default');\n await this.load();\n };\n\n /*\n Rendering\n */\n renderDialog(){\n const template = this.editing;\n if(!template) return nothing;\n return html`\n <k-dialog id=\"emailDialog\" style=\"--width: 60rem;\" confirm-text=${this.saving ? 'Saving…' : 'Save'} confirm-classes=\"primary ml\" cancel-text=\"Cancel\" .confirmAction=${this.confirmSave}>\n <h2 slot=\"title\" class=\"h5 pyh px m0\">${template.title || template.name}</h2>\n <div class=\"p\">\n <p class=\"tc-muted mb\">${template.template}. Saving keeps your copy in <strong>templates/emails</strong>, which is used from the next email and is not changed by updates. A template that does not render is not saved.</p>\n <k-code-editor id=\"emailEditor\" class=\"r b mb\" language=\"html\" .value=${template.source} style=\"height: 55vh; min-height: 300px;\"></k-code-editor>\n ${template.overridden ? html`<button type=\"button\" class=\"danger\" ?disabled=${this.saving} @click=${this.reset}>Reset to default</button>` : nothing}\n </div>\n </k-dialog>\n `;\n }\n\n render(){\n if(this.error) return html`<p class=\"tc-danger\" role=\"alert\">${this.error}</p>`;\n if(!this.templates) return html`<k-spinner></k-spinner>`;\n if(!this.templates.length) return html`<p class=\"tc-muted\">There are no email templates here.</p>`;\n return html`\n <table class=\"mb\">\n <thead><tr><th>Email</th><th>Template</th><th>Status</th><th></th></tr></thead>\n <tbody>${this.templates.map(template => html`<tr>\n <td>${template.title || template.name}</td>\n <td class=\"tc-muted\">${template.template}</td>\n <td>${template.overridden ? html`<span class=\"tc-warning\">Customized</span>` : html`<span class=\"tc-muted\">Default</span>`}</td>\n <td class=\"ta-right\">\n <a class=\"btn\" href=${`${this.previewUrl}?template=${encodeURIComponent(template.template)}`} target=\"email-preview\">Preview</a>\n <button aria-label=\"Edit\" title=\"Edit\" @click=${() => this.edit(template.template)}><k-icon name=\"edit\"></k-icon></button>\n </td>\n </tr>`)}</tbody>\n </table>\n ${this.renderDialog()}\n `;\n }\n}\n\ncustomElements.define('k-email-templates', EmailTemplates);\n"],"mappings":"OAAOA,MAAqB,yDACnBC,aAAMC,MAAe,iCACvB,wCACA,qCACA,uCACA,4CACAC,MAAW,6DACTC,sBAAoBC,uBAAkBC,wBAAmBC,MAA0B,+BAqB7E,MAAMC,UAAuBR,EAC1CS,kBAAoB,CAClBC,MAAO,CAAEC,KAAMC,QACfC,WAAY,CAAEF,KAAMC,OAAQE,UAAW,eACvCC,UAAW,CAAEC,OAAO,GACpBC,MAAO,CAAED,OAAO,GAChBE,QAAS,CAAEF,OAAO,GAClBG,OAAQ,CAAEH,OAAO,IAGnB,WAAAI,GACEC,QACAC,KAAKZ,MAAQ,GACbY,KAAKT,WAAa,qCAClBS,KAAKP,UAAY,KACjBO,KAAKL,MAAQ,GACbK,KAAKJ,QAAU,KACfI,KAAKH,QAAS,CAChB,CAKA,iBAAAI,GACEF,MAAME,oBACND,KAAKE,MACP,CAKAA,KAAOC,UACL,MAAOR,EAAOS,SAActB,EAAmBkB,KAAKZ,OACjDO,EACDK,KAAKL,MAAQA,EAAMU,IAGrBL,KAAKP,UAAYW,EAAKX,WAGxBa,KAAOH,UACL,MAAOR,EAAOY,SAAexB,EAAiByB,GAC3Cb,EACDd,EAAMc,MAAMA,EAAMU,MAGpBL,KAAKJ,QAAUW,QACTP,KAAKS,eACXT,KAAKU,WAAWC,cAAc,gBAAgBC,SAIhDC,YAAcC,IACZA,EAAMC,gBAAiB,EACvBf,KAAKgB,QAGPA,KAAOb,UACLH,KAAKH,QAAS,EACd,MAAOF,SAAeX,EAAkB,CAAEwB,SAAUR,KAAKJ,QAAQY,SAAUS,OAAQjB,KAAKU,WAAWC,cAAc,gBAAgBO,QACjIlB,KAAKH,QAAS,EACXF,EACDd,EAAMc,MAAMA,EAAMU,MAGpBL,KAAKU,WAAWC,cAAc,gBAAgBQ,QAC9CtC,EAAMuC,QAAQ,8BACRpB,KAAKE,SAGbmB,MAAQlB,UACNH,KAAKH,QAAS,EACd,MAAOF,SAAeV,EAAmBe,KAAKJ,QAAQY,UACtDR,KAAKH,QAAS,EACXF,EACDd,EAAMc,MAAMA,EAAMU,MAGpBL,KAAKU,WAAWC,cAAc,gBAAgBQ,QAC9CtC,EAAMuC,QAAQ,6CACRpB,KAAKE,SAMb,YAAAoB,GACE,MAAMd,EAAWR,KAAKJ,QACtB,OAAIY,EACG7B,CAAI;wEACyDqB,KAAKH,OAAS,UAAY,2EAA2EG,KAAKa;gDAClIL,EAASe,OAASf,EAASgB;;mCAExChB,EAASA;kFACsCA,EAASS;YAC/ET,EAASiB,WAAa9C,CAAI,kDAAkDqB,KAAKH,iBAAiBG,KAAKqB,kCAAoCzC;;;MAP9HA,CAWvB,CAEA,MAAA8C,GACE,OAAG1B,KAAKL,MAAchB,CAAI,qCAAqCqB,KAAKL,YAChEK,KAAKP,UACLO,KAAKP,UAAUkC,OACZhD,CAAI;;;iBAGEqB,KAAKP,UAAUmC,IAAIpB,GAAY7B,CAAI;gBACpC6B,EAASe,OAASf,EAASgB;iCACVhB,EAASA;gBAC1BA,EAASiB,WAAa9C,CAAI,6CAA+CA,CAAI;;kCAE3D,GAAGqB,KAAKT,uBAAuBsC,mBAAmBrB,EAASA;4DACjC,IAAMR,KAAKM,KAAKE,EAASA;;;;QAI7ER,KAAKsB;MAdyB3C,CAAI,6DADXA,CAAI,yBAiBjC,EAGFmD,eAAeC,OAAO,oBAAqB7C","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"src\\admin\\components\\EmailTemplates.js","names":["ShadowComponent","html","nothing","Toast","listEmailTemplates","getEmailTemplate","saveEmailTemplate","resetEmailTemplate","fields","name","label","editable","calculator","template","title","size","overridden","EmailTemplates","static","owner","type","String","previewUrl","attribute","templates","state","error","editing","saving","constructor","super","this","connectedCallback","pane","closest","active","load","addEventListener","detail","tab","async","data","msg","edit","found","updateComplete","renderRoot","querySelector","open","confirmSave","event","keepDialogOpen","save","source","value","close","success","reset","renderDialog","updated","changed","has","$table","setData","records","map","_url","encodeURIComponent","render","length","record","customElements","define"],"sources":["0"],"sourcesContent":["import ShadowComponent from '/kempo-ui/components/ShadowComponent.js';\nimport { html, nothing } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Spinner.js';\nimport '/kempo-ui/components/Icon.js';\nimport '/kempo-ui/components/Dialog.js';\nimport '/kempo-ui/components/CodeEditor.js';\nimport '/kempo-ui/components/Table.js';\nimport '/kempo-ui/components/controls/TcOpen.js';\nimport '/admin/components/tableControls/EditEmailTemplate.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport { listEmailTemplates, getEmailTemplate, saveEmailTemplate, resetEmailTemplate } from '/kempo/sdk.js';\n\nconst fields = [\n { name: 'title', label: 'Email', editable: false, calculator: template => template.title || template.name },\n { name: 'template', label: 'Template', size: 260, editable: false, calculator: template => template.template },\n { name: 'status', label: 'Status', size: 130, editable: false, calculator: template => template.overridden ? 'Customized' : 'Default' },\n];\n\n/*\n The email templates of one extension, to edit, served at /admin/components/EmailTemplates.js:\n\n <script type=\"module\" src=\"/admin/components/EmailTemplates.js\"></script>\n <k-email-templates owner=\"kempo-commerce\"></k-email-templates>\n\n Without `owner` it lists kempo's own templates (verify-email, reset-password). Each row opens a dialog with the template in\n a code editor. Saving writes the site's own copy to `templates/emails/<owner>/<name>.page.html`, which is used from the next\n email on and survives updates of the extension; **Reset to default** removes it. The packaged template is never changed.\n\n **Preview** opens the template as it would be sent, in one named tab that each preview replaces. By default that is kempo's\n preview, which draws it with no data; an extension that can do better (its own orders as the data) serves its own and gives\n its address as `preview-url`:\n\n <k-email-templates owner=\"kempo-commerce\" preview-url=\"/commerce/api/emails/preview\"></k-email-templates>\n\n The preview is the saved template, not what is being typed in the editor.\n An extension puts this on its settings page; it needs no code of its own.\n*/\nexport default class EmailTemplates extends ShadowComponent {\n static properties = {\n owner: { type: String },\n previewUrl: { type: String, attribute: 'preview-url' },\n templates: { state: true },\n error: { state: true },\n editing: { state: true },\n saving: { state: true },\n };\n\n constructor(){\n super();\n this.owner = '';\n this.previewUrl = '/kempo/api/email-templates/preview';\n this.templates = null;\n this.error = '';\n this.editing = null;\n this.saving = false;\n }\n\n /*\n Lifecycle\n */\n connectedCallback(){\n super.connectedCallback();\nconst pane = this.closest('k-tab-content');\n // Loads when its tab is first opened, so a tab nobody opens makes no requests.\n if(!pane || pane.active) this.load();\n else this.closest('k-tabs')?.addEventListener('tabfirstshown', ({ detail }) => {\n if(detail.tab === pane.name) this.load();\n });\n }\n\n /*\n Actions\n */\n load = async () => {\n const [error, data] = await listEmailTemplates(this.owner);\n if(error){\n this.error = error.msg;\n return;\n }\n this.templates = data.templates;\n };\n\n edit = async template => {\n const [error, found] = await getEmailTemplate(template);\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.editing = found;\n await this.updateComplete;\n this.renderRoot.querySelector('#emailDialog').open();\n };\n\n /* The dialog's Save button: it stays open until the template is saved, so a template that does not render is not lost. */\n confirmSave = event => {\n event.keepDialogOpen = true;\n this.save();\n };\n\n save = async () => {\n this.saving = true;\n const [error] = await saveEmailTemplate({ template: this.editing.template, source: this.renderRoot.querySelector('#emailEditor').value });\n this.saving = false;\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.renderRoot.querySelector('#emailDialog').close();\n Toast.success('Email template saved');\n await this.load();\n };\n\n reset = async () => {\n this.saving = true;\n const [error] = await resetEmailTemplate(this.editing.template);\n this.saving = false;\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.renderRoot.querySelector('#emailDialog').close();\n Toast.success('Email template reset to the default');\n await this.load();\n };\n\n /*\n Rendering\n */\n renderDialog(){\n const template = this.editing;\n if(!template) return nothing;\n return html`\n <k-dialog id=\"emailDialog\" style=\"--width: 60rem;\" confirm-text=${this.saving ? 'Saving…' : 'Save'} confirm-classes=\"primary ml\" cancel-text=\"Cancel\" .confirmAction=${this.confirmSave}>\n <h2 slot=\"title\" class=\"h5 pyh px m0\">${template.title || template.name}</h2>\n <div class=\"p\">\n <p class=\"tc-muted mb\">${template.template}. Saving keeps your copy in <strong>templates/emails</strong>, which is used from the next email and is not changed by updates. A template that does not render is not saved.</p>\n <k-code-editor id=\"emailEditor\" class=\"r b mb\" language=\"html\" .value=${template.source} style=\"height: 55vh; min-height: 300px;\"></k-code-editor>\n ${template.overridden ? html`<button type=\"button\" class=\"danger\" ?disabled=${this.saving} @click=${this.reset}>Reset to default</button>` : nothing}\n </div>\n </k-dialog>\n `;\n }\n\n /* Refills the table whenever the list or the preview address changes. */\n updated(changed){\n if(!changed.has('templates') && !changed.has('previewUrl')) return;\n const $table = this.renderRoot.querySelector('k-table');\n if(!$table || !this.templates) return;\n $table.setData({\n records: this.templates.map(template => ({ ...template, _url: `${this.previewUrl}?template=${encodeURIComponent(template.template)}` })),\n fields,\n });\n }\n\n render(){\n if(this.error) return html`<p class=\"tc-danger\" role=\"alert\">${this.error}</p>`;\n if(!this.templates) return html`<k-spinner></k-spinner>`;\n if(!this.templates.length) return html`<p class=\"tc-muted\">There are no email templates here.</p>`;\n return html`\n <k-table class=\"mb\" placeholder=\"There are no email templates here.\" @editTemplate=${event => this.edit(event.detail.record.template)}>\n <kc-tc-open slot=\"after\" target=\"_blank\" title=\"Preview\"></kc-tc-open>\n <admin-edit-email-template slot=\"after\"></admin-edit-email-template>\n </k-table>\n ${this.renderDialog()}\n `;\n }\n}\n\ncustomElements.define('k-email-templates', EmailTemplates);\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,49 +1,52 @@
|
|
|
1
|
-
import
|
|
1
|
+
import e from"/kempo-ui/components/ShadowComponent.js";import{html as t,nothing as s}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Spinner.js";import"/kempo-ui/components/Dialog.js";import"/kempo-ui/components/Table.js";import"/kempo-ui/components/controls/TcFirstPage.js";import"/kempo-ui/components/controls/TcPrevPage.js";import"/kempo-ui/components/controls/TcNextPage.js";import"/kempo-ui/components/controls/TcLastPage.js";import"/kempo-ui/components/controls/TcPageSelect.js";import"/kempo-ui/components/controls/TcPageSize.js";import"/admin/components/tableControls/ViewEmail.js";import a from"/kempo-ui/components/Toast.js";import{listEmails as i,getEmail as o,retryEmail as r,sendTestEmail as l,getAllCurrentUserPermissions as n}from"/kempo/sdk.js";const c={sent:"tc-success",failed:"tc-danger",sending:"tc-warning",queued:"tc-muted"},m=e=>e?new Date(e).toLocaleString():"",d=[{name:"status",label:"Status",size:110,editable:!1},{name:"recipient",label:"To",size:240,editable:!1},{name:"subject",label:"Subject",editable:!1,calculator:e=>e.lastError?`${e.subject} — ${e.lastError}`:e.subject},{name:"type",label:"Type",size:140,editable:!1},{name:"createdAt",label:"Created",size:190,editable:!1,formatter:m}];export default class p extends e{static properties={status:{state:!0},search:{state:!0},loading:{state:!0},error:{state:!0},canManage:{state:!0},open:{state:!0},testTo:{state:!0},busy:{state:!0}};constructor(){super(),this.status="",this.search="",this.loading=!0,this.error="",this.canManage=!1,this.open=null,this.testTo="",this.busy=!1}connectedCallback(){super.connectedCallback(),n().then(([,e])=>{this.canManage=(e?.permissions??[]).includes("system:emails:manage")});const e=this.closest("k-tab-content");!e||e.active?this.load():this.closest("k-tabs")?.addEventListener("tabfirstshown",({detail:t})=>{t.tab===e.name&&this.load()})}load=async()=>{await this.updateComplete;const e=this.renderRoot.querySelector("#emails"),t={status:this.status||void 0,search:this.search||void 0};this.loading=!0;const[s,a]=await i({...t,limit:25,offset:0});this.loading=!1,s?this.error=s.msg||"The emails could not be loaded":(this.error="",e.setData({records:a.emails,fields:d}),e.setupFetchRecords(a.total,async(e,s)=>{const[a,o]=await i({...t,limit:s,offset:e});return a?[]:o.emails}))};filter=e=>{Object.assign(this,e),this.load()};show=async e=>{const[t,s]=await o(e);t?a.error(t.msg):(this.open=s,await this.updateComplete,this.renderRoot.querySelector("#emailDialog").open())};retry=async()=>{this.busy=!0;const[e,t]=await r(this.open.id);this.busy=!1,e?a.error(e.msg):t.sent?a.success("The email was sent"):a.error(t.error||"The email could not be sent"),this.renderRoot.querySelector("#emailDialog").close(),await this.load()};openTest=()=>this.renderRoot.querySelector("#testDialog").open();confirmTest=e=>{e.keepDialogOpen=!0,this.sendTest()};sendTest=async()=>{this.busy=!0;const[e,t]=await l(this.testTo.trim());this.busy=!1,e?a.error(e.msg):(this.renderRoot.querySelector("#testDialog").close(),t.sent?a.success(`A test email was sent to ${this.testTo.trim()}`):a.error(t.error||"The test email could not be sent"),await this.load())};renderDetail(){const e=this.open;return e?t`
|
|
2
2
|
<k-dialog id="emailDialog" style="--width: 60rem;" cancel-text="Close">
|
|
3
|
-
<h2 slot="title" class="h5 pyh px m0">${
|
|
3
|
+
<h2 slot="title" class="h5 pyh px m0">${e.subject}</h2>
|
|
4
4
|
<div class="p">
|
|
5
|
-
<p class="mb"><strong class=${c[
|
|
6
|
-
<p class="mb">To <strong>${
|
|
7
|
-
${
|
|
8
|
-
${this.canManage&&["queued","failed"].includes(
|
|
9
|
-
<iframe title="The email" sandbox="" .srcdoc=${
|
|
5
|
+
<p class="mb"><strong class=${c[e.status]??""}>${e.status}</strong>${e.attempts?t` · ${e.attempts} ${1===e.attempts?"try":"tries"}`:s}${e.provider?t` · sent by ${e.provider}`:s}</p>
|
|
6
|
+
<p class="mb">To <strong>${e.recipient}</strong>, from ${e.sender}<br><span class="tc-muted">${e.type} · created ${m(e.createdAt)}${e.sentAt?` · sent ${m(e.sentAt)}`:""}</span></p>
|
|
7
|
+
${e.lastError?t`<p class="tc-danger mb" role="alert">${e.lastError}</p>`:s}
|
|
8
|
+
${this.canManage&&["queued","failed"].includes(e.status)?t`<p class="mb"><button class="primary" ?disabled=${this.busy} @click=${this.retry}>${this.busy?"Trying…":"Try again"}</button></p>`:s}
|
|
9
|
+
<iframe title="The email" sandbox="" .srcdoc=${e.html} class="full b r" height="420"></iframe>
|
|
10
10
|
</div>
|
|
11
11
|
</k-dialog>
|
|
12
|
-
`:
|
|
12
|
+
`:s}renderTest(){return t`
|
|
13
13
|
<k-dialog id="testDialog" confirm-text=${this.busy?"Sending…":"Send test email"} confirm-classes="primary ml" cancel-text="Cancel" .confirmAction=${this.confirmTest}>
|
|
14
14
|
<h2 slot="title" class="h5 pyh px m0">Send a test email</h2>
|
|
15
15
|
<div class="p">
|
|
16
|
-
<label class="mb d-b">Send it to<input type="email" .value=${this.testTo} @input=${
|
|
16
|
+
<label class="mb d-b">Send it to<input type="email" .value=${this.testTo} @input=${e=>{this.testTo=e.target.value}}></label>
|
|
17
17
|
<p class="tc-muted">It goes out through the email extension that is installed, and shows in the log below.</p>
|
|
18
18
|
</div>
|
|
19
19
|
</k-dialog>
|
|
20
|
-
`}render(){
|
|
20
|
+
`}render(){return t`
|
|
21
21
|
<div class="d-f mb">
|
|
22
22
|
<label class="mr">Status
|
|
23
|
-
<select @change=${
|
|
24
|
-
${["","queued","sending","sent","failed"].map(t
|
|
23
|
+
<select @change=${e=>this.filter({status:e.target.value})}>
|
|
24
|
+
${["","queued","sending","sent","failed"].map(e=>t`<option value=${e} ?selected=${this.status===e}>${e||"All"}</option>`)}
|
|
25
25
|
</select>
|
|
26
26
|
</label>
|
|
27
27
|
<label class="mr">Search
|
|
28
|
-
<input type="search" placeholder="Address or subject" .value=${this.search} @change=${
|
|
28
|
+
<input type="search" placeholder="Address or subject" .value=${this.search} @change=${e=>this.filter({search:e.target.value})}>
|
|
29
29
|
</label>
|
|
30
|
-
${this.canManage?
|
|
30
|
+
${this.canManage?t`<button class="mb" @click=${this.openTest}>Send a test email</button>`:s}
|
|
31
31
|
</div>
|
|
32
|
-
${this.error?
|
|
33
|
-
${this.loading?
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
</
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
32
|
+
${this.error?t`<p class="tc-danger" role="alert">${this.error}</p>`:s}
|
|
33
|
+
${this.loading?t`<k-spinner></k-spinner>`:s}
|
|
34
|
+
<k-table id="emails" enable-pages page-size="25" page-size-options="[10,25,50]" placeholder="No emails have been sent yet." @emailView=${e=>this.show(e.detail.record.id)}>
|
|
35
|
+
<admin-view-email slot="before"></admin-view-email>
|
|
36
|
+
<div class="d-f full" style="align-items: center;">
|
|
37
|
+
<div>
|
|
38
|
+
<kc-tc-first-page></kc-tc-first-page>
|
|
39
|
+
<kc-tc-prev-page></kc-tc-prev-page>
|
|
40
|
+
<kc-tc-next-page></kc-tc-next-page>
|
|
41
|
+
<kc-tc-last-page></kc-tc-last-page>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="flex"></div>
|
|
44
|
+
<div><kc-tc-page-select></kc-tc-page-select></div>
|
|
45
|
+
<div class="flex"></div>
|
|
46
|
+
<div><kc-tc-page-size></kc-tc-page-size></div>
|
|
47
|
+
</div>
|
|
48
|
+
</k-table>
|
|
46
49
|
${this.renderDetail()}
|
|
47
50
|
${this.renderTest()}
|
|
48
|
-
`}}customElements.define("k-admin-emails",
|
|
51
|
+
`}}customElements.define("k-admin-emails",p);
|
|
49
52
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\Emails.js.map
|