webspresso 0.0.108 → 0.0.110

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.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -3
  3. package/bin/commands/add-esbuild.js +7 -3
  4. package/bin/commands/add-tailwind.js +9 -5
  5. package/bin/commands/build.js +20 -4
  6. package/bin/commands/dev.js +57 -7
  7. package/bin/commands/doctor.js +42 -1
  8. package/bin/commands/favicon-generate.js +10 -3
  9. package/bin/commands/new.js +8 -3
  10. package/bin/commands/start.js +3 -1
  11. package/bin/utils/add-command.js +16 -0
  12. package/core/app.js +45 -3
  13. package/core/auth/authenticate.js +3 -3
  14. package/core/auth/hash.js +167 -43
  15. package/core/auth/index.js +13 -1
  16. package/core/auth/jwt.js +21 -2
  17. package/core/auth/manager.js +63 -14
  18. package/core/auth/middleware.js +4 -2
  19. package/core/auth/oauth.js +5 -1
  20. package/core/auth/tokens.js +7 -2
  21. package/core/auth/totp.js +42 -6
  22. package/core/auth/user-agent.js +2 -0
  23. package/core/client/adapter/datastar.js +1401 -0
  24. package/core/client/api.js +1337 -0
  25. package/core/client/controller.js +1706 -0
  26. package/core/client/debug.js +367 -0
  27. package/core/client/devtool.js +1134 -0
  28. package/core/client/forms.js +1164 -0
  29. package/core/client/fragments.js +894 -0
  30. package/core/client/index.d.ts +59 -0
  31. package/core/client/index.js +391 -0
  32. package/core/client/lite.js +175 -0
  33. package/core/client/server-fragments.js +868 -0
  34. package/core/client/storage.js +339 -0
  35. package/core/client/utils.js +281 -0
  36. package/core/compression/middleware.js +277 -26
  37. package/core/context/app-context.js +25 -3
  38. package/core/context/request.js +32 -4
  39. package/core/errors/middleware.js +14 -0
  40. package/core/events/dispatcher.js +33 -0
  41. package/core/forms/flash.js +136 -102
  42. package/core/health/middleware.js +36 -6
  43. package/core/http/pipeline.js +45 -6
  44. package/core/index.js +25 -7
  45. package/core/kernel/view.js +41 -10
  46. package/core/metrics/middleware.js +30 -6
  47. package/core/metrics/registry.js +13 -5
  48. package/core/orm/cache/fingerprint.js +32 -11
  49. package/core/orm/cache/layer.js +19 -2
  50. package/core/orm/hidden.js +145 -0
  51. package/core/orm/model.js +48 -2
  52. package/core/orm/query-builder.js +343 -6
  53. package/core/orm/query-filters.js +14 -2
  54. package/core/orm/repository.js +64 -13
  55. package/core/orm/schema-helpers.js +9 -6
  56. package/core/orm/transaction.js +50 -6
  57. package/core/orm/types.js +4 -0
  58. package/core/queue/adapters/database.js +184 -2
  59. package/core/queue/adapters/memory.js +153 -5
  60. package/core/queue/adapters/redis.js +59 -4
  61. package/core/queue/job.js +14 -1
  62. package/core/queue/manager.js +115 -0
  63. package/core/resilience/circuit-breaker.js +27 -8
  64. package/core/services/builtins/exchange.js +174 -7
  65. package/core/services/builtins/file-manager.js +36 -0
  66. package/core/services/builtins/mail.js +27 -0
  67. package/core/services/builtins/queue.js +144 -0
  68. package/core/services/builtins/security.js +118 -0
  69. package/core/services/builtins/webhook.js +110 -0
  70. package/core/services/executor.js +38 -16
  71. package/core/services/index.js +4 -1
  72. package/core/sse/stream.js +28 -5
  73. package/core/utils/timeout.js +8 -1
  74. package/core/validation/compiler.js +10 -7
  75. package/index.d.ts +1320 -74
  76. package/index.js +128 -8
  77. package/package.json +75 -27
  78. package/plugins/admin-panel/client/parts/08-record-list.js +717 -70
  79. package/plugins/admin-panel/index.js +19 -8
  80. package/plugins/csrf/index.js +3 -3
  81. package/plugins/data-exchange/export.js +214 -0
  82. package/plugins/data-exchange/import.js +48 -5
  83. package/plugins/data-exchange/index.js +70 -28
  84. package/plugins/data-exchange/preview.js +266 -0
  85. package/plugins/data-exchange/template.js +242 -0
  86. package/plugins/email/admin-component.js +783 -141
  87. package/plugins/email/api-handlers.js +51 -1
  88. package/plugins/email/index.js +5 -0
  89. package/plugins/email/service.js +48 -0
  90. package/plugins/file-manager/admin-component.js +1546 -70
  91. package/plugins/file-manager/api-handlers.js +166 -1
  92. package/plugins/file-manager/core.js +598 -8
  93. package/plugins/file-manager/index.js +8 -0
  94. package/plugins/index.js +7 -0
  95. package/plugins/mcp/auth.js +3 -3
  96. package/plugins/oauth/index.js +36 -3
  97. package/plugins/queue/admin-component.js +1092 -0
  98. package/plugins/queue/api-handlers.js +248 -0
  99. package/plugins/queue/index.js +69 -0
  100. package/plugins/rest-resources/index.js +7 -2
  101. package/plugins/security/admin-component.js +689 -0
  102. package/plugins/security/api-handlers.js +169 -0
  103. package/plugins/security/audit.js +153 -0
  104. package/plugins/security/bruteforce.js +244 -0
  105. package/plugins/security/firewall.js +459 -0
  106. package/plugins/security/headers.js +214 -0
  107. package/plugins/security/honeypot.js +234 -0
  108. package/plugins/security/incident-recorder.js +137 -0
  109. package/plugins/security/index.js +211 -0
  110. package/plugins/seo-checker/analyzer.js +20 -7
  111. package/plugins/upload/index.js +36 -0
  112. package/plugins/webhooks/admin-component.js +702 -175
  113. package/plugins/webhooks/api-handlers.js +75 -1
  114. package/plugins/webhooks/dispatcher.js +290 -11
  115. package/plugins/webhooks/index.js +37 -2
  116. package/plugins/webhooks/sender.js +56 -0
  117. package/plugins/xlsx/engine.js +19 -4
  118. package/src/api/api-loader.js +19 -1
  119. package/src/bundle/index.js +443 -17
  120. package/src/client-runtime/bootstrap-swup.js +9 -0
  121. package/src/client-runtime/mount.js +15 -20
  122. package/src/client-runtime/resolve.js +3 -2
  123. package/src/dev/api-typegen.js +1300 -0
  124. package/src/dev/banner.js +10 -1
  125. package/src/dev/colors.js +1 -1
  126. package/src/dev/index.js +7 -1
  127. package/src/dev/typegen.js +173 -17
  128. package/src/dev-reload/index.js +1 -1
  129. package/src/discovery/file-route-parser.js +17 -3
  130. package/src/discovery/scan-routes.js +4 -1
  131. package/src/file-router.js +253 -69
  132. package/src/helpers.js +46 -26
  133. package/src/njk-frontmatter.js +30 -4
  134. package/src/routing/cascading.js +26 -8
  135. package/src/routing/mount-discovered-routes.js +76 -0
  136. package/src/server.js +231 -70
  137. package/views/partials/webspresso-client-runtime.njk +1 -9
  138. package/.agents/ADMIN.md +0 -426
  139. package/.agents/AGENTS.md +0 -96
  140. package/.agents/ARCHITECTURE.md +0 -163
  141. package/.agents/AUTH.md +0 -581
  142. package/.agents/CACHE.md +0 -148
  143. package/.agents/CLI.md +0 -649
  144. package/.agents/COMPRESSION.md +0 -59
  145. package/.agents/CONTEXT.md +0 -152
  146. package/.agents/CONVENTIONS.md +0 -50
  147. package/.agents/EARLY_HINTS.md +0 -120
  148. package/.agents/EMAIL.md +0 -223
  149. package/.agents/ERRORS.md +0 -260
  150. package/.agents/EVENTS.md +0 -179
  151. package/.agents/FLAGS.md +0 -118
  152. package/.agents/HEALTH.md +0 -118
  153. package/.agents/LIMITS.md +0 -109
  154. package/.agents/LOGGING.md +0 -93
  155. package/.agents/MCP.md +0 -103
  156. package/.agents/METRICS.md +0 -127
  157. package/.agents/MIGRATION_JOBS.md +0 -95
  158. package/.agents/ORM.md +0 -700
  159. package/.agents/PLUGINS.md +0 -729
  160. package/.agents/QUEUE.md +0 -290
  161. package/.agents/RATE_LIMIT.md +0 -147
  162. package/.agents/REALTIME.md +0 -213
  163. package/.agents/RESILIENCE.md +0 -235
  164. package/.agents/ROUTING.md +0 -605
  165. package/.agents/SCALE_OUT.md +0 -109
  166. package/.agents/SECRETS.md +0 -117
  167. package/.agents/SERVICES.md +0 -341
  168. package/.agents/SHUTDOWN.md +0 -74
  169. package/.agents/STREAMING.md +0 -108
  170. package/.agents/WORKFLOW.md +0 -27
  171. package/.agents/internal/DOCUMENTATION_REVIEW.md +0 -485
  172. package/.agents/internal/TECHNICAL_REVIEW.md +0 -428
  173. package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +0 -122
  174. package/.agents/skills/webspresso-framework/SKILL.md +0 -89
  175. package/.agents/skills/webspresso-orm-and-database/SKILL.md +0 -127
  176. package/.agents/skills/webspresso-routing-and-api/SKILL.md +0 -130
  177. package/.agents/skills/webspresso-services/SKILL.md +0 -134
  178. package/.cursorrules +0 -14
  179. package/AGENTS.md +0 -203
  180. package/CLAUDE.md +0 -54
  181. package/core/kernel/run-demo.js +0 -58
  182. package/plugins/admin-panel/styles.js +0 -9
  183. package/plugins/data-exchange/export-xlsx.js +0 -119
  184. package/src/client-runtime/bootstrap-alpine-swup.js +0 -34
