@reldens/cms 0.20.0 → 0.23.0
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/README.md +648 -12
- package/admin/reldens-admin-client.css +77 -141
- package/admin/reldens-admin-client.js +108 -133
- package/admin/templates/clear-all-cache-button.html +7 -7
- package/admin/templates/edit.html +7 -0
- package/admin/templates/fields/view/audio.html +7 -0
- package/admin/templates/fields/view/audios.html +8 -0
- package/admin/templates/layout.html +15 -9
- package/admin/templates/list-content.html +4 -2
- package/admin/templates/list.html +24 -8
- package/admin/templates/view.html +21 -0
- package/install/index.html +4 -0
- package/lib/admin-manager/admin-filters-manager.js +177 -0
- package/lib/admin-manager/contents-builder.js +1 -0
- package/lib/admin-manager/default-translations.js +38 -0
- package/lib/admin-manager/router-contents.js +64 -52
- package/lib/admin-manager/router.js +19 -0
- package/lib/dynamic-form-renderer.js +228 -0
- package/lib/dynamic-form-request-handler.js +135 -0
- package/lib/dynamic-form.js +310 -0
- package/lib/frontend/content-renderer.js +178 -0
- package/lib/frontend/entity-access-manager.js +63 -0
- package/lib/frontend/request-processor.js +128 -0
- package/lib/frontend/response-manager.js +54 -0
- package/lib/frontend/template-cache.js +102 -0
- package/lib/frontend/template-resolver.js +111 -0
- package/lib/frontend.js +122 -629
- package/lib/installer.js +2 -1
- package/lib/manager.js +25 -12
- package/lib/search-renderer.js +15 -7
- package/lib/search-request-handler.js +67 -0
- package/lib/search.js +13 -1
- package/lib/template-engine/collections-single-transformer.js +11 -5
- package/lib/template-engine/collections-transformer.js +47 -34
- package/lib/template-engine/entities-transformer.js +3 -2
- package/lib/template-engine/forms-transformer.js +187 -0
- package/lib/template-engine/partials-transformer.js +5 -6
- package/lib/template-engine/system-variables-provider.js +4 -1
- package/lib/template-engine.js +28 -5
- package/lib/template-reloader.js +307 -0
- package/lib/templates-list.js +2 -0
- package/migrations/default-forms.sql +22 -0
- package/package.json +5 -5
- package/templates/{browserconfig.xml → assets/favicons/default/browserconfig.xml} +1 -1
- package/templates/assets/favicons/default/favicon.ico +0 -0
- package/templates/{site.webmanifest → assets/favicons/default/site.webmanifest} +3 -3
- package/templates/cms_forms/field_email.html +14 -0
- package/templates/cms_forms/field_number.html +17 -0
- package/templates/cms_forms/field_select.html +15 -0
- package/templates/cms_forms/field_text.html +16 -0
- package/templates/cms_forms/field_textarea.html +13 -0
- package/templates/cms_forms/form.html +22 -0
- package/templates/css/styles.css +4 -0
- package/templates/js/functions.js +144 -0
- package/templates/js/scripts.js +5 -0
- package/templates/page.html +11 -5
- package/templates/partials/pagedCollection.html +1 -1
- package/lib/admin-translations.js +0 -56
- package/templates/favicon.ico +0 -0
- /package/templates/assets/favicons/{android-icon-144x144.png → default/android-icon-144x144.png} +0 -0
- /package/templates/assets/favicons/{android-icon-192x192.png → default/android-icon-192x192.png} +0 -0
- /package/templates/assets/favicons/{android-icon-512x512.png → default/android-icon-512x512.png} +0 -0
- /package/templates/assets/favicons/{apple-touch-icon.png → default/apple-touch-icon.png} +0 -0
- /package/templates/assets/favicons/{favicon-16x16.png → default/favicon-16x16.png} +0 -0
- /package/templates/assets/favicons/{favicon-32x32.png → default/favicon-32x32.png} +0 -0
- /package/templates/assets/favicons/{mstile-150x150.png → default/mstile-150x150.png} +0 -0
- /package/templates/assets/favicons/{safari-pinned-tab.svg → default/safari-pinned-tab.svg} +0 -0
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<title>{{&brandingCompanyName}}</title>
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
<link rel="
|
|
11
|
-
<
|
|
12
|
-
<link rel="
|
|
5
|
+
<meta name="language" content="en"/>
|
|
6
|
+
<meta charset="utf-8"/>
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes, viewport-fit=cover"/>
|
|
8
|
+
<!-- favicons -->
|
|
9
|
+
<meta name="msapplication-config" content="/assets/favicons/default/browserconfig.xml"/>
|
|
10
|
+
<link rel="icon" href="/assets/favicons/default/favicon.ico" type="image/x-icon"/>
|
|
11
|
+
<link rel="shortcut icon" href="/assets/favicons/default/favicon.ico" type="image/x-icon">
|
|
12
|
+
<link rel="manifest" href="/assets/favicons/default/site.webmanifest"/>
|
|
13
|
+
<!-- CSS and JS -->
|
|
14
|
+
<link crossorigin rel="preconnect" href="https://fonts.googleapis.com"/>
|
|
15
|
+
<link crossorigin rel="preconnect" href="https://fonts.gstatic.com"/>
|
|
16
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Play:300,300i,400,400i,500,500i,600,600i,700,700i"/>
|
|
17
|
+
<link rel="stylesheet" href="{{stylesFilePath}}"/>
|
|
18
|
+
<script type="text/javascript" src="/js/functions.js"></script>
|
|
13
19
|
<script type="module" src="{{scriptsFilePath}}"></script>
|
|
14
20
|
</head>
|
|
15
21
|
<body class="reldens-admin-panel">
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<tr class="row row-header">
|
|
2
2
|
<th class="field field-actions">
|
|
3
3
|
<div class="field-actions-container">
|
|
4
|
-
<button class="button button-primary list-select" data-checked="1">Select All/None</button>
|
|
5
4
|
<form name="delete-selection-form" id="delete-selection-form" action="{{&deletePath}}" method="post">
|
|
6
5
|
<input type="hidden" name="ids" class="hidden-ids-input"/>
|
|
7
|
-
<button class="button button-danger list-delete-selection">Delete
|
|
6
|
+
<button class="button button-danger list-delete-selection">Bulk Delete</button>
|
|
8
7
|
</form>
|
|
8
|
+
<button class="button button-primary list-select" data-checked="1">
|
|
9
|
+
<input type="checkbox" id="input-check-select-all"/>
|
|
10
|
+
</button>
|
|
9
11
|
</div>
|
|
10
12
|
</th>
|
|
11
13
|
{{#fieldsHeaders}}
|
|
@@ -8,10 +8,20 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
<div class="sub-content filters">
|
|
10
10
|
<form class="sub-content-form filter-form" name="filter-form" id="filter-form" action="{{&entityListRoute}}" method="post">
|
|
11
|
-
<
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
<div class="filters-header">
|
|
12
|
+
<div class="search-controls">
|
|
13
|
+
<div class="filters-toggle">
|
|
14
|
+
<img src="/assets/admin/filters.png" alt="Filters"/>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="search-input-group">
|
|
17
|
+
<input type="text" id="entityFilterTerm" name="entityFilterTerm" placeholder="Search..." value="{{&entityFilterTermValue}}"/>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="filter-actions">
|
|
20
|
+
<input class="button button-primary button-filter" type="submit" value="Filter"/>
|
|
21
|
+
<a class="button button-secondary button-clear-filter" href="{{&entityListRoute}}?clearFilters=true">Clear Filters</a>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
15
25
|
<div class="filters-toggle-content hidden">
|
|
16
26
|
{{#filters}}
|
|
17
27
|
<div class="sub-content-box filter">
|
|
@@ -19,10 +29,6 @@
|
|
|
19
29
|
<input type="text" id="filter-{{&propertyKey}}" name="filters[{{&propertyKey}}]"{{&value}}/>
|
|
20
30
|
</div>
|
|
21
31
|
{{/filters}}
|
|
22
|
-
<div class="actions">
|
|
23
|
-
<input class="button button-primary button-filter" type="submit" value="Filter"/>
|
|
24
|
-
<a class="button button-secondary button-clear-filter" href="{{&entityListRoute}}">Clear Filters</a>
|
|
25
|
-
</div>
|
|
26
32
|
</div>
|
|
27
33
|
</form>
|
|
28
34
|
</div>
|
|
@@ -33,3 +39,13 @@
|
|
|
33
39
|
{{&pagination}}
|
|
34
40
|
</div>
|
|
35
41
|
</div>
|
|
42
|
+
<dialog class="confirm-dialog">
|
|
43
|
+
<div class="dialog-content">
|
|
44
|
+
<h5 class="dialog-title">Confirm Delete</h5>
|
|
45
|
+
<p class="dialog-message">Are you sure you want to delete the selected items? This action cannot be undone.</p>
|
|
46
|
+
<div class="dialog-actions">
|
|
47
|
+
<button type="button" class="button button-secondary dialog-cancel">Cancel</button>
|
|
48
|
+
<button type="button" class="button button-danger dialog-confirm">Delete</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</dialog>
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
<div class="entity-view {{&entityName}}-view">
|
|
2
2
|
<h2>{{&templateTitle}}</h2>
|
|
3
|
+
<div class="actions">
|
|
4
|
+
<a class="button button-primary" href="{{&entityNewRoute}}">Create New</a>
|
|
5
|
+
<a class="button button-primary" href="{{&entityEditRoute}}">Edit</a>
|
|
6
|
+
<a class="button button-secondary" href="{{&entityListRoute}}">Back</a>
|
|
7
|
+
<form class="form-delete" name="delete-form-top-{{&id}}" id="delete-form-top-{{&id}}" action="{{&entityDeleteRoute}}" method="post">
|
|
8
|
+
<span>
|
|
9
|
+
<input type="hidden" name="ids[]" value="{{&id}}"/>
|
|
10
|
+
<button class="button button-danger" type="submit">Delete</button>
|
|
11
|
+
</span>
|
|
12
|
+
</form>
|
|
13
|
+
</div>
|
|
3
14
|
{{#fields}}
|
|
4
15
|
<div class="view-field">
|
|
5
16
|
<span class="field-name">{{&name}}</span>
|
|
@@ -22,3 +33,13 @@
|
|
|
22
33
|
</div>
|
|
23
34
|
</div>
|
|
24
35
|
</div>
|
|
36
|
+
<dialog class="confirm-dialog">
|
|
37
|
+
<div class="dialog-content">
|
|
38
|
+
<h5 class="dialog-title">Confirm Delete</h5>
|
|
39
|
+
<p class="dialog-message">Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
40
|
+
<div class="dialog-actions">
|
|
41
|
+
<button type="button" class="button button-secondary dialog-cancel">Cancel</button>
|
|
42
|
+
<button type="button" class="button button-danger dialog-confirm">Delete</button>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</dialog>
|
package/install/index.html
CHANGED
|
@@ -101,6 +101,10 @@
|
|
|
101
101
|
<input type="checkbox" name="install-entity-access" id="install-entity-access" checked/>
|
|
102
102
|
<label for="install-entity-access">Install entity access control rules</label>
|
|
103
103
|
</div>
|
|
104
|
+
<div class="input-box input-checkbox install-dynamic-forms">
|
|
105
|
+
<input type="checkbox" name="install-dynamic-forms" id="install-dynamic-forms" checked/>
|
|
106
|
+
<label for="install-dynamic-forms">Install dynamic forms system</label>
|
|
107
|
+
</div>
|
|
104
108
|
<div class="input-box submit-container">
|
|
105
109
|
<input id="install-submit-button" type="submit" value="Install"/>
|
|
106
110
|
<img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - AdminFiltersManager
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Logger } = require('@reldens/utils');
|
|
8
|
+
|
|
9
|
+
class AdminFiltersManager
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
getFiltersFromSession(req, entityPath)
|
|
13
|
+
{
|
|
14
|
+
if(!req?.session){
|
|
15
|
+
return {regular: {}, entityFilterTerm: ''};
|
|
16
|
+
}
|
|
17
|
+
let sessionKey = 'adminFilters_'+entityPath;
|
|
18
|
+
let sessionData = req.session[sessionKey];
|
|
19
|
+
if(!sessionData || 'object' !== typeof sessionData){
|
|
20
|
+
return {regular: {}, entityFilterTerm: ''};
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
regular: sessionData.regular || {},
|
|
24
|
+
entityFilterTerm: sessionData.entityFilterTerm || ''
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
saveFiltersToSession(req, entityPath, filters)
|
|
29
|
+
{
|
|
30
|
+
if(!req?.session){
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
let sessionKey = 'adminFilters_'+entityPath;
|
|
34
|
+
req.session[sessionKey] = filters;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
clearFiltersFromSession(req, entityPath)
|
|
39
|
+
{
|
|
40
|
+
if(!req?.session){
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
let sessionKey = 'adminFilters_'+entityPath;
|
|
44
|
+
delete req.session[sessionKey];
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
prepareTextFilters(entityFilterTerm, driverResource)
|
|
49
|
+
{
|
|
50
|
+
if(!entityFilterTerm || '' === entityFilterTerm.trim()){
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
let textFields = [];
|
|
54
|
+
for(let propertyKey of Object.keys(driverResource.options.properties)){
|
|
55
|
+
let property = driverResource.options.properties[propertyKey];
|
|
56
|
+
if(property.isId){
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if(property.isUpload){
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if('reference' === property.type){
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if('boolean' === property.type){
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if('number' === property.type){
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if('datetime' === property.type){
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if('int' === property.dbType){
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if('bigint' === property.dbType){
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if('decimal' === property.dbType){
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if('float' === property.dbType){
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if('double' === property.dbType){
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if('datetime' === property.dbType){
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if('timestamp' === property.dbType){
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if('date' === property.dbType){
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if('time' === property.dbType){
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if('boolean' === property.dbType){
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
textFields.push(propertyKey);
|
|
105
|
+
}
|
|
106
|
+
if(0 === textFields.length){
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
let orConditions = [];
|
|
110
|
+
for(let field of textFields){
|
|
111
|
+
orConditions.push({
|
|
112
|
+
[field]: {operator: 'LIKE', value: '%'+entityFilterTerm+'%'}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return {OR: orConditions};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
combineFilters(regularFilters, textFilters)
|
|
119
|
+
{
|
|
120
|
+
let hasRegularFilters = regularFilters && 'object' === typeof regularFilters && 0 < Object.keys(regularFilters).length;
|
|
121
|
+
let hasTextFilters = textFilters && 'object' === typeof textFilters && 0 < Object.keys(textFilters).length;
|
|
122
|
+
if(!hasRegularFilters && !hasTextFilters){
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
if(!hasRegularFilters){
|
|
126
|
+
return textFilters;
|
|
127
|
+
}
|
|
128
|
+
if(!hasTextFilters){
|
|
129
|
+
return regularFilters;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
AND: [
|
|
133
|
+
regularFilters,
|
|
134
|
+
textFilters
|
|
135
|
+
]
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
prepareFilters(filtersList, driverResource)
|
|
140
|
+
{
|
|
141
|
+
if(!filtersList || 'object' !== typeof filtersList || 0 === Object.keys(filtersList).length){
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
let filters = {};
|
|
145
|
+
for(let i of Object.keys(filtersList)){
|
|
146
|
+
let filter = filtersList[i];
|
|
147
|
+
if('' === filter || null === filter || undefined === filter){
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
let rawConfigFilterProperties = driverResource.options.properties[i];
|
|
151
|
+
if(!rawConfigFilterProperties){
|
|
152
|
+
Logger.critical('Could not found property by key.', i);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if(rawConfigFilterProperties.isUpload){
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if('reference' === rawConfigFilterProperties.type){
|
|
159
|
+
filters[i] = Number(filter);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if('boolean' === rawConfigFilterProperties.type){
|
|
163
|
+
filters[i] = ('true' === filter || '1' === filter || 1 === filter);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if('number' === rawConfigFilterProperties.type || rawConfigFilterProperties.isId){
|
|
167
|
+
filters[i] = Number(filter);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
filters[i] = {operator: 'LIKE', value: '%'+filter+'%'};
|
|
171
|
+
}
|
|
172
|
+
return filters;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports.AdminFiltersManager = AdminFiltersManager;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Reldens - DefaultTranslations
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports.DefaultTranslations = {
|
|
8
|
+
messages: {
|
|
9
|
+
loginWelcome: 'CMS - Login',
|
|
10
|
+
reldensTitle: 'Reldens - CMS',
|
|
11
|
+
reldensSlogan: 'You can do it',
|
|
12
|
+
reldensDiscordTitle: 'Join our Discord server!',
|
|
13
|
+
reldensDiscordText: 'Talk with the creators and other Reldens users',
|
|
14
|
+
reldensGithubTitle: 'Find us on GitHub!',
|
|
15
|
+
reldensGithubText: 'Need a new feature?'
|
|
16
|
+
+' Would you like to contribute with code?'
|
|
17
|
+
+' Find the source code or create an issue in GitHub',
|
|
18
|
+
reldensLoading: 'Loading...',
|
|
19
|
+
confirmClearCacheMessage: 'Are you sure you want to clear all cache? This action cannot be undone.',
|
|
20
|
+
clearCacheWarning: 'This will clear all cached routes and may impact site performance temporarily.'
|
|
21
|
+
},
|
|
22
|
+
labels: {
|
|
23
|
+
navigation: 'Reldens - CMS',
|
|
24
|
+
adminVersion: 'Admin: {{version}}',
|
|
25
|
+
loginWelcome: 'Reldens',
|
|
26
|
+
pages: 'Server Management',
|
|
27
|
+
management: 'Management',
|
|
28
|
+
shuttingDown: 'Server is shutting down in:',
|
|
29
|
+
submitShutdownLabel: 'Shutdown Server',
|
|
30
|
+
submitCancelLabel: 'Cancel Server Shutdown',
|
|
31
|
+
cleanCache: 'Clean Cache',
|
|
32
|
+
clearAllCache: 'Clear All Cache',
|
|
33
|
+
confirmClearCache: 'Confirm Clear Cache',
|
|
34
|
+
warning: 'Warning:',
|
|
35
|
+
cancel: 'Cancel',
|
|
36
|
+
confirm: 'Continue',
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const { PageRangeProvider, Logger, sc } = require('@reldens/utils');
|
|
8
8
|
const { FileHandler } = require('@reldens/server-utils');
|
|
9
|
+
const { AdminFiltersManager } = require('./admin-filters-manager');
|
|
9
10
|
|
|
10
11
|
class RouterContents
|
|
11
12
|
{
|
|
@@ -30,29 +31,64 @@ class RouterContents
|
|
|
30
31
|
this.fetchTranslation = props.fetchTranslation;
|
|
31
32
|
this.fetchEntityIdPropertyKey = props.fetchEntityIdPropertyKey;
|
|
32
33
|
this.fetchUploadProperties = props.fetchUploadProperties;
|
|
34
|
+
this.filtersManager = new AdminFiltersManager();
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
async generateListRouteContent(req, driverResource, entityPath)
|
|
36
38
|
{
|
|
37
39
|
let currentPage = Number(req?.query?.page || 1);
|
|
38
40
|
let pageSize = Number(req?.query?.pageSize || 25);
|
|
39
|
-
let
|
|
40
|
-
let
|
|
41
|
-
let
|
|
41
|
+
let shouldClearFilters = 'true' === req?.query?.clearFilters;
|
|
42
|
+
let sessionFilters = this.filtersManager.getFiltersFromSession(req, entityPath);
|
|
43
|
+
let filtersFromParams = shouldClearFilters ? {} : req?.body?.filters || req?.query?.filters || {};
|
|
44
|
+
let entityFilterTerm = shouldClearFilters ? '' : req?.body?.entityFilterTerm || req?.query?.entityFilterTerm || '';
|
|
45
|
+
if(shouldClearFilters){
|
|
46
|
+
this.filtersManager.clearFiltersFromSession(req, entityPath);
|
|
47
|
+
}
|
|
48
|
+
let hasNewEntityFilterTerm = entityFilterTerm && '' !== entityFilterTerm;
|
|
49
|
+
let hasNewToggleFilters = Object.keys(filtersFromParams).length > 0;
|
|
50
|
+
if(hasNewEntityFilterTerm && hasNewToggleFilters){
|
|
51
|
+
filtersFromParams = {};
|
|
52
|
+
hasNewToggleFilters = false;
|
|
53
|
+
}
|
|
54
|
+
if(hasNewEntityFilterTerm || hasNewToggleFilters){
|
|
55
|
+
let filtersToSave = {
|
|
56
|
+
regular: hasNewToggleFilters ? filtersFromParams : {},
|
|
57
|
+
entityFilterTerm: hasNewEntityFilterTerm ? entityFilterTerm : ''
|
|
58
|
+
};
|
|
59
|
+
this.filtersManager.saveFiltersToSession(req, entityPath, filtersToSave);
|
|
60
|
+
sessionFilters = filtersToSave;
|
|
61
|
+
}
|
|
62
|
+
let mergedFilters = shouldClearFilters ? {} : Object.assign({}, sessionFilters.regular || {});
|
|
63
|
+
let finalEntityFilterTerm = shouldClearFilters ? '' : sessionFilters.entityFilterTerm || '';
|
|
64
|
+
let filters = this.filtersManager.prepareFilters(mergedFilters, driverResource);
|
|
65
|
+
let textFilters = this.filtersManager.prepareTextFilters(finalEntityFilterTerm, driverResource);
|
|
66
|
+
let combinedFilters = this.filtersManager.combineFilters(filters, textFilters);
|
|
67
|
+
let totalEntities = await this.countTotalEntities(driverResource, combinedFilters);
|
|
42
68
|
let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
|
|
43
69
|
let renderedPagination = '';
|
|
44
70
|
for(let paginationItem of PageRangeProvider.fetch(currentPage, totalPages)){
|
|
71
|
+
let paginationUrl = this.rootPath+'/'+driverResource.entityPath+'?page='+ paginationItem.value;
|
|
72
|
+
if(finalEntityFilterTerm){
|
|
73
|
+
paginationUrl += '&entityFilterTerm='+encodeURIComponent(finalEntityFilterTerm);
|
|
74
|
+
}
|
|
75
|
+
for(let filterKey of Object.keys(mergedFilters)){
|
|
76
|
+
if(mergedFilters[filterKey]){
|
|
77
|
+
paginationUrl += '&filters['+filterKey+']='+encodeURIComponent(mergedFilters[filterKey]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
45
80
|
renderedPagination += await this.adminContentsRender(
|
|
46
81
|
this.adminFilesContents.fields.view['link'],
|
|
47
82
|
{
|
|
48
83
|
fieldName: paginationItem.label,
|
|
49
|
-
fieldValue:
|
|
84
|
+
fieldValue: paginationUrl,
|
|
50
85
|
fieldOriginalValue: paginationItem.value,
|
|
51
86
|
}
|
|
52
87
|
);
|
|
53
88
|
}
|
|
54
89
|
let listProperties = {
|
|
55
|
-
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath
|
|
90
|
+
entityNewRoute: this.rootPath+'/'+driverResource.entityPath+this.editPath,
|
|
91
|
+
entityFilterTermValue: finalEntityFilterTerm || ''
|
|
56
92
|
};
|
|
57
93
|
await this.emitEvent('reldens.adminListPropertiesPopulation', {
|
|
58
94
|
req,
|
|
@@ -73,12 +109,13 @@ class RouterContents
|
|
|
73
109
|
);
|
|
74
110
|
return {name: property, value: '' !== alias ? alias + ' ('+propertyTitle+')' : propertyTitle};
|
|
75
111
|
}),
|
|
76
|
-
rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req,
|
|
112
|
+
rows: await this.loadEntitiesForList(driverResource, pageSize, currentPage, req, combinedFilters)
|
|
77
113
|
}),
|
|
78
114
|
pagination: renderedPagination,
|
|
79
|
-
extraContentForList: sc.get(listProperties, 'extraContentForList', '')
|
|
115
|
+
extraContentForList: sc.get(listProperties, 'extraContentForList', ''),
|
|
116
|
+
entityFilterTermValue: listProperties.entityFilterTermValue
|
|
80
117
|
}, ...driverResource.options.filterProperties.map((property) => {
|
|
81
|
-
let filterValue = (
|
|
118
|
+
let filterValue = (mergedFilters[property] || '');
|
|
82
119
|
return {[property]: '' === filterValue ? '' : 'value="'+filterValue+'"'};
|
|
83
120
|
}))
|
|
84
121
|
),
|
|
@@ -237,9 +274,13 @@ class RouterContents
|
|
|
237
274
|
}
|
|
238
275
|
}
|
|
239
276
|
}
|
|
240
|
-
|
|
277
|
+
let saveAction = sc.get(req.body, 'saveAction', 'save');
|
|
278
|
+
if('saveAndContinue' === saveAction){
|
|
241
279
|
return this.generateEntityRoute('editPath', driverResource, idProperty, saveResult) +'&result=success';
|
|
242
280
|
}
|
|
281
|
+
if('saveAndGoBack' === saveAction){
|
|
282
|
+
return this.rootPath+'/'+entityPath+'?result=success';
|
|
283
|
+
}
|
|
243
284
|
return this.generateEntityRoute('viewPath', driverResource, idProperty, saveResult) +'&result=success';
|
|
244
285
|
} catch (error) {
|
|
245
286
|
Logger.error('Save entity error.', error);
|
|
@@ -420,7 +461,7 @@ class RouterContents
|
|
|
420
461
|
}
|
|
421
462
|
}
|
|
422
463
|
return await this.adminContentsRender(
|
|
423
|
-
this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType)],
|
|
464
|
+
this.adminFilesContents.fields.view[this.propertyType(resourceProperty, templateType, fieldValue)],
|
|
424
465
|
{fieldName, fieldValue, fieldOriginalValue, target: ' target="_blank"'}
|
|
425
466
|
);
|
|
426
467
|
}
|
|
@@ -428,7 +469,7 @@ class RouterContents
|
|
|
428
469
|
generatePropertyRenderedValueWithLabel(entity, propertyKey, resourceProperty)
|
|
429
470
|
{
|
|
430
471
|
let fieldValue = (0 === entity[propertyKey] ? '0' : entity[propertyKey] || '');
|
|
431
|
-
if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
|
|
472
|
+
if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
|
|
432
473
|
fieldValue = sc.toJsonString(fieldValue);
|
|
433
474
|
}
|
|
434
475
|
let fieldName = propertyKey;
|
|
@@ -471,7 +512,7 @@ class RouterContents
|
|
|
471
512
|
if('null' === fieldValue){
|
|
472
513
|
fieldValue = '';
|
|
473
514
|
}
|
|
474
|
-
if(sc.isObject(fieldValue) && 'json' === resourceProperty.dbType){
|
|
515
|
+
if((sc.isObject(fieldValue) || sc.isArray(fieldValue)) && 'json' === resourceProperty.dbType){
|
|
475
516
|
fieldValue = sc.toJsonString(fieldValue);
|
|
476
517
|
}
|
|
477
518
|
if('boolean' === resourceProperty.type){
|
|
@@ -521,7 +562,7 @@ class RouterContents
|
|
|
521
562
|
return await relationEntityRepository.loadAll();
|
|
522
563
|
}
|
|
523
564
|
|
|
524
|
-
propertyType(resourceProperty, templateType)
|
|
565
|
+
propertyType(resourceProperty, templateType, fieldValue)
|
|
525
566
|
{
|
|
526
567
|
let propertyType = sc.get(resourceProperty, 'type', 'text');
|
|
527
568
|
if('reference' === propertyType && 'edit' === templateType){
|
|
@@ -532,11 +573,18 @@ class RouterContents
|
|
|
532
573
|
return 'file';
|
|
533
574
|
}
|
|
534
575
|
if('view' === templateType){
|
|
576
|
+
if(!fieldValue || '' === fieldValue){
|
|
577
|
+
return 'text';
|
|
578
|
+
}
|
|
535
579
|
let multiple = resourceProperty.isArray ? 's' : '';
|
|
536
|
-
|
|
537
|
-
|
|
580
|
+
let allowedTypes = sc.get(resourceProperty, 'allowedTypes', '');
|
|
581
|
+
if('' !== allowedTypes){
|
|
582
|
+
let templateName = allowedTypes + multiple;
|
|
583
|
+
if(sc.hasOwn(this.adminFilesContents.fields.view, templateName)){
|
|
584
|
+
return templateName;
|
|
585
|
+
}
|
|
538
586
|
}
|
|
539
|
-
if('text' ===
|
|
587
|
+
if('text' === allowedTypes){
|
|
540
588
|
return 'link'+multiple
|
|
541
589
|
}
|
|
542
590
|
return 'text';
|
|
@@ -577,42 +625,6 @@ class RouterContents
|
|
|
577
625
|
return this.rootPath + '/' + driverResource.entityPath + this[routeType] + idParam;
|
|
578
626
|
}
|
|
579
627
|
|
|
580
|
-
prepareFilters(filtersList, driverResource)
|
|
581
|
-
{
|
|
582
|
-
if(0 === Object.keys(filtersList).length){
|
|
583
|
-
return {};
|
|
584
|
-
}
|
|
585
|
-
let filters = {};
|
|
586
|
-
for(let i of Object.keys(filtersList)){
|
|
587
|
-
let filter = filtersList[i];
|
|
588
|
-
if('' === filter || null === filter || undefined === filter){
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
let rawConfigFilterProperties = driverResource.options.properties[i];
|
|
592
|
-
if(!rawConfigFilterProperties){
|
|
593
|
-
Logger.critical('Could not found property by key.', i);
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
if(rawConfigFilterProperties.isUpload){
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
if('reference' === rawConfigFilterProperties.type){
|
|
600
|
-
filters[i] = Number(filter);
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if('boolean' === rawConfigFilterProperties.type){
|
|
604
|
-
filters[i] = ('true' === filter || '1' === filter || 1 === filter);
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
if('number' === rawConfigFilterProperties.type || rawConfigFilterProperties.isId){
|
|
608
|
-
filters[i] = Number(filter);
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
filters[i] = {operator: 'LIKE', value: '%'+filter+'%'};
|
|
612
|
-
}
|
|
613
|
-
return filters;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
628
|
}
|
|
617
629
|
|
|
618
630
|
module.exports.RouterContents = RouterContents;
|
|
@@ -37,6 +37,7 @@ class Router
|
|
|
37
37
|
this.generateEditRouteContent = props.generateEditRouteContent;
|
|
38
38
|
this.processDeleteEntities = props.processDeleteEntities;
|
|
39
39
|
this.processSaveEntity = props.processSaveEntity;
|
|
40
|
+
this.checkAndReloadAdminTemplates = props.checkAndReloadAdminTemplates;
|
|
40
41
|
this.setupAdminRouter();
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -61,12 +62,22 @@ class Router
|
|
|
61
62
|
return true;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
async reloadTemplatesIfNeeded()
|
|
66
|
+
{
|
|
67
|
+
if(!this.checkAndReloadAdminTemplates){
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return await this.checkAndReloadAdminTemplates();
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
setupAdminRoutes()
|
|
65
74
|
{
|
|
66
75
|
this.adminRouter.get(this.loginPath, async (req, res) => {
|
|
76
|
+
await this.reloadTemplatesIfNeeded();
|
|
67
77
|
return res.send(this.adminContents().login);
|
|
68
78
|
});
|
|
69
79
|
this.adminRouter.post(this.loginPath, async (req, res) => {
|
|
80
|
+
//await this.reloadTemplatesIfNeeded();
|
|
70
81
|
let { email, password } = req.body;
|
|
71
82
|
let loginResult = await this.authenticationCallback(email, password, this.adminRoleId);
|
|
72
83
|
if(loginResult){
|
|
@@ -76,6 +87,7 @@ class Router
|
|
|
76
87
|
return res.redirect(this.rootPath+this.loginPath+'?login-error=true');
|
|
77
88
|
});
|
|
78
89
|
this.adminRouter.get('/', this.isAuthenticated.bind(this), async (req, res) => {
|
|
90
|
+
await this.reloadTemplatesIfNeeded();
|
|
79
91
|
return res.send(this.adminContents().dashboard);
|
|
80
92
|
});
|
|
81
93
|
this.adminRouter.get(this.logoutPath, (req, res) => {
|
|
@@ -95,12 +107,15 @@ class Router
|
|
|
95
107
|
let entityPath = driverResource.entityPath;
|
|
96
108
|
let entityRoute = '/'+entityPath;
|
|
97
109
|
this.adminRouter.get(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
|
|
110
|
+
await this.reloadTemplatesIfNeeded();
|
|
98
111
|
return res.send(await this.generateListRouteContent(req, driverResource, entityPath));
|
|
99
112
|
});
|
|
100
113
|
this.adminRouter.post(entityRoute, this.isAuthenticated.bind(this), async (req, res) => {
|
|
114
|
+
await this.reloadTemplatesIfNeeded();
|
|
101
115
|
return res.send(await this.generateListRouteContent(req, driverResource, entityPath));
|
|
102
116
|
});
|
|
103
117
|
this.adminRouter.get(entityRoute+this.viewPath, this.isAuthenticated.bind(this), async (req, res) => {
|
|
118
|
+
await this.reloadTemplatesIfNeeded();
|
|
104
119
|
let routeContents = await this.generateViewRouteContent(req, driverResource, entityPath);
|
|
105
120
|
if('' === routeContents){
|
|
106
121
|
return res.redirect(this.rootPath+'/'+entityPath+'?result=errorView');
|
|
@@ -108,6 +123,7 @@ class Router
|
|
|
108
123
|
return res.send(routeContents);
|
|
109
124
|
});
|
|
110
125
|
this.adminRouter.get(entityRoute+this.editPath, this.isAuthenticated.bind(this), async (req, res) => {
|
|
126
|
+
await this.reloadTemplatesIfNeeded();
|
|
111
127
|
await this.emitEvent('reldens.adminBeforeEntityEdit', {
|
|
112
128
|
req,
|
|
113
129
|
res,
|
|
@@ -122,6 +138,7 @@ class Router
|
|
|
122
138
|
});
|
|
123
139
|
this.setupSavePath(entityRoute, driverResource, entityPath);
|
|
124
140
|
this.adminRouter.post(entityRoute+this.deletePath, this.isAuthenticated.bind(this), async (req, res) => {
|
|
141
|
+
//await this.reloadTemplatesIfNeeded();
|
|
125
142
|
return res.redirect(await this.processDeleteEntities(req, res, driverResource, entityPath));
|
|
126
143
|
});
|
|
127
144
|
await this.emitEvent('reldens.setupEntitiesRoutes', {
|
|
@@ -140,6 +157,7 @@ class Router
|
|
|
140
157
|
entityRoute+this.savePath,
|
|
141
158
|
this.isAuthenticated.bind(this),
|
|
142
159
|
async (req, res) => {
|
|
160
|
+
//await this.reloadTemplatesIfNeeded();
|
|
143
161
|
await this.emitEvent('reldens.adminBeforeEntitySave', {
|
|
144
162
|
req,
|
|
145
163
|
res,
|
|
@@ -168,6 +186,7 @@ class Router
|
|
|
168
186
|
this.isAuthenticated.bind(this),
|
|
169
187
|
this.uploaderFactory.createUploader(fields, this.buckets, allowedFileTypes),
|
|
170
188
|
async (req, res) => {
|
|
189
|
+
//await this.reloadTemplatesIfNeeded();
|
|
171
190
|
await this.emitEvent('reldens.adminBeforeEntitySave', {
|
|
172
191
|
req,
|
|
173
192
|
res,
|