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
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteRecord.js"></script>
|
|
14
14
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteSelected.js"></script>
|
|
15
15
|
<script type="module" src="/kempo-ui/components/Icon.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 Fragments</h2>
|
|
27
|
-
<k-table id="customTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No custom fragments have been created yet.">
|
|
28
|
-
<
|
|
27
|
+
<k-table open-on-click id="customTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No custom fragments have been created yet.">
|
|
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:fragments:delete">
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
</k-table>
|
|
49
49
|
|
|
50
50
|
<h2>Extension Fragments</h2>
|
|
51
|
-
<k-table id="extensionTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No extension fragments installed.">
|
|
52
|
-
<
|
|
51
|
+
<k-table open-on-click id="extensionTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No extension fragments installed.">
|
|
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 Fragments</h2>
|
|
68
|
-
<k-table id="systemTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No system fragments found.">
|
|
69
|
-
<
|
|
68
|
+
<k-table open-on-click id="systemTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No system fragments found.">
|
|
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>
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
|
|
95
95
|
const fields = [
|
|
96
96
|
{ name: 'locked', label: '', size: 40, calculator: r => r.locked ? html`<k-icon name="lock"></k-icon>` : html`` },
|
|
97
|
-
{ name: 'name', label: 'Name'
|
|
97
|
+
{ name: 'name', label: 'Name' },
|
|
98
98
|
{ name: 'file', label: 'File', size: 300 },
|
|
99
99
|
{ name: 'author', label: 'Author', size: 150 },
|
|
100
100
|
{ name: 'updatedAt', label: 'Updated', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' },
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
Toast.error(error.msg || 'Failed to load fragments');
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
const
|
|
120
|
-
const custom = data.fragments.filter(f => f.owner === 'custom').map(
|
|
121
|
-
const extension = data.fragments.filter(f => f.owner !== 'custom' && f.owner !== 'system').map(
|
|
122
|
-
const system = data.fragments.filter(f => f.owner === 'system').map(
|
|
119
|
+
const withEditUrl = f => ({ ...f, _url: `/admin/content/fragments/edit?fragment=${encodeURIComponent(f.file.replace(/\.fragment\.html$/, ''))}` });
|
|
120
|
+
const custom = data.fragments.filter(f => f.owner === 'custom').map(withEditUrl);
|
|
121
|
+
const extension = data.fragments.filter(f => f.owner !== 'custom' && f.owner !== 'system').map(withEditUrl);
|
|
122
|
+
const system = data.fragments.filter(f => f.owner === 'system').map(withEditUrl);
|
|
123
123
|
$customTable.setData({ records: custom, fields });
|
|
124
124
|
$extensionTable.setData({ records: extension, fields: extensionFields });
|
|
125
125
|
$systemTable.setData({ records: system, fields });
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteSelected.js"></script>
|
|
15
15
|
<script type="module" src="/kempo-ui/components/Icon.js"></script>
|
|
16
16
|
<script type="module" src="/kempo-ui/components/Combobox.js"></script>
|
|
17
|
-
<script type="module" src="/
|
|
17
|
+
<script type="module" src="/kempo-ui/components/controls/TcOpen.js"></script>
|
|
18
18
|
<script type="module" src="/kempo/components/Permission.js"></script>
|
|
19
19
|
|
|
20
20
|
<div class="d-f mb" style="align-items: center; gap: var(--spacer);">
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
27
|
<h2>Custom</h2>
|
|
28
|
-
<k-table id="customTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No custom global content has been created yet.">
|
|
29
|
-
<
|
|
28
|
+
<k-table open-on-click id="customTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No custom global content has been created yet.">
|
|
29
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
30
30
|
<div slot="top" class="d-f full" style="align-items: center;">
|
|
31
31
|
<div>
|
|
32
32
|
<k-permission has="system:globals:delete">
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
</k-table>
|
|
50
50
|
|
|
51
51
|
<h2>Extensions</h2>
|
|
52
|
-
<k-table id="extensionTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No extension global content installed.">
|
|
53
|
-
<
|
|
52
|
+
<k-table open-on-click id="extensionTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No extension global content installed.">
|
|
53
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
54
54
|
<div class="d-f full" style="align-items: center;">
|
|
55
55
|
<div>
|
|
56
56
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
</k-table>
|
|
67
67
|
|
|
68
68
|
<h2>System</h2>
|
|
69
|
-
<k-table id="systemTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No system global content found.">
|
|
70
|
-
<
|
|
69
|
+
<k-table open-on-click id="systemTable" enable-pages page-size="25" page-size-options="[10,25,50,100]" placeholder="No system global content found.">
|
|
70
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
71
71
|
<div class="d-f full" style="align-items: center;">
|
|
72
72
|
<div>
|
|
73
73
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
|
|
96
96
|
const fields = [
|
|
97
97
|
{ name: 'locked', label: '', size: 40, calculator: r => r.locked ? html`<k-icon name="lock"></k-icon>` : html`` },
|
|
98
|
-
{ name: 'name', label: 'Name'
|
|
98
|
+
{ name: 'name', label: 'Name' },
|
|
99
99
|
{ name: 'location', label: 'Location', size: 150 },
|
|
100
100
|
{ name: 'priority', label: 'Priority', size: 80 },
|
|
101
101
|
{ name: 'author', label: 'Author', size: 150 },
|
|
@@ -118,9 +118,10 @@
|
|
|
118
118
|
Toast.error(error.msg || 'Failed to load global content');
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const
|
|
121
|
+
const withEditUrl = e => ({ ...e, _url: `/admin/content/globals/edit?id=${encodeURIComponent(e.id)}` });
|
|
122
|
+
const custom = data.entries.filter(e => e.owner === 'custom').map(withEditUrl);
|
|
123
|
+
const extension = data.entries.filter(e => e.owner !== 'custom' && e.owner !== 'system').map(withEditUrl);
|
|
124
|
+
const system = data.entries.filter(e => e.owner === 'system').map(withEditUrl);
|
|
124
125
|
$customTable.setData({ records: custom, fields });
|
|
125
126
|
$extensionTable.setData({ records: extension, fields: extensionFields });
|
|
126
127
|
$systemTable.setData({ records: system, fields });
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
></script>
|
|
46
46
|
<script
|
|
47
47
|
type="module"
|
|
48
|
-
src="/
|
|
48
|
+
src="/kempo-ui/components/controls/TcOpen.js"
|
|
49
49
|
></script>
|
|
50
50
|
<script
|
|
51
51
|
type="module"
|
|
@@ -67,18 +67,14 @@
|
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
69
|
<h2>Custom Pages</h2>
|
|
70
|
-
<k-table
|
|
70
|
+
<k-table open-on-click
|
|
71
71
|
id="customTable"
|
|
72
72
|
enable-pages
|
|
73
73
|
page-size="25"
|
|
74
74
|
page-size-options="[10,25,50,100]"
|
|
75
75
|
placeholder="No custom pages have been created yet."
|
|
76
76
|
>
|
|
77
|
-
<
|
|
78
|
-
slot="before"
|
|
79
|
-
url-pattern="/admin/content/pages/edit?path={editPath}"
|
|
80
|
-
field-name="editPath"
|
|
81
|
-
></admin-table-row-link>
|
|
77
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
82
78
|
<div
|
|
83
79
|
slot="top"
|
|
84
80
|
class="d-f full"
|
|
@@ -108,18 +104,14 @@
|
|
|
108
104
|
</k-table>
|
|
109
105
|
|
|
110
106
|
<h2>Extension Pages</h2>
|
|
111
|
-
<k-table
|
|
107
|
+
<k-table open-on-click
|
|
112
108
|
id="extensionTable"
|
|
113
109
|
enable-pages
|
|
114
110
|
page-size="25"
|
|
115
111
|
page-size-options="[10,25,50,100]"
|
|
116
112
|
placeholder="No extension pages installed."
|
|
117
113
|
>
|
|
118
|
-
<
|
|
119
|
-
slot="before"
|
|
120
|
-
url-pattern="/admin/content/pages/edit?path={editPath}"
|
|
121
|
-
field-name="editPath"
|
|
122
|
-
></admin-table-row-link>
|
|
114
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
123
115
|
<div
|
|
124
116
|
class="d-f full"
|
|
125
117
|
style="align-items: center;"
|
|
@@ -138,18 +130,14 @@
|
|
|
138
130
|
</k-table>
|
|
139
131
|
|
|
140
132
|
<h2>System Pages</h2>
|
|
141
|
-
<k-table
|
|
133
|
+
<k-table open-on-click
|
|
142
134
|
id="systemTable"
|
|
143
135
|
enable-pages
|
|
144
136
|
page-size="25"
|
|
145
137
|
page-size-options="[10,25,50,100]"
|
|
146
138
|
placeholder="No system pages found."
|
|
147
139
|
>
|
|
148
|
-
<
|
|
149
|
-
slot="before"
|
|
150
|
-
url-pattern="/admin/content/pages/edit?path={editPath}"
|
|
151
|
-
field-name="editPath"
|
|
152
|
-
></admin-table-row-link>
|
|
140
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
153
141
|
<div
|
|
154
142
|
class="d-f full"
|
|
155
143
|
style="align-items: center;"
|
|
@@ -183,7 +171,7 @@
|
|
|
183
171
|
{ name: 'name', label: 'Name', size: 200 },
|
|
184
172
|
{ name: 'url', label: 'URL', size: 250 },
|
|
185
173
|
{ name: 'author', label: 'Author', size: 150 },
|
|
186
|
-
{ name: 'description', label: 'Description'
|
|
174
|
+
{ name: 'description', label: 'Description' },
|
|
187
175
|
{ name: 'updatedAt', label: 'Updated', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' },
|
|
188
176
|
{ name: 'createdAt', label: 'Created', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' }
|
|
189
177
|
];
|
|
@@ -214,7 +202,7 @@
|
|
|
214
202
|
|
|
215
203
|
setInterval(updateScanInfo, 1000);
|
|
216
204
|
|
|
217
|
-
const
|
|
205
|
+
const withEditUrl = p => ({ ...p, _url: `/admin/content/pages/edit?path=${encodeURIComponent(p.file.replace(/\.page\.html$/, ''))}` });
|
|
218
206
|
|
|
219
207
|
const initTable = async ($table, tableFields, owner) => {
|
|
220
208
|
const [error, data] = await listPages({ owner, showHidden: false, offset: 0, limit: 25 });
|
|
@@ -222,11 +210,11 @@
|
|
|
222
210
|
Toast.error(error.msg || 'Failed to load pages');
|
|
223
211
|
return null;
|
|
224
212
|
}
|
|
225
|
-
$table.setData({ records: data.pages.map(
|
|
213
|
+
$table.setData({ records: data.pages.map(withEditUrl), fields: tableFields });
|
|
226
214
|
if(data.total > data.pages.length){
|
|
227
215
|
$table.setupFetchRecords(data.total, async (start, count) => {
|
|
228
216
|
const [, d] = await listPages({ owner, showHidden: false, offset: start, limit: count });
|
|
229
|
-
return (d?.pages || []).map(
|
|
217
|
+
return (d?.pages || []).map(withEditUrl);
|
|
230
218
|
});
|
|
231
219
|
}
|
|
232
220
|
return data.scannedAt;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteRecord.js"></script>
|
|
14
14
|
<script type="module" src="/kempo-ui/components/controls/TcDeleteSelected.js"></script>
|
|
15
15
|
<script type="module" src="/kempo-ui/components/Icon.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,18 +24,14 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
|
|
26
26
|
<h2>Custom Templates</h2>
|
|
27
|
-
<k-table
|
|
27
|
+
<k-table open-on-click
|
|
28
28
|
id="customTable"
|
|
29
29
|
enable-pages
|
|
30
30
|
page-size="25"
|
|
31
31
|
page-size-options="[10,25,50,100]"
|
|
32
32
|
placeholder="No custom templates have been created yet."
|
|
33
33
|
>
|
|
34
|
-
<
|
|
35
|
-
slot="before"
|
|
36
|
-
url-pattern="/admin/content/templates/edit?template={editPath}"
|
|
37
|
-
field-name="editPath"
|
|
38
|
-
></admin-table-row-link>
|
|
34
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
39
35
|
<div slot="top" class="d-f full" style="align-items: center;">
|
|
40
36
|
<div>
|
|
41
37
|
<k-permission has="system:pages:delete">
|
|
@@ -58,18 +54,14 @@
|
|
|
58
54
|
</k-table>
|
|
59
55
|
|
|
60
56
|
<h2>Extension Templates</h2>
|
|
61
|
-
<k-table
|
|
57
|
+
<k-table open-on-click
|
|
62
58
|
id="extensionTable"
|
|
63
59
|
enable-pages
|
|
64
60
|
page-size="25"
|
|
65
61
|
page-size-options="[10,25,50,100]"
|
|
66
62
|
placeholder="No extension templates installed."
|
|
67
63
|
>
|
|
68
|
-
<
|
|
69
|
-
slot="before"
|
|
70
|
-
url-pattern="/admin/content/templates/edit?template={editPath}"
|
|
71
|
-
field-name="editPath"
|
|
72
|
-
></admin-table-row-link>
|
|
64
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
73
65
|
<div class="d-f full" style="align-items: center;">
|
|
74
66
|
<div>
|
|
75
67
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -85,18 +77,14 @@
|
|
|
85
77
|
</k-table>
|
|
86
78
|
|
|
87
79
|
<h2>System Templates</h2>
|
|
88
|
-
<k-table
|
|
80
|
+
<k-table open-on-click
|
|
89
81
|
id="systemTable"
|
|
90
82
|
enable-pages
|
|
91
83
|
page-size="25"
|
|
92
84
|
page-size-options="[10,25,50,100]"
|
|
93
85
|
placeholder="No system templates found."
|
|
94
86
|
>
|
|
95
|
-
<
|
|
96
|
-
slot="before"
|
|
97
|
-
url-pattern="/admin/content/templates/edit?template={editPath}"
|
|
98
|
-
field-name="editPath"
|
|
99
|
-
></admin-table-row-link>
|
|
87
|
+
<kc-tc-open slot="before"></kc-tc-open>
|
|
100
88
|
<div class="d-f full" style="align-items: center;">
|
|
101
89
|
<div>
|
|
102
90
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -124,7 +112,7 @@
|
|
|
124
112
|
|
|
125
113
|
const fields = [
|
|
126
114
|
{ name: 'locked', label: '', size: 40, calculator: r => r.locked ? html`<k-icon name="lock"></k-icon>` : html`` },
|
|
127
|
-
{ name: 'name', label: 'Name'
|
|
115
|
+
{ name: 'name', label: 'Name' },
|
|
128
116
|
{ name: 'directory', label: 'Directory', size: 200 },
|
|
129
117
|
{ name: 'author', label: 'Author', size: 150 },
|
|
130
118
|
{ name: 'updatedAt', label: 'Updated', size: 190, formatter: v => v ? new Date(v).toLocaleString() : '' },
|
|
@@ -146,14 +134,14 @@
|
|
|
146
134
|
Toast.error(error.msg || 'Failed to load templates');
|
|
147
135
|
return;
|
|
148
136
|
}
|
|
149
|
-
const
|
|
137
|
+
const withEditUrl = t => ({
|
|
150
138
|
...t,
|
|
151
139
|
directory: t.directory === '.' ? '/' : t.directory,
|
|
152
|
-
|
|
140
|
+
_url: `/admin/content/templates/edit?template=${encodeURIComponent(t.directory === '.' ? t.name : `${t.directory}/${t.name}`)}`
|
|
153
141
|
});
|
|
154
|
-
const custom = data.templates.filter(t => t.owner === 'custom').map(
|
|
155
|
-
const extension = data.templates.filter(t => t.owner !== 'custom' && t.owner !== 'system').map(
|
|
156
|
-
const system = data.templates.filter(t => t.owner === 'system').map(
|
|
142
|
+
const custom = data.templates.filter(t => t.owner === 'custom').map(withEditUrl);
|
|
143
|
+
const extension = data.templates.filter(t => t.owner !== 'custom' && t.owner !== 'system').map(withEditUrl);
|
|
144
|
+
const system = data.templates.filter(t => t.owner === 'system').map(withEditUrl);
|
|
157
145
|
$customTable.setData({ records: custom, fields });
|
|
158
146
|
$extensionTable.setData({ records: extension, fields: extensionFields });
|
|
159
147
|
$systemTable.setData({ records: system, fields });
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<h1>Emails</h1>
|
|
11
11
|
<p class="tc-muted mb">Kempo does not send email itself: an email extension (such as kempo-resend or kempo-smtp) does. How long the log is kept, and how many times a failed email is tried again, are on the System tab of <a href="/admin/settings">Settings</a>.</p>
|
|
12
12
|
|
|
13
|
-
<k-tabs persistent-id="admin-emails">
|
|
13
|
+
<k-tabs persistent-id="admin-emails" hash-routing>
|
|
14
14
|
<k-tab for="log">Log</k-tab>
|
|
15
15
|
<k-tab for="templates">Templates</k-tab>
|
|
16
16
|
<k-tab-content name="log">
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<script type="module" src="/kempo-ui/components/controls/TcPageSelect.js"></script>
|
|
12
12
|
<script type="module" src="/kempo-ui/components/controls/TcPageSize.js"></script>
|
|
13
13
|
<script type="module" src="/admin/components/tableControls/InstallExtension.js"></script>
|
|
14
|
-
<script type="module" src="/
|
|
14
|
+
<script type="module" src="/kempo-ui/components/controls/TcOpen.js"></script>
|
|
15
15
|
<script type="module" src="/admin/components/tableControls/ToggleExtension.js"></script>
|
|
16
16
|
<script type="module" src="/admin/components/tableControls/UninstallExtension.js"></script>
|
|
17
17
|
<script type="module" src="/kempo/components/Permission.js"></script>
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
|
|
24
24
|
<h2>Installed Extensions</h2>
|
|
25
|
-
<k-table id="installedTable" enable-pages page-size="25" placeholder="No extensions are currently installed.">
|
|
26
|
-
<
|
|
25
|
+
<k-table open-on-click id="installedTable" enable-pages page-size="25" placeholder="No extensions are currently installed.">
|
|
26
|
+
<kc-tc-open slot="after" title="View in Store"><k-icon name="storefront"></k-icon></kc-tc-open>
|
|
27
27
|
<admin-toggle-extension slot="after"></admin-toggle-extension>
|
|
28
28
|
<admin-uninstall-extension slot="after"></admin-uninstall-extension>
|
|
29
29
|
<div class="d-f full" style="align-items: center;">
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
|
|
43
43
|
<h2>Downloaded, Not Installed</h2>
|
|
44
44
|
<p class="tc-muted">Extension packages already in this site. To find and download more, <a href="/admin/extensions/browse/">browse the marketplace</a>.</p>
|
|
45
|
-
<k-table id="availableTable" enable-pages page-size="25" placeholder="Every downloaded extension is installed.">
|
|
45
|
+
<k-table open-on-click id="availableTable" enable-pages page-size="25" placeholder="Every downloaded extension is installed.">
|
|
46
46
|
<admin-install-extension slot="after"></admin-install-extension>
|
|
47
|
-
<
|
|
47
|
+
<kc-tc-open slot="after" title="View in Store"><k-icon name="storefront"></k-icon></kc-tc-open>
|
|
48
48
|
<div class="d-f full" style="align-items: center;">
|
|
49
49
|
<div>
|
|
50
50
|
<kc-tc-first-page></kc-tc-first-page>
|
|
@@ -66,11 +66,12 @@
|
|
|
66
66
|
|
|
67
67
|
const installedTable = document.getElementById('installedTable');
|
|
68
68
|
const availableTable = document.getElementById('availableTable');
|
|
69
|
+
const withDetailsUrl = e => ({ ...e, _url: `/admin/extensions/details/?name=${encodeURIComponent(e.name)}` });
|
|
69
70
|
|
|
70
71
|
const [installedErr, installedData] = await listExtensions();
|
|
71
72
|
if(!installedErr){
|
|
72
73
|
installedTable.setData({
|
|
73
|
-
records: installedData.items,
|
|
74
|
+
records: installedData.items.map(withDetailsUrl),
|
|
74
75
|
fields: [
|
|
75
76
|
{ name: 'name', label: 'Name' },
|
|
76
77
|
{ name: 'version', label: 'Version' },
|
|
@@ -85,7 +86,7 @@
|
|
|
85
86
|
const installedNames = new Set((installedData?.items || []).map(e => e.name));
|
|
86
87
|
const notInstalled = availableData.items.filter(e => !installedNames.has(e.name));
|
|
87
88
|
availableTable.setData({
|
|
88
|
-
records: notInstalled,
|
|
89
|
+
records: notInstalled.map(withDetailsUrl),
|
|
89
90
|
fields: [
|
|
90
91
|
{ name: 'name', label: 'Name' },
|
|
91
92
|
{ name: 'version', label: 'Version' },
|
|
@@ -97,7 +98,7 @@
|
|
|
97
98
|
const refreshInstalledTable = async () => {
|
|
98
99
|
const [err, data] = await listExtensions();
|
|
99
100
|
if(!err) installedTable.setData({
|
|
100
|
-
records: data.items,
|
|
101
|
+
records: data.items.map(withDetailsUrl),
|
|
101
102
|
fields: [
|
|
102
103
|
{ name: 'name', label: 'Name' },
|
|
103
104
|
{ name: 'version', label: 'Version' },
|
|
@@ -113,7 +114,7 @@
|
|
|
113
114
|
if(!instErr && !availErr){
|
|
114
115
|
const installedNames = new Set((instData?.items || []).map(e => e.name));
|
|
115
116
|
availableTable.setData({
|
|
116
|
-
records: availData.items.filter(e => !installedNames.has(e.name)),
|
|
117
|
+
records: availData.items.filter(e => !installedNames.has(e.name)).map(withDetailsUrl),
|
|
117
118
|
fields: [
|
|
118
119
|
{ name: 'name', label: 'Name' },
|
|
119
120
|
{ name: 'version', label: 'Version' },
|
|
@@ -3,13 +3,23 @@
|
|
|
3
3
|
-->
|
|
4
4
|
<page title="Settings - Admin">
|
|
5
5
|
<content>
|
|
6
|
-
<script type="module" src="/
|
|
6
|
+
<script type="module" src="/kempo-ui/components/Tabs.js"></script>
|
|
7
|
+
<script type="module" src="/admin/components/settings/SettingsPanel.js"></script>
|
|
7
8
|
|
|
8
9
|
<h1>Settings</h1>
|
|
9
10
|
|
|
10
|
-
<k-
|
|
11
|
+
<k-tabs active="custom" hash-routing>
|
|
12
|
+
<k-tab for="custom">Custom</k-tab>
|
|
13
|
+
<k-tab for="system">System</k-tab>
|
|
11
14
|
<location name="admin-settings-tabs" />
|
|
12
|
-
|
|
15
|
+
|
|
16
|
+
<k-tab-content name="custom" class="pt">
|
|
17
|
+
<k-settings-panel owner="custom"></k-settings-panel>
|
|
18
|
+
</k-tab-content>
|
|
19
|
+
<k-tab-content name="system" class="pt">
|
|
20
|
+
<k-settings-panel owner="system"></k-settings-panel>
|
|
21
|
+
</k-tab-content>
|
|
22
|
+
</k-tabs>
|
|
13
23
|
<location name="admin-settings-scripts" />
|
|
14
24
|
|
|
15
25
|
</content>
|
package/middleware/kempo.js
CHANGED
|
@@ -257,6 +257,7 @@ export default config => {
|
|
|
257
257
|
['schema notice', notifySchemaAttention],
|
|
258
258
|
['jobs', () => registerJobs({ owner: 'kempo', jobs: [
|
|
259
259
|
{ name: 'email-maintenance', every: '1m', callback: './server/utils/email/maintainEmails.js' },
|
|
260
|
+
{ name: 'session-cleanup', every: '1h', callback: './server/utils/sessions/pruneExpiredSessions.js' },
|
|
260
261
|
{ name: 'update-check', every: '12h', callback: './server/utils/updates/runUpdateCheck.js' },
|
|
261
262
|
] })],
|
|
262
263
|
]){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kempo",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A fullstack CMS framework for creating websites with user authentication",
|
|
6
6
|
"scripts": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"kempo-css": ">=2.2.5",
|
|
63
63
|
"kempo-server": ">=3.4.0",
|
|
64
|
-
"kempo-ui": ">=0.
|
|
64
|
+
"kempo-ui": ">=0.7.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@electric-sql/pglite": "^0.5.9",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { lt } from 'drizzle-orm';
|
|
2
|
+
import db from '../../db/index.js';
|
|
3
|
+
import { session } from '../../db/schema.js';
|
|
4
|
+
import getSetting from '../settings/getSetting.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Deletes every session that expired more than `system:session_cleanup_hours` ago (default 24), for every user.
|
|
8
|
+
Run hourly by the scheduler (middleware/kempo.js registers it). 0 keeps expired sessions forever, and the
|
|
9
|
+
per-user "clear expired" button still works either way.
|
|
10
|
+
*/
|
|
11
|
+
export default async ({ now = new Date(), hours } = {}) => {
|
|
12
|
+
try {
|
|
13
|
+
let cutoffHours = hours;
|
|
14
|
+
|
|
15
|
+
if(cutoffHours === undefined){
|
|
16
|
+
const [, stored] = await getSetting('system', 'session_cleanup_hours', 24);
|
|
17
|
+
cutoffHours = Number(stored);
|
|
18
|
+
if(!Number.isFinite(cutoffHours) || cutoffHours < 0) cutoffHours = 24;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if(cutoffHours === 0) return [null, { deleted: 0 }];
|
|
22
|
+
|
|
23
|
+
const removed = await db
|
|
24
|
+
.delete(session)
|
|
25
|
+
.where(lt(session.expiresAt, new Date(now.getTime() - cutoffHours * 3600000)))
|
|
26
|
+
.returning({ token: session.token });
|
|
27
|
+
|
|
28
|
+
return [null, { deleted: removed.length }];
|
|
29
|
+
} catch(error) {
|
|
30
|
+
return [{ code: 500, msg: 'Failed to delete expired sessions' }, null];
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -12,6 +12,7 @@ const RULES = {
|
|
|
12
12
|
'system:email_retention_by_type': value => (value && typeof value === 'object' && !Array.isArray(value) && Object.values(value).every(days => whole(days, 0, 3650))
|
|
13
13
|
? null
|
|
14
14
|
: { code: 400, msg: 'Give each kind of email a whole number of days from 0 to 3650, like { "verify-email": 7 }' }),
|
|
15
|
+
'system:session_cleanup_hours': value => (whole(value, 0, 8760) ? null : { code: 400, msg: 'Hours must be a whole number from 0 to 8760 (one year)' }),
|
|
15
16
|
'system:email_from': value => (value === '' || isValidSender(value) ? null : { code: 400, msg: 'The sender must look like name@example.com or Shop Name <name@example.com>' }),
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -14,6 +14,7 @@ export const SYSTEM_SETTINGS = [
|
|
|
14
14
|
{ owner: 'system', name: 'allow_registration', value: true, type: 'boolean', isPublic: true, description: 'Allow new users to register' },
|
|
15
15
|
{ owner: 'system', name: 'require_email_verification', value: false, type: 'boolean', isPublic: false, description: 'Require email verification before account access' },
|
|
16
16
|
{ owner: 'system', name: 'verification_url', value: 'http://localhost:3000/verify-email/{{token}}', type: 'string', isPublic: false, description: 'Email verification URL template' },
|
|
17
|
+
{ owner: 'system', name: 'session_cleanup_hours', value: 24, type: 'number', isPublic: false, description: 'Hours after a session expires before it is deleted automatically (0 keeps expired sessions)' },
|
|
17
18
|
{ owner: 'system', name: 'notification_retention_days', value: 90, type: 'number', isPublic: false, description: 'Days to keep notifications before they are deleted (0 keeps them forever)' },
|
|
18
19
|
{ owner: 'system', name: 'password_reset_url', value: 'http://localhost:3000/reset-password/{{token}}', type: 'string', isPublic: false, description: 'Password reset URL template' },
|
|
19
20
|
{ owner: 'system', name: 'email_max_retries', value: 1, type: 'number', isPublic: false, description: 'How many times kempo tries again to send an email that could not be sent, after the first try (0 never retries)' },
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import t from"/kempo-ui/components/ShadowComponent.js";import{html as e}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Tabs.js";import"./SettingsPanel.js";import s from"./humanize.js";export default class n extends t{static properties={extensionTabs:{state:!0}};constructor(){super(),this.extensionTabs=[]}connectedCallback(){super.connectedCallback(),this.extensionTabs=[...this.querySelectorAll(":scope > k-settings-tab")].map((t,e)=>{const n=t.getAttribute("name")||t.getAttribute("label")||`tab-${e}`;return t.slot=`tab-${n}`,t.style.display="block",{id:n,slotName:`tab-${n}`,label:t.getAttribute("label")||s(n),position:Number(t.getAttribute("position"))||500,extension:!0}})}render(){const t=[{id:"custom",label:"Custom",position:100,content:()=>e`<k-settings-panel owner="custom"></k-settings-panel>`},{id:"system",label:"System",position:200,content:()=>e`<k-settings-panel owner="system"></k-settings-panel>`},...this.extensionTabs].sort((t,e)=>t.position-e.position);return e`
|
|
2
|
-
<k-tabs>
|
|
3
|
-
${t.map(t=>e`<k-tab for=${t.id}>${t.label}</k-tab>`)}
|
|
4
|
-
${t.map(t=>e`<k-tab-content name=${t.id} class="pt">${t.extension?e`<slot name=${t.slotName}></slot>`:t.content()}</k-tab-content>`)}
|
|
5
|
-
</k-tabs>
|
|
6
|
-
`}}customElements.define("k-admin-settings",n);
|
|
7
|
-
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\settings\AdminSettings.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"src\\admin\\components\\settings\\AdminSettings.js","names":["ShadowComponent","html","humanize","AdminSettings","static","extensionTabs","state","constructor","super","this","connectedCallback","querySelectorAll","map","$tab","index","name","getAttribute","slot","style","display","id","slotName","label","position","Number","extension","render","tabs","content","sort","a","b","tab","customElements","define"],"sources":["0"],"sourcesContent":["import ShadowComponent from '/kempo-ui/components/ShadowComponent.js';\nimport { html } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Tabs.js';\nimport './SettingsPanel.js';\nimport humanize from './humanize.js';\n\n/*\n The admin Settings page, served at /admin/components/settings/AdminSettings.js. Tabs: **Custom** (the site's own settings) and **System**\n (kempo's), then a tab for each extension that chooses to add one. An extension that does not add a tab has nothing here: it manages\n its own settings wherever it likes.\n\n **An extension adds a tab** by filling the `admin-settings-tabs` location of the page with a `k-settings-tab` (and loads what the tab\n uses from the `admin-settings-scripts` location). `position` orders the tabs (Custom is 100, System 200, a tab with none 500):\n\n <k-settings-tab name=\"shipping\" label=\"Shipping\" position=\"500\">\n <k-settings-panel owner=\"kempo-shipping\"></k-settings-panel>\n </k-settings-tab>\n\n The tab holds anything: the generic panel above, a few of the extension's settings with their editors, or a screen of its own.\n*/\nconst DEFAULT_TAB_POSITION = 500;\n\nexport default class AdminSettings extends ShadowComponent {\n static properties = {\n extensionTabs: { state: true },\n };\n\n constructor(){\n super();\n this.extensionTabs = [];\n }\n\n /*\n Lifecycle\n */\n connectedCallback(){\n super.connectedCallback();\n this.extensionTabs = [...this.querySelectorAll(':scope > k-settings-tab')].map(($tab, index) => {\n const name = $tab.getAttribute('name') || $tab.getAttribute('label') || `tab-${index}`;\n $tab.slot = `tab-${name}`;\n $tab.style.display = 'block';\n return {\n id: name,\n slotName: `tab-${name}`,\n label: $tab.getAttribute('label') || humanize(name),\n position: Number($tab.getAttribute('position')) || DEFAULT_TAB_POSITION,\n extension: true,\n };\n });\n }\n\n /*\n Rendering\n */\n render(){\n const tabs = [\n { id: 'custom', label: 'Custom', position: 100, content: () => html`<k-settings-panel owner=\"custom\"></k-settings-panel>` },\n { id: 'system', label: 'System', position: 200, content: () => html`<k-settings-panel owner=\"system\"></k-settings-panel>` },\n ...this.extensionTabs,\n ].sort((a, b) => a.position - b.position);\n\n return html`\n <k-tabs>\n ${tabs.map(tab => html`<k-tab for=${tab.id}>${tab.label}</k-tab>`)}\n ${tabs.map(tab => html`<k-tab-content name=${tab.id} class=\"pt\">${tab.extension ? html`<slot name=${tab.slotName}></slot>` : tab.content()}</k-tab-content>`)}\n </k-tabs>\n `;\n }\n}\n\ncustomElements.define('k-admin-settings', AdminSettings);\n"],"mappings":"OAAOA,MAAqB,yDACnBC,MAAY,iCACd,qCACA,4BACAC,MAAc,+BAkBN,MAAMC,UAAsBH,EACzCI,kBAAoB,CAClBC,cAAe,CAAEC,OAAO,IAG1B,WAAAC,GACEC,QACAC,KAAKJ,cAAgB,EACvB,CAKA,iBAAAK,GACEF,MAAME,oBACND,KAAKJ,cAAgB,IAAII,KAAKE,iBAAiB,4BAA4BC,IAAI,CAACC,EAAMC,KACpF,MAAMC,EAAOF,EAAKG,aAAa,SAAWH,EAAKG,aAAa,UAAY,OAAOF,IAG/E,OAFAD,EAAKI,KAAO,OAAOF,IACnBF,EAAKK,MAAMC,QAAU,QACd,CACLC,GAAIL,EACJM,SAAU,OAAON,IACjBO,MAAOT,EAAKG,aAAa,UAAYd,EAASa,GAC9CQ,SAAUC,OAAOX,EAAKG,aAAa,cAzBd,IA0BrBS,WAAW,IAGjB,CAKA,MAAAC,GACE,MAAMC,EAAO,CACX,CAAEP,GAAI,SAAUE,MAAO,SAAUC,SAAU,IAAKK,QAAS,IAAM3B,CAAI,wDACnE,CAAEmB,GAAI,SAAUE,MAAO,SAAUC,SAAU,IAAKK,QAAS,IAAM3B,CAAI,2DAChEQ,KAAKJ,eACRwB,KAAK,CAACC,EAAGC,IAAMD,EAAEP,SAAWQ,EAAER,UAEhC,OAAOtB,CAAI;;UAEL0B,EAAKf,IAAIoB,GAAO/B,CAAI,cAAc+B,EAAIZ,MAAMY,EAAIV;UAChDK,EAAKf,IAAIoB,GAAO/B,CAAI,uBAAuB+B,EAAIZ,iBAAiBY,EAAIP,UAAYxB,CAAI,cAAc+B,EAAIX,mBAAqBW,EAAIJ;;KAGvI,EAGFK,eAAeC,OAAO,mBAAoB/B","ignoreList":[]}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import e from"../AdminTableControl.js";import{html as n}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Icon.js";export default class t extends e{connectedCallback(){super.connectedCallback(),this.hasAttribute("title")||(this.title="View in Store")}handleAction(){this.record&&(window.location.href=`/admin/extensions/details/?name=${encodeURIComponent(this.record.name)}`)}render(){return n`<k-icon name="storefront"></k-icon>`}}customElements.define("admin-extension-details",t);
|
|
2
|
-
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\tableControls\ExtensionDetails.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"src\\admin\\components\\tableControls\\ExtensionDetails.js","names":["AdminTableControl","html","ExtensionDetails","connectedCallback","super","this","hasAttribute","title","handleAction","record","window","location","href","encodeURIComponent","name","render","customElements","define"],"sources":["0"],"sourcesContent":["import AdminTableControl from '../AdminTableControl.js';\nimport { html } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Icon.js';\n\n/*\n Opens the marketplace page for a row's extension: its README, what it requires and adds, its rating (which\n can be changed from there once it is installed). The page shows whatever the marketplace knows; for an\n extension it does not list it says so.\n*/\nexport default class ExtensionDetails extends AdminTableControl {\n connectedCallback(){\n super.connectedCallback();\n if(!this.hasAttribute('title')) this.title = 'View in Store';\n }\n\n handleAction(){\n if(!this.record) return;\n window.location.href = `/admin/extensions/details/?name=${encodeURIComponent(this.record.name)}`;\n }\n\n render(){ return html`<k-icon name=\"storefront\"></k-icon>`; }\n}\n\ncustomElements.define('admin-extension-details', ExtensionDetails);\n"],"mappings":"OAAOA,MAAuB,yCACrBC,MAAY,iCACd,8CAOQ,MAAMC,UAAyBF,EAC5C,iBAAAG,GACEC,MAAMD,oBACFE,KAAKC,aAAa,WAAUD,KAAKE,MAAQ,gBAC/C,CAEA,YAAAC,GACMH,KAAKI,SACTC,OAAOC,SAASC,KAAO,mCAAmCC,mBAAmBR,KAAKI,OAAOK,QAC3F,CAEA,MAAAC,GAAU,OAAOd,CAAI,qCAAuC,EAG9De,eAAeC,OAAO,0BAA2Bf","ignoreList":[]}
|