webspresso 0.0.89 → 0.0.91
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 +450 -53
- package/bin/commands/service.js +193 -0
- package/bin/webspresso.js +2 -0
- package/core/auth/manager.js +36 -26
- package/core/auth/middleware.js +21 -2
- package/core/compression/compressible.js +51 -0
- package/core/compression/compression-stream.js +59 -0
- package/core/compression/index.js +25 -0
- package/core/compression/middleware.js +301 -0
- package/core/compression/negotiate.js +153 -0
- package/core/errors/base.js +31 -0
- package/core/errors/domain.js +193 -0
- package/core/errors/http.js +133 -0
- package/core/errors/index.js +77 -0
- package/core/errors/middleware.js +162 -0
- package/core/errors/normalize.js +171 -0
- package/core/orm/complexity.js +120 -0
- package/core/orm/events.js +64 -14
- package/core/orm/index.js +45 -10
- package/core/orm/model.js +10 -2
- package/core/orm/query-builder.js +30 -4
- package/core/orm/repository.js +57 -25
- package/core/orm/scopes.js +2 -1
- package/core/orm/transaction.js +39 -5
- package/core/orm/utils.js +6 -3
- package/core/queue/adapters/database.js +284 -0
- package/core/queue/adapters/memory.js +155 -0
- package/core/queue/adapters/redis.js +336 -0
- package/core/queue/index.js +21 -0
- package/core/queue/job.js +103 -0
- package/core/queue/manager.js +387 -0
- package/core/realtime/adapters/redis.js +431 -0
- package/core/realtime/adapters/socket-io.js +203 -0
- package/core/realtime/adapters/sse.js +210 -0
- package/core/realtime/adapters/websocket.js +337 -0
- package/core/realtime/auth-manager.js +140 -0
- package/core/realtime/core.js +359 -0
- package/core/realtime/emitter.js +98 -0
- package/core/realtime/errors.js +71 -0
- package/core/realtime/index.js +79 -0
- package/core/realtime/reconnect-manager.js +118 -0
- package/core/realtime/registry.js +243 -0
- package/core/shutdown/http-adapter.js +134 -0
- package/core/shutdown/index.js +12 -0
- package/core/shutdown/shutdown-manager.js +312 -0
- package/core/ssr/index.js +13 -0
- package/core/ssr/stream.js +174 -0
- package/index.d.ts +829 -52
- package/index.js +103 -0
- package/package.json +22 -2
- package/plugins/admin-panel/api.js +37 -4
- package/plugins/admin-panel/app.js +67 -17
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
- package/plugins/admin-panel/client/parts/03-pagination-intro.js +25 -18
- package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
- package/plugins/admin-panel/client/parts/08-record-list.js +27 -34
- package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
- package/plugins/admin-panel/core/admin-module.js +2 -2
- package/plugins/admin-panel/core/api-extensions.js +149 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
- package/plugins/admin-panel/index.js +131 -18
- package/plugins/admin-panel/modules/admin-users.js +770 -0
- package/plugins/admin-panel/modules/custom-pages.js +144 -45
- package/plugins/admin-panel/modules/menu.js +85 -0
- package/plugins/admin-panel/modules/user-management.js +5 -2
- package/plugins/admin-panel/vendor/mithril.min.js +1 -0
- package/plugins/admin-panel/vendor/quill.min.js +7 -0
- package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
- package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
- package/plugins/basic-auth/index.js +293 -0
- package/plugins/content/client/inline-edit.js +41 -17
- package/plugins/cors.js +12 -8
- package/plugins/csrf/index.js +8 -8
- package/plugins/data-exchange/export-xlsx.js +5 -1
- package/plugins/data-exchange/import.js +16 -2
- package/plugins/data-exchange/index.js +14 -0
- package/plugins/email/index.js +14 -1
- package/plugins/index.js +29 -0
- package/plugins/queue/index.js +108 -0
- package/plugins/realtime/index.js +150 -0
- package/plugins/recaptcha/index.js +11 -0
- package/plugins/redirect/index.js +9 -1
- package/plugins/rest-resources/index.js +280 -11
- package/plugins/upload/index.js +16 -0
- package/src/app-context.js +46 -6
- package/src/file-router.js +220 -28
- package/src/helpers.js +24 -5
- package/src/plugin-manager.js +134 -7
- package/src/server.js +470 -44
- package/src/services/builtins/auth.js +273 -0
- package/src/services/builtins/exchange.js +229 -0
- package/src/services/builtins/mail.js +134 -0
- package/src/services/builtins/media.js +196 -0
- package/src/services/builtins/system.js +221 -0
- package/src/services/discovery.js +140 -0
- package/src/services/executor.js +292 -0
- package/src/services/index.js +70 -0
- package/src/services/memoize.js +256 -0
- package/src/services/registry.js +288 -0
- package/src/services/validator.js +281 -0
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +2 -2
package/index.js
CHANGED
|
@@ -10,9 +10,21 @@ const {
|
|
|
10
10
|
getAppContext,
|
|
11
11
|
getDb,
|
|
12
12
|
hasDb,
|
|
13
|
+
getShutdownManager,
|
|
14
|
+
hasShutdownManager,
|
|
15
|
+
getServiceRegistry,
|
|
16
|
+
hasServiceRegistry,
|
|
13
17
|
resetAppContext,
|
|
14
18
|
setAppContext,
|
|
15
19
|
} = require('./src/app-context');
|
|
20
|
+
const {
|
|
21
|
+
createServiceRegistry,
|
|
22
|
+
defineService,
|
|
23
|
+
ServiceRegistry,
|
|
24
|
+
} = require('./src/services');
|
|
25
|
+
const { ShutdownManager, NodeHttpAdapter } = require('./core/shutdown');
|
|
26
|
+
const compression = require('./core/compression');
|
|
27
|
+
const errors = require('./core/errors');
|
|
16
28
|
const {
|
|
17
29
|
mountPages,
|
|
18
30
|
filePathToRoute,
|
|
@@ -64,6 +76,17 @@ const {
|
|
|
64
76
|
rateLimitPlugin,
|
|
65
77
|
contentPlugin,
|
|
66
78
|
csrfPlugin,
|
|
79
|
+
corsPlugin,
|
|
80
|
+
emailPlugin,
|
|
81
|
+
basicAuthPlugin,
|
|
82
|
+
realtimePlugin,
|
|
83
|
+
realtime,
|
|
84
|
+
createRealtime,
|
|
85
|
+
websocket,
|
|
86
|
+
sse,
|
|
87
|
+
socketIo,
|
|
88
|
+
redis,
|
|
89
|
+
createRedisAdapter,
|
|
67
90
|
} = require('./plugins');
|
|
68
91
|
|
|
69
92
|
const content = require('./core/content');
|
|
@@ -78,8 +101,67 @@ module.exports = {
|
|
|
78
101
|
getAppContext,
|
|
79
102
|
getDb,
|
|
80
103
|
hasDb,
|
|
104
|
+
getShutdownManager,
|
|
105
|
+
hasShutdownManager,
|
|
106
|
+
getServiceRegistry,
|
|
107
|
+
hasServiceRegistry,
|
|
81
108
|
resetAppContext,
|
|
82
109
|
setAppContext,
|
|
110
|
+
|
|
111
|
+
// Services Layer
|
|
112
|
+
createServiceRegistry,
|
|
113
|
+
defineService,
|
|
114
|
+
service: defineService,
|
|
115
|
+
ServiceRegistry,
|
|
116
|
+
memoize: require('./src/services').memoize,
|
|
117
|
+
parseTtlMs: require('./src/services').parseTtlMs,
|
|
118
|
+
stableCacheKey: require('./src/services').stableCacheKey,
|
|
119
|
+
sanitizeInput: require('./src/services').sanitizeInput,
|
|
120
|
+
maskSensitiveData: require('./src/services').maskSensitiveData,
|
|
121
|
+
discoverServices: require('./src/services').discoverServices,
|
|
122
|
+
filePathToServiceName: require('./src/services').filePathToServiceName,
|
|
123
|
+
toCamelCase: require('./src/services').toCamelCase,
|
|
124
|
+
createAuthServices: require('./src/services').createAuthServices,
|
|
125
|
+
createMailServices: require('./src/services').createMailServices,
|
|
126
|
+
createMediaServices: require('./src/services').createMediaServices,
|
|
127
|
+
createSystemServices: require('./src/services').createSystemServices,
|
|
128
|
+
createExchangeServices: require('./src/services').createExchangeServices,
|
|
129
|
+
validateServiceInput: require('./src/services').validateServiceInput,
|
|
130
|
+
executeService: require('./src/services').executeService,
|
|
131
|
+
|
|
132
|
+
// Shutdown & Lifecycle
|
|
133
|
+
ShutdownManager,
|
|
134
|
+
NodeHttpAdapter,
|
|
135
|
+
|
|
136
|
+
// HTTP Response Compression
|
|
137
|
+
compression,
|
|
138
|
+
createCompressionMiddleware: compression.createCompressionMiddleware,
|
|
139
|
+
|
|
140
|
+
// Exceptions & Error Handling
|
|
141
|
+
errors,
|
|
142
|
+
WebspressoError: errors.WebspressoError,
|
|
143
|
+
HttpError: errors.HttpError,
|
|
144
|
+
BadRequestError: errors.BadRequestError,
|
|
145
|
+
UnauthorizedError: errors.UnauthorizedError,
|
|
146
|
+
ForbiddenError: errors.ForbiddenError,
|
|
147
|
+
NotFoundError: errors.NotFoundError,
|
|
148
|
+
MethodNotAllowedError: errors.MethodNotAllowedError,
|
|
149
|
+
ConflictError: errors.ConflictError,
|
|
150
|
+
PayloadTooLargeError: errors.PayloadTooLargeError,
|
|
151
|
+
UnsupportedMediaTypeError: errors.UnsupportedMediaTypeError,
|
|
152
|
+
UnprocessableEntityError: errors.UnprocessableEntityError,
|
|
153
|
+
TooManyRequestsError: errors.TooManyRequestsError,
|
|
154
|
+
ValidationError: errors.ValidationError,
|
|
155
|
+
ConfigurationError: errors.ConfigurationError,
|
|
156
|
+
PluginError: errors.PluginError,
|
|
157
|
+
SecurityError: errors.SecurityError,
|
|
158
|
+
RequestError: errors.RequestError,
|
|
159
|
+
RequestAbortedError: errors.RequestAbortedError,
|
|
160
|
+
RouterError: errors.RouterError,
|
|
161
|
+
RouteNotFoundError: errors.RouteNotFoundError,
|
|
162
|
+
RouteGenerationError: errors.RouteGenerationError,
|
|
163
|
+
normalizeError: errors.normalizeError,
|
|
164
|
+
toErrorResponseObject: errors.toErrorResponseObject,
|
|
83
165
|
|
|
84
166
|
// Router utilities (for advanced use)
|
|
85
167
|
mountPages,
|
|
@@ -97,6 +179,11 @@ module.exports = {
|
|
|
97
179
|
// Template helpers
|
|
98
180
|
createHelpers,
|
|
99
181
|
utils,
|
|
182
|
+
|
|
183
|
+
// SSR Streaming
|
|
184
|
+
ssr: require('./core/ssr'),
|
|
185
|
+
renderStream: require('./core/ssr').renderStream,
|
|
186
|
+
createHtmlStream: require('./core/ssr').createHtmlStream,
|
|
100
187
|
|
|
101
188
|
// Asset management
|
|
102
189
|
AssetManager,
|
|
@@ -135,7 +222,23 @@ module.exports = {
|
|
|
135
222
|
rateLimitPlugin,
|
|
136
223
|
contentPlugin,
|
|
137
224
|
csrfPlugin,
|
|
225
|
+
corsPlugin,
|
|
226
|
+
emailPlugin,
|
|
227
|
+
basicAuthPlugin,
|
|
228
|
+
realtimePlugin,
|
|
229
|
+
realtime,
|
|
230
|
+
createRealtime,
|
|
231
|
+
websocket,
|
|
232
|
+
sse,
|
|
233
|
+
socketIo,
|
|
234
|
+
redis,
|
|
235
|
+
createRedisAdapter,
|
|
236
|
+
queuePlugin: require('./plugins/queue'),
|
|
237
|
+
queue: require('./core/queue'),
|
|
238
|
+
createQueueManager: require('./core/queue').createQueueManager,
|
|
239
|
+
QueueManager: require('./core/queue').QueueManager,
|
|
138
240
|
|
|
139
241
|
// Schema-driven CMS core (framework-agnostic)
|
|
140
242
|
content,
|
|
141
243
|
};
|
|
244
|
+
|
package/package.json
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webspresso",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.91",
|
|
4
4
|
"description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"default": "./index.js"
|
|
11
|
+
},
|
|
12
|
+
"./errors": {
|
|
13
|
+
"types": "./index.d.ts",
|
|
14
|
+
"default": "./core/errors/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./plugins": "./plugins/index.js",
|
|
17
|
+
"./plugins/*": "./plugins/*",
|
|
18
|
+
"./realtime": "./core/realtime/index.js",
|
|
19
|
+
"./core/*": "./core/*",
|
|
20
|
+
"./src/*": "./src/*",
|
|
21
|
+
"./views/*": "./views/*",
|
|
22
|
+
"./templates/*": "./templates/*",
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
7
25
|
"bin": {
|
|
8
26
|
"webspresso": "./bin/webspresso.js"
|
|
9
27
|
},
|
|
10
28
|
"scripts": {
|
|
11
29
|
"test": "vitest run",
|
|
30
|
+
"test:security": "vitest run tests/security/",
|
|
31
|
+
"security": "npm run test:security && npm audit",
|
|
12
32
|
"test:watch": "vitest",
|
|
13
33
|
"test:coverage": "vitest run --coverage",
|
|
14
34
|
"test:e2e": "playwright test",
|
|
@@ -62,7 +82,7 @@
|
|
|
62
82
|
"csv-parse": "^5.6.0",
|
|
63
83
|
"dayjs": "^1.11.19",
|
|
64
84
|
"exceljs": "^4.4.0",
|
|
65
|
-
"express": "^
|
|
85
|
+
"express": "^5.2.1",
|
|
66
86
|
"express-session": "^1.18.0",
|
|
67
87
|
"helmet": "^7.2.0",
|
|
68
88
|
"inquirer": "^8.2.6",
|
|
@@ -384,6 +384,9 @@ function isColumnSortable(name, meta, model) {
|
|
|
384
384
|
|
|
385
385
|
// Apply filters
|
|
386
386
|
for (const [colName, filter] of Object.entries(filterParams)) {
|
|
387
|
+
if (!model.columns.has(colName) || !filter || typeof filter !== 'object') {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
387
390
|
const colMeta = model.columns.get(colName);
|
|
388
391
|
const colType = colMeta?.type || 'string';
|
|
389
392
|
const op = filter.op || (colType === 'boolean' ? 'eq' : 'contains');
|
|
@@ -568,6 +571,32 @@ function isColumnSortable(name, meta, model) {
|
|
|
568
571
|
}
|
|
569
572
|
}
|
|
570
573
|
|
|
574
|
+
/** Extract structured field errors from Zod, ORM or database validation exceptions */
|
|
575
|
+
function extractValidationErrors(error) {
|
|
576
|
+
if (!error) return null;
|
|
577
|
+
const fields = {};
|
|
578
|
+
if (error.errors && typeof error.errors === 'object') {
|
|
579
|
+
if (Array.isArray(error.errors)) {
|
|
580
|
+
for (const item of error.errors) {
|
|
581
|
+
if (item.path && item.message) {
|
|
582
|
+
const key = Array.isArray(item.path) ? item.path.join('.') : String(item.path);
|
|
583
|
+
fields[key] = item.message;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
Object.assign(fields, error.errors);
|
|
588
|
+
}
|
|
589
|
+
} else if (error.issues && Array.isArray(error.issues)) {
|
|
590
|
+
for (const issue of error.issues) {
|
|
591
|
+
const key = issue.path && issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
592
|
+
fields[key] = issue.message;
|
|
593
|
+
}
|
|
594
|
+
} else if (error.fields && typeof error.fields === 'object') {
|
|
595
|
+
Object.assign(fields, error.fields);
|
|
596
|
+
}
|
|
597
|
+
return Object.keys(fields).length > 0 ? fields : null;
|
|
598
|
+
}
|
|
599
|
+
|
|
571
600
|
/**
|
|
572
601
|
* Create record
|
|
573
602
|
*/
|
|
@@ -588,7 +617,8 @@ function isColumnSortable(name, meta, model) {
|
|
|
588
617
|
const value = req.body[colName];
|
|
589
618
|
if (isRichTextEmpty(value)) {
|
|
590
619
|
return res.status(400).json({
|
|
591
|
-
error: `Field "${colName}" is required
|
|
620
|
+
error: `Field "${colName}" is required`,
|
|
621
|
+
fields: { [colName]: `Field "${colName}" is required` },
|
|
592
622
|
});
|
|
593
623
|
}
|
|
594
624
|
}
|
|
@@ -599,7 +629,8 @@ function isColumnSortable(name, meta, model) {
|
|
|
599
629
|
|
|
600
630
|
res.status(201).json({ data: sanitizeForOutput(record, model) });
|
|
601
631
|
} catch (error) {
|
|
602
|
-
|
|
632
|
+
const fields = extractValidationErrors(error);
|
|
633
|
+
res.status(400).json({ error: error.message, ...(fields ? { fields } : {}) });
|
|
603
634
|
}
|
|
604
635
|
}
|
|
605
636
|
|
|
@@ -623,7 +654,8 @@ function isColumnSortable(name, meta, model) {
|
|
|
623
654
|
const value = req.body[colName];
|
|
624
655
|
if (isRichTextEmpty(value)) {
|
|
625
656
|
return res.status(400).json({
|
|
626
|
-
error: `Field "${colName}" is required
|
|
657
|
+
error: `Field "${colName}" is required`,
|
|
658
|
+
fields: { [colName]: `Field "${colName}" is required` },
|
|
627
659
|
});
|
|
628
660
|
}
|
|
629
661
|
}
|
|
@@ -638,7 +670,8 @@ function isColumnSortable(name, meta, model) {
|
|
|
638
670
|
|
|
639
671
|
res.json({ data: sanitizeForOutput(record, model) });
|
|
640
672
|
} catch (error) {
|
|
641
|
-
|
|
673
|
+
const fields = extractValidationErrors(error);
|
|
674
|
+
res.status(400).json({ error: error.message, ...(fields ? { fields } : {}) });
|
|
642
675
|
}
|
|
643
676
|
}
|
|
644
677
|
|
|
@@ -171,6 +171,16 @@ var routes = {
|
|
|
171
171
|
return ProfilePage;
|
|
172
172
|
}
|
|
173
173
|
},
|
|
174
|
+
'/admins': {
|
|
175
|
+
onmatch: async () => {
|
|
176
|
+
const isAuth = await checkAuth();
|
|
177
|
+
if (!isAuth) {
|
|
178
|
+
redirectToLogin();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
return AdminUsersPage;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
174
184
|
'/users/sessions': {
|
|
175
185
|
onmatch: async () => {
|
|
176
186
|
if (!(await guardUserManagementRoutes())) return;
|
|
@@ -237,36 +247,76 @@ function wrapCustomPageWithLayout(page, rawComp) {
|
|
|
237
247
|
|
|
238
248
|
return {
|
|
239
249
|
oninit: function(vnode) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
250
|
+
try {
|
|
251
|
+
vnode.state.instance = typeof rawComp === 'function' ? rawComp.call(this, vnode) : rawComp;
|
|
252
|
+
if (vnode.state.instance && typeof vnode.state.instance.oninit === 'function') {
|
|
253
|
+
return vnode.state.instance.oninit.call(this, vnode);
|
|
254
|
+
}
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error('[Admin Panel] Error in oninit for custom page "' + page.id + '":', err);
|
|
257
|
+
vnode.state.renderError = err;
|
|
243
258
|
}
|
|
244
259
|
},
|
|
245
260
|
oncreate: function(vnode) {
|
|
246
|
-
|
|
247
|
-
|
|
261
|
+
try {
|
|
262
|
+
const comp = vnode.state.instance || rawComp;
|
|
263
|
+
if (comp && typeof comp.oncreate === 'function') return comp.oncreate.call(this, vnode);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error('[Admin Panel] Error in oncreate for custom page "' + page.id + '":', err);
|
|
266
|
+
}
|
|
248
267
|
},
|
|
249
268
|
onupdate: function(vnode) {
|
|
250
|
-
|
|
251
|
-
|
|
269
|
+
try {
|
|
270
|
+
const comp = vnode.state.instance || rawComp;
|
|
271
|
+
if (comp && typeof comp.onupdate === 'function') return comp.onupdate.call(this, vnode);
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error('[Admin Panel] Error in onupdate for custom page "' + page.id + '":', err);
|
|
274
|
+
}
|
|
252
275
|
},
|
|
253
276
|
onbeforeremove: function(vnode) {
|
|
254
|
-
|
|
255
|
-
|
|
277
|
+
try {
|
|
278
|
+
const comp = vnode.state.instance || rawComp;
|
|
279
|
+
if (comp && typeof comp.onbeforeremove === 'function') return comp.onbeforeremove.call(this, vnode);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error('[Admin Panel] Error in onbeforeremove for custom page "' + page.id + '":', err);
|
|
282
|
+
}
|
|
256
283
|
},
|
|
257
284
|
onremove: function(vnode) {
|
|
258
|
-
|
|
259
|
-
|
|
285
|
+
try {
|
|
286
|
+
const comp = vnode.state.instance || rawComp;
|
|
287
|
+
if (comp && typeof comp.onremove === 'function') return comp.onremove.call(this, vnode);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.error('[Admin Panel] Error in onremove for custom page "' + page.id + '":', err);
|
|
290
|
+
}
|
|
260
291
|
},
|
|
261
292
|
view: function(vnode) {
|
|
293
|
+
if (vnode.state.renderError) {
|
|
294
|
+
var err = vnode.state.renderError;
|
|
295
|
+
var breadcrumbs = [{ label: page.title, href: page.path }];
|
|
296
|
+
return m(Layout, [
|
|
297
|
+
m(Breadcrumb, { items: breadcrumbs }),
|
|
298
|
+
m('.p-6.bg-red-50.border.border-red-200.rounded-xl', [
|
|
299
|
+
m('h3.text-lg.font-semibold.text-red-800', 'Sayfa Yüklenirken Hata Oluştu'),
|
|
300
|
+
m('p.text-sm.text-red-600.mt-1', err.message || String(err)),
|
|
301
|
+
])
|
|
302
|
+
]);
|
|
303
|
+
}
|
|
262
304
|
const comp = vnode.state.instance || rawComp;
|
|
263
305
|
var content;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
306
|
+
try {
|
|
307
|
+
if (comp && typeof comp.view === 'function') {
|
|
308
|
+
content = comp.view.call(this, vnode);
|
|
309
|
+
} else if (typeof comp === 'function') {
|
|
310
|
+
content = comp.call(this, vnode);
|
|
311
|
+
} else {
|
|
312
|
+
content = comp;
|
|
313
|
+
}
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.error('[Admin Panel] Error rendering custom page "' + page.id + '":', err);
|
|
316
|
+
content = m('.p-6.bg-red-50.border.border-red-200.rounded-xl', [
|
|
317
|
+
m('h3.text-lg.font-semibold.text-red-800', 'Sayfa Çizilirken Hata Oluştu'),
|
|
318
|
+
m('p.text-sm.text-red-600.mt-1', err.message || String(err)),
|
|
319
|
+
]);
|
|
270
320
|
}
|
|
271
321
|
if (content && (content.tag === Layout || (content.tag && content.tag.name === 'Layout'))) {
|
|
272
322
|
return content;
|
|
@@ -18,7 +18,10 @@ const api = {
|
|
|
18
18
|
if (response.status === 401 && path.indexOf('/auth/') !== 0) {
|
|
19
19
|
redirectToLogin();
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
const err = new Error(error.error || 'Request failed');
|
|
22
|
+
if (error.fields) err.fields = error.fields;
|
|
23
|
+
err.response = error;
|
|
24
|
+
throw err;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
return response.json();
|
|
@@ -16,52 +16,59 @@ const Pagination = {
|
|
|
16
16
|
pages.push(i);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
return m('.flex.items-center.justify-between.px-4.py-3.bg-white
|
|
20
|
-
m('.text-sm.text-gray-
|
|
19
|
+
return m('.flex.flex-col.sm:flex-row.items-center.justify-between.gap-3.px-4.py-3.bg-white.dark:bg-slate-800.border-t.border-gray-200.dark:border-slate-700', [
|
|
20
|
+
m('.text-xs.sm:text-sm.text-gray-600.dark:text-slate-400.text-center.sm:text-left', [
|
|
21
21
|
'Showing ',
|
|
22
|
-
m('span.font-
|
|
22
|
+
m('span.font-semibold.text-gray-900.dark:text-slate-200', ((page - 1) * perPage) + 1),
|
|
23
23
|
' to ',
|
|
24
|
-
m('span.font-
|
|
24
|
+
m('span.font-semibold.text-gray-900.dark:text-slate-200', Math.min(page * perPage, total)),
|
|
25
25
|
' of ',
|
|
26
|
-
m('span.font-
|
|
26
|
+
m('span.font-semibold.text-gray-900.dark:text-slate-200', total),
|
|
27
27
|
' results',
|
|
28
28
|
]),
|
|
29
|
-
m('nav.flex.items-center.
|
|
29
|
+
m('nav.flex.items-center.gap-1', [
|
|
30
30
|
// Previous button
|
|
31
|
-
m('button.px-3.py-1.rounded.border.text-sm', {
|
|
31
|
+
m('button.px-3.py-1.5.rounded-lg.border.border-gray-200.dark:border-slate-600.text-xs.sm:text-sm.font-medium.transition-colors', {
|
|
32
32
|
disabled: page <= 1,
|
|
33
|
-
class: page <= 1
|
|
33
|
+
class: page <= 1
|
|
34
|
+
? 'text-gray-300 dark:text-slate-600 cursor-not-allowed bg-gray-50 dark:bg-slate-900/50'
|
|
35
|
+
: 'text-gray-700 dark:text-slate-200 hover:bg-gray-100 dark:hover:bg-slate-700 bg-white dark:bg-slate-800',
|
|
34
36
|
onclick: () => page > 1 && onPageChange(page - 1),
|
|
35
37
|
}, '← Prev'),
|
|
38
|
+
|
|
39
|
+
// Mobile page indicator
|
|
40
|
+
m('span.sm:hidden.px-2.text-xs.font-medium.text-gray-600.dark:text-slate-400', `${page} / ${totalPages}`),
|
|
36
41
|
|
|
37
|
-
// Page numbers
|
|
42
|
+
// Page numbers (desktop only)
|
|
38
43
|
start > 1 ? [
|
|
39
|
-
m('button.px-3.py-1.rounded.text-sm.text-gray-700
|
|
44
|
+
m('button.hidden.sm:inline-flex.px-3.py-1.5.rounded-lg.text-xs.sm:text-sm.font-medium.text-gray-700.dark:text-slate-300.hover:bg-gray-100.dark:hover:bg-slate-700.transition-colors', {
|
|
40
45
|
onclick: () => onPageChange(1),
|
|
41
46
|
}, '1'),
|
|
42
|
-
start > 2 ? m('span.px-
|
|
47
|
+
start > 2 ? m('span.hidden.sm:inline-flex.px-1.text-gray-400.dark:text-slate-500', '...') : null,
|
|
43
48
|
] : null,
|
|
44
49
|
|
|
45
50
|
...pages.map(p =>
|
|
46
|
-
m('button.px-3.py-1.rounded.text-sm', {
|
|
51
|
+
m('button.hidden.sm:inline-flex.px-3.py-1.5.rounded-lg.text-xs.sm:text-sm.font-medium.transition-colors', {
|
|
47
52
|
class: p === page
|
|
48
|
-
? 'bg-blue-600 text-white'
|
|
49
|
-
: 'text-gray-700
|
|
53
|
+
? 'bg-blue-600 dark:bg-blue-500 text-white shadow-sm'
|
|
54
|
+
: 'text-gray-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-slate-700',
|
|
50
55
|
onclick: () => onPageChange(p),
|
|
51
56
|
}, p)
|
|
52
57
|
),
|
|
53
58
|
|
|
54
59
|
end < totalPages ? [
|
|
55
|
-
end < totalPages - 1 ? m('span.px-
|
|
56
|
-
m('button.px-3.py-1.rounded.text-sm.text-gray-700
|
|
60
|
+
end < totalPages - 1 ? m('span.hidden.sm:inline-flex.px-1.text-gray-400.dark:text-slate-500', '...') : null,
|
|
61
|
+
m('button.hidden.sm:inline-flex.px-3.py-1.5.rounded-lg.text-xs.sm:text-sm.font-medium.text-gray-700.dark:text-slate-300.hover:bg-gray-100.dark:hover:bg-slate-700.transition-colors', {
|
|
57
62
|
onclick: () => onPageChange(totalPages),
|
|
58
63
|
}, totalPages),
|
|
59
64
|
] : null,
|
|
60
65
|
|
|
61
66
|
// Next button
|
|
62
|
-
m('button.px-3.py-1.rounded.border.text-sm', {
|
|
67
|
+
m('button.px-3.py-1.5.rounded-lg.border.border-gray-200.dark:border-slate-600.text-xs.sm:text-sm.font-medium.transition-colors', {
|
|
63
68
|
disabled: page >= totalPages,
|
|
64
|
-
class: page >= totalPages
|
|
69
|
+
class: page >= totalPages
|
|
70
|
+
? 'text-gray-300 dark:text-slate-600 cursor-not-allowed bg-gray-50 dark:bg-slate-900/50'
|
|
71
|
+
: 'text-gray-700 dark:text-slate-200 hover:bg-gray-100 dark:hover:bg-slate-700 bg-white dark:bg-slate-800',
|
|
65
72
|
onclick: () => page < totalPages && onPageChange(page + 1),
|
|
66
73
|
}, 'Next →'),
|
|
67
74
|
]),
|