Merge branch 'develop' into fix/uptime%-charts-toggles

This commit is contained in:
antonstrover
2025-09-05 16:46:04 +01:00
committed by GitHub
47 changed files with 1075 additions and 1065 deletions

1
.coderabbit.yml Normal file
View File

@@ -0,0 +1 @@
release_notes: false

View File

@@ -7,7 +7,7 @@ import { URLSearchParams } from "url";
const API_TOKEN = process.env.POEDITOR_API;
const PROJECT_ID = process.env.POEDITOR_PROJECT_ID;
const LANGUAGES = (
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
).split(",");
const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json";

View File

@@ -16,7 +16,7 @@ jobs:
- name: Install client dependencies
working-directory: client
run: npm ci
run: npm install
- name: Check client formatting
working-directory: client
@@ -34,7 +34,7 @@ jobs:
- name: Install server dependencies
working-directory: server
run: npm ci
run: npm install
- name: Check server formatting
working-directory: server

View File

@@ -7,7 +7,7 @@ on:
languages:
description: "Languages to synchronize (comma separated, e.g.: tr,en,es)"
required: false
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja"
default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn"
format:
description: "Export format (key_value_json or json)"
required: false

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode

645
client/package-lock.json generated
View File

@@ -21,7 +21,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
"i18next": "^24.2.2",
"i18next": "25.4.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -46,9 +46,6 @@
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -283,9 +280,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz",
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -491,262 +488,6 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
@@ -763,134 +504,6 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
@@ -2063,123 +1676,6 @@
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
"integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
"integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
"integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
"integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
"integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
"integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
"integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
"integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
"integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
@@ -2193,71 +1689,6 @@
"linux"
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
"integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
"integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
"integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
"integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
"integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
@@ -2284,45 +1715,6 @@
"linux"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
"integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
"integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
"integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -4342,14 +3734,15 @@
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
@@ -4363,20 +3756,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4690,9 +4069,9 @@
}
},
"node_modules/i18next": {
"version": "24.2.3",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz",
"integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==",
"version": "25.4.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz",
"integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==",
"funding": [
{
"type": "individual",
@@ -4709,7 +4088,7 @@
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.26.10"
"@babel/runtime": "^7.27.6"
},
"peerDependencies": {
"typescript": "^5"

View File

@@ -26,7 +26,7 @@
"dayjs": "1.11.13",
"flag-icons": "7.3.2",
"html2canvas": "^1.4.1",
"i18next": "^24.2.2",
"i18next": "25.4.2",
"joi": "17.13.3",
"mui-color-input": "^6.0.0",
"react": "18.3.1",
@@ -59,8 +59,5 @@
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
"vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
}
}

View File

@@ -42,7 +42,7 @@ const Bar = forwardRef(
Bar.displayName = "Bar";
Bar.propTypes = {
width: PropTypes.string.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
height: PropTypes.string.isRequired,
backgroundColor: PropTypes.string.isRequired,
borderRadius: PropTypes.string,

View File

@@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) =>
};
Check.propTypes = {
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
noHighlightText: PropTypes.string,
variant: PropTypes.oneOf(["info", "error", "success"]),
outlined: PropTypes.bool,

View File

@@ -2,7 +2,7 @@ import { useId } from "react";
import PropTypes from "prop-types";
import { Modal, Stack, Typography } from "@mui/material";
const GenericDialog = ({ title, description, open, onClose, theme, children }) => {
const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => {
const titleId = useId();
const descriptionId = useId();
const ariaDescribedBy = description?.length > 0 ? descriptionId : "";
@@ -16,6 +16,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
>
<Stack
gap={theme.spacing(2)}
width={width}
sx={{
position: "absolute",
top: "50%",
@@ -39,6 +40,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
fontSize={16}
color={theme.palette.primary.contrastText}
fontWeight={600}
marginBottom={theme.spacing(4)}
>
{title}
</Typography>
@@ -46,6 +48,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
<Typography
id={descriptionId}
color={theme.palette.primary.contrastTextTertiary}
marginBottom={theme.spacing(4)}
>
{description}
</Typography>
@@ -64,6 +67,7 @@ GenericDialog.propTypes = {
theme: PropTypes.object.isRequired,
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node])
.isRequired,
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
};
export { GenericDialog };

View File

@@ -7,6 +7,7 @@ import {
TableHead,
TableRow,
} from "@mui/material";
import Tooltip from "@mui/material/Tooltip";
import SkeletonLayout from "./skeleton";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
@@ -41,6 +42,7 @@ const DataTable = ({
data = [],
config = {
emptyView: "No data",
tooltipContent: null,
onRowClick: () => {},
},
}) => {
@@ -101,24 +103,54 @@ const DataTable = ({
data.map((row) => {
const key = row.id || row._id || Math.random();
return (
<TableRow
<Tooltip
key={key}
sx={config?.rowSX ?? {}}
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
followCursor
enterDelay={500}
enterNextDelay={500}
title={
typeof config.tooltipContent === "function"
? config.tooltipContent(row)
: config.tooltipContent
}
slotProps={{
tooltip: {
sx: {
background: "unset",
},
},
popper: {
modifiers: [
{
name: "offset",
options: {
offset: ({ popper }) => {
return [popper.width / 2 + 20, -popper.height / 8];
},
},
},
],
},
}}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
sx={header.getCellSx ? header.getCellSx(row) : {}}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
<TableRow
sx={config?.rowSX ?? {}}
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
>
{headers.map((header, index) => {
return (
<TableCell
align={index === 0 ? "left" : "center"}
key={header.id}
onClick={header.onClick ? (e) => header.onClick(e, row) : null}
sx={header.getCellSx ? header.getCellSx(row) : {}}
>
{header.render(row)}
</TableCell>
);
})}
</TableRow>
</Tooltip>
);
})
)}

View File

@@ -14,25 +14,25 @@ const useGetInviteToken = () => {
const clearToken = () => {
setToken(undefined);
};
const fetchToken = async (email, role) => {
const response = await networkService.requestInvitationToken({ email, role });
const token = response?.data?.data?.token;
if (typeof token === "undefined") {
throw new Error(t("inviteNoTokenFound"));
}
return token;
};
const getInviteToken = async ({ email, role }) => {
try {
const response = await networkService.requestInvitationToken({
email,
role,
});
const token = response?.data?.data?.token;
if (typeof token === "undefined") {
throw new Error(t("inviteNoTokenFound"));
}
setIsLoading(true);
const token = await fetchToken(email, role);
let inviteLink = token;
if (typeof CLIENT_HOST !== "undefined") {
inviteLink = `${CLIENT_HOST}/register/${token}`;
}
setToken(inviteLink);
return token;
} catch (error) {
setError(error);
} finally {
@@ -40,7 +40,26 @@ const useGetInviteToken = () => {
}
};
return [getInviteToken, clearToken, isLoading, error, token];
const addTeamMember = async (formData, role) => {
try {
setIsLoading(true);
const token = await fetchToken(formData.email, role);
const toSubmit = {
...formData,
inviteToken: token,
};
delete toSubmit.confirm;
const responseRegister = await networkService.registerUser(toSubmit);
return responseRegister;
} catch (error) {
setError(error);
throw error;
} finally {
setIsLoading(false);
}
};
return [getInviteToken, clearToken, isLoading, error, token, addTeamMember];
};
export { useGetInviteToken };

View File

@@ -15,9 +15,6 @@ const useFetchLogs = () => {
setIsLoading(true);
const response = await networkService.getLogs();
setLogs(response.data.data);
createToast({
body: t("logsPage.toast.fetchLogsSuccess"),
});
} catch (error) {
setError(error);
createToast({

View File

@@ -0,0 +1,68 @@
import { useState } from "react";
import Button from "@mui/material/Button";
import Menu from "@mui/material/Menu";
import MenuItem from "@mui/material/MenuItem";
import { useTheme } from "@emotion/react";
import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown";
import { useTranslation } from "react-i18next";
import Proptypes from "prop-types";
const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => {
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl);
const { t } = useTranslation();
const theme = useTheme();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<Button
variant="contained"
color="accent"
endIcon={<ArrowDropDownIcon sx={{ color: theme.palette.secondary.light }} />}
onClick={handleClick}
>
{t("teamPanel.addTeamMember.addMemberMenu")}
</Button>
<Menu
anchorEl={anchorEl}
open={open}
onClose={handleClose}
sx={{
"& .MuiPaper-root": {
minWidth: anchorEl?.offsetWidth || "auto",
},
}}
>
<MenuItem
onClick={() => {
handleClose();
handleInviteOpen();
}}
>
{t("teamPanel.inviteTeamMember")}
</MenuItem>
<MenuItem
onClick={() => {
handleClose();
handleIsRegisterOpen(true);
}}
>
{t("teamPanel.register")}
</MenuItem>
</Menu>
</>
);
};
AddMemberMenu.propTypes = {
handleInviteOpen: Proptypes.func.isRequired,
handleIsRegisterOpen: Proptypes.func.isRequired,
};
export default AddMemberMenu;

View File

@@ -0,0 +1,48 @@
import { useState } from "react";
import { newOrChangedCredentials } from "../../../../../Validation/validation";
import { useTranslation } from "react-i18next";
const useAddTeamMember = () => {
const { t } = useTranslation();
const [errors, setErrors] = useState({});
const clearErrors = () => setErrors({});
const validateFields = (name, value, formData) => {
const { error } = newOrChangedCredentials.validate(
{ [name]: value },
{ abortEarly: false, context: { password: formData.password } }
);
setErrors((prev) => ({
...prev,
[name]: error?.details?.[0]?.message || "",
}));
};
const validateForm = (formData, role) => {
const { error } = newOrChangedCredentials.validate(formData, {
abortEarly: false,
context: { password: formData.password },
});
const formErrors = {};
if (error) {
for (const err of error.details) {
formErrors[err.path[0]] = err.message;
}
}
if (!role[0] || role.length === 0) {
formErrors.role = t(
"teamPanel.registerTeamMember.auth.common.inputs.role.errors.empty"
);
}
if (Object.keys(formErrors).length > 0) {
setErrors(formErrors);
return false;
}
setErrors({});
return true;
};
return { errors, setErrors, clearErrors, validateFields, validateForm };
};
export default useAddTeamMember;

View File

@@ -0,0 +1,213 @@
import { Button, Stack } from "@mui/material";
import { GenericDialog } from "../../../../Components/Dialog/genericDialog";
import TextInput from "../../../../Components/Inputs/TextInput";
import Select from "../../../../Components/Inputs/Select";
import { useGetInviteToken } from "../../../../Hooks/inviteHooks";
import { useTheme } from "@emotion/react";
import { useTranslation } from "react-i18next";
import { createToast } from "../../../../Utils/toastUtils";
import { useState } from "react";
import PasswordTooltip from "../../../Auth/components/PasswordTooltip";
import useAddTeamMember from "./hooks/useAddTeamMember";
import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback";
import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
import PropTypes from "prop-types";
const INITIAL_FORM_STATE = {
firstName: "",
lastName: "",
email: "",
password: "",
confirm: "",
teamId: "",
};
const INITIAL_ROLE_STATE = ["user"];
const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) => {
const theme = useTheme();
const { t } = useTranslation();
const { errors, setErrors, clearErrors, validateFields, validateForm } =
useAddTeamMember();
const { feedback, handlePasswordFeedback } = usePasswordFeedback();
const [getInviteToken, clearToken, isLoading, error, token, addTeamMember] =
useGetInviteToken();
const [form, setForm] = useState(INITIAL_FORM_STATE);
const [role, setRole] = useState(INITIAL_ROLE_STATE);
const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const closeAddMemberModal = () => {
handleIsRegisterOpen(false);
setForm(INITIAL_FORM_STATE);
setRole(INITIAL_ROLE_STATE);
clearErrors();
clearToken();
};
const onChange = (e) => {
let { name, value } = e.target;
if (name === "email") value = value.toLowerCase();
const updatedForm = { ...form, [name]: value };
validateFields(name, value, updatedForm);
setForm(updatedForm);
if (name === "password" || name === "confirm") {
handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors);
}
};
const onsubmitAddMember = async (event) => {
event.preventDefault();
if (!validateForm(form, role)) return;
try {
setIsLoadingSubmit(true);
await addTeamMember(form, role);
createToast({
body: t("teamPanel.registerToast.success"),
});
onMemberAdded();
closeAddMemberModal();
} catch (error) {
const errorMsg = error.response?.data?.msg || error.message || "unknownError";
createToast({
type: "error",
body: t(errorMsg),
});
} finally {
setIsLoadingSubmit(false);
}
};
const tErr = (key) => (key ? t([`teamPanel.registerTeamMember.${key}`, key]) : "");
return (
<>
<GenericDialog
title={t("teamPanel.addTeamMember.title")}
description={t("teamPanel.addTeamMember.description")}
open={isRegisterOpen}
onClose={closeAddMemberModal}
theme={theme}
width={{ sm: "55%", md: "50%", lg: "40%", xl: "30%" }}
>
<TextInput
name="firstName"
label={t("auth.common.inputs.firstName.label")}
isRequired={true}
gap={theme.spacing(4)}
placeholder={t("auth.common.inputs.firstName.placeholder")}
value={form.firstName}
onChange={onChange}
error={errors.firstName ? true : false}
helperText={errors.firstName ? tErr(errors.firstName) : null}
sx={{ mb: errors.firstName ? theme.spacing(15) : theme.spacing(5) }}
/>
<TextInput
name="lastName"
label={t("auth.common.inputs.lastName.label")}
isRequired={true}
gap={theme.spacing(4)}
placeholder={t("auth.common.inputs.lastName.placeholder")}
value={form.lastName}
onChange={onChange}
error={errors.lastName ? true : false}
helperText={errors.lastName ? tErr(errors.lastName) : null}
sx={{ mb: errors.lastName ? theme.spacing(15) : theme.spacing(5) }}
/>
<TextInput
type="email"
label={t("auth.common.inputs.email.label")}
name="email"
gap={theme.spacing(4)}
isRequired={true}
id="input-team-member"
placeholder={t("teamPanel.email")}
value={form.email}
onChange={onChange}
error={errors.email ? true : false}
helperText={errors.email ? tErr(errors.email) : null}
sx={{ mb: errors.email ? theme.spacing(15) : theme.spacing(5) }}
/>
<Select
label={t("teamPanel.role")}
id="team-member-role"
name="role"
required={true}
placeholder={t("teamPanel.selectRole")}
isHidden={true}
value={role[0] || ""}
sx={{ mb: theme.spacing(5) }}
onChange={(event) => setRole([event.target.value])}
items={[
{ _id: "admin", name: t("roles.admin") },
{ _id: "user", name: t("roles.teamMember") },
]}
/>
<PasswordTooltip
feedback={feedback}
form={form}
>
<TextInput
type="password"
id="register-password-input"
name="password"
label={t("auth.common.inputs.password.label")}
isRequired={true}
placeholder="••••••••••"
value={form.password}
onChange={onChange}
error={errors.password && errors.password[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
</PasswordTooltip>
<TextInput
type="password"
id="register-confirm-input"
name="confirm"
label={t("auth.common.inputs.passwordConfirm.label")}
gap={theme.spacing(4)}
isRequired={true}
placeholder={t("auth.common.inputs.passwordConfirm.placeholder")}
autoComplete="current-password"
value={form.confirm}
onChange={onChange}
error={errors.confirm && errors.confirm[0] ? true : false}
endAdornment={<PasswordEndAdornment />}
sx={{ mb: theme.spacing(5) }}
/>
<Stack
direction="row"
spacing={theme.spacing(10)}
mt={theme.spacing(8)}
justifyContent="flex-end"
>
<Button
variant="contained"
color="error"
onClick={closeAddMemberModal}
disabled={isLoadingSubmit}
>
{t("teamPanel.cancel")}
</Button>
<Button
variant="contained"
color="accent"
onClick={onsubmitAddMember}
disabled={isLoadingSubmit}
>
{t("teamPanel.addTeamMember.addButton")}
</Button>
</Stack>
</GenericDialog>
</>
);
};
AddTeamMember.propTypes = {
handleIsRegisterOpen: PropTypes.func.isRequired,
isRegisterOpen: PropTypes.bool.isRequired,
onMemberAdded: PropTypes.func.isRequired,
};
export default AddTeamMember;

View File

@@ -9,10 +9,12 @@ import { networkService } from "../../../main";
import { createToast } from "../../../Utils/toastUtils";
import Select from "../../../Components/Inputs/Select";
import { GenericDialog } from "../../../Components/Dialog/genericDialog";
import AddTeamMember from "../components/AddTeamMember";
import DataTable from "../../../Components/Table";
import { useGetInviteToken } from "../../../Hooks/inviteHooks";
import { useNavigate } from "react-router-dom";
import { useIsSuperAdmin } from "../../../Hooks/useIsAdmin";
import AddMemberMenu from "./AddMemberMenu";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
@@ -65,7 +67,10 @@ const TeamPanel = () => {
render: (row) => row.role,
},
];
const [refreshTrigger, setRefreshTrigger] = useState(false);
const refreshTeamList = () => {
setRefreshTrigger((prev) => !prev);
};
useEffect(() => {
const fetchTeam = async () => {
try {
@@ -79,7 +84,7 @@ const TeamPanel = () => {
};
fetchTeam();
}, []);
}, [refreshTrigger]);
useEffect(() => {
const ROLE_MAP = {
@@ -109,6 +114,10 @@ const TeamPanel = () => {
setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === "");
}, [errors, toInvite.email]);
const [isOpen, setIsOpen] = useState(false);
const [isRegisterOpen, setIsRegisterOpen] = useState(false);
const handleIsRegisterOpen = (open) => {
setIsRegisterOpen(open);
};
const handleChange = (event) => {
const { value } = event.target;
@@ -138,7 +147,6 @@ const TeamPanel = () => {
const handleGetToken = async () => {
await getInviteToken({ email: toInvite.email, role: toInvite.role });
};
const handleInviteMember = async () => {
if (!toInvite.email) {
setErrors((prev) => ({ ...prev, email: "Email is required." }));
@@ -239,13 +247,16 @@ const TeamPanel = () => {
</Button>
</ButtonGroup>
</Stack>
<Button
variant="contained"
color="accent"
onClick={() => setIsOpen(true)}
>
{t("teamPanel.inviteTeamMember")}
</Button>
<AddTeamMember
handleIsRegisterOpen={handleIsRegisterOpen}
isRegisterOpen={isRegisterOpen}
onMemberAdded={refreshTeamList}
/>
<AddMemberMenu
handleInviteOpen={() => setIsOpen(true)}
handleIsRegisterOpen={handleIsRegisterOpen}
/>
</Stack>
<DataTable
@@ -264,7 +275,6 @@ const TeamPanel = () => {
}}
/>
</Stack>
<GenericDialog
title={t("teamPanel.inviteNewTeamMember")}
description={t("teamPanel.inviteDescription")}

View File

@@ -93,14 +93,14 @@ const PasswordTooltip = ({ feedback, form, children }) => {
},
}}
>
{children}
<span style={{ display: "inline-block", width: "100%" }}>{children}</span>
</Tooltip>
);
};
PasswordTooltip.propTypes = {
feedback: PropTypes.shape({
length: PropTypes.string.isRequired,
length: PropTypes.string,
special: PropTypes.string,
number: PropTypes.string,
uppercase: PropTypes.string,

View File

@@ -0,0 +1,71 @@
import { useState } from "react";
import { newOrChangedCredentials } from "../../../Validation/validation";
const usePasswordFeedback = () => {
const [feedback, setFeedback] = useState({});
const getFeedbackStatus = (form, errors, field, criteria) => {
const fieldErrors = errors?.[field];
const isFieldEmpty = form?.[field]?.length === 0;
const hasError = fieldErrors?.includes(criteria) || fieldErrors?.includes("empty");
const isCorrect = !isFieldEmpty && !hasError;
if (isCorrect) {
return "success";
} else if (hasError) {
return "error";
} else {
return "info";
}
};
const handlePasswordFeedback = (updatedForm, name, value, form, errors, setErrors) => {
const validateValue = { [name]: value };
const validateOptions = { abortEarly: false, context: { password: form.password } };
if (name === "password" && form.confirm.length > 0) {
validateValue.confirm = form.confirm;
validateOptions.context = { password: value };
} else if (name === "confirm") {
validateValue.password = form.password;
}
const { error } = newOrChangedCredentials.validate(validateValue, validateOptions);
const pwdErrors = error?.details.map((error) => ({
path: error.path[0],
type: error.type,
}));
const errorsByPath =
pwdErrors &&
pwdErrors.reduce((acc, { path, type }) => {
if (!acc[path]) {
acc[path] = [];
}
acc[path].push(type);
return acc;
}, {});
const oldErrors = { ...errors };
if (name === "password") {
oldErrors.password = undefined;
} else if (name === "confirm") {
oldErrors.confirm = undefined;
}
const newErrors = { ...oldErrors, ...errorsByPath };
setErrors(newErrors);
const newFeedback = {
length: getFeedbackStatus(updatedForm, errorsByPath, "password", "string.min"),
special: getFeedbackStatus(updatedForm, errorsByPath, "password", "special"),
number: getFeedbackStatus(updatedForm, errorsByPath, "password", "number"),
uppercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "uppercase"),
lowercase: getFeedbackStatus(updatedForm, errorsByPath, "password", "lowercase"),
confirm: getFeedbackStatus(updatedForm, errorsByPath, "confirm", "different"),
};
setFeedback(newFeedback);
};
return { feedback, handlePasswordFeedback, getFeedbackStatus };
};
export default usePasswordFeedback;

View File

@@ -1,5 +1,10 @@
//Components
import Table from "../../../../Components/Table";
import Stack from "@mui/material/Stack";
import DataTable from "../../../../Components/Table";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableRow from "@mui/material/TableRow";
import TableCell from "@mui/material/TableCell";
import TableSkeleton from "../../../../Components/Table/skeleton";
import Pagination from "../../../../Components/Table/TablePagination";
import { StatusLabel } from "../../../../Components/Label";
@@ -16,7 +21,68 @@ import { useTranslation } from "react-i18next";
import { useFetchChecksTeam } from "../../../../Hooks/checkHooks";
import { useFetchChecksByMonitor } from "../../../../Hooks/checkHooks";
import { useResolveIncident } from "../../../../Hooks/checkHooks";
import { Button, Typography } from "@mui/material";
import { Button, Typography, useTheme } from "@mui/material";
import { lighten } from "@mui/material/styles";
const GetTooltip = (row) => {
const theme = useTheme();
const phases = row?.timings?.phases;
const phaseKeyFormattingMap = {
firstByte: "first byte",
};
return (
<Stack
backgroundColor={lighten(theme.palette.primary.main, 0.1)}
border={`1px solid ${theme.palette.primary.lowContrast}`}
borderRadius={theme.shape.borderRadius}
py={theme.spacing(2)}
px={theme.spacing(4)}
>
<Typography
variant="body2"
color={theme.palette.primary.contrastText}
>{`Status code: ${row?.statusCode}`}</Typography>
<Typography
variant="body2"
color={theme.palette.primary.contrastText}
>{`Response time: ${row?.responseTime} ms`}</Typography>
{phases && (
<>
<Typography
variant="body2"
color={theme.palette.primary.contrastText}
>{`Request timing: `}</Typography>
<Table
size="small"
sx={{ ml: theme.spacing(2), mt: theme.spacing(2) }}
>
<TableBody>
{Object.keys(phases)?.map((phaseKey) => (
<TableRow key={phaseKey}>
<TableCell sx={{ border: "none", p: 0 }}>
<Typography
variant="body2"
color="success"
>
{`${phaseKeyFormattingMap[phaseKey] || phaseKey}:`}
</Typography>
</TableCell>
<TableCell sx={{ border: "none", p: 0 }}>
<Typography
color={theme.palette.primary.contrastText}
variant="body2"
>{`${phases[phaseKey]} ms`}</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</>
)}
</Stack>
);
};
const IncidentTable = ({
isLoading,
@@ -167,9 +233,10 @@ const IncidentTable = ({
return (
<>
<Table
<DataTable
headers={headers}
data={checks}
config={{ tooltipContent: GetTooltip }}
/>
<Pagination
paginationLabel={t("incidentsTablePaginationLabel")}

View File

@@ -46,6 +46,11 @@ const JobTable = ({ jobs = [] }) => {
content: t("queuePage.jobTable.urlHeader"),
render: (row) => row.monitorUrl,
},
{
id: "interval",
content: t("queuePage.jobTable.intervalHeader"),
render: (row) => `${row.monitorInterval} ms`,
},
{
id: "type",
content: t("queuePage.jobTable.typeHeader"),

View File

@@ -223,7 +223,7 @@ const Routes = () => {
<Route
exact
path="/register/:token"
element={<AuthRegister />}
element={<AuthRegister superAdminExists={true} />}
/>
<Route

View File

@@ -130,10 +130,12 @@ const monitorValidation = joi.object({
then: joi
.string()
.trim()
.regex(/^[a-z0-9]{64}$/)
.regex(
/^(\/+)?([a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9]|[a-zA-Z0-9]+|[a-f0-9]{12,64})$/
)
.messages({
"string.empty": "This field is required.",
"string.pattern.base": "Please enter a valid 64-character Docker container ID.",
"string.pattern.base": "Please enter a valid container name or ID.",
}),
otherwise: joi
.string()

View File

@@ -251,7 +251,7 @@
"edit": "Edit",
"createA": "Create a",
"remove": "Remove",
"maintenanceWindowDescription": "Your pings won't be sent during this time frame",
"maintenanceWindowDescription": "During maintenance windows, all monitoring is suspended for selected monitors. No network checks will be performed, preventing any status updates or notifications from being triggered. Your monitors will appear frozen at their last known status, and status pages will display a maintenance indicator. Once the maintenance window ends, monitoring automatically resumes, and alerts will trigger if issues are detected. Maintenance periods do not count against uptime calculations.",
"startTime": "Start time",
"timeZoneInfo": "All dates and times are in GMT+0 time zone.",
"monitorsToApply": "Monitors to apply maintenance window to",
@@ -360,6 +360,12 @@
"demoUser": "Demo user"
},
"teamPanel": {
"addTeamMember": {
"addMemberMenu": "Add Team Member",
"title": "Register new team member",
"description": "Create a new user and share the credentials with them. This method gives the member immediate access to all monitors.",
"addButton": "Add Member"
},
"teamMembers": "Team members",
"filter": {
"all": "All",
@@ -375,6 +381,45 @@
"noMembers": "There are no team members with this role",
"getToken": "Get token",
"emailToken": "E-mail token",
"register": "Register a team member",
"registerToast": {
"success": "User created, share credentials with the member securely.",
"dbUserExists": "User already exists.",
"unknownError": "Unknown error occurred."
},
"registerTeamMember": {
"title": "Register team member",
"auth": {
"common": {
"inputs": {
"firstName": {
"errors": {
"empty": "Please enter a name",
"pattern": "Name must contain only letters, spaces, apostrophes, or hyphens"
}
},
"lastName": {
"errors": {
"empty": "Please enter a surname",
"pattern": "Surname must contain only letters, spaces, apostrophes, or hyphens"
}
},
"email": {
"errors": {
"empty": "To continue, please enter an email address",
"invalid": "Please recheck validity of entered email address"
}
},
"role": {
"errors": {
"empty": "Role is required"
}
}
}
}
}
},
"role": "Role",
"table": {
"name": "Name",
"email": "Email",
@@ -440,7 +485,7 @@
"uptimeGeneralInstructions": {
"http": "Enter the URL or IP to monitor (e.g., https://example.com/ or 192.168.1.100) and add a clear display name that appears on the dashboard.",
"ping": "Enter the IP address or hostname to ping (e.g., 192.168.1.100 or example.com) and add a clear display name that appears on the dashboard.",
"docker": "Enter the Docker ID of your container. Docker IDs must be the full 64 char Docker ID. You can run docker inspect <short_id> to get the full container ID.",
"docker": "Enter the Docker container name or ID. You can use either the container name (e.g., my-app) or the container ID (full 64-char ID or short ID).",
"port": "Enter the URL or IP of the server, the port number and a clear display name that appears on the dashboard.",
"game": "Enter the IP address or hostname and the port number to ping (e.g., 192.168.1.100 or example.com) and choose game type."
},
@@ -740,6 +785,7 @@
"idHeader": "Monitor ID",
"urlHeader": "URL",
"typeHeader": "Type",
"intervalHeader": "Interval",
"activeHeader": "Active",
"lockedAtHeader": "Locked at",
"runCountHeader": "Run count",
@@ -1014,9 +1060,9 @@
},
"monitorType": {
"docker": {
"label": "Container ID",
"label": "Container Name/ID",
"namePlaceholder": "My Container",
"placeholder": "abcd1234"
"placeholder": "my-app or abcd1234"
},
"http": {
"label": "URL to monitor",

View File

@@ -5,7 +5,7 @@ import { execSync } from "child_process";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
let version = "3.1.3";
let version = "3.1.5";
return {
base: "/",

View File

@@ -1,18 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine
FROM node:20-slim
WORKDIR /app

View File

@@ -1,19 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine
FROM node:20-slim
WORKDIR /app

View File

@@ -2,6 +2,7 @@ services:
server:
image: ghcr.io/bluewave-labs/checkmate-backend-mono-multiarch:latest
restart: always
pull_policy: always
ports:
- "52345:52345"
environment:
@@ -16,7 +17,7 @@ services:
mongodb:
image: mongo:4.4.18
restart: always
command: ["mongod", "--quiet", --bind_ip_all"]
command: ["mongod", "--quiet", "--bind_ip_all"]
ports:
- "27017:27017"
volumes:

View File

@@ -1,25 +1,37 @@
FROM node:20-alpine AS frontend-build
# ---------------------
# Frontend build stage
# ---------------------
FROM node:24-slim AS frontend-build
WORKDIR /app/client
COPY client/package*.json ./
RUN npm ci
COPY client/package.json ./
RUN npm install
RUN npm install esbuild@0.25.5 --build-from-source
COPY client ./
RUN npm run build
FROM node:20-alpine AS backend
# ---------------------
# Backend stage
# ---------------------
FROM node:24-slim AS backend
WORKDIR /app/server
COPY server/package.json ./
RUN npm install
COPY server ./
COPY --from=frontend-build /app/client/dist ./public
RUN npm ci
RUN chmod +x ./scripts/inject-vars.sh
EXPOSE 52345
CMD ./scripts/inject-vars.sh && node ./src/index.js
CMD ["sh", "-c", "./scripts/inject-vars.sh && node ./src/index.js"]

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine AS frontend-build
FROM node:20-slim AS frontend-build
WORKDIR /app/client
@@ -8,7 +8,7 @@ RUN npm install
COPY client ./
RUN npm run build
FROM node:20-alpine AS app
FROM node:20-slim AS app
WORKDIR /app/server

View File

@@ -1,18 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine
FROM node:20-slim
WORKDIR /app

View File

@@ -1,18 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine
FROM node:20-slim
ENV NODE_OPTIONS="--max-old-space-size=2048"

View File

@@ -1,19 +1,9 @@
FROM node:20-alpine AS build
FROM node:20-slim AS build
ENV NODE_OPTIONS="--max-old-space-size=4096"
WORKDIR /app
RUN apk add --no-cache \
python3 \
make g++ \
gcc \
libc-dev \
linux-headers \
libusb-dev \
eudev-dev
COPY ./client/package*.json ./
RUN npm install

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine
FROM node:20-slim
ENV NODE_OPTIONS="--max-old-space-size=2048"

566
server/package-lock.json generated
View File

@@ -39,7 +39,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
"super-simple-scheduler": "1.3.0",
"super-simple-scheduler": "1.4.1",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
@@ -94,9 +94,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
"version": "7.28.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz",
"integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -128,9 +128,9 @@
}
},
"node_modules/@csstools/color-helpers": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz",
"integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
"funding": [
{
"type": "github",
@@ -170,9 +170,9 @@
}
},
"node_modules/@csstools/css-color-parser": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
"integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
"funding": [
{
"type": "github",
@@ -185,7 +185,7 @@
],
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
"@csstools/color-helpers": "^5.1.0",
"@csstools/css-calc": "^2.1.4"
},
"engines": {
@@ -249,9 +249,9 @@
}
},
"node_modules/@emnapi/runtime": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz",
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
"license": "MIT",
"optional": true,
"dependencies": {
@@ -259,9 +259,9 @@
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.8.0.tgz",
"integrity": "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -316,9 +316,9 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
"integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -326,9 +326,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz",
"integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==",
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
"integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -376,9 +376,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz",
"integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==",
"version": "9.34.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
"integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -399,13 +399,13 @@
}
},
"node_modules/@eslint/plugin-kit": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz",
"integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==",
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
"integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/core": "^0.15.1",
"@eslint/core": "^0.15.2",
"levn": "^0.4.1"
},
"engines": {
@@ -469,33 +469,19 @@
}
},
"node_modules/@humanfs/node": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
"integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
"version": "0.16.7",
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanwhocodes/retry": "^0.3.0"
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
"integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=18.18"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@@ -886,9 +872,9 @@
}
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
"integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
@@ -929,16 +915,16 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"version": "0.3.30",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz",
"integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1198,14 +1184,13 @@
}
},
"node_modules/@sinonjs/samsam": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz",
"integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==",
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz",
"integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.1",
"lodash.get": "^4.4.2",
"type-detect": "^4.1.0"
}
},
@@ -1238,6 +1223,17 @@
"node": ">=14.16"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
"license": "ISC",
"optional": true,
"peer": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -1266,14 +1262,20 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz",
"integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==",
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
"undici-types": "~7.10.0"
}
},
"node_modules/@types/relateurl": {
"version": "0.2.33",
"resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.33.tgz",
"integrity": "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==",
"license": "MIT"
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
@@ -1383,9 +1385,9 @@
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz",
"integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==",
"license": "MIT",
"engines": {
"node": ">=12"
@@ -1503,12 +1505,6 @@
"readable-stream": "~1.0.2"
}
},
"node_modules/barse/node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/barse/node_modules/readable-stream": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
@@ -1664,9 +1660,9 @@
"license": "ISC"
},
"node_modules/browserslist": {
"version": "4.25.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
"version": "4.25.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz",
"integrity": "sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==",
"funding": [
{
"type": "opencollective",
@@ -1683,8 +1679,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
"caniuse-lite": "^1.0.30001737",
"electron-to-chromium": "^1.5.211",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@@ -1909,9 +1905,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
"version": "1.0.30001739",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
"integrity": "sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==",
"funding": [
{
"type": "opencollective",
@@ -2303,6 +2299,12 @@
"typedarray": "^0.0.6"
}
},
"node_modules/concat-stream/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/concat-stream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -2362,9 +2364,9 @@
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -2383,15 +2385,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -2511,6 +2504,21 @@
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"mdn-data": "2.0.30",
"source-map-js": "^1.0.1"
},
"engines": {
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
@@ -2536,12 +2544,12 @@
}
},
"node_modules/cssnano": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz",
"integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
"integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
"license": "MIT",
"dependencies": {
"cssnano-preset-default": "^7.0.8",
"cssnano-preset-default": "^7.0.9",
"lilconfig": "^3.1.3"
},
"engines": {
@@ -2556,9 +2564,9 @@
}
},
"node_modules/cssnano-preset-default": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz",
"integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==",
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz",
"integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.1",
@@ -2566,7 +2574,7 @@
"cssnano-utils": "^5.0.1",
"postcss-calc": "^10.1.1",
"postcss-colormin": "^7.0.4",
"postcss-convert-values": "^7.0.6",
"postcss-convert-values": "^7.0.7",
"postcss-discard-comments": "^7.0.4",
"postcss-discard-duplicates": "^7.0.2",
"postcss-discard-empty": "^7.0.1",
@@ -3012,9 +3020,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.190",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.190.tgz",
"integrity": "sha512-k4McmnB2091YIsdCgkS0fMVMPOJgxl93ltFzaryXqwip1AaxeDqKCGLxkXODDA5Ab/D+tV5EL5+aTx76RvLRxw==",
"version": "1.5.213",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.213.tgz",
"integrity": "sha512-xr9eRzSLNa4neDO0xVFrkXu3vyIzG4Ay08dApecw42Z1NbmCt+keEpXdvlYGVe0wtvY5dhW0Ay0lY0IOfsCg0Q==",
"license": "ISC"
},
"node_modules/emitter-component": {
@@ -3196,20 +3204,20 @@
}
},
"node_modules/eslint": {
"version": "9.31.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"version": "9.34.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
"integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.15.0",
"@eslint/config-helpers": "^0.3.1",
"@eslint/core": "^0.15.2",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.31.0",
"@eslint/plugin-kit": "^0.3.1",
"@eslint/js": "9.34.0",
"@eslint/plugin-kit": "^0.3.5",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@@ -3290,6 +3298,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-plugin-mocha/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -3503,6 +3524,15 @@
"express": ">= 4.11"
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -3705,9 +3735,9 @@
"license": "MIT"
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
@@ -3960,6 +3990,18 @@
"node": ">=14"
}
},
"node_modules/gaxios/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gbxremote": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz",
@@ -4037,18 +4079,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-stream/node_modules/is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -4156,18 +4186,6 @@
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/got/node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/handlebars": {
"version": "4.7.8",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
@@ -4277,11 +4295,12 @@
"license": "MIT"
},
"node_modules/htmlnano": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.2.tgz",
"integrity": "sha512-8Fst+0bhAfU362S6oHVb4wtJj/UYEFr0qiCLAEi8zioqmp1JYBQx5crZAADlFVX0Ly/6s/IQz6G7PL9/hgoJaQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.3.tgz",
"integrity": "sha512-mzTUHhxdfDw80X36rv5qFvjvor7r0uCwXI5mzo8CW61tImbe5Jpvl3JzPAetVh54wUYVuoa8x3qw8LFn8B3gHQ==",
"license": "MIT",
"dependencies": {
"@types/relateurl": "^0.2.33",
"cosmiconfig": "^9.0.0",
"posthtml": "^0.16.5"
},
@@ -4493,12 +4512,12 @@
"license": "ISC"
},
"node_modules/ioredis": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
"integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"@ioredis/commands": "^1.3.0",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
@@ -4614,12 +4633,12 @@
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
"node": ">=8"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -4639,9 +4658,9 @@
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"license": "MIT"
},
"node_modules/isexe": {
@@ -4717,9 +4736,9 @@
}
},
"node_modules/istanbul-reports": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
"integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -5066,14 +5085,6 @@
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.",
"dev": true,
"license": "MIT"
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
@@ -5182,9 +5193,9 @@
"license": "Apache-2.0"
},
"node_modules/loupe": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz",
"integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
"dev": true,
"license": "MIT"
},
@@ -5251,6 +5262,14 @@
"node": ">= 0.4"
}
},
"node_modules/mdn-data": {
"version": "2.0.30",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
"license": "CC0-1.0",
"optional": true,
"peer": true
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -5919,9 +5938,9 @@
}
},
"node_modules/mongodb": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"version": "6.19.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.19.0.tgz",
"integrity": "sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -5937,7 +5956,7 @@
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
@@ -5975,14 +5994,14 @@
}
},
"node_modules/mongoose": {
"version": "8.16.4",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz",
"integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.0.tgz",
"integrity": "sha512-3TixPihQKBdyaYDeJqRjzgb86KbilEH07JmzV8SoSjgoskNTpa6oTBmDxeoF9p8YnWQoz7shnCyPkSV/48y3yw==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.17.0",
"mongodb": "~6.18.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
@@ -5997,9 +6016,9 @@
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
"integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
@@ -6187,13 +6206,14 @@
}
},
"node_modules/nise/node_modules/path-to-regexp": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=16"
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/node-abort-controller": {
@@ -6751,9 +6771,9 @@
}
},
"node_modules/postcss-convert-values": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz",
"integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==",
"version": "7.0.7",
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz",
"integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.25.1",
@@ -7351,9 +7371,9 @@
"license": "MIT"
},
"node_modules/protobufjs": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz",
"integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==",
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -7995,9 +8015,9 @@
"license": "ISC"
},
"node_modules/ssh2": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
"integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
@@ -8008,7 +8028,7 @@
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.20.0"
"nan": "^2.23.0"
}
},
"node_modules/ssl-checker": {
@@ -8077,6 +8097,12 @@
"readable-stream": "^2.1.0"
}
},
"node_modules/string-to-stream/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/string-to-stream/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -8245,86 +8271,15 @@
}
},
"node_modules/super-simple-scheduler": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.3.0.tgz",
"integrity": "sha512-iEGZ+a9Xv7pIaA+XhVuHSGaasQg2T6/afTRlQhosQiiU/7ykpZBsiZB/L2Hqfem5YUrbr3Q8tzrjjXCXKMg81A==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.1.tgz",
"integrity": "sha512-jKVwxdRBXOHrUstA2pvO4jBZIA3CUinKiQ6k5C2Wn5tscoV3srppPxW0zG3VNKTCaGxo3yPn6Zaf3IDVBcAwRA==",
"license": "MIT",
"dependencies": {
"human-interval": "2.0.1",
"ioredis": "5.6.1",
"mongoose": "8.16.1",
"uuid": "11.1.0",
"winston": "3.17.0"
}
},
"node_modules/super-simple-scheduler/node_modules/mongodb": {
"version": "6.17.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz",
"integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.4",
"mongodb-connection-string-url": "^3.0.0"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/super-simple-scheduler/node_modules/mongoose": {
"version": "8.16.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.1.tgz",
"integrity": "sha512-Q+0TC+KLdY4SYE+u9gk9pdW1tWu/pl0jusyEkMGTgBoAbvwQdfy4f9IM8dmvCwb/blSfp7IfLkob7v76x6ZGpQ==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.17.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/super-simple-scheduler/node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
@@ -8351,10 +8306,48 @@
"node": ">=8"
}
},
"node_modules/svgo": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@trysound/sax": "0.2.0",
"commander": "^7.2.0",
"css-select": "^5.1.0",
"css-tree": "^2.3.1",
"css-what": "^6.1.0",
"csso": "^5.0.5",
"picocolors": "^1.0.0"
},
"bin": {
"svgo": "bin/svgo"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/svgo"
}
},
"node_modules/svgo/node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 10"
}
},
"node_modules/swagger-ui-dist": {
"version": "5.27.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.27.0.tgz",
"integrity": "sha512-tS6LRyBhY6yAqxrfsA9IYpGWPUJOri6sclySa7TdC7XQfGLvTwDY531KLgfQwHEtQsn+sT4JlUspbeQDBVGWig==",
"version": "5.28.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.28.1.tgz",
"integrity": "sha512-IvPrtNi8MvjiuDgoSmPYgg27Lvu38fnLD1OSd8Y103xXsPAqezVNnNeHnVCZ/d+CMXJblflGaIyHxAYIF3O71w==",
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
@@ -8588,13 +8581,12 @@
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -8640,18 +8632,18 @@
"license": "MIT"
},
"node_modules/undici": {
"version": "7.12.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.12.0.tgz",
"integrity": "sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==",
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.15.0.tgz",
"integrity": "sha512-7oZJCPvvMvTd0OlqWsIxTuItTpJBpU1tcbVl24FMn3xt3+VSunwUasmfPJRE57oNO1KsZ4PgA1xTdAX4hq8NyQ==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"license": "MIT"
},
"node_modules/unfetch": {
@@ -9031,6 +9023,18 @@
"node": ">= 12.0.0"
}
},
"node_modules/winston/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",

