@xenon-device-management/xenon 1.3.0 → 1.4.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 (58) hide show
  1. package/README.md +17 -0
  2. package/lib/package.json +1 -1
  3. package/lib/public/assets/{Layouts-D6IPfwoe.js → Layouts-BV_fQEKa.js} +1 -1
  4. package/lib/public/assets/{ai-settings-CflyFKan.js → ai-settings-BnoeJplt.js} +2 -2
  5. package/lib/public/assets/api-keys-TLWTJYrE.js +1 -0
  6. package/lib/public/assets/{apps-Da4dvQ1J.js → apps-BGCcB8_p.js} +2 -2
  7. package/lib/public/assets/arrow-left-B6-SH02F.js +6 -0
  8. package/lib/public/assets/{badge-BNR9umdu.js → badge-BtQKmQsj.js} +1 -1
  9. package/lib/public/assets/button-CNLKazSa.js +16 -0
  10. package/lib/public/assets/{calendar-fehdBtun.js → calendar-CPNkf0zl.js} +1 -1
  11. package/lib/public/assets/{clock-DrpxSvCL.js → clock-C8ZaLIQA.js} +1 -1
  12. package/lib/public/assets/copy-DCAmOyY9.js +11 -0
  13. package/lib/public/assets/{cpu-tuyMVZ4I.js → cpu-D3PzrmhN.js} +1 -1
  14. package/lib/public/assets/{device-explorer-DOfRH3zm.js → device-explorer-BfFe3bRe.js} +36 -41
  15. package/lib/public/assets/{index-BaTiUCeH.js → index-Do3u3FMx.js} +38 -28
  16. package/lib/public/assets/{lock-C6CoqSr2.js → lock-DNzAbx6Q.js} +1 -1
  17. package/lib/public/assets/{maintenance-settings-CM2oC7-i.js → maintenance-settings-yHAKnfz_.js} +2 -2
  18. package/lib/public/assets/{mouse-pointer-2-CXdnjXIg.js → mouse-pointer-2-B0KBsfLG.js} +1 -1
  19. package/lib/public/assets/{plus-B4B1Hukt.js → plus-DecbehZh.js} +1 -1
  20. package/lib/public/assets/{session-dashboard-B5OPMTz5.js → session-dashboard-CJvDLc6D.js} +17 -22
  21. package/lib/public/assets/settings-DjTQTjSq.js +1 -0
  22. package/lib/public/assets/shield-alert-Br1Ebl1G.js +6 -0
  23. package/lib/public/assets/teams-LILlQka3.js +1 -0
  24. package/lib/public/assets/{trash-2-NJMZJ2Ol.js → trash-2-DEafUZlN.js} +1 -1
  25. package/lib/public/assets/{useSocket-Ct2wo7P2.js → useSocket-Dzv_TTsO.js} +1 -1
  26. package/lib/public/assets/{webhook-settings-Cz35-QJ7.js → webhook-settings-CVwvtwI7.js} +1 -1
  27. package/lib/public/assets/{zap-CssSMAN5.js → zap-DCsIYdwr.js} +1 -1
  28. package/lib/public/index.html +1 -1
  29. package/lib/src/XenonCapabilityManager.js +30 -0
  30. package/lib/src/app/index.js +3 -0
  31. package/lib/src/app/routers/apikeys.js +5 -2
  32. package/lib/src/app/routers/grid.js +46 -2
  33. package/lib/src/app/routers/teams.js +66 -0
  34. package/lib/src/dashboard/event-manager.js +2 -0
  35. package/lib/src/data-service/device-store.js +12 -0
  36. package/lib/src/data-service/prisma-store.js +7 -0
  37. package/lib/src/device-utils.js +7 -1
  38. package/lib/src/generated/client/edge.js +18 -7
  39. package/lib/src/generated/client/index-browser.js +15 -4
  40. package/lib/src/generated/client/index.d.ts +2613 -391
  41. package/lib/src/generated/client/index.js +18 -7
  42. package/lib/src/generated/client/package.json +1 -1
  43. package/lib/src/generated/client/schema.prisma +18 -0
  44. package/lib/src/generated/client/wasm.js +15 -4
  45. package/lib/src/logger.js +17 -0
  46. package/lib/src/middleware/apiKeyMiddleware.js +22 -2
  47. package/lib/src/services/ApiKeyService.js +20 -1
  48. package/lib/src/services/ServerManager.js +10 -3
  49. package/lib/src/services/SessionLifecycleService.js +53 -4
  50. package/lib/src/services/TeamService.js +100 -0
  51. package/lib/src/sessions/XenonSession.js +3 -0
  52. package/lib/tsconfig.tsbuildinfo +1 -1
  53. package/package.json +1 -1
  54. package/prisma/migrations/20260424033547_add_teams/migration.sql +101 -0
  55. package/prisma/schema.prisma +18 -0
  56. package/lib/public/assets/button-hZFV1ypT.js +0 -26
  57. package/lib/public/assets/settings-BTHP7fj3.js +0 -1
  58. /package/lib/public/assets/{Layouts-DPMls9vh.css → settings-DPMls9vh.css} +0 -0
@@ -14,6 +14,7 @@ const typedi_1 = require("typedi");
14
14
  const ApiKeyService_1 = require("../services/ApiKeyService");
15
15
  const config_1 = require("../config");
16
16
  const SESSION_COOKIE = 'xenon_dashboard_session';
