webspresso 0.0.98 → 0.0.99

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 (56) hide show
  1. package/.agents/ADMIN.md +11 -1
  2. package/.agents/AGENTS.md +1 -1
  3. package/.agents/AUTH.md +169 -0
  4. package/.agents/ORM.md +88 -4
  5. package/.agents/PLUGINS.md +73 -5
  6. package/.agents/QUEUE.md +84 -0
  7. package/.agents/ROUTING.md +127 -7
  8. package/AGENTS.md +1 -0
  9. package/core/auth/index.js +60 -0
  10. package/core/auth/manager.js +134 -0
  11. package/core/auth/middleware.js +88 -0
  12. package/core/auth/oauth.js +517 -0
  13. package/core/auth/qrcode.js +499 -0
  14. package/core/auth/totp.js +504 -0
  15. package/core/orm/eager-loader.js +144 -10
  16. package/core/orm/index.js +8 -0
  17. package/core/orm/model.js +4 -0
  18. package/core/orm/query-builder.js +90 -4
  19. package/core/orm/repository.js +44 -3
  20. package/core/orm/search.js +213 -0
  21. package/core/queue/cron.js +331 -0
  22. package/core/queue/index.js +8 -1
  23. package/core/queue/manager.js +82 -1
  24. package/core/queue/scheduler.js +359 -0
  25. package/core/sse/index.js +19 -0
  26. package/core/sse/stream.js +430 -0
  27. package/index.d.ts +528 -0
  28. package/index.js +44 -0
  29. package/package.json +1 -1
  30. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +323 -31
  31. package/plugins/admin-panel/client/parts/08-record-list.js +5 -0
  32. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  33. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  34. package/plugins/admin-panel/field-renderers/index.js +2 -0
  35. package/plugins/api-key/crypto.js +101 -0
  36. package/plugins/api-key/index.js +288 -0
  37. package/plugins/api-key/middleware.js +461 -0
  38. package/plugins/api-key/migration-template.js +48 -0
  39. package/plugins/api-key/model.js +50 -0
  40. package/plugins/api-key/scopes.js +125 -0
  41. package/plugins/audit-log/admin-component.js +487 -70
  42. package/plugins/audit-log/api-handlers.js +114 -0
  43. package/plugins/audit-log/index.js +24 -0
  44. package/plugins/file-manager/admin-component.js +20 -4
  45. package/plugins/file-manager/index.js +21 -4
  46. package/plugins/index.js +6 -0
  47. package/plugins/oauth/index.js +338 -0
  48. package/plugins/queue/index.js +4 -0
  49. package/src/api/api-loader.js +1 -0
  50. package/src/discovery/file-route-parser.js +44 -1
  51. package/src/discovery/scan-routes.js +2 -2
  52. package/src/file-router.js +137 -100
  53. package/src/helpers.js +9 -0
  54. package/src/routing/cascading.js +378 -0
  55. package/src/routing/middleware-resolver.js +92 -0
  56. package/src/server.js +6 -0
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Audit log admin page — Mithril component source (string)
3
+ * Features dual Table & Timeline View (Zaman Tüneli) with record focus & diff metadata
3
4
  * @module plugins/audit-log/admin-component
4
5
  */
5
6
 
