Merge branch 'master' into release
108
App/FeatureSet/Notification/API/PushRelay.ts
Normal 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;
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
405
Dashboard/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 44 KiB |
18
MobileApp/eas.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 3.0.0"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
29
MobileApp/google-services.json
Normal 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"
|
||||
}
|
||||
35
MobileApp/package-lock.json
generated
@@ -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"
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ function AppContent(): React.JSX.Element {
|
||||
borderRadius: 999,
|
||||
}}
|
||||
/>
|
||||
<StatusBar style={theme.isDark ? "light" : "dark"} />
|
||||
<StatusBar style="light" />
|
||||
<RootNavigator />
|
||||
<OfflineBanner />
|
||||
</View>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)",
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
19
MobileApp/src/utils/logger.ts
Normal 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;
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM nginx:1.29.3-alpine
|
||||
FROM nginx:1.29.5-alpine
|
||||
|
||||
|
||||
ARG GIT_SHA
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
354
Probe/Utils/Monitors/MonitorTypes/SyntheticMonitorWorker.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
12
Scripts/package-lock.json
generated
@@ -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",
|
||||
|
||||
405
StatusPage/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
},
|
||||
|
||||