17
+ const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
17
18
  function readSessionCookie(req) {
18
19
  // cookie-parser isn't mounted globally, so parse the header inline.
19
20
  const header = req.headers.cookie;
@@ -31,11 +32,14 @@ function readSessionCookie(req) {
31
32
  }
32
33
  function apiKeyMiddleware(req, res, next) {
33
34
  return __awaiter(this, void 0, void 0, function* () {
35
+ var _a;
34
36
  if (config_1.config.authDisabled === true) {
35
37
  req.apiKey = { id: 'auth-disabled', scopes: 'admin', rateLimit: 100000 };
36
38
  return next();
37
39
  }
38
- const raw = req.headers['x-xenon-api-key'] || readSessionCookie(req);
40
+ const headerKey = req.headers['x-xenon-api-key'];
41
+ const cookieKey = headerKey ? undefined : readSessionCookie(req);
42
+ const raw = headerKey || cookieKey;
39
43
  if (!raw) {
40
44
  return res.status(401).json({ error: 'missing API key' });
41
45
  }
@@ -43,7 +47,23 @@ function apiKeyMiddleware(req, res, next) {
43
47
  if (!row) {
44
48
  return res.status(401).json({ error: 'invalid or revoked API key' });
45
49
  }
46
- req.apiKey = { id: row.id, scopes: row.scopes, rateLimit: row.rateLimit };
50
+ // Slide the dashboard session forward on every authenticated hit so active
51
+ // users don't get kicked out 24h after their original login.
52
+ if (cookieKey) {
53
+ const isSecure = req.secure || req.headers['x-forwarded-proto'] === 'https';
54
+ res.cookie(SESSION_COOKIE, raw, {
55
+ httpOnly: true,
56
+ secure: isSecure,
57
+ sameSite: 'strict',
58
+ maxAge: SESSION_TTL_MS,
59
+ });
60
+ }
61
+ req.apiKey = {
62
+ id: row.id,
63
+ scopes: row.scopes,
64
+ rateLimit: row.rateLimit,
65
+ teamId: (_a = row.teamId) !== null && _a !== void 0 ? _a : null,
66
+ };
47
67
  next();
48
68
  });
49
69
  }
@@ -77,7 +77,7 @@ let ApiKeyService = class ApiKeyService {
77
77
  }
78
78
  create(params) {
79
79
  return __awaiter(this, void 0, void 0, function* () {
80
- var _a;
80
+ var _a, _b;
81
81
  const raw = this.generateRaw();
82
82
  const row = yield prisma_1.prisma.apiKey.create({
83
83
  data: {
@@ -85,6 +85,7 @@ let ApiKeyService = class ApiKeyService {
85
85
  keyHash: this.hash(raw),
86
86
  scopes: params.scopes.join(','),
87
87
  rateLimit: (_a = params.rateLimit) !== null && _a !== void 0 ? _a : 300,
88
+ teamId: (_b = params.teamId) !== null && _b !== void 0 ? _b : null,
88
89
  },
89
90
  });
90
91
  return { id: row.id, raw };
@@ -95,6 +96,24 @@ let ApiKeyService = class ApiKeyService {
95
96
  yield prisma_1.prisma.apiKey.update({ where: { id }, data: { revokedAt: new Date() } });
96
97
  });
97
98
  }
99
+ list() {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ return prisma_1.prisma.apiKey.findMany({
102
+ where: { revokedAt: null },
103
+ select: {
104
+ id: true,
105
+ name: true,
106
+ scopes: true,
107
+ rateLimit: true,
108
+ createdAt: true,
109
+ lastUsedAt: true,
110
+ teamId: true,
111
+ role: true,
112
+ },
113
+ orderBy: { createdAt: 'desc' },
114
+ });
115
+ });
116
+ }
98
117
  };
99
118
  exports.ApiKeyService = ApiKeyService;
100
119
  exports.ApiKeyService = ApiKeyService = __decorate([
@@ -97,7 +97,7 @@ let ServerManager = ServerManager_1 = class ServerManager {
97
97
  const context = typedi_1.Container.get(PluginContext_1.PluginContext);
98
98
  context.setContext(pluginArgs, cliArgs.port, nodeId, cliArgs.basePath || '');
99
99
  yield this.syncDatabaseAndAIConfig(pluginArgs);
100
- yield this.initializeCoreSubsystems(pluginArgs);
100
+ yield this.initializeCoreSubsystems(pluginArgs, cliArgs.port);
101
101
  this.registerRoutes(expressApp, cliArgs, pluginArgs);
102
102
  yield this.bootEmulators(pluginArgs);
103
103
  this.registerDependenciesInContainer(pluginArgs, cliArgs, nodeId);
@@ -194,7 +194,7 @@ let ServerManager = ServerManager_1 = class ServerManager {
194
194
  }
195
195
  });
196
196
  }
197
- initializeCoreSubsystems(pluginArgs) {
197
+ initializeCoreSubsystems(pluginArgs, port) {
198
198
  return __awaiter(this, void 0, void 0, function* () {
199
199
  yield (0, device_utils_1.initializeStorage)();
200
200
  const { runMigrations } = yield Promise.resolve().then(() => __importStar(require('../scripts/run-migrations')));
@@ -202,7 +202,14 @@ let ServerManager = ServerManager_1 = class ServerManager {
202
202
  yield device_store_1.DeviceStoreFactory.getStore().clearStorage();
203
203
  const { ApiKeyService } = yield Promise.resolve().then(() => __importStar(require('./ApiKeyService')));
204
204
  const { config: xenonConfig } = yield Promise.resolve().then(() => __importStar(require('../config')));
205
- yield typedi_1.Container.get(ApiKeyService).bootstrapIfEmpty(xenonConfig.bootstrapKeyPath);
205
+ const raw = yield typedi_1.Container.get(ApiKeyService).bootstrapIfEmpty(xenonConfig.bootstrapKeyPath);
206
+ if (raw) {
207
+ logger_1.default.bootstrapKeyBanner({
208
+ dashboardUrl: `http://${pluginArgs.bindHostOrIp}:${port}/xenon/`,
209
+ key: raw,
210
+ keyFilePath: xenonConfig.bootstrapKeyPath,
211
+ });
212
+ }
206
213
  });
207
214
  }
208
215
  registerRoutes(expressApp, cliArgs, pluginArgs) {
@@ -81,6 +81,7 @@ const session_service_1 = require("../dashboard/services/session-service");
81
81
  const SessionStatus_1 = require("../types/SessionStatus");
82
82
  const SessionType_1 = __importDefault(require("../enums/SessionType"));
83
83
  const async_lock_1 = __importDefault(require("async-lock"));
84
+ const base_driver_1 = require("@appium/base-driver");
84
85
  const commandsQueueGuard = new async_lock_1.default();
85
86
  // Serializes concurrent deleteSession cleanup for the same sessionId so
86
87
  // that onSessionStopped events don't fire twice if two clients race.
@@ -99,6 +100,7 @@ let SessionLifecycleService = class SessionLifecycleService {
99
100
  this.logger.warn('Rejecting new session: hub is draining for shutdown');
100
101
  throw new Error('Hub is shutting down; please retry against a different node');
101
102
  }
103
+ const authResult = yield this.authorizeSessionRequest(caps);
102
104
  const context = typedi_1.Container.get(PluginContext_1.PluginContext);
103
105
  const pluginArgs = context.pluginArgs;
104
106
  this.logger.debug(`📱 pluginArgs: ${JSON.stringify(pluginArgs)}`);
@@ -145,7 +147,7 @@ let SessionLifecycleService = class SessionLifecycleService {
145
147
  this.logger.debug(`📱 Acquiring lock: ${lockName}`);
146
148
  const device = yield commandsQueueGuard.acquire(lockName, () => __awaiter(this, void 0, void 0, function* () {
147
149
  try {
148
- return yield (0, device_utils_1.allocateDeviceForSession)(caps, pluginArgs.deviceAvailabilityTimeoutMs, pluginArgs.deviceAvailabilityQueryIntervalMs, pluginArgs);
150
+ return yield (0, device_utils_1.allocateDeviceForSession)(caps, pluginArgs.deviceAvailabilityTimeoutMs, pluginArgs.deviceAvailabilityQueryIntervalMs, pluginArgs, authResult.scoped ? authResult.callerTeamId : undefined);
149
151
  }
150
152
  catch (err) {
151
153
  yield (0, pending_sessions_service_1.removePendingSession)(pendingSessionId);
@@ -171,7 +173,7 @@ let SessionLifecycleService = class SessionLifecycleService {
171
173
  this.logger.debug(`📱 Removing pending session with capability_id: ${pendingSessionId}`);
172
174
  yield (0, pending_sessions_service_1.removePendingSession)(pendingSessionId);
173
175
  if (this.isCreateSessionResponseInternal(session)) {
174
- yield this.finalizeSession(session, device, caps, driver, isRemoteOrCloudSession);
176
+ yield this.finalizeSession(session, device, caps, driver, isRemoteOrCloudSession, authResult.apiKeyId);
175
177
  }
176
178
  else {
177
179
  yield this.handleSessionFailure(session, device, isRemoteOrCloudSession);
@@ -179,6 +181,52 @@ let SessionLifecycleService = class SessionLifecycleService {
179
181
  return session;
180
182
  });
181
183
  }
184
+ // Verify a WebDriver session request against an API key supplied via the
185
+ // `xenon:accessKey` capability. Today this is best-effort: if no cap is
186
+ // supplied we log a warning and let the request through to avoid breaking
187
+ // pre-existing test clients. A supplied key that is invalid / revoked / has
188
+ // insufficient scope is rejected. Respects the global `authDisabled` flag.
189
+ //
190
+ // Returns the caller's { apiKeyId, callerTeamId } so allocation can filter
191
+ // devices by team. `apiKeyId` is null when auth is disabled or no key was
192
+ // presented (back-compat path); `callerTeamId` is null when the caller has
193
+ // no team (sees the shared pool only).
194
+ authorizeSessionRequest(caps) {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ var _a;
197
+ const { config: xenonConfig } = yield Promise.resolve().then(() => __importStar(require('../config')));
198
+ // `scoped` tells the allocator whether to restrict device candidates by
199
+ // team. False = see everything (admin, authDisabled, back-compat path);
200
+ // true = filter to { null, callerTeamId } where callerTeamId may itself
201
+ // be null (meaning "shared pool only").
202
+ if (xenonConfig.authDisabled === true) {
203
+ return { apiKeyId: null, callerTeamId: null, scoped: false };
204
+ }
205
+ const raw = (0, XenonCapabilityManager_1.extractAccessKeyCap)(caps);
206
+ if (!raw) {
207
+ this.logger.warn('Session created without xenon:accessKey capability. Add `xenon:accessKey` to your Appium capabilities to authenticate this session.');
208
+ return { apiKeyId: null, callerTeamId: null, scoped: false };
209
+ }
210
+ const { ApiKeyService } = yield Promise.resolve().then(() => __importStar(require('./ApiKeyService')));
211
+ const svc = typedi_1.Container.get(ApiKeyService);
212
+ const row = yield svc.verify(raw);
213
+ if (!row || !svc.hasScope(row, ['sessions'])) {
214
+ this.logger.error('Rejecting session: xenon:accessKey is invalid, revoked, or lacks the `sessions` scope');
215
+ throw new base_driver_1.errors.InvalidArgumentError('xenon:accessKey is invalid, revoked, or lacks the `sessions` scope');
216
+ }
217
+ const isAdmin = svc.hasScope(row, ['admin']);
218
+ const callerTeamId = (_a = row.teamId) !== null && _a !== void 0 ? _a : null;
219
+ const requestedTeam = (0, XenonCapabilityManager_1.extractTeamCap)(caps);
220
+ if (requestedTeam) {
221
+ if (!isAdmin && requestedTeam !== callerTeamId) {
222
+ this.logger.error(`Rejecting session: xenon:team=${requestedTeam} but caller key is bound to team ${callerTeamId !== null && callerTeamId !== void 0 ? callerTeamId : '(none)'}`);
223
+ throw new base_driver_1.errors.InvalidArgumentError(`xenon:team '${requestedTeam}' is not allowed for this API key`);
224
+ }
225
+ return { apiKeyId: row.id, callerTeamId: requestedTeam, scoped: !isAdmin };
226
+ }
227
+ return { apiKeyId: row.id, callerTeamId, scoped: !isAdmin };
228
+ });
229
+ }
182
230
  handleLocalWDAProvisioning(device, caps) {
183
231
  return __awaiter(this, void 0, void 0, function* () {
184
232
  if (device.platform === 'ios' && device.realDevice) {
@@ -252,8 +300,8 @@ let SessionLifecycleService = class SessionLifecycleService {
252
300
  delete caps.firstMatch[0]['appium:mjpegServerPort'];
253
301
  }
254
302
  }
255
- finalizeSession(session, device, caps, driver, isRemote) {
256
- return __awaiter(this, void 0, void 0, function* () {
303
+ finalizeSession(session_1, device_1, caps_1, driver_1, isRemote_1) {
304
+ return __awaiter(this, arguments, void 0, function* (session, device, caps, driver, isRemote, apiKeyId = null) {
257
305
  var _a;
258
306
  const sessionId = session.value[0];
259
307
  const sessionResponse = session.value[1];
@@ -284,6 +332,7 @@ let SessionLifecycleService = class SessionLifecycleService {
284
332
  }
285
333
  const freshDevice = yield this.getFreshDevice(device);
286
334
  const sessionInstance = this.createSessionInstance(sessionId, freshDevice, sessionResponse, xenonCapabilities, driver);
335
+ sessionInstance.apiKeyId = apiKeyId;
287
336
  yield this.applyPostSessionLogic(sessionInstance, xenonCapabilities, freshDevice);
288
337
  });
289
338
  }
@@ -0,0 +1,100 @@
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ var __importDefault = (this && this.__importDefault) || function (mod) {
18
+ return (mod && mod.__esModule) ? mod : { "default": mod };
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.TeamService = void 0;
22
+ const typedi_1 = require("typedi");
23
+ const prisma_1 = require("../prisma");
24
+ const logger_1 = __importDefault(require("../logger"));
25
+ let TeamService = class TeamService {
26
+ constructor() {
27
+ this.log = logger_1.default.scope('Team');
28
+ }
29
+ list() {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ const rows = yield prisma_1.prisma.team.findMany({
32
+ orderBy: { createdAt: 'asc' },
33
+ include: {
34
+ _count: { select: { devices: true, apiKeys: true } },
35
+ },
36
+ });
37
+ return rows.map((t) => ({
38
+ id: t.id,
39
+ name: t.name,
40
+ createdAt: t.createdAt,
41
+ deviceCount: t._count.devices,
42
+ memberCount: t._count.apiKeys,
43
+ }));
44
+ });
45
+ }
46
+ create(name) {
47
+ return __awaiter(this, void 0, void 0, function* () {
48
+ const trimmed = name.trim();
49
+ if (!trimmed)
50
+ throw new Error('name required');
51
+ return prisma_1.prisma.team.create({ data: { name: trimmed } });
52
+ });
53
+ }
54
+ delete(id) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ const [devices, apiKeys] = yield Promise.all([
57
+ prisma_1.prisma.device.count({ where: { teamId: id } }),
58
+ prisma_1.prisma.apiKey.count({ where: { teamId: id, revokedAt: null } }),
59
+ ]);
60
+ if (devices > 0 || apiKeys > 0) {
61
+ throw new Error(`Team still has ${devices} device(s) and ${apiKeys} active member(s). Reassign them before deleting.`);
62
+ }
63
+ // Clear the teamId on revoked keys so the FK doesn't block the delete.
64
+ yield prisma_1.prisma.apiKey.updateMany({ where: { teamId: id }, data: { teamId: null } });
65
+ yield prisma_1.prisma.team.delete({ where: { id } });
66
+ });
67
+ }
68
+ listMembers(teamId) {
69
+ return __awaiter(this, void 0, void 0, function* () {
70
+ return prisma_1.prisma.apiKey.findMany({
71
+ where: { teamId, revokedAt: null },
72
+ select: { id: true, name: true, role: true, scopes: true, createdAt: true },
73
+ orderBy: { createdAt: 'asc' },
74
+ });
75
+ });
76
+ }
77
+ addMember(teamId, apiKeyId, role) {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ if (role !== 'admin' && role !== 'member')
80
+ throw new Error('invalid role');
81
+ yield prisma_1.prisma.apiKey.update({
82
+ where: { id: apiKeyId },
83
+ data: { teamId, role },
84
+ });
85
+ });
86
+ }
87
+ removeMember(teamId, apiKeyId) {
88
+ return __awaiter(this, void 0, void 0, function* () {
89
+ // Clear the team link rather than delete the key — scopes still apply.
90
+ yield prisma_1.prisma.apiKey.updateMany({
91
+ where: { id: apiKeyId, teamId },
92
+ data: { teamId: null, role: 'member' },
93
+ });
94
+ });
95
+ }
96
+ };
97
+ exports.TeamService = TeamService;
98
+ exports.TeamService = TeamService = __decorate([
99
+ (0, typedi_1.Service)()
100
+ ], TeamService);
@@ -22,6 +22,9 @@ class XenonSession {
22
22
  this.options = options;
23
23
  this.isStopping = false;
24
24
  this.healthState = SessionHealthState.HEALTHY;
25
+ // Phase 2 audit: which API key created this session (null when auth is
26
+ // disabled or the client didn't present xenon:accessKey).
27
+ this.apiKeyId = null;
25
28
  this.sessionId = options.sessionId;
26
29
  this.xenonOption = options.xenonOption;
27
30
  }
@@ -1 +1 @@
1
- {"root":["../src/InternalHttpClient.ts","../src/PluginContext.ts","../src/XenonCapabilityManager.ts","../src/chromeUtils.ts","../src/config.ts","../src/device-utils.ts","../src/index.ts","../src/logger.ts","../src/plugin.ts","../src/prisma.ts","../src/app/index.ts","../src/app/swagger-docs.ts","../src/app/swagger.ts","../src/app/routers/apikeys.ts","../src/app/routers/apps.ts","../src/app/routers/auth.ts","../src/app/routers/config.ts","../src/app/routers/control.ts","../src/app/routers/dashboard.ts","../src/app/routers/grid.ts","../src/app/routers/processes.ts","../src/app/routers/reservation.ts","../src/app/routers/webhook.ts","../src/auth/nodeSecret.ts","../src/commands/handle.ts","../src/commands/index.ts","../src/dashboard/asset-manager.ts","../src/dashboard/commands.ts","../src/dashboard/event-manager.ts","../src/dashboard/services/app-service.ts","../src/dashboard/services/failure-analysis-service.ts","../src/dashboard/services/session-service.ts","../src/data-service/CircuitBreaker.ts","../src/data-service/config-service.ts","../src/data-service/db.ts","../src/data-service/device-service.ts","../src/data-service/device-store.interface.ts","../src/data-service/device-store.ts","../src/data-service/pending-sessions-service.ts","../src/data-service/pluginArgs.ts","../src/data-service/prisma-service.ts","../src/data-service/prisma-store.ts","../src/data-service/queue-service.ts","../src/data-service/web-config-service.ts","../src/device-managers/AndroidDeviceManager.ts","../src/device-managers/ChromeDriverManager.ts","../src/device-managers/HealthMonitorService.ts","../src/device-managers/IOSDeviceManager.ts","../src/device-managers/NodeDevices.ts","../src/device-managers/iOSTracker.ts","../src/device-managers/index.ts","../src/device-managers/android/AndroidStreamService.ts","../src/device-managers/android/DeviceLockManager.ts","../src/device-managers/cloud/CapabilityManager.ts","../src/device-managers/cloud/Devices.ts","../src/device-managers/ios/IOSDiscoveryService.ts","../src/device-managers/ios/IOSStreamService.ts","../src/device-managers/ios/WDAClient.ts","../src/enums/Capabilities.ts","../src/enums/Cloud.ts","../src/enums/Platform.ts","../src/enums/SessionType.ts","../src/enums/SocketEvents.ts","../src/generated/client/default.d.ts","../src/generated/client/default.js","../src/generated/client/edge.d.ts","../src/generated/client/edge.js","../src/generated/client/index-browser.js","../src/generated/client/index.d.ts","../src/generated/client/index.js","../src/generated/client/wasm.d.ts","../src/generated/client/wasm.js","../src/generated/client/runtime/edge-esm.js","../src/generated/client/runtime/edge.js","../src/generated/client/runtime/index-browser.d.ts","../src/generated/client/runtime/index-browser.js","../src/generated/client/runtime/library.d.ts","../src/generated/client/runtime/library.js","../src/generated/client/runtime/react-native.js","../src/generated/client/runtime/wasm.js","../src/helpers/UniversalMjpegProxy.ts","../src/helpers/index.ts","../src/interceptors/CommandInterceptor.ts","../src/interfaces/ICloudManager.ts","../src/interfaces/IDevice.ts","../src/interfaces/IDeviceFilterOptions.ts","../src/interfaces/IDeviceManager.ts","../src/interfaces/IOptions.ts","../src/interfaces/IPluginArgs.ts","../src/interfaces/ISessionCapability.ts","../src/logging/sessionContext.ts","../src/middleware/apiKeyMiddleware.ts","../src/middleware/csrfMiddleware.ts","../src/middleware/nodeSecretMiddleware.ts","../src/middleware/rateLimitMiddleware.ts","../src/middleware/scopeGuard.ts","../src/profiling/AndroidAppProfiler.ts","../src/proxy/wd-command-proxy.ts","../src/scripts/generate-database-migration.ts","../src/scripts/initialize-database.ts","../src/scripts/install-go-ios.ts","../src/scripts/prepare-prisma.ts","../src/scripts/run-migrations.ts","../src/services/AICommandService.ts","../src/services/AIService.ts","../src/services/ApiKeyService.ts","../src/services/CircuitBreaker.ts","../src/services/CleanupService.ts","../src/services/DeviceReconciler.ts","../src/services/EventBus.ts","../src/services/InspectorService.ts","../src/services/MetricsService.ts","../src/services/NetworkConditioningService.ts","../src/services/NotificationService.ts","../src/services/OrphanSweeper.ts","../src/services/PortAllocator.ts","../src/services/ProcessMetricsService.ts","../src/services/ProcessRegistry.ts","../src/services/RequestLogService.ts","../src/services/ResourceIsolationService.ts","../src/services/SecurityService.ts","../src/services/ServerManager.ts","../src/services/SessionHeartbeatService.ts","../src/services/SessionLifecycleService.ts","../src/services/ShutdownCoordinator.ts","../src/services/SocketClient.ts","../src/services/SocketServer.ts","../src/services/TracingService.ts","../src/services/VideoPipelineService.ts","../src/services/healing/FuzzyXmlHealingProvider.ts","../src/services/healing/HealEtalonService.ts","../src/services/healing/HealedLocatorGenerator.ts","../src/services/healing/HealingMetrics.ts","../src/services/healing/HealingOrchestrator.ts","../src/services/healing/LlmHealingProvider.ts","../src/services/healing/OcrHealingProvider.ts","../src/services/healing/ResilioTreeHealingProvider.ts","../src/services/healing/VisualAiHealingProvider.ts","../src/services/healing/types.ts","../src/services/omni-vision/OmniVisionService.ts","../src/services/omni-vision/VisionAssertionService.ts","../src/sessions/CloudSession.ts","../src/sessions/LocalSession.ts","../src/sessions/RemoteSession.ts","../src/sessions/SessionManager.ts","../src/sessions/XenonSession.ts","../src/types/CLIArgs.ts","../src/types/CloudArgs.ts","../src/types/CloudSchema.ts","../src/types/DeviceType.ts","../src/types/DeviceUpdate.ts","../src/types/IOSDevice.ts","../src/types/Platform.ts","../src/types/SessionStatus.ts","../src/types/express.d.ts","../src/types/healing.d.ts","../src/validators/CapabilityValidator.ts","../test/e2e/e2ehelper.ts","../test/e2e/plugin-harness.ts","../test/e2e/plugin.spec.ts","../test/e2e/startup-verification.spec.ts","../test/e2e/telemetry_verification.spec.ts","../test/e2e/video-recording-test.spec.js","../test/e2e/android/conf.spec.js","../test/e2e/android/conf2.spec.js","../test/e2e/android/conf3.spec.js","../test/e2e/hubnode/forward-request.spec.ts","../test/e2e/hubnode/hubnode.spec.ts","../test/e2e/ios/conf1.spec.js","../test/e2e/ios/conf2.spec.js","../test/helpers/test-container.ts","../test/integration/androidDevices.spec.ts","../test/integration/cliArgs.js","../test/integration/testHelpers.js","../test/integration/ios/01iOSSimulator.spec.ts","../test/integration/ios/02iOSDevices.spec.ts","../test/unit/AndroidDeviceManager.spec.ts","../test/unit/ApiKeyService.test.ts","../test/unit/ChromeDriverManager.spec.js","../test/unit/CleanupService.spec.ts","../test/unit/DeviceModel.spec.ts","../test/unit/FuzzyXmlHealingProvider.test.ts","../test/unit/GetAdbOriginal.js","../test/unit/HealingCascade.test.ts","../test/unit/IOSDeviceManager.spec.js","../test/unit/OrphanSweeper.test.ts","../test/unit/PortAllocator.test.ts","../test/unit/ProcessRegistry.test.ts","../test/unit/RemoteIOs.spec.js","../test/unit/ResilioTreeHealingProvider.test.ts","../test/unit/SessionHeartbeatWrite.test.ts","../test/unit/apiKeyMiddleware.test.ts","../test/unit/commands.spec.js","../test/unit/config.spec.js","../test/unit/device-service.spec.ts","../test/unit/device-utils.spec.ts","../test/unit/helpers.spec.ts","../test/unit/nodeSecretMiddleware.test.ts","../test/unit/omni-vision.spec.ts","../test/unit/plugin.spec.ts","../test/unit/prisma.spec.ts","../test/unit/rateLimitMiddleware.test.ts","../test/unit/fixtures/device.config.js","../test/unit/fixtures/devices.js"],"version":"5.9.3"}
1
+ {"root":["../src/InternalHttpClient.ts","../src/PluginContext.ts","../src/XenonCapabilityManager.ts","../src/chromeUtils.ts","../src/config.ts","../src/device-utils.ts","../src/index.ts","../src/logger.ts","../src/plugin.ts","../src/prisma.ts","../src/app/index.ts","../src/app/swagger-docs.ts","../src/app/swagger.ts","../src/app/routers/apikeys.ts","../src/app/routers/apps.ts","../src/app/routers/auth.ts","../src/app/routers/config.ts","../src/app/routers/control.ts","../src/app/routers/dashboard.ts","../src/app/routers/grid.ts","../src/app/routers/processes.ts","../src/app/routers/reservation.ts","../src/app/routers/teams.ts","../src/app/routers/webhook.ts","../src/auth/nodeSecret.ts","../src/commands/handle.ts","../src/commands/index.ts","../src/dashboard/asset-manager.ts","../src/dashboard/commands.ts","../src/dashboard/event-manager.ts","../src/dashboard/services/app-service.ts","../src/dashboard/services/failure-analysis-service.ts","../src/dashboard/services/session-service.ts","../src/data-service/CircuitBreaker.ts","../src/data-service/config-service.ts","../src/data-service/db.ts","../src/data-service/device-service.ts","../src/data-service/device-store.interface.ts","../src/data-service/device-store.ts","../src/data-service/pending-sessions-service.ts","../src/data-service/pluginArgs.ts","../src/data-service/prisma-service.ts","../src/data-service/prisma-store.ts","../src/data-service/queue-service.ts","../src/data-service/web-config-service.ts","../src/device-managers/AndroidDeviceManager.ts","../src/device-managers/ChromeDriverManager.ts","../src/device-managers/HealthMonitorService.ts","../src/device-managers/IOSDeviceManager.ts","../src/device-managers/NodeDevices.ts","../src/device-managers/iOSTracker.ts","../src/device-managers/index.ts","../src/device-managers/android/AndroidStreamService.ts","../src/device-managers/android/DeviceLockManager.ts","../src/device-managers/cloud/CapabilityManager.ts","../src/device-managers/cloud/Devices.ts","../src/device-managers/ios/IOSDiscoveryService.ts","../src/device-managers/ios/IOSStreamService.ts","../src/device-managers/ios/WDAClient.ts","../src/enums/Capabilities.ts","../src/enums/Cloud.ts","../src/enums/Platform.ts","../src/enums/SessionType.ts","../src/enums/SocketEvents.ts","../src/generated/client/default.d.ts","../src/generated/client/default.js","../src/generated/client/edge.d.ts","../src/generated/client/edge.js","../src/generated/client/index-browser.js","../src/generated/client/index.d.ts","../src/generated/client/index.js","../src/generated/client/wasm.d.ts","../src/generated/client/wasm.js","../src/generated/client/runtime/edge-esm.js","../src/generated/client/runtime/edge.js","../src/generated/client/runtime/index-browser.d.ts","../src/generated/client/runtime/index-browser.js","../src/generated/client/runtime/library.d.ts","../src/generated/client/runtime/library.js","../src/generated/client/runtime/react-native.js","../src/generated/client/runtime/wasm.js","../src/helpers/UniversalMjpegProxy.ts","../src/helpers/index.ts","../src/interceptors/CommandInterceptor.ts","../src/interfaces/ICloudManager.ts","../src/interfaces/IDevice.ts","../src/interfaces/IDeviceFilterOptions.ts","../src/interfaces/IDeviceManager.ts","../src/interfaces/IOptions.ts","../src/interfaces/IPluginArgs.ts","../src/interfaces/ISessionCapability.ts","../src/logging/sessionContext.ts","../src/middleware/apiKeyMiddleware.ts","../src/middleware/csrfMiddleware.ts","../src/middleware/nodeSecretMiddleware.ts","../src/middleware/rateLimitMiddleware.ts","../src/middleware/scopeGuard.ts","../src/profiling/AndroidAppProfiler.ts","../src/proxy/wd-command-proxy.ts","../src/scripts/generate-database-migration.ts","../src/scripts/initialize-database.ts","../src/scripts/install-go-ios.ts","../src/scripts/prepare-prisma.ts","../src/scripts/run-migrations.ts","../src/services/AICommandService.ts","../src/services/AIService.ts","../src/services/ApiKeyService.ts","../src/services/CircuitBreaker.ts","../src/services/CleanupService.ts","../src/services/DeviceReconciler.ts","../src/services/EventBus.ts","../src/services/InspectorService.ts","../src/services/MetricsService.ts","../src/services/NetworkConditioningService.ts","../src/services/NotificationService.ts","../src/services/OrphanSweeper.ts","../src/services/PortAllocator.ts","../src/services/ProcessMetricsService.ts","../src/services/ProcessRegistry.ts","../src/services/RequestLogService.ts","../src/services/ResourceIsolationService.ts","../src/services/SecurityService.ts","../src/services/ServerManager.ts","../src/services/SessionHeartbeatService.ts","../src/services/SessionLifecycleService.ts","../src/services/ShutdownCoordinator.ts","../src/services/SocketClient.ts","../src/services/SocketServer.ts","../src/services/TeamService.ts","../src/services/TracingService.ts","../src/services/VideoPipelineService.ts","../src/services/healing/FuzzyXmlHealingProvider.ts","../src/services/healing/HealEtalonService.ts","../src/services/healing/HealedLocatorGenerator.ts","../src/services/healing/HealingMetrics.ts","../src/services/healing/HealingOrchestrator.ts","../src/services/healing/LlmHealingProvider.ts","../src/services/healing/OcrHealingProvider.ts","../src/services/healing/ResilioTreeHealingProvider.ts","../src/services/healing/VisualAiHealingProvider.ts","../src/services/healing/types.ts","../src/services/omni-vision/OmniVisionService.ts","../src/services/omni-vision/VisionAssertionService.ts","../src/sessions/CloudSession.ts","../src/sessions/LocalSession.ts","../src/sessions/RemoteSession.ts","../src/sessions/SessionManager.ts","../src/sessions/XenonSession.ts","../src/types/CLIArgs.ts","../src/types/CloudArgs.ts","../src/types/CloudSchema.ts","../src/types/DeviceType.ts","../src/types/DeviceUpdate.ts","../src/types/IOSDevice.ts","../src/types/Platform.ts","../src/types/SessionStatus.ts","../src/types/express.d.ts","../src/types/healing.d.ts","../src/validators/CapabilityValidator.ts","../test/e2e/e2ehelper.ts","../test/e2e/plugin-harness.ts","../test/e2e/plugin.spec.ts","../test/e2e/startup-verification.spec.ts","../test/e2e/telemetry_verification.spec.ts","../test/e2e/video-recording-test.spec.js","../test/e2e/android/conf.spec.js","../test/e2e/android/conf2.spec.js","../test/e2e/android/conf3.spec.js","../test/e2e/hubnode/forward-request.spec.ts","../test/e2e/hubnode/hubnode.spec.ts","../test/e2e/ios/conf1.spec.js","../test/e2e/ios/conf2.spec.js","../test/helpers/test-container.ts","../test/integration/androidDevices.spec.ts","../test/integration/cliArgs.js","../test/integration/testHelpers.js","../test/integration/ios/01iOSSimulator.spec.ts","../test/integration/ios/02iOSDevices.spec.ts","../test/unit/AndroidDeviceManager.spec.ts","../test/unit/ApiKeyService.test.ts","../test/unit/ChromeDriverManager.spec.js","../test/unit/CleanupService.spec.ts","../test/unit/DeviceModel.spec.ts","../test/unit/FuzzyXmlHealingProvider.test.ts","../test/unit/GetAdbOriginal.js","../test/unit/HealingCascade.test.ts","../test/unit/IOSDeviceManager.spec.js","../test/unit/OrphanSweeper.test.ts","../test/unit/PortAllocator.test.ts","../test/unit/ProcessRegistry.test.ts","../test/unit/RemoteIOs.spec.js","../test/unit/ResilioTreeHealingProvider.test.ts","../test/unit/SessionHeartbeatWrite.test.ts","../test/unit/apiKeyMiddleware.test.ts","../test/unit/commands.spec.js","../test/unit/config.spec.js","../test/unit/device-service.spec.ts","../test/unit/device-utils.spec.ts","../test/unit/helpers.spec.ts","../test/unit/nodeSecretMiddleware.test.ts","../test/unit/omni-vision.spec.ts","../test/unit/plugin.spec.ts","../test/unit/prisma.spec.ts","../test/unit/rateLimitMiddleware.test.ts","../test/unit/fixtures/device.config.js","../test/unit/fixtures/devices.js"],"version":"5.9.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenon-device-management/xenon",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Xenon - Intelligent Mobile Infrastructure. A self-healing device orchestration platform for Appium.",
5
5
  "main": "./lib/src/index.js",
6
6
  "exports": {
@@ -0,0 +1,101 @@
1
+ -- AlterTable
2
+ ALTER TABLE "Session" ADD COLUMN "api_key_id" TEXT;
3
+
4
+ -- CreateTable
5
+ CREATE TABLE "Team" (
6
+ "id" TEXT NOT NULL PRIMARY KEY,
7
+ "name" TEXT NOT NULL,
8
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
9
+ );
10
+
11
+ -- RedefineTables
12
+ PRAGMA defer_foreign_keys=ON;
13
+ PRAGMA foreign_keys=OFF;
14
+ CREATE TABLE "new_ApiKey" (
15
+ "id" TEXT NOT NULL PRIMARY KEY,
16
+ "name" TEXT NOT NULL,
17
+ "keyHash" TEXT NOT NULL,
18
+ "scopes" TEXT NOT NULL,
19
+ "rateLimit" INTEGER NOT NULL DEFAULT 300,
20
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
21
+ "revokedAt" DATETIME,
22
+ "lastUsedAt" DATETIME,
23
+ "teamId" TEXT,
24
+ "role" TEXT NOT NULL DEFAULT 'member',
25
+ CONSTRAINT "ApiKey_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
26
+ );
27
+ INSERT INTO "new_ApiKey" ("createdAt", "id", "keyHash", "lastUsedAt", "name", "rateLimit", "revokedAt", "scopes") SELECT "createdAt", "id", "keyHash", "lastUsedAt", "name", "rateLimit", "revokedAt", "scopes" FROM "ApiKey";
28
+ DROP TABLE "ApiKey";
29
+ ALTER TABLE "new_ApiKey" RENAME TO "ApiKey";
30
+ CREATE UNIQUE INDEX "ApiKey_keyHash_key" ON "ApiKey"("keyHash");
31
+ CREATE INDEX "ApiKey_teamId_idx" ON "ApiKey"("teamId");
32
+ CREATE TABLE "new_Device" (
33
+ "udid" TEXT NOT NULL,
34
+ "host" TEXT NOT NULL,
35
+ "systemPort" INTEGER,
36
+ "proxyPort" INTEGER,
37
+ "proxyHost" TEXT,
38
+ "wdaLocalPort" INTEGER,
39
+ "name" TEXT DEFAULT 'unknown',
40
+ "state" TEXT DEFAULT 'available',
41
+ "sdk" TEXT DEFAULT 'unknown',
42
+ "platform" TEXT DEFAULT 'unknown',
43
+ "deviceType" TEXT DEFAULT 'real',
44
+ "busy" BOOLEAN DEFAULT false,
45
+ "userBlocked" BOOLEAN DEFAULT false,
46
+ "realDevice" BOOLEAN DEFAULT true,
47
+ "session_id" TEXT,
48
+ "offline" BOOLEAN DEFAULT false,
49
+ "mjpegServerPort" INTEGER,
50
+ "lastCmdExecutedAt" REAL,
51
+ "totalUtilizationTimeMilliSec" REAL NOT NULL DEFAULT 0,
52
+ "sessionStartTime" REAL NOT NULL DEFAULT 0,
53
+ "newCommandTimeout" INTEGER,
54
+ "cloud" TEXT,
55
+ "derivedDataPath" TEXT,
56
+ "chromeDriverPath" TEXT,
57
+ "capability" TEXT,
58
+ "adbRemoteHost" TEXT,
59
+ "adbPort" INTEGER,
60
+ "nodeId" TEXT,
61
+ "screenWidth" TEXT,
62
+ "screenHeight" TEXT,
63
+ "dashboard_link" TEXT,
64
+ "total_session_count" INTEGER DEFAULT 0,
65
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
66
+ "updatedAt" DATETIME NOT NULL,
67
+ "healthCheckError" TEXT,
68
+ "healthStatus" TEXT DEFAULT 'Healthy',
69
+ "lastHealthCheckAt" REAL,
70
+ "batteryLevel" INTEGER,
71
+ "reservationReason" TEXT,
72
+ "reservedBy" TEXT,
73
+ "reservedUntil" REAL,
74
+ "storageFree" TEXT,
75
+ "tags" TEXT,
76
+ "thermalStatus" TEXT,
77
+ "sessionProgress" TEXT DEFAULT '',
78
+ "totalHealedCount" INTEGER DEFAULT 0,
79
+ "ip" TEXT DEFAULT '',
80
+ "cpuArchitecture" TEXT,
81
+ "owning_session_id" TEXT,
82
+ "locked_at" REAL,
83
+ "teamId" TEXT,
84
+
85
+ PRIMARY KEY ("udid", "host"),
86
+ CONSTRAINT "Device_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team" ("id") ON DELETE SET NULL ON UPDATE CASCADE
87
+ );
88
+ INSERT INTO "new_Device" ("adbPort", "adbRemoteHost", "batteryLevel", "busy", "capability", "chromeDriverPath", "cloud", "cpuArchitecture", "createdAt", "dashboard_link", "derivedDataPath", "deviceType", "healthCheckError", "healthStatus", "host", "ip", "lastCmdExecutedAt", "lastHealthCheckAt", "locked_at", "mjpegServerPort", "name", "newCommandTimeout", "nodeId", "offline", "owning_session_id", "platform", "proxyHost", "proxyPort", "realDevice", "reservationReason", "reservedBy", "reservedUntil", "screenHeight", "screenWidth", "sdk", "sessionProgress", "sessionStartTime", "session_id", "state", "storageFree", "systemPort", "tags", "thermalStatus", "totalHealedCount", "totalUtilizationTimeMilliSec", "total_session_count", "udid", "updatedAt", "userBlocked", "wdaLocalPort") SELECT "adbPort", "adbRemoteHost", "batteryLevel", "busy", "capability", "chromeDriverPath", "cloud", "cpuArchitecture", "createdAt", "dashboard_link", "derivedDataPath", "deviceType", "healthCheckError", "healthStatus", "host", "ip", "lastCmdExecutedAt", "lastHealthCheckAt", "locked_at", "mjpegServerPort", "name", "newCommandTimeout", "nodeId", "offline", "owning_session_id", "platform", "proxyHost", "proxyPort", "realDevice", "reservationReason", "reservedBy", "reservedUntil", "screenHeight", "screenWidth", "sdk", "sessionProgress", "sessionStartTime", "session_id", "state", "storageFree", "systemPort", "tags", "thermalStatus", "totalHealedCount", "totalUtilizationTimeMilliSec", "total_session_count", "udid", "updatedAt", "userBlocked", "wdaLocalPort" FROM "Device";
89
+ DROP TABLE "Device";
90
+ ALTER TABLE "new_Device" RENAME TO "Device";
91
+ CREATE INDEX "Device_owning_session_id_idx" ON "Device"("owning_session_id");
92
+ CREATE INDEX "Device_teamId_idx" ON "Device"("teamId");
93
+ PRAGMA foreign_keys=ON;
94
+ PRAGMA defer_foreign_keys=OFF;
95
+
96
+ -- CreateIndex
97
+ CREATE UNIQUE INDEX "Team_name_key" ON "Team"("name");
98
+
99
+ -- CreateIndex
100
+ CREATE INDEX "Session_api_key_id_idx" ON "Session"("api_key_id");
101
+
@@ -47,6 +47,7 @@ model Session {
47
47
  last_heartbeat_at DateTime?
48
48
  heartbeat_pid Int?
49
49
  heartbeat_host String?
50
+ api_key_id String?
50
51
  Log Log[]
51
52
  Profiling Profiling[]
52
53
  build Build? @relation(fields: [build_id], references: [id])
@@ -56,6 +57,7 @@ model Session {
56
57
  @@index([build_id])
57
58
  @@index([device_udid])
58
59
  @@index([device_platform, status])
60
+ @@index([api_key_id])
59
61
  }
60
62
 
61
63
  model SessionLog {
@@ -175,9 +177,12 @@ model Device {
175
177
  cpuArchitecture String?
176
178
  owningSessionId String? @map("owning_session_id")
177
179
  lockedAt Float? @map("locked_at")
180
+ teamId String?
181
+ team Team? @relation(fields: [teamId], references: [id])
178
182
 
179
183
  @@id([udid, host])
180
184
  @@index([owningSessionId])
185
+ @@index([teamId])
181
186
  }
182
187
 
183
188
  model PendingSession {
@@ -245,4 +250,17 @@ model ApiKey {
245
250
  createdAt DateTime @default(now())
246
251
  revokedAt DateTime?
247
252
  lastUsedAt DateTime?
253
+ teamId String?
254
+ role String @default("member")
255
+ team Team? @relation(fields: [teamId], references: [id])
256
+
257
+ @@index([teamId])
258
+ }
259
+
260
+ model Team {
261
+ id String @id @default(uuid())
262
+ name String @unique
263
+ createdAt DateTime @default(now())
264
+ devices Device[]
265
+ apiKeys ApiKey[]
248
266
  }
@@ -1,26 +0,0 @@
1
- import{c as t,r as c,j as i}from"./index-BaTiUCeH.js";import{c as r,a as u}from"./badge-BNR9umdu.js";/**
2
- * @license lucide-react v0.555.0 - ISC
3
- *
4
- * This source code is licensed under the ISC license.
5
- * See the LICENSE file in the root directory of this source tree.
6
- */const d=[["path",{d:"M12 6.528V3a1 1 0 0 1 1-1h0",key:"11qiee"}],["path",{d:"M18.237 21A15 15 0 0 0 22 11a6 6 0 0 0-10-4.472A6 6 0 0 0 2 11a15.1 15.1 0 0 0 3.763 10 3 3 0 0 0 3.648.648 5.5 5.5 0 0 1 5.178 0A3 3 0 0 0 18.237 21",key:"110c12"}]],f=t("apple",d);/**
7
- * @license lucide-react v0.555.0 - ISC
8
- *
9
- * This source code is licensed under the ISC license.
10
- * See the LICENSE file in the root directory of this source tree.
11
- */const p=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],x=t("check",p);/**
12
- * @license lucide-react v0.555.0 - ISC
13
- *
14
- * This source code is licensed under the ISC license.
15
- * See the LICENSE file in the root directory of this source tree.
16
- */const l=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],_=t("copy",l);/**
17
- * @license lucide-react v0.555.0 - ISC
18
- *
19
- * This source code is licensed under the ISC license.
20
- * See the LICENSE file in the root directory of this source tree.
21
- */const h=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],g=t("terminal",h);/**
22
- * @license lucide-react v0.555.0 - ISC
23
- *
24
- * This source code is licensed under the ISC license.
25
- * See the LICENSE file in the root directory of this source tree.
26
- */const y=[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]],M=t("upload",y),b=u("button-base",{variants:{variant:{default:"button-variant-default",outline:"button-variant-outline",ghost:"button-variant-ghost",link:"button-variant-link",destructive:"button-variant-destructive"},size:{default:"button-default",sm:"button-sm",lg:"button-lg",icon:"button-icon"}},defaultVariants:{variant:"default",size:"default"}}),k=c.forwardRef(({className:a,variant:o,size:n,...e},s)=>i.jsx("button",{className:r(b({variant:o,size:n,className:a})),ref:s,...e}));k.displayName="Button";export{f as A,k as B,x as C,g as T,M as U,_ as a};
@@ -1 +0,0 @@
1
- import{r as i,j as e,g as j,l as S,B as C,i as y,T as N}from"./index-BaTiUCeH.js";import{X as c}from"./index-C1DBaoSh.js";import{A as b}from"./Layouts-D6IPfwoe.js";import{C as k}from"./clock-DrpxSvCL.js";import{C as I}from"./calendar-fehdBtun.js";import{M}from"./mouse-pointer-2-CXdnjXIg.js";const B=()=>{const[a,t]=i.useState({healthCheckIntervalMs:3e4,healthCheckSchedule:"",enableSelfHealing:!0}),[g,d]=i.useState(!0),[m,h]=i.useState(!1),[l,n]=i.useState(null);i.useEffect(()=>{o()},[]);const o=async()=>{d(!0);try{const s=await c.getGlobalConfig();t({healthCheckIntervalMs:s.healthCheckIntervalMs||3e4,healthCheckSchedule:s.healthCheckSchedule||"",enableSelfHealing:s.enableSelfHealing!==void 0?s.enableSelfHealing:!0})}catch(s){console.error("Failed to load settings",s),n({type:"error",message:"Failed to access infrastructure parameters."})}finally{d(!1)}},u=async(s=a)=>{h(!0),n(null);try{await c.updateGlobalConfig(s),n({type:"success",message:"Infrastructure parameters synchronized across fleet."}),setTimeout(()=>n(null),5e3)}catch(r){console.error("Failed to save settings",r),n({type:"error",message:"Synchronization failed. Check network integrity."})}finally{h(!1)}},v=async()=>{const s={healthCheckIntervalMs:3e4,healthCheckSchedule:"",enableSelfHealing:!0};t(s),await u(s);try{await c.resetMetrics()}catch(r){console.error("Failed to reset metrics",r)}},p=[{label:"Battery Saver (2 AM)",value:"0 2 * * *"},{label:"Standard (Hourly)",value:"0 * * * *"},{label:"Operational Coverage (30m)",value:"*/30 * * * *"},{label:"High Performance (10m)",value:"*/10 * * * *"},{label:"Disable Schedule",value:""}],f=s=>s?s.split(" ").filter(x=>x!=="").length!==5?"Invalid Cron format (needs 5 parts)":s==="0 2 * * *"?"Quiet hours: Daily at 2:00 AM":s==="0 * * * *"?"Standard rotation: Start of every hour":s==="*/30 * * * *"?"Balanced: Every 30 minutes":s==="*/10 * * * *"?"Intensive: Every 10 minutes":`Custom orchestration: ${s}`:"Using Idle Health Frequency (Continuous/Passive mode).";return g?e.jsxs("div",{className:"settings-loading",children:[e.jsx(j,{className:"animate-spin",size:32}),e.jsx("span",{children:"Synchronizing Global Infrastructure..."})]}):e.jsxs("div",{className:"settings-container mesh-gradient-infra",children:[e.jsx("div",{className:"scanline",style:{position:"absolute",inset:0,pointerEvents:"none",opacity:.05,zIndex:1001}}),e.jsxs("div",{className:"settings-header",children:[e.jsxs("div",{className:"settings-title-group",children:[e.jsx(S,{className:"settings-icon infra-icon",size:28}),e.jsx("h2",{children:"Infrastructure Control"})]}),e.jsx("p",{className:"settings-subtitle",children:"Manage core farm parameters, heartbeat frequency, and maintenance orchestrations across the global registry."})]}),e.jsxs("div",{className:"settings-content",children:[e.jsxs("div",{className:"settings-grid",children:[e.jsxs("div",{className:"setting-card stagger-1",children:[e.jsxs("div",{className:"setting-card-header",children:[e.jsx(k,{size:16}),e.jsx("h4",{children:"Idle Health Frequency"})]}),e.jsx("p",{className:"section-description-dense",children:"Frequency of passive health pings when the system is in idle state."}),e.jsx("div",{className:"setting-field",children:e.jsxs("div",{className:"input-group",children:[e.jsx("input",{type:"number",value:a.healthCheckIntervalMs,onChange:s=>t({...a,healthCheckIntervalMs:parseInt(s.target.value)}),min:5e3,step:5e3}),e.jsx("span",{className:"code-font",children:"MS"})]})}),e.jsx("div",{className:"setting-hint-clean",children:"Minimum safe value: 5000ms. Note: This frequency is overridden when a schedule is active."})]}),e.jsxs("div",{className:"setting-card stagger-2",children:[e.jsxs("div",{className:"setting-card-header",children:[e.jsx(I,{size:16}),e.jsx("h4",{children:"Deep Diagnostic Schedule"})]}),e.jsx("p",{className:"section-description-dense",children:"Execute intensive reliability bursts (WDA restarts, Cache purges) using standardized Cron syntax."}),e.jsx("div",{className:"setting-field",children:e.jsx("div",{className:"setting-input-wrapper",children:e.jsx("input",{type:"text",placeholder:"e.g. 0 * * * * (At internal min 0)",value:a.healthCheckSchedule,onChange:s=>t({...a,healthCheckSchedule:s.target.value})})})}),e.jsxs("div",{className:"cron-preview",children:[e.jsx("span",{className:"preview-label",children:"Active Logic:"}),e.jsx("span",{className:"preview-value",children:f(a.healthCheckSchedule)})]}),e.jsxs("div",{className:"cron-presets",children:[e.jsxs("div",{className:"presets-label",children:[e.jsx(M,{size:12}),e.jsx("span",{children:"Intent-Based Presets:"})]}),e.jsx("div",{className:"presets-grid",children:p.map(s=>e.jsx("button",{className:`preset-chip ${a.healthCheckSchedule===s.value?"active":""}`,onClick:()=>t({...a,healthCheckSchedule:s.value}),children:s.label},s.label))})]})]}),e.jsxs("div",{className:"setting-card stagger-3",children:[e.jsxs("div",{className:"setting-card-header",children:[e.jsx(C,{size:16}),e.jsx("h4",{children:"AI Self-Healing"})]}),e.jsx("p",{className:"section-description-dense",children:"Automatically intercept and recover from failing locators using Xenon's 5-tier strategy."}),e.jsxs("div",{className:"toggle-group",children:[e.jsxs("label",{className:"switch",children:[e.jsx("input",{type:"checkbox",checked:a.enableSelfHealing,onChange:s=>t({...a,enableSelfHealing:s.target.checked})}),e.jsx("span",{className:"slider round"})]}),e.jsx("span",{className:"toggle-label",children:a.enableSelfHealing?"ENABLED":"DISABLED"})]}),e.jsx("div",{className:"setting-hint-clean",children:"When enabled, Xenon will attempt to find elements via Fuzzy XML, OCR, Visual AI, and LLM before failing a test."})]})]}),l&&e.jsxs("div",{className:`status-banner ${l.type}`,children:[l.type==="success"?e.jsx(y,{size:18}):e.jsx(N,{size:18}),e.jsx("span",{children:l.message})]})]}),e.jsx(b,{onSave:u,onDiscard:o,onRestoreDefaults:v,isSaving:m,saveLabel:"Save Configuration"})]})};export{B as Settings};