Merge branch 'master' into release

This commit is contained in:
Nawaz Dhandala
2026-02-18 15:24:00 +00:00
86 changed files with 5209 additions and 3013 deletions

View File

@@ -0,0 +1,108 @@
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from "Common/Server/Utils/Express";
import Response from "Common/Server/Utils/Response";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import PushNotificationService from "Common/Server/Services/PushNotificationService";
const router: ExpressRouter = Express.getRouter();
// Simple in-memory rate limiter by IP
const rateLimitMap: Map<string, { count: number; resetTime: number }> =
new Map();
const RATE_LIMIT_WINDOW_MS: number = 60 * 1000; // 1 minute
const RATE_LIMIT_MAX_REQUESTS: number = 60; // 60 requests per minute per IP
function isRateLimited(ip: string): boolean {
const now: number = Date.now();
const entry: { count: number; resetTime: number } | undefined =
rateLimitMap.get(ip);
if (!entry || now > entry.resetTime) {
rateLimitMap.set(ip, { count: 1, resetTime: now + RATE_LIMIT_WINDOW_MS });
return false;
}
entry.count++;
return entry.count > RATE_LIMIT_MAX_REQUESTS;
}
// Clean up stale rate limit entries every 5 minutes
setInterval(
() => {
const now: number = Date.now();
for (const [ip, entry] of rateLimitMap.entries()) {
if (now > entry.resetTime) {
rateLimitMap.delete(ip);
}
}
},
5 * 60 * 1000,
);
router.post(
"/send",
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
try {
const clientIp: string =
(req.headers["x-forwarded-for"] as string)?.split(",")[0]?.trim() ||
req.socket.remoteAddress ||
"unknown";
if (isRateLimited(clientIp)) {
res.status(429).json({
message: "Rate limit exceeded. Please try again later.",
});
return;
}
if (!PushNotificationService.hasExpoAccessToken()) {
throw new BadDataException(
"Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.",
);
}
const body: JSONObject = req.body as JSONObject;
const to: string | undefined = body["to"] as string | undefined;
if (!to || !PushNotificationService.isValidExpoPushToken(to)) {
throw new BadDataException(
"Invalid or missing push token. Must be a valid Expo push token.",
);
}
const title: string | undefined = body["title"] as string | undefined;
const messageBody: string | undefined = body["body"] as
| string
| undefined;
if (!title && !messageBody) {
throw new BadDataException(
"At least one of 'title' or 'body' must be provided.",
);
}
await PushNotificationService.sendRelayPushNotification({
to: to,
title: title,
body: messageBody,
data: (body["data"] as { [key: string]: string }) || {},
sound: (body["sound"] as string) || "default",
priority: (body["priority"] as string) || "high",
channelId: (body["channelId"] as string) || "default",
});
return Response.sendJsonObjectResponse(req, res, { success: true });
} catch (err) {
return next(err);
}
},
);
export default router;

View File

@@ -4,6 +4,7 @@ import MailAPI from "./API/Mail";
import SmsAPI from "./API/SMS";
import WhatsAppAPI from "./API/WhatsApp";
import PushNotificationAPI from "./API/PushNotification";
import PushRelayAPI from "./API/PushRelay";
import SMTPConfigAPI from "./API/SMTPConfig";
import PhoneNumberAPI from "./API/PhoneNumber";
import IncomingCallAPI from "./API/IncomingCall";
@@ -21,6 +22,7 @@ const NotificationFeatureSet: FeatureSet = {
app.use([`/${APP_NAME}/sms`, "/sms"], SmsAPI);
app.use([`/${APP_NAME}/whatsapp`, "/whatsapp"], WhatsAppAPI);
app.use([`/${APP_NAME}/push`, "/push"], PushNotificationAPI);
app.use([`/${APP_NAME}/push-relay`, "/push-relay"], PushRelayAPI);
app.use([`/${APP_NAME}/call`, "/call"], CallAPI);
app.use([`/${APP_NAME}/smtp-config`, "/smtp-config"], SMTPConfigAPI);
app.use([`/${APP_NAME}/phone-number`, "/phone-number"], PhoneNumberAPI);

58
App/package-lock.json generated
View File

@@ -12,6 +12,7 @@
"@sendgrid/mail": "^8.1.0",
"Common": "file:../Common",
"ejs": "^3.1.9",
"expo-server-sdk": "^5.0.0",
"handlebars": "^4.7.8",
"nodemailer": "^6.9.7",
"ts-node": "^10.9.1",
@@ -2166,6 +2167,12 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"license": "MIT"
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -2299,6 +2306,20 @@
"node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
}
},
"node_modules/expo-server-sdk": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/expo-server-sdk/-/expo-server-sdk-5.0.0.tgz",
"integrity": "sha512-GEp1XYLU80iS/hdRo3c2n092E8TgTXcHSuw6Lw68dSoWaAgiLPI2R+e5hp5+hGF1TtJZOi2nxtJX63+XA3iz9g==",
"license": "MIT",
"dependencies": {
"promise-limit": "^2.7.0",
"promise-retry": "^2.0.1",
"undici": "^7.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@@ -4167,6 +4188,25 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/promise-limit": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz",
"integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==",
"license": "ISC"
},
"node_modules/promise-retry": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
"license": "MIT",
"dependencies": {
"err-code": "^2.0.2",
"retry": "^0.12.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -4290,6 +4330,15 @@
"node": ">=10"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -4801,6 +4850,15 @@
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true
},
"node_modules/undici": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",

View File

@@ -23,6 +23,7 @@
"@sendgrid/mail": "^8.1.0",
"Common": "file:../Common",
"ejs": "^3.1.9",
"expo-server-sdk": "^5.0.0",
"handlebars": "^4.7.8",
"nodemailer": "^6.9.7",
"ts-node": "^10.9.1",

147
CLI/package-lock.json generated
View File

@@ -83,7 +83,6 @@
"resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz",
"integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.10.0",
@@ -118,7 +117,6 @@
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz",
"integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@azure/abort-controller": "^2.1.2",
"@azure/core-auth": "^1.10.0",
@@ -290,7 +288,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -811,60 +808,47 @@
"resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.23.0.tgz",
"integrity": "sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@bull-board/api": "5.23.0"
}
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",
"integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/gast": "11.1.1",
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",
"integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/@chevrotain/gast/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",
"integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",
"integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",
"integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",
"license": "Apache-2.0"
},
"node_modules/@clickhouse/client": {
@@ -2151,12 +2135,12 @@
"license": "MIT"
},
"node_modules/@mermaid-js/parser": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz",
"integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz",
"integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==",
"license": "MIT",
"dependencies": {
"langium": "3.3.1"
"langium": "^4.0.0"
}
},
"node_modules/@monaco-editor/loader": {
@@ -2312,7 +2296,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -4765,7 +4748,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz",
"integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -4808,7 +4790,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -5019,7 +5000,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5816,7 +5796,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -6164,17 +6143,17 @@
}
},
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",
"integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/cst-dts-gen": "11.1.1",
"@chevrotain/gast": "11.1.1",
"@chevrotain/regexp-to-ast": "11.1.1",
"@chevrotain/types": "11.1.1",
"@chevrotain/utils": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/chevrotain-allstar": {
@@ -6189,12 +6168,6 @@
"chevrotain": "^11.0.0"
}
},
"node_modules/chevrotain/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -6446,9 +6419,9 @@
},
"node_modules/Common": {
"name": "@oneuptime/common",
"version": "9.5.10",
"resolved": "https://registry.npmjs.org/@oneuptime/common/-/common-9.5.10.tgz",
"integrity": "sha512-4UZ553L89Kd6pNVrUAZlB/eSiBI1e8QEm/5hWkHc+Pfv54yzkj1Smb7VssPlrLHU811I5iimI1/ZK+mD4B7aKA==",
"version": "9.5.13",
"resolved": "https://registry.npmjs.org/@oneuptime/common/-/common-9.5.13.tgz",
"integrity": "sha512-hHLUeApW4ILQcte5avvmj87m7w//tfcClDWWLsqkBWUSU94a4NSvpTEXh3dEN8WX3ty9/r7eYnobrjJK+7zIDA==",
"license": "Apache-2.0",
"dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.2",
@@ -6853,7 +6826,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10"
}
@@ -7275,7 +7247,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -9227,7 +9198,6 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.3.tgz",
"integrity": "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.5.0",
"cluster-key-slot": "^1.1.0",
@@ -9626,7 +9596,6 @@
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "^29.7.0",
"@jest/types": "^29.6.3",
@@ -10522,19 +10491,20 @@
}
},
"node_modules/langium": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz",
"integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
"integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
"license": "MIT",
"dependencies": {
"chevrotain": "~11.0.3",
"chevrotain-allstar": "~0.3.0",
"chevrotain": "~11.1.1",
"chevrotain-allstar": "~0.3.1",
"vscode-languageserver": "~9.0.1",
"vscode-languageserver-textdocument": "~1.0.11",
"vscode-uri": "~3.0.8"
"vscode-uri": "~3.1.0"
},
"engines": {
"node": ">=16.0.0"
"node": ">=20.10.0",
"npm": ">=10.2.3"
}
},
"node_modules/layout-base": {
@@ -11181,14 +11151,14 @@
}
},
"node_modules/mermaid": {
"version": "11.12.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz",
"integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
"version": "11.12.3",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz",
"integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "^0.6.3",
"@mermaid-js/parser": "^1.0.0",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
@@ -11200,7 +11170,7 @@
"dompurify": "^3.2.5",
"katex": "^0.16.22",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"lodash-es": "^4.17.23",
"marked": "^16.2.1",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
@@ -11956,6 +11926,7 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"peer": true,
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
@@ -11966,6 +11937,7 @@
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
"license": "(MPL-2.0 OR Apache-2.0)",
"peer": true,
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
@@ -11975,6 +11947,7 @@
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
"license": "MIT",
"peer": true,
"bin": {
"marked": "bin/marked.js"
},
@@ -12583,7 +12556,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.11.0",
"pg-pool": "^3.11.0",
@@ -13205,7 +13177,6 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -13502,7 +13473,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -13587,7 +13557,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -15998,7 +15967,6 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -16311,7 +16279,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -16754,9 +16721,9 @@
"license": "MIT"
},
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"license": "MIT"
},
"node_modules/walker": {
@@ -17157,7 +17124,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -17166,8 +17132,7 @@
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/zustand": {
"version": "4.5.7",

View File

@@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization";
import UserCallService, {
Service as UserCallServiceType,
} from "../Services/UserCallService";
import UserNotificationRuleService from "../Services/UserNotificationRuleService";
import {
ExpressRequest,
ExpressResponse,
@@ -9,8 +10,10 @@ import {
OneUptimeRequest,
} from "../Utils/Express";
import Response from "../Utils/Response";
import logger from "../Utils/Logger";
import BaseAPI from "./BaseAPI";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import UserCall from "../../Models/DatabaseModels/UserCall";
import UserSMS from "../../Models/DatabaseModels/UserSMS";
@@ -52,6 +55,7 @@ export default class UserCallAPI extends BaseAPI<
},
select: {
userId: true,
projectId: true,
verificationCode: true,
},
});
@@ -95,6 +99,21 @@ export default class UserCallAPI extends BaseAPI<
},
});
// Create default notification rules for this verified call number
try {
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
{
projectId: new ObjectID(item.projectId!.toString()),
userId: new ObjectID(item.userId!.toString()),
notificationMethod: {
userCallId: item.id!,
},
},
);
} catch (e) {
logger.error(e);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);

View File

@@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization";
import UserEmailService, {
Service as UserEmailServiceType,
} from "../Services/UserEmailService";
import UserNotificationRuleService from "../Services/UserNotificationRuleService";
import {
ExpressRequest,
ExpressResponse,
@@ -9,8 +10,10 @@ import {
OneUptimeRequest,
} from "../Utils/Express";
import Response from "../Utils/Response";
import logger from "../Utils/Logger";
import BaseAPI from "./BaseAPI";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import UserEmail from "../../Models/DatabaseModels/UserEmail";
export default class UserEmailAPI extends BaseAPI<
@@ -51,6 +54,7 @@ export default class UserEmailAPI extends BaseAPI<
},
select: {
userId: true,
projectId: true,
verificationCode: true,
},
});
@@ -94,6 +98,21 @@ export default class UserEmailAPI extends BaseAPI<
},
});
// Create default notification rules for this verified email
try {
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
{
projectId: new ObjectID(item.projectId!.toString()),
userId: new ObjectID(item.userId!.toString()),
notificationMethod: {
userEmailId: item.id!,
},
},
);
} catch (e) {
logger.error(e);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);

View File

