@qwickapps/server 1.0.0

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 (81) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +321 -0
  3. package/dist/core/control-panel.d.ts +21 -0
  4. package/dist/core/control-panel.d.ts.map +1 -0
  5. package/dist/core/control-panel.js +416 -0
  6. package/dist/core/control-panel.js.map +1 -0
  7. package/dist/core/gateway.d.ts +133 -0
  8. package/dist/core/gateway.d.ts.map +1 -0
  9. package/dist/core/gateway.js +270 -0
  10. package/dist/core/gateway.js.map +1 -0
  11. package/dist/core/health-manager.d.ts +52 -0
  12. package/dist/core/health-manager.d.ts.map +1 -0
  13. package/dist/core/health-manager.js +192 -0
  14. package/dist/core/health-manager.js.map +1 -0
  15. package/dist/core/index.d.ts +10 -0
  16. package/dist/core/index.d.ts.map +1 -0
  17. package/dist/core/index.js +8 -0
  18. package/dist/core/index.js.map +1 -0
  19. package/dist/core/logging.d.ts +83 -0
  20. package/dist/core/logging.d.ts.map +1 -0
  21. package/dist/core/logging.js +191 -0
  22. package/dist/core/logging.js.map +1 -0
  23. package/dist/core/types.d.ts +195 -0
  24. package/dist/core/types.d.ts.map +1 -0
  25. package/dist/core/types.js +7 -0
  26. package/dist/core/types.js.map +1 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +17 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/plugins/config-plugin.d.ts +15 -0
  32. package/dist/plugins/config-plugin.d.ts.map +1 -0
  33. package/dist/plugins/config-plugin.js +96 -0
  34. package/dist/plugins/config-plugin.js.map +1 -0
  35. package/dist/plugins/diagnostics-plugin.d.ts +29 -0
  36. package/dist/plugins/diagnostics-plugin.d.ts.map +1 -0
  37. package/dist/plugins/diagnostics-plugin.js +142 -0
  38. package/dist/plugins/diagnostics-plugin.js.map +1 -0
  39. package/dist/plugins/health-plugin.d.ts +17 -0
  40. package/dist/plugins/health-plugin.d.ts.map +1 -0
  41. package/dist/plugins/health-plugin.js +25 -0
  42. package/dist/plugins/health-plugin.js.map +1 -0
  43. package/dist/plugins/index.d.ts +14 -0
  44. package/dist/plugins/index.d.ts.map +1 -0
  45. package/dist/plugins/index.js +10 -0
  46. package/dist/plugins/index.js.map +1 -0
  47. package/dist/plugins/logs-plugin.d.ts +22 -0
  48. package/dist/plugins/logs-plugin.d.ts.map +1 -0
  49. package/dist/plugins/logs-plugin.js +242 -0
  50. package/dist/plugins/logs-plugin.js.map +1 -0
  51. package/dist-ui/assets/index-Bk7ypbI4.js +465 -0
  52. package/dist-ui/assets/index-Bk7ypbI4.js.map +1 -0
  53. package/dist-ui/assets/index-CiizQQnb.css +1 -0
  54. package/dist-ui/index.html +13 -0
  55. package/package.json +98 -0
  56. package/src/core/control-panel.ts +493 -0
  57. package/src/core/gateway.ts +421 -0
  58. package/src/core/health-manager.ts +227 -0
  59. package/src/core/index.ts +25 -0
  60. package/src/core/logging.ts +234 -0
  61. package/src/core/types.ts +218 -0
  62. package/src/index.ts +55 -0
  63. package/src/plugins/config-plugin.ts +117 -0
  64. package/src/plugins/diagnostics-plugin.ts +178 -0
  65. package/src/plugins/health-plugin.ts +35 -0
  66. package/src/plugins/index.ts +17 -0
  67. package/src/plugins/logs-plugin.ts +314 -0
  68. package/ui/index.html +12 -0
  69. package/ui/src/App.tsx +65 -0
  70. package/ui/src/api/controlPanelApi.ts +148 -0
  71. package/ui/src/config/AppConfig.ts +18 -0
  72. package/ui/src/index.css +29 -0
  73. package/ui/src/index.tsx +11 -0
  74. package/ui/src/pages/ConfigPage.tsx +199 -0
  75. package/ui/src/pages/DashboardPage.tsx +264 -0
  76. package/ui/src/pages/DiagnosticsPage.tsx +315 -0
  77. package/ui/src/pages/HealthPage.tsx +204 -0
  78. package/ui/src/pages/LogsPage.tsx +267 -0
  79. package/ui/src/pages/NotFoundPage.tsx +41 -0
  80. package/ui/tsconfig.json +19 -0
  81. package/ui/vite.config.ts +21 -0
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Diagnostics Plugin
3
+ *
4
+ * Provides AI-friendly diagnostic API for troubleshooting
5
+ *
6
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
+ */
8
+
9
+ import { existsSync, readFileSync } from 'fs';
10
+ import { resolve } from 'path';
11
+ import type { Request, Response } from 'express';
12
+ import type { ControlPanelPlugin, PluginContext } from '../core/types.js';
13
+
14
+ export interface DiagnosticsPluginConfig {
15
+ include?: {
16
+ logs?: {
17
+ startup?: number; // Last N lines from startup log
18
+ app?: number; // Last N lines from app log
19
+ };
20
+ health?: boolean;
21
+ config?: boolean;
22
+ system?: boolean;
23
+ };
24
+ logPaths?: {
25
+ startup?: string;
26
+ app?: string;
27
+ };
28
+ endpoint?: string;
29
+ }
30
+
31
+ /**
32
+ * Create a diagnostics plugin for AI agents
33
+ */
34
+ export function createDiagnosticsPlugin(config: DiagnosticsPluginConfig = {}): ControlPanelPlugin {
35
+ const {
36
+ include = { logs: { startup: 100, app: 200 }, health: true, config: true, system: true },
37
+ logPaths = { startup: './logs/startup.log', app: './logs/app.log' },
38
+ endpoint = '/diagnostics/full',
39
+ } = config;
40
+
41
+ return {
42
+ name: 'diagnostics',
43
+ order: 40,
44
+
45
+ routes: [
46
+ {
47
+ method: 'get',
48
+ path: endpoint,
49
+ handler: (_req: Request, res: Response) => {
50
+ try {
51
+ const report: Record<string, unknown> = {
52
+ timestamp: new Date().toISOString(),
53
+ generated_for: 'AI Agent Diagnostics',
54
+ };
55
+
56
+ // System info
57
+ if (include.system) {
58
+ const memUsage = process.memoryUsage();
59
+ report.system = {
60
+ nodeVersion: process.version,
61
+ platform: process.platform,
62
+ arch: process.arch,
63
+ pid: process.pid,
64
+ cwd: process.cwd(),
65
+ uptime: process.uptime(),
66
+ memory: {
67
+ rss: formatBytes(memUsage.rss),
68
+ heapTotal: formatBytes(memUsage.heapTotal),
69
+ heapUsed: formatBytes(memUsage.heapUsed),
70
+ external: formatBytes(memUsage.external),
71
+ },
72
+ };
73
+ }
74
+
75
+ // Environment check (not values, just presence)
76
+ if (include.config) {
77
+ const envCheck: Record<string, boolean> = {
78
+ NODE_ENV: !!process.env.NODE_ENV,
79
+ DATABASE_URI: !!process.env.DATABASE_URI,
80
+ PAYLOAD_SECRET: !!process.env.PAYLOAD_SECRET,
81
+ LOGFIRE_TOKEN: !!process.env.LOGFIRE_TOKEN,
82
+ };
83
+ report.envCheck = envCheck;
84
+ }
85
+
86
+ // Logs
87
+ if (include.logs) {
88
+ const logs: Record<string, string[]> = {};
89
+
90
+ if (include.logs.startup && logPaths.startup) {
91
+ logs.startup = readLastNLines(logPaths.startup, include.logs.startup);
92
+ }
93
+
94
+ if (include.logs.app && logPaths.app) {
95
+ logs.app = readLastNLines(logPaths.app, include.logs.app);
96
+ }
97
+
98
+ // Extract errors
99
+ const allLogs = [...(logs.startup || []), ...(logs.app || [])];
100
+ logs.errors = allLogs.filter((line) => {
101
+ const lower = line.toLowerCase();
102
+ return lower.includes('error') || lower.includes('fatal') || lower.includes('exception');
103
+ });
104
+
105
+ report.logs = logs;
106
+ }
107
+
108
+ res.json(report);
109
+ } catch (error) {
110
+ res.status(500).json({
111
+ error: 'Failed to generate diagnostics',
112
+ message: error instanceof Error ? error.message : String(error),
113
+ });
114
+ }
115
+ },
116
+ },
117
+ {
118
+ method: 'get',
119
+ path: '/diagnostics/summary',
120
+ handler: (_req: Request, res: Response) => {
121
+ try {
122
+ const memUsage = process.memoryUsage();
123
+
124
+ res.json({
125
+ status: 'ok',
126
+ timestamp: new Date().toISOString(),
127
+ uptime: process.uptime(),
128
+ memory: {
129
+ heapUsed: formatBytes(memUsage.heapUsed),
130
+ heapTotal: formatBytes(memUsage.heapTotal),
131
+ },
132
+ env: process.env.NODE_ENV || 'development',
133
+ });
134
+ } catch (error) {
135
+ res.status(500).json({
136
+ status: 'error',
137
+ message: error instanceof Error ? error.message : String(error),
138
+ });
139
+ }
140
+ },
141
+ },
142
+ ],
143
+
144
+ async onInit(context: PluginContext): Promise<void> {
145
+ context.logger.info('[DiagnosticsPlugin] Initialized');
146
+ },
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Read last N lines from a file
152
+ */
153
+ function readLastNLines(filePath: string, n: number): string[] {
154
+ const resolvedPath = resolve(filePath);
155
+
156
+ if (!existsSync(resolvedPath)) {
157
+ return [`File not found: ${filePath}`];
158
+ }
159
+
160
+ try {
161
+ const content = readFileSync(resolvedPath, 'utf-8');
162
+ const lines = content.split('\n').filter((line) => line.trim());
163
+ return lines.slice(-n);
164
+ } catch (error) {
165
+ return [`Error reading file: ${error instanceof Error ? error.message : String(error)}`];
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Format bytes to human readable
171
+ */
172
+ function formatBytes(bytes: number): string {
173
+ if (bytes === 0) return '0 B';
174
+ const k = 1024;
175
+ const sizes = ['B', 'KB', 'MB', 'GB'];
176
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
177
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
178
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Health Plugin
3
+ *
4
+ * Provides health check monitoring capabilities
5
+ *
6
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
+ */
8
+
9
+ import type { ControlPanelPlugin, HealthCheck, PluginContext } from '../core/types.js';
10
+
11
+ export interface HealthPluginConfig {
12
+ checks: HealthCheck[];
13
+ aggregateEndpoint?: string;
14
+ }
15
+
16
+ /**
17
+ * Create a health check plugin
18
+ */
19
+ export function createHealthPlugin(config: HealthPluginConfig): ControlPanelPlugin {
20
+ return {
21
+ name: 'health',
22
+ order: 10,
23
+
24
+ async onInit(context: PluginContext): Promise<void> {
25
+ const { registerHealthCheck, logger } = context;
26
+
27
+ // Register all health checks
28
+ for (const check of config.checks) {
29
+ registerHealthCheck(check);
30
+ }
31
+
32
+ logger.info(`[HealthPlugin] Registered ${config.checks.length} health checks`);
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Built-in plugins for @qwickapps/server
3
+ *
4
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
5
+ */
6
+
7
+ export { createHealthPlugin } from './health-plugin.js';
8
+ export type { HealthPluginConfig } from './health-plugin.js';
9
+
10
+ export { createLogsPlugin } from './logs-plugin.js';
11
+ export type { LogsPluginConfig } from './logs-plugin.js';
12
+
13
+ export { createConfigPlugin } from './config-plugin.js';
14
+ export type { ConfigPluginConfig } from './config-plugin.js';
15
+
16
+ export { createDiagnosticsPlugin } from './diagnostics-plugin.js';
17
+ export type { DiagnosticsPluginConfig } from './diagnostics-plugin.js';
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Logs Plugin
3
+ *
4
+ * Provides log viewing capabilities from various sources.
5
+ * If no sources are configured, automatically uses the logging subsystem's log paths.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import { existsSync, readFileSync, statSync } from 'fs';
11
+ import { resolve } from 'path';
12
+ import type { Request, Response } from 'express';
13
+ import type { ControlPanelPlugin, LogSource, PluginContext } from '../core/types.js';
14
+ import { getLoggingSubsystem } from '../core/logging.js';
15
+
16
+ export interface LogsPluginConfig {
17
+ /** Log sources to display. If empty, uses default sources from logging subsystem */
18
+ sources?: LogSource[];
19
+ retention?: {
20
+ maxLines?: number;
21
+ autoRefresh?: number;
22
+ };
23
+ }
24
+
25
+ interface LogEntry {
26
+ id: number;
27
+ level: string;
28
+ timestamp: string;
29
+ namespace: string;
30
+ message: string;
31
+ [key: string]: unknown;
32
+ }
33
+
34
+ interface LogStats {
35
+ totalLogs: number;
36
+ byLevel: {
37
+ debug: number;
38
+ info: number;
39
+ warn: number;
40
+ error: number;
41
+ };
42
+ fileSize: number;
43
+ fileSizeFormatted: string;
44
+ oldestLog: string | null;
45
+ newestLog: string | null;
46
+ }
47
+
48
+ /**
49
+ * Get default log sources from the logging subsystem
50
+ */
51
+ function getDefaultSources(): LogSource[] {
52
+ const loggingSubsystem = getLoggingSubsystem();
53
+ const logPaths = loggingSubsystem.getLogPaths();
54
+
55
+ return [
56
+ { name: 'app', type: 'file', path: logPaths.appLog },
57
+ { name: 'error', type: 'file', path: logPaths.errorLog },
58
+ ];
59
+ }
60
+
61
+ /**
62
+ * Create a logs plugin
63
+ */
64
+ export function createLogsPlugin(config: LogsPluginConfig = {}): ControlPanelPlugin {
65
+ const maxLines = config.retention?.maxLines || 10000;
66
+
67
+ // Use provided sources or default to logging subsystem paths
68
+ const getSources = (): LogSource[] => {
69
+ if (config.sources && config.sources.length > 0) {
70
+ return config.sources;
71
+ }
72
+ return getDefaultSources();
73
+ };
74
+
75
+ return {
76
+ name: 'logs',
77
+ order: 20,
78
+
79
+ routes: [
80
+ {
81
+ method: 'get',
82
+ path: '/logs/sources',
83
+ handler: (_req: Request, res: Response) => {
84
+ const sources = getSources();
85
+ res.json({
86
+ sources: sources.map((s) => ({
87
+ name: s.name,
88
+ type: s.type,
89
+ })),
90
+ });
91
+ },
92
+ },
93
+ {
94
+ method: 'get',
95
+ path: '/logs',
96
+ handler: (req: Request, res: Response) => {
97
+ try {
98
+ const sources = getSources();
99
+ const sourceName = (req.query.source as string) || sources[0]?.name;
100
+ const limit = Math.min(parseInt(req.query.limit as string) || 100, maxLines);
101
+ const offset = parseInt(req.query.offset as string) || 0;
102
+ const level = req.query.level as string;
103
+ const search = req.query.search as string;
104
+ const order = (req.query.order as 'asc' | 'desc') || 'desc';
105
+
106
+ const source = sources.find((s) => s.name === sourceName);
107
+ if (!source) {
108
+ return res.status(404).json({ error: `Source "${sourceName}" not found` });
109
+ }
110
+
111
+ if (source.type === 'file' && source.path) {
112
+ const logs = readLogsFromFile(source.path, { limit, offset, level, search, order });
113
+ return res.json(logs);
114
+ } else if (source.type === 'api' && source.url) {
115
+ // Proxy to remote API
116
+ return res.status(501).json({ error: 'API source not yet implemented' });
117
+ }
118
+
119
+ return res.status(400).json({ error: 'Invalid source configuration' });
120
+ } catch (error) {
121
+ return res.status(500).json({
122
+ error: 'Failed to read logs',
123
+ message: error instanceof Error ? error.message : String(error),
124
+ });
125
+ }
126
+ },
127
+ },
128
+ {
129
+ method: 'get',
130
+ path: '/logs/stats',
131
+ handler: (req: Request, res: Response) => {
132
+ try {
133
+ const sources = getSources();
134
+ const sourceName = (req.query.source as string) || sources[0]?.name;
135
+ const source = sources.find((s) => s.name === sourceName);
136
+
137
+ if (!source) {
138
+ return res.status(404).json({ error: `Source "${sourceName}" not found` });
139
+ }
140
+
141
+ if (source.type === 'file' && source.path) {
142
+ const stats = getLogStats(source.path);
143
+ return res.json(stats);
144
+ }
145
+
146
+ return res.status(400).json({ error: 'Stats only available for file sources' });
147
+ } catch (error) {
148
+ return res.status(500).json({
149
+ error: 'Failed to get log stats',
150
+ message: error instanceof Error ? error.message : String(error),
151
+ });
152
+ }
153
+ },
154
+ },
155
+ ],
156
+
157
+ async onInit(context: PluginContext): Promise<void> {
158
+ const sources = getSources();
159
+ context.logger.info(`Initialized with ${sources.length} sources`, {
160
+ sources: sources.map((s) => ({ name: s.name, type: s.type, path: s.path })),
161
+ });
162
+ },
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Read logs from a file
168
+ */
169
+ function readLogsFromFile(
170
+ filePath: string,
171
+ options: {
172
+ limit: number;
173
+ offset: number;
174
+ level?: string;
175
+ search?: string;
176
+ order: 'asc' | 'desc';
177
+ }
178
+ ): { logs: LogEntry[]; total: number } {
179
+ const resolvedPath = resolve(filePath);
180
+
181
+ if (!existsSync(resolvedPath)) {
182
+ return { logs: [], total: 0 };
183
+ }
184
+
185
+ const content = readFileSync(resolvedPath, 'utf-8');
186
+ const lines = content.split('\n').filter((line) => line.trim());
187
+
188
+ let entries: LogEntry[] = [];
189
+ let id = 0;
190
+
191
+ for (const line of lines) {
192
+ // Try to parse as JSON log entry
193
+ try {
194
+ const parsed = JSON.parse(line);
195
+ if (parsed.msg || parsed.message) {
196
+ entries.push({
197
+ id: id++,
198
+ level: parsed.level || 'info',
199
+ timestamp: parsed.timestamp || parsed.time || new Date().toISOString(),
200
+ namespace: parsed.ns || parsed.name || 'unknown',
201
+ message: parsed.msg || parsed.message,
202
+ ...parsed,
203
+ });
204
+ }
205
+ } catch {
206
+ // Try to parse as simple log format: "LEVEL [namespace] message"
207
+ const match = line.match(/^(\d{2}:\d{2}:\d{2})\s+\[([^\]]+)\]\s+(.*)$/);
208
+ if (match) {
209
+ entries.push({
210
+ id: id++,
211
+ level: 'info',
212
+ timestamp: match[1],
213
+ namespace: match[2],
214
+ message: match[3],
215
+ });
216
+ }
217
+ }
218
+ }
219
+
220
+ // Filter by level
221
+ if (options.level && options.level !== 'all') {
222
+ entries = entries.filter((e) => e.level === options.level);
223
+ }
224
+
225
+ // Filter by search
226
+ if (options.search) {
227
+ const searchLower = options.search.toLowerCase();
228
+ entries = entries.filter(
229
+ (e) =>
230
+ e.message.toLowerCase().includes(searchLower) ||
231
+ e.namespace.toLowerCase().includes(searchLower)
232
+ );
233
+ }
234
+
235
+ const total = entries.length;
236
+
237
+ // Sort
238
+ if (options.order === 'desc') {
239
+ entries.reverse();
240
+ }
241
+
242
+ // Paginate
243
+ entries = entries.slice(options.offset, options.offset + options.limit);
244
+
245
+ return { logs: entries, total };
246
+ }
247
+
248
+ /**
249
+ * Get log statistics from a file
250
+ */
251
+ function getLogStats(filePath: string): LogStats {
252
+ const resolvedPath = resolve(filePath);
253
+
254
+ if (!existsSync(resolvedPath)) {
255
+ return {
256
+ totalLogs: 0,
257
+ byLevel: { debug: 0, info: 0, warn: 0, error: 0 },
258
+ fileSize: 0,
259
+ fileSizeFormatted: '0 B',
260
+ oldestLog: null,
261
+ newestLog: null,
262
+ };
263
+ }
264
+
265
+ const stats = statSync(resolvedPath);
266
+ const content = readFileSync(resolvedPath, 'utf-8');
267
+ const lines = content.split('\n').filter((line) => line.trim());
268
+
269
+ const byLevel = { debug: 0, info: 0, warn: 0, error: 0 };
270
+ let oldestLog: string | null = null;
271
+ let newestLog: string | null = null;
272
+
273
+ for (const line of lines) {
274
+ try {
275
+ const parsed = JSON.parse(line);
276
+ const level = (parsed.level || 'info').toLowerCase();
277
+ if (level in byLevel) {
278
+ byLevel[level as keyof typeof byLevel]++;
279
+ }
280
+
281
+ const timestamp = parsed.timestamp || parsed.time;
282
+ if (timestamp) {
283
+ if (!oldestLog || timestamp < oldestLog) {
284
+ oldestLog = timestamp;
285
+ }
286
+ if (!newestLog || timestamp > newestLog) {
287
+ newestLog = timestamp;
288
+ }
289
+ }
290
+ } catch {
291
+ byLevel.info++; // Count non-JSON lines as info
292
+ }
293
+ }
294
+
295
+ return {
296
+ totalLogs: lines.length,
297
+ byLevel,
298
+ fileSize: stats.size,
299
+ fileSizeFormatted: formatBytes(stats.size),
300
+ oldestLog,
301
+ newestLog,
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Format bytes to human readable
307
+ */
308
+ function formatBytes(bytes: number): string {
309
+ if (bytes === 0) return '0 B';
310
+ const k = 1024;
311
+ const sizes = ['B', 'KB', 'MB', 'GB'];
312
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
313
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
314
+ }
package/ui/index.html ADDED
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Control Panel</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/index.tsx"></script>
11
+ </body>
12
+ </html>
package/ui/src/App.tsx ADDED
@@ -0,0 +1,65 @@
1
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
2
+ import { QwickApp, ProductLogo, Text } from '@qwickapps/react-framework';
3
+ import { Link, Box } from '@mui/material';
4
+ import { defaultConfig } from './config/AppConfig';
5
+ import { DashboardPage } from './pages/DashboardPage';
6
+ import { HealthPage } from './pages/HealthPage';
7
+ import { LogsPage } from './pages/LogsPage';
8
+ import { ConfigPage } from './pages/ConfigPage';
9
+ import { DiagnosticsPage } from './pages/DiagnosticsPage';
10
+ import { NotFoundPage } from './pages/NotFoundPage';
11
+
12
+ // Package version - injected at build time or fallback
13
+ const SERVER_VERSION = '1.0.0';
14
+
15
+ // Default logo - consumers can customize
16
+ const logo = <ProductLogo name="Control Panel" />;
17
+
18
+ // Default footer content with QwickApps Server branding
19
+ const footerContent = (
20
+ <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, py: 2 }}>
21
+ <Text variant="caption" customColor="var(--theme-text-secondary)">
22
+ Built with{' '}
23
+ <Link
24
+ href="https://qwickapps.com/products/qwickapps-server"
25
+ target="_blank"
26
+ rel="noopener noreferrer"
27
+ sx={{ color: 'primary.main' }}
28
+ >
29
+ QwickApps Server
30
+ </Link>
31
+ {' '}v{SERVER_VERSION}
32
+ </Text>
33
+ </Box>
34
+ );
35
+
36
+ export function App() {
37
+ return (
38
+ <BrowserRouter>
39
+ <QwickApp
40
+ config={defaultConfig}
41
+ logo={logo}
42
+ footerContent={footerContent}
43
+ enableScaffolding={true}
44
+ navigationItems={[
45
+ { id: 'dashboard', label: 'Dashboard', route: '/', icon: 'dashboard' },
46
+ { id: 'health', label: 'Health', route: '/health', icon: 'favorite' },
47
+ { id: 'logs', label: 'Logs', route: '/logs', icon: 'article' },
48
+ { id: 'config', label: 'Config', route: '/config', icon: 'settings' },
49
+ { id: 'diagnostics', label: 'Diagnostics', route: '/diagnostics', icon: 'bug_report' },
50
+ ]}
51
+ showThemeSwitcher={true}
52
+ showPaletteSwitcher={true}
53
+ >
54
+ <Routes>
55
+ <Route path="/" element={<DashboardPage />} />
56
+ <Route path="/health" element={<HealthPage />} />
57
+ <Route path="/logs" element={<LogsPage />} />
58
+ <Route path="/config" element={<ConfigPage />} />
59
+ <Route path="/diagnostics" element={<DiagnosticsPage />} />
60
+ <Route path="*" element={<NotFoundPage />} />
61
+ </Routes>
62
+ </QwickApp>
63
+ </BrowserRouter>
64
+ );
65
+ }