@qwickapps/server 1.1.6 → 1.1.9

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 (67) hide show
  1. package/README.md +1 -1
  2. package/dist/core/control-panel.d.ts.map +1 -1
  3. package/dist/core/control-panel.js +5 -8
  4. package/dist/core/control-panel.js.map +1 -1
  5. package/dist/core/gateway.d.ts +5 -0
  6. package/dist/core/gateway.d.ts.map +1 -1
  7. package/dist/core/gateway.js +390 -28
  8. package/dist/core/gateway.js.map +1 -1
  9. package/dist/core/health-manager.d.ts.map +1 -1
  10. package/dist/core/health-manager.js +3 -9
  11. package/dist/core/health-manager.js.map +1 -1
  12. package/dist/core/logging.d.ts.map +1 -1
  13. package/dist/core/logging.js +2 -6
  14. package/dist/core/logging.js.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +7 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/plugins/cache-plugin.d.ts +219 -0
  20. package/dist/plugins/cache-plugin.d.ts.map +1 -0
  21. package/dist/plugins/cache-plugin.js +326 -0
  22. package/dist/plugins/cache-plugin.js.map +1 -0
  23. package/dist/plugins/cache-plugin.test.d.ts +8 -0
  24. package/dist/plugins/cache-plugin.test.d.ts.map +1 -0
  25. package/dist/plugins/cache-plugin.test.js +188 -0
  26. package/dist/plugins/cache-plugin.test.js.map +1 -0
  27. package/dist/plugins/config-plugin.js +1 -1
  28. package/dist/plugins/config-plugin.js.map +1 -1
  29. package/dist/plugins/diagnostics-plugin.js +1 -1
  30. package/dist/plugins/diagnostics-plugin.js.map +1 -1
  31. package/dist/plugins/health-plugin.js +1 -1
  32. package/dist/plugins/health-plugin.js.map +1 -1
  33. package/dist/plugins/index.d.ts +6 -0
  34. package/dist/plugins/index.d.ts.map +1 -1
  35. package/dist/plugins/index.js +4 -0
  36. package/dist/plugins/index.js.map +1 -1
  37. package/dist/plugins/logs-plugin.d.ts.map +1 -1
  38. package/dist/plugins/logs-plugin.js +1 -3
  39. package/dist/plugins/logs-plugin.js.map +1 -1
  40. package/dist/plugins/postgres-plugin.d.ts +155 -0
  41. package/dist/plugins/postgres-plugin.d.ts.map +1 -0
  42. package/dist/plugins/postgres-plugin.js +244 -0
  43. package/dist/plugins/postgres-plugin.js.map +1 -0
  44. package/dist/plugins/postgres-plugin.test.d.ts +8 -0
  45. package/dist/plugins/postgres-plugin.test.d.ts.map +1 -0
  46. package/dist/plugins/postgres-plugin.test.js +165 -0
  47. package/dist/plugins/postgres-plugin.test.js.map +1 -0
  48. package/dist-ui/assets/{index-Bk7ypbI4.js → index-CW1BviRn.js} +2 -2
  49. package/dist-ui/assets/{index-Bk7ypbI4.js.map → index-CW1BviRn.js.map} +1 -1
  50. package/dist-ui/index.html +1 -1
  51. package/package.json +13 -2
  52. package/src/core/control-panel.ts +5 -8
  53. package/src/core/gateway.ts +412 -30
  54. package/src/core/health-manager.ts +3 -9
  55. package/src/core/logging.ts +2 -6
  56. package/src/index.ts +22 -0
  57. package/src/plugins/cache-plugin.test.ts +241 -0
  58. package/src/plugins/cache-plugin.ts +503 -0
  59. package/src/plugins/config-plugin.ts +1 -1
  60. package/src/plugins/diagnostics-plugin.ts +1 -1
  61. package/src/plugins/health-plugin.ts +1 -1
  62. package/src/plugins/index.ts +10 -0
  63. package/src/plugins/logs-plugin.ts +1 -3
  64. package/src/plugins/postgres-plugin.test.ts +213 -0
  65. package/src/plugins/postgres-plugin.ts +345 -0
  66. package/ui/src/api/controlPanelApi.ts +1 -1
  67. package/ui/src/pages/LogsPage.tsx +6 -10