@@ -2,8 +2,10 @@ import UserMiddleware from "../Middleware/UserAuthorization";
import UserPushService, {
Service as UserPushServiceType,
} from "../Services/UserPushService";
import UserNotificationRuleService from "../Services/UserNotificationRuleService";
import PushNotificationService from "../Services/PushNotificationService";
import PushNotificationUtil from "../Utils/PushNotificationUtil";
import logger from "../Utils/Logger";
import {
ExpressRequest,
ExpressResponse,
@@ -13,11 +15,23 @@ import {
import Response from "../Utils/Response";
import BaseAPI from "./BaseAPI";
import BadDataException from "../../Types/Exception/BadDataException";
import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException";
import ObjectID from "../../Types/ObjectID";
import PushDeviceType from "../../Types/PushNotification/PushDeviceType";
import UserPush from "../../Models/DatabaseModels/UserPush";
import PushNotificationMessage from "../../Types/PushNotification/PushNotificationMessage";
function getAuthenticatedUserId(req: ExpressRequest): ObjectID {
const userId: ObjectID | undefined = (req as OneUptimeRequest)
.userAuthorization?.userId;
if (!userId) {
throw new NotAuthenticatedException(
"You must be logged in to perform this action.",
);
}
return userId;
}
export default class UserPushAPI extends BaseAPI<
UserPush,
UserPushServiceType
@@ -32,6 +46,8 @@ export default class UserPushAPI extends BaseAPI<
try {
req = req as OneUptimeRequest;
const userId: ObjectID = getAuthenticatedUserId(req);
if (!req.body.deviceToken) {
return Response.sendErrorResponse(
req,
@@ -65,7 +81,7 @@ export default class UserPushAPI extends BaseAPI<
// Check if device is already registered
const existingDevice: UserPush | null = await this.service.findOneBy({
query: {
userId: (req as OneUptimeRequest).userAuthorization!.userId!,
userId: userId,
projectId: new ObjectID(req.body.projectId),
deviceToken: req.body.deviceToken,
},
@@ -78,17 +94,18 @@ export default class UserPushAPI extends BaseAPI<
});
if (existingDevice) {
// Mark as used and return a specific response indicating device was already registered
throw new BadDataException(
"This device is already registered for push notifications",
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"This device is already registered for push notifications",
),
);
}
// Create new device registration
const userPush: UserPush = new UserPush();
userPush.userId = (
req as OneUptimeRequest
).userAuthorization!.userId!;
userPush.userId = userId;
userPush.projectId = new ObjectID(req.body.projectId);
userPush.deviceToken = req.body.deviceToken;
userPush.deviceType = req.body.deviceType;
@@ -102,6 +119,21 @@ export default class UserPushAPI extends BaseAPI<
},
});
// Create default notification rules for this registered push device
try {
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
{
projectId: new ObjectID(req.body.projectId),
userId,
notificationMethod: {
userPushId: savedDevice.id!,
},
},
);
} catch (e) {
logger.error(e);
}
return Response.sendJsonObjectResponse(req, res, {
success: true,
deviceId: savedDevice._id!.toString(),
@@ -119,6 +151,8 @@ export default class UserPushAPI extends BaseAPI<
try {
req = req as OneUptimeRequest;
const userId: ObjectID = getAuthenticatedUserId(req);
if (!req.body.deviceToken) {
return Response.sendErrorResponse(
req,
@@ -127,9 +161,6 @@ export default class UserPushAPI extends BaseAPI<
);
}
const userId: ObjectID = (req as OneUptimeRequest).userAuthorization!
.userId!;
await this.service.deleteBy({
query: {
userId: userId,
@@ -159,6 +190,8 @@ export default class UserPushAPI extends BaseAPI<
try {
req = req as OneUptimeRequest;
const userId: ObjectID = getAuthenticatedUserId(req);
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
@@ -192,10 +225,7 @@ export default class UserPushAPI extends BaseAPI<
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
if (device.userId?.toString() !== userId.toString()) {
return Response.sendErrorResponse(
req,
res,
@@ -264,6 +294,8 @@ export default class UserPushAPI extends BaseAPI<
try {
req = req as OneUptimeRequest;
const userId: ObjectID = getAuthenticatedUserId(req);
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
@@ -279,6 +311,7 @@ export default class UserPushAPI extends BaseAPI<
},
select: {
userId: true,
projectId: true,
},
});
@@ -291,10 +324,7 @@ export default class UserPushAPI extends BaseAPI<
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
if (device.userId?.toString() !== userId.toString()) {
return Response.sendErrorResponse(
req,
res,
@@ -304,6 +334,21 @@ export default class UserPushAPI extends BaseAPI<
await this.service.verifyDevice(device._id!.toString());
// Create default notification rules for this verified push device
try {
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
{
projectId: new ObjectID(device.projectId!.toString()),
userId,
notificationMethod: {
userPushId: device.id!,
},
},
);
} catch (e) {
logger.error(e);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (error) {
return next(error);
@@ -318,6 +363,8 @@ export default class UserPushAPI extends BaseAPI<
try {
req = req as OneUptimeRequest;
const userId: ObjectID = getAuthenticatedUserId(req);
if (!req.params["deviceId"]) {
return Response.sendErrorResponse(
req,
@@ -345,10 +392,7 @@ export default class UserPushAPI extends BaseAPI<
}
// Check if the device belongs to the current user
if (
device.userId?.toString() !==
(req as OneUptimeRequest).userAuthorization!.userId!.toString()
) {
if (device.userId?.toString() !== userId.toString()) {
return Response.sendErrorResponse(
req,
res,

View File

@@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization";
import UserSMSService, {
Service as UserSMSServiceType,
} from "../Services/UserSmsService";
import UserNotificationRuleService from "../Services/UserNotificationRuleService";
import {
ExpressRequest,
ExpressResponse,
@@ -9,8 +10,10 @@ import {
OneUptimeRequest,
} from "../Utils/Express";
import Response from "../Utils/Response";
import logger from "../Utils/Logger";
import BaseAPI from "./BaseAPI";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import UserSMS from "../../Models/DatabaseModels/UserSMS";
export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
@@ -48,6 +51,7 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
},
select: {
userId: true,
projectId: true,
verificationCode: true,
},
});
@@ -91,6 +95,21 @@ export default class UserSMSAPI extends BaseAPI<UserSMS, UserSMSServiceType> {
},
});
// Create default notification rules for this verified SMS
try {
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
{
projectId: new ObjectID(item.projectId!.toString()),
userId: new ObjectID(item.userId!.toString()),
notificationMethod: {
userSmsId: item.id!,
},
},
);
} catch (e) {
logger.error(e);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);

View File

@@ -2,6 +2,7 @@ import UserMiddleware from "../Middleware/UserAuthorization";
import UserWhatsAppService, {
Service as UserWhatsAppServiceType,
} from "../Services/UserWhatsAppService";
import UserNotificationRuleService from "../Services/UserNotificationRuleService";
import {
ExpressRequest,
ExpressResponse,
@@ -9,8 +10,10 @@ import {
OneUptimeRequest,
} from "../Utils/Express";
import Response from "../Utils/Response";
import logger from "../Utils/Logger";
import BaseAPI from "./BaseAPI";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import UserWhatsApp from "../../Models/DatabaseModels/UserWhatsApp";
export default class UserWhatsAppAPI extends BaseAPI<
@@ -50,6 +53,7 @@ export default class UserWhatsAppAPI extends BaseAPI<
},
select: {
userId: true,
projectId: true,
verificationCode: true,
isVerified: true,
},
@@ -100,6 +104,21 @@ export default class UserWhatsAppAPI extends BaseAPI<
},
});
// Create default notification rules for this verified WhatsApp number
try {
await UserNotificationRuleService.addDefaultNotificationRulesForVerifiedMethod(
{
projectId: new ObjectID(item.projectId!.toString()),
userId: new ObjectID(item.userId!.toString()),
notificationMethod: {
userWhatsAppId: item.id!,
},
},
);
} catch (e) {
logger.error(e);
}
return Response.sendEmptySuccessResponse(req, res);
} catch (err) {
return next(err);

View File

@@ -161,6 +161,14 @@ export const ClusterKey: ObjectID = new ObjectID(
export const HasClusterKey: boolean = Boolean(process.env["ONEUPTIME_SECRET"]);
export const RegisterProbeKey: ObjectID = new ObjectID(
process.env["REGISTER_PROBE_KEY"] || "secret",
);
export const HasRegisterProbeKey: boolean = Boolean(
process.env["REGISTER_PROBE_KEY"],
);
export const AppApiHostname: Hostname = Hostname.fromString(
`${process.env["SERVER_APP_HOSTNAME"] || "localhost"}:${
process.env["APP_PORT"] || 80
@@ -529,6 +537,13 @@ export const VapidPrivateKey: string | undefined =
export const VapidSubject: string =
process.env["VAPID_SUBJECT"] || "mailto:support@oneuptime.com";
export const ExpoAccessToken: string | undefined =
process.env["EXPO_ACCESS_TOKEN"] || undefined;
export const PushNotificationRelayUrl: string =
process.env["PUSH_NOTIFICATION_RELAY_URL"] ||
"https://oneuptime.com/api/notification/push-relay/send";
export const EnterpriseLicenseValidationUrl: URL = URL.fromString(
"https://oneuptime.com/api/enterprise-license/validate",
);

View File

@@ -10,9 +10,16 @@ import {
VapidPublicKey,
VapidPrivateKey,
VapidSubject,
ExpoAccessToken,
PushNotificationRelayUrl,
} from "../EnvironmentConfig";
import webpush from "web-push";
import { Expo, ExpoPushMessage, ExpoPushTicket } from "expo-server-sdk";
import API from "../../Utils/API";
import URL from "../../Types/API/URL";
import HTTPErrorResponse from "../../Types/API/HTTPErrorResponse";
import HTTPResponse from "../../Types/API/HTTPResponse";
import { JSONObject } from "../../Types/JSON";
import PushNotificationUtil from "../Utils/PushNotificationUtil";
import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
import UserPush from "../../Models/DatabaseModels/UserPush";
@@ -43,7 +50,9 @@ export interface PushNotificationOptions {
export default class PushNotificationService {
public static isWebPushInitialized = false;
private static expoClient: Expo = new Expo();
private static expoClient: Expo = new Expo(
ExpoAccessToken ? { accessToken: ExpoAccessToken } : undefined,
);
public static initializeWebPush(): void {
if (this.isWebPushInitialized) {
@@ -340,20 +349,33 @@ export default class PushNotificationService {
);
}
const dataPayload: { [key: string]: string } = {};
if (message.data) {
for (const key of Object.keys(message.data)) {
dataPayload[key] = String(message.data[key]);
}
}
if (message.url || message.clickAction) {
dataPayload["url"] = message.url || message.clickAction || "";
}
const channelId: string =
deviceType === PushDeviceType.Android ? "oncall_high" : "default";
// If EXPO_ACCESS_TOKEN is not set, relay through the push notification gateway
if (!ExpoAccessToken) {
await this.sendViaRelay(
expoPushToken,
message,
dataPayload,
channelId,
deviceType,
);
return;
}
// Send directly via Expo SDK
try {
const dataPayload: { [key: string]: string } = {};
if (message.data) {
for (const key of Object.keys(message.data)) {
dataPayload[key] = String(message.data[key]);
}
}
if (message.url || message.clickAction) {
dataPayload["url"] = message.url || message.clickAction || "";
}
const channelId: string =
deviceType === PushDeviceType.Android ? "oncall_high" : "default";
const expoPushMessage: ExpoPushMessage = {
to: expoPushToken,
title: message.title,
@@ -403,6 +425,108 @@ export default class PushNotificationService {
}
}
private static async sendViaRelay(
expoPushToken: string,
message: PushNotificationMessage,
dataPayload: { [key: string]: string },
channelId: string,
deviceType: PushDeviceType,
): Promise<void> {
logger.info(
`Sending ${deviceType} push notification via relay: ${PushNotificationRelayUrl}`,
);
try {
const response: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.post<JSONObject>({
url: URL.fromString(PushNotificationRelayUrl),
data: {
to: expoPushToken,
title: message.title || "",
body: message.body || "",
data: dataPayload,
sound: "default",
priority: "high",
channelId: channelId,
},
});
if (response instanceof HTTPErrorResponse) {
throw new Error(
`Push relay error: ${JSON.stringify(response.jsonData)}`,
);
}
logger.info(
`Push notification sent via relay successfully to ${deviceType} device`,
);
} catch (error: any) {
logger.error(
`Failed to send push notification via relay to ${deviceType} device: ${error.message}`,
);
throw error;
}
}
public static isValidExpoPushToken(token: string): boolean {
return Expo.isExpoPushToken(token);
}
public static hasExpoAccessToken(): boolean {
return Boolean(ExpoAccessToken);
}
public static async sendRelayPushNotification(data: {
to: string;
title?: string;
body?: string;
data?: { [key: string]: string };
sound?: string;
priority?: string;
channelId?: string;
}): Promise<void> {
if (!ExpoAccessToken) {
throw new Error(
"Push relay is not configured. EXPO_ACCESS_TOKEN is not set on this server.",
);
}
const expoPushMessage: ExpoPushMessage = {
to: data.to,
title: data.title || "",
body: data.body || "",
data: data.data || {},
sound: (data.sound as "default" | null) || "default",
priority: (data.priority as "default" | "normal" | "high") || "high",
channelId: data.channelId || "default",
};
const tickets: ExpoPushTicket[] =
await this.expoClient.sendPushNotificationsAsync([expoPushMessage]);
const ticket: ExpoPushTicket | undefined = tickets[0];
if (ticket && ticket.status === "error") {
const errorTicket: ExpoPushTicket & {
message?: string;
details?: { error?: string };
} = ticket as ExpoPushTicket & {
message?: string;
details?: { error?: string };
};
logger.error(
`Push relay: Expo push notification error: ${errorTicket.message}`,
);
throw new Error(
`Failed to send push notification: ${errorTicket.message}`,
);
}
logger.info(`Push relay: notification sent successfully to ${data.to}`);
}
public static async sendPushNotificationToUser(
userId: ObjectID,
projectId: ObjectID,

View File

@@ -69,6 +69,14 @@ import PushNotificationMessage from "../../Types/PushNotification/PushNotificati
import logger from "../Utils/Logger";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
export interface NotificationMethodDescriptor {
userEmailId?: ObjectID;
userSmsId?: ObjectID;
userCallId?: ObjectID;
userWhatsAppId?: ObjectID;
userPushId?: ObjectID;
}
export class Service extends DatabaseService<Model> {
public constructor() {
super(Model);
@@ -2207,13 +2215,89 @@ export class Service extends DatabaseService<Model> {
}
@CaptureSpan()
public async addDefaultIncidentNotificationRuleForUser(data: {
public async addDefaultNotificationRulesForVerifiedMethod(data: {
projectId: ObjectID;
userId: ObjectID;
userEmail: UserEmail;
notificationMethod: NotificationMethodDescriptor;
}): Promise<void> {
const { projectId, userId, userEmail } = data;
const { projectId, userId, notificationMethod } = data;
await this.createIncidentOnCallRules(projectId, userId, notificationMethod);
await this.createAlertOnCallRules(projectId, userId, notificationMethod);
await this.createSingleRule(
projectId,
userId,
notificationMethod,
NotificationRuleType.ON_CALL_EXECUTED_ALERT_EPISODE,
);
await this.createSingleRule(
projectId,
userId,
notificationMethod,
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT_EPISODE,
);
await this.createSingleRule(
projectId,
userId,
notificationMethod,
NotificationRuleType.WHEN_USER_GOES_ON_CALL,
);
await this.createSingleRule(
projectId,
userId,
notificationMethod,
NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
);
}
private applyNotificationMethod(
rule: Model,
descriptor: NotificationMethodDescriptor,
): void {
if (descriptor.userEmailId) {
rule.userEmailId = descriptor.userEmailId;
}
if (descriptor.userSmsId) {
rule.userSmsId = descriptor.userSmsId;
}
if (descriptor.userCallId) {
rule.userCallId = descriptor.userCallId;
}
if (descriptor.userWhatsAppId) {
rule.userWhatsAppId = descriptor.userWhatsAppId;
}
if (descriptor.userPushId) {
rule.userPushId = descriptor.userPushId;
}
}
private getNotificationMethodQuery(
descriptor: NotificationMethodDescriptor,
): Record<string, ObjectID> {
const query: Record<string, ObjectID> = {};
if (descriptor.userEmailId) {
query["userEmailId"] = descriptor.userEmailId;
}
if (descriptor.userSmsId) {
query["userSmsId"] = descriptor.userSmsId;
}
if (descriptor.userCallId) {
query["userCallId"] = descriptor.userCallId;
}
if (descriptor.userWhatsAppId) {
query["userWhatsAppId"] = descriptor.userWhatsAppId;
}
if (descriptor.userPushId) {
query["userPushId"] = descriptor.userPushId;
}
return query;
}
private async createIncidentOnCallRules(
projectId: ObjectID,
userId: ObjectID,
notificationMethod: NotificationMethodDescriptor,
): Promise<void> {
const incidentSeverities: Array<IncidentSeverity> =
await IncidentSeverityService.findBy({
query: {
@@ -2229,38 +2313,34 @@ export class Service extends DatabaseService<Model> {
},
});
// create for incident severities.
for (const incidentSeverity of incidentSeverities) {
//check if this rule already exists.
const existingRule: Model | null = await this.findOneBy({
query: {
projectId,
userId,
userEmailId: userEmail.id!,
...this.getNotificationMethodQuery(notificationMethod),
incidentSeverityId: incidentSeverity.id!,
ruleType: NotificationRuleType.ON_CALL_EXECUTED_INCIDENT,
},
} as any,
props: {
isRoot: true,
},
});
if (existingRule) {
continue; // skip this rule.
continue;
}
const notificationRule: Model = new Model();
notificationRule.projectId = projectId;
notificationRule.userId = userId;
notificationRule.userEmailId = userEmail.id!;
notificationRule.incidentSeverityId = incidentSeverity.id!;
notificationRule.notifyAfterMinutes = 0;
notificationRule.ruleType =
NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
const rule: Model = new Model();
rule.projectId = projectId;
rule.userId = userId;
this.applyNotificationMethod(rule, notificationMethod);
rule.incidentSeverityId = incidentSeverity.id!;
rule.notifyAfterMinutes = 0;
rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_INCIDENT;
await this.create({
data: notificationRule,
data: rule,
props: {
isRoot: true,
},
@@ -2268,14 +2348,11 @@ export class Service extends DatabaseService<Model> {
}
}
@CaptureSpan()
public async addDefaultAlertNotificationRulesForUser(data: {
projectId: ObjectID;
userId: ObjectID;
userEmail: UserEmail;
}): Promise<void> {
const { projectId, userId, userEmail } = data;
private async createAlertOnCallRules(
projectId: ObjectID,
userId: ObjectID,
notificationMethod: NotificationMethodDescriptor,
): Promise<void> {
const alertSeverities: Array<AlertSeverity> =
await AlertSeverityService.findBy({
query: {
@@ -2291,37 +2368,34 @@ export class Service extends DatabaseService<Model> {
},
});
// create for Alert severities.
for (const alertSeverity of alertSeverities) {
//check if this rule already exists.
const existingRule: Model | null = await this.findOneBy({
query: {
projectId,
userId,
userEmailId: userEmail.id!,
...this.getNotificationMethodQuery(notificationMethod),
alertSeverityId: alertSeverity.id!,
ruleType: NotificationRuleType.ON_CALL_EXECUTED_ALERT,
},
} as any,
props: {
isRoot: true,
},
});
if (existingRule) {
continue; // skip this rule.
continue;
}
const notificationRule: Model = new Model();
notificationRule.projectId = projectId;
notificationRule.userId = userId;
notificationRule.userEmailId = userEmail.id!;
notificationRule.alertSeverityId = alertSeverity.id!;
notificationRule.notifyAfterMinutes = 0;
notificationRule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT;
const rule: Model = new Model();
rule.projectId = projectId;
rule.userId = userId;
this.applyNotificationMethod(rule, notificationMethod);
rule.alertSeverityId = alertSeverity.id!;
rule.notifyAfterMinutes = 0;
rule.ruleType = NotificationRuleType.ON_CALL_EXECUTED_ALERT;
await this.create({
data: notificationRule,
data: rule,
props: {
isRoot: true,
},
@@ -2329,6 +2403,43 @@ export class Service extends DatabaseService<Model> {
}
}
private async createSingleRule(
projectId: ObjectID,
userId: ObjectID,
notificationMethod: NotificationMethodDescriptor,
ruleType: NotificationRuleType,
): Promise<void> {
const existingRule: Model | null = await this.findOneBy({
query: {
projectId,
userId,
...this.getNotificationMethodQuery(notificationMethod),
ruleType,
} as any,
props: {
isRoot: true,
},
});
if (existingRule) {
return;
}
const rule: Model = new Model();
rule.projectId = projectId;
rule.userId = userId;
this.applyNotificationMethod(rule, notificationMethod);
rule.notifyAfterMinutes = 0;
rule.ruleType = ruleType;
await this.create({
data: rule,
props: {
isRoot: true,
},
});
}
@CaptureSpan()
public async addDefaultNotificationRuleForUser(
projectId: ObjectID,
@@ -2361,82 +2472,13 @@ export class Service extends DatabaseService<Model> {
});
}
// add default incident rules for user
await this.addDefaultIncidentNotificationRuleForUser({
await this.addDefaultNotificationRulesForVerifiedMethod({
projectId,
userId,
userEmail,
});
// add default alert rules for user, just like the incident
await this.addDefaultAlertNotificationRulesForUser({
projectId,
userId,
userEmail,
});
//check if this rule already exists.
const existingRuleOnCall: Model | null = await this.findOneBy({
query: {
projectId,
userId,
notificationMethod: {
userEmailId: userEmail.id!,
ruleType: NotificationRuleType.WHEN_USER_GOES_ON_CALL,
},
props: {
isRoot: true,
},
});
if (!existingRuleOnCall) {
// on and off call.
const onCallRule: Model = new Model();
onCallRule.projectId = projectId;
onCallRule.userId = userId;
onCallRule.userEmailId = userEmail.id!;
onCallRule.notifyAfterMinutes = 0;
onCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_ON_CALL;
await this.create({
data: onCallRule,
props: {
isRoot: true,
},
});
}
//check if this rule already exists.
const existingRuleOffCall: Model | null = await this.findOneBy({
query: {
projectId,
userId,
userEmailId: userEmail.id!,
ruleType: NotificationRuleType.WHEN_USER_GOES_OFF_CALL,
},
props: {
isRoot: true,
},
});
if (!existingRuleOffCall) {
// on and off call.
const offCallRule: Model = new Model();
offCallRule.projectId = projectId;
offCallRule.userId = userId;
offCallRule.userEmailId = userEmail.id!;
offCallRule.notifyAfterMinutes = 0;
offCallRule.ruleType = NotificationRuleType.WHEN_USER_GOES_OFF_CALL;
await this.create({
data: offCallRule,
props: {
isRoot: true,
},
});
}
}
}
export default new Service();

View File

@@ -1,12 +1,8 @@
import Dictionary from "../../../Types/Dictionary";
import GenericObject from "../../../Types/GenericObject";
import ReturnResult from "../../../Types/IsolatedVM/ReturnResult";
import { JSONObject, JSONValue } from "../../../Types/JSON";
import axios from "axios";
import http from "http";
import https from "https";
import { JSONObject } from "../../../Types/JSON";
import axios, { AxiosResponse } from "axios";
import crypto from "crypto";
import vm, { Context } from "node:vm";
import ivm from "isolated-vm";
import CaptureSpan from "../Telemetry/CaptureSpan";
export default class VMRunner {
@@ -16,49 +12,230 @@ export default class VMRunner {
options: {
timeout?: number;
args?: JSONObject | undefined;
context?: Dictionary<GenericObject | string> | undefined;
};
}): Promise<ReturnResult> {
const { code, options } = data;
const timeout: number = options.timeout || 5000;
const logMessages: string[] = [];
let sandbox: Context = {
console: {
log: (...args: JSONValue[]) => {
const isolate: ivm.Isolate = new ivm.Isolate({ memoryLimit: 128 });
try {
const context: ivm.Context = await isolate.createContext();
const jail: ivm.Reference<Record<string, unknown>> = context.global;
// Set up global object
await jail.set("global", jail.derefInto());
// console.log - fire-and-forget callback
await jail.set(
"_log",
new ivm.Callback((...args: string[]) => {
logMessages.push(args.join(" "));
}),
);
await context.eval(`
const console = { log: (...a) => _log(...a.map(v => {
try { return typeof v === 'object' ? JSON.stringify(v) : String(v); }
catch(_) { return String(v); }
}))};
`);
// args - deep copy into isolate
if (options.args) {
await jail.set("_args", new ivm.ExternalCopy(options.args).copyInto());
await context.eval("const args = _args;");
} else {
await context.eval("const args = {};");
}
// axios (get, post, put, delete) - bridged via applySyncPromise
const axiosRef: ivm.Reference<
(method: string, url: string, dataOrConfig?: string) => Promise<string>
> = new ivm.Reference(
async (
method: string,
url: string,
dataOrConfig?: string,
): Promise<string> => {
const parsed: JSONObject | undefined = dataOrConfig
? (JSON.parse(dataOrConfig) as JSONObject)
: undefined;
let response: AxiosResponse;
switch (method) {
case "get":
response = await axios.get(url, parsed);
break;
case "post":
response = await axios.post(url, parsed);
break;
case "put":
response = await axios.put(url, parsed);
break;
case "delete":
response = await axios.delete(url, parsed);
break;
default:
throw new Error(`Unsupported HTTP method: ${method}`);
}
return JSON.stringify({
status: response.status,
headers: response.headers,
data: response.data,
});
},
},
http: http,
https: https,
axios: axios,
crypto: crypto,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
...options.context,
};
);
if (options.args) {
sandbox = {
...sandbox,
args: options.args,
};
}
await jail.set("_axiosRef", axiosRef);
vm.createContext(sandbox); // Contextify the object.
await context.eval(`
const axios = {
get: async (url, config) => {
const r = await _axiosRef.applySyncPromise(undefined, ['get', url, config ? JSON.stringify(config) : undefined]);
return JSON.parse(r);
},
post: async (url, data) => {
const r = await _axiosRef.applySyncPromise(undefined, ['post', url, data ? JSON.stringify(data) : undefined]);
return JSON.parse(r);
},
put: async (url, data) => {
const r = await _axiosRef.applySyncPromise(undefined, ['put', url, data ? JSON.stringify(data) : undefined]);
return JSON.parse(r);
},
delete: async (url, config) => {
const r = await _axiosRef.applySyncPromise(undefined, ['delete', url, config ? JSON.stringify(config) : undefined]);
return JSON.parse(r);
},
};
`);
const script: string = `(async()=>{
${code}
// crypto (createHash, createHmac, randomBytes) - bridged via applySync
const cryptoRef: ivm.Reference<
(op: string, ...args: string[]) => string
> = new ivm.Reference((op: string, ...args: string[]): string => {
switch (op) {
case "createHash": {
const [algorithm, inputData, encoding] = args;
return crypto
.createHash(algorithm!)
.update(inputData!)
.digest((encoding as crypto.BinaryToTextEncoding) || "hex");
}
case "createHmac": {
const [algorithm, key, inputData, encoding] = args;
return crypto
.createHmac(algorithm!, key!)
.update(inputData!)
.digest((encoding as crypto.BinaryToTextEncoding) || "hex");
}
case "randomBytes": {
const [size] = args;
return crypto.randomBytes(parseInt(size!)).toString("hex");
}
default:
throw new Error(`Unsupported crypto operation: ${op}`);
}
});
await jail.set("_cryptoRef", cryptoRef);
await context.eval(`
const crypto = {
createHash: (algorithm) => ({
_alg: algorithm, _data: '',
update(d) { this._data = d; return this; },
digest(enc) { return _cryptoRef.applySync(undefined, ['createHash', this._alg, this._data, enc || 'hex']); }
}),
createHmac: (algorithm, key) => ({
_alg: algorithm, _key: key, _data: '',
update(d) { this._data = d; return this; },
digest(enc) { return _cryptoRef.applySync(undefined, ['createHmac', this._alg, this._key, this._data, enc || 'hex']); }
}),
randomBytes: (size) => ({
toString(enc) { return _cryptoRef.applySync(undefined, ['randomBytes', String(size)]); }
}),
};
`);
// setTimeout / sleep - bridged via applySyncPromise
const sleepRef: ivm.Reference<(ms: number) => Promise<void>> =
new ivm.Reference((ms: number): Promise<void> => {
return new Promise((resolve: () => void) => {
global.setTimeout(resolve, Math.min(ms, timeout));
});
});
await jail.set("_sleepRef", sleepRef);
await context.eval(`
function setTimeout(fn, ms) {
_sleepRef.applySyncPromise(undefined, [ms || 0]);
if (typeof fn === 'function') fn();
}
async function sleep(ms) {
await _sleepRef.applySyncPromise(undefined, [ms || 0]);
}
`);
/*
* Wrap user code in async IIFE. JSON.stringify the return value inside
* the isolate so only a plain string crosses the boundary — this avoids
* "A non-transferable value was passed" errors when user code returns
* objects containing functions, class instances, or other non-cloneable types.
*/
const wrappedCode: string = `(async () => {
const __result = await (async () => {
${code}
})();
try { return JSON.stringify(__result); }
catch(_) { return undefined; }
})()`;
const returnVal: any = await vm.runInContext(script, sandbox, {
timeout: options.timeout || 5000,
}); // run the script
// Run with overall timeout covering both CPU and I/O wait
const resultPromise: Promise<unknown> = context.eval(wrappedCode, {
promise: true,
timeout: timeout,
});
return {
returnValue: returnVal,
logMessages,
};
const overallTimeout: Promise<never> = new Promise(
(_resolve: (value: never) => void, reject: (reason: Error) => void) => {
global.setTimeout(() => {
reject(new Error("Script execution timed out"));
}, timeout + 5000); // 5s grace period beyond isolate timeout
},
);
const result: unknown = await Promise.race([
resultPromise,
overallTimeout,
]);
// Parse the JSON string returned from inside the isolate
let returnValue: unknown;
if (typeof result === "string") {
try {
returnValue = JSON.parse(result);
} catch {
returnValue = result;
}
} else {
returnValue = result;
}
return {
returnValue,
logMessages,
};
} finally {
if (!isolate.isDisposed) {
isolate.dispose();
}
}
}
}

465
Common/package-lock.json generated
View File

@@ -62,6 +62,7 @@
"formik": "^2.4.6",
"history": "^5.3.0",
"ioredis": "^5.3.2",
"isolated-vm": "^6.0.2",
"json2csv": "^5.0.7",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.0",
@@ -423,7 +424,6 @@
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.0",
@@ -926,60 +926,47 @@
"resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.23.0.tgz",
"integrity": "sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@bull-board/api": "5.23.0"
}
},
"node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",
"integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/gast": "11.1.1",
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",
"integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/@chevrotain/gast/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",
"integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",
"integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",
"license": "Apache-2.0"
},
"node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",
"integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",
"license": "Apache-2.0"
},
"node_modules/@clickhouse/client": {
@@ -2202,12 +2189,12 @@
"license": "MIT"
},
"node_modules/@mermaid-js/parser": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz",
"integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz",
"integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==",
"license": "MIT",
"dependencies": {
"langium": "3.3.1"
"langium": "^4.0.0"
}
},
"node_modules/@monaco-editor/loader": {
@@ -2366,7 +2353,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -5516,7 +5502,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -5692,7 +5677,8 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz",
"integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/@types/unist": {
"version": "2.0.11",
@@ -5862,7 +5848,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6430,6 +6415,55 @@
"node": ">=6.0.0"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/bl/node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/bn.js": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
@@ -6755,7 +6789,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001669",
"electron-to-chromium": "^1.5.41",
@@ -7078,17 +7111,17 @@
}
},
"node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",
"integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/cst-dts-gen": "11.1.1",
"@chevrotain/gast": "11.1.1",
"@chevrotain/regexp-to-ast": "11.1.1",
"@chevrotain/types": "11.1.1",
"@chevrotain/utils": "11.1.1",
"lodash-es": "4.17.23"
}
},
"node_modules/chevrotain-allstar": {
@@ -7103,12 +7136,6 @@
"chevrotain": "^11.0.0"
}
},
"node_modules/chevrotain/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -7125,6 +7152,12 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
@@ -7593,7 +7626,6 @@
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz",
"integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10"
}
@@ -8015,7 +8047,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -8199,6 +8230,21 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
@@ -8239,6 +8285,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/deepmerge": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
@@ -8398,7 +8453,6 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=8"
}
@@ -8653,6 +8707,15 @@
"node": ">= 0.8"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/engine.io": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
@@ -9030,6 +9093,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/expect": {
"version": "28.1.3",
"resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz",
@@ -9491,6 +9563,12 @@
"node": ">= 0.6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fs-extra": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
@@ -9634,6 +9712,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -10140,6 +10224,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/inline-style-parser": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
@@ -10675,6 +10765,19 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/isolated-vm": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-6.0.2.tgz",
"integrity": "sha512-Qw6AJuagG/VJuh2AIcSWmQPsAArti/L+lKhjXU+lyhYkbt3J57XZr+ZjgfTnOr4NJcY1r3f8f0eePS7MRGp+pg==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"prebuild-install": "^7.1.3"
},
"engines": {
"node": ">=22.0.0"
}
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@@ -10795,7 +10898,6 @@
"integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "^28.1.3",
"@jest/types": "^28.1.3",
@@ -12185,19 +12287,20 @@
}
},
"node_modules/langium": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz",
"integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
"integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
"license": "MIT",
"dependencies": {
"chevrotain": "~11.0.3",
"chevrotain-allstar": "~0.3.0",
"chevrotain": "~11.1.1",
"chevrotain-allstar": "~0.3.1",
"vscode-languageserver": "~9.0.1",
"vscode-languageserver-textdocument": "~1.0.11",
"vscode-uri": "~3.0.8"
"vscode-uri": "~3.1.0"
},
"engines": {
"node": ">=16.0.0"
"node": ">=20.10.0",
"npm": ">=10.2.3"
}
},
"node_modules/layout-base": {
@@ -13099,14 +13202,14 @@
}
},
"node_modules/mermaid": {
"version": "11.12.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz",
"integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
"version": "11.12.3",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz",
"integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "^0.6.3",
"@mermaid-js/parser": "^1.0.0",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
@@ -13118,7 +13221,7 @@
"dompurify": "^3.2.5",
"katex": "^0.16.22",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"lodash-es": "^4.17.23",
"marked": "^16.2.1",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
@@ -13785,6 +13888,18 @@
"node": ">=6"
}
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -13843,6 +13958,12 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/mlly": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
@@ -13947,6 +14068,12 @@
"node": ">= 10.16.0"
}
},
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -13973,6 +14100,18 @@
"tslib": "^2.0.3"
}
},
"node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
@@ -14200,7 +14339,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -14511,7 +14649,6 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
@@ -14788,6 +14925,32 @@
"url": "https://opencollective.com/preact"
}
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
@@ -14894,7 +15057,6 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -14973,6 +15135,16 @@
"punycode": "^2.3.1"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -15193,12 +15365,35 @@
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/rc/node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -15283,7 +15478,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -15804,8 +15998,7 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
"license": "Apache-2.0",
"peer": true
"license": "Apache-2.0"
},
"node_modules/refractor": {
"version": "5.0.0",
@@ -16585,6 +16778,51 @@
"dev": true,
"license": "ISC"
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -17552,6 +17790,48 @@
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-fs/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/tar-fs/node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tar-stream": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
@@ -17851,6 +18131,18 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/twilio": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/twilio/-/twilio-4.23.0.tgz",
@@ -18630,9 +18922,9 @@
"license": "MIT"
},
"node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"license": "MIT"
},
"node_modules/w3c-xmlserializer": {
@@ -18921,7 +19213,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/write-file-atomic": {
@@ -19102,7 +19393,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -19111,8 +19401,7 @@
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/zustand": {
"version": "4.5.5",

View File

@@ -101,6 +101,7 @@
"formik": "^2.4.6",
"history": "^5.3.0",
"ioredis": "^5.3.2",
"isolated-vm": "^6.0.2",
"json2csv": "^5.0.7",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.0",

View File

@@ -386,45 +386,45 @@
}
},
"../Common/node_modules/@aws-sdk/client-sso": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz",
"integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.990.0.tgz",
"integrity": "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
"@aws-sdk/middleware-user-agent": "^3.972.5",
"@aws-sdk/middleware-user-agent": "^3.972.10",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/util-endpoints": "3.980.0",
"@aws-sdk/util-endpoints": "3.990.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.8",
"@smithy/config-resolver": "^4.4.6",
"@smithy/core": "^3.22.0",
"@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
"@smithy/middleware-endpoint": "^4.4.12",
"@smithy/middleware-retry": "^4.4.29",
"@smithy/middleware-endpoint": "^4.4.14",
"@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/node-http-handler": "^4.4.8",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
"@smithy/util-defaults-mode-browser": "^4.3.28",
"@smithy/util-defaults-mode-node": "^4.2.31",
"@smithy/util-defaults-mode-browser": "^4.3.30",
"@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -436,9 +436,9 @@
}
},
"../Common/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
"integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz",
"integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -453,20 +453,20 @@
}
},
"../Common/node_modules/@aws-sdk/core": {
"version": "3.973.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz",
"integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==",
"version": "3.973.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.10.tgz",
"integrity": "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/xml-builder": "^3.972.2",
"@smithy/core": "^3.22.0",
"@aws-sdk/xml-builder": "^3.972.4",
"@smithy/core": "^3.23.0",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/signature-v4": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-middleware": "^4.2.8",
@@ -478,13 +478,13 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-env": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz",
"integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.8.tgz",
"integrity": "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/types": "^4.12.0",
@@ -495,21 +495,21 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-http": {
"version": "3.972.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz",
"integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==",
"version": "3.972.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.10.tgz",
"integrity": "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/node-http-handler": "^4.4.8",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/util-stream": "^4.5.10",
"@smithy/util-stream": "^4.5.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -517,20 +517,20 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz",
"integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.8.tgz",
"integrity": "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/credential-provider-env": "^3.972.3",
"@aws-sdk/credential-provider-http": "^3.972.5",
"@aws-sdk/credential-provider-login": "^3.972.3",
"@aws-sdk/credential-provider-process": "^3.972.3",
"@aws-sdk/credential-provider-sso": "^3.972.3",
"@aws-sdk/credential-provider-web-identity": "^3.972.3",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/credential-provider-env": "^3.972.8",
"@aws-sdk/credential-provider-http": "^3.972.10",
"@aws-sdk/credential-provider-login": "^3.972.8",
"@aws-sdk/credential-provider-process": "^3.972.8",
"@aws-sdk/credential-provider-sso": "^3.972.8",
"@aws-sdk/credential-provider-web-identity": "^3.972.8",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/property-provider": "^4.2.8",
@@ -543,14 +543,14 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-login": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz",
"integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.8.tgz",
"integrity": "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
@@ -563,18 +563,18 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-node": {
"version": "3.972.4",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz",
"integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==",
"version": "3.972.9",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.9.tgz",
"integrity": "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "^3.972.3",
"@aws-sdk/credential-provider-http": "^3.972.5",
"@aws-sdk/credential-provider-ini": "^3.972.3",
"@aws-sdk/credential-provider-process": "^3.972.3",
"@aws-sdk/credential-provider-sso": "^3.972.3",
"@aws-sdk/credential-provider-web-identity": "^3.972.3",
"@aws-sdk/credential-provider-env": "^3.972.8",
"@aws-sdk/credential-provider-http": "^3.972.10",
"@aws-sdk/credential-provider-ini": "^3.972.8",
"@aws-sdk/credential-provider-process": "^3.972.8",
"@aws-sdk/credential-provider-sso": "^3.972.8",
"@aws-sdk/credential-provider-web-identity": "^3.972.8",
"@aws-sdk/types": "^3.973.1",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/property-provider": "^4.2.8",
@@ -587,13 +587,13 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-process": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz",
"integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.8.tgz",
"integrity": "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -605,15 +605,15 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz",
"integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.8.tgz",
"integrity": "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/client-sso": "3.980.0",
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/token-providers": "3.980.0",
"@aws-sdk/client-sso": "3.990.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/token-providers": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -625,14 +625,14 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-web-identity": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz",
"integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.8.tgz",
"integrity": "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -692,16 +692,16 @@
}
},
"../Common/node_modules/@aws-sdk/middleware-user-agent": {
"version": "3.972.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz",
"integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==",
"version": "3.972.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.10.tgz",
"integrity": "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/util-endpoints": "3.980.0",
"@smithy/core": "^3.22.0",
"@aws-sdk/util-endpoints": "3.990.0",
"@smithy/core": "^3.23.0",
"@smithy/protocol-http": "^5.3.8",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
@@ -711,9 +711,9 @@
}
},
"../Common/node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
"integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz",
"integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -728,45 +728,45 @@
}
},
"../Common/node_modules/@aws-sdk/nested-clients": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz",
"integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz",
"integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
"@aws-sdk/middleware-user-agent": "^3.972.5",
"@aws-sdk/middleware-user-agent": "^3.972.10",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/util-endpoints": "3.980.0",
"@aws-sdk/util-endpoints": "3.990.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.8",
"@smithy/config-resolver": "^4.4.6",
"@smithy/core": "^3.22.0",
"@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
"@smithy/middleware-endpoint": "^4.4.12",
"@smithy/middleware-retry": "^4.4.29",
"@smithy/middleware-endpoint": "^4.4.14",
"@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/node-http-handler": "^4.4.8",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
"@smithy/util-defaults-mode-browser": "^4.3.28",
"@smithy/util-defaults-mode-node": "^4.2.31",
"@smithy/util-defaults-mode-browser": "^4.3.30",
"@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -778,9 +778,9 @@
}
},
"../Common/node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
"integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz",
"integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -812,14 +812,14 @@
}
},
"../Common/node_modules/@aws-sdk/token-providers": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz",
"integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.990.0.tgz",
"integrity": "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -888,13 +888,13 @@
}
},
"../Common/node_modules/@aws-sdk/util-user-agent-node": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz",
"integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.8.tgz",
"integrity": "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/middleware-user-agent": "^3.972.5",
"@aws-sdk/middleware-user-agent": "^3.972.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/types": "^4.12.0",
@@ -913,9 +913,9 @@
}
},
"../Common/node_modules/@aws-sdk/xml-builder": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz",
"integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==",
"version": "3.972.4",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz",
"integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1687,54 +1687,42 @@
}
},
"../Common/node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",
"integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/gast": "11.1.1",
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"../Common/node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"../Common/node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",
"integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"../Common/node_modules/@chevrotain/gast/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"../Common/node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",
"integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",
"license": "Apache-2.0"
},
"../Common/node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",
"integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",
"license": "Apache-2.0"
},
"../Common/node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",
"integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",
"license": "Apache-2.0"
},
"../Common/node_modules/@clickhouse/client": {
@@ -3039,12 +3027,12 @@
"license": "MIT"
},
"../Common/node_modules/@mermaid-js/parser": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz",
"integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz",
"integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==",
"license": "MIT",
"dependencies": {
"langium": "3.3.1"
"langium": "^4.0.0"
}
},
"../Common/node_modules/@monaco-editor/loader": {
@@ -5084,9 +5072,9 @@
}
},
"../Common/node_modules/@smithy/core": {
"version": "3.22.1",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz",
"integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==",
"version": "3.23.2",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.2.tgz",
"integrity": "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5096,7 +5084,7 @@
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-stream": "^4.5.11",
"@smithy/util-stream": "^4.5.12",
"@smithy/util-utf8": "^4.2.0",
"@smithy/uuid": "^1.1.0",
"tslib": "^2.6.2"
@@ -5198,13 +5186,13 @@
}
},
"../Common/node_modules/@smithy/middleware-endpoint": {
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz",
"integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==",
"version": "4.4.16",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.16.tgz",
"integrity": "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.22.1",
"@smithy/core": "^3.23.2",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -5218,16 +5206,16 @@
}
},
"../Common/node_modules/@smithy/middleware-retry": {
"version": "4.4.30",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz",
"integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==",
"version": "4.4.33",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.33.tgz",
"integrity": "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/node-config-provider": "^4.3.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/service-error-classification": "^4.2.8",
"@smithy/smithy-client": "^4.11.2",
"@smithy/smithy-client": "^4.11.5",
"@smithy/types": "^4.12.0",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -5284,9 +5272,9 @@
}
},
"../Common/node_modules/@smithy/node-http-handler": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz",
"integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==",
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz",
"integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5405,18 +5393,18 @@
}
},
"../Common/node_modules/@smithy/smithy-client": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz",
"integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==",
"version": "4.11.5",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.5.tgz",
"integrity": "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.22.1",
"@smithy/middleware-endpoint": "^4.4.13",
"@smithy/core": "^3.23.2",
"@smithy/middleware-endpoint": "^4.4.16",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/types": "^4.12.0",
"@smithy/util-stream": "^4.5.11",
"@smithy/util-stream": "^4.5.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -5520,14 +5508,14 @@
}
},
"../Common/node_modules/@smithy/util-defaults-mode-browser": {
"version": "4.3.29",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz",
"integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==",
"version": "4.3.32",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.32.tgz",
"integrity": "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/property-provider": "^4.2.8",
"@smithy/smithy-client": "^4.11.2",
"@smithy/smithy-client": "^4.11.5",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
},
@@ -5536,9 +5524,9 @@
}
},
"../Common/node_modules/@smithy/util-defaults-mode-node": {
"version": "4.2.32",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz",
"integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==",
"version": "4.2.35",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.35.tgz",
"integrity": "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5546,7 +5534,7 @@
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/property-provider": "^4.2.8",
"@smithy/smithy-client": "^4.11.2",
"@smithy/smithy-client": "^4.11.5",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
},
@@ -5612,14 +5600,14 @@
}
},
"../Common/node_modules/@smithy/util-stream": {
"version": "4.5.11",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz",
"integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==",
"version": "4.5.12",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz",
"integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/node-http-handler": "^4.4.9",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/types": "^4.12.0",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-buffer-from": "^4.2.0",
@@ -8040,17 +8028,17 @@
}
},
"../Common/node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",
"integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/cst-dts-gen": "11.1.1",
"@chevrotain/gast": "11.1.1",
"@chevrotain/regexp-to-ast": "11.1.1",
"@chevrotain/types": "11.1.1",
"@chevrotain/utils": "11.1.1",
"lodash-es": "4.17.23"
}
},
"../Common/node_modules/chevrotain-allstar": {
@@ -8065,12 +8053,6 @@
"chevrotain": "^11.0.0"
}
},
"../Common/node_modules/chevrotain/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"../Common/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -13296,19 +13278,20 @@
}
},
"../Common/node_modules/langium": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz",
"integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
"integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
"license": "MIT",
"dependencies": {
"chevrotain": "~11.0.3",
"chevrotain-allstar": "~0.3.0",
"chevrotain": "~11.1.1",
"chevrotain-allstar": "~0.3.1",
"vscode-languageserver": "~9.0.1",
"vscode-languageserver-textdocument": "~1.0.11",
"vscode-uri": "~3.0.8"
"vscode-uri": "~3.1.0"
},
"engines": {
"node": ">=16.0.0"
"node": ">=20.10.0",
"npm": ">=10.2.3"
}
},
"../Common/node_modules/layout-base": {
@@ -14106,14 +14089,14 @@
}
},
"../Common/node_modules/mermaid": {
"version": "11.12.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz",
"integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
"version": "11.12.3",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz",
"integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "^0.6.3",
"@mermaid-js/parser": "^1.0.0",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
@@ -14125,7 +14108,7 @@
"dompurify": "^3.2.5",
"katex": "^0.16.22",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"lodash-es": "^4.17.23",
"marked": "^16.2.1",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
@@ -19836,9 +19819,9 @@
"license": "MIT"
},
"../Common/node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"license": "MIT"
},
"../Common/node_modules/w3c-xmlserializer": {

View File

@@ -198,11 +198,29 @@ Usage:
{{- end }}
{{- end }}
{{- define "oneuptime.env.registerProbeKey" }}
- name: REGISTER_PROBE_KEY
{{- if $.Values.registerProbeKey }}
value: {{ $.Values.registerProbeKey }}
{{- else }}
valueFrom:
secretKeyRef:
name: {{ printf "%s-%s" $.Release.Name "secrets" }}
key: register-probe-key
{{- end }}
{{- end }}
{{- define "oneuptime.env.runtime" }}
- name: VAPID_PRIVATE_KEY
value: {{ $.Values.vapid.privateKey }}
- name: EXPO_ACCESS_TOKEN
value: {{ default "" $.Values.expo.accessToken | quote }}
- name: PUSH_NOTIFICATION_RELAY_URL
value: {{ default "https://oneuptime.com/api/notification/push-relay/send" $.Values.pushNotification.relayUrl | quote }}
- name: SLACK_APP_CLIENT_SECRET
value: {{ $.Values.slackApp.clientSecret }}

View File

@@ -73,12 +73,23 @@ spec:
{{- end }}
imagePullPolicy: {{ $.Values.image.pullPolicy }}
env:
{{- include "oneuptime.env.common" . | nindent 12 }}
{{- include "oneuptime.env.oneuptimeSecret" . | nindent 12 }}
- name: PORT
value: {{ $.Values.isolatedVM.ports.http | quote }}
- name: LOG_LEVEL
value: {{ $.Values.logLevel }}
- name: NODE_ENV
value: {{ $.Values.nodeEnvironment }}
- name: DISABLE_TELEMETRY
value: {{ $.Values.isolatedVM.disableTelemetryCollection | quote }}
{{- if $.Values.openTelemetryExporter.endpoint }}
- name: OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT
value: {{ $.Values.openTelemetryExporter.endpoint }}
{{- end }}
{{- if $.Values.openTelemetryExporter.headers }}
- name: OPENTELEMETRY_EXPORTER_OTLP_HEADERS
value: {{ $.Values.openTelemetryExporter.headers }}
{{- end }}
ports:
- containerPort: {{ $.Values.isolatedVM.ports.http }}

View File

@@ -112,6 +112,7 @@ spec:
value: {{ $.Values.probeIngest.disableTelemetryCollection | quote }}
- name: PROBE_INGEST_CONCURRENCY
value: {{ $.Values.probeIngest.concurrency | squote }}
{{- include "oneuptime.env.registerProbeKey" (dict "Values" $.Values "Release" $.Release) | nindent 12 }}
ports:
- containerPort: {{ $.Values.probeIngest.ports.http }}
protocol: TCP

View File

@@ -131,7 +131,7 @@ spec:
- name: NO_PROXY
value: {{ $val.proxy.noProxy | squote }}
{{- end }}
{{- include "oneuptime.env.runtime" (dict "Values" $.Values "Release" $.Release) | nindent 12 }}
{{- include "oneuptime.env.registerProbeKey" (dict "Values" $.Values "Release" $.Release) | nindent 12 }}
ports:
- containerPort: {{ if and $val.ports $val.ports.http }}{{ $val.ports.http }}{{ else }}3874{{ end }}
protocol: TCP

View File

@@ -17,6 +17,13 @@ stringData:
{{- else }}
oneuptime-secret: {{ index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "oneuptime-secret" | b64dec }}
{{- end }}
{{- if .Values.registerProbeKey }}
register-probe-key: {{ .Values.registerProbeKey | quote }}
{{- else if (index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "register-probe-key") }}
register-probe-key: {{ index (lookup "v1" "Secret" $.Release.Namespace (printf "%s-secrets" $.Release.Name)).data "register-probe-key" | b64dec }}
{{- else }}
register-probe-key: {{ randAlphaNum 32 | quote }}
{{- end }}
{{- if .Values.encryptionSecret }}
encryption-secret: {{ .Values.encryptionSecret | quote }}
{{- else }}
@@ -48,6 +55,11 @@ stringData:
{{- else }}
oneuptime-secret: {{ randAlphaNum 32 | quote }}
{{- end }}
{{- if .Values.registerProbeKey }}
register-probe-key: {{ .Values.registerProbeKey | quote }}
{{- else }}
register-probe-key: {{ randAlphaNum 32 | quote }}
{{- end }}
{{- if .Values.encryptionSecret }}
encryption-secret: {{ .Values.encryptionSecret | quote }}
{{- else }}

View File

@@ -35,6 +35,9 @@
"oneuptimeSecret": {
"type": ["string", "null"]
},
"registerProbeKey": {
"type": ["string", "null"]
},
"encryptionSecret": {
"type": ["string", "null"]
},
@@ -727,6 +730,24 @@
},
"additionalProperties": false
},
"expo": {
"type": "object",
"properties": {
"accessToken": {
"type": ["string", "null"]
}
},
"additionalProperties": false
},
"pushNotification": {
"type": "object",
"properties": {
"relayUrl": {
"type": ["string", "null"]
}
},
"additionalProperties": false
},
"incidents": {
"type": "object",
"properties": {

View File

@@ -32,6 +32,7 @@ image:
# Important: You do need to set this to a long random values if you're using OneUptime in production.
# Please set this to string.
oneuptimeSecret:
registerProbeKey:
encryptionSecret:
# External Secrets
@@ -287,6 +288,15 @@ vapid:
privateKey:
subject: mailto:support@oneuptime.com
# Expo access token for sending mobile push notifications directly via Expo SDK.
# If not set, notifications are relayed through the push notification relay URL.
expo:
accessToken:
# Push notification relay URL for self-hosted instances without Expo credentials
pushNotification:
relayUrl: https://oneuptime.com/api/notification/push-relay/send
incidents:
disableAutomaticCreation: false

View File

@@ -3,7 +3,7 @@
#
# Pull base image nodejs image.
FROM public.ecr.aws/docker/library/node:21.6-alpine3.18
FROM public.ecr.aws/docker/library/node:24.9-alpine3.21
RUN mkdir /tmp/npm && chmod 2777 /tmp/npm && chown 1000:1000 /tmp/npm && npm config set cache /tmp/npm --global
RUN npm config set fetch-retries 5

6
MCP/package-lock.json generated
View File

@@ -1297,9 +1297,9 @@
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",

View File

@@ -1,7 +1,7 @@
{
"expo": {
"name": "OneUptime",
"slug": "oneuptime",
"slug": "oneuptime-on-call",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
@@ -11,7 +11,7 @@
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#FFFFFF"
"backgroundColor": "#0D1117"
},
"ios": {
"supportsTablet": true,
@@ -29,9 +29,22 @@
"backgroundColor": "#0D1117"
},
"edgeToEdgeEnabled": false,
"package": "com.oneuptime.oncall"
"package": "com.oneuptime.oncall",
"googleServicesFile": "./google-services.json",
"permissions": [
"android.permission.USE_BIOMETRIC",
"android.permission.USE_FINGERPRINT"
]
},
"plugins": [
[
"expo-splash-screen",
{
"backgroundColor": "#0D1117",
"image": "./assets/splash-icon.png",
"imageWidth": 200
}
],
[
"expo-notifications",
{
@@ -43,6 +56,12 @@
],
"web": {
"favicon": "./assets/favicon.png"
}
},
"extra": {
"eas": {
"projectId": "d9f87edc-1c3e-466f-b032-1ced7621aa8a"
}
},
"owner": "oneuptime"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 44 KiB

18
MobileApp/eas.json Normal file
View File

@@ -0,0 +1,18 @@
{
"cli": {
"version": ">= 3.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal"
},
"production": {}
},
"submit": {
"production": {}
}
}

View File

@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "877286215861",
"project_id": "oneuptime-on-call",
"storage_bucket": "oneuptime-on-call.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:877286215861:android:f783c4aa68be6cba54eefc",
"android_client_info": {
"package_name": "com.oneuptime.oncall"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyBVUYdIldBliGtG0mIGa-nNs6eUJkJmKOM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}

View File

@@ -97,7 +97,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -1378,6 +1377,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
"integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -3171,7 +3171,6 @@
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.28.tgz",
"integrity": "sha512-d1QDn+KNHfHGt3UIwOZvupvdsDdiHYZBEj7+wL2yDVo3tMezamYy60H9s3EnNVE1Ae1ty0trc7F2OKqo/RmsdQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@react-navigation/core": "^7.14.0",
"escape-string-regexp": "^4.0.0",
@@ -3290,7 +3289,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz",
"integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tanstack/query-core": "5.90.20"
},
@@ -3408,7 +3406,6 @@
"integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4130,7 +4127,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -5206,7 +5202,6 @@
"resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz",
"integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "54.0.23",
@@ -5294,7 +5289,6 @@
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz",
"integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==",
"license": "MIT",
"peer": true,
"dependencies": {
"fontfaceobserver": "^2.1.0"
},
@@ -7206,7 +7200,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"license": "MIT",
"peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -8739,7 +8732,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -9071,7 +9063,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9091,7 +9082,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -9122,7 +9112,6 @@
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz",
"integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/assets-registry": "0.81.5",
@@ -9486,6 +9475,7 @@
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz",
"integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==",
"license": "MIT",
"peer": true,
"dependencies": {
"react-native-is-edge-to-edge": "1.2.1",
"semver": "7.7.3"
@@ -9501,6 +9491,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -9513,7 +9504,6 @@
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz",
"integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -9524,7 +9514,6 @@
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz",
"integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"react-freeze": "^1.0.0",
"react-native-is-edge-to-edge": "^1.2.1",
@@ -9555,7 +9544,6 @@
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.6",
"@react-native/normalize-colors": "^0.74.1",
@@ -9588,6 +9576,7 @@
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.3.tgz",
"integrity": "sha512-m/CIUCHvLQulboBn0BtgpsesXjOTeubU7t+V0lCPpBj0t2ExigwqDHoKj3ck7OeErnjgkD27wdAtQCubYATe3g==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/plugin-transform-arrow-functions": "7.27.1",
"@babel/plugin-transform-class-properties": "7.27.1",
@@ -9612,6 +9601,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
"integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1"
@@ -9628,6 +9618,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
"integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
@@ -9648,6 +9639,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
"integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -9663,6 +9655,7 @@
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
@@ -9679,6 +9672,7 @@
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
"integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
@@ -9698,6 +9692,7 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -9794,7 +9789,6 @@
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -10626,7 +10620,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -10660,9 +10653,9 @@
}
},
"node_modules/tar": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
@@ -10855,7 +10848,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -11416,7 +11408,6 @@
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"license": "ISC",
"peer": true,
"bin": {
"yaml": "bin.mjs"
},

