@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.
- package/LICENSE +45 -0
- package/README.md +321 -0
- package/dist/core/control-panel.d.ts +21 -0
- package/dist/core/control-panel.d.ts.map +1 -0
- package/dist/core/control-panel.js +416 -0
- package/dist/core/control-panel.js.map +1 -0
- package/dist/core/gateway.d.ts +133 -0
- package/dist/core/gateway.d.ts.map +1 -0
- package/dist/core/gateway.js +270 -0
- package/dist/core/gateway.js.map +1 -0
- package/dist/core/health-manager.d.ts +52 -0
- package/dist/core/health-manager.d.ts.map +1 -0
- package/dist/core/health-manager.js +192 -0
- package/dist/core/health-manager.js.map +1 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logging.d.ts +83 -0
- package/dist/core/logging.d.ts.map +1 -0
- package/dist/core/logging.js +191 -0
- package/dist/core/logging.js.map +1 -0
- package/dist/core/types.d.ts +195 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +7 -0
- package/dist/core/types.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/config-plugin.d.ts +15 -0
- package/dist/plugins/config-plugin.d.ts.map +1 -0
- package/dist/plugins/config-plugin.js +96 -0
- package/dist/plugins/config-plugin.js.map +1 -0
- package/dist/plugins/diagnostics-plugin.d.ts +29 -0
- package/dist/plugins/diagnostics-plugin.d.ts.map +1 -0
- package/dist/plugins/diagnostics-plugin.js +142 -0
- package/dist/plugins/diagnostics-plugin.js.map +1 -0
- package/dist/plugins/health-plugin.d.ts +17 -0
- package/dist/plugins/health-plugin.d.ts.map +1 -0
- package/dist/plugins/health-plugin.js +25 -0
- package/dist/plugins/health-plugin.js.map +1 -0
- package/dist/plugins/index.d.ts +14 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +10 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/logs-plugin.d.ts +22 -0
- package/dist/plugins/logs-plugin.d.ts.map +1 -0
- package/dist/plugins/logs-plugin.js +242 -0
- package/dist/plugins/logs-plugin.js.map +1 -0
- package/dist-ui/assets/index-Bk7ypbI4.js +465 -0
- package/dist-ui/assets/index-Bk7ypbI4.js.map +1 -0
- package/dist-ui/assets/index-CiizQQnb.css +1 -0
- package/dist-ui/index.html +13 -0
- package/package.json +98 -0
- package/src/core/control-panel.ts +493 -0
- package/src/core/gateway.ts +421 -0
- package/src/core/health-manager.ts +227 -0
- package/src/core/index.ts +25 -0
- package/src/core/logging.ts +234 -0
- package/src/core/types.ts +218 -0
- package/src/index.ts +55 -0
- package/src/plugins/config-plugin.ts +117 -0
- package/src/plugins/diagnostics-plugin.ts +178 -0
- package/src/plugins/health-plugin.ts +35 -0
- package/src/plugins/index.ts +17 -0
- package/src/plugins/logs-plugin.ts +314 -0
- package/ui/index.html +12 -0
- package/ui/src/App.tsx +65 -0
- package/ui/src/api/controlPanelApi.ts +148 -0
- package/ui/src/config/AppConfig.ts +18 -0
- package/ui/src/index.css +29 -0
- package/ui/src/index.tsx +11 -0
- package/ui/src/pages/ConfigPage.tsx +199 -0
- package/ui/src/pages/DashboardPage.tsx +264 -0
- package/ui/src/pages/DiagnosticsPage.tsx +315 -0
- package/ui/src/pages/HealthPage.tsx +204 -0
- package/ui/src/pages/LogsPage.tsx +267 -0
- package/ui/src/pages/NotFoundPage.tsx +41 -0
- package/ui/tsconfig.json +19 -0
- package/ui/vite.config.ts +21 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Control Panel Logging Subsystem
|
|
3
|
+
*
|
|
4
|
+
* Provides centralized logging for the control panel and all plugins.
|
|
5
|
+
* Configures @qwickapps/logging to write to files that the LogsPage can display.
|
|
6
|
+
*
|
|
7
|
+
* Environment Variables:
|
|
8
|
+
* LOG_LEVEL - Minimum log level (debug, info, warn, error). Default: debug in dev, info in prod
|
|
9
|
+
* LOG_DIR - Directory for log files. Default: ./logs
|
|
10
|
+
* LOG_FILE - Enable file logging. Default: true
|
|
11
|
+
* LOG_FILE_PATH - Path to log file. Default: ./logs/app.log (used by pino if available)
|
|
12
|
+
* LOG_CONSOLE - Enable console output. Default: true
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
18
|
+
import { resolve, dirname } from 'node:path';
|
|
19
|
+
import { Logger as QwickLogger, getLogger, LogLevel, LogTransport } from '@qwickapps/logging';
|
|
20
|
+
import type { Logger } from './types.js';
|
|
21
|
+
|
|
22
|
+
export interface LoggingConfig {
|
|
23
|
+
/** Product namespace for log entries */
|
|
24
|
+
namespace?: string;
|
|
25
|
+
/** Minimum log level */
|
|
26
|
+
level?: LogLevel;
|
|
27
|
+
/** Directory for log files */
|
|
28
|
+
logDir?: string;
|
|
29
|
+
/** Enable file logging */
|
|
30
|
+
fileLogging?: boolean;
|
|
31
|
+
/** Enable console output */
|
|
32
|
+
consoleOutput?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Default configuration
|
|
36
|
+
const DEFAULT_CONFIG: Required<LoggingConfig> = {
|
|
37
|
+
namespace: 'ControlPanel',
|
|
38
|
+
level: (process.env.LOG_LEVEL as LogLevel) || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
|
|
39
|
+
logDir: process.env.LOG_DIR || './logs',
|
|
40
|
+
fileLogging: process.env.LOG_FILE !== 'false',
|
|
41
|
+
consoleOutput: process.env.LOG_CONSOLE !== 'false',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* File transport for writing logs to disk
|
|
46
|
+
* Used when pino is not available or as additional transport
|
|
47
|
+
*/
|
|
48
|
+
class FileLogTransport implements LogTransport {
|
|
49
|
+
private logPath: string;
|
|
50
|
+
private errorLogPath: string;
|
|
51
|
+
|
|
52
|
+
constructor(logDir: string) {
|
|
53
|
+
this.logPath = resolve(logDir, 'app.log');
|
|
54
|
+
this.errorLogPath = resolve(logDir, 'error.log');
|
|
55
|
+
this.ensureLogDir(logDir);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private ensureLogDir(logDir: string): void {
|
|
59
|
+
const resolvedDir = resolve(logDir);
|
|
60
|
+
if (!existsSync(resolvedDir)) {
|
|
61
|
+
mkdirSync(resolvedDir, { recursive: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
handle(level: LogLevel, namespace: string, message: string, context?: Record<string, any>): void {
|
|
66
|
+
const entry = {
|
|
67
|
+
timestamp: new Date().toISOString(),
|
|
68
|
+
level,
|
|
69
|
+
ns: namespace,
|
|
70
|
+
msg: message,
|
|
71
|
+
...context,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const line = JSON.stringify(entry) + '\n';
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// Write to app.log
|
|
78
|
+
appendFileSync(this.logPath, line);
|
|
79
|
+
|
|
80
|
+
// Also write errors to error.log
|
|
81
|
+
if (level === 'error') {
|
|
82
|
+
appendFileSync(this.errorLogPath, line);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Silently fail - don't let logging errors break the application
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Logging subsystem instance
|
|
92
|
+
*/
|
|
93
|
+
class LoggingSubsystem {
|
|
94
|
+
private config: Required<LoggingConfig>;
|
|
95
|
+
private rootLogger: QwickLogger;
|
|
96
|
+
private fileTransport: FileLogTransport | null = null;
|
|
97
|
+
private initialized = false;
|
|
98
|
+
|
|
99
|
+
constructor() {
|
|
100
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
101
|
+
this.rootLogger = getLogger(this.config.namespace);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Initialize the logging subsystem with configuration
|
|
106
|
+
*/
|
|
107
|
+
initialize(config: LoggingConfig = {}): void {
|
|
108
|
+
if (this.initialized) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
113
|
+
|
|
114
|
+
// Set up file transport if enabled
|
|
115
|
+
if (this.config.fileLogging) {
|
|
116
|
+
this.fileTransport = new FileLogTransport(this.config.logDir);
|
|
117
|
+
|
|
118
|
+
// Configure the root logger with file transport
|
|
119
|
+
this.rootLogger.setConfig({
|
|
120
|
+
level: this.config.level,
|
|
121
|
+
disableConsole: !this.config.consoleOutput,
|
|
122
|
+
transports: [this.fileTransport],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Set LOG_FILE_PATH for pino (if available)
|
|
126
|
+
if (!process.env.LOG_FILE_PATH) {
|
|
127
|
+
process.env.LOG_FILE = 'true';
|
|
128
|
+
process.env.LOG_FILE_PATH = resolve(this.config.logDir, 'app.log');
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
this.rootLogger.setConfig({
|
|
132
|
+
level: this.config.level,
|
|
133
|
+
disableConsole: !this.config.consoleOutput,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.initialized = true;
|
|
138
|
+
this.rootLogger.info('Logging subsystem initialized', {
|
|
139
|
+
logDir: this.config.logDir,
|
|
140
|
+
level: this.config.level,
|
|
141
|
+
fileLogging: this.config.fileLogging,
|
|
142
|
+
consoleOutput: this.config.consoleOutput,
|
|
143
|
+
usingPino: this.rootLogger.isUsingPino(),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get a logger for a specific component/plugin
|
|
149
|
+
*/
|
|
150
|
+
getLogger(namespace: string): Logger {
|
|
151
|
+
const childLogger = this.rootLogger.child(namespace);
|
|
152
|
+
|
|
153
|
+
// Return a Logger interface compatible with control-panel types
|
|
154
|
+
return {
|
|
155
|
+
debug: (message: string, data?: Record<string, unknown>) => {
|
|
156
|
+
childLogger.debug(message, data || {});
|
|
157
|
+
},
|
|
158
|
+
info: (message: string, data?: Record<string, unknown>) => {
|
|
159
|
+
childLogger.info(message, data || {});
|
|
160
|
+
},
|
|
161
|
+
warn: (message: string, data?: Record<string, unknown>) => {
|
|
162
|
+
childLogger.warn(message, data || {});
|
|
163
|
+
},
|
|
164
|
+
error: (message: string, data?: Record<string, unknown>) => {
|
|
165
|
+
childLogger.error(message, data || {});
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get the root logger
|
|
172
|
+
*/
|
|
173
|
+
getRootLogger(): Logger {
|
|
174
|
+
return this.getLogger('Core');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if file logging is enabled and working
|
|
179
|
+
*/
|
|
180
|
+
isFileLoggingEnabled(): boolean {
|
|
181
|
+
return this.config.fileLogging && this.fileTransport !== null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the log directory path
|
|
186
|
+
*/
|
|
187
|
+
getLogDir(): string {
|
|
188
|
+
return resolve(this.config.logDir);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get the default log file paths
|
|
193
|
+
*/
|
|
194
|
+
getLogPaths(): { appLog: string; errorLog: string } {
|
|
195
|
+
return {
|
|
196
|
+
appLog: resolve(this.config.logDir, 'app.log'),
|
|
197
|
+
errorLog: resolve(this.config.logDir, 'error.log'),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Singleton instance
|
|
203
|
+
let loggingSubsystem: LoggingSubsystem | null = null;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get or create the logging subsystem
|
|
207
|
+
*/
|
|
208
|
+
export function getLoggingSubsystem(): LoggingSubsystem {
|
|
209
|
+
if (!loggingSubsystem) {
|
|
210
|
+
loggingSubsystem = new LoggingSubsystem();
|
|
211
|
+
}
|
|
212
|
+
return loggingSubsystem;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Initialize the logging subsystem with configuration
|
|
217
|
+
*/
|
|
218
|
+
export function initializeLogging(config: LoggingConfig = {}): LoggingSubsystem {
|
|
219
|
+
const subsystem = getLoggingSubsystem();
|
|
220
|
+
subsystem.initialize(config);
|
|
221
|
+
return subsystem;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get a logger for a specific namespace
|
|
226
|
+
*/
|
|
227
|
+
export function getControlPanelLogger(namespace: string): Logger {
|
|
228
|
+
return getLoggingSubsystem().getLogger(namespace);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Export for convenience
|
|
233
|
+
*/
|
|
234
|
+
export { LoggingSubsystem };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for @qwickapps/server
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Application, RequestHandler, Router } from 'express';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Control Panel Configuration
|
|
11
|
+
*/
|
|
12
|
+
export interface ControlPanelConfig {
|
|
13
|
+
/** Product name displayed in the control panel */
|
|
14
|
+
productName: string;
|
|
15
|
+
|
|
16
|
+
/** Port to run the control panel on */
|
|
17
|
+
port: number;
|
|
18
|
+
|
|
19
|
+
/** Optional: Product version */
|
|
20
|
+
version?: string;
|
|
21
|
+
|
|
22
|
+
/** Optional: Branding configuration */
|
|
23
|
+
branding?: {
|
|
24
|
+
logo?: string;
|
|
25
|
+
primaryColor?: string;
|
|
26
|
+
favicon?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Optional: Authentication configuration */
|
|
30
|
+
auth?: {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
provider: 'basic' | 'jwt' | 'custom';
|
|
33
|
+
users?: Array<{ username: string; password: string }>;
|
|
34
|
+
jwtSecret?: string;
|
|
35
|
+
customMiddleware?: RequestHandler;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Optional: CORS configuration */
|
|
39
|
+
cors?: {
|
|
40
|
+
origins: string[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Optional: Quick links to display */
|
|
44
|
+
links?: Array<{
|
|
45
|
+
label: string;
|
|
46
|
+
url: string;
|
|
47
|
+
icon?: string;
|
|
48
|
+
external?: boolean;
|
|
49
|
+
requiresHealth?: string; // Only show when this health check passes
|
|
50
|
+
}>;
|
|
51
|
+
|
|
52
|
+
/** Optional: Paths to skip body parsing for (useful for proxy middleware) */
|
|
53
|
+
skipBodyParserPaths?: string[];
|
|
54
|
+
|
|
55
|
+
/** Optional: Disable the built-in dashboard HTML (set true when serving a custom React UI) */
|
|
56
|
+
disableDashboard?: boolean;
|
|
57
|
+
|
|
58
|
+
/** Optional: Use rich React UI instead of basic HTML (default: true if dist-ui exists) */
|
|
59
|
+
useRichUI?: boolean;
|
|
60
|
+
|
|
61
|
+
/** Optional: Custom path to a dist-ui folder for serving a custom React UI */
|
|
62
|
+
customUiPath?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Plugin Context - passed to plugins during initialization
|
|
67
|
+
*/
|
|
68
|
+
export interface PluginContext {
|
|
69
|
+
config: ControlPanelConfig;
|
|
70
|
+
app: Application;
|
|
71
|
+
router: Router;
|
|
72
|
+
logger: Logger;
|
|
73
|
+
registerHealthCheck: (check: HealthCheck) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Control Panel Plugin interface
|
|
78
|
+
*/
|
|
79
|
+
export interface ControlPanelPlugin {
|
|
80
|
+
/** Unique plugin name */
|
|
81
|
+
name: string;
|
|
82
|
+
|
|
83
|
+
/** Order for tab display (lower = first) */
|
|
84
|
+
order?: number;
|
|
85
|
+
|
|
86
|
+
/** API routes provided by this plugin */
|
|
87
|
+
routes?: Array<{
|
|
88
|
+
method: 'get' | 'post' | 'put' | 'delete';
|
|
89
|
+
path: string;
|
|
90
|
+
handler: RequestHandler;
|
|
91
|
+
}>;
|
|
92
|
+
|
|
93
|
+
/** Lifecycle: Initialize plugin */
|
|
94
|
+
onInit?: (context: PluginContext) => Promise<void>;
|
|
95
|
+
|
|
96
|
+
/** Lifecycle: Shutdown plugin */
|
|
97
|
+
onShutdown?: () => Promise<void>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Health Check types
|
|
102
|
+
*/
|
|
103
|
+
export type HealthCheckType = 'http' | 'tcp' | 'custom';
|
|
104
|
+
export type HealthStatus = 'healthy' | 'degraded' | 'unhealthy' | 'unknown';
|
|
105
|
+
|
|
106
|
+
export interface HealthCheckResult {
|
|
107
|
+
status: HealthStatus;
|
|
108
|
+
latency?: number;
|
|
109
|
+
message?: string;
|
|
110
|
+
lastChecked: Date;
|
|
111
|
+
details?: Record<string, unknown>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface HealthCheck {
|
|
115
|
+
name: string;
|
|
116
|
+
type: HealthCheckType;
|
|
117
|
+
/** For http checks */
|
|
118
|
+
url?: string;
|
|
119
|
+
/** For tcp checks */
|
|
120
|
+
host?: string;
|
|
121
|
+
port?: number;
|
|
122
|
+
/** Check interval in ms */
|
|
123
|
+
interval?: number;
|
|
124
|
+
/** Timeout in ms */
|
|
125
|
+
timeout?: number;
|
|
126
|
+
/** For custom checks */
|
|
127
|
+
check?: () => Promise<{ healthy: boolean; latency?: number; details?: Record<string, unknown> }>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Log Source Configuration
|
|
132
|
+
*/
|
|
133
|
+
export interface LogSource {
|
|
134
|
+
name: string;
|
|
135
|
+
type: 'file' | 'api';
|
|
136
|
+
path?: string; // For file sources
|
|
137
|
+
url?: string; // For API sources
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Config Display Configuration
|
|
142
|
+
*/
|
|
143
|
+
export interface ConfigDisplayOptions {
|
|
144
|
+
/** Environment variables to show */
|
|
145
|
+
show: string[];
|
|
146
|
+
/** Environment variables to mask */
|
|
147
|
+
mask: string[];
|
|
148
|
+
/** Validation rules */
|
|
149
|
+
validate?: Array<{
|
|
150
|
+
key: string;
|
|
151
|
+
required?: boolean;
|
|
152
|
+
pattern?: RegExp;
|
|
153
|
+
minLength?: number;
|
|
154
|
+
}>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Simple logger interface
|
|
159
|
+
*/
|
|
160
|
+
export interface Logger {
|
|
161
|
+
debug: (message: string, data?: Record<string, unknown>) => void;
|
|
162
|
+
info: (message: string, data?: Record<string, unknown>) => void;
|
|
163
|
+
warn: (message: string, data?: Record<string, unknown>) => void;
|
|
164
|
+
error: (message: string, data?: Record<string, unknown>) => void;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Control Panel Instance
|
|
169
|
+
*/
|
|
170
|
+
export interface ControlPanelInstance {
|
|
171
|
+
/** Express application */
|
|
172
|
+
app: Application;
|
|
173
|
+
|
|
174
|
+
/** Start the control panel server */
|
|
175
|
+
start: () => Promise<void>;
|
|
176
|
+
|
|
177
|
+
/** Stop the control panel server */
|
|
178
|
+
stop: () => Promise<void>;
|
|
179
|
+
|
|
180
|
+
/** Register a plugin */
|
|
181
|
+
registerPlugin: (plugin: ControlPanelPlugin) => Promise<void>;
|
|
182
|
+
|
|
183
|
+
/** Get health check results */
|
|
184
|
+
getHealthStatus: () => Record<string, HealthCheckResult>;
|
|
185
|
+
|
|
186
|
+
/** Get diagnostics for AI agents */
|
|
187
|
+
getDiagnostics: () => DiagnosticsReport;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Diagnostics Report for AI agents
|
|
192
|
+
*/
|
|
193
|
+
export interface DiagnosticsReport {
|
|
194
|
+
timestamp: string;
|
|
195
|
+
product: string;
|
|
196
|
+
version?: string;
|
|
197
|
+
uptime: number;
|
|
198
|
+
health: Record<string, HealthCheckResult>;
|
|
199
|
+
system: {
|
|
200
|
+
nodeVersion: string;
|
|
201
|
+
platform: string;
|
|
202
|
+
arch: string;
|
|
203
|
+
memory: {
|
|
204
|
+
total: number;
|
|
205
|
+
used: number;
|
|
206
|
+
free: number;
|
|
207
|
+
};
|
|
208
|
+
cpu: {
|
|
209
|
+
usage: number;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
config?: Record<string, string>;
|
|
213
|
+
logs?: {
|
|
214
|
+
startup: string[];
|
|
215
|
+
recent: string[];
|
|
216
|
+
errors: string[];
|
|
217
|
+
};
|
|
218
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @qwickapps/server
|
|
3
|
+
*
|
|
4
|
+
* Independent control panel and management UI for QwickApps services
|
|
5
|
+
* Runs on a separate port, provides health checks, logs, config, and diagnostics
|
|
6
|
+
*
|
|
7
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Core exports
|
|
11
|
+
export { createControlPanel } from './core/control-panel.js';
|
|
12
|
+
export { createGateway } from './core/gateway.js';
|
|
13
|
+
export { HealthManager } from './core/health-manager.js';
|
|
14
|
+
|
|
15
|
+
// Logging exports
|
|
16
|
+
export {
|
|
17
|
+
initializeLogging,
|
|
18
|
+
getControlPanelLogger,
|
|
19
|
+
getLoggingSubsystem,
|
|
20
|
+
} from './core/logging.js';
|
|
21
|
+
export type { LoggingConfig } from './core/logging.js';
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
ControlPanelConfig,
|
|
25
|
+
ControlPanelPlugin,
|
|
26
|
+
ControlPanelInstance,
|
|
27
|
+
PluginContext,
|
|
28
|
+
HealthCheck,
|
|
29
|
+
HealthCheckType,
|
|
30
|
+
HealthCheckResult,
|
|
31
|
+
HealthStatus,
|
|
32
|
+
LogSource,
|
|
33
|
+
ConfigDisplayOptions,
|
|
34
|
+
Logger,
|
|
35
|
+
DiagnosticsReport,
|
|
36
|
+
} from './core/types.js';
|
|
37
|
+
export type {
|
|
38
|
+
GatewayConfig,
|
|
39
|
+
GatewayInstance,
|
|
40
|
+
ServiceFactory,
|
|
41
|
+
} from './core/gateway.js';
|
|
42
|
+
|
|
43
|
+
// Built-in plugins
|
|
44
|
+
export {
|
|
45
|
+
createHealthPlugin,
|
|
46
|
+
createLogsPlugin,
|
|
47
|
+
createConfigPlugin,
|
|
48
|
+
createDiagnosticsPlugin,
|
|
49
|
+
} from './plugins/index.js';
|
|
50
|
+
export type {
|
|
51
|
+
HealthPluginConfig,
|
|
52
|
+
LogsPluginConfig,
|
|
53
|
+
ConfigPluginConfig,
|
|
54
|
+
DiagnosticsPluginConfig,
|
|
55
|
+
} from './plugins/index.js';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Plugin
|
|
3
|
+
*
|
|
4
|
+
* Displays configuration and environment variables with secret masking
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2025 QwickApps.com. All rights reserved.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Request, Response } from 'express';
|
|
10
|
+
import type { ControlPanelPlugin, ConfigDisplayOptions, PluginContext } from '../core/types.js';
|
|
11
|
+
|
|
12
|
+
export interface ConfigPluginConfig extends ConfigDisplayOptions {}
|
|
13
|
+
|
|
14
|
+
interface ConfigValidationResult {
|
|
15
|
+
key: string;
|
|
16
|
+
valid: boolean;
|
|
17
|
+
message?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a config display plugin
|
|
22
|
+
*/
|
|
23
|
+
export function createConfigPlugin(config: ConfigPluginConfig): ControlPanelPlugin {
|
|
24
|
+
return {
|
|
25
|
+
name: 'config',
|
|
26
|
+
order: 30,
|
|
27
|
+
|
|
28
|
+
routes: [
|
|
29
|
+
{
|
|
30
|
+
method: 'get',
|
|
31
|
+
path: '/config',
|
|
32
|
+
handler: (_req: Request, res: Response) => {
|
|
33
|
+
const envVars: Record<string, string> = {};
|
|
34
|
+
|
|
35
|
+
// Get visible env vars
|
|
36
|
+
for (const key of config.show) {
|
|
37
|
+
const value = process.env[key];
|
|
38
|
+
if (value !== undefined) {
|
|
39
|
+
// Mask if in mask list
|
|
40
|
+
if (config.mask.some((m) => key.toLowerCase().includes(m.toLowerCase()))) {
|
|
41
|
+
envVars[key] = maskValue(value);
|
|
42
|
+
} else {
|
|
43
|
+
envVars[key] = value;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
envVars[key] = '<not set>';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
res.json({
|
|
51
|
+
environment: process.env.NODE_ENV || 'development',
|
|
52
|
+
config: envVars,
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
method: 'get',
|
|
58
|
+
path: '/config/validate',
|
|
59
|
+
handler: (_req: Request, res: Response) => {
|
|
60
|
+
const results: ConfigValidationResult[] = [];
|
|
61
|
+
let allValid = true;
|
|
62
|
+
|
|
63
|
+
if (config.validate) {
|
|
64
|
+
for (const rule of config.validate) {
|
|
65
|
+
const value = process.env[rule.key];
|
|
66
|
+
let valid = true;
|
|
67
|
+
let message: string | undefined;
|
|
68
|
+
|
|
69
|
+
// Required check
|
|
70
|
+
if (rule.required && !value) {
|
|
71
|
+
valid = false;
|
|
72
|
+
message = `Required environment variable "${rule.key}" is not set`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Pattern check
|
|
76
|
+
if (valid && value && rule.pattern && !rule.pattern.test(value)) {
|
|
77
|
+
valid = false;
|
|
78
|
+
message = `Environment variable "${rule.key}" does not match expected pattern`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Min length check
|
|
82
|
+
if (valid && value && rule.minLength && value.length < rule.minLength) {
|
|
83
|
+
valid = false;
|
|
84
|
+
message = `Environment variable "${rule.key}" is too short (min ${rule.minLength} chars)`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!valid) {
|
|
88
|
+
allValid = false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
results.push({ key: rule.key, valid, message });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
res.json({
|
|
96
|
+
valid: allValid,
|
|
97
|
+
results,
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
|
|
103
|
+
async onInit(context: PluginContext): Promise<void> {
|
|
104
|
+
context.logger.info(`[ConfigPlugin] Initialized with ${config.show.length} visible vars`);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Mask a sensitive value
|
|
111
|
+
*/
|
|
112
|
+
function maskValue(value: string): string {
|
|
113
|
+
if (value.length <= 4) {
|
|
114
|
+
return '****';
|
|
115
|
+
}
|
|
116
|
+
return value.substring(0, 2) + '*'.repeat(value.length - 4) + value.substring(value.length - 2);
|
|
117
|
+
}
|