@@ -0,0 +1,213 @@
1
+ /**
2
+ * PostgreSQL Plugin Tests
3
+ *
4
+ * Note: These tests use mocks since we don't want to require a real database.
5
+ * Integration tests should be run separately with a real PostgreSQL instance.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
9
+
10
+ // Mock pg before importing the plugin
11
+ vi.mock('pg', () => {
12
+ const mockClient = {
13
+ query: vi.fn().mockResolvedValue({ rows: [] }),
14
+ release: vi.fn(),
15
+ };
16
+
17
+ const mockPool = {
18
+ connect: vi.fn().mockResolvedValue(mockClient),
19
+ query: vi.fn().mockResolvedValue({ rows: [] }),
20
+ end: vi.fn().mockResolvedValue(undefined),
21
+ on: vi.fn(),
22
+ totalCount: 5,
23
+ idleCount: 3,
24
+ waitingCount: 0,
25
+ };
26
+
27
+ return {
28
+ default: {
29
+ Pool: vi.fn(() => mockPool),
30
+ },
31
+ Pool: vi.fn(() => mockPool),
32
+ };
33
+ });
34
+
35
+ import {
36
+ createPostgresPlugin,
37
+ getPostgres,
38
+ hasPostgres,
39
+ type PostgresPluginConfig,
40
+ } from './postgres-plugin.js';
41
+
42
+ describe('PostgreSQL Plugin', () => {
43
+ const mockConfig: PostgresPluginConfig = {
44
+ url: 'postgresql://test:test@localhost:5432/testdb',
45
+ maxConnections: 10,
46
+ healthCheck: false, // Disable for unit tests
47
+ };
48
+
49
+ const mockContext = {
50
+ config: { productName: 'Test', port: 3000 },
51
+ app: {} as any,
52
+ router: {} as any,
53
+ logger: {
54
+ debug: vi.fn(),
55
+ info: vi.fn(),
56
+ warn: vi.fn(),
57
+ error: vi.fn(),
58
+ },
59
+ registerHealthCheck: vi.fn(),
60
+ };
61
+
62
+ beforeEach(() => {
63
+ vi.clearAllMocks();
64
+ });
65
+
66
+ afterEach(async () => {
67
+ // Clean up any registered instances
68
+ if (hasPostgres('test')) {
69
+ const db = getPostgres('test');
70
+ await db.close();
71
+ }
72
+ });
73
+
74
+ describe('createPostgresPlugin', () => {
75
+ it('should create a plugin with correct name', () => {
76
+ const plugin = createPostgresPlugin(mockConfig, 'test');
77
+ expect(plugin.name).toBe('postgres:test');
78
+ });
79
+
80
+ it('should use "default" as instance name when not specified', () => {
81
+ const plugin = createPostgresPlugin(mockConfig);
82
+ expect(plugin.name).toBe('postgres:default');
83
+ });
84
+
85
+ it('should have low order number (initialize early)', () => {
86
+ const plugin = createPostgresPlugin(mockConfig);
87
+ expect(plugin.order).toBeLessThan(10);
88
+ });
89
+ });
90
+
91
+ describe('onInit', () => {
92
+ it('should register the postgres instance', async () => {
93
+ const plugin = createPostgresPlugin(mockConfig, 'test');
94
+ await plugin.onInit?.(mockContext as any);
95
+
96
+ expect(hasPostgres('test')).toBe(true);
97
+ });
98
+
99
+ it('should log debug message on successful connection', async () => {
100
+ const plugin = createPostgresPlugin(mockConfig, 'test');
101
+ await plugin.onInit?.(mockContext as any);
102
+
103
+ expect(mockContext.logger.debug).toHaveBeenCalledWith(
104
+ expect.stringContaining('connected')
105
+ );
106
+ });
107
+
108
+ it('should register health check when enabled', async () => {
109
+ const configWithHealth = { ...mockConfig, healthCheck: true };
110
+ const plugin = createPostgresPlugin(configWithHealth, 'test');
111
+ await plugin.onInit?.(mockContext as any);
112
+
113
+ expect(mockContext.registerHealthCheck).toHaveBeenCalledWith(
114
+ expect.objectContaining({
115
+ name: 'postgres',
116
+ type: 'custom',
117
+ })
118
+ );
119
+ });
120
+
121
+ it('should use custom health check name when provided', async () => {
122
+ const configWithCustomName = {
123
+ ...mockConfig,
124
+ healthCheck: true,
125
+ healthCheckName: 'custom-db',
126
+ };
127
+ const plugin = createPostgresPlugin(configWithCustomName, 'test');
128
+ await plugin.onInit?.(mockContext as any);
129
+
130
+ expect(mockContext.registerHealthCheck).toHaveBeenCalledWith(
131
+ expect.objectContaining({
132
+ name: 'custom-db',
133
+ })
134
+ );
135
+ });
136
+ });
137
+
138
+ describe('getPostgres', () => {
139
+ it('should return registered instance', async () => {
140
+ const plugin = createPostgresPlugin(mockConfig, 'test');
141
+ await plugin.onInit?.(mockContext as any);
142
+
143
+ const db = getPostgres('test');
144
+ expect(db).toBeDefined();
145
+ expect(db.query).toBeDefined();
146
+ expect(db.queryOne).toBeDefined();
147
+ expect(db.transaction).toBeDefined();
148
+ });
149
+
150
+ it('should throw error for unregistered instance', () => {
151
+ expect(() => getPostgres('nonexistent')).toThrow(
152
+ 'PostgreSQL instance "nonexistent" not found'
153
+ );
154
+ });
155
+ });
156
+
157
+ describe('hasPostgres', () => {
158
+ it('should return false for unregistered instance', () => {
159
+ expect(hasPostgres('nonexistent')).toBe(false);
160
+ });
161
+
162
+ it('should return true for registered instance', async () => {
163
+ const plugin = createPostgresPlugin(mockConfig, 'test');
164
+ await plugin.onInit?.(mockContext as any);
165
+
166
+ expect(hasPostgres('test')).toBe(true);
167
+ });
168
+ });
169
+
170
+ describe('PostgresInstance', () => {
171
+ it('should execute query and return rows', async () => {
172
+ const plugin = createPostgresPlugin(mockConfig, 'test');
173
+ await plugin.onInit?.(mockContext as any);
174
+
175
+ const db = getPostgres('test');
176
+ const result = await db.query('SELECT 1');
177
+ expect(result).toEqual([]);
178
+ });
179
+
180
+ it('should return null from queryOne when no rows', async () => {
181
+ const plugin = createPostgresPlugin(mockConfig, 'test');
182
+ await plugin.onInit?.(mockContext as any);
183
+
184
+ const db = getPostgres('test');
185
+ const result = await db.queryOne('SELECT 1');
186
+ expect(result).toBeNull();
187
+ });
188
+
189
+ it('should return pool stats', async () => {
190
+ const plugin = createPostgresPlugin(mockConfig, 'test');
191
+ await plugin.onInit?.(mockContext as any);
192
+
193
+ const db = getPostgres('test');
194
+ const stats = db.getStats();
195
+ expect(stats).toHaveProperty('total');
196
+ expect(stats).toHaveProperty('idle');
197
+ expect(stats).toHaveProperty('waiting');
198
+ });
199
+ });
200
+
201
+ describe('onShutdown', () => {
202
+ it('should close pool and unregister instance', async () => {
203
+ const plugin = createPostgresPlugin(mockConfig, 'test');
204
+ await plugin.onInit?.(mockContext as any);
205
+
206
+ expect(hasPostgres('test')).toBe(true);
207
+
208
+ await plugin.onShutdown?.();
209
+
210
+ expect(hasPostgres('test')).toBe(false);
211
+ });
212
+ });
213
+ });
@@ -0,0 +1,345 @@
1
+ /**
2
+ * PostgreSQL Plugin
3
+ *
4
+ * Provides PostgreSQL database connection pooling and health checks.
5
+ * Wraps the 'pg' library with a simple, reusable interface.
6
+ *
7
+ * ## Features
8
+ * - Connection pooling with configurable limits
9
+ * - Automatic health checks with pool stats
10
+ * - Transaction helpers
11
+ * - Multiple named instances support
12
+ * - Graceful shutdown
13
+ *
14
+ * ## Usage
15
+ *
16
+ * ```typescript
17
+ * import { createGateway, createPostgresPlugin, getPostgres } from '@qwickapps/server';
18
+ *
19
+ * const gateway = createGateway({
20
+ * // ... config
21
+ * plugins: [
22
+ * createPostgresPlugin({
23
+ * url: process.env.DATABASE_URL,
24
+ * maxConnections: 20,
25
+ * }),
26
+ * ],
27
+ * });
28
+ *
29
+ * // In your service code:
30
+ * const db = getPostgres();
31
+ * const users = await db.query<User>('SELECT * FROM users WHERE active = $1', [true]);
32
+ * ```
33
+ *
34
+ * ## Multiple Databases
35
+ *
36
+ * ```typescript
37
+ * // Register multiple databases with different names
38
+ * createPostgresPlugin({ url: primaryUrl }, 'primary');
39
+ * createPostgresPlugin({ url: replicaUrl }, 'replica');
40
+ *
41
+ * // Access by name
42
+ * const primary = getPostgres('primary');
43
+ * const replica = getPostgres('replica');
44
+ * ```
45
+ *
46
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
47
+ */
48
+
49
+ import pg from 'pg';
50
+ import type { ControlPanelPlugin, PluginContext } from '../core/types.js';
51
+
52
+ const { Pool } = pg;
53
+
54
+ /**
55
+ * Configuration for the PostgreSQL plugin
56
+ */
57
+ export interface PostgresPluginConfig {
58
+ /** Database connection URL (e.g., postgresql://user:pass@host:5432/db) */
59
+ url: string;
60
+
61
+ /** Maximum number of clients in the pool (default: 20) */
62
+ maxConnections?: number;
63
+
64
+ /** Minimum number of clients in the pool (default: 2) */
65
+ minConnections?: number;
66
+
67
+ /** Idle timeout in milliseconds - close idle clients after this time (default: 30000) */
68
+ idleTimeoutMs?: number;
69
+
70
+ /** Connection timeout in milliseconds - fail if can't connect within this time (default: 5000) */
71
+ connectionTimeoutMs?: number;
72
+
73
+ /** Statement timeout in milliseconds - cancel queries taking longer (default: none) */
74
+ statementTimeoutMs?: number;
75
+
76
+ /** Register a health check for this database (default: true) */
77
+ healthCheck?: boolean;
78
+
79
+ /** Name for the health check (default: 'postgres') */
80
+ healthCheckName?: string;
81
+
82
+ /** Health check interval in milliseconds (default: 30000) */
83
+ healthCheckInterval?: number;
84
+
85
+ /** Called when a client connects (for setup like setting search_path) */
86
+ onConnect?: (client: pg.PoolClient) => Promise<void>;
87
+
88
+ /** Called on pool errors */
89
+ onError?: (error: Error) => void;
90
+ }
91
+
92
+ /**
93
+ * Transaction callback function
94
+ */
95
+ export type TransactionCallback<T> = (client: pg.PoolClient) => Promise<T>;
96
+
97
+ /**
98
+ * PostgreSQL instance returned by the plugin
99
+ */
100
+ export interface PostgresInstance {
101
+ /** Get a client from the pool (remember to release it!) */
102
+ getClient(): Promise<pg.PoolClient>;
103
+
104
+ /** Execute a query and return rows */
105
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
106
+
107
+ /** Execute a query and return first row or null */
108
+ queryOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null>;
109
+
110
+ /** Execute a query and return the full result (includes rowCount, etc.) */
111
+ queryRaw(sql: string, params?: unknown[]): Promise<pg.QueryResult>;
112
+
113
+ /**
114
+ * Execute multiple queries in a transaction
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const result = await db.transaction(async (client) => {
119
+ * await client.query('INSERT INTO users (name) VALUES ($1)', ['Alice']);
120
+ * await client.query('INSERT INTO audit_log (action) VALUES ($1)', ['user_created']);
121
+ * return { success: true };
122
+ * });
123
+ * ```
124
+ */
125
+ transaction<T>(callback: TransactionCallback<T>): Promise<T>;
126
+
127
+ /** Get the underlying pool (for advanced use cases) */
128
+ getPool(): pg.Pool;
129
+
130
+ /** Get pool statistics */
131
+ getStats(): { total: number; idle: number; waiting: number };
132
+
133
+ /** Close all connections */
134
+ close(): Promise<void>;
135
+ }
136
+
137
+ // Global registry of PostgreSQL instances by name
138
+ const instances = new Map<string, PostgresInstance>();
139
+
140
+ /**
141
+ * Get a PostgreSQL instance by name
142
+ *
143
+ * @param name - Instance name (default: 'default')
144
+ * @returns The PostgreSQL instance
145
+ * @throws Error if the instance is not registered
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * const db = getPostgres();
150
+ * const users = await db.query<User>('SELECT * FROM users');
151
+ * ```
152
+ */
153
+ export function getPostgres(name = 'default'): PostgresInstance {
154
+ const instance = instances.get(name);
155
+ if (!instance) {
156
+ throw new Error(`PostgreSQL instance "${name}" not found. Did you register the postgres plugin?`);
157
+ }
158
+ return instance;
159
+ }
160
+
161
+ /**
162
+ * Check if a PostgreSQL instance is registered
163
+ *
164
+ * @param name - Instance name (default: 'default')
165
+ * @returns true if the instance exists
166
+ */
167
+ export function hasPostgres(name = 'default'): boolean {
168
+ return instances.has(name);
169
+ }
170
+
171
+ /**
172
+ * Create a PostgreSQL plugin
173
+ *
174
+ * @param config - PostgreSQL configuration
175
+ * @param instanceName - Name for this PostgreSQL instance (default: 'default')
176
+ * @returns A control panel plugin
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * createPostgresPlugin({
181
+ * url: process.env.DATABASE_URL,
182
+ * maxConnections: 20,
183
+ * healthCheck: true,
184
+ * });
185
+ * ```
186
+ */
187
+ export function createPostgresPlugin(
188
+ config: PostgresPluginConfig,
189
+ instanceName = 'default'
190
+ ): ControlPanelPlugin {
191
+ let pool: pg.Pool | null = null;
192
+
193
+ const createInstance = (): PostgresInstance => {
194
+ if (!pool) {
195
+ pool = new Pool({
196
+ connectionString: config.url,
197
+ max: config.maxConnections ?? 20,
198
+ min: config.minConnections ?? 2,
199
+ idleTimeoutMillis: config.idleTimeoutMs ?? 30000,
200
+ connectionTimeoutMillis: config.connectionTimeoutMs ?? 5000,
201
+ statement_timeout: config.statementTimeoutMs,
202
+ });
203
+
204
+ // Handle pool errors
205
+ pool.on('error', (err) => {
206
+ if (config.onError) {
207
+ config.onError(err);
208
+ } else {
209
+ console.error(`[database:${instanceName}] Pool error:`, err.message);
210
+ }
211
+ });
212
+
213
+ // Call onConnect for each new client
214
+ if (config.onConnect) {
215
+ pool.on('connect', (client) => {
216
+ config.onConnect!(client).catch((err) => {
217
+ console.error(`[database:${instanceName}] onConnect error:`, err.message);
218
+ });
219
+ });
220
+ }
221
+ }
222
+
223
+ const instance: PostgresInstance = {
224
+ async getClient(): Promise<pg.PoolClient> {
225
+ if (!pool) throw new Error('Database pool not initialized');
226
+ return pool.connect();
227
+ },
228
+
229
+ async query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]> {
230
+ if (!pool) throw new Error('Database pool not initialized');
231
+ const result = await pool.query(sql, params);
232
+ return result.rows as T[];
233
+ },
234
+
235
+ async queryOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T | null> {
236
+ const rows = await instance.query<T>(sql, params);
237
+ return rows[0] ?? null;
238
+ },
239
+
240
+ async queryRaw(sql: string, params?: unknown[]): Promise<pg.QueryResult> {
241
+ if (!pool) throw new Error('Database pool not initialized');
242
+ return pool.query(sql, params);
243
+ },
244
+
245
+ async transaction<T>(callback: TransactionCallback<T>): Promise<T> {
246
+ const client = await instance.getClient();
247
+ try {
248
+ await client.query('BEGIN');
249
+ const result = await callback(client);
250
+ await client.query('COMMIT');
251
+ return result;
252
+ } catch (err) {
253
+ await client.query('ROLLBACK');
254
+ throw err;
255
+ } finally {
256
+ client.release();
257
+ }
258
+ },
259
+
260
+ getPool(): pg.Pool {
261
+ if (!pool) throw new Error('Database pool not initialized');
262
+ return pool;
263
+ },
264
+
265
+ getStats(): { total: number; idle: number; waiting: number } {
266
+ return {
267
+ total: pool?.totalCount ?? 0,
268
+ idle: pool?.idleCount ?? 0,
269
+ waiting: pool?.waitingCount ?? 0,
270
+ };
271
+ },
272
+
273
+ async close(): Promise<void> {
274
+ if (pool) {
275
+ await pool.end();
276
+ pool = null;
277
+ }
278
+ },
279
+ };
280
+
281
+ return instance;
282
+ };
283
+
284
+ return {
285
+ name: `postgres:${instanceName}`,
286
+ order: 5, // Initialize early, before other plugins that may need DB
287
+
288
+ async onInit(context: PluginContext): Promise<void> {
289
+ const { registerHealthCheck, logger } = context;
290
+
291
+ // Create and register the instance
292
+ const instance = createInstance();
293
+ instances.set(instanceName, instance);
294
+
295
+ // Test connection
296
+ try {
297
+ await instance.query('SELECT 1');
298
+ logger.debug(`PostgreSQL "${instanceName}" connected`);
299
+ } catch (err) {
300
+ logger.error(`PostgreSQL "${instanceName}" connection failed: ${err instanceof Error ? err.message : String(err)}`);
301
+ throw err;
302
+ }
303
+
304
+ // Register health check if enabled
305
+ if (config.healthCheck !== false) {
306
+ registerHealthCheck({
307
+ name: config.healthCheckName ?? 'postgres',
308
+ type: 'custom',
309
+ interval: config.healthCheckInterval ?? 30000,
310
+ timeout: 5000,
311
+ check: async () => {
312
+ const start = Date.now();
313
+ try {
314
+ await instance.query('SELECT 1');
315
+ const stats = instance.getStats();
316
+ return {
317
+ healthy: true,
318
+ latency: Date.now() - start,
319
+ details: {
320
+ pool: stats,
321
+ },
322
+ };
323
+ } catch (err) {
324
+ return {
325
+ healthy: false,
326
+ latency: Date.now() - start,
327
+ details: {
328
+ error: err instanceof Error ? err.message : String(err),
329
+ },
330
+ };
331
+ }
332
+ },
333
+ });
334
+ }
335
+ },
336
+
337
+ async onShutdown(): Promise<void> {
338
+ const instance = instances.get(instanceName);
339
+ if (instance) {
340
+ await instance.close();
341
+ instances.delete(instanceName);
342
+ }
343
+ },
344
+ };
345
+ }
@@ -59,7 +59,7 @@ export interface LogEntry {
59
59
  timestamp: string;
60
60
  level: string;
61
61
  message: string;
62
- source?: string;
62
+ namespace?: string;
63
63
  }