View File

@@ -51,7 +51,7 @@ function AppContent(): React.JSX.Element {
borderRadius: 999,
}}
/>
<StatusBar style={theme.isDark ? "light" : "dark"} />
<StatusBar style="light" />
<RootNavigator />
<OfflineBanner />
</View>

View File

@@ -1,6 +1,7 @@
import { Platform } from "react-native";
import * as Device from "expo-device";
import apiClient from "./client";
import logger from "../utils/logger";
export async function registerPushDevice(params: {
deviceToken: string;
@@ -8,10 +9,10 @@ export async function registerPushDevice(params: {
}): Promise<void> {
const deviceType: string =
Platform.OS === "ios"
? "iOS"
? "ios"
: Platform.OS === "android"
? "Android"
: "Web";
? "android"
: "web";
try {
await apiClient.post("/api/user-push/register", {
@@ -20,11 +21,29 @@ export async function registerPushDevice(params: {
deviceName: Device.modelName || "Unknown Device",
projectId: params.projectId,
});
} catch (error: any) {
logger.info(
`[PushNotifications] Device registered successfully for project ${params.projectId}`,
);
} catch (error: unknown) {
const status: number | undefined = (
error as { response?: { status?: number } }
)?.response?.status;
const message: string =
(error as { response?: { data?: { message?: string } } })?.response?.data
?.message || String(error);
// Treat "already registered" as success
if (error?.response?.status === 400) {
if (status === 400 && message.includes("already registered")) {
logger.info(
`[PushNotifications] Device already registered for project ${params.projectId}`,
);
return;
}
// Log and re-throw other errors
logger.error(
`[PushNotifications] Registration failed (status=${status}): ${message}`,
);
throw error;
}
}

View File

@@ -49,32 +49,53 @@ export default function AddNoteModal({
onRequestClose={handleClose}
>
<KeyboardAvoidingView
className="flex-1 justify-end"
style={{ backgroundColor: "rgba(0,0,0,0.5)" }}
style={{
flex: 1,
justifyContent: "flex-end",
backgroundColor: "rgba(0,0,0,0.5)",
}}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<View
className="rounded-t-3xl p-5 pb-9"
style={{
backgroundColor: theme.isDark
? theme.colors.backgroundElevated
: theme.colors.backgroundPrimary,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
padding: 20,
paddingBottom: 36,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderBottomWidth: 0,
borderColor: theme.colors.borderGlass,
}}
>
<View className="items-center pt-1 pb-5">
<View
style={{ alignItems: "center", paddingTop: 4, paddingBottom: 20 }}
>
<View
className="w-9 h-1 rounded-full"
style={{ backgroundColor: theme.colors.borderDefault }}
style={{
width: 36,
height: 4,
borderRadius: 9999,
backgroundColor: theme.colors.borderDefault,
}}
/>
</View>
<View className="flex-row items-center mb-5">
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
}}
>
<View
className="w-8 h-8 rounded-lg items-center justify-center mr-3"
style={{
width: 32,
height: 32,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
backgroundColor: theme.colors.iconBackground,
}}
>
@@ -85,8 +106,9 @@ export default function AddNoteModal({
/>
</View>
<Text
className="text-[18px] font-bold"
style={{
fontSize: 18,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.3,
}}
@@ -96,8 +118,11 @@ export default function AddNoteModal({
</View>
<TextInput
className="min-h-[120px] rounded-xl p-4 text-[15px]"
style={{
minHeight: 120,
borderRadius: 12,
padding: 16,
fontSize: 15,
backgroundColor: theme.colors.backgroundSecondary,
borderWidth: 1,
borderColor: theme.colors.borderDefault,
@@ -112,16 +137,22 @@ export default function AddNoteModal({
editable={!isSubmitting}
/>
<View className="flex-row gap-3 mt-5">
<View
style={{
flexDirection: "row",
gap: 12,
marginTop: 20,
alignItems: "center",
}}
>
<Pressable
style={({ pressed }: { pressed: boolean }) => {
return {
flex: 1,
paddingVertical: 12,
height: 50,
borderRadius: 12,
alignItems: "center" as const,
justifyContent: "center" as const,
minHeight: 48,
borderWidth: 1,
borderColor: theme.colors.borderDefault,
opacity: pressed ? 0.7 : 1,
@@ -131,21 +162,23 @@ export default function AddNoteModal({
disabled={isSubmitting}
>
<Text
className="text-[15px] font-semibold"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 15,
fontWeight: "600",
color: theme.colors.textSecondary,
}}
>
Cancel
</Text>
</Pressable>
<View className="flex-1">
<GradientButton
label="Submit"
onPress={handleSubmit}
loading={isSubmitting}
disabled={!noteText.trim() || isSubmitting}
/>
</View>
<GradientButton
label="Submit"
onPress={handleSubmit}
loading={isSubmitting}
disabled={!noteText.trim() || isSubmitting}
style={{ flex: 1 }}
/>
</View>
</View>
</KeyboardAvoidingView>

View File

@@ -45,13 +45,14 @@ export default function AlertCard({
accessibilityLabel={`Alert ${alert.alertNumberWithPrefix || alert.alertNumber}, ${alert.title}. State: ${alert.currentAlertState?.name ?? "unknown"}. Severity: ${alert.alertSeverity?.name ?? "unknown"}.`}
>
<View
className="rounded-3xl overflow-hidden"
style={{
borderRadius: 24,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : "#111827",
shadowOpacity: theme.isDark ? 0.22 : 0.08,
shadowColor: "#000",
shadowOpacity: 0.22,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 14,
elevation: 5,
@@ -64,13 +65,32 @@ export default function AlertCard({
opacity: 1,
}}
/>
<View className="p-4">
<View className="flex-row justify-between items-center mb-2.5">
<View className="flex-row items-center gap-2">
<View style={{ padding: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
{projectName ? <ProjectBadge name={projectName} /> : null}
<View
className="flex-row items-center px-2 py-1 rounded-full"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.iconBackground,
}}
>
<Ionicons
name="notifications-outline"
@@ -79,8 +99,9 @@ export default function AlertCard({
style={{ marginRight: 4 }}
/>
<Text
className="text-[10px] font-semibold"
style={{
fontSize: 10,
fontWeight: "600",
color: theme.colors.textSecondary,
letterSpacing: 0.3,
}}
@@ -89,16 +110,19 @@ export default function AlertCard({
</Text>
</View>
<View
className="px-2.5 py-1 rounded-full"
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderDefault,
}}
>
<Text
className="text-[12px] font-bold"
style={{
fontSize: 12,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: 0.2,
}}
@@ -107,26 +131,35 @@ export default function AlertCard({
</Text>
</View>
</View>
<View className="flex-row items-center">
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name="time-outline"
size={12}
color={theme.colors.textTertiary}
style={{ marginRight: 4 }}
/>
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
>
<Text style={{ fontSize: 12, color: theme.colors.textTertiary }}>
{timeString}
</Text>
</View>
</View>
<View className="flex-row items-start mt-0.5">
<View
style={{
flexDirection: "row",
alignItems: "flex-start",
marginTop: 2,
}}
>
<Text
className="text-[16px] font-semibold flex-1 pr-2"
style={{ color: theme.colors.textPrimary, letterSpacing: -0.2 }}
style={{
fontSize: 16,
fontWeight: "600",
flex: 1,
paddingRight: 8,
color: theme.colors.textPrimary,
letterSpacing: -0.2,
}}
numberOfLines={2}
>
{alert.title}
@@ -139,21 +172,40 @@ export default function AlertCard({
/>
</View>
<View className="flex-row flex-wrap gap-2 mt-3">
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{alert.currentAlertState ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
className="text-[11px] font-semibold"
style={{ color: stateColor }}
style={{
fontSize: 11,
fontWeight: "600",
color: stateColor,
}}
>
{alert.currentAlertState.name}
</Text>
@@ -162,12 +214,21 @@ export default function AlertCard({
{alert.alertSeverity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
<Text
className="text-[11px] font-semibold"
style={{ color: severityColor }}
style={{
fontSize: 11,
fontWeight: "600",
color: severityColor,
}}
>
{alert.alertSeverity.name}
</Text>
@@ -177,33 +238,45 @@ export default function AlertCard({
{alert.monitor ? (
<View
className="flex-row items-center mt-3 pt-3"
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: theme.colors.borderSubtle,
}}
>
<View
className="w-6 h-6 rounded-full items-center justify-center mr-2"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
width: 24,
height: 24,
borderRadius: 9999,
alignItems: "center",
justifyContent: "center",
marginRight: 8,
backgroundColor: theme.colors.iconBackground,
}}
>
<Ionicons
name="desktop-outline"
name="pulse-outline"
size={12}
color={theme.colors.textSecondary}
/>
</View>
<View className="flex-1">
<View style={{ flex: 1 }}>
<Text
className="text-[12px]"
style={{ color: theme.colors.textSecondary }}
style={{ fontSize: 12, color: theme.colors.textSecondary }}
numberOfLines={1}
>
{alert.monitor.name}
</Text>
<Text
className="text-[11px] mt-0.5"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 11,
marginTop: 2,
color: theme.colors.textTertiary,
}}
>
Linked monitor
</Text>

View File

@@ -32,10 +32,22 @@ export default function EmptyState({
const { theme } = useTheme();
return (
<View className="flex-1 items-center justify-center px-10 py-28">
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 40,
paddingVertical: 112,
}}
>
<View
className="w-20 h-20 rounded-2xl items-center justify-center"
style={{
width: 80,
height: 80,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.iconBackground,
}}
>
@@ -47,20 +59,35 @@ export default function EmptyState({
</View>
<Text
className="text-[20px] font-bold text-text-primary text-center mt-6"
style={{ letterSpacing: -0.3 }}
style={{
fontSize: 20,
fontWeight: "bold",
color: theme.colors.textPrimary,
textAlign: "center",
marginTop: 24,
letterSpacing: -0.3,
}}
>
{title}
</Text>
{subtitle ? (
<Text className="text-[15px] text-text-secondary text-center mt-2 leading-[22px] max-w-[280px]">
<Text
style={{
fontSize: 15,
color: theme.colors.textSecondary,
textAlign: "center",
marginTop: 8,
lineHeight: 22,
maxWidth: 280,
}}
>
{subtitle}
</Text>
) : null}
{actionLabel && onAction ? (
<View className="mt-6 w-[180px]">
<View style={{ marginTop: 24, width: 180 }}>
<GradientButton label={actionLabel} onPress={onAction} />
</View>
) : null}

View File

@@ -61,169 +61,242 @@ export default function EpisodeCard(
);
return (
<Pressable
style={({ pressed }: { pressed: boolean }) => {
return {
marginBottom: 12,
opacity: pressed ? 0.7 : muted ? 0.5 : 1,
};
}}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={`${type === "incident" ? "Incident" : "Alert"} episode ${episode.episodeNumberWithPrefix || episode.episodeNumber}, ${episode.title}. State: ${state?.name ?? "unknown"}. Severity: ${severity?.name ?? "unknown"}.`}
>
<View
className="rounded-3xl overflow-hidden"
style={{
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : "#111827",
shadowOpacity: theme.isDark ? 0.22 : 0.08,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 14,
elevation: 5,
<View style={{ marginBottom: 12 }}>
<Pressable
style={({ pressed }: { pressed: boolean }) => {
return {
opacity: pressed ? 0.7 : muted ? 0.5 : 1,
};
}}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={`${type === "incident" ? "Incident" : "Alert"} episode ${episode.episodeNumberWithPrefix || episode.episodeNumber}, ${episode.title}. State: ${state?.name ?? "unknown"}. Severity: ${severity?.name ?? "unknown"}.`}
>
<View
style={{
height: 3,
backgroundColor: theme.colors.borderSubtle,
opacity: 1,
borderRadius: 24,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: "#000",
shadowOpacity: 0.22,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 14,
elevation: 5,
}}
/>
<View className="p-4">
<View className="flex-row justify-between items-center mb-2.5">
<View className="flex-row items-center gap-2">
{projectName ? <ProjectBadge name={projectName} /> : null}
>
<View
style={{
height: 3,
backgroundColor: theme.colors.borderSubtle,
opacity: 1,
}}
/>
<View style={{ padding: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<View
className="flex-row items-center px-2 py-1 rounded-full"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
{projectName ? <ProjectBadge name={projectName} /> : null}
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.iconBackground,
}}
>
<Ionicons
name={
type === "incident"
? "warning-outline"
: "notifications-outline"
}
size={10}
color={theme.colors.textSecondary}
style={{ marginRight: 4 }}
/>
<Text
style={{
fontSize: 10,
fontWeight: "600",
color: theme.colors.textSecondary,
letterSpacing: 0.3,
}}
>
{type === "incident" ? "INCIDENT EPISODE" : "ALERT EPISODE"}
</Text>
</View>
<View
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderDefault,
}}
>
<Text
style={{
fontSize: 12,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: 0.2,
}}
>
{episode.episodeNumberWithPrefix ||
`#${episode.episodeNumber}`}
</Text>
</View>
</View>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name={
type === "incident"
? "warning-outline"
: "notifications-outline"
}
size={10}
color={theme.colors.textSecondary}
name="time-outline"
size={12}
color={theme.colors.textTertiary}
style={{ marginRight: 4 }}
/>
<Text
className="text-[10px] font-semibold"
style={{
color: theme.colors.textSecondary,
letterSpacing: 0.3,
}}
style={{ fontSize: 12, color: theme.colors.textTertiary }}
>
{type === "incident" ? "INCIDENT EPISODE" : "ALERT EPISODE"}
</Text>
</View>
<View
className="px-2.5 py-1 rounded-full"
style={{
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderDefault,
}}
>
<Text
className="text-[12px] font-bold"
style={{
color: theme.colors.textPrimary,
letterSpacing: 0.2,
}}
>
{episode.episodeNumberWithPrefix ||
`#${episode.episodeNumber}`}
{timeString}
</Text>
</View>
</View>
<View className="flex-row items-center">
<Ionicons
name="time-outline"
size={12}
color={theme.colors.textTertiary}
style={{ marginRight: 4 }}
/>
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
>
{timeString}
</Text>
</View>
</View>
<View className="flex-row items-start mt-0.5">
<Text
className="text-[16px] font-semibold flex-1 pr-2"
style={{ color: theme.colors.textPrimary, letterSpacing: -0.2 }}
numberOfLines={2}
<View
style={{
flexDirection: "row",
alignItems: "flex-start",
marginTop: 2,
}}
>
{episode.title}
</Text>
<Ionicons
name="chevron-forward"
size={16}
color={theme.colors.textTertiary}
style={{ marginTop: 2 }}
/>
</View>
<View className="flex-row flex-wrap gap-2 mt-3">
{state ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
<Text
style={{
backgroundColor: theme.colors.backgroundTertiary,
fontSize: 16,
fontWeight: "600",
flex: 1,
paddingRight: 8,
color: theme.colors.textPrimary,
letterSpacing: -0.2,
}}
numberOfLines={2}
>
{episode.title}
</Text>
<Ionicons
name="chevron-forward"
size={16}
color={theme.colors.textTertiary}
style={{ marginTop: 2 }}
/>
</View>
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{state ? (
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
/>
<Text
className="text-[11px] font-semibold"
style={{ color: stateColor }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
{state.name}
</Text>
</View>
) : null}
<View
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
style={{
fontSize: 11,
fontWeight: "600",
color: stateColor,
}}
>
{state.name}
</Text>
</View>
) : null}
{severity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
>
<Text
className="text-[11px] font-semibold"
style={{ color: severityColor }}
{severity ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
{severity.name}
</Text>
</View>
) : null}
<Text
style={{
fontSize: 11,
fontWeight: "600",
color: severityColor,
}}
>
{severity.name}
</Text>
</View>
) : null}
{childCount > 0 ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
style={{ backgroundColor: theme.colors.iconBackground }}
>
<Text
className="text-[11px] font-semibold"
style={{ color: theme.colors.actionPrimary }}
{childCount > 0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.iconBackground,
}}
>
{childCount} {type === "incident" ? "incident" : "alert"}
{childCount !== 1 ? "s" : ""}
</Text>
</View>
) : null}
<Text
style={{
fontSize: 11,
fontWeight: "600",
color: theme.colors.actionPrimary,
}}
>
{childCount} {type === "incident" ? "incident" : "alert"}
{childCount !== 1 ? "s" : ""}
</Text>
</View>
) : null}
</View>
</View>
</View>
</View>
</Pressable>
</Pressable>
</View>
);
}

View File

@@ -28,24 +28,35 @@ export default function FeedTimeline({
const moreText: string | undefined = entry.moreInformationInMarkdown;
return (
<View key={entry._id} className="flex-row">
<View className="items-center mr-3.5">
<View key={entry._id} style={{ flexDirection: "row" }}>
<View style={{ alignItems: "center", marginRight: 14 }}>
<View
className="w-2.5 h-2.5 rounded-full mt-2"
style={{ backgroundColor: entryColor }}
style={{
width: 10,
height: 10,
borderRadius: 9999,
marginTop: 8,
backgroundColor: entryColor,
}}
/>
{!isLast ? (
<View
className="w-px flex-1 my-1.5"
style={{
width: 1,
flex: 1,
marginVertical: 6,
backgroundColor: theme.colors.borderDefault,
}}
/>
) : null}
</View>
<View
className="flex-1 pb-3 mb-2.5 rounded-2xl p-3"
style={{
flex: 1,
paddingBottom: 12,
marginBottom: 10,
borderRadius: 16,
padding: 12,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -53,13 +64,16 @@ export default function FeedTimeline({
>
<MarkdownContent content={entry.feedInfoInMarkdown} />
{moreText ? (
<View className="mt-1.5">
<View style={{ marginTop: 6 }}>
<MarkdownContent content={moreText} variant="secondary" />
</View>
) : null}
<Text
className="text-[12px] mt-2"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 12,
marginTop: 8,
color: theme.colors.textTertiary,
}}
>
{timeString}
</Text>

View File

@@ -17,17 +17,16 @@ export default function GlassCard({
return (
<View
className="rounded-2xl overflow-hidden"
style={[
{
backgroundColor: opaque
? theme.colors.backgroundElevated
: theme.colors.backgroundGlass,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
},
style,
]}
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: opaque
? theme.colors.backgroundElevated
: theme.colors.backgroundGlass,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
...style,
}}
>
{children}
</View>

View File

@@ -29,9 +29,7 @@ export default function GradientButton({
style,
}: GradientButtonProps): React.JSX.Element {
const { theme } = useTheme();
const primaryContentColor: string = theme.isDark
? theme.colors.backgroundPrimary
: "#FFFFFF";
const primaryContentColor: string = theme.colors.backgroundPrimary;
const isDisabled: boolean = disabled || loading;

View File

@@ -53,13 +53,14 @@ export default function IncidentCard({
accessibilityLabel={`Incident ${incident.incidentNumberWithPrefix || incident.incidentNumber}, ${incident.title}. State: ${incident.currentIncidentState?.name ?? "unknown"}. Severity: ${incident.incidentSeverity?.name ?? "unknown"}.`}
>
<View
className="rounded-3xl overflow-hidden"
style={{
borderRadius: 24,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : "#111827",
shadowOpacity: theme.isDark ? 0.22 : 0.08,
shadowColor: "#000",
shadowOpacity: 0.22,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 14,
elevation: 5,
@@ -72,13 +73,32 @@ export default function IncidentCard({
opacity: 1,
}}
/>
<View className="p-4">
<View className="flex-row justify-between items-center mb-2.5">
<View className="flex-row items-center gap-2">
<View style={{ padding: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 8,
}}
>
{projectName ? <ProjectBadge name={projectName} /> : null}
<View
className="flex-row items-center px-2 py-1 rounded-full"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.iconBackground,
}}
>
<Ionicons
name="warning-outline"
@@ -87,8 +107,9 @@ export default function IncidentCard({
style={{ marginRight: 4 }}
/>
<Text
className="text-[10px] font-semibold"
style={{
fontSize: 10,
fontWeight: "600",
color: theme.colors.textSecondary,
letterSpacing: 0.3,
}}
@@ -97,16 +118,19 @@ export default function IncidentCard({
</Text>
</View>
<View
className="px-2.5 py-1 rounded-full"
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderDefault,
}}
>
<Text
className="text-[12px] font-bold"
style={{
fontSize: 12,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: 0.2,
}}
@@ -116,26 +140,35 @@ export default function IncidentCard({
</Text>
</View>
</View>
<View className="flex-row items-center">
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name="time-outline"
size={12}
color={theme.colors.textTertiary}
style={{ marginRight: 4 }}
/>
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
>
<Text style={{ fontSize: 12, color: theme.colors.textTertiary }}>
{timeString}
</Text>
</View>
</View>
<View className="flex-row items-start mt-0.5">
<View
style={{
flexDirection: "row",
alignItems: "flex-start",
marginTop: 2,
}}
>
<Text
className="text-[16px] font-semibold flex-1 pr-2"
style={{ color: theme.colors.textPrimary, letterSpacing: -0.2 }}
style={{
fontSize: 16,
fontWeight: "600",
flex: 1,
paddingRight: 8,
color: theme.colors.textPrimary,
letterSpacing: -0.2,
}}
numberOfLines={2}
>
{incident.title}
@@ -148,21 +181,40 @@ export default function IncidentCard({
/>
</View>
<View className="flex-row flex-wrap gap-2 mt-3">
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{incident.currentIncidentState ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
className="text-[11px] font-semibold"
style={{ color: stateColor }}
style={{
fontSize: 11,
fontWeight: "600",
color: stateColor,
}}
>
{incident.currentIncidentState.name}
</Text>
@@ -171,12 +223,21 @@ export default function IncidentCard({
{incident.incidentSeverity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-full"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
<Text
className="text-[11px] font-semibold"
style={{ color: severityColor }}
style={{
fontSize: 11,
fontWeight: "600",
color: severityColor,
}}
>
{incident.incidentSeverity.name}
</Text>
@@ -186,33 +247,45 @@ export default function IncidentCard({
{monitorCount > 0 ? (
<View
className="flex-row items-center mt-3 pt-3"
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: theme.colors.borderSubtle,
}}
>
<View
className="w-6 h-6 rounded-full items-center justify-center mr-2"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
width: 24,
height: 24,
borderRadius: 9999,
alignItems: "center",
justifyContent: "center",
marginRight: 8,
backgroundColor: theme.colors.iconBackground,
}}
>
<Ionicons
name="desktop-outline"
name="pulse-outline"
size={12}
color={theme.colors.textSecondary}
/>
</View>
<View className="flex-1">
<View style={{ flex: 1 }}>
<Text
className="text-[12px]"
style={{ color: theme.colors.textSecondary }}
style={{ fontSize: 12, color: theme.colors.textSecondary }}
numberOfLines={1}
>
{monitorNames}
</Text>
<Text
className="text-[11px] mt-0.5"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 11,
marginTop: 2,
color: theme.colors.textTertiary,
}}
>
{monitorCount} monitor{monitorCount !== 1 ? "s" : ""}
</Text>

View File

@@ -16,14 +16,19 @@ export default function NotesSection({
setNoteModalVisible,
}: NotesSectionProps): React.JSX.Element {
const { theme } = useTheme();
const addNoteContentColor: string = theme.isDark
? theme.colors.backgroundPrimary
: "#FFFFFF";
const addNoteContentColor: string = "#FFFFFF";
return (
<View className="mb-2 mt-1">
<View className="flex-row justify-between items-center mb-3.5">
<View className="flex-row items-center">
<View style={{ marginBottom: 8, marginTop: 4 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 14,
}}
>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<Ionicons
name="chatbubble-outline"
size={14}
@@ -31,8 +36,13 @@ export default function NotesSection({
style={{ marginRight: 6 }}
/>
<Text
className="text-[12px] font-semibold uppercase"
style={{ color: theme.colors.textSecondary, letterSpacing: 1 }}
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
color: theme.colors.textSecondary,
letterSpacing: 1,
}}
>
Internal Notes
</Text>
@@ -45,7 +55,7 @@ export default function NotesSection({
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 6,
backgroundColor: theme.colors.actionPrimary,
backgroundColor: theme.colors.accentGradientStart,
opacity: pressed ? 0.85 : 1,
};
}}
@@ -60,8 +70,11 @@ export default function NotesSection({
style={{ marginRight: 4 }}
/>
<Text
className="text-[12px] font-semibold"
style={{ color: addNoteContentColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: addNoteContentColor,
}}
>
Add Note
</Text>
@@ -76,39 +89,52 @@ export default function NotesSection({
return (
<View
key={note._id || `${note.createdAt}-${index}`}
className="rounded-2xl overflow-hidden mb-2.5"
style={{
borderRadius: 16,
overflow: "hidden",
marginBottom: 10,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark
? "#000"
: theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.16 : 0.06,
shadowColor: "#000",
shadowOpacity: 0.16,
shadowOffset: { width: 0, height: 5 },
shadowRadius: 10,
elevation: 3,
}}
>
<View className="p-4">
<View style={{ padding: 16 }}>
<Text
className="text-[14px] leading-[22px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 14,
lineHeight: 22,
color: theme.colors.textPrimary,
}}
>
{noteText}
</Text>
<View className="flex-row justify-between mt-2.5">
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
marginTop: 10,
}}
>
{note.createdByUser ? (
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 12,
color: theme.colors.textTertiary,
}}
>
{authorName}
</Text>
) : null}
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 12,
color: theme.colors.textTertiary,
}}
>
{formatDateTime(note.createdAt)}
</Text>
@@ -121,16 +147,20 @@ export default function NotesSection({
{notes && notes.length === 0 ? (
<View
className="rounded-2xl p-4 items-center"
style={{
borderRadius: 16,
padding: 16,
alignItems: "center",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[13px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
color: theme.colors.textTertiary,
}}
>
No notes yet.
</Text>

View File

@@ -26,8 +26,15 @@ export default function OfflineBanner(): React.JSX.Element | null {
return (
<Animated.View
className="absolute top-0 left-0 right-0 z-[100] pt-[50px] pb-2.5 px-4"
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
zIndex: 100,
paddingTop: 50,
paddingBottom: 10,
paddingHorizontal: 16,
backgroundColor: theme.colors.statusError,
transform: [{ translateY: slideAnim }],
shadowColor: theme.colors.statusError,
@@ -37,14 +44,27 @@ export default function OfflineBanner(): React.JSX.Element | null {
elevation: 8,
}}
>
<View className="flex-row items-center justify-center">
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
}}
>
<Ionicons
name="cloud-offline-outline"
size={16}
color="#FFFFFF"
style={{ marginRight: 8, opacity: 0.9 }}
/>
<Text className="text-[13px] font-semibold tracking-tight text-white">
<Text
style={{
fontSize: 13,
fontWeight: "600",
letterSpacing: -0.5,
color: "#FFFFFF",
}}
>
No internet connection
</Text>
</View>

View File

@@ -13,14 +13,22 @@ export default function ProjectBadge({
}: ProjectBadgeProps): React.JSX.Element {
const { theme } = useTheme();
return (
<View className="flex-row items-center">
<View style={{ flexDirection: "row", alignItems: "center" }}>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: color || theme.colors.actionPrimary }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: color || theme.colors.actionPrimary,
}}
/>
<Text
className="text-[12px] font-medium"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 12,
fontWeight: "500",
color: theme.colors.textSecondary,
}}
numberOfLines={1}
>
{name}

View File

@@ -14,20 +14,24 @@ export default function RootCauseCard({
return (
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="p-4">
<View style={{ padding: 16 }}>
{rootCauseText ? (
<MarkdownContent content={rootCauseText} />
) : (
<Text
className="text-[14px] leading-[22px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 14,
lineHeight: 22,
color: theme.colors.textTertiary,
}}
>
No root cause documented yet.
</Text>

View File

@@ -14,10 +14,21 @@ export default function SectionHeader({
}: SectionHeaderProps): React.JSX.Element {
const { theme } = useTheme();
return (
<View className="flex-row items-center mb-3.5">
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 14,
}}
>
<View
className="w-6 h-6 rounded-lg items-center justify-center mr-2"
style={{
width: 24,
height: 24,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
marginRight: 8,
backgroundColor: theme.colors.iconBackground,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -30,8 +41,10 @@ export default function SectionHeader({
/>
</View>
<Text
className="text-[12px] font-semibold uppercase"
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
color: theme.colors.textSecondary,
letterSpacing: 1,
}}

View File

@@ -1,5 +1,5 @@
import React from "react";
import { View, Text, Pressable } from "react-native";
import { View, Text, TouchableOpacity } from "react-native";
import { useTheme } from "../theme";
interface Segment<T extends string> {
@@ -19,52 +19,46 @@ export default function SegmentedControl<T extends string>({
onSelect,
}: SegmentedControlProps<T>): React.JSX.Element {
const { theme } = useTheme();
const activeContentColor: string = theme.isDark
? theme.colors.backgroundPrimary
: "#FFFFFF";
const activeContentColor: string = theme.colors.backgroundPrimary;
return (
<View
className="flex-row mx-4 mt-3 mb-2 rounded-2xl p-1.5"
style={{
flexDirection: "row",
marginHorizontal: 16,
marginTop: 12,
marginBottom: 8,
borderRadius: 16,
padding: 6,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
{segments.map((segment: Segment<T>) => {
{segments.map((segment: Segment<T>, index: number) => {
const isActive: boolean = segment.key === selected;
return (
<Pressable
<TouchableOpacity
key={segment.key}
style={({ pressed }: { pressed: boolean }) => {
return [
{
flex: 1,
alignItems: "center" as const,
paddingVertical: 10,
borderRadius: 12,
opacity: pressed ? 0.7 : 1,
},
isActive
? {
backgroundColor: theme.colors.actionPrimary,
shadowColor: theme.colors.actionPrimary,
shadowOpacity: theme.isDark ? 0.28 : 0.18,
shadowOffset: { width: 0, height: 5 },
shadowRadius: 10,
elevation: 4,
}
: undefined,
];
}}
activeOpacity={0.7}
onPress={() => {
return onSelect(segment.key);
}}
style={{
flex: 1,
alignItems: "center",
paddingVertical: 10,
borderRadius: 12,
marginLeft: index > 0 ? 4 : 0,
backgroundColor: isActive
? theme.colors.actionPrimary
: "transparent",
}}
>
<Text
className="text-body-sm font-semibold"
style={{
fontSize: 14,
fontWeight: "600",
color: isActive
? activeContentColor
: theme.colors.textSecondary,
@@ -73,7 +67,7 @@ export default function SegmentedControl<T extends string>({
>
{segment.label}
</Text>
</Pressable>
</TouchableOpacity>
);
})}
</View>

View File

@@ -43,12 +43,21 @@ export default function SeverityBadge({
return (
<View
className="px-2 py-1 rounded-md self-start"
style={{ backgroundColor: colors.bg }}
style={{
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 6,
alignSelf: "flex-start",
backgroundColor: colors.bg,
}}
>
<Text
className="text-xs font-semibold tracking-wide"
style={{ color: colors.text }}
style={{
fontSize: 12,
fontWeight: "600",
letterSpacing: 0.5,
color: colors.text,
}}
>
{displayLabel.toUpperCase()}
</Text>

View File

@@ -59,8 +59,10 @@ export default function SkeletonCard({
if (variant === "compact") {
return (
<Animated.View
className="rounded-2xl mb-3 overflow-hidden"
style={{
borderRadius: 16,
marginBottom: 12,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -75,20 +77,40 @@ export default function SkeletonCard({
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View className="p-4">
<View className="flex-row justify-between items-center mb-2.5">
<View style={{ padding: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<View
className="h-4 w-14 rounded"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 16,
width: 56,
borderRadius: 4,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View
className="h-3 w-8 rounded"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 12,
width: 32,
borderRadius: 4,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
</View>
<View
className="h-[18px] rounded w-3/4 mb-3"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 18,
borderRadius: 4,
width: "75%",
marginBottom: 12,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
</View>
</Animated.View>
@@ -98,14 +120,15 @@ export default function SkeletonCard({
if (variant === "detail") {
return (
<Animated.View
className="p-5"
style={{ opacity }}
style={{ padding: 20, opacity }}
accessibilityLabel="Loading content"
accessibilityRole="progressbar"
>
<View
className="rounded-2xl overflow-hidden mb-5"
style={{
borderRadius: 16,
overflow: "hidden",
marginBottom: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -117,30 +140,49 @@ export default function SkeletonCard({
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View className="p-5">
<View style={{ padding: 20 }}>
<View
className="h-4 w-16 rounded mb-3"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 16,
width: 64,
borderRadius: 4,
marginBottom: 12,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View
className="h-7 w-4/5 rounded mb-3"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 28,
width: "80%",
borderRadius: 4,
marginBottom: 12,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View className="flex-row gap-2">
<View style={{ flexDirection: "row", gap: 8 }}>
<View
className="h-6 w-20 rounded-md"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 24,
width: 80,
borderRadius: 6,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View
className="h-6 w-14 rounded-md"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 24,
width: 56,
borderRadius: 6,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
</View>
</View>
</View>
<View
className="rounded-xl overflow-hidden"
style={{
borderRadius: 12,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -148,17 +190,29 @@ export default function SkeletonCard({
borderLeftColor: theme.colors.backgroundTertiary,
}}
>
<View className="p-4">
<View style={{ padding: 16 }}>
{Array.from({ length: 3 }).map((_: unknown, index: number) => {
return (
<View key={index} className="flex-row mb-3">
<View
key={index}
style={{ flexDirection: "row", marginBottom: 12 }}
>
<View
className="h-3.5 w-20 rounded mr-4"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 14,
width: 80,
borderRadius: 4,
marginRight: 16,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View
className="h-3.5 w-[120px] rounded"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 14,
width: 120,
borderRadius: 4,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
</View>
);
@@ -171,8 +225,10 @@ export default function SkeletonCard({
return (
<Animated.View
className="rounded-2xl mb-3 overflow-hidden"
style={{
borderRadius: 16,
marginBottom: 12,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -187,29 +243,63 @@ export default function SkeletonCard({
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View className="p-4">
<View className="flex-row justify-between items-center mb-3">
<View style={{ padding: 16 }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 12,
}}
>
<View
className="h-3.5 w-14 rounded"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 14,
width: 56,
borderRadius: 4,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View
className="h-3 w-10 rounded"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 12,
width: 40,
borderRadius: 4,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
</View>
<View
className="h-[18px] rounded w-[70%] mb-3"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 18,
borderRadius: 4,
width: "70%",
marginBottom: 12,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View className="flex-row gap-2 mb-3">
<View
style={{
flexDirection: "row",
gap: 8,
marginBottom: 12,
}}
>
<View
className="h-6 w-20 rounded-md"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 24,
width: 80,
borderRadius: 6,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
<View
className="h-6 w-14 rounded-md"
style={{ backgroundColor: theme.colors.backgroundTertiary }}
style={{
height: 24,
width: 56,
borderRadius: 6,
backgroundColor: theme.colors.backgroundTertiary,
}}
/>
</View>
{Array.from({ length: Math.max(lines - 1, 1) }).map(
@@ -217,8 +307,10 @@ export default function SkeletonCard({
return (
<View
key={index}
className="h-3 rounded mb-2"
style={{
height: 12,
borderRadius: 4,
marginBottom: 8,
width: lineWidths[index % lineWidths.length],
backgroundColor: theme.colors.backgroundTertiary,
}}

View File

@@ -32,12 +32,33 @@ export default function StateBadge({
const displayLabel: string = label || state;
return (
<View className="flex-row items-center px-2 py-1 rounded-md self-start bg-bg-tertiary">
<View
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 6,
alignSelf: "flex-start",
backgroundColor: theme.colors.backgroundTertiary,
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: color }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: color,
}}
/>
<Text className="text-xs font-semibold text-text-primary">
<Text
style={{
fontSize: 12,
fontWeight: "600",
color: theme.colors.textPrimary,
}}
>
{displayLabel.charAt(0).toUpperCase() + displayLabel.slice(1)}
</Text>
</View>

View File

@@ -94,25 +94,63 @@ export default function SwipeableCard({
).current;
return (
<View className="overflow-hidden rounded-xl">
<View style={{ overflow: "hidden", borderRadius: 12, marginBottom: 12 }}>
{/* Background actions */}
<View className="absolute inset-0 flex-row justify-between items-center">
<View
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
{leftAction ? (
<View
className="flex-1 h-full justify-center pl-5 rounded-xl"
style={{ backgroundColor: leftAction.color }}
style={{
flex: 1,
height: "100%",
justifyContent: "center",
paddingLeft: 20,
borderRadius: 12,
backgroundColor: leftAction.color,
}}
>
<Text className="text-white text-sm font-bold tracking-tight">
<Text
style={{
color: "#FFFFFF",
fontSize: 14,
fontWeight: "bold",
letterSpacing: -0.5,
}}
>
{leftAction.label}
</Text>
</View>
) : null}
{rightAction ? (
<View
className="flex-1 h-full justify-center items-end pr-5 rounded-xl"
style={{ backgroundColor: rightAction.color }}
style={{
flex: 1,
height: "100%",
justifyContent: "center",
alignItems: "flex-end",
paddingRight: 20,
borderRadius: 12,
backgroundColor: rightAction.color,
}}
>
<Text className="text-white text-sm font-bold tracking-tight">
<Text
style={{
color: "#FFFFFF",
fontSize: 14,
fontWeight: "bold",
letterSpacing: -0.5,
}}
>
{rightAction.label}
</Text>
</View>
@@ -121,8 +159,8 @@ export default function SwipeableCard({
{/* Foreground content */}
<Animated.View
className="z-[1]"
style={{
zIndex: 1,
transform: [{ translateX }],
backgroundColor: theme.colors.backgroundPrimary,
}}

View File

@@ -15,6 +15,10 @@ import { registerPushDevice } from "../api/pushDevice";
import { useAuth } from "./useAuth";
import { useProject } from "./useProject";
import { PUSH_TOKEN_KEY } from "./pushTokenUtils";
import logger from "../utils/logger";
const RETRY_DELAY_MS: number = 5000;
const MAX_RETRIES: number = 3;
export function usePushNotifications(navigationRef: unknown): void {
const { isAuthenticated }: { isAuthenticated: boolean } = useAuth();
@@ -46,8 +50,31 @@ export function usePushNotifications(navigationRef: unknown): void {
let cancelled: boolean = false;
const register: () => Promise<void> = async (): Promise<void> => {
const token: string | null = await requestPermissionsAndGetToken();
let token: string | null = null;
let attempt: number = 0;
// Retry obtaining the push token
while (!token && attempt < MAX_RETRIES && !cancelled) {
token = await requestPermissionsAndGetToken();
if (!token && !cancelled) {
attempt++;
if (attempt < MAX_RETRIES) {
logger.warn(
`[PushNotifications] Push token not available, retrying in ${RETRY_DELAY_MS}ms (attempt ${attempt}/${MAX_RETRIES})`,
);
await new Promise<void>((resolve: () => void): void => {
setTimeout(resolve, RETRY_DELAY_MS);
});
}
}
}
if (!token || cancelled) {
if (!token) {
logger.warn(
"[PushNotifications] Could not obtain push token after all retries — device will not be registered",
);
}
return;
}
@@ -63,13 +90,21 @@ export function usePushNotifications(navigationRef: unknown): void {
deviceToken: token,
projectId: project._id,
});
} catch {
// Continue registering with other projects
} catch (error: unknown) {
logger.warn(
`[PushNotifications] Failed to register device for project ${project._id}:`,
error,
);
}
}
};
register();
register().catch((error: unknown): void => {
logger.error(
"[PushNotifications] Unexpected error during push registration:",
error,
);
});
return (): void => {
cancelled = true;

View File

@@ -27,12 +27,15 @@ function TabIcon({
accentColor: string;
}): React.JSX.Element {
return (
<View className="items-center justify-center">
<View style={{ alignItems: "center", justifyContent: "center" }}>
<Ionicons name={focused ? focusedName : name} size={22} color={color} />
{focused ? (
<View
className="w-1 h-1 rounded-full mt-0.5"
style={{
width: 4,
height: 4,
borderRadius: 9999,
marginTop: 2,
backgroundColor: accentColor,
}}
/>
@@ -74,10 +77,8 @@ export default function MainTabNavigator(): React.JSX.Element {
height: Platform.OS === "ios" ? 78 : 68,
paddingBottom: Platform.OS === "ios" ? 18 : 10,
paddingTop: 10,
shadowColor: theme.isDark
? "#000000"
: theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.35 : 0.12,
shadowColor: "#000000",
shadowOpacity: 0.35,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 18,
elevation: 16,

View File

@@ -74,7 +74,7 @@ export default function RootNavigator(): React.JSX.Element {
const navigationTheme: Theme = {
...DefaultTheme,
dark: theme.isDark,
dark: true,
colors: {
...DefaultTheme.colors,
primary: theme.colors.actionPrimary,

View File

@@ -4,6 +4,7 @@ import * as Device from "expo-device";
import Constants from "expo-constants";
import { Platform } from "react-native";
import { PermissionStatus } from "expo-modules-core";
import logger from "../utils/logger";
// Show notifications when app is in foreground
Notifications.setNotificationHandler({
@@ -80,6 +81,9 @@ export async function setupNotificationCategories(): Promise<void> {
export async function requestPermissionsAndGetToken(): Promise<string | null> {
if (!Device.isDevice) {
logger.warn(
"[PushNotifications] Not a physical device — skipping push token registration",
);
return null;
}
@@ -92,6 +96,10 @@ export async function requestPermissionsAndGetToken(): Promise<string | null> {
}
if (finalStatus !== "granted") {
logger.warn(
"[PushNotifications] Push notification permission not granted:",
finalStatus,
);
return null;
}
@@ -100,12 +108,20 @@ export async function requestPermissionsAndGetToken(): Promise<string | null> {
Constants.easConfig?.projectId;
if (!projectId) {
logger.warn(
"[PushNotifications] EAS project ID not found — cannot register for push notifications",
);
return null;
}
const tokenData: ExpoPushToken = await Notifications.getExpoPushTokenAsync({
projectId,
});
try {
const tokenData: ExpoPushToken = await Notifications.getExpoPushTokenAsync({
projectId,
});
return tokenData.data;
return tokenData.data;
} catch (error: unknown) {
logger.error("[PushNotifications] Failed to get push token:", error);
return null;
}
}

View File

@@ -140,8 +140,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
if (isLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SkeletonCard variant="detail" />
</View>
@@ -151,13 +150,14 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
if (!alert) {
return (
<View
className="flex-1 items-center justify-center"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<Text
className="text-[15px]"
style={{ color: theme.colors.textSecondary }}
>
<Text style={{ fontSize: 15, color: theme.colors.textSecondary }}>
Alert not found.
</Text>
</View>
@@ -203,13 +203,15 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
>
{/* Header card */}
<View
className="rounded-3xl overflow-hidden mb-5"
style={{
borderRadius: 24,
overflow: "hidden",
marginBottom: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : stateColor,
shadowOpacity: theme.isDark ? 0.28 : 0.12,
shadowColor: "#000",
shadowOpacity: 0.28,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
@@ -233,17 +235,22 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
backgroundColor: stateColor,
}}
/>
<View className="p-5">
<View style={{ padding: 20 }}>
<Text
className="text-[13px] font-semibold mb-2"
style={{ color: stateColor }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
color: stateColor,
}}
>
{alert.alertNumberWithPrefix || `#${alert.alertNumber}`}
</Text>
<Text
className="text-[24px] font-bold"
style={{
fontSize: 24,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.6,
}}
@@ -251,19 +258,40 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{alert.title}
</Text>
<View className="flex-row flex-wrap gap-2 mt-3">
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{alert.currentAlertState ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: stateColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: stateColor + "14",
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
className="text-[12px] font-semibold"
style={{ color: stateColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: stateColor,
}}
>
{alert.currentAlertState.name}
</Text>
@@ -272,12 +300,21 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{alert.alertSeverity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: severityColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: severityColor + "14",
}}
>
<Text
className="text-[12px] font-semibold"
style={{ color: severityColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: severityColor,
}}
>
{alert.alertSeverity.name}
</Text>
@@ -289,19 +326,23 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{/* Description */}
{descriptionText ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Description" iconName="document-text-outline" />
<View
className="rounded-2xl p-4"
style={{
borderRadius: 16,
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[14px] leading-[22px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 14,
lineHeight: 22,
color: theme.colors.textPrimary,
}}
>
{descriptionText}
</Text>
@@ -309,49 +350,60 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
</View>
) : null}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Root Cause" iconName="bulb-outline" />
<RootCauseCard rootCauseText={rootCauseText} />
</View>
{/* Details */}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Details" iconName="information-circle-outline" />
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="p-4">
<View className="flex-row mb-3">
<View style={{ padding: 16 }}>
<View style={{ flexDirection: "row", marginBottom: 12 }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Created
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{formatDateTime(alert.createdAt)}
</Text>
</View>
{alert.monitor ? (
<View className="flex-row">
<View style={{ flexDirection: "row" }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Monitor
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{alert.monitor.name}
</Text>
@@ -363,103 +415,118 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{/* State Change Actions */}
{!isResolved ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Actions" iconName="flash-outline" />
<View
className="rounded-2xl p-3"
style={{
borderRadius: 16,
padding: 12,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="flex-row gap-3">
<View style={{ flexDirection: "row" }}>
{!isAcknowledged && !isResolved && acknowledgeState ? (
<Pressable
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Acknowledge alert"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Acknowledge
</Text>
</>
)}
</Pressable>
<View style={{ flex: 1 }}>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Acknowledge alert"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Acknowledge
</Text>
</>
)}
</Pressable>
</View>
) : null}
{resolveState ? (
<Pressable
<View
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateResolved,
marginLeft:
!isAcknowledged && !isResolved && acknowledgeState
? 12
: 0,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Resolve alert"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Resolve
</Text>
</>
)}
</Pressable>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateResolved,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Resolve alert"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Resolve
</Text>
</>
)}
</Pressable>
</View>
) : null}
</View>
</View>
@@ -468,7 +535,7 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element {
{/* Activity Feed */}
{feed && feed.length > 0 ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Activity Feed" iconName="list-outline" />
<FeedTimeline feed={feed} />
</View>

View File

@@ -148,8 +148,7 @@ export default function AlertEpisodeDetailScreen({
if (isLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SkeletonCard variant="detail" />
</View>
@@ -159,13 +158,14 @@ export default function AlertEpisodeDetailScreen({
if (!episode) {
return (
<View
className="flex-1 items-center justify-center"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<Text
className="text-[15px]"
style={{ color: theme.colors.textSecondary }}
>
<Text style={{ fontSize: 15, color: theme.colors.textSecondary }}>
Episode not found.
</Text>
</View>
@@ -210,13 +210,15 @@ export default function AlertEpisodeDetailScreen({
}
>
<View
className="rounded-3xl overflow-hidden mb-5"
style={{
borderRadius: 24,
overflow: "hidden",
marginBottom: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : stateColor,
shadowOpacity: theme.isDark ? 0.28 : 0.12,
shadowColor: "#000",
shadowOpacity: 0.28,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
@@ -235,32 +237,61 @@ export default function AlertEpisodeDetailScreen({
}}
/>
<View style={{ height: 3, backgroundColor: stateColor }} />
<View className="p-5">
<View style={{ padding: 20 }}>
<Text
className="text-[13px] font-semibold mb-2"
style={{ color: stateColor }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
color: stateColor,
}}
>
{episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`}
</Text>
<Text
className="text-[24px] font-bold"
style={{ color: theme.colors.textPrimary, letterSpacing: -0.6 }}
style={{
fontSize: 24,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.6,
}}
>
{episode.title}
</Text>
<View className="flex-row flex-wrap gap-2 mt-3">
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{episode.currentAlertState ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: stateColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: stateColor + "14",
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
className="text-[12px] font-semibold"
style={{ color: stateColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: stateColor,
}}
>
{episode.currentAlertState.name}
</Text>
@@ -268,12 +299,21 @@ export default function AlertEpisodeDetailScreen({
) : null}
{episode.alertSeverity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: severityColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: severityColor + "14",
}}
>
<Text
className="text-[12px] font-semibold"
style={{ color: severityColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: severityColor,
}}
>
{episode.alertSeverity.name}
</Text>
@@ -284,19 +324,23 @@ export default function AlertEpisodeDetailScreen({
</View>
{descriptionText ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Description" iconName="document-text-outline" />
<View
className="rounded-2xl p-4"
style={{
borderRadius: 16,
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[14px] leading-[22px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 14,
lineHeight: 22,
color: theme.colors.textPrimary,
}}
>
{descriptionText}
</Text>
@@ -304,19 +348,21 @@ export default function AlertEpisodeDetailScreen({
</View>
) : null}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Root Cause" iconName="git-branch-outline" />
<View
className="rounded-2xl p-4"
style={{
borderRadius: 16,
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[14px] leading-[22px]"
style={{
fontSize: 14,
lineHeight: 22,
color: rootCauseText
? theme.colors.textPrimary
: theme.colors.textTertiary,
@@ -327,41 +373,52 @@ export default function AlertEpisodeDetailScreen({
</View>
</View>
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Details" iconName="information-circle-outline" />
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="p-4">
<View className="flex-row mb-3">
<View style={{ padding: 16 }}>
<View style={{ flexDirection: "row", marginBottom: 12 }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Created
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{formatDateTime(episode.createdAt)}
</Text>
</View>
<View className="flex-row">
<View style={{ flexDirection: "row" }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Alerts
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{episode.alertCount ?? 0}
</Text>
@@ -371,98 +428,113 @@ export default function AlertEpisodeDetailScreen({
</View>
{!isResolved ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Actions" iconName="flash-outline" />
<View
className="rounded-2xl p-3"
style={{
borderRadius: 16,
padding: 12,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="flex-row gap-3">
<View style={{ flexDirection: "row" }}>
{!isAcknowledged && !isResolved && acknowledgeState ? (
<Pressable
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Acknowledge
</Text>
</>
)}
</Pressable>
<View style={{ flex: 1 }}>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Acknowledge
</Text>
</>
)}
</Pressable>
</View>
) : null}
{resolveState ? (
<Pressable
<View
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateResolved,
marginLeft:
!isAcknowledged && !isResolved && acknowledgeState
? 12
: 0,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Resolve
</Text>
</>
)}
</Pressable>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateResolved,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Resolve
</Text>
</>
)}
</Pressable>
</View>
) : null}
</View>
</View>
@@ -470,7 +542,7 @@ export default function AlertEpisodeDetailScreen({
) : null}
{feed && feed.length > 0 ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Activity Feed" iconName="list-outline" />
<FeedTimeline feed={feed} />
</View>

View File

@@ -60,8 +60,13 @@ function SectionHeader({
const { theme } = useTheme();
return (
<View
className="flex-row items-center pb-2 pt-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flexDirection: "row",
alignItems: "center",
paddingBottom: 8,
paddingTop: 4,
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<Ionicons
name={isActive ? "flame" : "checkmark-done"}
@@ -72,8 +77,10 @@ function SectionHeader({
style={{ marginRight: 6 }}
/>
<Text
className="text-[12px] font-semibold uppercase"
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
color: isActive
? theme.colors.textPrimary
: theme.colors.textTertiary,
@@ -83,16 +90,20 @@ function SectionHeader({
{title}
</Text>
<View
className="ml-2 px-1.5 py-0.5 rounded"
style={{
marginLeft: 8,
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
backgroundColor: isActive
? theme.colors.severityCritical + "18"
: theme.colors.backgroundTertiary,
}}
>
<Text
className="text-[11px] font-bold"
style={{
fontSize: 11,
fontWeight: "bold",
color: isActive
? theme.colors.severityCritical
: theme.colors.textTertiary,
@@ -294,8 +305,7 @@ export default function AlertsScreen(): React.JSX.Element {
if (showLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SegmentedControl
segments={[
@@ -305,7 +315,7 @@ export default function AlertsScreen(): React.JSX.Element {
selected={segment}
onSelect={setSegment}
/>
<View className="p-4">
<View style={{ padding: 16 }}>
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
@@ -325,8 +335,7 @@ export default function AlertsScreen(): React.JSX.Element {
};
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SegmentedControl
segments={[
@@ -352,10 +361,7 @@ export default function AlertsScreen(): React.JSX.Element {
}
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
>
<View style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}>
<SegmentedControl
segments={[
{ key: "alerts" as const, label: "Alerts" },

View File

@@ -34,12 +34,22 @@ export default function BiometricLockScreen({
return (
<View
className="flex-1 items-center justify-center px-10"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 40,
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<View
className="w-20 h-20 rounded-2xl items-center justify-center mb-6"
style={{
width: 80,
height: 80,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
marginBottom: 24,
backgroundColor: theme.colors.iconBackground,
}}
>
@@ -47,8 +57,10 @@ export default function BiometricLockScreen({
</View>
<Text
className="text-[20px] font-bold text-center"
style={{
fontSize: 20,
fontWeight: "bold",
textAlign: "center",
color: theme.colors.textPrimary,
letterSpacing: -0.3,
}}
@@ -57,13 +69,17 @@ export default function BiometricLockScreen({
</Text>
<Text
className="text-[15px] mt-2 text-center"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 15,
marginTop: 8,
textAlign: "center",
color: theme.colors.textSecondary,
}}
>
Use {biometricType.toLowerCase()} to unlock
</Text>
<View className="mt-10 w-full" style={{ maxWidth: 260 }}>
<View style={{ marginTop: 40, width: "100%", maxWidth: 260 }}>
<GradientButton
label="Unlock"
onPress={authenticate}

View File

@@ -6,6 +6,7 @@ import {
RefreshControl,
ActivityIndicator,
Pressable,
TouchableOpacity,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
@@ -48,18 +49,15 @@ function StatCard({
};
return (
<Pressable
style={({ pressed }: { pressed: boolean }) => {
return {
flex: 1,
borderRadius: 24,
overflow: "hidden" as const,
opacity: pressed ? 0.7 : 1,
};
}}
<TouchableOpacity
activeOpacity={0.7}
onPress={handlePress}
accessibilityLabel={`${count ?? 0} ${label}. Tap to view.`}
accessibilityRole="button"
style={{
borderRadius: 24,
overflow: "hidden",
}}
>
<LinearGradient
colors={[
@@ -78,23 +76,36 @@ function StatCard({
}}
/>
<View
className="p-4"
style={{
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
borderRadius: 22,
shadowColor: theme.isDark ? "#000" : theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.25 : 0.1,
shadowColor: "#000",
shadowOpacity: 0.25,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 16,
elevation: 6,
}}
>
<View className="flex-row items-center justify-between mb-3">
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 12,
}}
>
<View
className="w-10 h-10 rounded-2xl items-center justify-center"
style={{ backgroundColor: accentColor + "14" }}
style={{
width: 40,
height: 40,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
backgroundColor: accentColor + "14",
}}
>
<Ionicons name={iconName} size={18} color={accentColor} />
</View>
@@ -105,8 +116,9 @@ function StatCard({
/>
</View>
<Text
className="text-[30px] font-bold"
style={{
fontSize: 30,
fontWeight: "bold",
color: theme.colors.textPrimary,
fontVariant: ["tabular-nums"],
letterSpacing: -1.1,
@@ -115,8 +127,10 @@ function StatCard({
{isLoading ? "--" : count ?? 0}
</Text>
<Text
className="text-[12px] font-semibold mt-1"
style={{
fontSize: 12,
fontWeight: "600",
marginTop: 4,
color: theme.colors.textSecondary,
letterSpacing: 0.3,
}}
@@ -125,7 +139,7 @@ function StatCard({
{label}
</Text>
</View>
</Pressable>
</TouchableOpacity>
);
}
@@ -181,10 +195,22 @@ export default function HomeScreen(): React.JSX.Element {
/>
}
>
<View className="flex-1 items-center justify-center px-8">
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: 32,
}}
>
<View
className="w-20 h-20 rounded-2xl items-center justify-center mb-6"
style={{
width: 80,
height: 80,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
marginBottom: 24,
backgroundColor: "#000000",
borderWidth: 1,
borderColor: "#1F1F1F",
@@ -194,8 +220,10 @@ export default function HomeScreen(): React.JSX.Element {
</View>
<Text
className="text-[22px] font-bold text-center"
style={{
fontSize: 22,
fontWeight: "bold",
textAlign: "center",
color: theme.colors.textPrimary,
letterSpacing: -0.5,
}}
@@ -203,14 +231,20 @@ export default function HomeScreen(): React.JSX.Element {
No Projects Found
</Text>
<Text
className="text-[15px] text-center mt-2 leading-[22px] max-w-[300px]"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 15,
textAlign: "center",
marginTop: 8,
lineHeight: 22,
maxWidth: 300,
color: theme.colors.textSecondary,
}}
>
You don&apos;t have access to any projects. Contact your
administrator or pull to refresh.
</Text>
<View className="mt-8 w-[200px]">
<View style={{ marginTop: 32, width: 200 }}>
<GradientButton
label="Retry"
onPress={refreshProjects}
@@ -225,8 +259,12 @@ export default function HomeScreen(): React.JSX.Element {
if (isLoadingProjects) {
return (
<View
className="flex-1 items-center justify-center"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<ActivityIndicator size="large" color={theme.colors.actionPrimary} />
</View>
@@ -250,15 +288,19 @@ export default function HomeScreen(): React.JSX.Element {
/>
}
>
<View className="px-5 pt-4 pb-4">
<View
style={{ paddingHorizontal: 20, paddingTop: 16, paddingBottom: 16 }}
>
<View
className="rounded-3xl overflow-hidden p-5"
style={{
borderRadius: 24,
overflow: "hidden",
padding: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.3 : 0.12,
shadowColor: "#000",
shadowOpacity: 0.3,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
@@ -280,10 +322,15 @@ export default function HomeScreen(): React.JSX.Element {
}}
/>
<View className="flex-row items-center">
<View style={{ flexDirection: "row", alignItems: "center" }}>
<View
className="w-12 h-12 rounded-2xl items-center justify-center mr-3"
style={{
width: 48,
height: 48,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
backgroundColor: "#000000",
borderWidth: 1,
borderColor: "#1F1F1F",
@@ -291,19 +338,21 @@ export default function HomeScreen(): React.JSX.Element {
>
<Logo size={44} />
</View>
<View className="flex-1">
<View style={{ flex: 1 }}>
<Text
className="text-[13px] font-medium"
style={{
fontSize: 13,
fontWeight: "500",
color: theme.colors.textSecondary,
}}
>
{getGreeting()}
</Text>
<Text
className="text-[24px] font-bold"
accessibilityRole="header"
style={{
fontSize: 24,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.6,
}}
@@ -314,17 +363,15 @@ export default function HomeScreen(): React.JSX.Element {
</View>
</View>
<View className="mt-4">
<View style={{ marginTop: 16 }}>
<View>
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
>
<Text style={{ fontSize: 12, color: theme.colors.textTertiary }}>
Total active items
</Text>
<Text
className="text-[30px] font-bold"
style={{
fontSize: 30,
fontWeight: "bold",
color: theme.colors.textPrimary,
fontVariant: ["tabular-nums"],
letterSpacing: -1,
@@ -340,11 +387,14 @@ export default function HomeScreen(): React.JSX.Element {
</View>
</View>
<View className="gap-4 px-5">
<View style={{ paddingHorizontal: 20, gap: 16 }}>
<View>
<Text
className="text-[12px] font-semibold uppercase mb-2"
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
color: theme.colors.textSecondary,
letterSpacing: 1,
}}
@@ -358,89 +408,113 @@ export default function HomeScreen(): React.JSX.Element {
}}
style={({ pressed }: { pressed: boolean }) => {
return {
borderRadius: 24,
overflow: "hidden" as const,
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark
? "#000"
: theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.24 : 0.09,
shadowOffset: { width: 0, height: 8 },
shadowRadius: 14,
elevation: 5,
opacity: pressed ? 0.8 : 1,
};
}}
accessibilityRole="button"
accessibilityLabel="View my on-call assignments"
>
<LinearGradient
colors={[
theme.colors.oncallActiveBg,
theme.colors.accentGradientEnd + "06",
]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
<View
style={{
position: "absolute",
top: -20,
left: -10,
right: -10,
height: 120,
borderRadius: 24,
overflow: "hidden",
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
/>
>
<LinearGradient
colors={[
theme.colors.oncallActiveBg,
theme.colors.accentGradientEnd + "06",
]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 100,
}}
/>
<View className="flex-row items-center justify-between">
<View className="flex-row items-center flex-1">
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<View
className="w-10 h-10 rounded-2xl items-center justify-center mr-3"
style={{ backgroundColor: theme.colors.oncallActiveBg }}
>
<Ionicons
name="call-outline"
size={18}
color={theme.colors.oncallActive}
/>
</View>
<View className="flex-1">
<Text
className="text-[15px] font-bold"
style={{ color: theme.colors.textPrimary }}
>
My On-Call Policies
</Text>
<Text
className="text-[12px] mt-0.5"
style={{ color: theme.colors.textSecondary }}
>
{onCallLoading
? "Loading assignments..."
: totalAssignments > 0
? `${totalAssignments} active ${totalAssignments === 1 ? "assignment" : "assignments"} across ${onCallProjects.length} ${onCallProjects.length === 1 ? "project" : "projects"}`
: "You are not currently on-call"}
</Text>
</View>
</View>
<View className="items-end ml-3">
<Text
className="text-[28px] font-bold"
style={{
color: theme.colors.textPrimary,
fontVariant: ["tabular-nums"],
letterSpacing: -1,
flexDirection: "row",
alignItems: "center",
flex: 1,
}}
>
{onCallLoading ? "--" : totalAssignments}
</Text>
<Ionicons
name="chevron-forward"
size={14}
color={theme.colors.textTertiary}
/>
<View
style={{
width: 40,
height: 40,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
backgroundColor: theme.colors.oncallActiveBg,
}}
>
<Ionicons
name="call-outline"
size={18}
color={theme.colors.oncallActive}
/>
</View>
<View style={{ flex: 1 }}>
<Text
style={{
fontSize: 15,
fontWeight: "bold",
color: theme.colors.textPrimary,
}}
>
My On-Call Policies
</Text>
<Text
style={{
fontSize: 12,
marginTop: 2,
color: theme.colors.textSecondary,
}}
>
{onCallLoading
? "Loading assignments..."
: totalAssignments > 0
? `${totalAssignments} active ${totalAssignments === 1 ? "assignment" : "assignments"} across ${onCallProjects.length} ${onCallProjects.length === 1 ? "project" : "projects"}`
: "You are not currently on-call"}
</Text>
</View>
</View>
<View style={{ alignItems: "flex-end", marginLeft: 12 }}>
<Text
style={{
fontSize: 28,
fontWeight: "bold",
color: theme.colors.textPrimary,
fontVariant: ["tabular-nums"],
letterSpacing: -1,
}}
>
{onCallLoading ? "--" : totalAssignments}
</Text>
<Ionicons
name="chevron-forward"
size={14}
color={theme.colors.textTertiary}
/>
</View>
</View>
</View>
</Pressable>
@@ -448,69 +522,83 @@ export default function HomeScreen(): React.JSX.Element {
<View>
<Text
className="text-[12px] font-semibold uppercase mb-2"
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
color: theme.colors.textSecondary,
letterSpacing: 1,
}}
>
Incidents
</Text>
<View className="flex-row gap-3">
<StatCard
count={incidentCount}
label="Active Incidents"
accentColor={theme.colors.severityCritical}
iconName="warning-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Incidents");
}}
/>
<StatCard
count={incidentEpisodeCount}
label="Inc. Episodes"
accentColor={theme.colors.severityInfo}
iconName="layers-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Incidents");
}}
/>
<View style={{ flexDirection: "row" }}>
<View style={{ flex: 1 }}>
<StatCard
count={incidentCount}
label="Active Incidents"
accentColor={theme.colors.severityCritical}
iconName="warning-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Incidents");
}}
/>
</View>
<View style={{ flex: 1, marginLeft: 12 }}>
<StatCard
count={incidentEpisodeCount}
label="Inc. Episodes"
accentColor={theme.colors.severityInfo}
iconName="layers-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Incidents");
}}
/>
</View>
</View>
</View>
<View>
<Text
className="text-[12px] font-semibold uppercase mb-2"
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
color: theme.colors.textSecondary,
letterSpacing: 1,
}}
>
Alerts
</Text>
<View className="flex-row gap-3">
<StatCard
count={alertCount}
label="Active Alerts"
accentColor={theme.colors.severityMajor}
iconName="alert-circle-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Alerts");
}}
/>
<StatCard
count={alertEpisodeCount}
label="Alert Episodes"
accentColor={theme.colors.severityWarning}
iconName="layers-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Alerts");
}}
/>
<View style={{ flexDirection: "row" }}>
<View style={{ flex: 1 }}>
<StatCard
count={alertCount}
label="Active Alerts"
accentColor={theme.colors.severityMajor}
iconName="alert-circle-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Alerts");
}}
/>
</View>
<View style={{ flex: 1, marginLeft: 12 }}>
<StatCard
count={alertEpisodeCount}
label="Alert Episodes"
accentColor={theme.colors.severityWarning}
iconName="layers-outline"
isLoading={anyLoading}
onPress={() => {
return navigation.navigate("Alerts");
}}
/>
</View>
</View>
</View>
</View>

View File

@@ -152,8 +152,7 @@ export default function IncidentDetailScreen({
if (isLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SkeletonCard variant="detail" />
</View>
@@ -163,13 +162,14 @@ export default function IncidentDetailScreen({
if (!incident) {
return (
<View
className="flex-1 items-center justify-center"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<Text
className="text-[15px]"
style={{ color: theme.colors.textSecondary }}
>
<Text style={{ fontSize: 15, color: theme.colors.textSecondary }}>
Incident not found.
</Text>
</View>
@@ -217,13 +217,15 @@ export default function IncidentDetailScreen({
>
{/* Header card */}
<View
className="rounded-3xl overflow-hidden mb-5"
style={{
borderRadius: 24,
overflow: "hidden",
marginBottom: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : stateColor,
shadowOpacity: theme.isDark ? 0.28 : 0.12,
shadowColor: "#000",
shadowOpacity: 0.28,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
@@ -247,17 +249,22 @@ export default function IncidentDetailScreen({
backgroundColor: stateColor,
}}
/>
<View className="p-5">
<View style={{ padding: 20 }}>
<Text
className="text-[13px] font-semibold mb-2"
style={{ color: stateColor }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
color: stateColor,
}}
>
{incident.incidentNumberWithPrefix || `#${incident.incidentNumber}`}
</Text>
<Text
className="text-[24px] font-bold"
style={{
fontSize: 24,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.6,
}}
@@ -265,19 +272,40 @@ export default function IncidentDetailScreen({
{incident.title}
</Text>
<View className="flex-row flex-wrap gap-2 mt-3">
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{incident.currentIncidentState ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: stateColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: stateColor + "14",
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
className="text-[12px] font-semibold"
style={{ color: stateColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: stateColor,
}}
>
{incident.currentIncidentState.name}
</Text>
@@ -286,12 +314,21 @@ export default function IncidentDetailScreen({
{incident.incidentSeverity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: severityColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: severityColor + "14",
}}
>
<Text
className="text-[12px] font-semibold"
style={{ color: severityColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: severityColor,
}}
>
{incident.incidentSeverity.name}
</Text>
@@ -303,19 +340,23 @@ export default function IncidentDetailScreen({
{/* Description */}
{descriptionText ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Description" iconName="document-text-outline" />
<View
className="rounded-2xl p-4"
style={{
borderRadius: 16,
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[14px] leading-[22px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 14,
lineHeight: 22,
color: theme.colors.textPrimary,
}}
>
{descriptionText}
</Text>
@@ -323,66 +364,83 @@ export default function IncidentDetailScreen({
</View>
) : null}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Root Cause" iconName="bulb-outline" />
<RootCauseCard rootCauseText={rootCauseText} />
</View>
{/* Details */}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Details" iconName="information-circle-outline" />
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="p-4">
<View style={{ padding: 16 }}>
{incident.declaredAt ? (
<View className="flex-row mb-3">
<View style={{ flexDirection: "row", marginBottom: 12 }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Declared
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{formatDateTime(incident.declaredAt)}
</Text>
</View>
) : null}
<View className="flex-row mb-3">
<View style={{ flexDirection: "row", marginBottom: 12 }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Created
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{formatDateTime(incident.createdAt)}
</Text>
</View>
{incident.monitors?.length > 0 ? (
<View className="flex-row">
<View style={{ flexDirection: "row" }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Monitors
</Text>
<Text
className="text-[13px] flex-1"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
flex: 1,
color: theme.colors.textPrimary,
}}
>
{incident.monitors
.map((m: NamedEntity) => {
@@ -398,103 +456,118 @@ export default function IncidentDetailScreen({
{/* State Change Actions */}
{!isResolved ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Actions" iconName="flash-outline" />
<View
className="rounded-2xl p-3"
style={{
borderRadius: 16,
padding: 12,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="flex-row gap-3">
<View style={{ flexDirection: "row" }}>
{!isAcknowledged && !isResolved && acknowledgeState ? (
<Pressable
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Acknowledge incident"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Acknowledge
</Text>
</>
)}
</Pressable>
<View style={{ flex: 1 }}>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Acknowledge incident"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Acknowledge
</Text>
</>
)}
</Pressable>
</View>
) : null}
{resolveState ? (
<Pressable
<View
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateResolved,
marginLeft:
!isAcknowledged && !isResolved && acknowledgeState
? 12
: 0,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Resolve incident"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Resolve
</Text>
</>
)}
</Pressable>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateResolved,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
accessibilityRole="button"
accessibilityLabel="Resolve incident"
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Resolve
</Text>
</>
)}
</Pressable>
</View>
) : null}
</View>
</View>
@@ -503,7 +576,7 @@ export default function IncidentDetailScreen({
{/* Activity Feed */}
{feed && feed.length > 0 ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Activity Feed" iconName="list-outline" />
<FeedTimeline feed={feed} />
</View>

View File

@@ -155,8 +155,7 @@ export default function IncidentEpisodeDetailScreen({
if (isLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SkeletonCard variant="detail" />
</View>
@@ -166,13 +165,14 @@ export default function IncidentEpisodeDetailScreen({
if (!episode) {
return (
<View
className="flex-1 items-center justify-center"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<Text
className="text-[15px]"
style={{ color: theme.colors.textSecondary }}
>
<Text style={{ fontSize: 15, color: theme.colors.textSecondary }}>
Episode not found.
</Text>
</View>
@@ -219,13 +219,15 @@ export default function IncidentEpisodeDetailScreen({
}
>
<View
className="rounded-3xl overflow-hidden mb-5"
style={{
borderRadius: 24,
overflow: "hidden",
marginBottom: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : stateColor,
shadowOpacity: theme.isDark ? 0.28 : 0.12,
shadowColor: "#000",
shadowOpacity: 0.28,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
@@ -244,32 +246,61 @@ export default function IncidentEpisodeDetailScreen({
}}
/>
<View style={{ height: 3, backgroundColor: stateColor }} />
<View className="p-5">
<View style={{ padding: 20 }}>
<Text
className="text-[13px] font-semibold mb-2"
style={{ color: stateColor }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
color: stateColor,
}}
>
{episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`}
</Text>
<Text
className="text-[24px] font-bold"
style={{ color: theme.colors.textPrimary, letterSpacing: -0.6 }}
style={{
fontSize: 24,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.6,
}}
>
{episode.title}
</Text>
<View className="flex-row flex-wrap gap-2 mt-3">
<View
style={{
flexDirection: "row",
flexWrap: "wrap",
gap: 8,
marginTop: 12,
}}
>
{episode.currentIncidentState ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: stateColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: stateColor + "14",
}}
>
<View
className="w-2 h-2 rounded-full mr-1.5"
style={{ backgroundColor: stateColor }}
style={{
width: 8,
height: 8,
borderRadius: 9999,
marginRight: 6,
backgroundColor: stateColor,
}}
/>
<Text
className="text-[12px] font-semibold"
style={{ color: stateColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: stateColor,
}}
>
{episode.currentIncidentState.name}
</Text>
@@ -277,12 +308,21 @@ export default function IncidentEpisodeDetailScreen({
) : null}
{episode.incidentSeverity ? (
<View
className="flex-row items-center px-2.5 py-1 rounded-md"
style={{ backgroundColor: severityColor + "14" }}
style={{
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 6,
backgroundColor: severityColor + "14",
}}
>
<Text
className="text-[12px] font-semibold"
style={{ color: severityColor }}
style={{
fontSize: 12,
fontWeight: "600",
color: severityColor,
}}
>
{episode.incidentSeverity.name}
</Text>
@@ -293,19 +333,23 @@ export default function IncidentEpisodeDetailScreen({
</View>
{descriptionText ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Description" iconName="document-text-outline" />
<View
className="rounded-2xl p-4"
style={{
borderRadius: 16,
padding: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[14px] leading-[22px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 14,
lineHeight: 22,
color: theme.colors.textPrimary,
}}
>
{descriptionText}
</Text>
@@ -313,62 +357,78 @@ export default function IncidentEpisodeDetailScreen({
</View>
) : null}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Root Cause" iconName="bulb-outline" />
<RootCauseCard rootCauseText={rootCauseText} />
</View>
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Details" iconName="information-circle-outline" />
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="p-4">
<View style={{ padding: 16 }}>
{episode.declaredAt ? (
<View className="flex-row mb-3">
<View style={{ flexDirection: "row", marginBottom: 12 }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Declared
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{formatDateTime(episode.declaredAt)}
</Text>
</View>
) : null}
<View className="flex-row mb-3">
<View style={{ flexDirection: "row", marginBottom: 12 }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Created
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{formatDateTime(episode.createdAt)}
</Text>
</View>
<View className="flex-row">
<View style={{ flexDirection: "row" }}>
<Text
className="text-[13px] w-[90px]"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 13,
width: 90,
color: theme.colors.textTertiary,
}}
>
Incidents
</Text>
<Text
className="text-[13px]"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 13,
color: theme.colors.textPrimary,
}}
>
{episode.incidentCount ?? 0}
</Text>
@@ -378,98 +438,113 @@ export default function IncidentEpisodeDetailScreen({
</View>
{!isResolved ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Actions" iconName="flash-outline" />
<View
className="rounded-2xl p-3"
style={{
borderRadius: 16,
padding: 12,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="flex-row gap-3">
<View style={{ flexDirection: "row" }}>
{!isAcknowledged && !isResolved && acknowledgeState ? (
<Pressable
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Acknowledge
</Text>
</>
)}
</Pressable>
<View style={{ flex: 1 }}>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateAcknowledged,
}}
onPress={() => {
return handleStateChange(
acknowledgeState._id,
acknowledgeState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-circle-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Acknowledge
</Text>
</>
)}
</Pressable>
</View>
) : null}
{resolveState ? (
<Pressable
<View
style={{
flex: 1,
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
overflow: "hidden",
backgroundColor: theme.colors.stateResolved,
marginLeft:
!isAcknowledged && !isResolved && acknowledgeState
? 12
: 0,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
className="text-[14px] font-bold"
style={{ color: "#FFFFFF" }}
>
Resolve
</Text>
</>
)}
</Pressable>
<Pressable
style={{
flexDirection: "row",
paddingVertical: 12,
borderRadius: 12,
alignItems: "center",
justifyContent: "center",
minHeight: 48,
backgroundColor: theme.colors.stateResolved,
}}
onPress={() => {
return handleStateChange(
resolveState._id,
resolveState.name,
);
}}
disabled={changingState}
>
{changingState ? (
<ActivityIndicator size="small" color="#FFFFFF" />
) : (
<>
<Ionicons
name="checkmark-done-outline"
size={17}
color="#FFFFFF"
style={{ marginRight: 6 }}
/>
<Text
style={{
fontSize: 14,
fontWeight: "bold",
color: "#FFFFFF",
}}
>
Resolve
</Text>
</>
)}
</Pressable>
</View>
) : null}
</View>
</View>
@@ -477,7 +552,7 @@ export default function IncidentEpisodeDetailScreen({
) : null}
{feed && feed.length > 0 ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<SectionHeader title="Activity Feed" iconName="list-outline" />
<FeedTimeline feed={feed} />
</View>

View File

@@ -63,8 +63,13 @@ function SectionHeader({
const { theme } = useTheme();
return (
<View
className="flex-row items-center pb-2 pt-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{
flexDirection: "row",
alignItems: "center",
paddingBottom: 8,
paddingTop: 4,
backgroundColor: theme.colors.backgroundPrimary,
}}
>
<Ionicons
name={isActive ? "flame" : "checkmark-done"}
@@ -75,8 +80,10 @@ function SectionHeader({
style={{ marginRight: 6 }}
/>
<Text
className="text-[12px] font-semibold uppercase"
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
color: isActive
? theme.colors.textPrimary
: theme.colors.textTertiary,
@@ -86,16 +93,20 @@ function SectionHeader({
{title}
</Text>
<View
className="ml-2 px-1.5 py-0.5 rounded"
style={{
marginLeft: 8,
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
backgroundColor: isActive
? theme.colors.severityCritical + "18"
: theme.colors.backgroundTertiary,
}}
>
<Text
className="text-[11px] font-bold"
style={{
fontSize: 11,
fontWeight: "bold",
color: isActive
? theme.colors.severityCritical
: theme.colors.textTertiary,
@@ -299,8 +310,7 @@ export default function IncidentsScreen(): React.JSX.Element {
if (showLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SegmentedControl
segments={[
@@ -310,7 +320,7 @@ export default function IncidentsScreen(): React.JSX.Element {
selected={segment}
onSelect={setSegment}
/>
<View className="p-4">
<View style={{ padding: 16 }}>
<SkeletonCard />
<SkeletonCard />
<SkeletonCard />
@@ -330,8 +340,7 @@ export default function IncidentsScreen(): React.JSX.Element {
};
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<SegmentedControl
segments={[
@@ -357,10 +366,7 @@ export default function IncidentsScreen(): React.JSX.Element {
}
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
>
<View style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}>
<SegmentedControl
segments={[
{ key: "incidents" as const, label: "Incidents" },

View File

@@ -1,13 +1,6 @@
import React, { useMemo } from "react";
import {
View,
Text,
ScrollView,
RefreshControl,
Pressable,
} from "react-native";
import { View, Text, ScrollView, RefreshControl } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
import { useTheme } from "../theme";
import { useHaptics } from "../hooks/useHaptics";
import { useAllProjectOnCallPolicies } from "../hooks/useAllProjectOnCallPolicies";
@@ -33,8 +26,8 @@ function getAssignmentBadge(
successBg: string;
info: string;
infoBg: string;
warning: string;
warningBg: string;
purple: string;
purpleBg: string;
},
): AssignmentBadgeConfig {
switch (type) {
@@ -54,10 +47,10 @@ function getAssignmentBadge(
};
case "schedule":
return {
icon: "time-outline",
icon: "calendar-outline",
label: "Schedule",
color: colors.warning,
background: colors.warningBg,
color: colors.purple,
background: colors.purpleBg,
};
}
}
@@ -86,13 +79,15 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
if (isLoading) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<ScrollView contentContainerStyle={{ padding: 16, paddingBottom: 44 }}>
<View
className="rounded-3xl overflow-hidden p-5 mb-4"
style={{
borderRadius: 24,
overflow: "hidden",
padding: 20,
marginBottom: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -111,8 +106,7 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
if (isError) {
return (
<View
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
>
<EmptyState
title="Could not load on-call assignments"
@@ -130,7 +124,7 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
return (
<ScrollView
style={{ backgroundColor: theme.colors.backgroundPrimary }}
contentContainerStyle={{ padding: 16, paddingBottom: 56 }}
contentContainerStyle={{ padding: 20, paddingBottom: 56 }}
refreshControl={
<RefreshControl
refreshing={false}
@@ -140,39 +134,31 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
}
>
<View
className="rounded-3xl overflow-hidden p-5 mb-5"
style={{
borderRadius: 24,
padding: 20,
marginBottom: 20,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.32 : 0.1,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
}}
>
<LinearGradient
colors={[
theme.colors.oncallActiveBg,
theme.colors.accentGradientEnd + "08",
]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
<View
style={{
position: "absolute",
top: -70,
left: -30,
right: -20,
height: 220,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
/>
<View className="flex-row items-center justify-between">
<View className="flex-row items-center flex-1">
>
<View style={{ flexDirection: "row", alignItems: "center", flex: 1 }}>
<View
className="w-11 h-11 rounded-2xl items-center justify-center mr-3"
style={{
width: 44,
height: 44,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
backgroundColor: theme.colors.oncallActiveBg,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -184,10 +170,11 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
color={theme.colors.oncallActive}
/>
</View>
<View className="flex-1">
<View style={{ flex: 1 }}>
<Text
className="text-[20px] font-bold"
style={{
fontSize: 20,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.4,
}}
@@ -195,8 +182,11 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
On-Call Now
</Text>
<Text
className="text-[12px] mt-0.5"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 12,
marginTop: 2,
color: theme.colors.textSecondary,
}}
>
Live duty assignments
</Text>
@@ -204,16 +194,19 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
</View>
<View
className="px-3 py-1.5 rounded-xl"
style={{
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 12,
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderSubtle,
}}
>
<Text
className="text-[18px] font-bold"
style={{
fontSize: 18,
fontWeight: "bold",
color: theme.colors.textPrimary,
fontVariant: ["tabular-nums"],
}}
@@ -224,8 +217,12 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
</View>
<Text
className="text-[13px] mt-4 leading-5"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 13,
marginTop: 16,
lineHeight: 20,
color: theme.colors.textSecondary,
}}
>
{summaryText}
</Text>
@@ -233,8 +230,9 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
{projects.length === 0 ? (
<View
className="rounded-3xl overflow-hidden"
style={{
borderRadius: 24,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -247,42 +245,53 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
/>
</View>
) : (
<View className="gap-4">
<View style={{ gap: 16 }}>
{projects.map((projectData: ProjectOnCallAssignments) => {
return (
<View
key={projectData.projectId}
className="rounded-3xl overflow-hidden"
style={{
borderRadius: 24,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark
? "#000"
: theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.2 : 0.08,
shadowOffset: { width: 0, height: 6 },
shadowRadius: 14,
elevation: 4,
}}
>
<View
className="px-4 py-3.5 flex-row items-center justify-between"
style={{
paddingHorizontal: 20,
paddingVertical: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
borderBottomWidth: 1,
borderBottomColor: theme.colors.borderSubtle,
backgroundColor: theme.colors.backgroundSecondary,
borderTopLeftRadius: 23,
borderTopRightRadius: 23,
}}
>
<View className="flex-row items-center flex-1">
<View
style={{
flexDirection: "row",
alignItems: "center",
flex: 1,
}}
>
<Ionicons
name="folder-open-outline"
size={16}
color={theme.colors.textSecondary}
/>
<Text
className="text-[14px] font-semibold ml-2 mr-2 flex-1"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 14,
fontWeight: "600",
marginLeft: 8,
marginRight: 8,
flex: 1,
color: theme.colors.textPrimary,
}}
numberOfLines={1}
>
{projectData.projectName}
@@ -290,16 +299,21 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
</View>
<View
className="px-2 py-1 rounded-lg"
style={{
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderSubtle,
}}
>
<Text
className="text-[11px] font-semibold"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 11,
fontWeight: "600",
color: theme.colors.textSecondary,
}}
>
{projectData.assignments.length} active
</Text>
@@ -319,44 +333,53 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
successBg: theme.colors.oncallActiveBg,
info: theme.colors.severityInfo,
infoBg: theme.colors.severityInfoBg,
warning: theme.colors.severityWarning,
warningBg: theme.colors.severityWarningBg,
purple: "#A855F7",
purpleBg: "rgba(168, 85, 247, 0.12)",
},
);
return (
<Pressable
key={`${assignment.projectId}-${assignment.policyId ?? assignmentIndex}`}
style={({ pressed }: { pressed: boolean }) => {
return [
{
paddingHorizontal: 16,
paddingVertical: 14,
opacity: pressed ? 0.82 : 1,
},
assignmentIndex !==
projectData.assignments.length - 1
? {
borderBottomWidth: 1,
borderBottomColor:
theme.colors.borderSubtle,
}
: undefined,
];
<View
key={`${assignment.projectId}-${assignment.policyId ?? "unknown"}-${assignmentIndex}`}
style={{
paddingHorizontal: 20,
paddingVertical: 16,
...(assignmentIndex !==
projectData.assignments.length - 1
? {
borderBottomWidth: 1,
borderBottomColor: theme.colors.borderSubtle,
}
: {}),
}}
>
<View className="flex-row items-center justify-between">
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text
className="text-[15px] font-semibold flex-1 mr-3"
style={{ color: theme.colors.textPrimary }}
style={{
fontSize: 15,
fontWeight: "600",
flex: 1,
marginRight: 12,
color: theme.colors.textPrimary,
}}
numberOfLines={1}
>
{assignment.policyName}
</Text>
<View
className="px-2.5 py-1 rounded-full flex-row items-center"
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
flexDirection: "row",
alignItems: "center",
backgroundColor: badge.background,
}}
>
@@ -366,45 +389,66 @@ export default function MyOnCallPoliciesScreen(): React.JSX.Element {
color={badge.color}
/>
<Text
className="text-[11px] font-semibold ml-1"
style={{ color: badge.color }}
style={{
fontSize: 11,
fontWeight: "600",
marginLeft: 4,
color: badge.color,
}}
>
{badge.label}
</Text>
</View>
</View>
<View className="mt-2">
<View className="flex-row items-center">
<View style={{ marginTop: 8 }}>
<View
style={{
flexDirection: "row",
alignItems: "center",
}}
>
<Ionicons
name="git-branch-outline"
size={13}
color={theme.colors.textTertiary}
/>
<Text
className="text-[12px] ml-1.5"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 12,
marginLeft: 6,
color: theme.colors.textSecondary,
}}
numberOfLines={1}
>
Rule: {assignment.escalationRuleName}
</Text>
</View>
<View className="flex-row items-center mt-1">
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 4,
}}
>
<Ionicons
name="information-circle-outline"
size={13}
color={theme.colors.textTertiary}
/>
<Text
className="text-[12px] ml-1.5"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 12,
marginLeft: 6,
color: theme.colors.textSecondary,
}}
numberOfLines={1}
>
{assignment.assignmentDetail}
</Text>
</View>
</View>
</Pressable>
</View>
);
},
)}

View File

@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import { View, Text, ScrollView, Switch, Pressable } from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
import { useTheme, ThemeMode } from "../theme";
import { useTheme } from "../theme";
import { useAuth } from "../hooks/useAuth";
import { useBiometric } from "../hooks/useBiometric";
import { useHaptics } from "../hooks/useHaptics";
@@ -34,21 +34,30 @@ function SettingsRow({
const content: React.JSX.Element = (
<View
className="flex-row justify-between items-center px-4 min-h-[52px]"
style={
!isLast
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 16,
minHeight: 52,
...(!isLast
? {
borderBottomWidth: 1,
borderBottomColor: theme.colors.borderSubtle,
}
: undefined
}
: {}),
}}
>
<View className="flex-row items-center flex-1">
<View style={{ flexDirection: "row", alignItems: "center", flex: 1 }}>
{iconName ? (
<View
className="w-7 h-7 rounded-lg items-center justify-center mr-3"
style={{
width: 28,
height: 28,
borderRadius: 8,
alignItems: "center",
justifyContent: "center",
marginRight: 12,
backgroundColor: destructive
? theme.colors.statusErrorBg
: theme.colors.iconBackground,
@@ -66,8 +75,10 @@ function SettingsRow({
</View>
) : null}
<Text
className="text-[15px] font-medium py-3"
style={{
fontSize: 15,
fontWeight: "500",
paddingVertical: 12,
color: destructive
? theme.colors.actionDestructive
: theme.colors.textPrimary,
@@ -78,10 +89,7 @@ function SettingsRow({
</View>
{rightElement ??
(value ? (
<Text
className="text-[14px]"
style={{ color: theme.colors.textTertiary }}
>
<Text style={{ fontSize: 14, color: theme.colors.textTertiary }}>
{value}
</Text>
) : onPress ? (
@@ -111,26 +119,16 @@ function SettingsRow({
}
export default function SettingsScreen(): React.JSX.Element {
const { theme, themeMode, setThemeMode } = useTheme();
const { theme } = useTheme();
const { logout } = useAuth();
const biometric: ReturnType<typeof useBiometric> = useBiometric();
const { selectionFeedback } = useHaptics();
const [serverUrl, setServerUrlState] = useState("");
const activeThemeOptionColor: string = theme.isDark
? theme.colors.backgroundPrimary
: "#FFFFFF";
useEffect(() => {
getServerUrl().then(setServerUrlState);
}, []);
const handleThemeChange: (mode: ThemeMode) => void = (
mode: ThemeMode,
): void => {
selectionFeedback();
setThemeMode(mode);
};
const handleBiometricToggle: (value: boolean) => Promise<void> = async (
value: boolean,
): Promise<void> => {
@@ -147,13 +145,16 @@ export default function SettingsScreen(): React.JSX.Element {
>
{/* Header */}
<View
className="rounded-3xl overflow-hidden p-5 mb-6"
style={{
borderRadius: 24,
overflow: "hidden",
padding: 20,
marginBottom: 24,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
shadowColor: theme.isDark ? "#000" : theme.colors.accentGradientMid,
shadowOpacity: theme.isDark ? 0.28 : 0.12,
shadowColor: "#000",
shadowOpacity: 0.28,
shadowOffset: { width: 0, height: 10 },
shadowRadius: 18,
elevation: 7,
@@ -175,10 +176,14 @@ export default function SettingsScreen(): React.JSX.Element {
}}
/>
<View className="flex-row items-center">
<View style={{ flexDirection: "row", alignItems: "center" }}>
<View
className="w-14 h-14 rounded-2xl items-center justify-center"
style={{
width: 56,
height: 56,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#000000",
borderWidth: 1,
borderColor: "#1F1F1F",
@@ -187,16 +192,21 @@ export default function SettingsScreen(): React.JSX.Element {
<Logo size={52} />
</View>
<View className="ml-3 flex-1">
<View style={{ marginLeft: 12, flex: 1 }}>
<Text
className="text-[20px] font-bold"
style={{ color: theme.colors.textPrimary, letterSpacing: -0.3 }}
style={{
fontSize: 20,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -0.3,
}}
>
Preferences
</Text>
<Text
className="text-[12px] mt-0.5"
style={{
fontSize: 12,
marginTop: 2,
color: theme.colors.textSecondary,
letterSpacing: 0.2,
}}
@@ -206,10 +216,19 @@ export default function SettingsScreen(): React.JSX.Element {
</View>
</View>
<View className="mt-4 flex-row items-center justify-between">
<View
style={{
marginTop: 16,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
}}
>
<Text
className="text-[11px] font-semibold uppercase"
style={{
fontSize: 11,
fontWeight: "600",
textTransform: "uppercase",
color: theme.colors.textTertiary,
letterSpacing: 1,
}}
@@ -218,16 +237,21 @@ export default function SettingsScreen(): React.JSX.Element {
</Text>
<View
className="px-2.5 py-1 rounded-lg"
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 8,
backgroundColor: theme.colors.accentCyanBg,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<Text
className="text-[11px] font-semibold"
style={{ color: theme.colors.accentCyan }}
style={{
fontSize: 11,
fontWeight: "600",
color: theme.colors.accentCyan,
}}
>
{serverUrl || "oneuptime.com"}
</Text>
@@ -235,106 +259,26 @@ export default function SettingsScreen(): React.JSX.Element {
</View>
</View>
{/* Appearance */}
<View className="mb-6">
<Text
className="text-[12px] font-semibold uppercase mb-2 ml-1"
style={{ color: theme.colors.textTertiary, letterSpacing: 0.8 }}
>
Appearance
</Text>
<View
className="rounded-2xl overflow-hidden"
style={{
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View className="p-1.5">
<View className="flex-row rounded-xl gap-1">
{(["dark", "light", "system"] as ThemeMode[]).map(
(mode: ThemeMode) => {
const isActive: boolean = themeMode === mode;
return (
<Pressable
key={mode}
style={({ pressed }: { pressed: boolean }) => {
return [
{
flex: 1,
flexDirection: "row" as const,
alignItems: "center" as const,
justifyContent: "center" as const,
paddingVertical: 10,
borderRadius: 10,
gap: 6,
overflow: "hidden" as const,
opacity: pressed ? 0.7 : 1,
},
isActive
? {
backgroundColor: theme.colors.actionPrimary,
shadowColor: theme.colors.actionPrimary,
shadowOpacity: 0.3,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 6,
elevation: 3,
}
: undefined,
];
}}
onPress={() => {
return handleThemeChange(mode);
}}
>
<Ionicons
name={
mode === "dark"
? "moon-outline"
: mode === "light"
? "sunny-outline"
: "phone-portrait-outline"
}
size={15}
color={
isActive
? activeThemeOptionColor
: theme.colors.textSecondary
}
/>
<Text
className="text-[13px] font-semibold"
style={{
color: isActive
? activeThemeOptionColor
: theme.colors.textPrimary,
letterSpacing: 0.2,
}}
>
{mode.charAt(0).toUpperCase() + mode.slice(1)}
</Text>
</Pressable>
);
},
)}
</View>
</View>
</View>
</View>
{/* Security */}
{biometric.isAvailable ? (
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<Text
className="text-[12px] font-semibold uppercase mb-2 ml-1"
style={{ color: theme.colors.textTertiary, letterSpacing: 0.8 }}
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
marginLeft: 4,
color: theme.colors.textTertiary,
letterSpacing: 0.8,
}}
>
Security
</Text>
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -358,8 +302,13 @@ export default function SettingsScreen(): React.JSX.Element {
/>
</View>
<Text
className="text-[12px] mt-1.5 ml-1 leading-4"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 12,
marginTop: 6,
marginLeft: 4,
lineHeight: 16,
color: theme.colors.textTertiary,
}}
>
Require biometrics to unlock the app
</Text>
@@ -367,16 +316,24 @@ export default function SettingsScreen(): React.JSX.Element {
) : null}
{/* Server */}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<Text
className="text-[12px] font-semibold uppercase mb-2 ml-1"
style={{ color: theme.colors.textTertiary, letterSpacing: 0.8 }}
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
marginLeft: 4,
color: theme.colors.textTertiary,
letterSpacing: 0.8,
}}
>
Server
</Text>
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -392,16 +349,24 @@ export default function SettingsScreen(): React.JSX.Element {
</View>
{/* Account */}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<Text
className="text-[12px] font-semibold uppercase mb-2 ml-1"
style={{ color: theme.colors.textTertiary, letterSpacing: 0.8 }}
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
marginLeft: 4,
color: theme.colors.textTertiary,
letterSpacing: 0.8,
}}
>
Account
</Text>
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -418,16 +383,24 @@ export default function SettingsScreen(): React.JSX.Element {
</View>
{/* About */}
<View className="mb-6">
<View style={{ marginBottom: 24 }}>
<Text
className="text-[12px] font-semibold uppercase mb-2 ml-1"
style={{ color: theme.colors.textTertiary, letterSpacing: 0.8 }}
style={{
fontSize: 12,
fontWeight: "600",
textTransform: "uppercase",
marginBottom: 8,
marginLeft: 4,
color: theme.colors.textTertiary,
letterSpacing: 0.8,
}}
>
About
</Text>
<View
className="rounded-2xl overflow-hidden"
style={{
borderRadius: 16,
overflow: "hidden",
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
@@ -443,29 +416,45 @@ export default function SettingsScreen(): React.JSX.Element {
</View>
{/* Footer */}
<View className="pt-2 pb-2">
<View style={{ paddingTop: 8, paddingBottom: 8 }}>
<View
className="rounded-2xl overflow-hidden px-4 py-4"
style={{
borderRadius: 16,
overflow: "hidden",
paddingHorizontal: 16,
paddingVertical: 16,
backgroundColor: theme.colors.backgroundElevated,
borderWidth: 1,
borderColor: theme.colors.borderGlass,
}}
>
<View
className="absolute top-0 left-0 right-0"
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 3,
backgroundColor: theme.colors.actionPrimary,
opacity: theme.isDark ? 0.45 : 0.85,
opacity: 0.45,
}}
/>
<View className="items-center mt-1 mb-2.5">
<View className="flex-row items-center gap-2">
<View
style={{ alignItems: "center", marginTop: 4, marginBottom: 10 }}
>
<View
style={{ flexDirection: "row", alignItems: "center", gap: 8 }}
>
<View
className="w-8 h-8 rounded-full items-center justify-center"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
width: 32,
height: 32,
borderRadius: 9999,
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.iconBackground,
}}
>
<Ionicons
name="heart-outline"
@@ -474,12 +463,17 @@ export default function SettingsScreen(): React.JSX.Element {
/>
</View>
<View
className="px-2.5 py-1 rounded-full"
style={{ backgroundColor: theme.colors.iconBackground }}
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.iconBackground,
}}
>
<Text
className="text-[10px] font-semibold"
style={{
fontSize: 10,
fontWeight: "600",
color: theme.colors.textSecondary,
letterSpacing: 0.4,
}}
@@ -491,31 +485,39 @@ export default function SettingsScreen(): React.JSX.Element {
</View>
<Text
className="text-[14px] font-semibold"
style={{ color: theme.colors.textPrimary, textAlign: "center" }}
style={{
fontSize: 14,
fontWeight: "600",
color: theme.colors.textPrimary,
textAlign: "center",
}}
>
Thank you for supporting open source software.
</Text>
<Text
className="text-[12px] mt-2 leading-5"
style={{ color: theme.colors.textSecondary, textAlign: "center" }}
style={{
fontSize: 12,
marginTop: 8,
lineHeight: 20,
color: theme.colors.textSecondary,
textAlign: "center",
}}
>
Built and maintained by contributors around the world.
</Text>
<View className="items-center mt-3">
<View style={{ alignItems: "center", marginTop: 12 }}>
<View
className="px-2.5 py-1 rounded-full"
style={{
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 9999,
backgroundColor: theme.colors.backgroundTertiary,
borderWidth: 1,
borderColor: theme.colors.borderSubtle,
}}
>
<Text
className="text-[11px]"
style={{ color: theme.colors.textTertiary }}
>
<Text style={{ fontSize: 11, color: theme.colors.textTertiary }}>
Licensed under Apache 2.0
</Text>
</View>

View File

@@ -74,28 +74,33 @@ export default function LoginScreen(): React.JSX.Element {
return (
<KeyboardAvoidingView
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
>
<View className="flex-1 justify-center px-7">
<View className="items-center mb-12">
<View
style={{ flex: 1, justifyContent: "center", paddingHorizontal: 28 }}
>
<View style={{ alignItems: "center", marginBottom: 48 }}>
<View
className="w-16 h-16 rounded-2xl items-center justify-center mb-5"
style={{
backgroundColor: theme.colors.iconBackground,
borderWidth: 2,
borderColor: theme.colors.borderDefault,
borderRadius: 20,
marginBottom: 20,
overflow: "hidden",
}}
>
<Logo size={36} />
<Logo size={90} />
</View>
<Text
className="text-[30px] font-bold"
style={{
fontSize: 30,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -1,
}}
@@ -103,22 +108,27 @@ export default function LoginScreen(): React.JSX.Element {
OneUptime
</Text>
<Text
className="text-[15px] mt-1"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 15,
marginTop: 4,
color: theme.colors.textSecondary,
}}
>
Sign in to continue
</Text>
{serverUrl ? (
<View
className="mt-3 px-3 py-1 rounded-lg"
style={{
marginTop: 12,
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 8,
backgroundColor: theme.colors.backgroundTertiary,
}}
>
<Text
className="text-[12px]"
style={{ color: theme.colors.textTertiary }}
style={{ fontSize: 12, color: theme.colors.textTertiary }}
>
{serverUrl}
</Text>
@@ -128,14 +138,22 @@ export default function LoginScreen(): React.JSX.Element {
<View>
<Text
className="text-[13px] font-semibold mb-2"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
color: theme.colors.textSecondary,
}}
>
Email
</Text>
<View
className="flex-row items-center h-[48px] rounded-xl px-3.5"
style={{
flexDirection: "row",
alignItems: "center",
height: 48,
borderRadius: 12,
paddingHorizontal: 14,
backgroundColor: theme.colors.backgroundSecondary,
borderWidth: 1.5,
borderColor: emailFocused
@@ -154,8 +172,11 @@ export default function LoginScreen(): React.JSX.Element {
style={{ marginRight: 10 }}
/>
<TextInput
className="flex-1 text-[15px]"
style={{ color: theme.colors.textPrimary }}
style={{
flex: 1,
fontSize: 15,
color: theme.colors.textPrimary,
}}
value={email}
onChangeText={(text: string) => {
setEmail(text);
@@ -178,14 +199,23 @@ export default function LoginScreen(): React.JSX.Element {
</View>
<Text
className="text-[13px] font-semibold mb-2 mt-4"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
marginTop: 16,
color: theme.colors.textSecondary,
}}
>
Password
</Text>
<View
className="flex-row items-center h-[48px] rounded-xl px-3.5"
style={{
flexDirection: "row",
alignItems: "center",
height: 48,
borderRadius: 12,
paddingHorizontal: 14,
backgroundColor: theme.colors.backgroundSecondary,
borderWidth: 1.5,
borderColor: passwordFocused
@@ -204,8 +234,11 @@ export default function LoginScreen(): React.JSX.Element {
style={{ marginRight: 10 }}
/>
<TextInput
className="flex-1 text-[15px]"
style={{ color: theme.colors.textPrimary }}
style={{
flex: 1,
fontSize: 15,
color: theme.colors.textPrimary,
}}
value={password}
onChangeText={(text: string) => {
setPassword(text);
@@ -227,7 +260,13 @@ export default function LoginScreen(): React.JSX.Element {
</View>
{error ? (
<View className="flex-row items-start mt-3">
<View
style={{
flexDirection: "row",
alignItems: "flex-start",
marginTop: 12,
}}
>
<Ionicons
name="alert-circle"
size={14}
@@ -235,15 +274,18 @@ export default function LoginScreen(): React.JSX.Element {
style={{ marginRight: 6, marginTop: 2 }}
/>
<Text
className="text-[13px] flex-1"
style={{ color: theme.colors.statusError }}
style={{
fontSize: 13,
flex: 1,
color: theme.colors.statusError,
}}
>
{error}
</Text>
</View>
) : null}
<View className="mt-6">
<View style={{ marginTop: 24 }}>
<GradientButton
label="Sign In"
onPress={handleLogin}
@@ -253,7 +295,7 @@ export default function LoginScreen(): React.JSX.Element {
</View>
</View>
<View className="mt-4">
<View style={{ marginTop: 16 }}>
<GradientButton
label="Change Server"
onPress={handleChangeServer}

View File

@@ -65,28 +65,33 @@ export default function ServerUrlScreen(): React.JSX.Element {
return (
<KeyboardAvoidingView
className="flex-1"
style={{ backgroundColor: theme.colors.backgroundPrimary }}
style={{ flex: 1, backgroundColor: theme.colors.backgroundPrimary }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
keyboardShouldPersistTaps="handled"
>
<View className="flex-1 justify-center px-7">
<View className="items-center mb-14">
<View
style={{ flex: 1, justifyContent: "center", paddingHorizontal: 28 }}
>
<View style={{ alignItems: "center", marginBottom: 56 }}>
<View
className="w-16 h-16 rounded-2xl items-center justify-center mb-5"
style={{
backgroundColor: theme.colors.iconBackground,
borderWidth: 2,
borderColor: theme.colors.borderDefault,
borderRadius: 20,
marginBottom: 20,
overflow: "hidden",
}}
>
<Logo size={36} />
<Logo size={90} />
</View>
<Text
className="text-[30px] font-bold"
style={{
fontSize: 30,
fontWeight: "bold",
color: theme.colors.textPrimary,
letterSpacing: -1,
}}
@@ -94,8 +99,13 @@ export default function ServerUrlScreen(): React.JSX.Element {
OneUptime
</Text>
<Text
className="text-[15px] mt-2 text-center leading-[22px]"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 15,
marginTop: 8,
textAlign: "center",
lineHeight: 22,
color: theme.colors.textSecondary,
}}
>
Connect to your OneUptime instance
</Text>
@@ -103,14 +113,22 @@ export default function ServerUrlScreen(): React.JSX.Element {
<View>
<Text
className="text-[13px] font-semibold mb-2"
style={{ color: theme.colors.textSecondary }}
style={{
fontSize: 13,
fontWeight: "600",
marginBottom: 8,
color: theme.colors.textSecondary,
}}
>
Server URL
</Text>
<View
className="flex-row items-center h-[48px] rounded-xl px-3.5"
style={{
flexDirection: "row",
alignItems: "center",
height: 48,
borderRadius: 12,
paddingHorizontal: 14,
backgroundColor: theme.colors.backgroundSecondary,
borderWidth: 1.5,
borderColor: error
@@ -131,8 +149,11 @@ export default function ServerUrlScreen(): React.JSX.Element {
style={{ marginRight: 10 }}
/>
<TextInput
className="flex-1 text-[15px]"
style={{ color: theme.colors.textPrimary }}
style={{
flex: 1,
fontSize: 15,
color: theme.colors.textPrimary,
}}
value={url}
onChangeText={(text: string) => {
setUrl(text);
@@ -155,7 +176,13 @@ export default function ServerUrlScreen(): React.JSX.Element {
</View>
{error ? (
<View className="flex-row items-center mt-3">
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 12,
}}
>
<Ionicons
name="alert-circle"
size={14}
@@ -163,15 +190,18 @@ export default function ServerUrlScreen(): React.JSX.Element {
style={{ marginRight: 6 }}
/>
<Text
className="text-[13px] flex-1"
style={{ color: theme.colors.statusError }}
style={{
fontSize: 13,
flex: 1,
color: theme.colors.statusError,
}}
>
{error}
</Text>
</View>
) : null}
<View className="mt-6">
<View style={{ marginTop: 24 }}>
<GradientButton
label="Connect"
onPress={handleConnect}
@@ -182,8 +212,13 @@ export default function ServerUrlScreen(): React.JSX.Element {
</View>
<Text
className="text-[12px] text-center mt-6 leading-5"
style={{ color: theme.colors.textTertiary }}
style={{
fontSize: 12,
textAlign: "center",
marginTop: 24,
lineHeight: 20,
color: theme.colors.textTertiary,
}}
>
Self-hosting? Enter your OneUptime server URL above.
</Text>

View File

@@ -1,26 +1,11 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import type { ThemeMode } from "../theme";
const KEYS: {
readonly THEME_MODE: "oneuptime_theme_mode";
readonly BIOMETRIC_ENABLED: "oneuptime_biometric_enabled";
} = {
THEME_MODE: "oneuptime_theme_mode",
BIOMETRIC_ENABLED: "oneuptime_biometric_enabled",
} as const;
export async function getThemeMode(): Promise<ThemeMode> {
const stored: string | null = await AsyncStorage.getItem(KEYS.THEME_MODE);
if (stored === "dark" || stored === "light" || stored === "system") {
return stored;
}
return "dark";
}
export async function setThemeMode(mode: ThemeMode): Promise<void> {
await AsyncStorage.setItem(KEYS.THEME_MODE, mode);
}
export async function getBiometricEnabled(): Promise<boolean> {
const stored: string | null = await AsyncStorage.getItem(
KEYS.BIOMETRIC_ENABLED,

View File

@@ -1,33 +1,19 @@
import React, {
createContext,
useContext,
useState,
useEffect,
useMemo,
ReactNode,
} from "react";
import { View, useColorScheme } from "react-native";
import { ColorTokens, darkColors, lightColors } from "./colors";
import {
getThemeMode as loadThemeMode,
setThemeMode as saveThemeMode,
} from "../storage/preferences";
export type ThemeMode = "dark" | "light" | "system";
import React, { createContext, useContext, ReactNode } from "react";
import { View } from "react-native";
import { ColorTokens, darkColors } from "./colors";
export interface Theme {
colors: ColorTokens;
isDark: boolean;
}
interface ThemeContextValue {
theme: Theme;
themeMode: ThemeMode;
setThemeMode: (mode: ThemeMode) => void;
}
const ThemeContext: React.Context<ThemeContextValue | undefined> =
createContext<ThemeContextValue | undefined>(undefined);
const theme: Theme = { colors: darkColors };
const ThemeContext: React.Context<ThemeContextValue> =
createContext<ThemeContextValue>({ theme });
interface ThemeProviderProps {
children: ReactNode;
@@ -36,56 +22,13 @@ interface ThemeProviderProps {
export function ThemeProvider({
children,
}: ThemeProviderProps): React.JSX.Element {
const systemColorScheme: "light" | "dark" | null | undefined =
useColorScheme();
const [themeMode, setThemeModeState] = useState<ThemeMode>("light");
// Load persisted theme on mount
useEffect(() => {
loadThemeMode().then((mode: ThemeMode) => {
setThemeModeState(mode);
});
}, []);
const setThemeMode: (mode: ThemeMode) => void = (mode: ThemeMode): void => {
setThemeModeState(mode);
saveThemeMode(mode);
};
const theme: Theme = useMemo((): Theme => {
let isDark: boolean;
if (themeMode === "system") {
isDark = systemColorScheme !== "light";
} else {
isDark = themeMode === "dark";
}
return {
colors: isDark ? darkColors : lightColors,
isDark,
};
}, [themeMode, systemColorScheme]);
const value: ThemeContextValue = useMemo((): ThemeContextValue => {
return {
theme,
themeMode,
setThemeMode,
};
}, [theme, themeMode]);
return (
<ThemeContext.Provider value={value}>
<ThemeContext.Provider value={{ theme }}>
<View style={{ flex: 1 }}>{children}</View>
</ThemeContext.Provider>
);
}
export function useTheme(): ThemeContextValue {
const context: ThemeContextValue | undefined = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
return useContext(ThemeContext);
}

View File

@@ -141,75 +141,3 @@ export const darkColors: ColorTokens = {
statusError: "#EF4444",
statusErrorBg: "rgba(239, 68, 68, 0.12)",
};
export const lightColors: ColorTokens = {
// Background — clean white with warm gray tones
backgroundPrimary: "#FFFFFF",
backgroundSecondary: "#F9FAFB",
backgroundTertiary: "#F3F4F6",
backgroundElevated: "#FFFFFF",
// Accent
cardAccent: "rgba(0, 0, 0, 0.02)",
backgroundGlass: "rgba(255, 255, 255, 0.80)",
iconBackground: "rgba(0, 0, 0, 0.05)",
// Gradient — neutral monochrome accent
accentGradientStart: "#52525B",
accentGradientMid: "#3F3F46",
accentGradientEnd: "#27272A",
accentCyan: "#52525B",
accentCyanBg: "rgba(82, 82, 91, 0.08)",
surfaceGlow: "rgba(0, 0, 0, 0.04)",
headerGradient: "rgba(0, 0, 0, 0.03)",
gradientStart: "rgba(0, 0, 0, 0.04)",
gradientEnd: "transparent",
// Border
borderDefault: "#E5E7EB",
borderSubtle: "#F3F4F6",
borderGlass: "rgba(0, 0, 0, 0.05)",
// Text
textPrimary: "#111827",
textSecondary: "#6B7280",
textTertiary: "#9CA3AF",
textInverse: "#FFFFFF",
// Severity
severityCritical: "#DC2626",
severityCriticalBg: "rgba(220, 38, 38, 0.08)",
severityMajor: "#EA580C",
severityMajorBg: "rgba(234, 88, 12, 0.08)",
severityMinor: "#CA8A04",
severityMinorBg: "rgba(202, 138, 4, 0.08)",
severityWarning: "#D97706",
severityWarningBg: "rgba(217, 119, 6, 0.08)",
severityInfo: "#2563EB",
severityInfoBg: "rgba(37, 99, 235, 0.08)",
// State
stateCreated: "#DC2626",
stateAcknowledged: "#D97706",
stateResolved: "#16A34A",
stateInvestigating: "#EA580C",
stateMuted: "#9CA3AF",
// On-Call
oncallActive: "#16A34A",
oncallActiveBg: "rgba(22, 163, 74, 0.08)",
oncallInactive: "#9CA3AF",
oncallInactiveBg: "rgba(156, 163, 175, 0.08)",
// Action — neutral accent
actionPrimary: "#27272A",
actionPrimaryPressed: "#3F3F46",
actionDestructive: "#DC2626",
actionDestructivePressed: "#B91C1C",
// Status
statusSuccess: "#16A34A",
statusSuccessBg: "rgba(22, 163, 74, 0.08)",
statusError: "#DC2626",
statusErrorBg: "rgba(220, 38, 38, 0.08)",
};

View File

@@ -1,4 +1,4 @@
export { darkColors, lightColors } from "./colors";
export { darkColors } from "./colors";
export type { ColorTokens } from "./colors";
export { ThemeProvider, useTheme } from "./ThemeContext";
export type { Theme, ThemeMode } from "./ThemeContext";
export type { Theme } from "./ThemeContext";

View File

@@ -0,0 +1,19 @@
/* eslint-disable no-console */
const logger: {
info: (...args: unknown[]) => void;
warn: (...args: unknown[]) => void;
error: (...args: unknown[]) => void;
} = {
info: (...args: unknown[]): void => {
console.info(...args);
},
warn: (...args: unknown[]): void => {
console.warn(...args);
},
error: (...args: unknown[]): void => {
console.error(...args);
},
};
export default logger;

View File

@@ -1,4 +1,4 @@
FROM nginx:1.29.3-alpine
FROM nginx:1.29.5-alpine
ARG GIT_SHA

View File

@@ -15,10 +15,12 @@ import { JSONObject } from "Common/Types/JSON";
import ProbeStatusReport from "Common/Types/Probe/ProbeStatusReport";
import Sleep from "Common/Types/Sleep";
import API from "Common/Utils/API";
import { HasClusterKey } from "Common/Server/EnvironmentConfig";
import {
HasRegisterProbeKey,
RegisterProbeKey,
} from "Common/Server/EnvironmentConfig";
import LocalCache from "Common/Server/Infrastructure/LocalCache";
import logger from "Common/Server/Utils/Logger";
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
import ProxyConfig from "../Utils/ProxyConfig";
export default class Register {
@@ -117,7 +119,7 @@ export default class Register {
}
private static async _registerProbe(): Promise<void> {
if (HasClusterKey) {
if (HasRegisterProbeKey) {
const probeRegistrationUrl: URL = URL.fromString(
PROBE_INGEST_URL.toString(),
).addRoute("/register");
@@ -131,7 +133,7 @@ export default class Register {
probeKey: PROBE_KEY,
probeName: PROBE_NAME,
probeDescription: PROBE_DESCRIPTION,
clusterKey: ClusterKeyAuthorization.getClusterKey(),
registerProbeKey: RegisterProbeKey.toString(),
},
options: {
...ProxyConfig.getRequestProxyAgents(probeRegistrationUrl),
@@ -149,7 +151,7 @@ export default class Register {
} else {
// validate probe.
if (!PROBE_ID) {
logger.error("PROBE_ID or ONEUPTIME_SECRET should be set");
logger.error("PROBE_ID or REGISTER_PROBE_KEY should be set");
return process.exit();
}

View File

@@ -1,16 +1,12 @@
import { PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS } from "../../../Config";
import ProxyConfig from "../../ProxyConfig";
import BadDataException from "Common/Types/Exception/BadDataException";
import ReturnResult from "Common/Types/IsolatedVM/ReturnResult";
import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType";
import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType";
import SyntheticMonitorResponse from "Common/Types/Monitor/SyntheticMonitors/SyntheticMonitorResponse";
import ObjectID from "Common/Types/ObjectID";
import logger from "Common/Server/Utils/Logger";
import VMRunner from "Common/Server/Utils/VM/VMRunner";
import { Browser, BrowserContext, Page, chromium, firefox } from "playwright";
import LocalFile from "Common/Server/Utils/LocalFile";
import os from "os";
import { ChildProcess, fork } from "child_process";
import path from "path";
export interface SyntheticMonitorOptions {
monitorId?: ObjectID | undefined;
@@ -20,24 +16,26 @@ export interface SyntheticMonitorOptions {
retryCountOnError?: number | undefined;
}
interface BrowserLaunchOptions {
executablePath?: string;
proxy?: {
server: string;
username?: string;
password?: string;
bypass?: string;
};
args?: string[];
headless?: boolean;
devtools?: boolean;
timeout?: number;
interface WorkerConfig {
script: string;
browserType: BrowserType;
screenSizeType: ScreenSizeType;
timeout: number;
proxy?:
| {
server: string;
username?: string | undefined;
password?: string | undefined;
}
| undefined;
}
interface BrowserSession {
browser: Browser;
context: BrowserContext;
page: Page;
interface WorkerResult {
logMessages: string[];
scriptError?: string | undefined;
result?: unknown | undefined;
screenshots: Record<string, string>;
executionTimeInMS: number;
}
export default class SyntheticMonitor {
@@ -111,13 +109,72 @@ export default class SyntheticMonitor {
return result;
}
private static getSanitizedEnv(): Record<string, string> {
/*
* Only pass safe environment variables to the worker process.
* Explicitly exclude all secrets (DATABASE_PASSWORD, REDIS_PASSWORD,
* CLICKHOUSE_PASSWORD, ONEUPTIME_SECRET, ENCRYPTION_SECRET, BILLING_PRIVATE_KEY, etc.)
*/
const safeKeys: string[] = [
"PATH",
"HOME",
"NODE_ENV",
"PLAYWRIGHT_BROWSERS_PATH",
"HTTP_PROXY_URL",
"http_proxy",
"HTTPS_PROXY_URL",
"https_proxy",
"NO_PROXY",
"no_proxy",
];
const env: Record<string, string> = {};
for (const key of safeKeys) {
if (process.env[key]) {
env[key] = process.env[key]!;
}
}
return env;
}
private static getProxyConfig(): WorkerConfig["proxy"] | undefined {
if (!ProxyConfig.isProxyConfigured()) {
return undefined;
}
const httpsProxyUrl: string | null = ProxyConfig.getHttpsProxyUrl();
const httpProxyUrl: string | null = ProxyConfig.getHttpProxyUrl();
const proxyUrl: string | null = httpsProxyUrl || httpProxyUrl;
if (!proxyUrl) {
return undefined;
}
const proxyConfig: WorkerConfig["proxy"] = {
server: proxyUrl,
};
try {
const parsedUrl: globalThis.URL = new URL(proxyUrl);
if (parsedUrl.username && parsedUrl.password) {
proxyConfig.username = parsedUrl.username;
proxyConfig.password = parsedUrl.password;
}
} catch (error) {
logger.warn(`Failed to parse proxy URL for authentication: ${error}`);
}
return proxyConfig;
}
private static async executeByBrowserAndScreenSize(options: {
script: string;
browserType: BrowserType;
screenSizeType: ScreenSizeType;
}): Promise<SyntheticMonitorResponse | null> {
if (!options) {
// this should never happen
options = {
script: "",
browserType: BrowserType.Chromium,
@@ -135,385 +192,124 @@ export default class SyntheticMonitor {
screenSizeType: options.screenSizeType,
};
let browserSession: BrowserSession | null = null;
const timeout: number = PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS;
const workerConfig: WorkerConfig = {
script: options.script,
browserType: options.browserType,
screenSizeType: options.screenSizeType,
timeout: timeout,
proxy: this.getProxyConfig(),
};
try {
let result: ReturnResult | null = null;
const startTime: [number, number] = process.hrtime();
browserSession = await SyntheticMonitor.getPageByBrowserType({
browserType: options.browserType,
screenSizeType: options.screenSizeType,
});
if (!browserSession) {
throw new BadDataException(
"Could not create Playwright browser session",
);
}
result = await VMRunner.runCodeInSandbox({
code: options.script,
options: {
timeout: PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS,
args: {},
context: {
browser: browserSession.browser,
page: browserSession.page,
screenSizeType: options.screenSizeType,
browserType: options.browserType,
},
},
});
const endTime: [number, number] = process.hrtime(startTime);
const executionTimeInMS: number = Math.ceil(
(endTime[0] * 1000000000 + endTime[1]) / 1000000,
const workerResult: WorkerResult = await this.forkWorker(
workerConfig,
timeout,
);
scriptResult.executionTimeInMS = executionTimeInMS;
scriptResult.logMessages = result.logMessages;
if (result.returnValue?.screenshots) {
if (!scriptResult.screenshots) {
scriptResult.screenshots = {};
}
for (const screenshotName in result.returnValue.screenshots) {
if (!result.returnValue.screenshots[screenshotName]) {
continue;
}
// check if this is of type Buffer. If it is not, continue.
if (
!(result.returnValue.screenshots[screenshotName] instanceof Buffer)
) {
continue;
}
const screenshotBuffer: Buffer = result.returnValue.screenshots[
screenshotName
] as Buffer;
scriptResult.screenshots[screenshotName] =
screenshotBuffer.toString("base64"); // convert screenshots to base 64
}
}
scriptResult.result = result?.returnValue?.data;
scriptResult.logMessages = workerResult.logMessages;
scriptResult.scriptError = workerResult.scriptError;
scriptResult.result = workerResult.result as typeof scriptResult.result;
scriptResult.screenshots = workerResult.screenshots;
scriptResult.executionTimeInMS = workerResult.executionTimeInMS;
} catch (err: unknown) {
logger.error(err);
scriptResult.scriptError =
(err as Error)?.message || (err as Error).toString();
} finally {
// Always dispose browser session to prevent zombie processes
await SyntheticMonitor.disposeBrowserSession(browserSession);
}
return scriptResult;
}
private static getViewportHeightAndWidth(options: {
screenSizeType: ScreenSizeType;
}): {
height: number;
width: number;
} {
let viewPortHeight: number = 0;
let viewPortWidth: number = 0;
switch (options.screenSizeType) {
case ScreenSizeType.Desktop:
viewPortHeight = 1080;
viewPortWidth = 1920;
break;
case ScreenSizeType.Mobile:
viewPortHeight = 640;
viewPortWidth = 360;
break;
case ScreenSizeType.Tablet:
viewPortHeight = 768;
viewPortWidth = 1024;
break;
default:
viewPortHeight = 1080;
viewPortWidth = 1920;
break;
}
return { height: viewPortHeight, width: viewPortWidth };
}
private static getPlaywrightBrowsersPath(): string {
return (
process.env["PLAYWRIGHT_BROWSERS_PATH"] ||
`${os.homedir()}/.cache/ms-playwright`
);
}
public static async getChromeExecutablePath(): Promise<string> {
const browsersPath: string = this.getPlaywrightBrowsersPath();
const doesDirectoryExist: boolean =
await LocalFile.doesDirectoryExist(browsersPath);
if (!doesDirectoryExist) {
throw new BadDataException("Chrome executable path not found.");
}
// get list of files in the directory
const directories: string[] =
await LocalFile.getListOfDirectories(browsersPath);
if (directories.length === 0) {
throw new BadDataException("Chrome executable path not found.");
}
const chromeInstallationName: string | undefined = directories.find(
(directory: string) => {
return directory.includes("chromium");
},
);
if (!chromeInstallationName) {
throw new BadDataException("Chrome executable path not found.");
}
const chromeExecutableCandidates: Array<string> = [
`${browsersPath}/${chromeInstallationName}/chrome-linux/chrome`,
`${browsersPath}/${chromeInstallationName}/chrome-linux64/chrome`,
`${browsersPath}/${chromeInstallationName}/chrome64/chrome`,
`${browsersPath}/${chromeInstallationName}/chrome/chrome`,
];
for (const executablePath of chromeExecutableCandidates) {
if (await LocalFile.doesFileExist(executablePath)) {
return executablePath;
}
}
throw new BadDataException("Chrome executable path not found.");
}
public static async getFirefoxExecutablePath(): Promise<string> {
const browsersPath: string = this.getPlaywrightBrowsersPath();
const doesDirectoryExist: boolean =
await LocalFile.doesDirectoryExist(browsersPath);
if (!doesDirectoryExist) {
throw new BadDataException("Firefox executable path not found.");
}
// get list of files in the directory
const directories: string[] =
await LocalFile.getListOfDirectories(browsersPath);
if (directories.length === 0) {
throw new BadDataException("Firefox executable path not found.");
}
const firefoxInstallationName: string | undefined = directories.find(
(directory: string) => {
return directory.includes("firefox");
},
);
if (!firefoxInstallationName) {
throw new BadDataException("Firefox executable path not found.");
}
const firefoxExecutableCandidates: Array<string> = [
`${browsersPath}/${firefoxInstallationName}/firefox/firefox`,
`${browsersPath}/${firefoxInstallationName}/firefox-linux64/firefox`,
`${browsersPath}/${firefoxInstallationName}/firefox64/firefox`,
`${browsersPath}/${firefoxInstallationName}/firefox-64/firefox`,
];
for (const executablePath of firefoxExecutableCandidates) {
if (await LocalFile.doesFileExist(executablePath)) {
return executablePath;
}
}
throw new BadDataException("Firefox executable path not found.");
}
private static async getPageByBrowserType(data: {
browserType: BrowserType;
screenSizeType: ScreenSizeType;
}): Promise<BrowserSession> {
const viewport: {
height: number;
width: number;
} = SyntheticMonitor.getViewportHeightAndWidth({
screenSizeType: data.screenSizeType,
});
// Prepare browser launch options with proxy support
const baseOptions: BrowserLaunchOptions = {};
// Configure proxy if available
if (ProxyConfig.isProxyConfigured()) {
const httpsProxyUrl: string | null = ProxyConfig.getHttpsProxyUrl();
const httpProxyUrl: string | null = ProxyConfig.getHttpProxyUrl();
// Prefer HTTPS proxy, fall back to HTTP proxy
const proxyUrl: string | null = httpsProxyUrl || httpProxyUrl;
if (proxyUrl) {
baseOptions.proxy = {
server: proxyUrl,
};
// Extract username and password if present in proxy URL
try {
const parsedUrl: globalThis.URL = new URL(proxyUrl);
if (parsedUrl.username && parsedUrl.password) {
baseOptions.proxy.username = parsedUrl.username;
baseOptions.proxy.password = parsedUrl.password;
}
} catch (error) {
logger.warn(`Failed to parse proxy URL for authentication: ${error}`);
}
logger.debug(
`Synthetic Monitor using proxy: ${proxyUrl} (HTTPS: ${Boolean(httpsProxyUrl)}, HTTP: ${Boolean(httpProxyUrl)})`,
private static async forkWorker(
config: WorkerConfig,
timeout: number,
): Promise<WorkerResult> {
return new Promise(
(
resolve: (value: WorkerResult) => void,
reject: (reason: Error) => void,
) => {
const workerPath: string = path.resolve(
__dirname,
"SyntheticMonitorWorker",
);
}
}
if (data.browserType === BrowserType.Chromium) {
const browser: Browser = await chromium.launch({
executablePath: await this.getChromeExecutablePath(),
...baseOptions,
});
const context: BrowserContext = await browser.newContext({
viewport: {
width: viewport.width,
height: viewport.height,
},
});
const page: Page = await context.newPage();
return {
browser,
context,
page,
};
}
if (data.browserType === BrowserType.Firefox) {
const browser: Browser = await firefox.launch({
executablePath: await this.getFirefoxExecutablePath(),
...baseOptions,
});
let context: BrowserContext | null = null;
try {
context = await browser.newContext({
viewport: {
width: viewport.width,
height: viewport.height,
},
const child: ChildProcess = fork(workerPath, [], {
env: this.getSanitizedEnv(),
timeout: timeout + 30000, // fork-level timeout: script timeout + 30s for browser startup/shutdown
stdio: ["pipe", "pipe", "pipe", "ipc"],
});
const page: Page = await context.newPage();
let resolved: boolean = false;
let stderrOutput: string = "";
return {
browser,
context,
page,
};
} catch (error) {
await SyntheticMonitor.safeCloseBrowserContext(context);
await SyntheticMonitor.safeCloseBrowser(browser);
throw error;
}
}
// Capture child stderr for debugging worker crashes
if (child.stderr) {
child.stderr.on("data", (data: Buffer) => {
stderrOutput += data.toString();
});
}
throw new BadDataException("Invalid Browser Type.");
}
// Explicit kill timer as final safety net
const killTimer: ReturnType<typeof setTimeout> = global.setTimeout(
() => {
if (!resolved) {
resolved = true;
child.kill("SIGKILL");
reject(
new Error("Synthetic monitor worker killed after timeout"),
);
}
},
timeout + 60000, // kill after script timeout + 60s
);
private static async disposeBrowserSession(
session: BrowserSession | null,
): Promise<void> {
if (!session) {
return;
}
child.on("message", (result: WorkerResult) => {
if (!resolved) {
resolved = true;
global.clearTimeout(killTimer);
resolve(result);
}
});
await SyntheticMonitor.safeClosePage(session.page);
await SyntheticMonitor.safeCloseBrowserContexts({
browser: session.browser,
});
await SyntheticMonitor.safeCloseBrowser(session.browser);
}
child.on("error", (err: Error) => {
if (!resolved) {
resolved = true;
global.clearTimeout(killTimer);
reject(err);
}
});
private static async safeClosePage(page?: Page | null): Promise<void> {
if (!page) {
return;
}
child.on("exit", (exitCode: number | null) => {
if (!resolved) {
resolved = true;
global.clearTimeout(killTimer);
try {
if (!page.isClosed()) {
await page.close();
}
} catch (error) {
logger.warn(
`Failed to close Playwright page: ${(error as Error)?.message || error}`,
);
}
}
const stderrInfo: string = stderrOutput.trim()
? `: ${stderrOutput.trim().substring(0, 500)}`
: "";
private static async safeCloseBrowserContext(
context?: BrowserContext | null,
): Promise<void> {
if (!context) {
return;
}
if (exitCode !== 0) {
reject(
new Error(
`Synthetic monitor worker exited with code ${exitCode}${stderrInfo}`,
),
);
} else {
reject(
new Error(
`Synthetic monitor worker exited without sending results${stderrInfo}`,
),
);
}
}
});
try {
await context.close();
} catch (error) {
logger.warn(
`Failed to close Playwright browser context: ${(error as Error)?.message || error}`,
);
}
}
private static async safeCloseBrowser(
browser?: Browser | null,
): Promise<void> {
if (!browser) {
return;
}
try {
if (browser.isConnected()) {
await browser.close();
}
} catch (error) {
logger.warn(
`Failed to close Playwright browser: ${(error as Error)?.message || error}`,
);
}
}
private static async safeCloseBrowserContexts(data: {
browser: Browser;
}): Promise<void> {
if (!data.browser || !data.browser.contexts) {
return;
}
const contexts: Array<BrowserContext> = data.browser.contexts();
for (const context of contexts) {
await SyntheticMonitor.safeCloseBrowserContext(context);
}
// Send config to worker via IPC
child.send(config);
},
);
}
}

View File

@@ -0,0 +1,354 @@
/*
* This script is executed via child_process.fork() with a sanitized environment
* It launches Playwright, runs user code with node:vm (safe because env is stripped),
* and sends results back via IPC.
*/
import BrowserType from "Common/Types/Monitor/SyntheticMonitors/BrowserType";
import ScreenSizeType from "Common/Types/Monitor/SyntheticMonitors/ScreenSizeType";
import vm, { Context } from "node:vm";
import { Browser, BrowserContext, Page, chromium, firefox } from "playwright";
import LocalFile from "Common/Server/Utils/LocalFile";
import os from "os";
interface WorkerConfig {
script: string;
browserType: BrowserType;
screenSizeType: ScreenSizeType;
timeout: number;
proxy?:
| {
server: string;
username?: string | undefined;
password?: string | undefined;
}
| undefined;
}
interface WorkerResult {
logMessages: string[];
scriptError?: string | undefined;
result?: unknown | undefined;
screenshots: Record<string, string>;
executionTimeInMS: number;
}
interface ProxyOptions {
server: string;
username?: string | undefined;
password?: string | undefined;
}
function getViewportHeightAndWidth(screenSizeType: ScreenSizeType): {
height: number;
width: number;
} {
switch (screenSizeType) {
case ScreenSizeType.Desktop:
return { height: 1080, width: 1920 };
case ScreenSizeType.Mobile:
return { height: 640, width: 360 };
case ScreenSizeType.Tablet:
return { height: 768, width: 1024 };
default:
return { height: 1080, width: 1920 };
}
}
function getPlaywrightBrowsersPath(): string {
return (
process.env["PLAYWRIGHT_BROWSERS_PATH"] ||
`${os.homedir()}/.cache/ms-playwright`
);
}
async function getChromeExecutablePath(): Promise<string> {
const browsersPath: string = getPlaywrightBrowsersPath();
const doesDirectoryExist: boolean =
await LocalFile.doesDirectoryExist(browsersPath);
if (!doesDirectoryExist) {
throw new Error("Chrome executable path not found.");
}
const directories: string[] =
await LocalFile.getListOfDirectories(browsersPath);
if (directories.length === 0) {
throw new Error("Chrome executable path not found.");
}
const chromeInstallationName: string | undefined = directories.find(
(directory: string) => {
return directory.includes("chromium");
},
);
if (!chromeInstallationName) {
throw new Error("Chrome executable path not found.");
}
const candidates: Array<string> = [
`${browsersPath}/${chromeInstallationName}/chrome-linux/chrome`,
`${browsersPath}/${chromeInstallationName}/chrome-linux64/chrome`,
`${browsersPath}/${chromeInstallationName}/chrome64/chrome`,
`${browsersPath}/${chromeInstallationName}/chrome/chrome`,
];
for (const executablePath of candidates) {
if (await LocalFile.doesFileExist(executablePath)) {
return executablePath;
}
}
throw new Error("Chrome executable path not found.");
}
async function getFirefoxExecutablePath(): Promise<string> {
const browsersPath: string = getPlaywrightBrowsersPath();
const doesDirectoryExist: boolean =
await LocalFile.doesDirectoryExist(browsersPath);
if (!doesDirectoryExist) {
throw new Error("Firefox executable path not found.");
}
const directories: string[] =
await LocalFile.getListOfDirectories(browsersPath);
if (directories.length === 0) {
throw new Error("Firefox executable path not found.");
}
const firefoxInstallationName: string | undefined = directories.find(
(directory: string) => {
return directory.includes("firefox");
},
);
if (!firefoxInstallationName) {
throw new Error("Firefox executable path not found.");
}
const candidates: Array<string> = [
`${browsersPath}/${firefoxInstallationName}/firefox/firefox`,
`${browsersPath}/${firefoxInstallationName}/firefox-linux64/firefox`,
`${browsersPath}/${firefoxInstallationName}/firefox64/firefox`,
`${browsersPath}/${firefoxInstallationName}/firefox-64/firefox`,
];
for (const executablePath of candidates) {
if (await LocalFile.doesFileExist(executablePath)) {
return executablePath;
}
}
throw new Error("Firefox executable path not found.");
}
async function launchBrowser(
config: WorkerConfig,
): Promise<{ browser: Browser; context: BrowserContext; page: Page }> {
const viewport: { height: number; width: number } = getViewportHeightAndWidth(
config.screenSizeType,
);
let proxyOptions: ProxyOptions | undefined;
if (config.proxy) {
proxyOptions = {
server: config.proxy.server,
};
if (config.proxy.username && config.proxy.password) {
proxyOptions.username = config.proxy.username;
proxyOptions.password = config.proxy.password;
}
}
let browser: Browser;
if (config.browserType === BrowserType.Chromium) {
const launchOptions: Record<string, unknown> = {
executablePath: await getChromeExecutablePath(),
};
if (proxyOptions) {
launchOptions["proxy"] = proxyOptions;
}
browser = await chromium.launch(launchOptions);
} else if (config.browserType === BrowserType.Firefox) {
const launchOptions: Record<string, unknown> = {
executablePath: await getFirefoxExecutablePath(),
};
if (proxyOptions) {
launchOptions["proxy"] = proxyOptions;
}
browser = await firefox.launch(launchOptions);
} else {
throw new Error("Invalid Browser Type.");
}
const context: BrowserContext = await browser.newContext({
viewport: {
width: viewport.width,
height: viewport.height,
},
});
const page: Page = await context.newPage();
return { browser, context, page };
}
async function run(config: WorkerConfig): Promise<WorkerResult> {
const workerResult: WorkerResult = {
logMessages: [],
scriptError: undefined,
result: undefined,
screenshots: {},
executionTimeInMS: 0,
};
let browser: Browser | null = null;
try {
const startTime: [number, number] = process.hrtime();
const session: { browser: Browser; context: BrowserContext; page: Page } =
await launchBrowser(config);
browser = session.browser;
const logMessages: string[] = [];
const sandbox: Context = {
console: {
log: (...args: unknown[]) => {
logMessages.push(
args
.map((v: unknown) => {
return typeof v === "object" ? JSON.stringify(v) : String(v);
})
.join(" "),
);
},
},
browser: session.browser,
page: session.page,
screenSizeType: config.screenSizeType,
browserType: config.browserType,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
};
vm.createContext(sandbox);
const script: string = `(async()=>{
${config.script}
})()`;
const returnVal: unknown = await vm.runInContext(script, sandbox, {
timeout: config.timeout,
});
const endTime: [number, number] = process.hrtime(startTime);
const executionTimeInMS: number = Math.ceil(
(endTime[0] * 1000000000 + endTime[1]) / 1000000,
);
workerResult.executionTimeInMS = executionTimeInMS;
workerResult.logMessages = logMessages;
// Convert screenshots from Buffer to base64
const returnObj: Record<string, unknown> =
returnVal && typeof returnVal === "object"
? (returnVal as Record<string, unknown>)
: {};
if (returnObj["screenshots"]) {
const screenshots: Record<string, unknown> = returnObj[
"screenshots"
] as Record<string, unknown>;
for (const screenshotName in screenshots) {
if (!screenshots[screenshotName]) {
continue;
}
if (!(screenshots[screenshotName] instanceof Buffer)) {
continue;
}
const screenshotBuffer: Buffer = screenshots[screenshotName] as Buffer;
workerResult.screenshots[screenshotName] =
screenshotBuffer.toString("base64");
}
}
workerResult.result = returnObj["data"];
} catch (err: unknown) {
workerResult.scriptError = (err as Error)?.message || String(err);
} finally {
// Close browser
if (browser) {
try {
const contexts: Array<BrowserContext> = browser.contexts();
for (const ctx of contexts) {
try {
await ctx.close();
} catch {
// ignore
}
}
if (browser.isConnected()) {
await browser.close();
}
} catch {
// ignore cleanup errors
}
}
}
return workerResult;
}
// Entry point: receive config via IPC message
process.on("message", (config: WorkerConfig) => {
run(config)
.then((result: WorkerResult) => {
if (process.send) {
/*
* Wait for the IPC message to be flushed before exiting.
* process.send() is async — calling process.exit() immediately
* can kill the process before the message is delivered.
*/
process.send(result, () => {
process.exit(0);
});
} else {
process.exit(0);
}
})
.catch((err: unknown) => {
const errorResult: WorkerResult = {
logMessages: [],
scriptError: (err as Error)?.message || String(err),
result: undefined,
screenshots: {},
executionTimeInMS: 0,
};
if (process.send) {
process.send(errorResult, () => {
process.exit(1);
});
} else {
process.exit(1);
}
});
});

View File

@@ -29,7 +29,7 @@
"https-proxy-agent": "^7.0.5",
"net-snmp": "^3.26.1",
"ping": "^0.4.4",
"playwright": "^1.57.0",
"playwright": "^1.58.0",
"ts-node": "^10.9.1",
"whois-json": "^2.0.4"
},

View File

@@ -1,7 +1,7 @@
import OneUptimeDate from "Common/Types/Date";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import ClusterKeyAuthorization from "Common/Server/Middleware/ClusterKeyAuthorization";
import { RegisterProbeKey } from "Common/Server/EnvironmentConfig";
import ProbeService from "Common/Server/Services/ProbeService";
import Express, {
ExpressRequest,
@@ -19,7 +19,6 @@ const router: ExpressRouter = Express.getRouter();
// Register Global Probe. Custom Probe can be registered via dashboard.
router.post(
"/register",
ClusterKeyAuthorization.isAuthorizedServiceMiddleware,
async (
req: ExpressRequest,
res: ExpressResponse,
@@ -28,6 +27,23 @@ router.post(
try {
const data: JSONObject = req.body;
const registerProbeKey: string | undefined = data[
"registerProbeKey"
] as string;
if (
!registerProbeKey ||
registerProbeKey !== RegisterProbeKey.toString()
) {
return Response.sendErrorResponse(
req,
res,
new BadDataException(
"Invalid or missing registerProbeKey. Please check REGISTER_PROBE_KEY environment variable.",
),
);
}
if (!data["probeKey"]) {
return Response.sendErrorResponse(
req,

View File

@@ -1386,9 +1386,9 @@
}
},
"node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -5737,9 +5737,9 @@
}
},
"ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"requires": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",

View File

@@ -383,45 +383,45 @@
}
},
"../Common/node_modules/@aws-sdk/client-sso": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.980.0.tgz",
"integrity": "sha512-AhNXQaJ46C1I+lQ+6Kj+L24il5K9lqqIanJd8lMszPmP7bLnmX0wTKK0dxywcvrLdij3zhWttjAKEBNgLtS8/A==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.990.0.tgz",
"integrity": "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
"@aws-sdk/middleware-user-agent": "^3.972.5",
"@aws-sdk/middleware-user-agent": "^3.972.10",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/util-endpoints": "3.980.0",
"@aws-sdk/util-endpoints": "3.990.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.8",
"@smithy/config-resolver": "^4.4.6",
"@smithy/core": "^3.22.0",
"@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
"@smithy/middleware-endpoint": "^4.4.12",
"@smithy/middleware-retry": "^4.4.29",
"@smithy/middleware-endpoint": "^4.4.14",
"@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/node-http-handler": "^4.4.8",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
"@smithy/util-defaults-mode-browser": "^4.3.28",
"@smithy/util-defaults-mode-node": "^4.2.31",
"@smithy/util-defaults-mode-browser": "^4.3.30",
"@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -433,9 +433,9 @@
}
},
"../Common/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
"integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz",
"integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -450,20 +450,20 @@
}
},
"../Common/node_modules/@aws-sdk/core": {
"version": "3.973.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.5.tgz",
"integrity": "sha512-IMM7xGfLGW6lMvubsA4j6BHU5FPgGAxoQ/NA63KqNLMwTS+PeMBcx8DPHL12Vg6yqOZnqok9Mu4H2BdQyq7gSA==",
"version": "3.973.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.10.tgz",
"integrity": "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/xml-builder": "^3.972.2",
"@smithy/core": "^3.22.0",
"@aws-sdk/xml-builder": "^3.972.4",
"@smithy/core": "^3.23.0",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/signature-v4": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-middleware": "^4.2.8",
@@ -475,13 +475,13 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-env": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.3.tgz",
"integrity": "sha512-OBYNY4xQPq7Rx+oOhtyuyO0AQvdJSpXRg7JuPNBJH4a1XXIzJQl4UHQTPKZKwfJXmYLpv4+OkcFen4LYmDPd3g==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.8.tgz",
"integrity": "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/types": "^4.12.0",
@@ -492,21 +492,21 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-http": {
"version": "3.972.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.5.tgz",
"integrity": "sha512-GpvBgEmSZPvlDekd26Zi+XsI27Qz7y0utUx0g2fSTSiDzhnd1FSa1owuodxR0BcUKNL7U2cOVhhDxgZ4iSoPVg==",
"version": "3.972.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.10.tgz",
"integrity": "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/node-http-handler": "^4.4.8",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/util-stream": "^4.5.10",
"@smithy/util-stream": "^4.5.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -514,20 +514,20 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.3.tgz",
"integrity": "sha512-rMQAIxstP7cLgYfsRGrGOlpyMl0l8JL2mcke3dsIPLWke05zKOFyR7yoJzWCsI/QiIxjRbxpvPiAeKEA6CoYkg==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.8.tgz",
"integrity": "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/credential-provider-env": "^3.972.3",
"@aws-sdk/credential-provider-http": "^3.972.5",
"@aws-sdk/credential-provider-login": "^3.972.3",
"@aws-sdk/credential-provider-process": "^3.972.3",
"@aws-sdk/credential-provider-sso": "^3.972.3",
"@aws-sdk/credential-provider-web-identity": "^3.972.3",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/credential-provider-env": "^3.972.8",
"@aws-sdk/credential-provider-http": "^3.972.10",
"@aws-sdk/credential-provider-login": "^3.972.8",
"@aws-sdk/credential-provider-process": "^3.972.8",
"@aws-sdk/credential-provider-sso": "^3.972.8",
"@aws-sdk/credential-provider-web-identity": "^3.972.8",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/property-provider": "^4.2.8",
@@ -540,14 +540,14 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-login": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.3.tgz",
"integrity": "sha512-Gc3O91iVvA47kp2CLIXOwuo5ffo1cIpmmyIewcYjAcvurdFHQ8YdcBe1KHidnbbBO4/ZtywGBACsAX5vr3UdoA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.8.tgz",
"integrity": "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
@@ -560,18 +560,18 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-node": {
"version": "3.972.4",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.4.tgz",
"integrity": "sha512-UwerdzosMSY7V5oIZm3NsMDZPv2aSVzSkZxYxIOWHBeKTZlUqW7XpHtJMZ4PZpJ+HMRhgP+MDGQx4THndgqJfQ==",
"version": "3.972.9",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.9.tgz",
"integrity": "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/credential-provider-env": "^3.972.3",
"@aws-sdk/credential-provider-http": "^3.972.5",
"@aws-sdk/credential-provider-ini": "^3.972.3",
"@aws-sdk/credential-provider-process": "^3.972.3",
"@aws-sdk/credential-provider-sso": "^3.972.3",
"@aws-sdk/credential-provider-web-identity": "^3.972.3",
"@aws-sdk/credential-provider-env": "^3.972.8",
"@aws-sdk/credential-provider-http": "^3.972.10",
"@aws-sdk/credential-provider-ini": "^3.972.8",
"@aws-sdk/credential-provider-process": "^3.972.8",
"@aws-sdk/credential-provider-sso": "^3.972.8",
"@aws-sdk/credential-provider-web-identity": "^3.972.8",
"@aws-sdk/types": "^3.973.1",
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/property-provider": "^4.2.8",
@@ -584,13 +584,13 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-process": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.3.tgz",
"integrity": "sha512-xkSY7zjRqeVc6TXK2xr3z1bTLm0wD8cj3lAkproRGaO4Ku7dPlKy843YKnHrUOUzOnMezdZ4xtmFc0eKIDTo2w==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.8.tgz",
"integrity": "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -602,15 +602,15 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.3.tgz",
"integrity": "sha512-8Ww3F5Ngk8dZ6JPL/V5LhCU1BwMfQd3tLdoEuzaewX8FdnT633tPr+KTHySz9FK7fFPcz5qG3R5edVEhWQD4AA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.8.tgz",
"integrity": "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/client-sso": "3.980.0",
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/token-providers": "3.980.0",
"@aws-sdk/client-sso": "3.990.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/token-providers": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -622,14 +622,14 @@
}
},
"../Common/node_modules/@aws-sdk/credential-provider-web-identity": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.3.tgz",
"integrity": "sha512-62VufdcH5rRfiRKZRcf1wVbbt/1jAntMj1+J0qAd+r5pQRg2t0/P9/Rz16B1o5/0Se9lVL506LRjrhIJAhYBfA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.8.tgz",
"integrity": "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -689,16 +689,16 @@
}
},
"../Common/node_modules/@aws-sdk/middleware-user-agent": {
"version": "3.972.5",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.5.tgz",
"integrity": "sha512-TVZQ6PWPwQbahUI8V+Er+gS41ctIawcI/uMNmQtQ7RMcg3JYn6gyKAFKUb3HFYx2OjYlx1u11sETSwwEUxVHTg==",
"version": "3.972.10",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.10.tgz",
"integrity": "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/util-endpoints": "3.980.0",
"@smithy/core": "^3.22.0",
"@aws-sdk/util-endpoints": "3.990.0",
"@smithy/core": "^3.23.0",
"@smithy/protocol-http": "^5.3.8",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
@@ -708,9 +708,9 @@
}
},
"../Common/node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/util-endpoints": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
"integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz",
"integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -725,45 +725,45 @@
}
},
"../Common/node_modules/@aws-sdk/nested-clients": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.980.0.tgz",
"integrity": "sha512-/dONY5xc5/CCKzOqHZCTidtAR4lJXWkGefXvTRKdSKMGaYbbKsxDckisd6GfnvPSLxWtvQzwgRGRutMRoYUApQ==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.990.0.tgz",
"integrity": "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/middleware-host-header": "^3.972.3",
"@aws-sdk/middleware-logger": "^3.972.3",
"@aws-sdk/middleware-recursion-detection": "^3.972.3",
"@aws-sdk/middleware-user-agent": "^3.972.5",
"@aws-sdk/middleware-user-agent": "^3.972.10",
"@aws-sdk/region-config-resolver": "^3.972.3",
"@aws-sdk/types": "^3.973.1",
"@aws-sdk/util-endpoints": "3.980.0",
"@aws-sdk/util-endpoints": "3.990.0",
"@aws-sdk/util-user-agent-browser": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.3",
"@aws-sdk/util-user-agent-node": "^3.972.8",
"@smithy/config-resolver": "^4.4.6",
"@smithy/core": "^3.22.0",
"@smithy/core": "^3.23.0",
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/hash-node": "^4.2.8",
"@smithy/invalid-dependency": "^4.2.8",
"@smithy/middleware-content-length": "^4.2.8",
"@smithy/middleware-endpoint": "^4.4.12",
"@smithy/middleware-retry": "^4.4.29",
"@smithy/middleware-endpoint": "^4.4.14",
"@smithy/middleware-retry": "^4.4.31",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/node-http-handler": "^4.4.8",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/protocol-http": "^5.3.8",
"@smithy/smithy-client": "^4.11.1",
"@smithy/smithy-client": "^4.11.3",
"@smithy/types": "^4.12.0",
"@smithy/url-parser": "^4.2.8",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-body-length-node": "^4.2.1",
"@smithy/util-defaults-mode-browser": "^4.3.28",
"@smithy/util-defaults-mode-node": "^4.2.31",
"@smithy/util-defaults-mode-browser": "^4.3.30",
"@smithy/util-defaults-mode-node": "^4.2.33",
"@smithy/util-endpoints": "^3.2.8",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -775,9 +775,9 @@
}
},
"../Common/node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz",
"integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.990.0.tgz",
"integrity": "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -809,14 +809,14 @@
}
},
"../Common/node_modules/@aws-sdk/token-providers": {
"version": "3.980.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.980.0.tgz",
"integrity": "sha512-1nFileg1wAgDmieRoj9dOawgr2hhlh7xdvcH57b1NnqfPaVlcqVJyPc6k3TLDUFPY69eEwNxdGue/0wIz58vjA==",
"version": "3.990.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.990.0.tgz",
"integrity": "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/core": "^3.973.5",
"@aws-sdk/nested-clients": "3.980.0",
"@aws-sdk/core": "^3.973.10",
"@aws-sdk/nested-clients": "3.990.0",
"@aws-sdk/types": "^3.973.1",
"@smithy/property-provider": "^4.2.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -885,13 +885,13 @@
}
},
"../Common/node_modules/@aws-sdk/util-user-agent-node": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.3.tgz",
"integrity": "sha512-gqG+02/lXQtO0j3US6EVnxtwwoXQC5l2qkhLCrqUrqdtcQxV7FDMbm9wLjKqoronSHyELGTjbFKK/xV5q1bZNA==",
"version": "3.972.8",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.8.tgz",
"integrity": "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/middleware-user-agent": "^3.972.5",
"@aws-sdk/middleware-user-agent": "^3.972.10",
"@aws-sdk/types": "^3.973.1",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/types": "^4.12.0",
@@ -910,9 +910,9 @@
}
},
"../Common/node_modules/@aws-sdk/xml-builder": {
"version": "3.972.3",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.3.tgz",
"integrity": "sha512-bCk63RsBNCWW4tt5atv5Sbrh+3J3e8YzgyF6aZb1JeXcdzG4k5SlPLeTMFOIXFuuFHIwgphUhn4i3uS/q49eww==",
"version": "3.972.4",
"resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.4.tgz",
"integrity": "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -1684,54 +1684,42 @@
}
},
"../Common/node_modules/@chevrotain/cst-dts-gen": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz",
"integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.1.tgz",
"integrity": "sha512-fRHyv6/f542qQqiRGalrfJl/evD39mAvbJLCekPazhiextEatq1Jx1K/i9gSd5NNO0ds03ek0Cbo/4uVKmOBcw==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/gast": "11.0.3",
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/gast": "11.1.1",
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"../Common/node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"../Common/node_modules/@chevrotain/gast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz",
"integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.1.tgz",
"integrity": "sha512-Ko/5vPEYy1vn5CbCjjvnSO4U7GgxyGm+dfUZZJIWTlQFkXkyym0jFYrWEU10hyCjrA7rQtiHtBr0EaZqvHFZvg==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/types": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/types": "11.1.1",
"lodash-es": "4.17.23"
}
},
"../Common/node_modules/@chevrotain/gast/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"../Common/node_modules/@chevrotain/regexp-to-ast": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz",
"integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.1.tgz",
"integrity": "sha512-ctRw1OKSXkOrR8VTvOxrQ5USEc4sNrfwXHa1NuTcR7wre4YbjPcKw+82C2uylg/TEwFRgwLmbhlln4qkmDyteg==",
"license": "Apache-2.0"
},
"../Common/node_modules/@chevrotain/types": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz",
"integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.1.tgz",
"integrity": "sha512-wb2ToxG8LkgPYnKe9FH8oGn3TMCBdnwiuNC5l5y+CtlaVRbCytU0kbVsk6CGrqTL4ZN4ksJa0TXOYbxpbthtqw==",
"license": "Apache-2.0"
},
"../Common/node_modules/@chevrotain/utils": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz",
"integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.1.tgz",
"integrity": "sha512-71eTYMzYXYSFPrbg/ZwftSaSDld7UYlS8OQa3lNnn9jzNtpFbaReRRyghzqS7rI3CDaorqpPJJcXGHK+FE1TVQ==",
"license": "Apache-2.0"
},
"../Common/node_modules/@clickhouse/client": {
@@ -3092,12 +3080,12 @@
"license": "MIT"
},
"../Common/node_modules/@mermaid-js/parser": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz",
"integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.0.tgz",
"integrity": "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw==",
"license": "MIT",
"dependencies": {
"langium": "3.3.1"
"langium": "^4.0.0"
}
},
"../Common/node_modules/@monaco-editor/loader": {
@@ -5137,9 +5125,9 @@
}
},
"../Common/node_modules/@smithy/core": {
"version": "3.22.1",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.22.1.tgz",
"integrity": "sha512-x3ie6Crr58MWrm4viHqqy2Du2rHYZjwu8BekasrQx4ca+Y24dzVAwq3yErdqIbc2G3I0kLQA13PQ+/rde+u65g==",
"version": "3.23.2",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.2.tgz",
"integrity": "sha512-HaaH4VbGie4t0+9nY3tNBRSxVTr96wzIqexUa6C2qx3MPePAuz7lIxPxYtt1Wc//SPfJLNoZJzfdt0B6ksj2jA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5149,7 +5137,7 @@
"@smithy/util-base64": "^4.3.0",
"@smithy/util-body-length-browser": "^4.2.0",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-stream": "^4.5.11",
"@smithy/util-stream": "^4.5.12",
"@smithy/util-utf8": "^4.2.0",
"@smithy/uuid": "^1.1.0",
"tslib": "^2.6.2"
@@ -5251,13 +5239,13 @@
}
},
"../Common/node_modules/@smithy/middleware-endpoint": {
"version": "4.4.13",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.13.tgz",
"integrity": "sha512-x6vn0PjYmGdNuKh/juUJJewZh7MoQ46jYaJ2mvekF4EesMuFfrl4LaW/k97Zjf8PTCPQmPgMvwewg7eNoH9n5w==",
"version": "4.4.16",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.16.tgz",
"integrity": "sha512-L5GICFCSsNhbJ5JSKeWFGFy16Q2OhoBizb3X2DrxaJwXSEujVvjG9Jt386dpQn2t7jINglQl0b4K/Su69BdbMA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.22.1",
"@smithy/core": "^3.23.2",
"@smithy/middleware-serde": "^4.2.9",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/shared-ini-file-loader": "^4.4.3",
@@ -5271,16 +5259,16 @@
}
},
"../Common/node_modules/@smithy/middleware-retry": {
"version": "4.4.30",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz",
"integrity": "sha512-CBGyFvN0f8hlnqKH/jckRDz78Snrp345+PVk8Ux7pnkUCW97Iinse59lY78hBt04h1GZ6hjBN94BRwZy1xC8Bg==",
"version": "4.4.33",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.33.tgz",
"integrity": "sha512-jLqZOdJhtIL4lnA9hXnAG6GgnJlo1sD3FqsTxm9wSfjviqgWesY/TMBVnT84yr4O0Vfe0jWoXlfFbzsBVph3WA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/node-config-provider": "^4.3.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/service-error-classification": "^4.2.8",
"@smithy/smithy-client": "^4.11.2",
"@smithy/smithy-client": "^4.11.5",
"@smithy/types": "^4.12.0",
"@smithy/util-middleware": "^4.2.8",
"@smithy/util-retry": "^4.2.8",
@@ -5337,9 +5325,9 @@
}
},
"../Common/node_modules/@smithy/node-http-handler": {
"version": "4.4.9",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.9.tgz",
"integrity": "sha512-KX5Wml5mF+luxm1szW4QDz32e3NObgJ4Fyw+irhph4I/2geXwUy4jkIMUs5ZPGflRBeR6BUkC2wqIab4Llgm3w==",
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.10.tgz",
"integrity": "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5458,18 +5446,18 @@
}
},
"../Common/node_modules/@smithy/smithy-client": {
"version": "4.11.2",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.2.tgz",
"integrity": "sha512-SCkGmFak/xC1n7hKRsUr6wOnBTJ3L22Qd4e8H1fQIuKTAjntwgU8lrdMe7uHdiT2mJAOWA/60qaW9tiMu69n1A==",
"version": "4.11.5",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.5.tgz",
"integrity": "sha512-xixwBRqoeP2IUgcAl3U9dvJXc+qJum4lzo3maaJxifsZxKUYLfVfCXvhT4/jD01sRrHg5zjd1cw2Zmjr4/SuKQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/core": "^3.22.1",
"@smithy/middleware-endpoint": "^4.4.13",
"@smithy/core": "^3.23.2",
"@smithy/middleware-endpoint": "^4.4.16",
"@smithy/middleware-stack": "^4.2.8",
"@smithy/protocol-http": "^5.3.8",
"@smithy/types": "^4.12.0",
"@smithy/util-stream": "^4.5.11",
"@smithy/util-stream": "^4.5.12",
"tslib": "^2.6.2"
},
"engines": {
@@ -5573,14 +5561,14 @@
}
},
"../Common/node_modules/@smithy/util-defaults-mode-browser": {
"version": "4.3.29",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.29.tgz",
"integrity": "sha512-nIGy3DNRmOjaYaaKcQDzmWsro9uxlaqUOhZDHQed9MW/GmkBZPtnU70Pu1+GT9IBmUXwRdDuiyaeiy9Xtpn3+Q==",
"version": "4.3.32",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.32.tgz",
"integrity": "sha512-092sjYfFMQ/iaPH798LY/OJFBcYu0sSK34Oy9vdixhsU36zlZu8OcYjF3TD4e2ARupyK7xaxPXl+T0VIJTEkkg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/property-provider": "^4.2.8",
"@smithy/smithy-client": "^4.11.2",
"@smithy/smithy-client": "^4.11.5",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
},
@@ -5589,9 +5577,9 @@
}
},
"../Common/node_modules/@smithy/util-defaults-mode-node": {
"version": "4.2.32",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.32.tgz",
"integrity": "sha512-7dtFff6pu5fsjqrVve0YMhrnzJtccCWDacNKOkiZjJ++fmjGExmmSu341x+WU6Oc1IccL7lDuaUj7SfrHpWc5Q==",
"version": "4.2.35",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.35.tgz",
"integrity": "sha512-miz/ggz87M8VuM29y7jJZMYkn7+IErM5p5UgKIf8OtqVs/h2bXr1Bt3uTsREsI/4nK8a0PQERbAPsVPVNIsG7Q==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -5599,7 +5587,7 @@
"@smithy/credential-provider-imds": "^4.2.8",
"@smithy/node-config-provider": "^4.3.8",
"@smithy/property-provider": "^4.2.8",
"@smithy/smithy-client": "^4.11.2",
"@smithy/smithy-client": "^4.11.5",
"@smithy/types": "^4.12.0",
"tslib": "^2.6.2"
},
@@ -5665,14 +5653,14 @@
}
},
"../Common/node_modules/@smithy/util-stream": {
"version": "4.5.11",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.11.tgz",
"integrity": "sha512-lKmZ0S/3Qj2OF5H1+VzvDLb6kRxGzZHq6f3rAsoSu5cTLGsn3v3VQBA8czkNNXlLjoFEtVu3OQT2jEeOtOE2CA==",
"version": "4.5.12",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.12.tgz",
"integrity": "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@smithy/fetch-http-handler": "^5.3.9",
"@smithy/node-http-handler": "^4.4.9",
"@smithy/node-http-handler": "^4.4.10",
"@smithy/types": "^4.12.0",
"@smithy/util-base64": "^4.3.0",
"@smithy/util-buffer-from": "^4.2.0",
@@ -8107,17 +8095,17 @@
}
},
"../Common/node_modules/chevrotain": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz",
"integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.1.tgz",
"integrity": "sha512-f0yv5CPKaFxfsPTBzX7vGuim4oIC1/gcS7LUGdBSwl2dU6+FON6LVUksdOo1qJjoUvXNn45urgh8C+0a24pACQ==",
"license": "Apache-2.0",
"dependencies": {
"@chevrotain/cst-dts-gen": "11.0.3",
"@chevrotain/gast": "11.0.3",
"@chevrotain/regexp-to-ast": "11.0.3",
"@chevrotain/types": "11.0.3",
"@chevrotain/utils": "11.0.3",
"lodash-es": "4.17.21"
"@chevrotain/cst-dts-gen": "11.1.1",
"@chevrotain/gast": "11.1.1",
"@chevrotain/regexp-to-ast": "11.1.1",
"@chevrotain/types": "11.1.1",
"@chevrotain/utils": "11.1.1",
"lodash-es": "4.17.23"
}
},
"../Common/node_modules/chevrotain-allstar": {
@@ -8132,12 +8120,6 @@
"chevrotain": "^11.0.0"
}
},
"../Common/node_modules/chevrotain/node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"../Common/node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -13618,19 +13600,20 @@
}
},
"../Common/node_modules/langium": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz",
"integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==",
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz",
"integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==",
"license": "MIT",
"dependencies": {
"chevrotain": "~11.0.3",
"chevrotain-allstar": "~0.3.0",
"chevrotain": "~11.1.1",
"chevrotain-allstar": "~0.3.1",
"vscode-languageserver": "~9.0.1",
"vscode-languageserver-textdocument": "~1.0.11",
"vscode-uri": "~3.0.8"
"vscode-uri": "~3.1.0"
},
"engines": {
"node": ">=16.0.0"
"node": ">=20.10.0",
"npm": ">=10.2.3"
}
},
"../Common/node_modules/layout-base": {
@@ -14428,14 +14411,14 @@
}
},
"../Common/node_modules/mermaid": {
"version": "11.12.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz",
"integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==",
"version": "11.12.3",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.3.tgz",
"integrity": "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ==",
"license": "MIT",
"dependencies": {
"@braintree/sanitize-url": "^7.1.1",
"@iconify/utils": "^3.0.1",
"@mermaid-js/parser": "^0.6.3",
"@mermaid-js/parser": "^1.0.0",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.3",
"cytoscape-cose-bilkent": "^4.1.0",
@@ -14447,7 +14430,7 @@
"dompurify": "^3.2.5",
"katex": "^0.16.22",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
"lodash-es": "^4.17.23",
"marked": "^16.2.1",
"roughjs": "^4.6.6",
"stylis": "^4.3.6",
@@ -20201,9 +20184,9 @@
"license": "MIT"
},
"../Common/node_modules/vscode-uri": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz",
"integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
"integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
"license": "MIT"
},
"../Common/node_modules/w3c-xmlserializer": {

View File

@@ -22,6 +22,7 @@ CAPTCHA_SECRET_KEY=
# Secrets - PLEASE CHANGE THESE. Please change these to something random. All of these can be different values.
ONEUPTIME_SECRET=please-change-this-to-random-value
REGISTER_PROBE_KEY=please-change-this-to-random-value
DATABASE_PASSWORD=please-change-this-to-random-value
CLICKHOUSE_PASSWORD=please-change-this-to-random-value
REDIS_PASSWORD=please-change-this-to-random-value
@@ -297,6 +298,14 @@ VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:support@oneuptime.com
# Expo access token for sending mobile push notifications directly via Expo SDK.
# If not set, push notifications are relayed through the push notification relay URL below.
EXPO_ACCESS_TOKEN=
# Push notification relay URL for self-hosted instances without Expo credentials.
# Self-hosted servers relay push notifications through this gateway.
PUSH_NOTIFICATION_RELAY_URL=https://oneuptime.com/api/notification/push-relay/send
# LLM Environment Variables
# Hugging Face Token for LLM Server to downlod models from Hugging Face

View File

@@ -82,6 +82,9 @@ x-common-runtime-variables: &common-runtime-variables
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY}
EXPO_ACCESS_TOKEN: ${EXPO_ACCESS_TOKEN}
PUSH_NOTIFICATION_RELAY_URL: ${PUSH_NOTIFICATION_RELAY_URL}
DATABASE_PORT: ${DATABASE_PORT}
DATABASE_USERNAME: ${DATABASE_USERNAME}
DATABASE_PASSWORD: ${DATABASE_PASSWORD}
@@ -392,21 +395,25 @@ services:
options:
max-size: "1000m"
probe-1:
probe-1:
restart: always
network_mode: host
environment:
<<: *common-runtime-variables
ONEUPTIME_URL: ${GLOBAL_PROBE_1_ONEUPTIME_URL}
REGISTER_PROBE_KEY: ${REGISTER_PROBE_KEY}
PROBE_NAME: ${GLOBAL_PROBE_1_NAME}
PROBE_DESCRIPTION: ${GLOBAL_PROBE_1_DESCRIPTION}
PROBE_MONITORING_WORKERS: ${GLOBAL_PROBE_1_MONITORING_WORKERS}
PROBE_KEY: ${GLOBAL_PROBE_1_KEY}
PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_1_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS}
PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_1_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS}
ONEUPTIME_URL: ${GLOBAL_PROBE_1_ONEUPTIME_URL}
PROBE_MONITOR_FETCH_LIMIT: ${GLOBAL_PROBE_1_MONITOR_FETCH_LIMIT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE}
PORT: ${GLOBAL_PROBE_1_PORT}
NODE_ENV: ${ENVIRONMENT}
LOG_LEVEL: ${LOG_LEVEL}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE}
OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT: ${OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT}
OPENTELEMETRY_EXPORTER_OTLP_HEADERS: ${OPENTELEMETRY_EXPORTER_OTLP_HEADERS}
logging:
driver: "local"
options:
@@ -416,17 +423,21 @@ services:
restart: always
network_mode: host
environment:
<<: *common-runtime-variables
ONEUPTIME_URL: ${GLOBAL_PROBE_2_ONEUPTIME_URL}
REGISTER_PROBE_KEY: ${REGISTER_PROBE_KEY}
PROBE_NAME: ${GLOBAL_PROBE_2_NAME}
PROBE_DESCRIPTION: ${GLOBAL_PROBE_2_DESCRIPTION}
PROBE_MONITORING_WORKERS: ${GLOBAL_PROBE_2_MONITORING_WORKERS}
PROBE_KEY: ${GLOBAL_PROBE_2_KEY}
ONEUPTIME_URL: ${GLOBAL_PROBE_2_ONEUPTIME_URL}
PROBE_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_2_SYNTHETIC_MONITOR_SCRIPT_TIMEOUT_IN_MS}
PROBE_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS: ${GLOBAL_PROBE_2_CUSTOM_CODE_MONITOR_SCRIPT_TIMEOUT_IN_MS}
PROBE_MONITOR_FETCH_LIMIT: ${GLOBAL_PROBE_2_MONITOR_FETCH_LIMIT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE}
PORT: ${GLOBAL_PROBE_2_PORT}
NODE_ENV: ${ENVIRONMENT}
LOG_LEVEL: ${LOG_LEVEL}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE}
OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT: ${OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT}
OPENTELEMETRY_EXPORTER_OTLP_HEADERS: ${OPENTELEMETRY_EXPORTER_OTLP_HEADERS}
logging:
driver: "local"
options:
@@ -504,6 +515,7 @@ services:
PORT: ${PROBE_INGEST_PORT}
DISABLE_TELEMETRY: ${DISABLE_TELEMETRY_FOR_PROBE_INGEST}
PROBE_INGEST_CONCURRENCY: ${PROBE_INGEST_CONCURRENCY}
REGISTER_PROBE_KEY: ${REGISTER_PROBE_KEY}
logging:
driver: "local"
options:

424
package-lock.json generated
View File

@@ -568,9 +568,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"license": "MIT",
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
@@ -586,21 +586,21 @@
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.12.1",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
"integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"license": "MIT",
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/config-array": {
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^2.1.6",
"@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
"minimatch": "^3.1.2"
},
@@ -609,18 +609,21 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.17.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/core": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
@@ -630,9 +633,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
@@ -641,7 +644,7 @@
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"js-yaml": "^4.1.1",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
@@ -665,9 +668,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz",
"integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==",
"version": "9.39.2",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -677,21 +680,21 @@
}
},
"node_modules/@eslint/object-schema": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
"version": "2.1.7",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.15.2",
"@eslint/core": "^0.17.0",
"levn": "^0.4.1"
},
"engines": {
@@ -1348,7 +1351,7 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"devOptional": true,
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -1361,7 +1364,7 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">= 8"
}
@@ -1370,7 +1373,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"devOptional": true,
"dev": true,
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -1390,15 +1393,15 @@
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"dev": true,
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@selderee/plugin-htmlparser2": {
@@ -1594,21 +1597,20 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz",
"integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
"integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.44.1",
"@typescript-eslint/type-utils": "8.44.1",
"@typescript-eslint/utils": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.56.0",
"@typescript-eslint/type-utils": "8.56.0",
"@typescript-eslint/utils": "8.56.0",
"@typescript-eslint/visitor-keys": "8.56.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
"ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1618,8 +1620,8 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.44.1",
"eslint": "^8.57.0 || ^9.0.0",
"@typescript-eslint/parser": "^8.56.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
@@ -1634,17 +1636,17 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz",
"integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
"integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.44.1",
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1",
"debug": "^4.3.4"
"@typescript-eslint/scope-manager": "8.56.0",
"@typescript-eslint/types": "8.56.0",
"@typescript-eslint/typescript-estree": "8.56.0",
"@typescript-eslint/visitor-keys": "8.56.0",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1654,20 +1656,20 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz",
"integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
"integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.44.1",
"@typescript-eslint/types": "^8.44.1",
"debug": "^4.3.4"
"@typescript-eslint/tsconfig-utils": "^8.56.0",
"@typescript-eslint/types": "^8.56.0",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1681,14 +1683,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz",
"integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
"integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1"
"@typescript-eslint/types": "8.56.0",
"@typescript-eslint/visitor-keys": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1699,9 +1701,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz",
"integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
"integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
"devOptional": true,
"license": "MIT",
"engines": {
@@ -1716,17 +1718,17 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz",
"integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz",
"integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1",
"@typescript-eslint/utils": "8.44.1",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
"@typescript-eslint/types": "8.56.0",
"@typescript-eslint/typescript-estree": "8.56.0",
"@typescript-eslint/utils": "8.56.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1736,14 +1738,14 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz",
"integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
"integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
"devOptional": true,
"license": "MIT",
"engines": {
@@ -1755,22 +1757,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz",
"integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
"integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.44.1",
"@typescript-eslint/tsconfig-utils": "8.44.1",
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/visitor-keys": "8.44.1",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^2.1.0"
"@typescript-eslint/project-service": "8.56.0",
"@typescript-eslint/tsconfig-utils": "8.56.0",
"@typescript-eslint/types": "8.56.0",
"@typescript-eslint/visitor-keys": "8.56.0",
"debug": "^4.4.3",
"minimatch": "^9.0.5",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.4.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1810,9 +1811,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"devOptional": true,
"license": "ISC",
"bin": {
@@ -1823,16 +1824,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz",
"integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz",
"integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.44.1",
"@typescript-eslint/types": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1"
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.56.0",
"@typescript-eslint/types": "8.56.0",
"@typescript-eslint/typescript-estree": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1842,19 +1843,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz",
"integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
"integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.44.1",
"eslint-visitor-keys": "^4.2.1"
"@typescript-eslint/types": "8.56.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1865,13 +1866,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
"integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -3566,24 +3567,23 @@
}
},
"node_modules/eslint": {
"version": "9.36.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz",
"integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==",
"version": "9.39.2",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/config-array": "^0.21.1",
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.36.0",
"@eslint/plugin-kit": "^0.3.5",
"@eslint/js": "9.39.2",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
@@ -3626,10 +3626,11 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
"integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz",
"integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -3638,13 +3639,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz",
"integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==",
"version": "5.5.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
"integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.8.6"
"prettier-linter-helpers": "^1.0.1",
"synckit": "^0.11.12"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -3655,7 +3657,7 @@
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": "*",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
@@ -3727,13 +3729,13 @@
}
},
"node_modules/eslint-plugin-unused-imports": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz",
"integrity": "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.4.1.tgz",
"integrity": "sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==",
"license": "MIT",
"peerDependencies": {
"@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
"eslint": "^9.0.0 || ^8.0.0"
"eslint": "^10.0.0 || ^9.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@typescript-eslint/eslint-plugin": {
@@ -3931,13 +3933,14 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -3954,7 +3957,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"devOptional": true,
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
@@ -3976,7 +3979,7 @@
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"devOptional": true,
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
}
@@ -4397,12 +4400,6 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"devOptional": true
},
"node_modules/has-bigints": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
@@ -5887,18 +5884,6 @@
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/jest-snapshot/node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/jest-snapshot/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
@@ -5911,21 +5896,6 @@
"node": ">=10"
}
},
"node_modules/jest-snapshot/node_modules/synckit": {
"version": "0.11.11",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz",
"integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==",
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/jest-util": {
"version": "30.2.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz",
@@ -6539,7 +6509,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"devOptional": true,
"dev": true,
"engines": {
"node": ">= 8"
}
@@ -7191,10 +7161,11 @@
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
"integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
@@ -7287,7 +7258,7 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"devOptional": true,
"dev": true,
"funding": [
{
"type": "github",
@@ -7453,7 +7424,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"devOptional": true,
"dev": true,
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@@ -7469,7 +7440,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"devOptional": true,
"dev": true,
"funding": [
{
"type": "github",
@@ -8142,19 +8113,18 @@
}
},
"node_modules/synckit": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
"integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==",
"dev": true,
"version": "0.11.12",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
"integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
"url": "https://opencollective.com/synckit"
}
},
"node_modules/syntax-error": {
@@ -8207,6 +8177,54 @@
"integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
"dev": true
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tlds": {
"version": "1.261.0",
"resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz",
@@ -8261,9 +8279,9 @@
}
},
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
"integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"devOptional": true,
"license": "MIT",
"engines": {
@@ -8620,16 +8638,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.44.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz",
"integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==",
"version": "8.56.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz",
"integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.44.1",
"@typescript-eslint/parser": "8.44.1",
"@typescript-eslint/typescript-estree": "8.44.1",
"@typescript-eslint/utils": "8.44.1"
"@typescript-eslint/eslint-plugin": "8.56.0",
"@typescript-eslint/parser": "8.56.0",
"@typescript-eslint/typescript-estree": "8.56.0",
"@typescript-eslint/utils": "8.56.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -8639,7 +8657,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},