diff --git a/client/package-lock.json b/client/package-lock.json
index 4050adfbb..f58f56f44 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -45,7 +45,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
- "vite": "^5.4.19"
+ "vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
@@ -492,9 +492,9 @@
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
- "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "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"
],
@@ -504,13 +504,13 @@
"aix"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
- "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
+ "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
@@ -520,13 +520,13 @@
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
- "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "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"
],
@@ -536,13 +536,13 @@
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
- "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "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"
],
@@ -552,13 +552,13 @@
"android"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
- "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "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"
],
@@ -568,13 +568,13 @@
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
- "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "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"
],
@@ -584,13 +584,13 @@
"darwin"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
- "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "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"
],
@@ -600,13 +600,13 @@
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
- "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "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"
],
@@ -616,13 +616,13 @@
"freebsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
- "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
+ "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
@@ -632,13 +632,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
- "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "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"
],
@@ -648,13 +648,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
- "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "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"
],
@@ -664,13 +664,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
- "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
+ "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
@@ -680,13 +680,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
- "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "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"
],
@@ -696,13 +696,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
- "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "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"
],
@@ -712,13 +712,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
- "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "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"
],
@@ -728,13 +728,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
- "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
+ "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
@@ -744,13 +744,13 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
- "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
+ "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
"cpu": [
"x64"
],
@@ -760,13 +760,29 @@
"linux"
],
"engines": {
- "node": ">=12"
+ "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.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
- "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "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"
],
@@ -776,13 +792,29 @@
"netbsd"
],
"engines": {
- "node": ">=12"
+ "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.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
- "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "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"
],
@@ -792,13 +824,13 @@
"openbsd"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
- "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "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"
],
@@ -808,13 +840,13 @@
"sunos"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
- "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "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"
],
@@ -824,13 +856,13 @@
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
- "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
+ "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
@@ -840,13 +872,13 @@
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
- "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "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"
],
@@ -856,7 +888,7 @@
"win32"
],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
"node_modules/@eslint-community/eslint-utils": {
@@ -3040,9 +3072,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3172,23 +3204,6 @@
],
"license": "CC-BY-4.0"
},
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3259,6 +3274,15 @@
"node": ">=10"
}
},
+ "node_modules/cosmiconfig/node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -3820,41 +3844,43 @@
}
},
"node_modules/esbuild": {
- "version": "0.21.5",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
- "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "version": "0.25.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
+ "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.21.5",
- "@esbuild/android-arm": "0.21.5",
- "@esbuild/android-arm64": "0.21.5",
- "@esbuild/android-x64": "0.21.5",
- "@esbuild/darwin-arm64": "0.21.5",
- "@esbuild/darwin-x64": "0.21.5",
- "@esbuild/freebsd-arm64": "0.21.5",
- "@esbuild/freebsd-x64": "0.21.5",
- "@esbuild/linux-arm": "0.21.5",
- "@esbuild/linux-arm64": "0.21.5",
- "@esbuild/linux-ia32": "0.21.5",
- "@esbuild/linux-loong64": "0.21.5",
- "@esbuild/linux-mips64el": "0.21.5",
- "@esbuild/linux-ppc64": "0.21.5",
- "@esbuild/linux-riscv64": "0.21.5",
- "@esbuild/linux-s390x": "0.21.5",
- "@esbuild/linux-x64": "0.21.5",
- "@esbuild/netbsd-x64": "0.21.5",
- "@esbuild/openbsd-x64": "0.21.5",
- "@esbuild/sunos-x64": "0.21.5",
- "@esbuild/win32-arm64": "0.21.5",
- "@esbuild/win32-ia32": "0.21.5",
- "@esbuild/win32-x64": "0.21.5"
+ "@esbuild/aix-ppc64": "0.25.5",
+ "@esbuild/android-arm": "0.25.5",
+ "@esbuild/android-arm64": "0.25.5",
+ "@esbuild/android-x64": "0.25.5",
+ "@esbuild/darwin-arm64": "0.25.5",
+ "@esbuild/darwin-x64": "0.25.5",
+ "@esbuild/freebsd-arm64": "0.25.5",
+ "@esbuild/freebsd-x64": "0.25.5",
+ "@esbuild/linux-arm": "0.25.5",
+ "@esbuild/linux-arm64": "0.25.5",
+ "@esbuild/linux-ia32": "0.25.5",
+ "@esbuild/linux-loong64": "0.25.5",
+ "@esbuild/linux-mips64el": "0.25.5",
+ "@esbuild/linux-ppc64": "0.25.5",
+ "@esbuild/linux-riscv64": "0.25.5",
+ "@esbuild/linux-s390x": "0.25.5",
+ "@esbuild/linux-x64": "0.25.5",
+ "@esbuild/netbsd-arm64": "0.25.5",
+ "@esbuild/netbsd-x64": "0.25.5",
+ "@esbuild/openbsd-arm64": "0.25.5",
+ "@esbuild/openbsd-x64": "0.25.5",
+ "@esbuild/sunos-x64": "0.25.5",
+ "@esbuild/win32-arm64": "0.25.5",
+ "@esbuild/win32-ia32": "0.25.5",
+ "@esbuild/win32-x64": "0.25.5"
}
},
"node_modules/escalade": {
@@ -4052,6 +4078,23 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/eslint/node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
@@ -4184,6 +4227,20 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fdir": {
+ "version": "6.4.6",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
+ "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -6713,6 +6770,22 @@
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
"license": "MIT"
},
+ "node_modules/tinyglobby": {
+ "version": "0.2.14",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
+ "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -6923,20 +6996,23 @@
}
},
"node_modules/vite": {
- "version": "5.4.19",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
- "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"license": "MIT",
"dependencies": {
- "esbuild": "^0.21.3",
- "postcss": "^8.4.43",
- "rollup": "^4.20.0"
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
- "node": "^18.0.0 || >=20.0.0"
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@@ -6945,19 +7021,25 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
- "terser": "^5.4.0"
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
+ "jiti": {
+ "optional": true
+ },
"less": {
"optional": true
},
@@ -6978,6 +7060,12 @@
},
"terser": {
"optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
}
}
},
@@ -7133,12 +7221,17 @@
"license": "ISC"
},
"node_modules/yaml": {
- "version": "1.10.2",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
- "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"license": "ISC",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "yaml": "bin.mjs"
+ },
"engines": {
- "node": ">= 6"
+ "node": ">= 14.6"
}
},
"node_modules/yocto-queue": {
diff --git a/client/package.json b/client/package.json
index 83dfe8407..611ba5093 100644
--- a/client/package.json
+++ b/client/package.json
@@ -58,7 +58,7 @@
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.6",
"prettier": "^3.3.3",
- "vite": "^5.4.19"
+ "vite": "6.3.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-arm64-musl": "4.41.0"
diff --git a/client/src/Components/Layouts/AppLayout/index.jsx b/client/src/Components/Layouts/AppLayout/index.jsx
index 564012d72..d13f6d879 100644
--- a/client/src/Components/Layouts/AppLayout/index.jsx
+++ b/client/src/Components/Layouts/AppLayout/index.jsx
@@ -2,16 +2,17 @@ import Box from "@mui/material/Box";
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import BackgroundSVG from "../../../assets/Images/background.svg";
+import { useSelector } from "react-redux";
const AppLayout = ({ children }) => {
const theme = useTheme();
-
+ const ui = useSelector((state) => state.ui);
return (
[
{ name: t("menu.statusPages"), path: "status", icon: },
{ name: t("menu.maintenance"), path: "maintenance", icon: },
+ { name: t("menu.logs"), path: "logs", icon: },
{
name: t("menu.settings"),
diff --git a/client/src/Components/StatusBoxes/index.jsx b/client/src/Components/StatusBoxes/index.jsx
index 40ea2d182..d97bb6211 100644
--- a/client/src/Components/StatusBoxes/index.jsx
+++ b/client/src/Components/StatusBoxes/index.jsx
@@ -4,7 +4,7 @@ import SkeletonLayout from "./skeleton";
// Utils
import { useTheme } from "@mui/material/styles";
import PropTypes from "prop-types";
-const StatusBoxes = ({ shouldRender, flexWrap = "nowrap", children }) => {
+const StatusBoxes = ({ shouldRender = true, flexWrap = "nowrap", children }) => {
const theme = useTheme();
if (!shouldRender) {
return (
diff --git a/client/src/Components/TextLink/index.jsx b/client/src/Components/TextLink/index.jsx
index a10839e84..83ae251a5 100644
--- a/client/src/Components/TextLink/index.jsx
+++ b/client/src/Components/TextLink/index.jsx
@@ -5,7 +5,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Link as RouterLink } from "react-router-dom";
-const TextLink = ({ text, linkText, href }) => {
+const TextLink = ({ text, linkText, href, target = "_self" }) => {
const theme = useTheme();
return (
@@ -18,6 +18,7 @@ const TextLink = ({ text, linkText, href }) => {
color="accent"
to={href}
component={RouterLink}
+ target={target}
>
{linkText}
@@ -29,6 +30,7 @@ TextLink.propTypes = {
text: PropTypes.string,
linkText: PropTypes.string,
href: PropTypes.string,
+ target: PropTypes.string,
};
export default TextLink;
diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/logHooks.js
new file mode 100644
index 000000000..000e4d0f0
--- /dev/null
+++ b/client/src/Hooks/logHooks.js
@@ -0,0 +1,35 @@
+import { useState, useEffect } from "react";
+import { networkService } from "../main";
+import { createToast } from "../Utils/toastUtils";
+import { useTranslation } from "react-i18next";
+
+const useFetchLogs = () => {
+ const { t } = useTranslation();
+ const [logs, setLogs] = useState(undefined);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(undefined);
+
+ useEffect(() => {
+ const fetchLogs = async () => {
+ try {
+ setIsLoading(true);
+ const response = await networkService.getLogs();
+ setLogs(response.data.data);
+ createToast({
+ body: t("logsPage.toast.fetchLogsSuccess"),
+ });
+ } catch (error) {
+ setError(error);
+ createToast({
+ message: error.message,
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fetchLogs();
+ }, [t]);
+ return [logs, isLoading, error];
+};
+
+export { useFetchLogs };
diff --git a/client/src/Pages/Logs/Logs/index.jsx b/client/src/Pages/Logs/Logs/index.jsx
new file mode 100644
index 000000000..6c5e1a301
--- /dev/null
+++ b/client/src/Pages/Logs/Logs/index.jsx
@@ -0,0 +1,97 @@
+import Stack from "@mui/material/Stack";
+import Box from "@mui/material/Box";
+import Select from "../../../Components/Inputs/Select";
+import Typography from "@mui/material/Typography";
+
+import { useFetchLogs } from "../../../Hooks/logHooks";
+import { useTheme } from "@emotion/react";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+
+const formatLog = (theme, log, idx) => {
+ const levelColors = {
+ info: theme.palette.success.main,
+ warn: theme.palette.warning.main,
+ error: theme.palette.error.main,
+ debug: theme.palette.accent.main,
+ };
+
+ const color = levelColors[log.level] || theme.palette.primary.contrastText;
+
+ return (
+
+ [{log.timestamp}]{" "}
+ {log.level.toUpperCase()}
+ {": "}
+ {`(${log.service})`}
+ {`(${log.method})`}
+ {": "}
+ {log.message}
+
+
+ );
+};
+
+const Logs = () => {
+ // Local state
+ const [logLevel, setLogLevel] = useState("all");
+
+ // Hooks
+ const theme = useTheme();
+ const { t } = useTranslation();
+ const [logs, isLoading, error] = useFetchLogs();
+ // Setup
+ const LOG_LEVELS = [
+ { _id: "all", name: t("logsPage.logLevelSelect.values.all") },
+ { _id: "info", name: t("logsPage.logLevelSelect.values.info") },
+ { _id: "warn", name: t("logsPage.logLevelSelect.values.warn") },
+ { _id: "error", name: t("logsPage.logLevelSelect.values.error") },
+ { _id: "debug", name: t("logsPage.logLevelSelect.values.debug") },
+ ];
+ return (
+
+
+ {t("logsPage.description")}
+
+
+ {t("logsPage.logLevelSelect.title")}
+
+
+
+
+ {logs
+ ?.filter((log) => {
+ if (logLevel === "all") return true;
+ return log.level === logLevel;
+ })
+ .reverse()
+ .map((log, idx) => formatLog(theme, log, idx))}
+
+
+
+ );
+};
+
+export default Logs;
diff --git a/client/src/Pages/Queue/components/FailedJobTable/index.jsx b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
similarity index 93%
rename from client/src/Pages/Queue/components/FailedJobTable/index.jsx
rename to client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
index d7284f9da..94fc2f5c3 100644
--- a/client/src/Pages/Queue/components/FailedJobTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/FailedJobTable/index.jsx
@@ -1,10 +1,10 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import DataTable from "../../../../Components/Table";
+import DataTable from "../../../../../Components/Table";
import { useNavigate } from "react-router-dom";
import { useTheme } from "@emotion/react";
-import { TypeToPathMap } from "../../../../Utils/monitorUtils";
+import { TypeToPathMap } from "../../../../../Utils/monitorUtils";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Queue/components/JobTable/index.jsx b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
similarity index 93%
rename from client/src/Pages/Queue/components/JobTable/index.jsx
rename to client/src/Pages/Logs/Queue/components/JobTable/index.jsx
index f4aa0768c..e27dd103f 100644
--- a/client/src/Pages/Queue/components/JobTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/JobTable/index.jsx
@@ -1,13 +1,13 @@
import Stack from "@mui/material/Stack";
-import DataTable from "../../../../Components/Table";
+import DataTable from "../../../../../Components/Table";
import Typography from "@mui/material/Typography";
// Utils
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { useNavigate } from "react-router-dom";
-import { TypeToPathMap } from "../../../../Utils/monitorUtils";
+import { TypeToPathMap } from "../../../../../Utils/monitorUtils";
import { useTranslation } from "react-i18next";
-import { createHeaderFactory } from "../../../../Components/Table/TableUtils";
+import { createHeaderFactory } from "../../../../../Components/Table/TableUtils";
const JobTable = ({ jobs = [] }) => {
const theme = useTheme();
diff --git a/client/src/Pages/Logs/Queue/components/Metrics/index.jsx b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
new file mode 100644
index 000000000..af1cf8010
--- /dev/null
+++ b/client/src/Pages/Logs/Queue/components/Metrics/index.jsx
@@ -0,0 +1,43 @@
+import Stack from "@mui/material/Stack";
+import Typography from "@mui/material/Typography";
+import StatBox from "../../../../../Components/StatBox";
+import StatusBoxes from "../../../../../Components/StatusBoxes";
+
+import { useTranslation } from "react-i18next";
+import { useTheme } from "@emotion/react";
+
+const camelToTitle = (str) => {
+ return str
+ .replace(/([A-Z])/g, " $1")
+ .toLowerCase()
+ .replace(/^./, (m) => m.toUpperCase());
+};
+
+const Metrics = ({ metrics = {} }) => {
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ const data = Object.keys(metrics)
+ .filter((key) => key !== "jobsWithFailures")
+ .map((key) => {
+ return { key, title: camelToTitle(key), value: metrics[key] };
+ });
+
+ return (
+
+ {t("queuePage.metricsTable.title")}
+
+ {data.map((metric) => {
+ return (
+
+ );
+ })}
+
+
+ );
+};
+export default Metrics;
diff --git a/client/src/Pages/Queue/components/MetricsTable/index.jsx b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
similarity index 95%
rename from client/src/Pages/Queue/components/MetricsTable/index.jsx
rename to client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
index 818b4e543..204b90035 100644
--- a/client/src/Pages/Queue/components/MetricsTable/index.jsx
+++ b/client/src/Pages/Logs/Queue/components/MetricsTable/index.jsx
@@ -1,6 +1,6 @@
import Stack from "@mui/material/Stack";
import Typography from "@mui/material/Typography";
-import DataTable from "../../../../Components/Table";
+import DataTable from "../../../../../Components/Table";
// Utils
import { useTranslation } from "react-i18next";
diff --git a/client/src/Pages/Queue/index.jsx b/client/src/Pages/Logs/Queue/index.jsx
similarity index 82%
rename from client/src/Pages/Queue/index.jsx
rename to client/src/Pages/Logs/Queue/index.jsx
index eb732560c..729f87fe3 100644
--- a/client/src/Pages/Queue/index.jsx
+++ b/client/src/Pages/Logs/Queue/index.jsx
@@ -1,15 +1,14 @@
// Components
import Stack from "@mui/material/Stack";
-import Breadcrumbs from "../../Components/Breadcrumbs";
import JobTable from "./components/JobTable";
-import MetricsTable from "./components/MetricsTable";
+import Metrics from "./components/Metrics";
import FailedJobTable from "./components/FailedJobTable";
import ButtonGroup from "@mui/material/ButtonGroup";
import Button from "@mui/material/Button";
// Utils
import { useState } from "react";
-import { useFetchQueueData, useFlushQueue } from "../../Hooks/queueHooks";
+import { useFetchQueueData, useFlushQueue } from "../../../Hooks/queueHooks";
import { useTranslation } from "react-i18next";
import { useTheme } from "@emotion/react";
@@ -23,15 +22,13 @@ const QueueDetails = () => {
const [jobs, metrics, isLoading, error] = useFetchQueueData(trigger);
const [flushQueue, isFlushing, flushError] = useFlushQueue();
- const BREADCRUMBS = [{ name: t("queuePage.title"), path: "/queue" }];
if (isLoading) return Loading...
;
if (error || flushError) return Error: {error.message}
;
return (
-
+
-
{
+ const { t } = useTranslation();
+ const theme = useTheme();
+
+ // Local state
+ const [value, setValue] = useState(0);
+
+ // Handlers
+ const handleChange = (event, newValue) => {
+ setValue(newValue);
+ };
+
+ const BREADCRUMBS = [{ name: t("logsPage.title"), path: "/logs" }];
+ return (
+
+
+
+
+
+
+ {value === 0 && }
+ {value === 1 && }
+
+ );
+};
+
+export default Logs;
diff --git a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx
index a798838cb..bb5842e9f 100644
--- a/client/src/Pages/Maintenance/MaintenanceTable/index.jsx
+++ b/client/src/Pages/Maintenance/MaintenanceTable/index.jsx
@@ -11,6 +11,8 @@ import { formatDurationRounded } from "../../../Utils/timeUtils";
import { StatusLabel } from "../../../Components/Label";
import { setRowsPerPage } from "../../../Features/UI/uiSlice";
import { useTranslation } from "react-i18next";
+import { useTheme } from "@emotion/react";
+import { useNavigate } from "react-router-dom";
import dayjs from "dayjs";
/**
* Component for pagination actions (first, previous, next, last).
@@ -36,7 +38,8 @@ const MaintenanceTable = ({
}) => {
const rowsPerPage = useSelector((state) => state?.ui?.maintenance?.rowsPerPage ?? 5);
const dispatch = useDispatch();
-
+ const theme = useTheme();
+ const navigate = useNavigate();
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
@@ -180,6 +183,18 @@ const MaintenanceTable = ({
return (
<>
{
+ navigate(`/maintenance/create/${row._id}`);
+ },
+ }}
headers={headers}
data={maintenanceWindows}
/>
diff --git a/client/src/Pages/Settings/SettingsDev.jsx b/client/src/Pages/Settings/SettingsDev.jsx
deleted file mode 100644
index 721a17d5c..000000000
--- a/client/src/Pages/Settings/SettingsDev.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import Box from "@mui/material/Box";
-import Typography from "@mui/material/Typography";
-import ConfigBox from "../../Components/ConfigBox";
-// Utils
-import { useTranslation } from "react-i18next";
-import { useNavigate } from "react-router-dom";
-import Link from "../../Components/Link";
-
-const SettingsDev = ({ isAdmin, HEADER_SX }) => {
- const { t } = useTranslation();
-
- if (!isAdmin) return null;
-
- return (
-
-
-
- {t("settingsDev")}
-
- {t("settingsDevDescription")}
-
-
-
-
-
- );
-};
-
-export default SettingsDev;
diff --git a/client/src/Pages/Settings/SettingsEmail.jsx b/client/src/Pages/Settings/SettingsEmail.jsx
index 061cf2602..1ae4a1f02 100644
--- a/client/src/Pages/Settings/SettingsEmail.jsx
+++ b/client/src/Pages/Settings/SettingsEmail.jsx
@@ -4,6 +4,8 @@ import ConfigBox from "../../Components/ConfigBox";
import TextInput from "../../Components/Inputs/TextInput";
import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
+import { Switch } from "@mui/material";
+import TextLink from "../../Components/TextLink";
// Utils
import { useTheme } from "@emotion/react";
import { PropTypes } from "prop-types";
@@ -12,7 +14,6 @@ import { useTranslation } from "react-i18next";
import { PasswordEndAdornment } from "../../Components/Inputs/TextInput/Adornments";
import { useSendTestEmail } from "../../Hooks/useSendTestEmail";
import { createToast } from "../../Utils/toastUtils";
-import { Switch } from "@mui/material";
const SettingsEmail = ({
isAdmin,
@@ -239,6 +240,46 @@ const SettingsEmail = ({
/>
))}
+
+
+
+
+ {JSON.stringify(
+ {
+ host: systemEmailHost,
+ port: systemEmailPort,
+ secure: systemEmailSecure,
+ auth: {
+ user: systemEmailUser || systemEmailAddress,
+ pass: "",
+ },
+ name: systemEmailConnectionHost || "localhost",
+ pool: systemEmailPool,
+ tls: {
+ rejectUnauthorized: systemEmailRejectUnauthorized,
+ ignoreTLS: systemEmailIgnoreTLS,
+ requireTLS: systemEmailRequireTLS,
+ servername: systemEmailTLSServername,
+ },
+ },
+ null,
+ 2
+ )}
+
+
diff --git a/client/src/Pages/Settings/index.jsx b/client/src/Pages/Settings/index.jsx
index ef57a16c7..0cd954e52 100644
--- a/client/src/Pages/Settings/index.jsx
+++ b/client/src/Pages/Settings/index.jsx
@@ -8,7 +8,6 @@ import SettingsPagespeed from "./SettingsPagespeed";
import SettingsDemoMonitors from "./SettingsDemoMonitors";
import SettingsAbout from "./SettingsAbout";
import SettingsEmail from "./SettingsEmail";
-import SettingsDev from "./SettingsDev";
import Button from "@mui/material/Button";
// Utils
import { settingsValidation } from "../../Validation/validation";
@@ -209,10 +208,7 @@ const Settings = () => {
emailPasswordHasBeenReset={emailPasswordHasBeenReset}
setEmailPasswordHasBeenReset={setEmailPasswordHasBeenReset}
/>
-
+
+
diff --git a/client/src/Pages/StatusPage/Create/index.jsx b/client/src/Pages/StatusPage/Create/index.jsx
index 50535cd90..63c248786 100644
--- a/client/src/Pages/StatusPage/Create/index.jsx
+++ b/client/src/Pages/StatusPage/Create/index.jsx
@@ -20,7 +20,7 @@ const TAB_LIST = ["General settings", "Contents"];
const ERROR_TAB_MAPPING = [
["companyName", "url", "timezone", "color", "isPublished", "logo"],
- ["monitors", "showUptimePercentage", "showCharts"],
+ ["monitors", "showUptimePercentage", "showCharts", "showAdminLoginLink"],
];
const CreateStatusPage = () => {
@@ -39,6 +39,7 @@ const CreateStatusPage = () => {
monitors: [],
showCharts: true,
showUptimePercentage: true,
+ showAdminLoginLink: false,
});
const [errors, setErrors] = useState({});
const [selectedMonitors, setSelectedMonitors] = useState([]);
@@ -195,6 +196,7 @@ const CreateStatusPage = () => {
logo: newLogo,
showCharts: statusPage?.showCharts ?? true,
showUptimePercentage: statusPage?.showUptimePercentage ?? true,
+ showAdminLoginLink: statusPage?.showAdminLoginLink ?? false,
};
});
setSelectedMonitors(statusPageMonitors);
diff --git a/client/src/Pages/StatusPage/Status/index.jsx b/client/src/Pages/StatusPage/Status/index.jsx
index 727daeeb1..45822f6c6 100644
--- a/client/src/Pages/StatusPage/Status/index.jsx
+++ b/client/src/Pages/StatusPage/Status/index.jsx
@@ -48,7 +48,7 @@ const PublicStatus = () => {
let link = undefined;
const isPublic = location.pathname.startsWith("/status/uptime/public");
// Public status page
- if (isPublic) {
+ if (isPublic && statusPage && statusPage.showAdminLoginLink === true) {
sx = {
paddingTop: theme.spacing(20),
paddingLeft: "20vw",
diff --git a/client/src/Routes/index.jsx b/client/src/Routes/index.jsx
index ba27482ff..60df27211 100644
--- a/client/src/Routes/index.jsx
+++ b/client/src/Routes/index.jsx
@@ -51,7 +51,7 @@ import ProtectedRoute from "../Components/ProtectedRoute";
import CreateNewMaintenanceWindow from "../Pages/Maintenance/CreateMaintenance";
import withAdminCheck from "../Components/HOC/withAdminCheck";
import BulkImport from "../Pages/Uptime/BulkImport";
-import Queue from "../Pages/Queue";
+import Logs from "../Pages/Logs";
const Routes = () => {
const AdminCheckedRegister = withAdminCheck(AuthRegister);
@@ -187,9 +187,10 @@ const Routes = () => {
path="account/team"
element={}
/>
+
}
+ path="logs"
+ element={}
/>
diff --git a/client/src/Utils/NetworkService.js b/client/src/Utils/NetworkService.js
index 651f72fe0..714268b0b 100644
--- a/client/src/Utils/NetworkService.js
+++ b/client/src/Utils/NetworkService.js
@@ -871,6 +871,9 @@ class NetworkService {
if (form.showUptimePercentage !== undefined) {
fd.append("showUptimePercentage", String(form.showUptimePercentage));
}
+ if (form.showAdminLoginLink !== undefined) {
+ fd.append("showAdminLoginLink", String(form.showAdminLoginLink));
+ }
form.monitors &&
form.monitors.forEach((monitorId) => {
fd.append("monitors[]", monitorId);
@@ -1041,6 +1044,14 @@ class NetworkService {
});
return response;
}
+
+ async getLogs() {
+ return this.axiosInstance.get(`/logs`, {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ }
}
export default NetworkService;
diff --git a/client/src/Utils/Theme/globalTheme.js b/client/src/Utils/Theme/globalTheme.js
index 58d9943be..ae014432d 100644
--- a/client/src/Utils/Theme/globalTheme.js
+++ b/client/src/Utils/Theme/globalTheme.js
@@ -407,6 +407,7 @@ const baseTheme = (palette) => ({
}),
},
},
+
MuiTab: {
styleOverrides: {
root: ({ theme }) => ({
@@ -475,6 +476,8 @@ const baseTheme = (palette) => ({
MuiTabs: {
styleOverrides: {
root: ({ theme }) => ({
+ height: "34px",
+ minHeight: "34px",
display: "inline-flex",
borderRadius: 0,
"& .MuiTabs-indicator": {
diff --git a/client/src/Validation/validation.js b/client/src/Validation/validation.js
index 4e6a806c8..d86bbd556 100644
--- a/client/src/Validation/validation.js
+++ b/client/src/Validation/validation.js
@@ -277,6 +277,7 @@ const statusPageValidation = joi.object({
logo: logoImageValidation,
showUptimePercentage: joi.boolean(),
showCharts: joi.boolean(),
+ showAdminLoginLink: joi.boolean(),
});
const settingsValidation = joi.object({
diff --git a/client/src/assets/icons/logs.svg b/client/src/assets/icons/logs.svg
new file mode 100644
index 000000000..556c3ba33
--- /dev/null
+++ b/client/src/assets/icons/logs.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/client/src/locales/ar.json b/client/src/locales/ar.json
index fd4d925a9..06789abb0 100644
--- a/client/src/locales/ar.json
+++ b/client/src/locales/ar.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/cs.json b/client/src/locales/cs.json
index 66b2993d2..110d9a508 100644
--- a/client/src/locales/cs.json
+++ b/client/src/locales/cs.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": "Nastala chyba při ověřování dat."
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/de.json b/client/src/locales/de.json
index 6d5c5a4d8..5561188a0 100644
--- a/client/src/locales/de.json
+++ b/client/src/locales/de.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/en.json b/client/src/locales/en.json
index 14317e689..b57bb1e15 100644
--- a/client/src/locales/en.json
+++ b/client/src/locales/en.json
@@ -30,9 +30,6 @@
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
"settingsRemoveAllMonitorsDialogConfirm": "Yes, remove all monitors",
"settingsAbout": "About",
- "settingsDev": "Developer",
- "settingsDevDescription": "Developer settings",
- "settingsDevViewJobQueueDetails": "View job queue details",
"settingsDevelopedBy": "Developed by Bluewave Labs.",
"settingsSave": "Save",
"settingsSuccessSaved": "Settings saved successfully",
@@ -319,6 +316,7 @@
"statusPageCreateTabsContentFeaturesDescription": "Show more details on the status page",
"showCharts": "Show charts",
"showUptimePercentage": "Show uptime percentage",
+ "showAdminLoginLink": "Show \"Administrator? Login Here\" link on the status page",
"removeLogo": "Remove Logo",
"statusPageStatus": "A public status page is not set up.",
"statusPageStatusContactAdmin": "Please contact to your administrator",
@@ -431,6 +429,7 @@
"statusPages": "Status pages",
"maintenance": "Maintenance",
"integrations": "Integrations",
+ "logs": "Logs",
"settings": "Settings",
"support": "Support",
"discussions": "Discussions",
@@ -450,6 +449,8 @@
"settingsEmailPassword": "Email password - Password for authentication",
"settingsEmailUser": "Email user - Username for authentication, overrides email address if specified",
"settingsEmailFieldResetLabel": "Password is set. Click Reset to change it.",
+ "settingsEmailTransportLinkDescription": "This builds an SMTP transport for NodeMailer",
+ "settingsEmailTransportLinkText": "See specifications here",
"state": "State",
"statusBreadCrumbsStatusPages": "Status Pages",
"statusBreadCrumbsDetails": "Details",
@@ -510,13 +511,6 @@
},
"auth": {
"common": {
- "fields": {
- "password": {
- "errors": {
- "incorrect": "The password you provided does not match our records"
- }
- }
- },
"navigation": {
"continue": "Continue",
"back": "Back"
@@ -597,6 +591,13 @@
},
"errors": {
"validation": "Error validating data."
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": "The password you provided does not match our records"
+ }
+ }
}
},
"login": {
@@ -717,23 +718,23 @@
"webhookLabel": "Slack webhook URL",
"webhookPlaceholder": "https://hooks.slack.com/services/..."
},
- "pagerDutySettings": {
+ "pagerdutySettings": {
"title": "PagerDuty",
"description": "Configure your PagerDuty integration here",
"integrationKeyLabel": "Integration key",
"integrationKeyPlaceholder": "1234567890"
},
- "webhookSettings": {
- "title": "Webhook",
- "description": "Configure your webhook here",
- "webhookLabel": "Webhook URL",
- "webhookPlaceholder": "https://your-server.com/webhook"
- },
"discordSettings": {
"title": "Discord",
"description": "Configure your Discord webhook here",
"webhookLabel": "Discord Webhook URL",
"webhookPlaceholder": "https://your-server.com/webhook"
+ },
+ "webhookSettings": {
+ "title": "Webhook",
+ "description": "Configure your webhook here",
+ "webhookLabel": "Webhook URL",
+ "webhookPlaceholder": "https://your-server.com/webhook"
}
},
"notificationConfig": {
@@ -749,13 +750,34 @@
},
"advancedMatching": "Advanced matching",
"settingsEmailTLSServername": "TLS Servername - Optional Hostname for TLS Validation when host is an IP",
- "settingsEmailIgnoreTLS": "Disable STARTTLS: Don't use TLS even if the server supports it",
- "settingsEmailRequireTLS": "Force STARTTLS: Require TLS upgrade, fail if not supported",
- "settingsEmailRejectUnauthorized": "Reject invalid certificates: Reject connections with self-signed or untrusted certificates",
- "settingsEmailSecure": "Use SSL (recommended): Encrypt the connection using SSL/TLS",
- "settingsEmailPool": "Enable connection pooling: Reuse existing connections to improve performance",
+ "settingsEmailIgnoreTLS": "Ignore TLS - Disable STARTTLS",
+ "settingsEmailRequireTLS": "Require TLS - Force STARTTLS",
+ "settingsEmailRejectUnauthorized": "Reject Unauthorized",
+ "settingsEmailSecure": "Secure - Use SSL",
+ "settingsEmailPool": "Pool - Enable connection pooling",
"sendTestNotifications": "Send test notifications",
"selectAll": "Select all",
+ "logsPage": {
+ "title": "Logs",
+ "description": "This page shows the latest 1000 lines of logs from the Checkmate server",
+ "tabs": {
+ "queue": "Job queue",
+ "logs": "Server logs"
+ },
+ "toast": {
+ "fetchLogsSuccess": "Logs fetched successfully"
+ },
+ "logLevelSelect": {
+ "title": "Log level",
+ "values": {
+ "all": "All",
+ "info": "Info",
+ "warn": "Warn",
+ "error": "Error",
+ "debug": "Debug"
+ }
+ }
+ },
"queuePage": {
"title": "Queue",
"refreshButton": "Refresh",
@@ -785,16 +807,16 @@
"failCountHeader": "Fail count",
"failedAtHeader": "Last failed at",
"failReasonHeader": "Fail reason"
- },
- "export": {
- "title": "Export Monitors",
- "success": "Monitors exported successfully!",
- "failed": "Failed to export monitors"
- },
- "monitorActions": {
- "title": "Export/Import",
- "import": "Import Monitors",
- "export": "Export Monitors"
}
+ },
+ "export": {
+ "title": "Export Monitors",
+ "success": "Monitors exported successfully!",
+ "failed": "Failed to export monitors"
+ },
+ "monitorActions": {
+ "title": "Export/Import",
+ "import": "Import Monitors",
+ "export": "Export Monitors"
}
}
diff --git a/client/src/locales/es.json b/client/src/locales/es.json
index fb8207e48..b23a57ed3 100644
--- a/client/src/locales/es.json
+++ b/client/src/locales/es.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/fi.json b/client/src/locales/fi.json
index f085b5857..44b56516b 100644
--- a/client/src/locales/fi.json
+++ b/client/src/locales/fi.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/fr.json b/client/src/locales/fr.json
index 48834602e..0f218d1c6 100644
--- a/client/src/locales/fr.json
+++ b/client/src/locales/fr.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/pt-BR.json b/client/src/locales/pt-BR.json
index a783a820c..38b08adaf 100644
--- a/client/src/locales/pt-BR.json
+++ b/client/src/locales/pt-BR.json
@@ -312,6 +312,7 @@
"statusPageCreateTabsContentFeaturesDescription": "Mostrar mais detalhes na página de status",
"showCharts": "Mostrar gráficos",
"showUptimePercentage": "Mostrar porcentagem de Uptime",
+ "showAdminLoginLink": "Mostrar o link \"Administrador? Efetue login aqui\" na página de status",
"removeLogo": "Remover logo",
"statusPageStatus": "Uma página de status pública não está configurada.",
"statusPageStatusContactAdmin": "Entre em contato com seu administrador",
@@ -583,6 +584,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +717,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +748,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/ru.json b/client/src/locales/ru.json
index 539bb2d51..65a4fe4a0 100644
--- a/client/src/locales/ru.json
+++ b/client/src/locales/ru.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/client/src/locales/tr.json b/client/src/locales/tr.json
index 1d86de06c..ed03d7e6c 100644
--- a/client/src/locales/tr.json
+++ b/client/src/locales/tr.json
@@ -117,11 +117,11 @@
"title": "",
"checks": [""]
},
- "createButton": "",
- "createTitle": "",
+ "createButton": "Bildirim kanalı oluştur",
+ "createTitle": "Bildirim kanalı",
"create": {
"success": "",
- "failed": ""
+ "failed": "Bildirim kanalı oluşturulamadı"
},
"fetch": {
"success": "",
@@ -136,7 +136,7 @@
"failed": ""
},
"test": {
- "success": "",
+ "success": "Test bildirimi başarıyla gönderildi",
"failed": ""
}
},
@@ -373,33 +373,33 @@
"pageSpeedApiKeyFieldDescription": "Sayfa hızı izlemeyi etkinleştirmek için Google PageSpeed API anahtarınızı girin. Anahtarı güncellemek için Sıfırla'ya tıklayın.",
"pageSpeedApiKeyFieldResetLabel": "API anahtarı ayarlandı. Değiştirmek için Sıfırla'ya tıklayın.",
"reset": "Sıfırla",
- "ignoreTLSError": "",
- "tlsErrorIgnored": "",
+ "ignoreTLSError": "TLS/SSL hatalarını gözardı et",
+ "tlsErrorIgnored": "TLS/SSL hataları gözardı ediliyor",
"ignoreTLSErrorDescription": "",
- "createNew": "",
+ "createNew": "Yeni oluştur",
"greeting": {
- "prepend": "",
+ "prepend": "Merhaba",
"append": "",
"overview": ""
},
"roles": {
- "superAdmin": "",
- "admin": "",
- "teamMember": "",
- "demoUser": ""
+ "superAdmin": "Süper yönetici",
+ "admin": "Yönetici",
+ "teamMember": "Ekip elemanı",
+ "demoUser": "Demo kullanıcısı"
},
"teamPanel": {
- "teamMembers": "",
+ "teamMembers": "Ekip elemanları",
"filter": {
- "all": "",
- "member": ""
+ "all": "Tümü",
+ "member": "Eleman"
},
- "inviteTeamMember": "",
- "inviteNewTeamMember": "",
+ "inviteTeamMember": "Ekip elemanı davet et",
+ "inviteNewTeamMember": "Yeni ekip elemanı davet et",
"inviteDescription": "",
- "email": "",
- "selectRole": "",
- "inviteLink": "",
+ "email": "Eposta",
+ "selectRole": "Rolü seçin",
+ "inviteLink": "Davet linki",
"cancel": "",
"noMembers": "",
"getToken": "",
@@ -422,21 +422,21 @@
"infrastructure": "",
"incidents": "",
"statusPages": "",
- "maintenance": "",
- "integrations": "",
- "settings": "",
- "support": "",
- "discussions": "",
- "docs": "",
- "changelog": "",
- "profile": "",
- "password": "",
- "team": "",
+ "maintenance": "Bakım",
+ "integrations": "Entegrasyonlar",
+ "settings": "Ayarlar",
+ "support": "Destek",
+ "discussions": "Soru-cevap",
+ "docs": "Dokümanlar",
+ "changelog": "Değişiklik günlüğü",
+ "profile": "Profil",
+ "password": "Parola",
+ "team": "Ekip",
"logOut": "Çıkış yap",
- "notifications": ""
+ "notifications": "Bildirimler"
},
- "settingsEmail": "",
- "settingsEmailDescription": "",
+ "settingsEmail": "Eposta",
+ "settingsEmailDescription": "Sistem için eposta ayarlarını düzenleyin. Bu işlem eposta üzerinden bildirim ve alarm almak için gereklidir.",
"settingsEmailHost": "",
"settingsEmailPort": "",
"settingsEmailAddress": "",
@@ -490,73 +490,73 @@
"port": ""
},
"common": {
- "appName": "",
+ "appName": "Checkmate",
"monitoringAgentName": "",
"buttons": {
"toggleTheme": ""
},
"toasts": {
"networkError": "",
- "checkConnection": "",
- "unknownError": ""
+ "checkConnection": "Lütfen bağlantınızı kontrol edin",
+ "unknownError": "Bilinmeyen hata"
}
},
"auth": {
"common": {
"navigation": {
- "continue": "",
- "back": ""
+ "continue": "Devam et",
+ "back": "Geri"
},
"inputs": {
"email": {
- "label": "",
+ "label": "Eposta",
"placeholder": "",
"errors": {
- "empty": "",
+ "empty": "Devam etmek için eposta adresinizi girin",
"invalid": ""
}
},
"password": {
- "label": "",
+ "label": "Parola",
"rules": {
"length": {
"beginning": "",
- "highlighted": ""
+ "highlighted": "8 karakter uzunluğunda"
},
"special": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "En az içermelidir: ",
+ "highlighted": "bir özel karakter"
},
"number": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "En az içermelidir: ",
+ "highlighted": "bir sayı"
},
"uppercase": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "En az içermelidir: ",
+ "highlighted": "Bir büyük harf"
},
"lowercase": {
- "beginning": "",
- "highlighted": ""
+ "beginning": "En az içermelidir: ",
+ "highlighted": "Bir küçük harf"
},
"match": {
"beginning": "",
- "highlighted": ""
+ "highlighted": "eşleşmelidir"
}
},
"errors": {
- "empty": "",
- "length": "",
- "uppercase": "",
- "lowercase": "",
- "number": "",
- "special": "",
- "incorrect": ""
+ "empty": "Lütfen parolanızı girin",
+ "length": "Parolanız en az 8 karakter uzunluğunda olmalıdır",
+ "uppercase": "Parola en az 1 büyük harf içermelidir",
+ "lowercase": "Parola en az 1 küçük harf içermelidir",
+ "number": "Parola en az 1 sayı içermelidir",
+ "special": "Parola en az 1 özel karakter içermelidir",
+ "incorrect": "Parolanız sistemdeki ile uyuşmuyor"
}
},
"passwordConfirm": {
- "label": "",
- "placeholder": "",
+ "label": "Parolayı onayla",
+ "placeholder": "Onaylamak için parolayı yeniden gir",
"errors": {
"empty": "",
"different": ""
@@ -572,78 +572,85 @@
}
},
"lastName": {
- "label": "",
- "placeholder": "",
+ "label": "Soyadı",
+ "placeholder": "Ellis",
"errors": {
- "empty": "",
- "length": "",
+ "empty": "Lütfen soyadınızı girin",
+ "length": "Soyadı en çok 50 karakter olmalıdır",
"pattern": ""
}
}
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
"heading": "",
"subheadings": {
- "stepOne": "",
- "stepTwo": ""
+ "stepOne": "Eposta adresinizi girin",
+ "stepTwo": "Parolanızı girin"
},
"links": {
- "forgotPassword": "",
- "register": "",
- "forgotPasswordLink": "",
- "registerLink": ""
+ "forgotPassword": "Parolanızı mı unuttunuz?",
+ "register": "Hesabınız yok mu?",
+ "forgotPasswordLink": "Parolayı sıfırla",
+ "registerLink": "Buradan kayıt olun"
},
"toasts": {
- "success": "",
- "incorrectPassword": ""
+ "success": "Tekrar hoşgeldiniz! Başarıyla sisteme giriş yaptınız.",
+ "incorrectPassword": "Hatalı parola"
},
"errors": {
"password": {
- "incorrect": ""
+ "incorrect": "Giriş yaptığınız parola sistemdekiyle uyuşmuyor"
}
}
},
"registration": {
"heading": {
- "superAdmin": "",
- "user": ""
+ "superAdmin": "Süper yönetici oluştur",
+ "user": "Kayıt ol"
},
"subheadings": {
- "stepOne": "",
- "stepTwo": "",
- "stepThree": ""
+ "stepOne": "Kişisel bilgilerinizi girin",
+ "stepTwo": "Eposta adresinizi girin",
+ "stepThree": "Parolanızı oluturun"
},
"description": {
- "superAdmin": "",
+ "superAdmin": "Başlamak için süper yönetici hesabı oluşturun",
"user": ""
},
"gettingStartedButton": {
- "superAdmin": "",
+ "superAdmin": "Süper yönetici hesabı oluşturun",
"user": ""
},
"termsAndPolicies": "",
"links": {
- "login": ""
+ "login": "Bir hesabınız mı var? Giriş yapın"
},
"toasts": {
- "success": ""
+ "success": "Hoşgeldiniz! Hesabınız başarıyla oluşturuldu."
}
},
"forgotPassword": {
- "heading": "",
+ "heading": "Parolanızı mı kaybettiniz?",
"subheadings": {
- "stepOne": "",
- "stepTwo": "",
+ "stepOne": "Endişelenmeyin, sıfırlamak için gerekli bilgileri göndereceğiz.",
+ "stepTwo": "Parola sıfırlama için gerekli bilgileri hesabına ilettik.",
"stepThree": "",
"stepFour": ""
},
"buttons": {
- "openEmail": "",
- "resetPassword": ""
+ "openEmail": "Eposta uygulamasını aç",
+ "resetPassword": "Parolayı sıfırla"
},
"imageAlts": {
"passwordKey": "",
@@ -670,75 +677,76 @@
"reconnected": "",
"stillUnreachable": ""
},
- "alertBox": "",
+ "alertBox": "Sunucu bağlantı hatası",
"description": "",
"retryButton": {
- "default": "",
- "processing": ""
+ "default": "Bağlantıyı tekrarla",
+ "processing": "Bağlanıyor..."
}
}
},
"createNotifications": {
- "title": "",
+ "title": "Bildirim kanalları oluştur",
"nameSettings": {
- "title": "",
- "description": "",
- "nameLabel": "",
- "namePlaceholder": ""
+ "title": "İsim",
+ "description": "Entegrasyon için açıklama",
+ "nameLabel": "İsim",
+ "namePlaceholder": "örn. Slack bildirimleri"
},
"typeSettings": {
- "title": "",
+ "title": "Tür",
"description": "",
- "typeLabel": ""
+ "typeLabel": "Tür"
},
"emailSettings": {
- "title": "",
- "description": "",
- "emailLabel": "",
- "emailPlaceholder": ""
+ "title": "Eposta",
+ "description": "Hedef eposta adresi",
+ "emailLabel": "Eposta adresi",
+ "emailPlaceholder": "örn. john@example.com"
},
"slackSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "Slack",
+ "description": "Slack webhook'u burada tanımlayın",
+ "webhookLabel": "Slack webhook adresi",
+ "webhookPlaceholder": "https://hooks.slack.com/services/...\n"
},
"pagerdutySettings": {
- "title": "",
- "description": "",
- "integrationKeyLabel": "",
- "integrationKeyPlaceholder": ""
- },
- "genericWebhookSettings": {
- "title": "",
- "description": "",
- "webhookLabel": "",
- "webhookPlaceholder": ""
+ "title": "PagerDuty",
+ "description": "PagerDuty entegrasyonunu burada yapın",
+ "integrationKeyLabel": "Entegrasyon anahtarı",
+ "integrationKeyPlaceholder": "1234567890"
},
"discordSettings": {
- "title": "",
+ "title": "Discord",
+ "description": "Discord webhook'u buradan ayarlayın",
+ "webhookLabel": "Discord Webhook adresi",
+ "webhookPlaceholder": "https://sunucu-adı.com/webhook\n"
+ },
+ "webhookSettings": {
+ "title": "Webhook",
"description": "",
"webhookLabel": "",
- "webhookPlaceholder": ""
+ "webhookPlaceholder": "https://sunucu-adı.com/webhook\n"
}
},
"notificationConfig": {
- "title": "",
+ "title": "Bildirimler",
"description": ""
},
"monitorStatus": {
"checkingEvery": "",
"withCaptureAgent": "",
- "up": "",
- "down": "",
- "paused": ""
+ "up": "ayakta",
+ "down": "çalışmıyor",
+ "paused": "beklemede"
},
"advancedMatching": "",
"settingsEmailTLSServername": "",
"settingsEmailIgnoreTLS": "",
"settingsEmailRequireTLS": "",
"settingsEmailRejectUnauthorized": "",
- "settingsEmailSecure": "",
+ "settingsEmailSecure": "Güvenli - SSL kullan",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "Bildirimleri dene",
+ "selectAll": "Tümünü seç"
}
diff --git a/client/src/locales/zh-TW.json b/client/src/locales/zh-TW.json
index 6e864b7ee..5f9409f6a 100644
--- a/client/src/locales/zh-TW.json
+++ b/client/src/locales/zh-TW.json
@@ -583,6 +583,13 @@
},
"errors": {
"validation": ""
+ },
+ "fields": {
+ "password": {
+ "errors": {
+ "incorrect": ""
+ }
+ }
}
},
"login": {
@@ -709,13 +716,13 @@
"integrationKeyLabel": "",
"integrationKeyPlaceholder": ""
},
- "genericWebhookSettings": {
+ "discordSettings": {
"title": "",
"description": "",
"webhookLabel": "",
"webhookPlaceholder": ""
},
- "discordSettings": {
+ "webhookSettings": {
"title": "",
"description": "",
"webhookLabel": "",
@@ -740,5 +747,6 @@
"settingsEmailRejectUnauthorized": "",
"settingsEmailSecure": "",
"settingsEmailPool": "",
- "sendTestNotifications": ""
+ "sendTestNotifications": "",
+ "selectAll": ""
}
diff --git a/docker/prod/docker-compose.yaml b/docker/prod/docker-compose.yaml
index 9cc4a82cb..980a0e2f5 100755
--- a/docker/prod/docker-compose.yaml
+++ b/docker/prod/docker-compose.yaml
@@ -21,6 +21,8 @@ services:
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw
+ entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait $${!}; done;'"
+
server:
image: ghcr.io/bluewave-labs/checkmate:backend-demo
restart: always
@@ -29,21 +31,9 @@ services:
env_file:
- server.env
depends_on:
- - redis
- mongodb
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- redis:
- image: ghcr.io/bluewave-labs/checkmate:redis-demo
- restart: always
- volumes:
- - ./redis/data:/data
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 30s
- timeout: 10s
- retries: 5
- start_period: 5s
mongodb:
image: ghcr.io/bluewave-labs/checkmate:mongo-demo
restart: always
diff --git a/server/controllers/logController.js b/server/controllers/logController.js
new file mode 100644
index 000000000..5381fd3fa
--- /dev/null
+++ b/server/controllers/logController.js
@@ -0,0 +1,23 @@
+import { handleError } from "./controllerUtils.js";
+
+const SERVICE_NAME = "JobQueueController";
+
+class LogController {
+ constructor(logger) {
+ this.logger = logger;
+ }
+
+ getLogs = async (req, res, next) => {
+ try {
+ const logs = await this.logger.getLogs();
+ res.success({
+ msg: "Logs fetched successfully",
+ data: logs,
+ });
+ } catch (error) {
+ next(handleError(error, SERVICE_NAME, "getLogs"));
+ return;
+ }
+ };
+}
+export default LogController;
diff --git a/server/db/models/StatusPage.js b/server/db/models/StatusPage.js
index e4c81707f..92cbab1cd 100755
--- a/server/db/models/StatusPage.js
+++ b/server/db/models/StatusPage.js
@@ -70,6 +70,10 @@ const StatusPageSchema = mongoose.Schema(
type: Boolean,
default: true,
},
+ showAdminLoginLink: {
+ type: Boolean,
+ default: false,
+ },
},
{ timestamps: true }
);
diff --git a/server/db/mongo/modules/statusPageModule.js b/server/db/mongo/modules/statusPageModule.js
index d6331fcf8..e94ffbd55 100755
--- a/server/db/mongo/modules/statusPageModule.js
+++ b/server/db/mongo/modules/statusPageModule.js
@@ -153,6 +153,7 @@ const getStatusPage = async (url) => {
showCharts: 1,
showUptimePercentage: 1,
timezone: 1,
+ showAdminLoginLink: 1,
url: 1,
},
monitors: {
diff --git a/server/index.js b/server/index.js
index 99be6de7a..4b74ef46c 100755
--- a/server/index.js
+++ b/server/index.js
@@ -36,6 +36,9 @@ import StatusPageController from "./controllers/statusPageController.js";
import QueueRoutes from "./routes/queueRoute.js";
import QueueController from "./controllers/queueController.js";
+import LogRoutes from "./routes/logRoutes.js";
+import LogController from "./controllers/logController.js";
+
import NotificationRoutes from "./routes/notificationRoute.js";
import NotificationController from "./controllers/notificationController.js";
@@ -294,6 +297,8 @@ const startApp = async () => {
ServiceRegistry.get(StringService.SERVICE_NAME)
);
+ const logController = new LogController(logger);
+
const statusPageController = new StatusPageController(
ServiceRegistry.get(MongoDB.SERVICE_NAME),
ServiceRegistry.get(StringService.SERVICE_NAME)
@@ -320,6 +325,7 @@ const startApp = async () => {
maintenanceWindowController
);
const queueRoutes = new QueueRoutes(queueController);
+ const logRoutes = new LogRoutes(logController);
const statusPageRoutes = new StatusPageRoutes(statusPageController);
const notificationRoutes = new NotificationRoutes(notificationController);
@@ -372,6 +378,7 @@ const startApp = async () => {
app.use("/api/v1/checks", verifyJWT, checkRoutes.getRouter());
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRoutes.getRouter());
app.use("/api/v1/queue", verifyJWT, queueRoutes.getRouter());
+ app.use("/api/v1/logs", verifyJWT, logRoutes.getRouter());
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
diff --git a/server/middleware/verifyOwnership.js b/server/middleware/verifyOwnership.js
index ca2476f54..011b178a4 100755
--- a/server/middleware/verifyOwnership.js
+++ b/server/middleware/verifyOwnership.js
@@ -8,6 +8,7 @@ const verifyOwnership = (Model, paramName) => {
return async (req, res, next) => {
const userId = req.user._id;
const documentId = req.params[paramName];
+
try {
const doc = await Model.findById(documentId);
//If the document is not found, return a 404 error
@@ -35,7 +36,7 @@ const verifyOwnership = (Model, paramName) => {
// If the userID does not match the document's userID, return a 403 error
if (userId.toString() !== doc.userId.toString()) {
- const error = new Error(stringService.verifyOwnerUnauthorized);
+ const error = new Error("Unauthorized");
error.status = 403;
throw error;
}
diff --git a/server/middleware/verifyTeamAccess.js b/server/middleware/verifyTeamAccess.js
new file mode 100644
index 000000000..60647bdc2
--- /dev/null
+++ b/server/middleware/verifyTeamAccess.js
@@ -0,0 +1,38 @@
+const SERVICE_NAME = "verifyTeamAccess";
+
+const verifyTeamAccess = (Model, paramName) => {
+ return async (req, res, next) => {
+ try {
+ const documentId = req.params[paramName];
+ const doc = await Model.findById(documentId);
+
+ if (!doc) {
+ const error = new Error("Document not found");
+ error.status = 404;
+ throw error;
+ }
+
+ if (!req?.user?.teamId || !doc.teamId) {
+ const error = new Error("Missing team information");
+ error.status = 400;
+ throw error;
+ }
+
+ if (req.user.teamId.toString() === doc.teamId.toString()) {
+ next();
+ return;
+ }
+
+ const error = new Error("Unauthorized");
+ error.status = 403;
+ throw error;
+ } catch (error) {
+ error.service = SERVICE_NAME;
+ error.method = "verifyTeamAccess";
+ next(error);
+ return;
+ }
+ };
+};
+
+export { verifyTeamAccess };
diff --git a/server/routes/logRoutes.js b/server/routes/logRoutes.js
new file mode 100755
index 000000000..867826dde
--- /dev/null
+++ b/server/routes/logRoutes.js
@@ -0,0 +1,18 @@
+import { Router } from "express";
+import { isAllowed } from "../middleware/isAllowed.js";
+class LogRoutes {
+ constructor(logController) {
+ this.router = Router();
+ this.logController = logController;
+ this.initRoutes();
+ }
+ initRoutes() {
+ this.router.get("/", isAllowed(["admin", "superadmin"]), this.logController.getLogs);
+ }
+
+ getRouter() {
+ return this.router;
+ }
+}
+
+export default LogRoutes;
diff --git a/server/routes/maintenanceWindowRoute.js b/server/routes/maintenanceWindowRoute.js
index 2665c2bb8..36f982e25 100755
--- a/server/routes/maintenanceWindowRoute.js
+++ b/server/routes/maintenanceWindowRoute.js
@@ -1,7 +1,8 @@
import { Router } from "express";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
+import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js";
import Monitor from "../db/models/Monitor.js";
-
+import MaintenanceWindow from "../db/models/MaintenanceWindow.js";
class MaintenanceWindowRoutes {
constructor(maintenanceWindowController) {
this.router = Router();
@@ -24,9 +25,17 @@ class MaintenanceWindowRoutes {
this.router.get("/:id", this.maintenanceWindowController.getMaintenanceWindowById);
- this.router.put("/:id", this.maintenanceWindowController.editMaintenanceWindow);
+ this.router.put(
+ "/:id",
+ verifyTeamAccess(MaintenanceWindow, "id"),
+ this.maintenanceWindowController.editMaintenanceWindow
+ );
- this.router.delete("/:id", this.maintenanceWindowController.deleteMaintenanceWindow);
+ this.router.delete(
+ "/:id",
+ verifyTeamAccess(MaintenanceWindow, "id"),
+ this.maintenanceWindowController.deleteMaintenanceWindow
+ );
}
getRouter() {
diff --git a/server/routes/monitorRoute.js b/server/routes/monitorRoute.js
index 2c6ae870e..168f8b88d 100755
--- a/server/routes/monitorRoute.js
+++ b/server/routes/monitorRoute.js
@@ -2,7 +2,9 @@ import { Router } from "express";
import { isAllowed } from "../middleware/isAllowed.js";
import multer from "multer";
import { fetchMonitorCertificate } from "../controllers/controllerUtils.js";
-
+import Monitor from "../db/models/Monitor.js";
+import { verifyOwnership } from "../middleware/verifyOwnership.js";
+import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js";
const upload = multer({
storage: multer.memoryStorage(), // Store file in memory as Buffer
});
@@ -62,6 +64,7 @@ class MonitorRoutes {
this.router.delete(
"/:monitorId",
+ verifyOwnership(Monitor, "monitorId"),
isAllowed(["admin", "superadmin"]),
this.monitorController.deleteMonitor
);
@@ -74,6 +77,7 @@ class MonitorRoutes {
this.router.put(
"/:monitorId",
+ verifyTeamAccess(Monitor, "monitorId"),
isAllowed(["admin", "superadmin"]),
this.monitorController.editMonitor
);
diff --git a/server/routes/notificationRoute.js b/server/routes/notificationRoute.js
index 164c0afcc..09becd16c 100755
--- a/server/routes/notificationRoute.js
+++ b/server/routes/notificationRoute.js
@@ -1,6 +1,8 @@
import { Router } from "express";
import { verifyJWT } from "../middleware/verifyJWT.js";
-
+import { verifyOwnership } from "../middleware/verifyOwnership.js";
+import { verifyTeamAccess } from "../middleware/verifyTeamAccess.js";
+import Notification from "../db/models/Notification.js";
class NotificationRoutes {
constructor(notificationController) {
this.router = Router();
@@ -18,10 +20,18 @@ class NotificationRoutes {
this.router.get("/team", this.notificationController.getNotificationsByTeamId);
- this.router.delete("/:id", this.notificationController.deleteNotification);
+ this.router.delete(
+ "/:id",
+ verifyOwnership(Notification, "id"),
+ this.notificationController.deleteNotification
+ );
this.router.get("/:id", this.notificationController.getNotificationById);
- this.router.put("/:id", this.notificationController.editNotification);
+ this.router.put(
+ "/:id",
+ verifyTeamAccess(Notification, "id"),
+ this.notificationController.editNotification
+ );
}
getRouter() {
diff --git a/server/utils/logger.js b/server/utils/logger.js
index 155adc957..0864176c5 100755
--- a/server/utils/logger.js
+++ b/server/utils/logger.js
@@ -4,6 +4,8 @@ dotenv.config();
class Logger {
constructor() {
+ this.logCache = [];
+ this.maxCacheSize = 1000;
const consoleFormat = format.printf(
({ level, message, service, method, details, timestamp, stack }) => {
if (message instanceof Object) {
@@ -72,11 +74,9 @@ class Logger {
* @param {Object} config.details - Additional details.
*/
info(config) {
- this.logger.info(config.message, {
- service: config.service,
- method: config.method,
- details: config.details,
- });
+ const logEntry = this.buildLogEntry("info", config);
+ this.cacheLog(logEntry);
+ this.logger.info(config.message, logEntry);
}
/**
@@ -88,11 +88,9 @@ class Logger {
* @param {Object} config.details - Additional details.
*/
warn(config) {
- this.logger.warn(config.message, {
- service: config.service,
- method: config.method,
- details: config.details,
- });
+ const logEntry = this.buildLogEntry("warn", config);
+ this.cacheLog(logEntry);
+ this.logger.warn(config.message, logEntry);
}
/**
@@ -104,12 +102,9 @@ class Logger {
* @param {Object} config.details - Additional details.
*/
error(config) {
- this.logger.error(config.message, {
- service: config.service,
- method: config.method,
- details: config.details,
- stack: config.stack,
- });
+ const logEntry = this.buildLogEntry("error", config);
+ this.cacheLog(logEntry);
+ this.logger.error(config.message, logEntry);
}
/**
* Logs a debug message.
@@ -120,12 +115,32 @@ class Logger {
* @param {Object} config.details - Additional details.
*/
debug(config) {
- this.logger.debug(config.message, {
+ const logEntry = this.buildLogEntry("debug", config);
+ this.cacheLog(logEntry);
+ this.logger.debug(config.message, logEntry);
+ }
+
+ cacheLog(entry) {
+ this.logCache.push(entry);
+ if (this.logCache.length > this.maxCacheSize) {
+ this.logCache.shift();
+ }
+ }
+
+ getLogs() {
+ return this.logCache;
+ }
+
+ buildLogEntry(level, config) {
+ return {
+ level,
+ message: config.message,
service: config.service,
method: config.method,
details: config.details,
stack: config.stack,
- });
+ timestamp: new Date().toISOString(),
+ };
}
}
diff --git a/server/validation/joi.js b/server/validation/joi.js
index 448a844f1..e5c1d6e43 100755
--- a/server/validation/joi.js
+++ b/server/validation/joi.js
@@ -463,6 +463,7 @@ const createStatusPageBodyValidation = joi.object({
isPublished: joi.boolean(),
showCharts: joi.boolean().optional(),
showUptimePercentage: joi.boolean(),
+ showAdminLoginLink: joi.boolean().optional(),
});
const imageValidation = joi