@@ -0,0 +1,689 @@
1
+ /**
2
+ * Security Shield Admin Panel Component — Mithril SPA Generator
3
+ * Interactive Security Cockpit featuring live KPI metrics, IP Firewall & Ban Manager,
4
+ * Honeypot Incident Feed, Manual Ban Modal, and Security Posture Audit Checklist.
5
+ * @module plugins/security/admin-component
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ /**
11
+ * Generate client-side Mithril SPA component script string for Security Shield
12
+ *
13
+ * @param {Object} [options]
14
+ * @param {string} [options.apiPrefix='/security']
15
+ * @returns {string}
16
+ */
17
+ function generateSecurityComponent(options = {}) {
18
+ const apiPrefix = options.apiPrefix || '/security';
19
+
20
+ return `
21
+ (function() {
22
+ var API = '${apiPrefix}';
23
+
24
+ function formatRelativeTime(dateStrOrTs) {
25
+ if (!dateStrOrTs) return '—';
26
+ try {
27
+ var d = new Date(dateStrOrTs);
28
+ var now = new Date();
29
+ var diffMs = now - d;
30
+ var isFuture = diffMs < 0;
31
+ var absSec = Math.floor(Math.abs(diffMs) / 1000);
32
+ var absMin = Math.floor(absSec / 60);
33
+ var absHour = Math.floor(absMin / 60);
34
+ var absDay = Math.floor(absHour / 24);
35
+
36
+ if (isFuture) {
37
+ if (absSec < 60) return 'in ' + absSec + 's';
38
+ if (absMin < 60) return 'in ' + absMin + 'm';
39
+ if (absHour < 24) return 'in ' + absHour + 'h';
40
+ return 'in ' + absDay + 'd';
41
+ }
42
+
43
+ if (absSec < 60) return absSec + 's ago';
44
+ if (absMin < 60) return absMin + 'm ago';
45
+ if (absHour < 24) return absHour + 'h ago';
46
+ return absDay + 'd ago';
47
+ } catch (e) {
48
+ return String(dateStrOrTs);
49
+ }
50
+ }
51
+
52
+ function getReasonBadgeStyle(reason) {
53
+ var r = (reason || '').toLowerCase();
54
+ if (r.indexOf('honeypot') !== -1) {
55
+ return 'bg-purple-100 text-purple-800 dark:bg-purple-950/60 dark:text-purple-300 border-purple-200 dark:border-purple-800';
56
+ }
57
+ if (r.indexOf('traversal') !== -1) {
58
+ return 'bg-rose-100 text-rose-800 dark:bg-rose-950/60 dark:text-rose-300 border-rose-200 dark:border-rose-800';
59
+ }
60
+ if (r.indexOf('brute') !== -1) {
61
+ return 'bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-300 border-amber-200 dark:border-amber-800';
62
+ }
63
+ if (r.indexOf('manual') !== -1) {
64
+ return 'bg-blue-100 text-blue-800 dark:bg-blue-950/60 dark:text-blue-300 border-blue-200 dark:border-blue-800';
65
+ }
66
+ return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 border-gray-200 dark:border-gray-700';
67
+ }
68
+
69
+ function getActionBadgeStyle(action) {
70
+ var a = (action || '').toLowerCase();
71
+ if (a === 'banned') {
72
+ return 'bg-rose-100 text-rose-800 dark:bg-rose-950/60 dark:text-rose-300 border-rose-200 dark:border-rose-800';
73
+ }
74
+ if (a === 'blocked') {
75
+ return 'bg-orange-100 text-orange-800 dark:bg-orange-950/60 dark:text-orange-300 border-orange-200 dark:border-orange-800';
76
+ }
77
+ if (a === 'throttled') {
78
+ return 'bg-amber-100 text-amber-800 dark:bg-amber-950/60 dark:text-amber-300 border-amber-200 dark:border-amber-800';
79
+ }
80
+ return 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300 border-emerald-200 dark:border-emerald-800';
81
+ }
82
+
83
+ var SecurityDashboard = {
84
+ stats: {
85
+ activeBans: 0,
86
+ permanentBans: 0,
87
+ autoBans24h: 0,
88
+ totalBans: 0,
89
+ totalViolations: 0,
90
+ whitelistCount: 0,
91
+ blacklistCount: 0,
92
+ incidents24h: 0,
93
+ incidentBreakdown: {},
94
+ securityScore: 100,
95
+ securityGrade: 'A+',
96
+ passedChecks: 8,
97
+ totalChecks: 8,
98
+ },
99
+ bannedIps: [],
100
+ bannedTotal: 0,
101
+ bannedPage: 1,
102
+ bannedLimit: 20,
103
+ bannedTotalPages: 1,
104
+
105
+ incidents: [],
106
+ incidentsTotal: 0,
107
+ auditData: null,
108
+
109
+ activeTab: 'banned', // 'banned' | 'incidents' | 'audit'
110
+ searchQuery: '',
111
+ reasonFilter: 'all',
112
+ incidentTypeFilter: 'all',
113
+
114
+ isLoading: false,
115
+ errorMessage: '',
116
+ successMessage: '',
117
+
118
+ showBanModal: false,
119
+ banModalData: {
120
+ ip: '',
121
+ reason: 'Malicious activity',
122
+ duration: '24h',
123
+ isSubmitting: false,
124
+ },
125
+
126
+ loadStats: function() {
127
+ var self = this;
128
+ return m.request({
129
+ method: 'GET',
130
+ url: '/_admin/api' + API + '/stats',
131
+ withCredentials: true,
132
+ }).then(function(res) {
133
+ if (res && res.status === 'ok') {
134
+ self.stats = res;
135
+ }
136
+ }).catch(function(err) {
137
+ console.error('Failed to load security stats:', err);
138
+ });
139
+ },
140
+
141
+ loadBannedIps: function() {
142
+ var self = this;
143
+ self.isLoading = true;
144
+ var params = {
145
+ page: self.bannedPage,
146
+ limit: self.bannedLimit,
147
+ };
148
+ if (self.searchQuery) params.search = self.searchQuery;
149
+ if (self.reasonFilter && self.reasonFilter !== 'all') params.reason = self.reasonFilter;
150
+
151
+ var queryString = m.buildQueryString(params);
152
+
153
+ return m.request({
154
+ method: 'GET',
155
+ url: '/_admin/api' + API + '/banned' + (queryString ? '?' + queryString : ''),
156
+ withCredentials: true,
157
+ }).then(function(res) {
158
+ self.isLoading = false;
159
+ if (res && Array.isArray(res.items)) {
160
+ self.bannedIps = res.items;
161
+ self.bannedTotal = res.total;
162
+ self.bannedTotalPages = res.totalPages || 1;
163
+ }
164
+ }).catch(function(err) {
165
+ self.isLoading = false;
166
+ self.errorMessage = 'Failed to load banned IPs: ' + (err.message || 'Server error');
167
+ });
168
+ },
169
+
170
+ loadIncidents: function() {
171
+ var self = this;
172
+ self.isLoading = true;
173
+ var params = { limit: 50 };
174
+ if (self.incidentTypeFilter && self.incidentTypeFilter !== 'all') {
175
+ params.type = self.incidentTypeFilter;
176
+ }
177
+ if (self.searchQuery) {
178
+ params.search = self.searchQuery;
179
+ }
180
+
181
+ var queryString = m.buildQueryString(params);
182
+
183
+ return m.request({
184
+ method: 'GET',
185
+ url: '/_admin/api' + API + '/incidents' + (queryString ? '?' + queryString : ''),
186
+ withCredentials: true,
187
+ }).then(function(res) {
188
+ self.isLoading = false;
189
+ if (res && Array.isArray(res.items)) {
190
+ self.incidents = res.items;
191
+ self.incidentsTotal = res.total;
192
+ }
193
+ }).catch(function(err) {
194
+ self.isLoading = false;
195
+ console.error('Failed to load incidents:', err);
196
+ });
197
+ },
198
+
199
+ loadAudit: function() {
200
+ var self = this;
201
+ self.isLoading = true;
202
+ return m.request({
203
+ method: 'GET',
204
+ url: '/_admin/api' + API + '/audit',
205
+ withCredentials: true,
206
+ }).then(function(res) {
207
+ self.isLoading = false;
208
+ self.auditData = res;
209
+ }).catch(function(err) {
210
+ self.isLoading = false;
211
+ console.error('Failed to load audit:', err);
212
+ });
213
+ },
214
+
215
+ unbanIp: function(ip) {
216
+ var self = this;
217
+ if (!confirm('Are you sure you want to lift the ban on IP ' + ip + '?')) return;
218
+
219
+ m.request({
220
+ method: 'POST',
221
+ url: '/_admin/api' + API + '/unban',
222
+ body: { ip: ip },
223
+ withCredentials: true,
224
+ }).then(function() {
225
+ self.successMessage = 'IP ' + ip + ' unbanned successfully.';
226
+ self.loadStats();
227
+ self.loadBannedIps();
228
+ setTimeout(function() { self.successMessage = ''; m.redraw(); }, 4000);
229
+ }).catch(function(err) {
230
+ alert('Failed to unban IP: ' + (err.message || 'Server error'));
231
+ });
232
+ },
233
+
234
+ clearIncidents: function() {
235
+ var self = this;
236
+ if (!confirm('Clear all logged security incidents?')) return;
237
+
238
+ m.request({
239
+ method: 'POST',
240
+ url: '/_admin/api' + API + '/clear-incidents',
241
+ withCredentials: true,
242
+ }).then(function() {
243
+ self.incidents = [];
244
+ self.loadStats();
245
+ m.redraw();
246
+ });
247
+ },
248
+
249
+ submitBan: function() {
250
+ var self = this;
251
+ var data = self.banModalData;
252
+ if (!data.ip.trim()) {
253
+ alert('Please enter a valid IP address.');
254
+ return;
255
+ }
256
+
257
+ data.isSubmitting = true;
258
+ m.request({
259
+ method: 'POST',
260
+ url: '/_admin/api' + API + '/ban',
261
+ body: {
262
+ ip: data.ip.trim(),
263
+ reason: data.reason.trim(),
264
+ duration: data.duration,
265
+ },
266
+ withCredentials: true,
267
+ }).then(function() {
268
+ data.isSubmitting = false;
269
+ self.showBanModal = false;
270
+ self.successMessage = 'IP ' + data.ip.trim() + ' banned successfully.';
271
+ data.ip = '';
272
+ self.loadStats();
273
+ self.loadBannedIps();
274
+ setTimeout(function() { self.successMessage = ''; m.redraw(); }, 4000);
275
+ }).catch(function(err) {
276
+ data.isSubmitting = false;
277
+ alert('Failed to ban IP: ' + (err.message || 'Server error'));
278
+ });
279
+ },
280
+
281
+ oninit: function() {
282
+ this.loadStats();
283
+ this.loadBannedIps();
284
+ },
285
+
286
+ view: function() {
287
+ var self = this;
288
+ var stats = self.stats;
289
+
290
+ var gradeColor = 'text-emerald-500';
291
+ if (stats.securityGrade === 'B') gradeColor = 'text-blue-500';
292
+ if (stats.securityGrade === 'C') gradeColor = 'text-amber-500';
293
+ if (stats.securityGrade === 'F') gradeColor = 'text-rose-500';
294
+
295
+ return m('div.space-y-6', [
296
+ // Header
297
+ m('div.flex.flex-col.md:flex-row.md:items-center.md:justify-between.gap-4', [
298
+ m('div', [
299
+ m('div.flex.items-center.gap-3', [
300
+ m('h1.text-2xl.font-bold.text-gray-900.dark:text-white', 'Security Shield & Threat Defense'),
301
+ m('span.inline-flex.items-center.gap-1.5.px-2.5.py-0.5.rounded-full.text-xs.font-semibold.bg-emerald-100.text-emerald-800.dark:bg-emerald-950/60.dark:text-emerald-300.border.border-emerald-200.dark:border-emerald-800', [
302
+ m('span.w-2.h-2.rounded-full.bg-emerald-500.animate-pulse'),
303
+ 'Shield Active',
304
+ ]),
305
+ ]),
306
+ m('p.text-sm.text-gray-500.dark:text-gray-400.mt-1', 'Native WAF, IP firewall, honeypot traps, brute-force defense, and security posture audit.'),
307
+ ]),
308
+ m('div.flex.items-center.gap-2.5', [
309
+ m('button.px-3.5.py-2.text-sm.font-medium.text-white.bg-rose-600.hover:bg-rose-700.rounded-lg.shadow-sm.flex.items-center.gap-1.5.transition-colors', {
310
+ onclick: function() { self.showBanModal = true; },
311
+ }, [
312
+ m('span', '+'),
313
+ m('span', 'Ban IP Manually'),
314
+ ]),
315
+ m('button.px-3.5.py-2.text-sm.font-medium.text-gray-700.dark:text-gray-200.bg-white.dark:bg-gray-800.border.border-gray-300.dark:border-gray-700.hover:bg-gray-50.dark:hover:bg-gray-700.rounded-lg.shadow-sm.flex.items-center.gap-1.5.transition-colors', {
316
+ onclick: function() {
317
+ self.loadStats();
318
+ if (self.activeTab === 'banned') self.loadBannedIps();
319
+ if (self.activeTab === 'incidents') self.loadIncidents();
320
+ if (self.activeTab === 'audit') self.loadAudit();
321
+ },
322
+ }, [
323
+ m('span', '⟳'),
324
+ m('span', 'Refresh'),
325
+ ]),
326
+ ]),
327
+ ]),
328
+
329
+ // Flash message
330
+ self.successMessage ? m('div.p-4.rounded-lg.bg-emerald-50.dark:bg-emerald-950/50.border.border-emerald-200.dark:border-emerald-800.text-emerald-800.dark:text-emerald-300.text-sm', self.successMessage) : null,
331
+ self.errorMessage ? m('div.p-4.rounded-lg.bg-rose-50.dark:bg-rose-950/50.border.border-rose-200.dark:border-rose-800.text-rose-800.dark:text-rose-300.text-sm', self.errorMessage) : null,
332
+
333
+ // Live KPI Cards
334
+ m('div.grid.grid-cols-1.sm:grid-cols-2.lg:grid-cols-4.gap-4', [
335
+ // Card 1: Shield Status
336
+ m('div.bg-white.dark:bg-gray-800.border.border-gray-200.dark:border-gray-700.rounded-xl.p-5.shadow-sm', [
337
+ m('div.flex.items-center.justify-between', [
338
+ m('span.text-xs.font-semibold.text-gray-500.dark:text-gray-400.uppercase.tracking-wider', 'Shield Engine'),
339
+ m('span.text-xs.px-2.py-0.5.rounded-full.bg-emerald-100.dark:bg-emerald-900/40.text-emerald-700.dark:text-emerald-300.font-medium', 'Zero-Dep'),
340
+ ]),
341
+ m('div.mt-2.flex.items-baseline.gap-2', [
342
+ m('span.text-2xl.font-bold.text-emerald-600.dark:text-emerald-400', 'Enforcing'),
343
+ ]),
344
+ m('div.mt-1.text-xs.text-gray-500.dark:text-gray-400', 'Headers & Firewall running'),
345
+ ]),
346
+
347
+ // Card 2: Active IP Bans
348
+ m('div.bg-white.dark:bg-gray-800.border.border-gray-200.dark:border-gray-700.rounded-xl.p-5.shadow-sm', [
349
+ m('div.flex.items-center.justify-between', [
350
+ m('span.text-xs.font-semibold.text-gray-500.dark:text-gray-400.uppercase.tracking-wider', 'Active IP Bans'),
351
+ m('span.text-xs.px-2.py-0.5.rounded-full.bg-rose-100.dark:bg-rose-900/40.text-rose-700.dark:text-rose-300.font-medium', stats.permanentBans + ' Perm'),
352
+ ]),
353
+ m('div.mt-2.flex.items-baseline.gap-2', [
354
+ m('span.text-2xl.font-bold.text-gray-900.dark:text-white', stats.activeBans || 0),
355
+ m('span.text-xs.text-gray-500.dark:text-gray-400', 'banned IPs'),
356
+ ]),
357
+ m('div.mt-1.text-xs.text-gray-500.dark:text-gray-400', (stats.autoBans24h || 0) + ' auto-banned in last 24h'),
358
+ ]),
359
+
360
+ // Card 3: Attacks Blocked
361
+ m('div.bg-white.dark:bg-gray-800.border.border-gray-200.dark:border-gray-700.rounded-xl.p-5.shadow-sm', [
362
+ m('div.flex.items-center.justify-between', [
363
+ m('span.text-xs.font-semibold.text-gray-500.dark:text-gray-400.uppercase.tracking-wider', 'Attacks Blocked (24h)'),
364
+ m('span.text-xs.px-2.py-0.5.rounded-full.bg-purple-100.dark:bg-purple-900/40.text-purple-700.dark:text-purple-300.font-medium', (stats.totalViolations || 0) + ' total'),
365
+ ]),
366
+ m('div.mt-2.flex.items-baseline.gap-2', [
367
+ m('span.text-2xl.font-bold.text-gray-900.dark:text-white', stats.incidents24h || 0),
368
+ m('span.text-xs.text-gray-500.dark:text-gray-400', 'threat attempts'),
369
+ ]),
370
+ m('div.mt-1.text-xs.text-gray-500.dark:text-gray-400', 'Traps, LFI & scanner hits'),
371
+ ]),
372
+
373
+ // Card 4: Health Score
374
+ m('div.bg-white.dark:bg-gray-800.border.border-gray-200.dark:border-gray-700.rounded-xl.p-5.shadow-sm', [
375
+ m('div.flex.items-center.justify-between', [
376
+ m('span.text-xs.font-semibold.text-gray-500.dark:text-gray-400.uppercase.tracking-wider', 'Security Score'),
377
+ m('span.text-xs.px-2.py-0.5.rounded-full.bg-blue-100.dark:bg-blue-900/40.text-blue-700.dark:text-blue-300.font-bold', stats.securityGrade || 'A+'),
378
+ ]),
379
+ m('div.mt-2.flex.items-baseline.gap-2', [
380
+ m('span.text-2xl.font-bold.' + gradeColor, (stats.securityScore || 100) + '%'),
381
+ m('span.text-xs.text-gray-500.dark:text-gray-400', '(' + stats.passedChecks + '/' + stats.totalChecks + ' checks)'),
382
+ ]),
383
+ m('div.mt-1.text-xs.text-gray-500.dark:text-gray-400', 'Full compliance with OWASP standard'),
384
+ ]),
385
+ ]),
386
+
387
+ // Tab Navigation
388
+ m('div.border-b.border-gray-200.dark:border-gray-700', [
389
+ m('nav.flex.space-x-8', [
390
+ m('button.py-3.px-1.border-b-2.font-medium.text-sm.transition-colors', {
391
+ class: self.activeTab === 'banned'
392
+ ? 'border-rose-500 text-rose-600 dark:text-rose-400 font-semibold'
393
+ : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300',
394
+ onclick: function() {
395
+ self.activeTab = 'banned';
396
+ self.loadBannedIps();
397
+ },
398
+ }, [
399
+ m('span', 'Banned IPs & Firewall'),
400
+ m('span.ml-2.py-0.5.px-2.rounded-full.text-xs.bg-gray-100.dark:bg-gray-700.text-gray-600.dark:text-gray-300', stats.activeBans || 0),
401
+ ]),
402
+ m('button.py-3.px-1.border-b-2.font-medium.text-sm.transition-colors', {
403
+ class: self.activeTab === 'incidents'
404
+ ? 'border-purple-500 text-purple-600 dark:text-purple-400 font-semibold'
405
+ : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300',
406
+ onclick: function() {
407
+ self.activeTab = 'incidents';
408
+ self.loadIncidents();
409
+ },
410
+ }, [
411
+ m('span', 'Incident Threat Log'),
412
+ m('span.ml-2.py-0.5.px-2.rounded-full.text-xs.bg-gray-100.dark:bg-gray-700.text-gray-600.dark:text-gray-300', stats.incidents24h || 0),
413
+ ]),
414
+ m('button.py-3.px-1.border-b-2.font-medium.text-sm.transition-colors', {
415
+ class: self.activeTab === 'audit'
416
+ ? 'border-blue-500 text-blue-600 dark:text-blue-400 font-semibold'
417
+ : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300',
418
+ onclick: function() {
419
+ self.activeTab = 'audit';
420
+ self.loadAudit();
421
+ },
422
+ }, [
423
+ m('span', 'Security Posture Audit'),
424
+ m('span.ml-2.py-0.5.px-2.rounded-full.text-xs.bg-blue-100.dark:bg-blue-900/40.text-blue-700.dark:text-blue-300', stats.securityGrade || 'A+'),
425
+ ]),
426
+ ]),
427
+ ]),
428
+
429
+ // TAB 1: BANNED IPS
430
+ self.activeTab === 'banned' ? m('div.space-y-4', [
431
+ // Filter & Search bar
432
+ m('div.flex.flex-col.sm:flex-row.sm:items-center.sm:justify-between.gap-3', [
433
+ m('div.flex.items-center.gap-2.flex-1', [
434
+ m('input.w-full.max-w-xs.px-3.py-2.text-sm.rounded-lg.border.border-gray-300.dark:border-gray-700.bg-white.dark:bg-gray-800.text-gray-900.dark:text-white', {
435
+ placeholder: 'Search IP or reason...',
436
+ value: self.searchQuery,
437
+ oninput: function(e) {
438
+ self.searchQuery = e.target.value;
439
+ self.bannedPage = 1;
440
+ },
441
+ onkeyup: function(e) {
442
+ if (e.key === 'Enter') self.loadBannedIps();
443
+ },
444
+ }),
445
+ m('select.px-3.py-2.text-sm.rounded-lg.border.border-gray-300.dark:border-gray-700.bg-white.dark:bg-gray-800.text-gray-700.dark:text-gray-200', {
446
+ value: self.reasonFilter,
447
+ onchange: function(e) {
448
+ self.reasonFilter = e.target.value;
449
+ self.bannedPage = 1;
450
+ self.loadBannedIps();
451
+ },
452
+ }, [
453
+ m('option', { value: 'all' }, 'All Reasons'),
454
+ m('option', { value: 'honeypot' }, 'Honeypot Hits'),
455
+ m('option', { value: 'traversal' }, 'Path Traversal'),
456
+ m('option', { value: 'brute' }, 'Brute-Force'),
457
+ m('option', { value: 'manual' }, 'Manual Bans'),
458
+ ]),
459
+ ]),
460
+ m('div.text-xs.text-gray-500.dark:text-gray-400', 'Showing ' + self.bannedIps.length + ' of ' + self.bannedTotal + ' banned IPs'),
461
+ ]),
462
+
463
+ // Table
464
+ m('div.bg-white.dark:bg-gray-800.border.border-gray-200.dark:border-gray-700.rounded-xl.overflow-hidden.shadow-sm', [
465
+ m('div.overflow-x-auto', [
466
+ m('table.min-w-full.divide-y.divide-gray-200.dark:divide-gray-700.text-sm', [
467
+ m('thead.bg-gray-50.dark:bg-gray-900/50.text-gray-500.dark:text-gray-400.text-xs.uppercase.font-semibold', [
468
+ m('tr', [
469
+ m('th.px-4.py-3.text-left', 'IP Address'),
470
+ m('th.px-4.py-3.text-left', 'Reason'),
471
+ m('th.px-4.py-3.text-left', 'Banned At'),
472
+ m('th.px-4.py-3.text-left', 'Expires In'),
473
+ m('th.px-4.py-3.text-center', 'Violations'),
474
+ m('th.px-4.py-3.text-right', 'Actions'),
475
+ ]),
476
+ ]),
477
+ m('tbody.divide-y.divide-gray-200.dark:divide-gray-700', [
478
+ self.bannedIps.length === 0 ? m('tr', [
479
+ m('td.px-4.py-8.text-center.text-gray-500.dark:text-gray-400', { colspan: 6 }, [
480
+ m('div.text-3xl.mb-2', '🛡️'),
481
+ m('div.font-medium', 'No banned IP addresses found.'),
482
+ m('div.text-xs.text-gray-400.mt-1', 'The firewall is active and monitoring for malicious activity.'),
483
+ ]),
484
+ ]) : self.bannedIps.map(function(item) {
485
+ var expiryStr = item.isPermanent ? 'Permanent' : formatRelativeTime(item.expiresAt);
486
+ return m('tr.hover:bg-gray-50.dark:hover:bg-gray-700/50.transition-colors', { key: item.ip }, [
487
+ m('td.px-4.py-3.font-mono.font-semibold.text-gray-900.dark:text-white', item.ip),
488
+ m('td.px-4.py-3', [
489
+ m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.border', {
490
+ class: getReasonBadgeStyle(item.reason),
491
+ }, item.reason || 'manual_ban'),
492
+ ]),
493
+ m('td.px-4.py-3.text-gray-500.dark:text-gray-400', formatRelativeTime(item.bannedAt)),
494
+ m('td.px-4.py-3', [
495
+ item.isPermanent
496
+ ? m('span.px-2.py-0.5.rounded-full.text-xs.bg-gray-100.dark:bg-gray-700.text-gray-700.dark:text-gray-300.font-medium', 'Permanent')
497
+ : m('span.text-xs.text-amber-600.dark:text-amber-400.font-medium', expiryStr),
498
+ ]),
499
+ m('td.px-4.py-3.text-center.font-mono.text-xs.text-gray-600.dark:text-gray-300', item.violations || 0),
500
+ m('td.px-4.py-3.text-right', [
501
+ m('button.px-2.5.py-1.text-xs.font-medium.text-rose-600.dark:text-rose-400.hover:bg-rose-50.dark:hover:bg-rose-950/60.rounded.transition-colors', {
502
+ onclick: function() { self.unbanIp(item.ip); },
503
+ }, 'Unban'),
504
+ ]),
505
+ ]);
506
+ }),
507
+ ]),
508
+ ]),
509
+ ]),
510
+ ]),
511
+ ]) : null,
512
+
513
+ // TAB 2: INCIDENTS FEED
514
+ self.activeTab === 'incidents' ? m('div.space-y-4', [
515
+ m('div.flex.flex-col.sm:flex-row.sm:items-center.sm:justify-between.gap-3', [
516
+ m('div.flex.items-center.gap-2.flex-1', [
517
+ m('select.px-3.py-2.text-sm.rounded-lg.border.border-gray-300.dark:border-gray-700.bg-white.dark:bg-gray-800.text-gray-700.dark:text-gray-200', {
518
+ value: self.incidentTypeFilter,
519
+ onchange: function(e) {
520
+ self.incidentTypeFilter = e.target.value;
521
+ self.loadIncidents();
522
+ },
523
+ }, [
524
+ m('option', { value: 'all' }, 'All Threat Types'),
525
+ m('option', { value: 'honeypot' }, 'Honeypots'),
526
+ m('option', { value: 'path_traversal' }, 'Path Traversal'),
527
+ m('option', { value: 'bad_bot' }, 'Vulnerability Scanners'),
528
+ m('option', { value: 'bruteforce' }, 'Brute-Force'),
529
+ m('option', { value: 'manual_ban' }, 'Manual Bans'),
530
+ ]),
531
+ m('button.px-3.py-2.text-xs.font-medium.text-gray-600.dark:text-gray-300.bg-gray-100.dark:bg-gray-800.hover:bg-gray-200.dark:hover:bg-gray-700.rounded-lg.transition-colors', {
532
+ onclick: function() { self.clearIncidents(); },
533
+ }, 'Clear Log'),
534
+ ]),
535
+ m('div.text-xs.text-gray-500.dark:text-gray-400', 'Showing latest ' + self.incidents.length + ' security incidents'),
536
+ ]),
537
+
538
+ m('div.bg-white.dark:bg-gray-800.border.border-gray-200.dark:border-gray-700.rounded-xl.overflow-hidden.shadow-sm', [
539
+ m('div.overflow-x-auto', [
540
+ m('table.min-w-full.divide-y.divide-gray-200.dark:divide-gray-700.text-sm', [
541
+ m('thead.bg-gray-50.dark:bg-gray-900/50.text-gray-500.dark:text-gray-400.text-xs.uppercase.font-semibold', [
542
+ m('tr', [
543
+ m('th.px-4.py-3.text-left', 'Time'),
544
+ m('th.px-4.py-3.text-left', 'Type'),
545
+ m('th.px-4.py-3.text-left', 'Attacker IP'),
546
+ m('th.px-4.py-3.text-left', 'Target Route / Path'),
547
+ m('th.px-4.py-3.text-left', 'Reason'),
548
+ m('th.px-4.py-3.text-right', 'Action Taken'),
549
+ ]),
550
+ ]),
551
+ m('tbody.divide-y.divide-gray-200.dark:divide-gray-700', [
552
+ self.incidents.length === 0 ? m('tr', [
553
+ m('td.px-4.py-8.text-center.text-gray-500.dark:text-gray-400', { colspan: 6 }, 'No incidents recorded yet.'),
554
+ ]) : self.incidents.map(function(inc) {
555
+ return m('tr.hover:bg-gray-50.dark:hover:bg-gray-700/50.transition-colors', { key: inc.id }, [
556
+ m('td.px-4.py-3.text-gray-500.dark:text-gray-400.text-xs.whitespace-nowrap', formatRelativeTime(inc.timestamp)),
557
+ m('td.px-4.py-3', [
558
+ m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-medium.border', {
559
+ class: getReasonBadgeStyle(inc.type),
560
+ }, inc.type),
561
+ ]),
562
+ m('td.px-4.py-3.font-mono.font-semibold.text-gray-900.dark:text-white', inc.ip),
563
+ m('td.px-4.py-3.font-mono.text-xs.text-gray-700.dark:text-gray-300', [
564
+ m('span.font-bold.text-indigo-600.dark:text-indigo-400.mr-1', inc.method),
565
+ inc.path,
566
+ ]),
567
+ m('td.px-4.py-3.text-xs.text-gray-500.dark:text-gray-400', inc.reason),
568
+ m('td.px-4.py-3.text-right', [
569
+ m('span.inline-flex.items-center.px-2.py-0.5.rounded-full.text-xs.font-bold.border', {
570
+ class: getActionBadgeStyle(inc.actionTaken),
571
+ }, (inc.actionTaken || 'BLOCKED').toUpperCase()),
572
+ ]),
573
+ ]);
574
+ }),
575
+ ]),
576
+ ]),
577
+ ]),
578
+ ]),
579
+ ]) : null,
580
+
581
+ // TAB 3: AUDIT CHECKLIST
582
+ self.activeTab === 'audit' ? m('div.space-y-6', [
583
+ self.auditData ? m('div.space-y-4', [
584
+ // Audit Overview Header Card
585
+ m('div.bg-gradient-to-r.from-slate-900.to-slate-800.text-white.p-6.rounded-xl.shadow-md.flex.flex-col.md:flex-row.md:items-center.md:justify-between.gap-4', [
586
+ m('div', [
587
+ m('div.text-xs.font-semibold.uppercase.tracking-wider.text-slate-400', 'OWASP Security Compliance Grade'),
588
+ m('div.text-3xl.font-extrabold.mt-1.flex.items-center.gap-3', [
589
+ m('span', self.auditData.grade + ' Rating'),
590
+ m('span.text-lg.font-medium.text-emerald-400', '(' + self.auditData.score + '% Pass Rate)'),
591
+ ]),
592
+ m('div.text-sm.text-slate-300.mt-1', self.auditData.passedChecks + ' out of ' + self.auditData.totalChecks + ' security defenses verified and active.'),
593
+ ]),
594
+ m('div.flex.items-center.gap-2', [
595
+ m('span.text-xs.text-slate-400', 'Last audited: ' + formatRelativeTime(self.auditData.auditedAt)),
596
+ ]),
597
+ ]),
598
+
599
+ // Checklist Items
600
+ m('div.grid.grid-cols-1.md:grid-cols-2.gap-4', [
601
+ self.auditData.checks.map(function(chk) {
602
+ return m('div.bg-white.dark:bg-gray-800.border.rounded-xl.p-5.shadow-sm.flex.flex-col.justify-between.transition-all', {
603
+ class: chk.passed
604
+ ? 'border-gray-200 dark:border-gray-700'
605
+ : 'border-rose-200 dark:border-rose-900/60 bg-rose-50/20 dark:bg-rose-950/10',
606
+ }, [
607
+ m('div', [
608
+ m('div.flex.items-start.justify-between.gap-2', [
609
+ m('span.font-semibold.text-gray-900.dark:text-white', chk.title),
610
+ chk.passed
611
+ ? m('span.px-2.py-0.5.rounded-full.text-xs.font-bold.bg-emerald-100.text-emerald-800.dark:bg-emerald-950/60.dark:text-emerald-300', 'PASS')
612
+ : m('span.px-2.py-0.5.rounded-full.text-xs.font-bold.bg-rose-100.text-rose-800.dark:bg-rose-950/60.dark:text-rose-300', 'FAIL'),
613
+ ]),
614
+ m('p.text-xs.text-gray-500.dark:text-gray-400.mt-1.5', chk.description),
615
+ ]),
616
+ chk.recommendation ? m('div.mt-3.p-2.5.rounded-lg.bg-rose-50.dark:bg-rose-950/40.border.border-rose-200.dark:border-rose-800.text-xs.text-rose-700.dark:text-rose-300', [
617
+ m('span.font-semibold', 'Recommendation: '),
618
+ chk.recommendation,
619
+ ]) : null,
620
+ ]);
621
+ }),
622
+ ]),
623
+ ]) : m('div.p-8.text-center.text-gray-500', 'Loading security audit...'),
624
+ ]) : null,
625
+
626
+ // BAN IP MODAL
627
+ self.showBanModal ? m('div.fixed.inset-0.z-50.flex.items-center.justify-center.bg-black/60.p-4', [
628
+ m('div.bg-white.dark:bg-gray-800.rounded-2xl.border.border-gray-200.dark:border-gray-700.max-w-md.w-full.p-6.shadow-2xl.space-y-4', [
629
+ m('div.flex.items-center.justify-between', [
630
+ m('h2.text-lg.font-bold.text-gray-900.dark:text-white', 'Ban IP Address'),
631
+ m('button.text-gray-400.hover:text-gray-600.dark:hover:text-gray-200.text-xl', {
632
+ onclick: function() { self.showBanModal = false; },
633
+ }, '✕'),
634
+ ]),
635
+ m('div.space-y-3', [
636
+ m('div', [
637
+ m('label.block.text-xs.font-medium.text-gray-700.dark:text-gray-300.mb-1', 'IP Address (IPv4 or IPv6)'),
638
+ m('input.w-full.px-3.py-2.text-sm.rounded-lg.border.border-gray-300.dark:border-gray-600.bg-white.dark:bg-gray-700.text-gray-900.dark:text-white', {
639
+ placeholder: 'e.g. 198.51.100.42',
640
+ value: self.banModalData.ip,
641
+ oninput: function(e) { self.banModalData.ip = e.target.value; },
642
+ }),
643
+ ]),
644
+ m('div', [
645
+ m('label.block.text-xs.font-medium.text-gray-700.dark:text-gray-300.mb-1', 'Reason'),
646
+ m('input.w-full.px-3.py-2.text-sm.rounded-lg.border.border-gray-300.dark:border-gray-600.bg-white.dark:bg-gray-700.text-gray-900.dark:text-white', {
647
+ placeholder: 'e.g. Automated scanning or malicious abuse',
648
+ value: self.banModalData.reason,
649
+ oninput: function(e) { self.banModalData.reason = e.target.value; },
650
+ }),
651
+ ]),
652
+ m('div', [
653
+ m('label.block.text-xs.font-medium.text-gray-700.dark:text-gray-300.mb-1', 'Ban Duration'),
654
+ m('select.w-full.px-3.py-2.text-sm.rounded-lg.border.border-gray-300.dark:border-gray-600.bg-white.dark:bg-gray-700.text-gray-900.dark:text-white', {
655
+ value: self.banModalData.duration,
656
+ onchange: function(e) { self.banModalData.duration = e.target.value; },
657
+ }, [
658
+ m('option', { value: '1h' }, '1 Hour'),
659
+ m('option', { value: '6h' }, '6 Hours'),
660
+ m('option', { value: '24h' }, '24 Hours (1 Day)'),
661
+ m('option', { value: '7d' }, '7 Days'),
662
+ m('option', { value: '30d' }, '30 Days'),
663
+ m('option', { value: 'permanent' }, 'Permanent'),
664
+ ]),
665
+ ]),
666
+ ]),
667
+ m('div.flex.items-center.justify-end.gap-3.pt-2', [
668
+ m('button.px-4.py-2.text-sm.text-gray-700.dark:text-gray-300.hover:bg-gray-100.dark:hover:bg-gray-700.rounded-lg.transition-colors', {
669
+ onclick: function() { self.showBanModal = false; },
670
+ }, 'Cancel'),
671
+ m('button.px-4.py-2.text-sm.font-semibold.text-white.bg-rose-600.hover:bg-rose-700.rounded-lg.shadow-sm.transition-colors', {
672
+ disabled: self.banModalData.isSubmitting,
673
+ onclick: function() { self.submitBan(); },
674
+ }, self.banModalData.isSubmitting ? 'Banning...' : 'Confirm Ban'),
675
+ ]),
676
+ ]),
677
+ ]) : null,
678
+ ]);
679
+ },
680
+ };
681
+
682
+ return SecurityDashboard;
683
+ })();
684
+ `;
685
+ }
686
+
687
+ module.exports = {
688
+ generateSecurityComponent,
689
+ };