parse-dashboard-analytics 7.4.1 → 7.4.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/Parse-Dashboard/Authentication.js +9 -4
- package/Parse-Dashboard/CLI/mfa.js +11 -5
- package/Parse-Dashboard/app.js +89 -20
- package/Parse-Dashboard/browser-control/BrowserControlAPI.js +468 -0
- package/Parse-Dashboard/browser-control/BrowserEventStream.js +294 -0
- package/Parse-Dashboard/browser-control/BrowserSessionManager.js +304 -0
- package/Parse-Dashboard/browser-control/README.md +852 -0
- package/Parse-Dashboard/browser-control/ServerOrchestrator.js +334 -0
- package/Parse-Dashboard/browser-control/index.js +27 -0
- package/Parse-Dashboard/browser-control/setup.js +405 -0
- package/Parse-Dashboard/public/bundles/120.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/183.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/19.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/221.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/372.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/448.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/573.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/578.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/584.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/629.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/643.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/647.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/701.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/729.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/75.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/753.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/817.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/844.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/862.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/866.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/874.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/881.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/929.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/950.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/97.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/976.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/dashboard.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/dashboard.bundle.js.LICENSE.txt +27 -33
- package/Parse-Dashboard/public/bundles/login.bundle.js +1 -1
- package/Parse-Dashboard/public/bundles/sprites.svg +294 -154
- package/Parse-Dashboard/server.js +33 -2
- package/README.md +475 -117
- package/package.json +68 -53
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const express = require('express');
|
|
3
|
+
|
|
4
|
+
// ESM-only modules - use .default for CommonJS compatibility
|
|
5
|
+
const getPortModule = require('get-port');
|
|
6
|
+
const getPort = getPortModule.default || getPortModule;
|
|
7
|
+
const fetchModule = require('node-fetch');
|
|
8
|
+
const fetch = fetchModule.default || fetchModule;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ServerOrchestrator
|
|
12
|
+
*
|
|
13
|
+
* Manages Parse Server and Parse Dashboard instances for browser testing.
|
|
14
|
+
* Handles starting servers on dynamic ports, health checks, and cleanup.
|
|
15
|
+
*
|
|
16
|
+
* Features:
|
|
17
|
+
* - Programmatically start Parse Server via child process
|
|
18
|
+
* - Start Parse Dashboard on available port
|
|
19
|
+
* - Health check utilities to wait for server readiness
|
|
20
|
+
* - Clean shutdown and resource cleanup
|
|
21
|
+
*/
|
|
22
|
+
class ServerOrchestrator {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.parseServerProcess = null;
|
|
25
|
+
this.dashboardServer = null;
|
|
26
|
+
this.parseServerConfig = null;
|
|
27
|
+
this.dashboardConfig = null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Start Parse Server programmatically
|
|
32
|
+
* @param {Object} options - Configuration options
|
|
33
|
+
* @param {number} options.port - Port for Parse Server (default: find available)
|
|
34
|
+
* @param {string} options.appId - Application ID (default: 'testAppId')
|
|
35
|
+
* @param {string} options.masterKey - Master key (default: 'testMasterKey')
|
|
36
|
+
* @param {string} options.databaseURI - Database URI (default: 'mongodb://localhost:27017/test')
|
|
37
|
+
* @param {string} options.mountPath - Mount path (default: '/parse')
|
|
38
|
+
* @returns {Promise<Object>} - { process, port, serverURL, appId, masterKey }
|
|
39
|
+
*/
|
|
40
|
+
async startParseServer(options = {}) {
|
|
41
|
+
// Find available port
|
|
42
|
+
const port = options.port || await getPort({ port: 1337 });
|
|
43
|
+
const appId = options.appId || 'testAppId';
|
|
44
|
+
const masterKey = options.masterKey || 'testMasterKey';
|
|
45
|
+
const databaseURI = options.databaseURI || 'mongodb://localhost:27017/test';
|
|
46
|
+
const mountPath = options.mountPath || '/parse';
|
|
47
|
+
const serverURL = `http://localhost:${port}${mountPath}`;
|
|
48
|
+
|
|
49
|
+
// Check if parse-server is available
|
|
50
|
+
try {
|
|
51
|
+
require.resolve('parse-server');
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error(
|
|
54
|
+
'parse-server is not installed. Please install it with: npm install parse-server --save-dev'
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
// Spawn parse-server process
|
|
60
|
+
const parseServer = spawn('npx', [
|
|
61
|
+
'parse-server',
|
|
62
|
+
'--appId', appId,
|
|
63
|
+
'--masterKey', masterKey,
|
|
64
|
+
'--databaseURI', databaseURI,
|
|
65
|
+
'--port', port.toString(),
|
|
66
|
+
'--mountPath', mountPath,
|
|
67
|
+
'--serverURL', serverURL
|
|
68
|
+
], {
|
|
69
|
+
stdio: 'pipe',
|
|
70
|
+
env: { ...process.env }
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let serverStarted = false;
|
|
74
|
+
let startupOutput = '';
|
|
75
|
+
|
|
76
|
+
// Collect stdout for diagnostics (but don't use for startup detection)
|
|
77
|
+
parseServer.stdout.on('data', (data) => {
|
|
78
|
+
startupOutput += data.toString();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
parseServer.stderr.on('data', (data) => {
|
|
82
|
+
console.error('[Parse Server Error]:', data.toString());
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
parseServer.on('error', (error) => {
|
|
86
|
+
if (!serverStarted) {
|
|
87
|
+
reject(new Error(`Failed to start Parse Server: ${error.message}`));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
parseServer.on('exit', (code) => {
|
|
92
|
+
if (!serverStarted && code !== 0) {
|
|
93
|
+
reject(new Error(`Parse Server exited with code ${code}. Output: ${startupOutput}`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Build health check URL from configured mount path
|
|
98
|
+
// Normalize mount path: ensure single leading slash, no trailing slash
|
|
99
|
+
const normalizedMountPath = mountPath.startsWith('/') ? mountPath : `/${mountPath}`;
|
|
100
|
+
const healthPath = normalizedMountPath.endsWith('/')
|
|
101
|
+
? `${normalizedMountPath}health`
|
|
102
|
+
: `${normalizedMountPath}/health`;
|
|
103
|
+
const healthUrl = `http://localhost:${port}${healthPath}`;
|
|
104
|
+
|
|
105
|
+
// Poll health endpoint to detect when server is ready (more reliable than string matching)
|
|
106
|
+
this.waitForServer(healthUrl, 20000)
|
|
107
|
+
.then(() => {
|
|
108
|
+
serverStarted = true;
|
|
109
|
+
this.parseServerProcess = parseServer;
|
|
110
|
+
this.parseServerConfig = {
|
|
111
|
+
process: parseServer,
|
|
112
|
+
port,
|
|
113
|
+
serverURL,
|
|
114
|
+
appId,
|
|
115
|
+
masterKey,
|
|
116
|
+
databaseURI,
|
|
117
|
+
mountPath
|
|
118
|
+
};
|
|
119
|
+
resolve(this.parseServerConfig);
|
|
120
|
+
})
|
|
121
|
+
.catch(err => {
|
|
122
|
+
if (!serverStarted) {
|
|
123
|
+
parseServer.kill();
|
|
124
|
+
reject(new Error(`Parse Server health check failed: ${err.message}. Output: ${startupOutput}`));
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Start Parse Dashboard
|
|
132
|
+
* @param {Object} parseServerConfig - Parse Server configuration from startParseServer()
|
|
133
|
+
* @param {Object} options - Dashboard options
|
|
134
|
+
* @param {number} options.port - Port for Dashboard (default: find available)
|
|
135
|
+
* @param {string} options.appName - App name (default: 'TestApp')
|
|
136
|
+
* @param {string} options.mount - Mount path (default: '/')
|
|
137
|
+
* @returns {Promise<Object>} - { server, port, url }
|
|
138
|
+
*/
|
|
139
|
+
async startDashboard(parseServerConfig, options = {}) {
|
|
140
|
+
if (!parseServerConfig) {
|
|
141
|
+
throw new Error('parseServerConfig is required');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Find available port
|
|
145
|
+
const port = options.port || await getPort({ port: 4040 });
|
|
146
|
+
const appName = options.appName || 'TestApp';
|
|
147
|
+
const mount = options.mount || '/';
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Import Parse Dashboard app
|
|
151
|
+
const ParseDashboard = require('../app');
|
|
152
|
+
|
|
153
|
+
// Dashboard settings
|
|
154
|
+
const dashboardSettings = {
|
|
155
|
+
apps: [{
|
|
156
|
+
serverURL: parseServerConfig.serverURL,
|
|
157
|
+
appId: parseServerConfig.appId,
|
|
158
|
+
masterKey: parseServerConfig.masterKey,
|
|
159
|
+
appName: appName
|
|
160
|
+
}],
|
|
161
|
+
trustProxy: 1
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Create Express app
|
|
165
|
+
const app = express();
|
|
166
|
+
|
|
167
|
+
// Mount Parse Dashboard
|
|
168
|
+
app.use(mount, ParseDashboard(dashboardSettings, { allowInsecureHTTP: true }));
|
|
169
|
+
|
|
170
|
+
// Start server
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const server = app.listen(port, () => {
|
|
173
|
+
const url = `http://localhost:${port}${mount}`;
|
|
174
|
+
this.dashboardServer = server;
|
|
175
|
+
this.dashboardConfig = {
|
|
176
|
+
server,
|
|
177
|
+
port,
|
|
178
|
+
url,
|
|
179
|
+
mount,
|
|
180
|
+
appName,
|
|
181
|
+
settings: dashboardSettings
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
console.log(`Parse Dashboard started at ${url}`);
|
|
185
|
+
resolve(this.dashboardConfig);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Handle errors via server's 'error' event (correct approach)
|
|
189
|
+
server.on('error', (err) => {
|
|
190
|
+
reject(new Error(`Failed to start Dashboard: ${err.message}`));
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new Error(`Failed to start Dashboard: ${error.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Wait for a server to respond to health check
|
|
200
|
+
* @param {string} healthUrl - Health check URL
|
|
201
|
+
* @param {number} timeout - Timeout in milliseconds (default: 10000)
|
|
202
|
+
* @returns {Promise<void>}
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
async waitForServer(healthUrl, timeout = 10000) {
|
|
206
|
+
const startTime = Date.now();
|
|
207
|
+
const interval = 500; // Check every 500ms
|
|
208
|
+
|
|
209
|
+
while (Date.now() - startTime < timeout) {
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetch(healthUrl);
|
|
212
|
+
if (response.ok || response.status === 200 || response.status === 404) {
|
|
213
|
+
return; // Server is responding
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
// Server not ready yet, continue waiting
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Wait before next attempt
|
|
220
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw new Error(`Server health check timeout after ${timeout}ms`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Stop Parse Server
|
|
228
|
+
* @returns {Promise<void>}
|
|
229
|
+
*/
|
|
230
|
+
async stopParseServer() {
|
|
231
|
+
if (!this.parseServerProcess) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Capture reference to avoid race conditions
|
|
236
|
+
const proc = this.parseServerProcess;
|
|
237
|
+
|
|
238
|
+
// Check if process already exited
|
|
239
|
+
if (proc.exitCode !== null || proc.killed) {
|
|
240
|
+
this.parseServerProcess = null;
|
|
241
|
+
this.parseServerConfig = null;
|
|
242
|
+
console.log('Parse Server already stopped');
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return new Promise((resolve) => {
|
|
247
|
+
let forceKillTimeout = null;
|
|
248
|
+
|
|
249
|
+
// Attach exit handler BEFORE sending signals
|
|
250
|
+
proc.once('exit', () => {
|
|
251
|
+
// Clear the force kill timeout
|
|
252
|
+
if (forceKillTimeout) {
|
|
253
|
+
clearTimeout(forceKillTimeout);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Null references exactly once
|
|
257
|
+
this.parseServerProcess = null;
|
|
258
|
+
this.parseServerConfig = null;
|
|
259
|
+
console.log('Parse Server stopped');
|
|
260
|
+
resolve();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Try graceful shutdown first
|
|
264
|
+
proc.kill('SIGTERM');
|
|
265
|
+
|
|
266
|
+
// Force kill after 5 seconds if still running
|
|
267
|
+
forceKillTimeout = setTimeout(() => {
|
|
268
|
+
// Use captured reference, not this.parseServerProcess
|
|
269
|
+
if (!proc.killed && proc.exitCode === null) {
|
|
270
|
+
proc.kill('SIGKILL');
|
|
271
|
+
}
|
|
272
|
+
}, 5000);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Stop Parse Dashboard
|
|
278
|
+
* @returns {Promise<void>}
|
|
279
|
+
*/
|
|
280
|
+
async stopDashboard() {
|
|
281
|
+
if (!this.dashboardServer) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Capture reference to avoid race conditions
|
|
286
|
+
const server = this.dashboardServer;
|
|
287
|
+
|
|
288
|
+
return new Promise((resolve) => {
|
|
289
|
+
server.close(() => {
|
|
290
|
+
this.dashboardServer = null;
|
|
291
|
+
this.dashboardConfig = null;
|
|
292
|
+
console.log('Parse Dashboard stopped');
|
|
293
|
+
resolve();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Stop all servers
|
|
300
|
+
* @returns {Promise<void>}
|
|
301
|
+
*/
|
|
302
|
+
async stopAll() {
|
|
303
|
+
await Promise.all([
|
|
304
|
+
this.stopDashboard(),
|
|
305
|
+
this.stopParseServer()
|
|
306
|
+
]);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get current server status
|
|
311
|
+
* @returns {Object} - Status information
|
|
312
|
+
*/
|
|
313
|
+
getStatus() {
|
|
314
|
+
return {
|
|
315
|
+
parseServer: this.parseServerConfig ? {
|
|
316
|
+
running: true,
|
|
317
|
+
port: this.parseServerConfig.port,
|
|
318
|
+
serverURL: this.parseServerConfig.serverURL,
|
|
319
|
+
appId: this.parseServerConfig.appId
|
|
320
|
+
} : {
|
|
321
|
+
running: false
|
|
322
|
+
},
|
|
323
|
+
dashboard: this.dashboardConfig ? {
|
|
324
|
+
running: true,
|
|
325
|
+
port: this.dashboardConfig.port,
|
|
326
|
+
url: this.dashboardConfig.url
|
|
327
|
+
} : {
|
|
328
|
+
running: false
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = ServerOrchestrator;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Control for Parse Dashboard
|
|
3
|
+
*
|
|
4
|
+
* Real-time browser automation system for AI agents to verify features during development.
|
|
5
|
+
* This is a development-time debugging tool, NOT a replacement for unit/E2E tests.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npm run browser-control
|
|
9
|
+
*
|
|
10
|
+
* API:
|
|
11
|
+
* HTTP: http://localhost:4040/browser-control/
|
|
12
|
+
* WebSocket: ws://localhost:4040/browser-control/stream/:sessionId
|
|
13
|
+
*
|
|
14
|
+
* @module browser-control
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const BrowserSessionManager = require('./BrowserSessionManager');
|
|
18
|
+
const BrowserControlAPI = require('./BrowserControlAPI');
|
|
19
|
+
const BrowserEventStream = require('./BrowserEventStream');
|
|
20
|
+
const ServerOrchestrator = require('./ServerOrchestrator');
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
BrowserSessionManager,
|
|
24
|
+
BrowserControlAPI,
|
|
25
|
+
BrowserEventStream,
|
|
26
|
+
ServerOrchestrator
|
|
27
|
+
};
|