64
64
 
65
65
  export interface LogsResponse {
@@ -202,11 +202,9 @@ export function LogsPage() {
202
202
  <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)', width: 100 }}>
203
203
  Level
204
204
  </TableCell>
205
- {sources.length > 0 && (
206
- <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)', width: 120 }}>
207
- Source
208
- </TableCell>
209
- )}
205
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)', width: 120 }}>
206
+ Component
207
+ </TableCell>
210
208
  <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
211
209
  Message
212
210
  </TableCell>
@@ -230,11 +228,9 @@ export function LogsPage() {
230
228
  }}
231
229
  />
232
230
  </TableCell>
233
- {sources.length > 0 && (
234
- <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)' }}>
235
- {log.source || '-'}
236
- </TableCell>
237
- )}
231
+ <TableCell sx={{ color: 'var(--theme-text-secondary)', borderColor: 'var(--theme-border)', fontSize: '0.75rem' }}>
232
+ {log.namespace || '-'}
233
+ </TableCell>
238
234
  <TableCell sx={{ color: 'var(--theme-text-primary)', borderColor: 'var(--theme-border)', fontFamily: 'monospace', fontSize: '0.8rem', whiteSpace: 'pre-wrap', wordBreak: 'break-all' }}>
239
235
  {log.message}
240
236
  </TableCell>