View File

@@ -46,7 +46,7 @@
"ping": "0.4.4",
"sharp": "0.33.5",
"ssl-checker": "2.0.10",
"super-simple-scheduler": "1.3.0",
"super-simple-scheduler": "1.4.1",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},

View File

@@ -38,7 +38,7 @@ const MonitorSchema = mongoose.Schema(
},
statusWindowThreshold: {
type: Number,
default: 0.6,
default: 60,
},
type: {
type: String,

View File

@@ -1,6 +1,6 @@
import mongoose from "mongoose";
import AppSettings from "../models/AppSettings.js";
import { runMigrations } from "./migration/index.js";
class MongoDB {
static SERVICE_NAME = "MongoDB";
@@ -65,6 +65,8 @@ class MongoDB {
service: this.SERVICE_NAME,
method: "connect",
});
await runMigrations();
} catch (error) {
this.logger.error({
message: error.message,

View File

@@ -0,0 +1,18 @@
import Monitor from "../../models/Monitor.js";
async function migrateStatusWindowThreshold() {
try {
const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } });
for (const monitor of monitors) {
monitor.statusWindowThreshold = monitor.statusWindowThreshold * 100;
await monitor.save();
console.log(`Migrated monitor ${monitor._id}: statusWindowThreshold set to ${monitor.statusWindowThreshold}`);
}
console.log("StatusWindowThreshold migration complete.");
return true;
} catch (err) {
console.error("Migration error:", err);
return false;
}
}
export { migrateStatusWindowThreshold };