@@ -14,6 +15,116 @@ function generateAuditLogComponent(options = {}) {
14
15
  (function() {
15
16
  var API = '${apiPrefix}';
16
17
 
18
+ function formatRelativeTime(dateStr) {
19
+ if (!dateStr) return '';
20
+ try {
21
+ var d = new Date(dateStr);
22
+ var now = new Date();
23
+ var diffMs = now - d;
24
+ var diffSec = Math.floor(diffMs / 1000);
25
+ var diffMin = Math.floor(diffSec / 60);
26
+ var diffHour = Math.floor(diffMin / 60);
27
+ var diffDay = Math.floor(diffHour / 24);
28
+
29
+ if (diffSec < 60) return 'Az önce';
30
+ if (diffMin < 60) return diffMin + ' dk önce';
31
+ if (diffHour < 24) return diffHour + ' sa önce';
32
+ if (diffDay === 1) return 'Dün';
33
+ if (diffDay < 30) return diffDay + ' gün önce';
34
+ return d.toLocaleDateString();
35
+ } catch (e) {
36
+ return dateStr;
37
+ }
38
+ }
39
+
40
+ function getActionTheme(action) {
41
+ switch (action) {
42
+ case 'create':
43
+ return {
44
+ label: 'OLUŞTURULDU',
45
+ color: 'emerald',
46
+ badgeClass: 'bg-emerald-50 text-emerald-700 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800',
47
+ dotClass: 'bg-emerald-500 text-white ring-4 ring-emerald-100 dark:ring-emerald-950/60',
48
+ icon: 'plus',
49
+ };
50
+ case 'update':
51
+ return {
52
+ label: 'GÜNCELLENDİ',
53
+ color: 'blue',
54
+ badgeClass: 'bg-blue-50 text-blue-700 border-blue-200 dark:bg-blue-950/40 dark:text-blue-300 dark:border-blue-800',
55
+ dotClass: 'bg-blue-500 text-white ring-4 ring-blue-100 dark:ring-blue-950/60',
56
+ icon: 'pencil',
57
+ };
58
+ case 'delete':
59
+ return {
60
+ label: 'SİLİNDİ',
61
+ color: 'rose',
62
+ badgeClass: 'bg-rose-50 text-rose-700 border-rose-200 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-800',
63
+ dotClass: 'bg-rose-500 text-white ring-4 ring-rose-100 dark:ring-rose-950/60',
64
+ icon: 'trash',
65
+ };
66
+ case 'restore':
67
+ return {
68
+ label: 'GERİ YÜKLENDİ',
69
+ color: 'amber',
70
+ badgeClass: 'bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-800',
71
+ dotClass: 'bg-amber-500 text-white ring-4 ring-amber-100 dark:ring-amber-950/60',
72
+ icon: 'undo',
73
+ };
74
+ default:
75
+ return {
76
+ label: (action || 'İŞLEM').toUpperCase(),
77
+ color: 'gray',
78
+ badgeClass: 'bg-gray-100 text-gray-700 border-gray-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700',
79
+ dotClass: 'bg-gray-500 text-white ring-4 ring-gray-100 dark:ring-slate-800',
80
+ icon: 'activity',
81
+ };
82
+ }
83
+ }
84
+
85
+ function renderSvgIcon(name, cls) {
86
+ cls = cls || 'w-4 h-4';
87
+ if (name === 'plus') {
88
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
89
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2.5', d: 'M12 4v16m8-8H4' })
90
+ );
91
+ }
92
+ if (name === 'pencil') {
93
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
94
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z' })
95
+ );
96
+ }
97
+ if (name === 'trash') {
98
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
99
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16' })
100
+ );
101
+ }
102
+ if (name === 'undo') {
103
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
104
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M3 10h10a5 5 0 015 5v2m-15-7l4-4m-4 4l4 4' })
105
+ );
106
+ }
107
+ if (name === 'clock') {
108
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
109
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z' })
110
+ );
111
+ }
112
+ if (name === 'table') {
113
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
114
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M3 10h18M3 14h18m-9-4v8m-7 4h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z' })
115
+ );
116
+ }
117
+ if (name === 'search') {
118
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
119
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z' })
120
+ );
121
+ }
122
+ // Default activity
123
+ return m('svg', { class: cls, fill: 'none', viewBox: '0 0 24 24', stroke: 'currentColor' },
124
+ m('path', { 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'stroke-width': '2', d: 'M13 10V3L4 14h7v7l9-11h-7z' })
125
+ );
126
+ }
127
+
17
128
  function AuditLogPage() {
18
129
  var rows = [];
19
130
  var loading = true;
@@ -21,8 +132,33 @@ function generateAuditLogComponent(options = {}) {
21
132
  var page = 1;
22
133
  var perPage = 25;
23
134
  var total = 0;
135
+ var viewMode = 'timeline'; // 'timeline' | 'table'
136
+
24
137
  var filterModel = '';
25
138
  var filterAction = '';
139
+ var filterRecordId = '';
140
+ var filterSearch = '';
141
+
142
+ var expandedIds = {};
143
+
144
+ function parseQueryParams() {
145
+ try {
146
+ var searchStr = window.location.search || '';
147
+ if (!searchStr && typeof m !== 'undefined' && m.route && m.route.get) {
148
+ var cur = m.route.get();
149
+ if (cur.indexOf('?') !== -1) {
150
+ searchStr = cur.substring(cur.indexOf('?'));
151
+ }
152
+ }
153
+ var params = new URLSearchParams(searchStr);
154
+ if (params.get('model')) filterModel = params.get('model');
155
+ if (params.get('recordId') || params.get('resourceId') || params.get('id')) {
156
+ filterRecordId = params.get('recordId') || params.get('resourceId') || params.get('id');
157
+ }
158
+ if (params.get('action')) filterAction = params.get('action');
159
+ if (params.get('view')) viewMode = params.get('view');
160
+ } catch (e) {}
161
+ }
26
162
 
27
163
  function load() {
28
164
  loading = true;
@@ -30,6 +166,9 @@ function generateAuditLogComponent(options = {}) {
30
166
  var q = '?page=' + page + '&perPage=' + perPage;
31
167
  if (filterModel) q += '&model=' + encodeURIComponent(filterModel);
32
168
  if (filterAction) q += '&action=' + encodeURIComponent(filterAction);
169
+ if (filterRecordId) q += '&recordId=' + encodeURIComponent(filterRecordId);
170
+ if (filterSearch) q += '&search=' + encodeURIComponent(filterSearch);
171
+
33
172
  api.get(API + q).then(function(res) {
34
173
  rows = res.data || [];
35
174
  total = (res.meta && res.meta.total) || 0;
@@ -42,89 +181,367 @@ function generateAuditLogComponent(options = {}) {
42
181
  });
43
182
  }
44
183
 
184
+ function toggleExpand(id) {
185
+ expandedIds[id] = !expandedIds[id];
186
+ }
187
+
188
+ function focusRecord(model, id) {
189
+ filterModel = model || '';
190
+ filterRecordId = id != null ? String(id) : '';
191
+ page = 1;
192
+ viewMode = 'timeline';
193
+ load();
194
+ }
195
+
196
+ function clearFocus() {
197
+ filterRecordId = '';
198
+ page = 1;
199
+ load();
200
+ }
201
+
202
+ function clearAllFilters() {
203
+ filterModel = '';
204
+ filterAction = '';
205
+ filterRecordId = '';
206
+ filterSearch = '';
207
+ page = 1;
208
+ load();
209
+ }
210
+
45
211
  return {
46
- oninit: load,
212
+ oninit: function() {
213
+ parseQueryParams();
214
+ load();
215
+ },
47
216
  view: function() {
48
- if (loading) {
49
- return m('div.p-8.text-gray-500', 'Loading…');
50
- }
51
- if (error) {
52
- return m('div.p-8.text-red-600', error);
53
- }
54
217
  var maxPage = Math.max(1, Math.ceil(total / perPage) || 1);
55
- return m('div.p-6.space-y-4', [
56
- m('div.flex.flex-wrap.items-end.gap-4', [
218
+
219
+ return m('div.p-6.max-w-7xl.mx-auto.space-y-6', [
220
+ // 1. Header with View Toggle & Purge
221
+ m('div.flex.flex-col.md:flex-row.md:items-center.justify-between.gap-4.pb-4.border-b.border-gray-200.dark:border-slate-800', [
57
222
  m('div', [
58
- m('label.block.text-xs.text-gray-500.mb-1', 'Model'),
59
- m('input.border.rounded.px-2.py-1', {
60
- value: filterModel,
61
- oninput: function(e) { filterModel = e.target.value; },
62
- placeholder: 'Post',
63
- }),
223
+ m('div.flex.items-center.gap-2.5', [
224
+ m('span.p-2.rounded-lg.bg-indigo-50.text-indigo-600.dark:bg-indigo-950/40.dark:text-indigo-400',
225
+ renderSvgIcon('clock', 'w-5 h-5')
226
+ ),
227
+ m('h1.text-2xl.font-bold.text-gray-900.dark:text-slate-100', 'Denetim Günlüğü & Zaman Tüneli'),
228
+ ]),
229
+ m('p.text-sm.text-gray-500.dark:text-slate-400.mt-1',
230
+ 'Tüm yönetici CRUD işlemlerinin ve model mutasyonlarının denetim geçmişi.'
231
+ ),
64
232
  ]),
65
- m('div', [
66
- m('label.block.text-xs.text-gray-500.mb-1', 'Action'),
67
- m('select.border.rounded.px-2.py-1', {
68
- value: filterAction,
69
- onchange: function(e) { filterAction = e.target.value; },
70
- }, [
71
- m('option', { value: '' }, 'All'),
72
- m('option', { value: 'create' }, 'create'),
73
- m('option', { value: 'update' }, 'update'),
74
- m('option', { value: 'delete' }, 'delete'),
75
- m('option', { value: 'restore' }, 'restore'),
233
+
234
+ m('div.flex.items-center.gap-3', [
235
+ // View Switcher (Timeline vs Table)
236
+ m('div.inline-flex.rounded-lg.border.border-gray-200.dark:border-slate-700.p-1.bg-gray-50.dark:bg-slate-900', [
237
+ m('button.px-3.py-1.5.text-xs.font-medium.rounded-md.flex.items-center.gap-1.5.transition-all', {
238
+ class: viewMode === 'timeline'
239
+ ? 'bg-white dark:bg-slate-800 text-indigo-600 dark:text-indigo-400 shadow-sm'
240
+ : 'text-gray-600 dark:text-slate-400 hover:text-gray-900 dark:hover:text-slate-200',
241
+ onclick: function() { viewMode = 'timeline'; }
242
+ }, [
243
+ renderSvgIcon('clock', 'w-3.5 h-3.5'),
244
+ m('span', 'Zaman Tüneli'),
245
+ ]),
246
+ m('button.px-3.py-1.5.text-xs.font-medium.rounded-md.flex.items-center.gap-1.5.transition-all', {
247
+ class: viewMode === 'table'
248
+ ? 'bg-white dark:bg-slate-800 text-indigo-600 dark:text-indigo-400 shadow-sm'
249
+ : 'text-gray-600 dark:text-slate-400 hover:text-gray-900 dark:hover:text-slate-200',
250
+ onclick: function() { viewMode = 'table'; }
251
+ }, [
252
+ renderSvgIcon('table', 'w-3.5 h-3.5'),
253
+ m('span', 'Tablo'),
254
+ ]),
76
255
  ]),
256
+
257
+ // Purge button
258
+ m('button.px-3.py-1.5.text-xs.font-medium.border.border-rose-200.dark:border-rose-900.text-rose-700.dark:text-rose-400.rounded-lg.hover:bg-rose-50.dark:hover:bg-rose-950/40.transition-colors', {
259
+ onclick: function() {
260
+ if (!confirm('90 günden eski denetim günlükleri silinsin mi?')) return;
261
+ api.post(API + '/purge', { days: 90 }).then(function() {
262
+ page = 1;
263
+ load();
264
+ }).catch(function(e) {
265
+ alert('Temizleme hatası: ' + (e.message || String(e)));
266
+ });
267
+ },
268
+ }, '90 Gün Öncesini Temizle'),
77
269
  ]),
78
- m('button.bg-blue-600.text-white.px-4.py-1.rounded', {
79
- onclick: function() { page = 1; load(); },
80
- }, 'Apply'),
81
- m('button.border.px-3.py-1.rounded.text-red-700.ml-auto', {
82
- onclick: function() {
83
- if (!confirm('Delete audit logs older than 90 days?')) return;
84
- api.post(API + '/purge', { days: 90 }).then(function() {
85
- page = 1;
86
- load();
87
- }).catch(function(e) {
88
- alert('Purge failed: ' + (e.message || String(e)));
89
- });
90
- },
91
- }, 'Purge 90d'),
92
270
  ]),
93
- m('div.text-sm.text-gray-500', 'Total: ' + total),
94
- m('div.overflow-x-auto.border.rounded', [
95
- m('table.min-w-full.text-sm', [
96
- m('thead.bg-gray-50', [
97
- m('tr', [
98
- m('th.text-left.p-2', 'Time'),
99
- m('th.text-left.p-2', 'Actor'),
100
- m('th.text-left.p-2', 'Action'),
101
- m('th.text-left.p-2', 'Model'),
102
- m('th.text-left.p-2', 'Id'),
103
- m('th.text-left.p-2', 'Path'),
271
+
272
+ // 2. Focused Record Banner (if filterRecordId is active)
273
+ filterRecordId ? m('div.p-4.rounded-xl.bg-gradient-to-r.from-indigo-50.to-blue-50.dark:from-indigo-950/40.dark:to-blue-950/40.border.border-indigo-200.dark:border-indigo-800.flex.items-center.justify-between.gap-4', [
274
+ m('div.flex.items-center.gap-3', [
275
+ m('span.w-9.h-9.rounded-full.bg-indigo-600.text-white.flex.items-center.justify-center.font-bold.text-sm', '📌'),
276
+ m('div', [
277
+ m('h3.text-sm.font-semibold.text-indigo-950.dark:text-indigo-200',
278
+ (filterModel ? filterModel + ' #' : 'Kayıt #') + filterRecordId + ' İçin Değişiklik Zaman Tüneli'
279
+ ),
280
+ m('p.text-xs.text-indigo-700.dark:text-indigo-400',
281
+ 'Bu kayda ait tüm ekleme, güncelleme ve silme geçmişi filtreleniyor.'
282
+ ),
283
+ ]),
284
+ ]),
285
+ m('button.px-3.py-1.5.text-xs.font-medium.bg-white.dark:bg-slate-800.text-indigo-700.dark:text-indigo-300.border.border-indigo-200.dark:border-indigo-700.rounded-lg.hover:bg-indigo-50.dark:hover:bg-indigo-900/60.transition-colors', {
286
+ onclick: clearFocus,
287
+ }, '✕ Filtreyi Kaldır'),
288
+ ]) : null,
289
+
290
+ // 3. Search & Filter Controls Card
291
+ m('div.p-4.rounded-xl.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.shadow-sm.space-y-4', [
292
+ m('div.grid.grid-cols-1.sm:grid-cols-2.lg:grid-cols-5.gap-3.items-end', [
293
+ // Model Filter
294
+ m('div', [
295
+ m('label.block.text-xs.font-medium.text-gray-600.dark:text-slate-400.mb-1', 'Model'),
296
+ m('input.w-full.border.border-gray-300.dark:border-slate-600.bg-white.dark:bg-slate-900.text-gray-900.dark:text-slate-100.rounded-lg.px-3.py-1.5.text-sm.focus:ring-2.focus:ring-indigo-500.focus:outline-none', {
297
+ value: filterModel,
298
+ placeholder: 'Örn. Post, User',
299
+ oninput: function(e) { filterModel = e.target.value; },
300
+ onkeydown: function(e) { if (e.key === 'Enter') { page = 1; load(); } },
301
+ }),
302
+ ]),
303
+
304
+ // Record ID Filter
305
+ m('div', [
306
+ m('label.block.text-xs.font-medium.text-gray-600.dark:text-slate-400.mb-1', 'Kayıt ID'),
307
+ m('input.w-full.border.border-gray-300.dark:border-slate-600.bg-white.dark:bg-slate-900.text-gray-900.dark:text-slate-100.rounded-lg.px-3.py-1.5.text-sm.focus:ring-2.focus:ring-indigo-500.focus:outline-none', {
308
+ value: filterRecordId,
309
+ placeholder: 'Örn. 42',
310
+ oninput: function(e) { filterRecordId = e.target.value; },
311
+ onkeydown: function(e) { if (e.key === 'Enter') { page = 1; load(); } },
312
+ }),
313
+ ]),
314
+
315
+ // Action Filter
316
+ m('div', [
317
+ m('label.block.text-xs.font-medium.text-gray-600.dark:text-slate-400.mb-1', 'İşlem Türü'),
318
+ m('select.w-full.border.border-gray-300.dark:border-slate-600.bg-white.dark:bg-slate-900.text-gray-900.dark:text-slate-100.rounded-lg.px-3.py-1.5.text-sm.focus:ring-2.focus:ring-indigo-500.focus:outline-none', {
319
+ value: filterAction,
320
+ onchange: function(e) { filterAction = e.target.value; page = 1; load(); },
321
+ }, [
322
+ m('option', { value: '' }, 'Tüm İşlemler'),
323
+ m('option', { value: 'create' }, 'Oluşturma (create)'),
324
+ m('option', { value: 'update' }, 'Güncelleme (update)'),
325
+ m('option', { value: 'delete' }, 'Silme (delete)'),
326
+ m('option', { value: 'restore' }, 'Geri Yükleme (restore)'),
104
327
  ]),
105
328
  ]),
106
- m('tbody', rows.map(function(r) {
107
- return m('tr.border-t', [
108
- m('td.p-2.whitespace-nowrap', r.created_at || ''),
109
- m('td.p-2', (r.actor_email || '') + (r.actor_id != null ? ' #' + r.actor_id : '')),
110
- m('td.p-2', r.action),
111
- m('td.p-2', r.resource_model),
112
- m('td.p-2', r.resource_id != null ? String(r.resource_id) : '—'),
113
- m('td.p-2.max-w-md.truncate', { title: r.path || '' }, r.path || ''),
329
+
330
+ // Search Filter (Actor, Path)
331
+ m('div', [
332
+ m('label.block.text-xs.font-medium.text-gray-600.dark:text-slate-400.mb-1', 'Arama'),
333
+ m('div.relative', [
334
+ m('input.w-full.border.border-gray-300.dark:border-slate-600.bg-white.dark:bg-slate-900.text-gray-900.dark:text-slate-100.rounded-lg.pl-8.pr-3.py-1.5.text-sm.focus:ring-2.focus:ring-indigo-500.focus:outline-none', {
335
+ value: filterSearch,
336
+ placeholder: 'E-posta, IP veya yol...',
337
+ oninput: function(e) { filterSearch = e.target.value; },
338
+ onkeydown: function(e) { if (e.key === 'Enter') { page = 1; load(); } },
339
+ }),
340
+ m('span.absolute.left-2.5.top-2.text-gray-400', renderSvgIcon('search', 'w-4 h-4')),
341
+ ]),
342
+ ]),
343
+
344
+ // Action Buttons
345
+ m('div.flex.items-center.gap-2', [
346
+ m('button.flex-1.bg-indigo-600.text-white.px-4.py-1.5.rounded-lg.text-sm.font-medium.hover:bg-indigo-700.transition-colors.shadow-sm', {
347
+ onclick: function() { page = 1; load(); },
348
+ }, 'Apply'),
349
+ m('button.border.border-gray-300.dark:border-slate-600.text-gray-700.dark:text-slate-300.px-3.py-1.5.rounded-lg.text-sm.hover:bg-gray-100.dark:hover:bg-slate-700.transition-colors', {
350
+ title: 'Clear Filters',
351
+ onclick: clearAllFilters,
352
+ }, 'Clear'),
353
+ ]),
354
+ ]),
355
+ ]),
356
+
357
+ // Total Counter (required by Playwright E2E: getByText(/Total:/))
358
+ m('div.text-sm.text-gray-500.dark:text-slate-400', 'Total: ' + total),
359
+
360
+ // 4. Main Content Area (Timeline vs Table)
361
+ loading ? m('div.py-16.text-center.space-y-3', [
362
+ m('div.inline-block.animate-spin.rounded-full.h-8.w-8.border-4.border-indigo-600.border-t-transparent'),
363
+ m('p.text-sm.text-gray-500.dark:text-slate-400', 'Denetim kayıtları yükleniyor...'),
364
+ ]) : error ? m('div.p-6.rounded-xl.bg-red-50.dark:bg-red-950/40.border.border-red-200.dark:border-red-800.text-red-700.dark:text-red-300', [
365
+ m('h4.font-semibold.text-sm', 'Kayıtlar Yüklenemedi'),
366
+ m('p.text-xs.mt-1', error),
367
+ ]) : rows.length === 0 ? m('div.py-16.text-center.bg-white.dark:bg-slate-800.rounded-xl.border.border-dashed.border-gray-300.dark:border-slate-700.p-8', [
368
+ m('div.w-12.h-12.mx-auto.rounded-full.bg-gray-100.dark:bg-slate-700.flex.items-center.justify-center.text-gray-400.mb-3',
369
+ renderSvgIcon('clock', 'w-6 h-6')
370
+ ),
371
+ m('h3.text-base.font-medium.text-gray-900.dark:text-slate-100', 'Kayıt Bulunamadı'),
372
+ m('p.text-sm.text-gray-500.dark:text-slate-400.mt-1', 'Seçilen filtrelere uygun herhangi bir denetim kaydı bulunamadı.'),
373
+ (filterModel || filterAction || filterRecordId || filterSearch) ? m('button.mt-4.text-xs.text-indigo-600.dark:text-indigo-400.hover:underline', {
374
+ onclick: clearAllFilters,
375
+ }, 'Filtreleri temizle ve tekrar dene') : null,
376
+ ]) : viewMode === 'timeline' ? (
377
+ // ==================== TIMELINE VIEW ====================
378
+ m('div.space-y-6', [
379
+ m('div.relative.pl-6.border-l-2.border-indigo-200.dark:border-slate-700.ml-4.space-y-8', rows.map(function(r) {
380
+ var theme = getActionTheme(r.action);
381
+ var isExpanded = !!expandedIds[r.id];
382
+ var metadata = r.metadata;
383
+ if (typeof metadata === 'string') {
384
+ try { metadata = JSON.parse(metadata); } catch (e) {}
385
+ }
386
+ var changedFields = metadata && Array.isArray(metadata.changedFields) ? metadata.changedFields : [];
387
+
388
+ return m('div.relative.group', [
389
+ // Spine Node Dot
390
+ m('span.absolute.-left-[33px].top-1.5.flex.items-center.justify-center.w-7.h-7.rounded-full.shadow-sm', {
391
+ class: theme.dotClass,
392
+ }, renderSvgIcon(theme.icon, 'w-3.5 h-3.5')),
393
+
394
+ // Event Card
395
+ m('div.rounded-xl.bg-white.dark:bg-slate-800.border.border-gray-200.dark:border-slate-700.p-5.shadow-sm.hover:shadow-md.transition-shadow.space-y-3', [
396
+ // Card Header
397
+ m('div.flex.flex-wrap.items-center.justify-between.gap-2', [
398
+ m('div.flex.items-center.gap-2.5', [
399
+ // Action Badge
400
+ m('span.px-2.5.py-0.5.text-xs.font-semibold.rounded-full.border', {
401
+ class: theme.badgeClass,
402
+ }, theme.label),
403
+
404
+ // Model & ID Tag (Clickable to focus)
405
+ m('button.px-2.5.py-0.5.text-xs.font-medium.bg-gray-100.dark:bg-slate-700.text-gray-800.dark:text-slate-200.rounded-md.hover:bg-indigo-100.dark:hover:bg-indigo-900/60.hover:text-indigo-700.transition-colors.flex.items-center.gap-1', {
406
+ title: 'Bu kaydın tüm geçmişini zaman tünelinde göster',
407
+ onclick: function() { focusRecord(r.resource_model, r.resource_id); },
408
+ }, [
409
+ m('span.font-semibold', r.resource_model),
410
+ r.resource_id != null ? m('span.text-indigo-600.dark:text-indigo-400', '#' + r.resource_id) : null,
411
+ ]),
412
+ ]),
413
+
414
+ // Timestamp & Relative Time
415
+ m('div.text-xs.text-gray-500.dark:text-slate-400.flex.items-center.gap-1.5', [
416
+ m('span.font-medium.text-gray-700.dark:text-slate-300', formatRelativeTime(r.created_at)),
417
+ m('span', '•'),
418
+ m('span', r.created_at || ''),
419
+ ]),
420
+ ]),
421
+
422
+ // Actor Identity Row
423
+ m('div.flex.items-center.justify-between.text-xs.pt-1', [
424
+ m('div.flex.items-center.gap-2', [
425
+ m('span.w-6.h-6.rounded-full.bg-slate-200.dark:bg-slate-700.text-slate-700.dark:text-slate-300.font-bold.flex.items-center.justify-center.text-[10px]',
426
+ ((r.actor_email && r.actor_email[0]) || 'A').toUpperCase()
427
+ ),
428
+ m('span.font-medium.text-gray-900.dark:text-slate-100', r.actor_email || 'Sistem / Otomasyon'),
429
+ r.actor_id != null ? m('span.text-gray-400', '(ID: ' + r.actor_id + ')') : null,
430
+ ]),
431
+
432
+ r.ip ? m('span.px-2.py-0.5.rounded.bg-gray-50.dark:bg-slate-900.text-gray-500.border.border-gray-200.dark:border-slate-700.font-mono.text-[11px]', r.ip) : null,
433
+ ]),
434
+
435
+ // Changes / Mutated Fields Summary
436
+ r.action === 'update' && changedFields.length > 0 ? m('div.pt-1.space-y-1.5', [
437
+ m('span.text-xs.font-medium.text-gray-500.dark:text-slate-400', 'Değiştirilen Alanlar:'),
438
+ m('div.flex.flex-wrap.gap-1.5', changedFields.map(function(f) {
439
+ return m('span.px-2.py-0.5.rounded-md.bg-blue-50.text-blue-700.dark:bg-blue-950/40.dark:text-blue-300.border.border-blue-200.dark:border-blue-800.text-xs.font-mono', f);
440
+ })),
441
+ ]) : r.action === 'create' ? m('p.text-xs.text-gray-600.dark:text-slate-300',
442
+ 'Kayıt başarıyla oluşturuldu ve veritabanına eklendi.'
443
+ ) : r.action === 'delete' ? m('p.text-xs.text-rose-600.dark:text-rose-400',
444
+ 'Kayıt veritabanından silindi (soft delete/hard delete).'
445
+ ) : r.action === 'restore' ? m('p.text-xs.text-amber-600.dark:text-amber-400',
446
+ 'Silinmiş kayıt başarıyla geri yüklendi.'
447
+ ) : null,
448
+
449
+ // Details Toggle Accordion
450
+ m('div.pt-2.border-t.border-gray-100.dark:border-slate-750', [
451
+ m('button.text-xs.text-indigo-600.dark:text-indigo-400.hover:underline.flex.items-center.gap-1', {
452
+ onclick: function() { toggleExpand(r.id); }
453
+ }, [
454
+ m('span', isExpanded ? '▲ Teknik Detayları Gizle' : '▼ Teknik Detayları Göster'),
455
+ ]),
456
+
457
+ isExpanded ? m('div.mt-3.p-3.rounded-lg.bg-gray-50.dark:bg-slate-900.border.border-gray-200.dark:border-slate-700.text-xs.space-y-2', [
458
+ m('div.grid.grid-cols-1.md:grid-cols-2.gap-2', [
459
+ m('div', [
460
+ m('span.font-semibold.text-gray-600.dark:text-slate-400', 'Yol: '),
461
+ m('span.font-mono.text-gray-800.dark:text-slate-200', (r.http_method || '') + ' ' + (r.path || '—')),
462
+ ]),
463
+ m('div', [
464
+ m('span.font-semibold.text-gray-600.dark:text-slate-400', 'Tarayıcı / Agent: '),
465
+ m('span.truncate.text-gray-800.dark:text-slate-200.inline-block.max-w-xs', { title: r.user_agent || '' }, r.user_agent || '—'),
466
+ ]),
467
+ ]),
468
+ metadata ? m('div.pt-1', [
469
+ m('span.font-semibold.text-gray-600.dark:text-slate-400.block.mb-1', 'Metadata:'),
470
+ m('pre.p-2.rounded.bg-gray-900.text-emerald-400.font-mono.text-[11px].overflow-x-auto',
471
+ JSON.stringify(metadata, null, 2)
472
+ ),
473
+ ]) : null,
474
+ ]) : null,
475
+ ]),
476
+ ]),
114
477
  ]);
115
478
  })),
479
+ ])
480
+ ) : (
481
+ // ==================== TABLE VIEW ====================
482
+ m('div.overflow-x-auto.border.border-gray-200.dark:border-slate-700.rounded-xl.bg-white.dark:bg-slate-800.shadow-sm', [
483
+ m('table.min-w-full.text-sm', [
484
+ m('thead.bg-gray-50.dark:bg-slate-900.border-b.border-gray-200.dark:border-slate-700', [
485
+ m('tr', [
486
+ m('th.text-left.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'Zaman'),
487
+ m('th.text-left.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'Kullanıcı'),
488
+ m('th.text-left.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'İşlem'),
489
+ m('th.text-left.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'Model'),
490
+ m('th.text-left.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'Kayıt ID'),
491
+ m('th.text-left.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'Yol'),
492
+ m('th.text-right.p-3.font-semibold.text-gray-700.dark:text-slate-300', 'Zaman Tüneli'),
493
+ ]),
494
+ ]),
495
+ m('tbody.divide-y.divide-gray-100.dark:divide-slate-700', rows.map(function(r) {
496
+ var theme = getActionTheme(r.action);
497
+ return m('tr.hover:bg-gray-50.dark:hover:bg-slate-750.transition-colors', [
498
+ m('td.p-3.whitespace-nowrap.text-gray-600.dark:text-slate-300', [
499
+ m('span.block.font-medium.text-gray-900.dark:text-slate-100', formatRelativeTime(r.created_at)),
500
+ m('span.text-xs.text-gray-400', r.created_at || ''),
501
+ ]),
502
+ m('td.p-3.text-gray-800.dark:text-slate-200', [
503
+ m('span.block.font-medium', r.actor_email || 'Sistem'),
504
+ r.actor_id != null ? m('span.text-xs.text-gray-400', '#' + r.actor_id) : null,
505
+ ]),
506
+ m('td.p-3', [
507
+ m('span.px-2.py-0.5.text-xs.font-semibold.rounded-full.border', {
508
+ class: theme.badgeClass,
509
+ }, r.action),
510
+ ]),
511
+ m('td.p-3.font-medium.text-gray-900.dark:text-slate-100', r.resource_model),
512
+ m('td.p-3.text-gray-600.dark:text-slate-300', r.resource_id != null ? '#' + r.resource_id : '—'),
513
+ m('td.p-3.max-w-xs.truncate.text-xs.font-mono.text-gray-500.dark:text-slate-400', { title: r.path || '' }, r.path || ''),
514
+ m('td.p-3.text-right', [
515
+ m('button.px-2.5.py-1.text-xs.font-medium.text-indigo-600.dark:text-indigo-400.hover:bg-indigo-50.dark:hover:bg-indigo-950/40.rounded-lg.transition-colors', {
516
+ onclick: function() { focusRecord(r.resource_model, r.resource_id); },
517
+ }, 'Tünelde Gör →'),
518
+ ]),
519
+ ]);
520
+ })),
521
+ ]),
522
+ ])
523
+ ),
524
+
525
+ // 5. Pagination Bar
526
+ m('div.flex.flex-col.sm:flex-row.items-center.justify-between.gap-3.pt-2', [
527
+ m('div.text-xs.text-gray-500.dark:text-slate-400', [
528
+ m('strong.font-semibold.text-gray-800.dark:text-slate-200', total),
529
+ m('span', ' records listed'),
530
+ ]),
531
+
532
+ m('div.flex.items-center.gap-2', [
533
+ m('button.px-3.py-1.5.text-xs.font-medium.border.border-gray-300.dark:border-slate-700.rounded-lg.bg-white.dark:bg-slate-800.text-gray-700.dark:text-slate-200.hover:bg-gray-50.dark:hover:bg-slate-750.disabled:opacity-40.disabled:cursor-not-allowed.transition-colors', {
534
+ disabled: page <= 1,
535
+ onclick: function() { if (page > 1) { page--; load(); } },
536
+ }, 'Prev'),
537
+ m('span.text-xs.font-medium.text-gray-700.dark:text-slate-300.px-2',
538
+ 'Page ' + page + ' / ' + maxPage
539
+ ),
540
+ m('button.px-3.py-1.5.text-xs.font-medium.border.border-gray-300.dark:border-slate-700.rounded-lg.bg-white.dark:bg-slate-800.text-gray-700.dark:text-slate-200.hover:bg-gray-50.dark:hover:bg-slate-750.disabled:opacity-40.disabled:cursor-not-allowed.transition-colors', {
541
+ disabled: page >= maxPage,
542
+ onclick: function() { if (page < maxPage) { page++; load(); } },
543
+ }, 'Next'),
116
544
  ]),
117
- ]),
118
- m('div.flex.items-center.gap-2', [
119
- m('button.px-3.py-1.border.rounded', {
120
- disabled: page <= 1,
121
- onclick: function() { if (page > 1) { page--; load(); } },
122
- }, 'Prev'),
123
- m('span.text-sm', 'Page ' + page + ' / ' + maxPage),
124
- m('button.px-3.py-1.border.rounded', {
125
- disabled: page >= maxPage,
126
- onclick: function() { if (page < maxPage) { page++; load(); } },
127
- }, 'Next'),
128
545
  ]),
129
546
  ]);
130
547
  },