@ngn-net/nestjs-telescope 0.2.2 → 0.2.4
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/dist/telescope.module.js +18 -0
- package/dist/ui/index.html +210 -2
- package/dist/ui/manifest.json +2 -2
- package/dist/watchers/http-service.watcher.d.ts +14 -0
- package/dist/watchers/http-service.watcher.js +95 -0
- package/dist/watchers/model.subscriber.d.ts +14 -0
- package/dist/watchers/model.subscriber.js +70 -0
- package/package.json +1 -1
- package/ui/index.html +210 -2
package/dist/telescope.module.js
CHANGED
|
@@ -33,11 +33,13 @@ const mail_watcher_1 = require("./watchers/mail.watcher");
|
|
|
33
33
|
const log_watcher_1 = require("./watchers/log.watcher");
|
|
34
34
|
const exception_watcher_1 = require("./watchers/exception.watcher");
|
|
35
35
|
const schedule_watcher_1 = require("./watchers/schedule.watcher");
|
|
36
|
+
const http_service_watcher_1 = require("./watchers/http-service.watcher");
|
|
36
37
|
const redis_watcher_1 = require("./watchers/redis.watcher");
|
|
37
38
|
const telescope_jwt_guard_1 = require("./guards/telescope-jwt.guard");
|
|
38
39
|
const command_watcher_1 = require("./watchers/command.watcher");
|
|
39
40
|
const model_watcher_1 = require("./watchers/model.watcher");
|
|
40
41
|
const notification_watcher_1 = require("./watchers/notification.watcher");
|
|
42
|
+
const gate_watcher_1 = require("./watchers/gate.watcher");
|
|
41
43
|
const constants_1 = require("./constants");
|
|
42
44
|
const entry_type_enum_1 = require("./enums/entry-type.enum");
|
|
43
45
|
let TelescopeModule = TelescopeModule_1 = class TelescopeModule {
|
|
@@ -233,6 +235,22 @@ let TelescopeModule = TelescopeModule_1 = class TelescopeModule {
|
|
|
233
235
|
}
|
|
234
236
|
}
|
|
235
237
|
}
|
|
238
|
+
// Additional optional watchers (non-interceptors)
|
|
239
|
+
if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.HTTP_CLIENT)) {
|
|
240
|
+
providers.push(http_service_watcher_1.HttpServiceWatcher);
|
|
241
|
+
}
|
|
242
|
+
if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.COMMAND)) {
|
|
243
|
+
providers.push(command_watcher_1.CommandWatcher);
|
|
244
|
+
}
|
|
245
|
+
if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.MODEL)) {
|
|
246
|
+
providers.push(model_watcher_1.ModelWatcher);
|
|
247
|
+
}
|
|
248
|
+
if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.NOTIFICATION)) {
|
|
249
|
+
providers.push(notification_watcher_1.NotificationWatcher);
|
|
250
|
+
}
|
|
251
|
+
if (!options.enabledEntryTypes || options.enabledEntryTypes.includes(entry_type_enum_1.EntryType.GATE)) {
|
|
252
|
+
providers.push(gate_watcher_1.GateWatcher);
|
|
253
|
+
}
|
|
236
254
|
// 2. CacheWatcher
|
|
237
255
|
try {
|
|
238
256
|
require('@nestjs/cache-manager');
|
package/dist/ui/index.html
CHANGED
|
@@ -487,6 +487,7 @@
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
.badge-request { background-color: rgba(59, 130, 246, 0.12); color: var(--accent-blue); }
|
|
490
|
+
.badge-http_client { background-color: rgba(99, 102, 241, 0.12); color: var(--primary); }
|
|
490
491
|
.badge-query { background-color: rgba(16, 185, 129, 0.12); color: var(--accent-green); }
|
|
491
492
|
.badge-cache { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
492
493
|
.badge-job { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
@@ -496,6 +497,11 @@
|
|
|
496
497
|
.badge-exception { background-color: rgba(244, 63, 94, 0.12); color: var(--accent-red); }
|
|
497
498
|
.badge-scheduled_task { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
498
499
|
.badge-redis { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
500
|
+
.badge-gate { background-color: rgba(244, 63, 94, 0.12); color: var(--accent-red); }
|
|
501
|
+
.badge-command { background-color: rgba(148, 163, 184, 0.15); color: var(--text-main); }
|
|
502
|
+
.badge-model { background-color: rgba(16, 185, 129, 0.12); color: var(--accent-green); }
|
|
503
|
+
.badge-notification { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
504
|
+
.badge-dump { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
499
505
|
|
|
500
506
|
.badge-get { background-color: rgba(16, 185, 129, 0.15); color: var(--accent-green); }
|
|
501
507
|
.badge-post { background-color: rgba(59, 130, 246, 0.15); color: var(--accent-blue); }
|
|
@@ -845,6 +851,7 @@
|
|
|
845
851
|
const NAVIGATION_TABS = [
|
|
846
852
|
{ id: 'all', label: 'All Entries', icon: '🔍' },
|
|
847
853
|
{ id: 'request', label: 'HTTP Requests', icon: '🌐' },
|
|
854
|
+
{ id: 'http_client', label: 'HTTP Client Requests', icon: '📞' },
|
|
848
855
|
{ id: 'query', label: 'Database Queries', icon: '💾' },
|
|
849
856
|
{ id: 'cache', label: 'Cache Ops', icon: '⚡' },
|
|
850
857
|
{ id: 'job', label: 'Queue Jobs', icon: '⚙️' },
|
|
@@ -854,6 +861,11 @@
|
|
|
854
861
|
{ id: 'exception', label: 'Exceptions', icon: '❌' },
|
|
855
862
|
{ id: 'scheduled_task', label: 'Scheduled Tasks', icon: '⏱️' },
|
|
856
863
|
{ id: 'redis', label: 'Redis Commands', icon: '🔑' },
|
|
864
|
+
{ id: 'gate', label: 'Gates', icon: '🚪' },
|
|
865
|
+
{ id: 'command', label: 'Commands', icon: '💻' },
|
|
866
|
+
{ id: 'model', label: 'Models', icon: '📦' },
|
|
867
|
+
{ id: 'notification', label: 'Notifications', icon: '🔔' },
|
|
868
|
+
{ id: 'dump', label: 'Dumps', icon: '🗑️' },
|
|
857
869
|
];
|
|
858
870
|
|
|
859
871
|
function App() {
|
|
@@ -1151,7 +1163,7 @@
|
|
|
1151
1163
|
{/* Stats Grid at the top for quick insights */}
|
|
1152
1164
|
{activeTab === 'all' && (
|
|
1153
1165
|
<div className="stats-grid">
|
|
1154
|
-
{NAVIGATION_TABS.slice(1
|
|
1166
|
+
{NAVIGATION_TABS.slice(1).map((tab) => (
|
|
1155
1167
|
<div
|
|
1156
1168
|
key={tab.id}
|
|
1157
1169
|
className={`stat-card ${activeTab === tab.id ? 'active' : ''}`}
|
|
@@ -1324,7 +1336,7 @@
|
|
|
1324
1336
|
|
|
1325
1337
|
// Determine font family for subtitle
|
|
1326
1338
|
function varMono(type) {
|
|
1327
|
-
return (type === 'query' || type === 'redis' || type === 'request') ? 'var(--font-mono)' : 'var(--font-sans)';
|
|
1339
|
+
return (type === 'query' || type === 'redis' || type === 'request' || type === 'http_client' || type === 'command') ? 'var(--font-mono)' : 'var(--font-sans)';
|
|
1328
1340
|
}
|
|
1329
1341
|
|
|
1330
1342
|
// Helper functions for displaying table items
|
|
@@ -1345,6 +1357,16 @@
|
|
|
1345
1357
|
subtitle: `${req.url || '/'}`,
|
|
1346
1358
|
status: statusBadge
|
|
1347
1359
|
};
|
|
1360
|
+
case 'http_client':
|
|
1361
|
+
return {
|
|
1362
|
+
title: `${content.method || 'GET'}`,
|
|
1363
|
+
subtitle: `${content.url || ''} (${content.duration || 0}ms)`,
|
|
1364
|
+
status: content.responseStatus >= 400 ? (
|
|
1365
|
+
<span className="badge badge-status-err">{content.responseStatus}</span>
|
|
1366
|
+
) : (
|
|
1367
|
+
<span className="badge badge-status-ok">{content.responseStatus || '200'}</span>
|
|
1368
|
+
)
|
|
1369
|
+
};
|
|
1348
1370
|
case 'query':
|
|
1349
1371
|
return {
|
|
1350
1372
|
title: content.query || 'DB Query',
|
|
@@ -1419,6 +1441,40 @@
|
|
|
1419
1441
|
<span className="badge badge-status-ok">success</span>
|
|
1420
1442
|
)
|
|
1421
1443
|
};
|
|
1444
|
+
case 'gate':
|
|
1445
|
+
return {
|
|
1446
|
+
title: `Gate: ${content.gate || ''}`,
|
|
1447
|
+
subtitle: content.user ? `User: ${JSON.stringify(content.user)}` : 'Anonymous',
|
|
1448
|
+
status: content.allowed ? (
|
|
1449
|
+
<span className="badge badge-status-ok">allowed</span>
|
|
1450
|
+
) : (
|
|
1451
|
+
<span className="badge badge-status-err">denied</span>
|
|
1452
|
+
)
|
|
1453
|
+
};
|
|
1454
|
+
case 'command':
|
|
1455
|
+
return {
|
|
1456
|
+
title: `Command: ${content.command || ''}`,
|
|
1457
|
+
subtitle: (content.args || []).join(' '),
|
|
1458
|
+
status: <span className="badge badge-status-ok">{content.exitCode ?? 'executed'}</span>
|
|
1459
|
+
};
|
|
1460
|
+
case 'model':
|
|
1461
|
+
return {
|
|
1462
|
+
title: `${(content.action || '').toUpperCase()} model`,
|
|
1463
|
+
subtitle: `${content.entity || ''} (${content.primaryKey || ''})`,
|
|
1464
|
+
status: <span className="badge badge-status-ok">{content.action}</span>
|
|
1465
|
+
};
|
|
1466
|
+
case 'notification':
|
|
1467
|
+
return {
|
|
1468
|
+
title: `Notification: ${content.notification || ''}`,
|
|
1469
|
+
subtitle: content.timestamp || '',
|
|
1470
|
+
status: <span className="badge badge-status-ok">sent</span>
|
|
1471
|
+
};
|
|
1472
|
+
case 'dump':
|
|
1473
|
+
return {
|
|
1474
|
+
title: `Dump`,
|
|
1475
|
+
subtitle: typeof content.message === 'string' ? content.message : JSON.stringify(content.message),
|
|
1476
|
+
status: <span className="badge badge-status-ok">dumped</span>
|
|
1477
|
+
};
|
|
1422
1478
|
default:
|
|
1423
1479
|
return {
|
|
1424
1480
|
title: 'Telescope Entry',
|
|
@@ -1471,6 +1527,59 @@
|
|
|
1471
1527
|
)}
|
|
1472
1528
|
</React.Fragment>
|
|
1473
1529
|
);
|
|
1530
|
+
case 'http_client':
|
|
1531
|
+
return (
|
|
1532
|
+
<React.Fragment>
|
|
1533
|
+
<div className="key-val-row">
|
|
1534
|
+
<div className="key-val-label">HTTP Method</div>
|
|
1535
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.method}</div>
|
|
1536
|
+
</div>
|
|
1537
|
+
<div className="key-val-row">
|
|
1538
|
+
<div className="key-val-label">URL</div>
|
|
1539
|
+
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)', color: '#818cf8' }}>{content.url}</div>
|
|
1540
|
+
</div>
|
|
1541
|
+
<div className="key-val-row">
|
|
1542
|
+
<div className="key-val-label">Duration</div>
|
|
1543
|
+
<div className="key-val-value">{content.duration} ms</div>
|
|
1544
|
+
</div>
|
|
1545
|
+
{content.requestHeaders && Object.keys(content.requestHeaders).length > 0 && (
|
|
1546
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1547
|
+
<div className="key-val-label">Request Headers</div>
|
|
1548
|
+
<pre className="json-code">{JSON.stringify(content.requestHeaders, null, 2)}</pre>
|
|
1549
|
+
</div>
|
|
1550
|
+
)}
|
|
1551
|
+
{content.requestBody && (
|
|
1552
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1553
|
+
<div className="key-val-label">Request Body</div>
|
|
1554
|
+
<pre className="json-code">{JSON.stringify(content.requestBody, null, 2)}</pre>
|
|
1555
|
+
</div>
|
|
1556
|
+
)}
|
|
1557
|
+
{content.responseStatus !== undefined && (
|
|
1558
|
+
<div className="key-val-row">
|
|
1559
|
+
<div className="key-val-label">Response Status</div>
|
|
1560
|
+
<div className="key-val-value">{content.responseStatus}</div>
|
|
1561
|
+
</div>
|
|
1562
|
+
)}
|
|
1563
|
+
{content.responseHeaders && Object.keys(content.responseHeaders).length > 0 && (
|
|
1564
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1565
|
+
<div className="key-val-label">Response Headers</div>
|
|
1566
|
+
<pre className="json-code">{JSON.stringify(content.responseHeaders, null, 2)}</pre>
|
|
1567
|
+
</div>
|
|
1568
|
+
)}
|
|
1569
|
+
{content.responseBody && (
|
|
1570
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1571
|
+
<div className="key-val-label">Response Body</div>
|
|
1572
|
+
<pre className="json-code">{JSON.stringify(content.responseBody, null, 2)}</pre>
|
|
1573
|
+
</div>
|
|
1574
|
+
)}
|
|
1575
|
+
{content.error && (
|
|
1576
|
+
<div className="key-val-row">
|
|
1577
|
+
<div className="key-val-label">Error</div>
|
|
1578
|
+
<div className="key-val-value" style={{ color: 'var(--accent-red)' }}>{content.error}</div>
|
|
1579
|
+
</div>
|
|
1580
|
+
)}
|
|
1581
|
+
</React.Fragment>
|
|
1582
|
+
);
|
|
1474
1583
|
case 'query':
|
|
1475
1584
|
return (
|
|
1476
1585
|
<React.Fragment>
|
|
@@ -1617,6 +1726,105 @@
|
|
|
1617
1726
|
)}
|
|
1618
1727
|
</React.Fragment>
|
|
1619
1728
|
);
|
|
1729
|
+
case 'gate':
|
|
1730
|
+
return (
|
|
1731
|
+
<React.Fragment>
|
|
1732
|
+
<div className="key-val-row">
|
|
1733
|
+
<div className="key-val-label">Gate Name</div>
|
|
1734
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.gate}</div>
|
|
1735
|
+
</div>
|
|
1736
|
+
<div className="key-val-row">
|
|
1737
|
+
<div className="key-val-label">Result</div>
|
|
1738
|
+
<div className="key-val-value">
|
|
1739
|
+
{content.allowed ? (
|
|
1740
|
+
<span className="badge badge-status-ok">Allowed</span>
|
|
1741
|
+
) : (
|
|
1742
|
+
<span className="badge badge-status-err">Denied</span>
|
|
1743
|
+
)}
|
|
1744
|
+
</div>
|
|
1745
|
+
</div>
|
|
1746
|
+
{content.user && (
|
|
1747
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1748
|
+
<div className="key-val-label">User Context</div>
|
|
1749
|
+
<pre className="json-code">{JSON.stringify(content.user, null, 2)}</pre>
|
|
1750
|
+
</div>
|
|
1751
|
+
)}
|
|
1752
|
+
</React.Fragment>
|
|
1753
|
+
);
|
|
1754
|
+
case 'command':
|
|
1755
|
+
return (
|
|
1756
|
+
<React.Fragment>
|
|
1757
|
+
<div className="key-val-row">
|
|
1758
|
+
<div className="key-val-label">Command</div>
|
|
1759
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff', fontFamily: 'var(--font-mono)' }}>{content.command}</div>
|
|
1760
|
+
</div>
|
|
1761
|
+
{content.args && content.args.length > 0 && (
|
|
1762
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1763
|
+
<div className="key-val-label">Arguments</div>
|
|
1764
|
+
<pre className="json-code">{JSON.stringify(content.args, null, 2)}</pre>
|
|
1765
|
+
</div>
|
|
1766
|
+
)}
|
|
1767
|
+
{content.exitCode !== undefined && (
|
|
1768
|
+
<div className="key-val-row">
|
|
1769
|
+
<div className="key-val-label">Exit Code</div>
|
|
1770
|
+
<div className="key-val-value">{content.exitCode}</div>
|
|
1771
|
+
</div>
|
|
1772
|
+
)}
|
|
1773
|
+
</React.Fragment>
|
|
1774
|
+
);
|
|
1775
|
+
case 'model':
|
|
1776
|
+
return (
|
|
1777
|
+
<React.Fragment>
|
|
1778
|
+
<div className="key-val-row">
|
|
1779
|
+
<div className="key-val-label">Action</div>
|
|
1780
|
+
<div className="key-val-value" style={{ textTransform: 'uppercase', fontWeight: '600', color: '#fff' }}>{content.action}</div>
|
|
1781
|
+
</div>
|
|
1782
|
+
<div className="key-val-row">
|
|
1783
|
+
<div className="key-val-label">Entity</div>
|
|
1784
|
+
<div className="key-val-value">{content.entity}</div>
|
|
1785
|
+
</div>
|
|
1786
|
+
<div className="key-val-row">
|
|
1787
|
+
<div className="key-val-label">Primary Key</div>
|
|
1788
|
+
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)' }}>{String(content.primaryKey)}</div>
|
|
1789
|
+
</div>
|
|
1790
|
+
{content.updatedColumns && content.updatedColumns.length > 0 && (
|
|
1791
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1792
|
+
<div className="key-val-label">Updated Columns</div>
|
|
1793
|
+
<pre className="json-code">{JSON.stringify(content.updatedColumns, null, 2)}</pre>
|
|
1794
|
+
</div>
|
|
1795
|
+
)}
|
|
1796
|
+
{content.data && (
|
|
1797
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1798
|
+
<div className="key-val-label">Model Data</div>
|
|
1799
|
+
<pre className="json-code">{JSON.stringify(content.data, null, 2)}</pre>
|
|
1800
|
+
</div>
|
|
1801
|
+
)}
|
|
1802
|
+
</React.Fragment>
|
|
1803
|
+
);
|
|
1804
|
+
case 'notification':
|
|
1805
|
+
return (
|
|
1806
|
+
<React.Fragment>
|
|
1807
|
+
<div className="key-val-row">
|
|
1808
|
+
<div className="key-val-label">Notification</div>
|
|
1809
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.notification}</div>
|
|
1810
|
+
</div>
|
|
1811
|
+
{content.payload && (
|
|
1812
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1813
|
+
<div className="key-val-label">Payload</div>
|
|
1814
|
+
<pre className="json-code">{JSON.stringify(content.payload, null, 2)}</pre>
|
|
1815
|
+
</div>
|
|
1816
|
+
)}
|
|
1817
|
+
</React.Fragment>
|
|
1818
|
+
);
|
|
1819
|
+
case 'dump':
|
|
1820
|
+
return (
|
|
1821
|
+
<React.Fragment>
|
|
1822
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1823
|
+
<div className="key-val-label">Message / Data</div>
|
|
1824
|
+
<pre className="json-code">{typeof content.message === 'string' ? content.message : JSON.stringify(content.message, null, 2)}</pre>
|
|
1825
|
+
</div>
|
|
1826
|
+
</React.Fragment>
|
|
1827
|
+
);
|
|
1620
1828
|
default:
|
|
1621
1829
|
return (
|
|
1622
1830
|
<div className="key-val-row">
|
package/dist/ui/manifest.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { TelescopeService } from '../telescope.service';
|
|
3
|
+
import { TelescopeOptions } from '../interfaces/telescope-options.interface';
|
|
4
|
+
/**
|
|
5
|
+
* Watches outbound HTTP requests made via NestJS's HttpService (axios based).
|
|
6
|
+
* It patches the HttpService prototype methods to record request/response data.
|
|
7
|
+
* The watcher is optional – it only activates if @nestjs/axios is installed.
|
|
8
|
+
*/
|
|
9
|
+
export declare class HttpServiceWatcher implements OnModuleInit {
|
|
10
|
+
private readonly telescope;
|
|
11
|
+
private readonly options?;
|
|
12
|
+
constructor(telescope: TelescopeService, options?: TelescopeOptions | undefined);
|
|
13
|
+
onModuleInit(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.HttpServiceWatcher = void 0;
|
|
16
|
+
// src/watchers/http-service.watcher.ts
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const telescope_service_1 = require("../telescope.service");
|
|
19
|
+
const entry_type_enum_1 = require("../enums/entry-type.enum");
|
|
20
|
+
const constants_1 = require("../constants");
|
|
21
|
+
/**
|
|
22
|
+
* Watches outbound HTTP requests made via NestJS's HttpService (axios based).
|
|
23
|
+
* It patches the HttpService prototype methods to record request/response data.
|
|
24
|
+
* The watcher is optional – it only activates if @nestjs/axios is installed.
|
|
25
|
+
*/
|
|
26
|
+
let HttpServiceWatcher = class HttpServiceWatcher {
|
|
27
|
+
telescope;
|
|
28
|
+
options;
|
|
29
|
+
constructor(telescope, options) {
|
|
30
|
+
this.telescope = telescope;
|
|
31
|
+
this.options = options;
|
|
32
|
+
}
|
|
33
|
+
onModuleInit() {
|
|
34
|
+
try {
|
|
35
|
+
const { HttpService } = require('@nestjs/axios');
|
|
36
|
+
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
|
|
37
|
+
for (const method of methods) {
|
|
38
|
+
if (typeof HttpService.prototype[method] !== 'function')
|
|
39
|
+
continue;
|
|
40
|
+
const original = HttpService.prototype[method];
|
|
41
|
+
const self = this;
|
|
42
|
+
HttpService.prototype[method] = async function (...args) {
|
|
43
|
+
const url = args[0];
|
|
44
|
+
const config = args[1] ?? {};
|
|
45
|
+
const start = Date.now();
|
|
46
|
+
try {
|
|
47
|
+
const response = await original.apply(this, args);
|
|
48
|
+
const duration = Date.now() - start;
|
|
49
|
+
// Record successful request
|
|
50
|
+
self.telescope
|
|
51
|
+
.record({
|
|
52
|
+
type: entry_type_enum_1.EntryType.HTTP_CLIENT,
|
|
53
|
+
content: {
|
|
54
|
+
method: method.toUpperCase(),
|
|
55
|
+
url,
|
|
56
|
+
requestConfig: config,
|
|
57
|
+
responseStatus: response?.status,
|
|
58
|
+
responseData: response?.data,
|
|
59
|
+
duration,
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
.catch(() => { });
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const duration = Date.now() - start;
|
|
67
|
+
self.telescope
|
|
68
|
+
.record({
|
|
69
|
+
type: entry_type_enum_1.EntryType.HTTP_CLIENT,
|
|
70
|
+
content: {
|
|
71
|
+
method: method.toUpperCase(),
|
|
72
|
+
url,
|
|
73
|
+
requestConfig: config,
|
|
74
|
+
error: err?.message,
|
|
75
|
+
duration,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
.catch(() => { });
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
// @nestjs/axios not installed – watcher remains inactive.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
exports.HttpServiceWatcher = HttpServiceWatcher;
|
|
90
|
+
exports.HttpServiceWatcher = HttpServiceWatcher = __decorate([
|
|
91
|
+
(0, common_1.Injectable)(),
|
|
92
|
+
__param(1, (0, common_1.Inject)(constants_1.TELESCOPE_OPTIONS)),
|
|
93
|
+
__param(1, (0, common_1.Optional)()),
|
|
94
|
+
__metadata("design:paramtypes", [telescope_service_1.TelescopeService, Object])
|
|
95
|
+
], HttpServiceWatcher);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from 'typeorm';
|
|
2
|
+
import { TelescopeService } from '../telescope.service';
|
|
3
|
+
/**
|
|
4
|
+
* Subscribes to TypeORM entity lifecycle events and records them.
|
|
5
|
+
* This watcher is automatically registered when TypeORM is present.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ModelSubscriber implements EntitySubscriberInterface {
|
|
8
|
+
private readonly telescope;
|
|
9
|
+
constructor(telescope: TelescopeService);
|
|
10
|
+
listenTo(): ObjectConstructor;
|
|
11
|
+
afterInsert(event: InsertEvent<any>): void;
|
|
12
|
+
afterUpdate(event: UpdateEvent<any>): void;
|
|
13
|
+
afterRemove(event: RemoveEvent<any>): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ModelSubscriber = void 0;
|
|
13
|
+
// src/watchers/model.subscriber.ts
|
|
14
|
+
const typeorm_1 = require("typeorm");
|
|
15
|
+
const common_1 = require("@nestjs/common");
|
|
16
|
+
const telescope_service_1 = require("../telescope.service");
|
|
17
|
+
const entry_type_enum_1 = require("../enums/entry-type.enum");
|
|
18
|
+
/**
|
|
19
|
+
* Subscribes to TypeORM entity lifecycle events and records them.
|
|
20
|
+
* This watcher is automatically registered when TypeORM is present.
|
|
21
|
+
*/
|
|
22
|
+
let ModelSubscriber = class ModelSubscriber {
|
|
23
|
+
telescope;
|
|
24
|
+
constructor(telescope) {
|
|
25
|
+
this.telescope = telescope;
|
|
26
|
+
}
|
|
27
|
+
listenTo() {
|
|
28
|
+
// Listen to all entities
|
|
29
|
+
return Object;
|
|
30
|
+
}
|
|
31
|
+
afterInsert(event) {
|
|
32
|
+
this.telescope.record({
|
|
33
|
+
type: entry_type_enum_1.EntryType.MODEL,
|
|
34
|
+
content: {
|
|
35
|
+
action: 'insert',
|
|
36
|
+
entity: event.metadata.name,
|
|
37
|
+
primaryKey: event.entity?.id ?? null,
|
|
38
|
+
data: event.entity,
|
|
39
|
+
},
|
|
40
|
+
}).catch(() => { });
|
|
41
|
+
}
|
|
42
|
+
afterUpdate(event) {
|
|
43
|
+
this.telescope.record({
|
|
44
|
+
type: entry_type_enum_1.EntryType.MODEL,
|
|
45
|
+
content: {
|
|
46
|
+
action: 'update',
|
|
47
|
+
entity: event.metadata.name,
|
|
48
|
+
primaryKey: event.entity?.id ?? null,
|
|
49
|
+
updatedColumns: event.updatedColumns.map(col => col.propertyName),
|
|
50
|
+
data: event.entity,
|
|
51
|
+
},
|
|
52
|
+
}).catch(() => { });
|
|
53
|
+
}
|
|
54
|
+
afterRemove(event) {
|
|
55
|
+
this.telescope.record({
|
|
56
|
+
type: entry_type_enum_1.EntryType.MODEL,
|
|
57
|
+
content: {
|
|
58
|
+
action: 'remove',
|
|
59
|
+
entity: event.metadata.name,
|
|
60
|
+
primaryKey: event.entityId,
|
|
61
|
+
},
|
|
62
|
+
}).catch(() => { });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
exports.ModelSubscriber = ModelSubscriber;
|
|
66
|
+
exports.ModelSubscriber = ModelSubscriber = __decorate([
|
|
67
|
+
(0, typeorm_1.EventSubscriber)(),
|
|
68
|
+
(0, common_1.Injectable)(),
|
|
69
|
+
__metadata("design:paramtypes", [telescope_service_1.TelescopeService])
|
|
70
|
+
], ModelSubscriber);
|
package/package.json
CHANGED
package/ui/index.html
CHANGED
|
@@ -487,6 +487,7 @@
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
.badge-request { background-color: rgba(59, 130, 246, 0.12); color: var(--accent-blue); }
|
|
490
|
+
.badge-http_client { background-color: rgba(99, 102, 241, 0.12); color: var(--primary); }
|
|
490
491
|
.badge-query { background-color: rgba(16, 185, 129, 0.12); color: var(--accent-green); }
|
|
491
492
|
.badge-cache { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
492
493
|
.badge-job { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
@@ -496,6 +497,11 @@
|
|
|
496
497
|
.badge-exception { background-color: rgba(244, 63, 94, 0.12); color: var(--accent-red); }
|
|
497
498
|
.badge-scheduled_task { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
498
499
|
.badge-redis { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
500
|
+
.badge-gate { background-color: rgba(244, 63, 94, 0.12); color: var(--accent-red); }
|
|
501
|
+
.badge-command { background-color: rgba(148, 163, 184, 0.15); color: var(--text-main); }
|
|
502
|
+
.badge-model { background-color: rgba(16, 185, 129, 0.12); color: var(--accent-green); }
|
|
503
|
+
.badge-notification { background-color: rgba(245, 158, 11, 0.12); color: var(--accent-amber); }
|
|
504
|
+
.badge-dump { background-color: rgba(139, 92, 246, 0.12); color: var(--accent-purple); }
|
|
499
505
|
|
|
500
506
|
.badge-get { background-color: rgba(16, 185, 129, 0.15); color: var(--accent-green); }
|
|
501
507
|
.badge-post { background-color: rgba(59, 130, 246, 0.15); color: var(--accent-blue); }
|
|
@@ -845,6 +851,7 @@
|
|
|
845
851
|
const NAVIGATION_TABS = [
|
|
846
852
|
{ id: 'all', label: 'All Entries', icon: '🔍' },
|
|
847
853
|
{ id: 'request', label: 'HTTP Requests', icon: '🌐' },
|
|
854
|
+
{ id: 'http_client', label: 'HTTP Client Requests', icon: '📞' },
|
|
848
855
|
{ id: 'query', label: 'Database Queries', icon: '💾' },
|
|
849
856
|
{ id: 'cache', label: 'Cache Ops', icon: '⚡' },
|
|
850
857
|
{ id: 'job', label: 'Queue Jobs', icon: '⚙️' },
|
|
@@ -854,6 +861,11 @@
|
|
|
854
861
|
{ id: 'exception', label: 'Exceptions', icon: '❌' },
|
|
855
862
|
{ id: 'scheduled_task', label: 'Scheduled Tasks', icon: '⏱️' },
|
|
856
863
|
{ id: 'redis', label: 'Redis Commands', icon: '🔑' },
|
|
864
|
+
{ id: 'gate', label: 'Gates', icon: '🚪' },
|
|
865
|
+
{ id: 'command', label: 'Commands', icon: '💻' },
|
|
866
|
+
{ id: 'model', label: 'Models', icon: '📦' },
|
|
867
|
+
{ id: 'notification', label: 'Notifications', icon: '🔔' },
|
|
868
|
+
{ id: 'dump', label: 'Dumps', icon: '🗑️' },
|
|
857
869
|
];
|
|
858
870
|
|
|
859
871
|
function App() {
|
|
@@ -1151,7 +1163,7 @@
|
|
|
1151
1163
|
{/* Stats Grid at the top for quick insights */}
|
|
1152
1164
|
{activeTab === 'all' && (
|
|
1153
1165
|
<div className="stats-grid">
|
|
1154
|
-
{NAVIGATION_TABS.slice(1
|
|
1166
|
+
{NAVIGATION_TABS.slice(1).map((tab) => (
|
|
1155
1167
|
<div
|
|
1156
1168
|
key={tab.id}
|
|
1157
1169
|
className={`stat-card ${activeTab === tab.id ? 'active' : ''}`}
|
|
@@ -1324,7 +1336,7 @@
|
|
|
1324
1336
|
|
|
1325
1337
|
// Determine font family for subtitle
|
|
1326
1338
|
function varMono(type) {
|
|
1327
|
-
return (type === 'query' || type === 'redis' || type === 'request') ? 'var(--font-mono)' : 'var(--font-sans)';
|
|
1339
|
+
return (type === 'query' || type === 'redis' || type === 'request' || type === 'http_client' || type === 'command') ? 'var(--font-mono)' : 'var(--font-sans)';
|
|
1328
1340
|
}
|
|
1329
1341
|
|
|
1330
1342
|
// Helper functions for displaying table items
|
|
@@ -1345,6 +1357,16 @@
|
|
|
1345
1357
|
subtitle: `${req.url || '/'}`,
|
|
1346
1358
|
status: statusBadge
|
|
1347
1359
|
};
|
|
1360
|
+
case 'http_client':
|
|
1361
|
+
return {
|
|
1362
|
+
title: `${content.method || 'GET'}`,
|
|
1363
|
+
subtitle: `${content.url || ''} (${content.duration || 0}ms)`,
|
|
1364
|
+
status: content.responseStatus >= 400 ? (
|
|
1365
|
+
<span className="badge badge-status-err">{content.responseStatus}</span>
|
|
1366
|
+
) : (
|
|
1367
|
+
<span className="badge badge-status-ok">{content.responseStatus || '200'}</span>
|
|
1368
|
+
)
|
|
1369
|
+
};
|
|
1348
1370
|
case 'query':
|
|
1349
1371
|
return {
|
|
1350
1372
|
title: content.query || 'DB Query',
|
|
@@ -1419,6 +1441,40 @@
|
|
|
1419
1441
|
<span className="badge badge-status-ok">success</span>
|
|
1420
1442
|
)
|
|
1421
1443
|
};
|
|
1444
|
+
case 'gate':
|
|
1445
|
+
return {
|
|
1446
|
+
title: `Gate: ${content.gate || ''}`,
|
|
1447
|
+
subtitle: content.user ? `User: ${JSON.stringify(content.user)}` : 'Anonymous',
|
|
1448
|
+
status: content.allowed ? (
|
|
1449
|
+
<span className="badge badge-status-ok">allowed</span>
|
|
1450
|
+
) : (
|
|
1451
|
+
<span className="badge badge-status-err">denied</span>
|
|
1452
|
+
)
|
|
1453
|
+
};
|
|
1454
|
+
case 'command':
|
|
1455
|
+
return {
|
|
1456
|
+
title: `Command: ${content.command || ''}`,
|
|
1457
|
+
subtitle: (content.args || []).join(' '),
|
|
1458
|
+
status: <span className="badge badge-status-ok">{content.exitCode ?? 'executed'}</span>
|
|
1459
|
+
};
|
|
1460
|
+
case 'model':
|
|
1461
|
+
return {
|
|
1462
|
+
title: `${(content.action || '').toUpperCase()} model`,
|
|
1463
|
+
subtitle: `${content.entity || ''} (${content.primaryKey || ''})`,
|
|
1464
|
+
status: <span className="badge badge-status-ok">{content.action}</span>
|
|
1465
|
+
};
|
|
1466
|
+
case 'notification':
|
|
1467
|
+
return {
|
|
1468
|
+
title: `Notification: ${content.notification || ''}`,
|
|
1469
|
+
subtitle: content.timestamp || '',
|
|
1470
|
+
status: <span className="badge badge-status-ok">sent</span>
|
|
1471
|
+
};
|
|
1472
|
+
case 'dump':
|
|
1473
|
+
return {
|
|
1474
|
+
title: `Dump`,
|
|
1475
|
+
subtitle: typeof content.message === 'string' ? content.message : JSON.stringify(content.message),
|
|
1476
|
+
status: <span className="badge badge-status-ok">dumped</span>
|
|
1477
|
+
};
|
|
1422
1478
|
default:
|
|
1423
1479
|
return {
|
|
1424
1480
|
title: 'Telescope Entry',
|
|
@@ -1471,6 +1527,59 @@
|
|
|
1471
1527
|
)}
|
|
1472
1528
|
</React.Fragment>
|
|
1473
1529
|
);
|
|
1530
|
+
case 'http_client':
|
|
1531
|
+
return (
|
|
1532
|
+
<React.Fragment>
|
|
1533
|
+
<div className="key-val-row">
|
|
1534
|
+
<div className="key-val-label">HTTP Method</div>
|
|
1535
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.method}</div>
|
|
1536
|
+
</div>
|
|
1537
|
+
<div className="key-val-row">
|
|
1538
|
+
<div className="key-val-label">URL</div>
|
|
1539
|
+
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)', color: '#818cf8' }}>{content.url}</div>
|
|
1540
|
+
</div>
|
|
1541
|
+
<div className="key-val-row">
|
|
1542
|
+
<div className="key-val-label">Duration</div>
|
|
1543
|
+
<div className="key-val-value">{content.duration} ms</div>
|
|
1544
|
+
</div>
|
|
1545
|
+
{content.requestHeaders && Object.keys(content.requestHeaders).length > 0 && (
|
|
1546
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1547
|
+
<div className="key-val-label">Request Headers</div>
|
|
1548
|
+
<pre className="json-code">{JSON.stringify(content.requestHeaders, null, 2)}</pre>
|
|
1549
|
+
</div>
|
|
1550
|
+
)}
|
|
1551
|
+
{content.requestBody && (
|
|
1552
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1553
|
+
<div className="key-val-label">Request Body</div>
|
|
1554
|
+
<pre className="json-code">{JSON.stringify(content.requestBody, null, 2)}</pre>
|
|
1555
|
+
</div>
|
|
1556
|
+
)}
|
|
1557
|
+
{content.responseStatus !== undefined && (
|
|
1558
|
+
<div className="key-val-row">
|
|
1559
|
+
<div className="key-val-label">Response Status</div>
|
|
1560
|
+
<div className="key-val-value">{content.responseStatus}</div>
|
|
1561
|
+
</div>
|
|
1562
|
+
)}
|
|
1563
|
+
{content.responseHeaders && Object.keys(content.responseHeaders).length > 0 && (
|
|
1564
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1565
|
+
<div className="key-val-label">Response Headers</div>
|
|
1566
|
+
<pre className="json-code">{JSON.stringify(content.responseHeaders, null, 2)}</pre>
|
|
1567
|
+
</div>
|
|
1568
|
+
)}
|
|
1569
|
+
{content.responseBody && (
|
|
1570
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1571
|
+
<div className="key-val-label">Response Body</div>
|
|
1572
|
+
<pre className="json-code">{JSON.stringify(content.responseBody, null, 2)}</pre>
|
|
1573
|
+
</div>
|
|
1574
|
+
)}
|
|
1575
|
+
{content.error && (
|
|
1576
|
+
<div className="key-val-row">
|
|
1577
|
+
<div className="key-val-label">Error</div>
|
|
1578
|
+
<div className="key-val-value" style={{ color: 'var(--accent-red)' }}>{content.error}</div>
|
|
1579
|
+
</div>
|
|
1580
|
+
)}
|
|
1581
|
+
</React.Fragment>
|
|
1582
|
+
);
|
|
1474
1583
|
case 'query':
|
|
1475
1584
|
return (
|
|
1476
1585
|
<React.Fragment>
|
|
@@ -1617,6 +1726,105 @@
|
|
|
1617
1726
|
)}
|
|
1618
1727
|
</React.Fragment>
|
|
1619
1728
|
);
|
|
1729
|
+
case 'gate':
|
|
1730
|
+
return (
|
|
1731
|
+
<React.Fragment>
|
|
1732
|
+
<div className="key-val-row">
|
|
1733
|
+
<div className="key-val-label">Gate Name</div>
|
|
1734
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.gate}</div>
|
|
1735
|
+
</div>
|
|
1736
|
+
<div className="key-val-row">
|
|
1737
|
+
<div className="key-val-label">Result</div>
|
|
1738
|
+
<div className="key-val-value">
|
|
1739
|
+
{content.allowed ? (
|
|
1740
|
+
<span className="badge badge-status-ok">Allowed</span>
|
|
1741
|
+
) : (
|
|
1742
|
+
<span className="badge badge-status-err">Denied</span>
|
|
1743
|
+
)}
|
|
1744
|
+
</div>
|
|
1745
|
+
</div>
|
|
1746
|
+
{content.user && (
|
|
1747
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1748
|
+
<div className="key-val-label">User Context</div>
|
|
1749
|
+
<pre className="json-code">{JSON.stringify(content.user, null, 2)}</pre>
|
|
1750
|
+
</div>
|
|
1751
|
+
)}
|
|
1752
|
+
</React.Fragment>
|
|
1753
|
+
);
|
|
1754
|
+
case 'command':
|
|
1755
|
+
return (
|
|
1756
|
+
<React.Fragment>
|
|
1757
|
+
<div className="key-val-row">
|
|
1758
|
+
<div className="key-val-label">Command</div>
|
|
1759
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff', fontFamily: 'var(--font-mono)' }}>{content.command}</div>
|
|
1760
|
+
</div>
|
|
1761
|
+
{content.args && content.args.length > 0 && (
|
|
1762
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1763
|
+
<div className="key-val-label">Arguments</div>
|
|
1764
|
+
<pre className="json-code">{JSON.stringify(content.args, null, 2)}</pre>
|
|
1765
|
+
</div>
|
|
1766
|
+
)}
|
|
1767
|
+
{content.exitCode !== undefined && (
|
|
1768
|
+
<div className="key-val-row">
|
|
1769
|
+
<div className="key-val-label">Exit Code</div>
|
|
1770
|
+
<div className="key-val-value">{content.exitCode}</div>
|
|
1771
|
+
</div>
|
|
1772
|
+
)}
|
|
1773
|
+
</React.Fragment>
|
|
1774
|
+
);
|
|
1775
|
+
case 'model':
|
|
1776
|
+
return (
|
|
1777
|
+
<React.Fragment>
|
|
1778
|
+
<div className="key-val-row">
|
|
1779
|
+
<div className="key-val-label">Action</div>
|
|
1780
|
+
<div className="key-val-value" style={{ textTransform: 'uppercase', fontWeight: '600', color: '#fff' }}>{content.action}</div>
|
|
1781
|
+
</div>
|
|
1782
|
+
<div className="key-val-row">
|
|
1783
|
+
<div className="key-val-label">Entity</div>
|
|
1784
|
+
<div className="key-val-value">{content.entity}</div>
|
|
1785
|
+
</div>
|
|
1786
|
+
<div className="key-val-row">
|
|
1787
|
+
<div className="key-val-label">Primary Key</div>
|
|
1788
|
+
<div className="key-val-value" style={{ fontFamily: 'var(--font-mono)' }}>{String(content.primaryKey)}</div>
|
|
1789
|
+
</div>
|
|
1790
|
+
{content.updatedColumns && content.updatedColumns.length > 0 && (
|
|
1791
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1792
|
+
<div className="key-val-label">Updated Columns</div>
|
|
1793
|
+
<pre className="json-code">{JSON.stringify(content.updatedColumns, null, 2)}</pre>
|
|
1794
|
+
</div>
|
|
1795
|
+
)}
|
|
1796
|
+
{content.data && (
|
|
1797
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1798
|
+
<div className="key-val-label">Model Data</div>
|
|
1799
|
+
<pre className="json-code">{JSON.stringify(content.data, null, 2)}</pre>
|
|
1800
|
+
</div>
|
|
1801
|
+
)}
|
|
1802
|
+
</React.Fragment>
|
|
1803
|
+
);
|
|
1804
|
+
case 'notification':
|
|
1805
|
+
return (
|
|
1806
|
+
<React.Fragment>
|
|
1807
|
+
<div className="key-val-row">
|
|
1808
|
+
<div className="key-val-label">Notification</div>
|
|
1809
|
+
<div className="key-val-value" style={{ fontWeight: '600', color: '#fff' }}>{content.notification}</div>
|
|
1810
|
+
</div>
|
|
1811
|
+
{content.payload && (
|
|
1812
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1813
|
+
<div className="key-val-label">Payload</div>
|
|
1814
|
+
<pre className="json-code">{JSON.stringify(content.payload, null, 2)}</pre>
|
|
1815
|
+
</div>
|
|
1816
|
+
)}
|
|
1817
|
+
</React.Fragment>
|
|
1818
|
+
);
|
|
1819
|
+
case 'dump':
|
|
1820
|
+
return (
|
|
1821
|
+
<React.Fragment>
|
|
1822
|
+
<div className="key-val-row" style={{ flexDirection: 'column', display: 'flex', gap: '8px' }}>
|
|
1823
|
+
<div className="key-val-label">Message / Data</div>
|
|
1824
|
+
<pre className="json-code">{typeof content.message === 'string' ? content.message : JSON.stringify(content.message, null, 2)}</pre>
|
|
1825
|
+
</div>
|
|
1826
|
+
</React.Fragment>
|
|
1827
|
+
);
|
|
1620
1828
|
default:
|
|
1621
1829
|
return (
|
|
1622
1830
|
<div className="key-val-row">
|