@reldens/cms 0.8.0 → 0.10.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 +316 -68
- package/admin/templates/fields/edit/text.html +1 -1
- package/install/css/installer.css +8 -0
- package/install/index.html +14 -6
- package/lib/admin-manager.js +39 -31
- package/lib/frontend.js +336 -105
- package/lib/installer.js +36 -9
- package/lib/manager.js +37 -5
- package/migrations/default-blocks.sql +8 -0
- package/migrations/default-entity-access.sql +9 -0
- package/migrations/default-homepage.sql +1 -1
- package/migrations/install.sql +34 -9
- package/migrations/users-authentication.sql +2 -2
- package/package.json +4 -4
- package/templates/404.html +7 -29
- package/templates/css/styles.css +4 -1
- package/templates/domains/.gitkeep +0 -0
- package/templates/js/scripts.js +9 -7
- package/templates/layouts/default.html +21 -0
- package/templates/page.html +14 -32
- package/templates/partials/footer.html +35 -0
- package/templates/partials/header.html +36 -0
package/README.md
CHANGED
|
@@ -1,112 +1,354 @@
|
|
|
1
1
|
[](https://github.com/damian-pastorini/reldens)
|
|
2
2
|
|
|
3
|
-
# Reldens
|
|
3
|
+
# Reldens CMS
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Reldens CMS is a straightforward content management system designed for easy setup and extensibility.
|
|
8
|
-
It provides both an administration panel for content management and a frontend for content delivery.
|
|
5
|
+
A powerful, flexible Content Management System built with Node.js, featuring an admin panel, multi-domain frontend support, enhanced templating with reusable content blocks, and automated installation.
|
|
9
6
|
|
|
10
7
|
## Features
|
|
11
8
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
9
|
+
### - Quick Setup
|
|
10
|
+
- **Web-based installer** with a guided setup process
|
|
11
|
+
- **Automatic database schema creation** and seeding
|
|
12
|
+
- **Environment configuration generation**
|
|
13
|
+
- **Directory structure initialization**
|
|
14
|
+
|
|
15
|
+
### - Frontend Engine
|
|
16
|
+
- **Multi-domain support** with domain-specific templates and partials
|
|
17
|
+
- **Dynamic routing** from database-driven routes
|
|
18
|
+
- **Entity-based URLs** (e.g., `/articles/123`)
|
|
19
|
+
- **Template fallback system** (domain → default → base)
|
|
20
|
+
- **Layout system** with body content layouts and page wrappers
|
|
21
|
+
- **Reusable content blocks** with `{{ entity() }}` template functions
|
|
22
|
+
- **Entity access control** for public/private content
|
|
23
|
+
- **Static asset serving** with Express integration as default
|
|
24
|
+
- **Template engine** with Mustache integration as default
|
|
25
|
+
- **Custom 404 handling**
|
|
26
|
+
|
|
27
|
+
### - Admin Panel
|
|
28
|
+
- **Full CRUD operations** for all entities including content blocks
|
|
29
|
+
- **File upload handling** with multiple storage buckets
|
|
30
|
+
- **Role-based authentication** and access control
|
|
31
|
+
- **Advanced filtering and search** across entity properties
|
|
32
|
+
- **Bulk operations** (delete multiple records)
|
|
33
|
+
- **Relationship management** with foreign key support
|
|
34
|
+
- **Template-driven UI** with customizable admin themes
|
|
35
|
+
|
|
36
|
+
### -️ Database & Entities
|
|
37
|
+
- **Multiple database drivers** (Prisma by default, others via DriversMap)
|
|
38
|
+
- **Automatic entity generation** from a database schema
|
|
39
|
+
- **Relationship mapping** and foreign key handling
|
|
40
|
+
- **Custom entity configuration** with validation rules
|
|
41
|
+
- **Translation support** for entity labels and properties
|
|
42
|
+
- **Content blocks management** via cms_blocks table
|
|
43
|
+
- **Entity access control** via cms_entity_access table
|
|
44
|
+
|
|
45
|
+
### - Configuration & Architecture
|
|
46
|
+
- **Environment-based configuration** (.env file)
|
|
47
|
+
- **Modular service architecture** (Frontend, AdminManager, DataServer)
|
|
48
|
+
- **Event-driven system** with hooks for customization
|
|
49
|
+
- **Extensible authentication** (database users or custom callbacks)
|
|
50
|
+
- **File security** with path validation and dangerous key filtering
|
|
18
51
|
|
|
19
52
|
## Installation
|
|
20
53
|
|
|
21
|
-
###
|
|
22
|
-
|
|
23
|
-
The easiest way to install Reldens CMS is using NPX:
|
|
24
|
-
|
|
54
|
+
### Method 1: Automated Web Installer
|
|
25
55
|
```bash
|
|
26
|
-
npx
|
|
56
|
+
npx reldens-cms
|
|
27
57
|
```
|
|
58
|
+
Navigate to `http://localhost:8080` and follow the installation wizard.
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
60
|
+
### Method 2: Manual Setup
|
|
61
|
+
```javascript
|
|
62
|
+
const { Manager } = require('@reldens/cms');
|
|
32
63
|
|
|
33
|
-
|
|
64
|
+
const cms = new Manager({
|
|
65
|
+
projectRoot: process.cwd(),
|
|
66
|
+
entityAccess: {
|
|
67
|
+
cmsPages: { public: true, operations: ['read'] },
|
|
68
|
+
products: { public: true, operations: ['read'] },
|
|
69
|
+
users: { public: false }
|
|
70
|
+
}
|
|
71
|
+
});
|
|
34
72
|
|
|
35
|
-
|
|
36
|
-
npm install @reldens/cms
|
|
73
|
+
cms.start();
|
|
37
74
|
```
|
|
38
75
|
|
|
39
|
-
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
### Environment Variables
|
|
79
|
+
```env
|
|
80
|
+
RELDENS_APP_HOST=http://localhost
|
|
81
|
+
RELDENS_APP_PORT=8080
|
|
82
|
+
RELDENS_ADMIN_ROUTE_PATH=/admin
|
|
83
|
+
RELDENS_ADMIN_SECRET=your-secret-key
|
|
84
|
+
|
|
85
|
+
RELDENS_DB_CLIENT=mysql
|
|
86
|
+
RELDENS_DB_HOST=localhost
|
|
87
|
+
RELDENS_DB_PORT=3306
|
|
88
|
+
RELDENS_DB_NAME=cms_db
|
|
89
|
+
RELDENS_DB_USER=username
|
|
90
|
+
RELDENS_DB_PASSWORD=password
|
|
91
|
+
RELDENS_STORAGE_DRIVER=prisma
|
|
92
|
+
|
|
93
|
+
RELDENS_DEFAULT_DOMAIN=example.com
|
|
94
|
+
RELDENS_DOMAIN_MAPPING={"dev.example.com":"development"}
|
|
95
|
+
RELDENS_SITE_KEY_MAPPING={"example.com":"main"}
|
|
96
|
+
```
|
|
40
97
|
|
|
98
|
+
### Custom Entity Configuration
|
|
41
99
|
```javascript
|
|
42
|
-
const
|
|
100
|
+
const entityConfig = {
|
|
101
|
+
articles: {
|
|
102
|
+
listProperties: ['title', 'status', 'created_at'],
|
|
103
|
+
showProperties: ['title', 'content', 'author', 'status'],
|
|
104
|
+
editProperties: ['title', 'content', 'author_id', 'status'],
|
|
105
|
+
filterProperties: ['status', 'author_id'],
|
|
106
|
+
titleProperty: 'title',
|
|
107
|
+
parentItemLabel: 'Content',
|
|
108
|
+
properties: {
|
|
109
|
+
title: { type: 'string', isRequired: true },
|
|
110
|
+
content: { type: 'text' },
|
|
111
|
+
author_id: { type: 'reference', reference: 'users' },
|
|
112
|
+
featured_image: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
isUpload: true,
|
|
115
|
+
allowedTypes: 'image',
|
|
116
|
+
bucket: 'uploads'
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
43
121
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
authenticationMethod: 'db-users' // or 'callback'
|
|
122
|
+
const cms = new Manager({
|
|
123
|
+
entitiesConfig: entityConfig
|
|
47
124
|
});
|
|
48
|
-
|
|
49
|
-
manager.start().catch(console.error);
|
|
50
125
|
```
|
|
51
126
|
|
|
52
|
-
##
|
|
127
|
+
## Enhanced Templating System
|
|
53
128
|
|
|
54
|
-
|
|
129
|
+
### Template Functions
|
|
130
|
+
Templates now support dynamic content blocks and entity rendering:
|
|
131
|
+
```html
|
|
132
|
+
<!-- Render content blocks -->
|
|
133
|
+
{{ entity('cms_blocks', 'header-main') }}
|
|
134
|
+
{{ entity('cms_blocks', 'sidebar-left') }}
|
|
55
135
|
|
|
56
|
-
|
|
57
|
-
|
|
136
|
+
<!-- Render other entities -->
|
|
137
|
+
{{ entity('products', '123') }}
|
|
138
|
+
{{ entity('cms_pages', '1') }}
|
|
139
|
+
```
|
|
58
140
|
|
|
59
|
-
|
|
141
|
+
### Layout System
|
|
142
|
+
The CMS uses a two-tier layout system:
|
|
143
|
+
|
|
144
|
+
**page.html** - Full HTML wrapper:
|
|
145
|
+
```html
|
|
146
|
+
<!DOCTYPE html>
|
|
147
|
+
<html lang="{{locale}}">
|
|
148
|
+
<head>
|
|
149
|
+
<title>{{title}}</title>
|
|
150
|
+
<meta name="description" content="{{description}}"/>
|
|
151
|
+
<link href="/css/styles.css" rel="stylesheet"/>
|
|
152
|
+
</head>
|
|
153
|
+
<body class="{{siteHandle}}">
|
|
154
|
+
{{{content}}}
|
|
155
|
+
<script src="/js/scripts.js"></script>
|
|
156
|
+
</body>
|
|
157
|
+
</html>
|
|
158
|
+
```
|
|
60
159
|
|
|
160
|
+
**layouts/default.html** - Body content only:
|
|
161
|
+
```html
|
|
162
|
+
{{ entity('cms_blocks', 'header-main') }}
|
|
163
|
+
|
|
164
|
+
<main id="main" class="main-container">
|
|
165
|
+
<div class="container">
|
|
166
|
+
<div class="row">
|
|
167
|
+
<div class="col-md-3">
|
|
168
|
+
{{ entity('cms_blocks', 'sidebar-left') }}
|
|
169
|
+
</div>
|
|
170
|
+
<div class="col-md-9">
|
|
171
|
+
{{{content}}}
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
</main>
|
|
176
|
+
|
|
177
|
+
{{ entity('cms_blocks', 'footer-main') }}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Pages can use different layouts by setting the `layout` field in `cms_pages`:
|
|
181
|
+
- `default` - Header, sidebar, main content, footer
|
|
182
|
+
- `full-width` - Full width without sidebars
|
|
183
|
+
- `minimal` - Basic layout with minimal styling
|
|
184
|
+
|
|
185
|
+
### Content Blocks
|
|
186
|
+
Create reusable content blocks in the `cms_blocks` table via admin panel:
|
|
187
|
+
```sql
|
|
188
|
+
INSERT INTO cms_blocks (name, title, content) VALUES
|
|
189
|
+
('contact-info', 'Contact Information', '<p>Email: info@example.com</p>'),
|
|
190
|
+
('product-sidebar', 'Product Categories',
|
|
191
|
+
'<div class="categories"><h3>Categories</h3><ul><li><a href="/products/electronics">Electronics</a></li></ul></div>');
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Entity Access Control
|
|
195
|
+
Control which entities are publicly accessible:
|
|
61
196
|
```javascript
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
197
|
+
const cms = new Manager({
|
|
198
|
+
entityAccess: {
|
|
199
|
+
products: { public: true, operations: ['read'] },
|
|
200
|
+
cmsPages: { public: true, operations: ['read'] },
|
|
201
|
+
users: { public: false }
|
|
67
202
|
}
|
|
68
203
|
});
|
|
69
204
|
```
|
|
70
205
|
|
|
71
|
-
##
|
|
206
|
+
## Multi-Domain Setup
|
|
207
|
+
|
|
208
|
+
### Directory Structure
|
|
209
|
+
```
|
|
210
|
+
templates/
|
|
211
|
+
├── layouts/
|
|
212
|
+
│ ├── default.html # Body content layouts
|
|
213
|
+
│ ├── full-width.html
|
|
214
|
+
│ └── minimal.html
|
|
215
|
+
├── domains/
|
|
216
|
+
│ ├── example.com/
|
|
217
|
+
│ │ ├── layouts/ # Domain-specific layouts
|
|
218
|
+
│ │ ├── partials/
|
|
219
|
+
│ │ │ ├── header.html
|
|
220
|
+
│ │ │ └── footer.html
|
|
221
|
+
│ │ ├── page.html # Domain-specific page wrapper
|
|
222
|
+
│ │ └── index.html
|
|
223
|
+
│ └── dev.example.com/
|
|
224
|
+
│ └── page.html
|
|
225
|
+
├── partials/
|
|
226
|
+
│ ├── header.html (default)
|
|
227
|
+
│ └── footer.html (default)
|
|
228
|
+
├── page.html (base HTML wrapper)
|
|
229
|
+
└── 404.html
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Advanced Usage
|
|
72
233
|
|
|
73
|
-
|
|
234
|
+
### Custom Authentication
|
|
235
|
+
```javascript
|
|
236
|
+
const customAuth = async (email, password, roleId) => {
|
|
237
|
+
const user = await yourAuthService.authenticate(email, password);
|
|
238
|
+
return user && user.role_id === roleId ? user : false;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const cms = new Manager({
|
|
242
|
+
authenticationMethod: 'custom',
|
|
243
|
+
authenticationCallback: customAuth
|
|
244
|
+
});
|
|
245
|
+
```
|
|
74
246
|
|
|
247
|
+
### File Upload Configuration
|
|
75
248
|
```javascript
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
properties: {
|
|
85
|
-
id: { isId: true },
|
|
86
|
-
name: { isRequired: true },
|
|
87
|
-
price: { type: 'number', isRequired: true },
|
|
88
|
-
description: { type: 'textarea' },
|
|
89
|
-
status: { type: 'boolean' },
|
|
90
|
-
created_at: { type: 'datetime' }
|
|
91
|
-
},
|
|
92
|
-
titleProperty: 'name',
|
|
93
|
-
navigationPosition: 100
|
|
94
|
-
}
|
|
95
|
-
}
|
|
249
|
+
const uploadConfig = {
|
|
250
|
+
mimeTypes: {
|
|
251
|
+
image: ['image/jpeg', 'image/png', 'image/webp'],
|
|
252
|
+
document: ['application/pdf', 'text/plain']
|
|
253
|
+
},
|
|
254
|
+
allowedExtensions: {
|
|
255
|
+
image: ['.jpg', '.jpeg', '.png', '.webp'],
|
|
256
|
+
document: ['.pdf', '.txt']
|
|
96
257
|
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const cms = new Manager(uploadConfig);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Event Hooks
|
|
264
|
+
```javascript
|
|
265
|
+
cms.events.on('reldens.setupAdminRoutes', ({adminManager}) => {
|
|
266
|
+
// Add custom admin routes
|
|
267
|
+
adminManager.adminRouter.get('/custom', (req, res) => {
|
|
268
|
+
res.send('Custom admin page');
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
|
|
273
|
+
// Add extra data to admin views
|
|
274
|
+
entitySerializedData.customField = 'Custom Value';
|
|
97
275
|
});
|
|
98
276
|
```
|
|
99
277
|
|
|
100
|
-
##
|
|
278
|
+
## Default database Schema
|
|
279
|
+
|
|
280
|
+
### Core Tables
|
|
281
|
+
- `routes` - URL routing and SEO metadata
|
|
282
|
+
- `cms_pages` - Page content with layout assignments
|
|
283
|
+
- `cms_blocks` - Reusable content blocks
|
|
284
|
+
- `cms_entity_access` - Entity access control rules
|
|
285
|
+
- `entities_meta` - Generic metadata storage
|
|
286
|
+
- `cms_pages_meta` - Page-specific metadata
|
|
287
|
+
|
|
288
|
+
### Installation Options
|
|
289
|
+
The installer provides checkboxes for:
|
|
290
|
+
- CMS core tables
|
|
291
|
+
- User authentication system
|
|
292
|
+
- Default admin user
|
|
293
|
+
- Default homepage
|
|
294
|
+
- Default content blocks
|
|
295
|
+
- Entity access control rules
|
|
296
|
+
|
|
297
|
+
## API Reference
|
|
298
|
+
|
|
299
|
+
### Manager Class
|
|
300
|
+
- `start()` - Initialize and start the CMS
|
|
301
|
+
- `isInstalled()` - Check if CMS is installed
|
|
302
|
+
- `initializeServices()` - Initialize all services
|
|
303
|
+
|
|
304
|
+
### Frontend Class
|
|
305
|
+
- `initialize()` - Setup frontend routes and templates
|
|
306
|
+
- `handleRequest(req, res)` - Main request handler
|
|
307
|
+
- `findRouteByPath(path)` - Database route lookup
|
|
308
|
+
- `findEntityByPath(path)` - Entity-based URL handling
|
|
309
|
+
- `processCustomTemplateFunctions(template)` - Process {{ entity() }} functions
|
|
310
|
+
|
|
311
|
+
### AdminManager Class
|
|
312
|
+
- `setupAdmin()` - Initialize admin panel
|
|
313
|
+
- `generateListRouteContent()` - Entity list pages
|
|
314
|
+
- `generateEditRouteContent()` - Entity edit forms
|
|
315
|
+
- `processSaveEntity()` - Handle form submissions
|
|
316
|
+
|
|
317
|
+
### Installer Class
|
|
318
|
+
- `prepareSetup()` - Setup installation routes
|
|
319
|
+
- `executeInstallProcess()` - Run installation
|
|
320
|
+
- `generateEntities()` - Create entity files
|
|
321
|
+
|
|
322
|
+
## File Structure
|
|
101
323
|
|
|
102
|
-
|
|
324
|
+
```
|
|
325
|
+
project/
|
|
326
|
+
├── admin/
|
|
327
|
+
│ └── templates/ # Admin panel templates
|
|
328
|
+
├── templates/
|
|
329
|
+
│ ├── layouts/ # Body content layouts
|
|
330
|
+
│ ├── domains/ # Domain-specific templates
|
|
331
|
+
│ ├── partials/ # Shared template partials
|
|
332
|
+
│ ├── page.html # Base HTML wrapper
|
|
333
|
+
│ └── 404.html # Error page
|
|
334
|
+
├── public/
|
|
335
|
+
│ ├── css/ # Stylesheets
|
|
336
|
+
│ ├── js/ # Client scripts
|
|
337
|
+
│ └── assets/ # Static assets
|
|
338
|
+
├── entities/ # Generated entity classes
|
|
339
|
+
├── .env # Environment configuration
|
|
340
|
+
├── install.lock # Installation lock file
|
|
341
|
+
└── index.js # Main application file
|
|
342
|
+
```
|
|
103
343
|
|
|
104
|
-
|
|
105
|
-
- `layout.html`: The main layout template
|
|
106
|
-
- `page.html`: Default page template
|
|
107
|
-
- `404.html`: Not found page
|
|
344
|
+
---
|
|
108
345
|
|
|
109
|
-
|
|
346
|
+
## Contributing
|
|
347
|
+
|
|
348
|
+
1. Fork the repository
|
|
349
|
+
2. Create a feature branch
|
|
350
|
+
3. Follow the coding standards in the JavaScript rules
|
|
351
|
+
4. Submit a pull request
|
|
110
352
|
|
|
111
353
|
---
|
|
112
354
|
|
|
@@ -120,6 +362,12 @@ Need something specific?
|
|
|
120
362
|
|
|
121
363
|
---
|
|
122
364
|
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
MIT License - see LICENSE file for details.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
123
371
|
### [Reldens](https://github.com/damian-pastorini/reldens/ "Reldens")
|
|
124
372
|
|
|
125
373
|
##### [By DwDeveloper](https://www.dwdeveloper.com/ "DwDeveloper")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<input type="
|
|
1
|
+
<input type="{{&inputType}}" name="{{&fieldName}}" id="{{&fieldName}}" value="{{fieldValue}}"{{&required}}{{&fieldDisabled}}/>
|
package/install/index.html
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
<div class="input-box app-admin-secret">
|
|
34
34
|
<label for="app-admin-secret">Admin Panel Secret</label>
|
|
35
|
-
<input type="text" name="app-admin-secret" id="app-admin-secret" value="{{app-admin-secret}}"/>
|
|
35
|
+
<input type="text" name="app-admin-secret" id="app-admin-secret" value="{{app-admin-secret}}" required/>
|
|
36
36
|
</div>
|
|
37
37
|
<div class="input-box app-error">
|
|
38
38
|
<p class="error installation-process-failed">There was an error during the installation process.</p>
|
|
@@ -77,25 +77,33 @@
|
|
|
77
77
|
<p class="error db-installation-process-failed">There was an error during the installation process.</p>
|
|
78
78
|
</div>
|
|
79
79
|
<h3 class="form-title">Installation Options</h3>
|
|
80
|
-
<div class="input-box install-cms-tables">
|
|
80
|
+
<div class="input-box input-checkbox install-cms-tables">
|
|
81
81
|
<input type="checkbox" name="install-cms-tables" id="install-cms-tables" checked/>
|
|
82
82
|
<label for="install-cms-tables">Install default CMS pages tables</label>
|
|
83
83
|
</div>
|
|
84
|
-
<div class="input-box install-user-auth">
|
|
84
|
+
<div class="input-box input-checkbox install-user-auth">
|
|
85
85
|
<input type="checkbox" name="install-user-auth" id="install-user-auth" checked/>
|
|
86
86
|
<label for="install-user-auth">Install users authentication</label>
|
|
87
87
|
</div>
|
|
88
|
-
<div class="input-box install-default-user">
|
|
88
|
+
<div class="input-box input-checkbox install-default-user">
|
|
89
89
|
<input type="checkbox" name="install-default-user" id="install-default-user" checked/>
|
|
90
90
|
<label for="install-default-user">Install default admin user (root@cms-admin.com / root)</label>
|
|
91
91
|
</div>
|
|
92
|
-
<div class="input-box install-default-homepage">
|
|
92
|
+
<div class="input-box input-checkbox install-default-homepage">
|
|
93
93
|
<input type="checkbox" name="install-default-homepage" id="install-default-homepage" checked/>
|
|
94
94
|
<label for="install-default-homepage">Install default homepage</label>
|
|
95
95
|
</div>
|
|
96
|
+
<div class="input-box input-checkbox install-default-blocks">
|
|
97
|
+
<input type="checkbox" name="install-default-blocks" id="install-default-blocks" checked/>
|
|
98
|
+
<label for="install-default-blocks">Install default content blocks</label>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="input-box input-checkbox install-entity-access">
|
|
101
|
+
<input type="checkbox" name="install-entity-access" id="install-entity-access" checked/>
|
|
102
|
+
<label for="install-entity-access">Install entity access control rules</label>
|
|
103
|
+
</div>
|
|
96
104
|
<div class="input-box submit-container">
|
|
97
|
-
<img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
|
|
98
105
|
<input id="install-submit-button" type="submit" value="Install"/>
|
|
106
|
+
<img class="install-loading hidden" src="/install-assets/img/loading.gif"/>
|
|
99
107
|
</div>
|
|
100
108
|
<div class="input-box response-error"></div>
|
|
101
109
|
</form>
|
package/lib/admin-manager.js
CHANGED
|
@@ -662,10 +662,10 @@ class AdminManager
|
|
|
662
662
|
async generateEditRouteContent(req, driverResource, entityPath)
|
|
663
663
|
{
|
|
664
664
|
let idProperty = this.fetchEntityIdPropertyKey(driverResource);
|
|
665
|
-
let idValue = (req?.query
|
|
666
|
-
let templateTitle = (
|
|
667
|
-
let loadedEntity =
|
|
668
|
-
let entityViewRoute =
|
|
665
|
+
let idValue = String(sc.get(req?.query, idProperty, ''));
|
|
666
|
+
let templateTitle = (!idValue ? 'Create' : 'Edit')+' '+this.translations.labels[driverResource.id()];
|
|
667
|
+
let loadedEntity = !idValue ? null :await this.loadEntityById(driverResource, idValue);
|
|
668
|
+
let entityViewRoute = !idValue
|
|
669
669
|
? this.rootPath+'/'+driverResource.entityPath
|
|
670
670
|
: this.generateEntityRoute('viewPath', driverResource, idProperty, loadedEntity);
|
|
671
671
|
let renderedEditProperties = {
|
|
@@ -677,32 +677,29 @@ class AdminManager
|
|
|
677
677
|
};
|
|
678
678
|
let propertiesKeys = Object.keys(driverResource.options.properties);
|
|
679
679
|
for(let propertyKey of propertiesKeys){
|
|
680
|
-
let
|
|
680
|
+
let property = driverResource.options.properties[propertyKey];
|
|
681
681
|
let fieldDisabled = -1 === driverResource.options.editProperties.indexOf(propertyKey);
|
|
682
|
-
let isRequired = resourceProperty.isRequired ? ' required="required"' : '';
|
|
683
|
-
if(resourceProperty.isUpload && loadedEntity){
|
|
684
|
-
isRequired = '';
|
|
685
|
-
}
|
|
686
|
-
let inputType = this.getInputType(resourceProperty, fieldDisabled);
|
|
687
682
|
renderedEditProperties[propertyKey] = await this.render(
|
|
688
|
-
this.adminFilesContents.fields.edit[this.propertyType(
|
|
683
|
+
this.adminFilesContents.fields.edit[this.propertyType(property, 'edit')],
|
|
689
684
|
{
|
|
690
685
|
fieldName: propertyKey,
|
|
691
686
|
fieldValue: await this.generatePropertyEditRenderedValue(
|
|
692
687
|
loadedEntity,
|
|
693
688
|
propertyKey,
|
|
694
|
-
|
|
689
|
+
property,
|
|
695
690
|
fieldDisabled
|
|
696
691
|
),
|
|
697
692
|
fieldDisabled: fieldDisabled ? ' disabled="disabled"' : '',
|
|
698
|
-
required: isRequired,
|
|
699
|
-
multiple:
|
|
700
|
-
inputType
|
|
693
|
+
required: (!property.isUpload || !loadedEntity) && property.isRequired ? ' required="required"' : '',
|
|
694
|
+
multiple: property.isArray ? ' multiple="multiple"' : '',
|
|
695
|
+
inputType: this.getInputType(property, fieldDisabled)
|
|
701
696
|
}
|
|
702
697
|
);
|
|
703
698
|
}
|
|
704
|
-
|
|
705
|
-
|
|
699
|
+
return await this.renderRoute(
|
|
700
|
+
await this.render(this.adminContents.entities[entityPath].edit, renderedEditProperties),
|
|
701
|
+
this.adminContents.sideBar
|
|
702
|
+
);
|
|
706
703
|
}
|
|
707
704
|
|
|
708
705
|
getInputType(resourceProperty, fieldDisabled)
|
|
@@ -710,12 +707,18 @@ class AdminManager
|
|
|
710
707
|
if('datetime' === resourceProperty.type && !fieldDisabled){
|
|
711
708
|
return 'datetime-local';
|
|
712
709
|
}
|
|
710
|
+
if('number' === resourceProperty.type){
|
|
711
|
+
return 'number';
|
|
712
|
+
}
|
|
713
713
|
return 'text';
|
|
714
714
|
}
|
|
715
715
|
|
|
716
716
|
async loadEntityById(driverResource, id)
|
|
717
717
|
{
|
|
718
718
|
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
719
|
+
if(!entityRepository){
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
719
722
|
return await entityRepository.loadByIdWithRelations(id);
|
|
720
723
|
}
|
|
721
724
|
|
|
@@ -735,24 +738,23 @@ class AdminManager
|
|
|
735
738
|
};
|
|
736
739
|
let entitySerializedData = {};
|
|
737
740
|
for(let propertyKey of driverResource.options.showProperties){
|
|
738
|
-
let
|
|
741
|
+
let property = driverResource.options.properties[propertyKey];
|
|
739
742
|
let {fieldValue, fieldName} = this.generatePropertyRenderedValueWithLabel(
|
|
740
743
|
loadedEntity,
|
|
741
744
|
propertyKey,
|
|
742
|
-
|
|
745
|
+
property
|
|
743
746
|
);
|
|
744
747
|
entitySerializedData[fieldName] = fieldValue;
|
|
745
|
-
let renderedFieldValue = await this.generatePropertyRenderedValue(
|
|
746
|
-
fieldValue,
|
|
747
|
-
fieldName,
|
|
748
|
-
resourceProperty,
|
|
749
|
-
'view'
|
|
750
|
-
);
|
|
751
748
|
renderedViewProperties[propertyKey] = await this.render(
|
|
752
|
-
this.adminFilesContents.fields.view[this.propertyType(
|
|
749
|
+
this.adminFilesContents.fields.view[this.propertyType(property)],
|
|
753
750
|
{
|
|
754
751
|
fieldName: propertyKey,
|
|
755
|
-
fieldValue:
|
|
752
|
+
fieldValue: await this.generatePropertyRenderedValue(
|
|
753
|
+
fieldValue,
|
|
754
|
+
fieldName,
|
|
755
|
+
property,
|
|
756
|
+
'view'
|
|
757
|
+
),
|
|
756
758
|
fieldOriginalValue: fieldValue,
|
|
757
759
|
target: ' target="_blank"'
|
|
758
760
|
}
|
|
@@ -762,13 +764,15 @@ class AdminManager
|
|
|
762
764
|
await this.events.emit('adminEntityExtraData', extraDataEvent);
|
|
763
765
|
entitySerializedData = extraDataEvent.entitySerializedData;
|
|
764
766
|
renderedViewProperties.entitySerializedData = JSON.stringify(entitySerializedData).replace(/"/g, '"');
|
|
765
|
-
|
|
766
|
-
|
|
767
|
+
return await this.renderRoute(
|
|
768
|
+
await this.render(this.adminContents.entities[entityPath].view, renderedViewProperties),
|
|
769
|
+
this.adminContents.sideBar
|
|
770
|
+
);
|
|
767
771
|
}
|
|
768
772
|
|
|
769
773
|
propertyType(resourceProperty, templateType)
|
|
770
774
|
{
|
|
771
|
-
let propertyType = resourceProperty
|
|
775
|
+
let propertyType = sc.get(resourceProperty, 'type', 'text');
|
|
772
776
|
if('reference' === propertyType && 'edit' === templateType){
|
|
773
777
|
return 'select';
|
|
774
778
|
}
|
|
@@ -805,7 +809,8 @@ class AdminManager
|
|
|
805
809
|
});
|
|
806
810
|
let entitiesRows = await this.loadEntitiesForList(driverResource, pageSize, page, req, filters);
|
|
807
811
|
let listRawContent = this.adminContents.entities[entityPath].list.toString();
|
|
808
|
-
let
|
|
812
|
+
let totalEntities = await this.countTotalEntities(driverResource, filters);
|
|
813
|
+
let totalPages = totalEntities <= pageSize ? 1 : Math.ceil(totalEntities / pageSize);
|
|
809
814
|
let pages = PageRangeProvider.fetch(page, totalPages);
|
|
810
815
|
let renderedPagination = '';
|
|
811
816
|
for(let page of pages){
|
|
@@ -858,6 +863,9 @@ class AdminManager
|
|
|
858
863
|
{
|
|
859
864
|
/** @type {BaseDriver|ObjectionJsDriver} entityRepository **/
|
|
860
865
|
let entityRepository = this.dataServer.getEntity(driverResource.entityKey);
|
|
866
|
+
if(!entityRepository){
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
861
869
|
return await entityRepository.count(filters);
|
|
862
870
|
}
|
|
863
871
|
|