View File

@@ -0,0 +1,7 @@
import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js";
const runMigrations = async () => {
await migrateStatusWindowThreshold();
};
export { runMigrations };

View File

@@ -88,7 +88,7 @@ class SuperSimpleQueue {
};
updateJob = async (monitor) => {
this.scheduler.updateJob(monitor._id.toString(), monitor.interval);
this.scheduler.updateJob(monitor._id.toString(), { repeat: monitor.interval, data: monitor.toObject() });
};
shutdown = async () => {
@@ -141,6 +141,7 @@ class SuperSimpleQueue {
monitorId: job.id,
monitorUrl: job?.data?.url || null,
monitorType: job?.data?.type || null,
monitorInterval: job?.data?.interval || null,
active: job.active,
lockedAt: job.lockedAt,
runCount: job.runCount || 0,

View File

@@ -253,24 +253,76 @@ class NetworkService {
handleError: true, // Enable error handling
});
const containers = await docker.listContainers({ all: true });
const containerExists = containers.some((c) => c.Id.startsWith(monitor.url));
if (!containerExists) {
throw new Error(this.stringService.dockerNotFound);
}
const container = docker.getContainer(monitor.url);
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
const dockerResponse = {
monitorId: monitor._id,
type: monitor.type,
responseTime,
status: response?.State?.Status === "running" ? true : false,
code: 200,
message: "Docker container status fetched successfully",
};
const containers = await docker.listContainers({ all: true });
// Normalize input: strip leading slashes and convert to lowercase for comparison
const normalizedInput = monitor.url.replace(/^\/+/, "").toLowerCase();
// Priority-based matching to avoid ambiguity:
// 1. Exact full ID match (64-char)
let exactIdMatch = containers.find((c) => c.Id.toLowerCase() === normalizedInput);
// 2. Exact container name match (case-insensitive)
let exactNameMatch = containers.find((c) =>
c.Names.some((name) => {
const cleanName = name.replace(/^\/+/, "").toLowerCase();
return cleanName === normalizedInput;
})
);
// 3. Partial ID match (fallback for backwards compatibility)
let partialIdMatch = containers.find((c) => c.Id.toLowerCase().startsWith(normalizedInput));
// Select container based on priority
let targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
// Return negative response if no container
if (!targetContainer) {
this.logger.warn({
message: `No container found for "${monitor.url}".`,
service: this.SERVICE_NAME,
method: "requestDocker",
details: { url: monitor.url },
});
dockerResponse.code = 404;
dockerResponse.status = false;
dockerResponse.message = this.stringService.dockerNotFound;
return dockerResponse;
}
// Return negative response if ambiguous matches exist
const matchTypes = [];
if (exactIdMatch) matchTypes.push("exact ID");
if (exactNameMatch) matchTypes.push("exact name");
if (partialIdMatch && !exactIdMatch) matchTypes.push("partial ID");
if (matchTypes.length > 1) {
this.logger.warn({
message: `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`,
service: this.SERVICE_NAME,
method: "requestDocker",
details: { url: monitor.url },
});
dockerResponse.status = 404;
dockerResponse.status = false;
dockerResponse.message = `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`;
return dockerResponse;
}
const container = docker.getContainer(targetContainer.Id);
const { response, responseTime, error } = await this.timeRequest(() => container.inspect());
dockerResponse.responseTime = responseTime;
dockerResponse.status = response?.State?.Status === "running" ? true : false;
dockerResponse.code = 200;
dockerResponse.message = "Docker container status fetched successfully";
if (error) {
dockerResponse.status = false;
dockerResponse.code = error.statusCode || this.NETWORK_ERROR;

View File

@@ -158,7 +158,7 @@ class StatusService {
statusChanged = true;
}
// If the failure rate is below the threshold and the monitor is down, recover:
else if (failureRate <= monitor.statusWindowThreshold && monitor.status === false) {
else if (failureRate < monitor.statusWindowThreshold && monitor.status === false) {
newStatus = true;
statusChanged = true;
}