mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-27 23:47:09 -05:00
Compare commits
2 Commits
03aab90c90
...
d903e96e13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d903e96e13 | ||
|
|
a74b8d6e74 |
119
package-lock.json
generated
119
package-lock.json
generated
@@ -65,6 +65,7 @@
|
|||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
|
"idb": "^8.0.3",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
@@ -108,6 +109,7 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -1591,6 +1593,7 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -1616,6 +1619,7 @@
|
|||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -4454,7 +4458,7 @@
|
|||||||
"version": "1.14.0",
|
"version": "1.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.14.0.tgz",
|
||||||
"integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==",
|
"integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4663,7 +4667,7 @@
|
|||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
@@ -4679,7 +4683,7 @@
|
|||||||
"version": "0.1.25",
|
"version": "0.1.25",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@swc/counter": "^0.1.3"
|
"@swc/counter": "^0.1.3"
|
||||||
@@ -4976,12 +4980,14 @@
|
|||||||
"version": "15.7.15",
|
"version": "15.7.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.26",
|
"version": "18.3.26",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
||||||
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -4992,7 +4998,7 @@
|
|||||||
"version": "18.3.7",
|
"version": "18.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^18.0.0"
|
"@types/react": "^18.0.0"
|
||||||
@@ -5838,12 +5844,14 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@@ -5857,6 +5865,7 @@
|
|||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
@@ -6007,6 +6016,7 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -6131,6 +6141,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -6228,6 +6239,7 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
@@ -6252,6 +6264,7 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -6401,6 +6414,7 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -6472,6 +6486,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"cssesc": "bin/cssesc"
|
"cssesc": "bin/cssesc"
|
||||||
@@ -6723,6 +6738,7 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/diff": {
|
"node_modules/diff": {
|
||||||
@@ -6738,6 +6754,7 @@
|
|||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dom-helpers": {
|
"node_modules/dom-helpers": {
|
||||||
@@ -7944,6 +7961,7 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -8033,6 +8051,7 @@
|
|||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
@@ -8257,6 +8276,12 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/idb": {
|
||||||
|
"version": "8.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz",
|
||||||
|
"integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@@ -8374,6 +8399,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
@@ -8386,6 +8412,7 @@
|
|||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
@@ -8498,17 +8525,6 @@
|
|||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/isomorphic.js": {
|
|
||||||
"version": "0.2.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz",
|
|
||||||
"integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
|
||||||
"type": "GitHub Sponsors ❤",
|
|
||||||
"url": "https://github.com/sponsors/dmonad"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jackspeak": {
|
"node_modules/jackspeak": {
|
||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
@@ -8528,6 +8544,7 @@
|
|||||||
"version": "1.21.7",
|
"version": "1.21.7",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
@@ -8621,32 +8638,11 @@
|
|||||||
"integrity": "sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==",
|
"integrity": "sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lib0": {
|
|
||||||
"version": "0.2.114",
|
|
||||||
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz",
|
|
||||||
"integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"isomorphic.js": "^0.2.4"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
|
|
||||||
"0gentesthtml": "bin/gentesthtml.js",
|
|
||||||
"0serve": "bin/0serve.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "GitHub Sponsors ❤",
|
|
||||||
"url": "https://github.com/sponsors/dmonad"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lilconfig": {
|
"node_modules/lilconfig": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
@@ -8659,6 +8655,7 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
@@ -9910,6 +9907,7 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0",
|
"any-promise": "^1.0.0",
|
||||||
@@ -10027,6 +10025,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -10055,6 +10054,7 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -10252,6 +10252,7 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
@@ -10287,6 +10288,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@@ -10305,6 +10307,7 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -10314,6 +10317,7 @@
|
|||||||
"version": "4.0.7",
|
"version": "4.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -10367,6 +10371,7 @@
|
|||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10395,6 +10400,7 @@
|
|||||||
"version": "15.1.0",
|
"version": "15.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"postcss-value-parser": "^4.0.0",
|
"postcss-value-parser": "^4.0.0",
|
||||||
@@ -10412,6 +10418,7 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
||||||
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10437,6 +10444,7 @@
|
|||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
||||||
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
|
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10479,6 +10487,7 @@
|
|||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -10504,6 +10513,7 @@
|
|||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
@@ -10531,12 +10541,14 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/postcss/node_modules/nanoid": {
|
"node_modules/postcss/node_modules/nanoid": {
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -10928,6 +10940,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pify": "^2.3.0"
|
"pify": "^2.3.0"
|
||||||
@@ -10937,6 +10950,7 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@@ -11049,6 +11063,7 @@
|
|||||||
"version": "1.22.11",
|
"version": "1.22.11",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.16.1",
|
"is-core-module": "^2.16.1",
|
||||||
@@ -11098,7 +11113,7 @@
|
|||||||
"version": "4.52.5",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||||
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.8"
|
"@types/estree": "1.0.8"
|
||||||
@@ -11402,6 +11417,7 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -11586,6 +11602,7 @@
|
|||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
@@ -11621,6 +11638,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -11649,6 +11667,7 @@
|
|||||||
"version": "3.4.18",
|
"version": "3.4.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
|
||||||
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
|
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -11695,6 +11714,7 @@
|
|||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
@@ -11724,6 +11744,7 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0"
|
"any-promise": "^1.0.0"
|
||||||
@@ -11733,6 +11754,7 @@
|
|||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"thenify": ">= 3.1.0 < 4"
|
"thenify": ">= 3.1.0 < 4"
|
||||||
@@ -11817,6 +11839,7 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/ts-morph": {
|
"node_modules/ts-morph": {
|
||||||
@@ -11922,6 +11945,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -12203,6 +12227,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
"node_modules/uuid": {
|
||||||
@@ -12980,24 +13005,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yjs": {
|
|
||||||
"version": "13.6.27",
|
|
||||||
"resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz",
|
|
||||||
"integrity": "sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"lib0": "^0.2.99"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.0.0",
|
|
||||||
"npm": ">=8.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "GitHub Sponsors ❤",
|
|
||||||
"url": "https://github.com/sponsors/dmonad"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yn": {
|
"node_modules/yn": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"dompurify": "^3.3.0",
|
"dompurify": "^3.3.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
|
"idb": "^8.0.3",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
|
|||||||
124
src/components/admin/PipelineHealthAlerts.tsx
Normal file
124
src/components/admin/PipelineHealthAlerts.tsx
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* Pipeline Health Alerts Component
|
||||||
|
*
|
||||||
|
* Displays critical pipeline alerts on the admin error monitoring dashboard.
|
||||||
|
* Shows top 10 active alerts with severity-based styling and resolution actions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { useSystemAlerts } from '@/hooks/useSystemHealth';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { AlertTriangle, CheckCircle, XCircle, AlertCircle } from 'lucide-react';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import { supabase } from '@/lib/supabaseClient';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
const SEVERITY_CONFIG = {
|
||||||
|
critical: { color: 'destructive', icon: XCircle },
|
||||||
|
high: { color: 'destructive', icon: AlertCircle },
|
||||||
|
medium: { color: 'default', icon: AlertTriangle },
|
||||||
|
low: { color: 'secondary', icon: CheckCircle },
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const ALERT_TYPE_LABELS: Record<string, string> = {
|
||||||
|
failed_submissions: 'Failed Submissions',
|
||||||
|
high_ban_rate: 'High Ban Attempt Rate',
|
||||||
|
temp_ref_error: 'Temp Reference Error',
|
||||||
|
orphaned_images: 'Orphaned Images',
|
||||||
|
slow_approval: 'Slow Approvals',
|
||||||
|
submission_queue_backlog: 'Queue Backlog',
|
||||||
|
ban_attempt: 'Ban Attempt',
|
||||||
|
upload_timeout: 'Upload Timeout',
|
||||||
|
high_error_rate: 'High Error Rate',
|
||||||
|
validation_error: 'Validation Error',
|
||||||
|
stale_submissions: 'Stale Submissions',
|
||||||
|
circular_dependency: 'Circular Dependency',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PipelineHealthAlerts() {
|
||||||
|
const { data: criticalAlerts } = useSystemAlerts('critical');
|
||||||
|
const { data: highAlerts } = useSystemAlerts('high');
|
||||||
|
const { data: mediumAlerts } = useSystemAlerts('medium');
|
||||||
|
|
||||||
|
const allAlerts = [
|
||||||
|
...(criticalAlerts || []),
|
||||||
|
...(highAlerts || []),
|
||||||
|
...(mediumAlerts || [])
|
||||||
|
].slice(0, 10);
|
||||||
|
|
||||||
|
const resolveAlert = async (alertId: string) => {
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('system_alerts')
|
||||||
|
.update({ resolved_at: new Date().toISOString() })
|
||||||
|
.eq('id', alertId);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
toast.error('Failed to resolve alert');
|
||||||
|
} else {
|
||||||
|
toast.success('Alert resolved');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!allAlerts.length) {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-500" />
|
||||||
|
Pipeline Health: All Systems Operational
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-sm text-muted-foreground">No active alerts. The sacred pipeline is flowing smoothly.</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>🚨 Active Pipeline Alerts</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Critical issues requiring attention ({allAlerts.length} active)
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{allAlerts.map((alert) => {
|
||||||
|
const config = SEVERITY_CONFIG[alert.severity];
|
||||||
|
const Icon = config.icon;
|
||||||
|
const label = ALERT_TYPE_LABELS[alert.alert_type] || alert.alert_type;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={alert.id}
|
||||||
|
className="flex items-start justify-between p-3 border rounded-lg hover:bg-accent transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-3 flex-1">
|
||||||
|
<Icon className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<Badge variant={config.color as any}>{alert.severity.toUpperCase()}</Badge>
|
||||||
|
<span className="text-sm font-medium">{label}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">{alert.message}</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
{format(new Date(alert.created_at), 'PPp')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => resolveAlert(alert.id)}
|
||||||
|
>
|
||||||
|
Resolve
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6311,9 +6311,19 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Returns: undefined
|
Returns: undefined
|
||||||
}
|
}
|
||||||
mark_orphaned_images: { Args: never; Returns: undefined }
|
mark_orphaned_images: {
|
||||||
|
Args: never
|
||||||
|
Returns: {
|
||||||
|
details: Json
|
||||||
|
status: string
|
||||||
|
task: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
migrate_ride_technical_data: { Args: never; Returns: undefined }
|
migrate_ride_technical_data: { Args: never; Returns: undefined }
|
||||||
migrate_user_list_items: { Args: never; Returns: undefined }
|
migrate_user_list_items: { Args: never; Returns: undefined }
|
||||||
|
monitor_ban_attempts: { Args: never; Returns: undefined }
|
||||||
|
monitor_failed_submissions: { Args: never; Returns: undefined }
|
||||||
|
monitor_slow_approvals: { Args: never; Returns: undefined }
|
||||||
process_approval_transaction: {
|
process_approval_transaction: {
|
||||||
Args: {
|
Args: {
|
||||||
p_idempotency_key?: string
|
p_idempotency_key?: string
|
||||||
@@ -6349,6 +6359,14 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Returns: string
|
Returns: string
|
||||||
}
|
}
|
||||||
|
run_pipeline_monitoring: {
|
||||||
|
Args: never
|
||||||
|
Returns: {
|
||||||
|
check_name: string
|
||||||
|
details: Json
|
||||||
|
status: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
run_system_maintenance: {
|
run_system_maintenance: {
|
||||||
Args: never
|
Args: never
|
||||||
Returns: {
|
Returns: {
|
||||||
|
|||||||
@@ -411,6 +411,51 @@ async function submitCompositeCreation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Validate all temp refs were properly resolved
|
||||||
|
const validateTempRefs = () => {
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
if (uploadedPrimary.type === 'park') {
|
||||||
|
if ('_temp_operator_ref' in primaryData && primaryData._temp_operator_ref === undefined) {
|
||||||
|
errors.push('Invalid operator reference - dependency not found');
|
||||||
|
}
|
||||||
|
if ('_temp_property_owner_ref' in primaryData && primaryData._temp_property_owner_ref === undefined) {
|
||||||
|
errors.push('Invalid property owner reference - dependency not found');
|
||||||
|
}
|
||||||
|
} else if (uploadedPrimary.type === 'ride') {
|
||||||
|
if ('_temp_park_ref' in primaryData && primaryData._temp_park_ref === undefined) {
|
||||||
|
errors.push('Invalid park reference - dependency not found');
|
||||||
|
}
|
||||||
|
if ('_temp_manufacturer_ref' in primaryData && primaryData._temp_manufacturer_ref === undefined) {
|
||||||
|
errors.push('Invalid manufacturer reference - dependency not found');
|
||||||
|
}
|
||||||
|
if ('_temp_designer_ref' in primaryData && primaryData._temp_designer_ref === undefined) {
|
||||||
|
errors.push('Invalid designer reference - dependency not found');
|
||||||
|
}
|
||||||
|
if ('_temp_ride_model_ref' in primaryData && primaryData._temp_ride_model_ref === undefined) {
|
||||||
|
errors.push('Invalid ride model reference - dependency not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
// Report to system alerts (non-blocking)
|
||||||
|
import('./pipelineAlerts').then(async ({ reportTempRefError }) => {
|
||||||
|
try {
|
||||||
|
const { data: { user } } = await supabase.auth.getUser();
|
||||||
|
if (user) {
|
||||||
|
await reportTempRefError(uploadedPrimary.type, errors, user.id);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to report temp ref error:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(`Temp reference validation failed: ${errors.join(', ')}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
validateTempRefs();
|
||||||
|
|
||||||
submissionItems.push({
|
submissionItems.push({
|
||||||
item_type: uploadedPrimary.type,
|
item_type: uploadedPrimary.type,
|
||||||
action_type: 'create' as const,
|
action_type: 'create' as const,
|
||||||
|
|||||||
@@ -62,17 +62,34 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Step 2: Upload file directly to Cloudflare (with timeout)
|
// Step 2: Upload file directly to Cloudflare with retry on transient failures
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', image.file);
|
formData.append('file', image.file);
|
||||||
|
|
||||||
const uploadResponse = await withTimeout(
|
const { withRetry } = await import('./retryHelpers');
|
||||||
fetch(uploadUrlData.uploadURL, {
|
const uploadResponse = await withRetry(
|
||||||
method: 'POST',
|
() => withTimeout(
|
||||||
body: formData,
|
fetch(uploadUrlData.uploadURL, {
|
||||||
}),
|
method: 'POST',
|
||||||
UPLOAD_TIMEOUT_MS,
|
body: formData,
|
||||||
'Cloudflare upload'
|
}),
|
||||||
|
UPLOAD_TIMEOUT_MS,
|
||||||
|
'Cloudflare upload'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
maxAttempts: 3,
|
||||||
|
baseDelay: 500,
|
||||||
|
shouldRetry: (error) => {
|
||||||
|
// Retry on network errors, timeouts, or 5xx errors
|
||||||
|
if (error instanceof Error) {
|
||||||
|
const msg = error.message.toLowerCase();
|
||||||
|
if (msg.includes('timeout')) return true;
|
||||||
|
if (msg.includes('network')) return true;
|
||||||
|
if (msg.includes('failed to fetch')) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!uploadResponse.ok) {
|
if (!uploadResponse.ok) {
|
||||||
|
|||||||
82
src/lib/pipelineAlerts.ts
Normal file
82
src/lib/pipelineAlerts.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Pipeline Alert Reporting
|
||||||
|
*
|
||||||
|
* Client-side utilities for reporting critical pipeline issues to system alerts.
|
||||||
|
* Non-blocking operations that enhance monitoring without disrupting user flows.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { supabase } from '@/lib/supabaseClient';
|
||||||
|
import { handleNonCriticalError } from '@/lib/errorHandler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report temp ref validation errors to system alerts
|
||||||
|
* Called when validateTempRefs() fails in entitySubmissionHelpers
|
||||||
|
*/
|
||||||
|
export async function reportTempRefError(
|
||||||
|
entityType: 'park' | 'ride',
|
||||||
|
errors: string[],
|
||||||
|
userId: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
await supabase.rpc('create_system_alert', {
|
||||||
|
p_alert_type: 'temp_ref_error',
|
||||||
|
p_severity: 'high',
|
||||||
|
p_message: `Temp reference validation failed for ${entityType}: ${errors.join(', ')}`,
|
||||||
|
p_metadata: {
|
||||||
|
entity_type: entityType,
|
||||||
|
errors,
|
||||||
|
user_id: userId,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleNonCriticalError(error, {
|
||||||
|
action: 'Report temp ref error to alerts'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report submission queue backlog
|
||||||
|
* Called when IndexedDB queue exceeds threshold
|
||||||
|
*/
|
||||||
|
export async function reportQueueBacklog(
|
||||||
|
pendingCount: number,
|
||||||
|
userId?: string
|
||||||
|
): Promise<void> {
|
||||||
|
// Only report if backlog > 10
|
||||||
|
if (pendingCount <= 10) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await supabase.rpc('create_system_alert', {
|
||||||
|
p_alert_type: 'submission_queue_backlog',
|
||||||
|
p_severity: pendingCount > 50 ? 'high' : 'medium',
|
||||||
|
p_message: `Submission queue backlog: ${pendingCount} pending submissions`,
|
||||||
|
p_metadata: {
|
||||||
|
pending_count: pendingCount,
|
||||||
|
user_id: userId,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleNonCriticalError(error, {
|
||||||
|
action: 'Report queue backlog to alerts'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check queue status and report if needed
|
||||||
|
* Called on app startup and periodically
|
||||||
|
*/
|
||||||
|
export async function checkAndReportQueueStatus(userId?: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const { getPendingCount } = await import('./submissionQueue');
|
||||||
|
const pendingCount = await getPendingCount();
|
||||||
|
await reportQueueBacklog(pendingCount, userId);
|
||||||
|
} catch (error) {
|
||||||
|
handleNonCriticalError(error, {
|
||||||
|
action: 'Check queue status'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/lib/submissionQueue.ts
Normal file
192
src/lib/submissionQueue.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/**
|
||||||
|
* Submission Queue with IndexedDB Fallback
|
||||||
|
*
|
||||||
|
* Provides resilience when edge functions are unavailable by queuing
|
||||||
|
* submissions locally and retrying when connectivity is restored.
|
||||||
|
*
|
||||||
|
* Part of Sacred Pipeline Phase 3: Fortify Defenses
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { openDB, DBSchema, IDBPDatabase } from 'idb';
|
||||||
|
|
||||||
|
interface SubmissionQueueDB extends DBSchema {
|
||||||
|
submissions: {
|
||||||
|
key: string;
|
||||||
|
value: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
data: any;
|
||||||
|
timestamp: number;
|
||||||
|
retries: number;
|
||||||
|
lastAttempt: number | null;
|
||||||
|
error: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const DB_NAME = 'thrillwiki-submission-queue';
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
const STORE_NAME = 'submissions';
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
|
||||||
|
let dbInstance: IDBPDatabase<SubmissionQueueDB> | null = null;
|
||||||
|
|
||||||
|
async function getDB(): Promise<IDBPDatabase<SubmissionQueueDB>> {
|
||||||
|
if (dbInstance) return dbInstance;
|
||||||
|
|
||||||
|
dbInstance = await openDB<SubmissionQueueDB>(DB_NAME, DB_VERSION, {
|
||||||
|
upgrade(db) {
|
||||||
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||||
|
db.createObjectStore(STORE_NAME, { keyPath: 'id' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return dbInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue a submission for later processing
|
||||||
|
*/
|
||||||
|
export async function queueSubmission(type: string, data: any): Promise<string> {
|
||||||
|
const db = await getDB();
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
|
await db.add(STORE_NAME, {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
data,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
retries: 0,
|
||||||
|
lastAttempt: null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info(`[SubmissionQueue] Queued ${type} submission ${id}`);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all pending submissions
|
||||||
|
*/
|
||||||
|
export async function getPendingSubmissions() {
|
||||||
|
const db = await getDB();
|
||||||
|
return await db.getAll(STORE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get count of pending submissions
|
||||||
|
*/
|
||||||
|
export async function getPendingCount(): Promise<number> {
|
||||||
|
const db = await getDB();
|
||||||
|
const all = await db.getAll(STORE_NAME);
|
||||||
|
return all.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a submission from the queue
|
||||||
|
*/
|
||||||
|
export async function removeFromQueue(id: string): Promise<void> {
|
||||||
|
const db = await getDB();
|
||||||
|
await db.delete(STORE_NAME, id);
|
||||||
|
console.info(`[SubmissionQueue] Removed submission ${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update submission retry count and error
|
||||||
|
*/
|
||||||
|
export async function updateSubmissionRetry(
|
||||||
|
id: string,
|
||||||
|
error: string
|
||||||
|
): Promise<void> {
|
||||||
|
const db = await getDB();
|
||||||
|
const item = await db.get(STORE_NAME, id);
|
||||||
|
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
item.retries += 1;
|
||||||
|
item.lastAttempt = Date.now();
|
||||||
|
item.error = error;
|
||||||
|
|
||||||
|
await db.put(STORE_NAME, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process all queued submissions
|
||||||
|
* Called when connectivity is restored or on app startup
|
||||||
|
*/
|
||||||
|
export async function processQueue(
|
||||||
|
submitFn: (type: string, data: any) => Promise<void>
|
||||||
|
): Promise<{ processed: number; failed: number }> {
|
||||||
|
const db = await getDB();
|
||||||
|
const pending = await db.getAll(STORE_NAME);
|
||||||
|
|
||||||
|
let processed = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (const item of pending) {
|
||||||
|
try {
|
||||||
|
console.info(`[SubmissionQueue] Processing ${item.type} submission ${item.id} (attempt ${item.retries + 1})`);
|
||||||
|
|
||||||
|
await submitFn(item.type, item.data);
|
||||||
|
await db.delete(STORE_NAME, item.id);
|
||||||
|
processed++;
|
||||||
|
|
||||||
|
console.info(`[SubmissionQueue] Successfully processed ${item.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
if (item.retries >= MAX_RETRIES - 1) {
|
||||||
|
// Max retries exceeded, remove from queue
|
||||||
|
await db.delete(STORE_NAME, item.id);
|
||||||
|
failed++;
|
||||||
|
console.error(`[SubmissionQueue] Max retries exceeded for ${item.id}:`, errorMsg);
|
||||||
|
} else {
|
||||||
|
// Update retry count
|
||||||
|
await updateSubmissionRetry(item.id, errorMsg);
|
||||||
|
console.warn(`[SubmissionQueue] Retry ${item.retries + 1}/${MAX_RETRIES} failed for ${item.id}:`, errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { processed, failed };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all queued submissions (use with caution!)
|
||||||
|
*/
|
||||||
|
export async function clearQueue(): Promise<number> {
|
||||||
|
const db = await getDB();
|
||||||
|
const tx = db.transaction(STORE_NAME, 'readwrite');
|
||||||
|
const store = tx.objectStore(STORE_NAME);
|
||||||
|
const all = await store.getAll();
|
||||||
|
|
||||||
|
await store.clear();
|
||||||
|
await tx.done;
|
||||||
|
|
||||||
|
console.warn(`[SubmissionQueue] Cleared ${all.length} submissions from queue`);
|
||||||
|
return all.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if edge function is available
|
||||||
|
*/
|
||||||
|
export async function checkEdgeFunctionHealth(
|
||||||
|
functionUrl: string
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
|
|
||||||
|
const response = await fetch(functionUrl, {
|
||||||
|
method: 'HEAD',
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(timeout);
|
||||||
|
return response.ok || response.status === 405; // 405 = Method Not Allowed is OK
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[SubmissionQueue] Health check failed:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,75 @@ export interface ValidationResult {
|
|||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SlugValidationResult extends ValidationResult {
|
||||||
|
suggestedSlug?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates slug format matching database constraints
|
||||||
|
* Pattern: lowercase alphanumeric with hyphens only
|
||||||
|
* No consecutive hyphens, no leading/trailing hyphens
|
||||||
|
*/
|
||||||
|
export function validateSlugFormat(slug: string): SlugValidationResult {
|
||||||
|
if (!slug) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
missingFields: ['slug'],
|
||||||
|
errorMessage: 'Slug is required'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must match DB regex: ^[a-z0-9]+(-[a-z0-9]+)*$
|
||||||
|
const slugRegex = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
||||||
|
if (!slugRegex.test(slug)) {
|
||||||
|
const suggested = slug
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9-]/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-|-$/g, '');
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
missingFields: ['slug'],
|
||||||
|
errorMessage: 'Slug must be lowercase alphanumeric with hyphens only (no spaces or special characters)',
|
||||||
|
suggestedSlug: suggested
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length constraints
|
||||||
|
if (slug.length < 2) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
missingFields: ['slug'],
|
||||||
|
errorMessage: 'Slug too short (minimum 2 characters)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (slug.length > 100) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
missingFields: ['slug'],
|
||||||
|
errorMessage: 'Slug too long (maximum 100 characters)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserved slugs that could conflict with routes
|
||||||
|
const reserved = [
|
||||||
|
'admin', 'api', 'auth', 'new', 'edit', 'delete', 'create',
|
||||||
|
'update', 'null', 'undefined', 'settings', 'profile', 'login',
|
||||||
|
'logout', 'signup', 'dashboard', 'moderator', 'moderation'
|
||||||
|
];
|
||||||
|
if (reserved.includes(slug)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
missingFields: ['slug'],
|
||||||
|
errorMessage: `'${slug}' is a reserved slug and cannot be used`,
|
||||||
|
suggestedSlug: `${slug}-1`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true, missingFields: [] };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates required fields for park creation
|
* Validates required fields for park creation
|
||||||
*/
|
*/
|
||||||
@@ -28,6 +97,14 @@ export function validateParkCreateFields(data: any): ValidationResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate slug format
|
||||||
|
if (data.slug?.trim()) {
|
||||||
|
const slugValidation = validateSlugFormat(data.slug.trim());
|
||||||
|
if (!slugValidation.valid) {
|
||||||
|
return slugValidation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { valid: true, missingFields: [] };
|
return { valid: true, missingFields: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +127,14 @@ export function validateRideCreateFields(data: any): ValidationResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate slug format
|
||||||
|
if (data.slug?.trim()) {
|
||||||
|
const slugValidation = validateSlugFormat(data.slug.trim());
|
||||||
|
if (!slugValidation.valid) {
|
||||||
|
return slugValidation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { valid: true, missingFields: [] };
|
return { valid: true, missingFields: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +156,14 @@ export function validateCompanyCreateFields(data: any): ValidationResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate slug format
|
||||||
|
if (data.slug?.trim()) {
|
||||||
|
const slugValidation = validateSlugFormat(data.slug.trim());
|
||||||
|
if (!slugValidation.valid) {
|
||||||
|
return slugValidation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { valid: true, missingFields: [] };
|
return { valid: true, missingFields: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +186,14 @@ export function validateRideModelCreateFields(data: any): ValidationResult {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate slug format
|
||||||
|
if (data.slug?.trim()) {
|
||||||
|
const slugValidation = validateSlugFormat(data.slug.trim());
|
||||||
|
if (!slugValidation.valid) {
|
||||||
|
return slugValidation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { valid: true, missingFields: [] };
|
return { valid: true, missingFields: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { RefreshButton } from '@/components/ui/refresh-button';
|
|||||||
import { ErrorDetailsModal } from '@/components/admin/ErrorDetailsModal';
|
import { ErrorDetailsModal } from '@/components/admin/ErrorDetailsModal';
|
||||||
import { ApprovalFailureModal } from '@/components/admin/ApprovalFailureModal';
|
import { ApprovalFailureModal } from '@/components/admin/ApprovalFailureModal';
|
||||||
import { ErrorAnalytics } from '@/components/admin/ErrorAnalytics';
|
import { ErrorAnalytics } from '@/components/admin/ErrorAnalytics';
|
||||||
|
import { PipelineHealthAlerts } from '@/components/admin/PipelineHealthAlerts';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
// Helper to calculate date threshold for filtering
|
// Helper to calculate date threshold for filtering
|
||||||
@@ -180,6 +181,9 @@ export default function ErrorMonitoring() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pipeline Health Alerts */}
|
||||||
|
<PipelineHealthAlerts />
|
||||||
|
|
||||||
{/* Analytics Section */}
|
{/* Analytics Section */}
|
||||||
<ErrorAnalytics errorSummary={errorSummary} approvalMetrics={approvalMetrics} />
|
<ErrorAnalytics errorSummary={errorSummary} approvalMetrics={approvalMetrics} />
|
||||||
|
|
||||||
|
|||||||
@@ -74,3 +74,6 @@ verify_jwt = false
|
|||||||
|
|
||||||
[functions.cleanup-old-versions]
|
[functions.cleanup-old-versions]
|
||||||
verify_jwt = false
|
verify_jwt = false
|
||||||
|
|
||||||
|
[functions.scheduled-maintenance]
|
||||||
|
verify_jwt = false
|
||||||
|
|||||||
73
supabase/functions/scheduled-maintenance/index.ts
Normal file
73
supabase/functions/scheduled-maintenance/index.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
||||||
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
||||||
|
import { edgeLogger } from '../_shared/logger.ts';
|
||||||
|
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||||
|
};
|
||||||
|
|
||||||
|
serve(async (req: Request) => {
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
return new Response(null, { headers: corsHeaders });
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestId = crypto.randomUUID();
|
||||||
|
|
||||||
|
try {
|
||||||
|
edgeLogger.info('Starting scheduled maintenance', { requestId });
|
||||||
|
|
||||||
|
const supabase = createClient(
|
||||||
|
Deno.env.get('SUPABASE_URL')!,
|
||||||
|
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
||||||
|
);
|
||||||
|
|
||||||
|
// Run system maintenance (orphaned image cleanup)
|
||||||
|
const { data: maintenanceData, error: maintenanceError } = await supabase.rpc('run_system_maintenance');
|
||||||
|
|
||||||
|
if (maintenanceError) {
|
||||||
|
edgeLogger.error('Maintenance failed', { requestId, error: maintenanceError.message });
|
||||||
|
} else {
|
||||||
|
edgeLogger.info('Maintenance completed', { requestId, result: maintenanceData });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run pipeline monitoring checks
|
||||||
|
const { data: monitoringData, error: monitoringError } = await supabase.rpc('run_pipeline_monitoring');
|
||||||
|
|
||||||
|
if (monitoringError) {
|
||||||
|
edgeLogger.error('Pipeline monitoring failed', { requestId, error: monitoringError.message });
|
||||||
|
} else {
|
||||||
|
edgeLogger.info('Pipeline monitoring completed', { requestId, result: monitoringData });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
maintenance: maintenanceData,
|
||||||
|
monitoring: monitoringData,
|
||||||
|
requestId
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
edgeLogger.error('Maintenance exception', {
|
||||||
|
requestId,
|
||||||
|
error: error instanceof Error ? error.message : String(error)
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: 'Internal server error',
|
||||||
|
requestId
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
-- Phase 1: Critical Security Fixes for Sacred Pipeline
|
||||||
|
-- Fix 1.1: Attach ban prevention trigger to content_submissions
|
||||||
|
CREATE TRIGGER prevent_banned_submissions
|
||||||
|
BEFORE INSERT ON content_submissions
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION prevent_banned_user_submissions();
|
||||||
|
|
||||||
|
-- Fix 1.2: Add RLS policy to prevent banned users from submitting
|
||||||
|
CREATE POLICY "Banned users cannot submit"
|
||||||
|
ON content_submissions
|
||||||
|
FOR INSERT
|
||||||
|
TO authenticated
|
||||||
|
WITH CHECK (
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1 FROM profiles
|
||||||
|
WHERE user_id = auth.uid() AND banned = true
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
-- Pipeline Monitoring Alert System Migration
|
||||||
|
-- Adds comprehensive monitoring for critical pipeline metrics
|
||||||
|
|
||||||
|
-- 1. Expand alert types to include pipeline-specific alerts
|
||||||
|
ALTER TABLE system_alerts
|
||||||
|
DROP CONSTRAINT IF EXISTS system_alerts_alert_type_check;
|
||||||
|
|
||||||
|
ALTER TABLE system_alerts
|
||||||
|
ADD CONSTRAINT system_alerts_alert_type_check CHECK (alert_type IN (
|
||||||
|
'orphaned_images',
|
||||||
|
'stale_submissions',
|
||||||
|
'circular_dependency',
|
||||||
|
'validation_error',
|
||||||
|
'ban_attempt',
|
||||||
|
'upload_timeout',
|
||||||
|
'high_error_rate',
|
||||||
|
'failed_submissions',
|
||||||
|
'temp_ref_error',
|
||||||
|
'submission_queue_backlog',
|
||||||
|
'slow_approval',
|
||||||
|
'high_ban_rate'
|
||||||
|
));
|
||||||
|
|
||||||
|
-- 2. Monitor Failed Submissions
|
||||||
|
CREATE OR REPLACE FUNCTION monitor_failed_submissions()
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_total_last_hour INTEGER;
|
||||||
|
v_failed_last_hour INTEGER;
|
||||||
|
v_failure_rate NUMERIC;
|
||||||
|
v_consecutive_failures INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
COUNT(*),
|
||||||
|
COUNT(*) FILTER (WHERE success = false)
|
||||||
|
INTO v_total_last_hour, v_failed_last_hour
|
||||||
|
FROM approval_transaction_metrics
|
||||||
|
WHERE created_at > now() - interval '1 hour';
|
||||||
|
|
||||||
|
IF v_total_last_hour > 0 THEN
|
||||||
|
v_failure_rate := (v_failed_last_hour::NUMERIC / v_total_last_hour::NUMERIC) * 100;
|
||||||
|
|
||||||
|
IF v_failure_rate > 10 AND v_failed_last_hour >= 3 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'failed_submissions',
|
||||||
|
CASE
|
||||||
|
WHEN v_failure_rate > 50 THEN 'critical'
|
||||||
|
WHEN v_failure_rate > 25 THEN 'high'
|
||||||
|
ELSE 'medium'
|
||||||
|
END,
|
||||||
|
format('High approval failure rate: %.1f%% (%s/%s in last hour)',
|
||||||
|
v_failure_rate, v_failed_last_hour, v_total_last_hour),
|
||||||
|
jsonb_build_object(
|
||||||
|
'failure_rate', v_failure_rate,
|
||||||
|
'failed_count', v_failed_last_hour,
|
||||||
|
'total_count', v_total_last_hour,
|
||||||
|
'checked_at', now()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO v_consecutive_failures
|
||||||
|
FROM (
|
||||||
|
SELECT success
|
||||||
|
FROM approval_transaction_metrics
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT 5
|
||||||
|
) recent
|
||||||
|
WHERE success = false;
|
||||||
|
|
||||||
|
IF v_consecutive_failures >= 5 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'failed_submissions',
|
||||||
|
'critical',
|
||||||
|
format('System failure: %s consecutive approval failures', v_consecutive_failures),
|
||||||
|
jsonb_build_object(
|
||||||
|
'consecutive_failures', v_consecutive_failures,
|
||||||
|
'checked_at', now()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 3. Monitor Ban Attempt Patterns
|
||||||
|
CREATE OR REPLACE FUNCTION monitor_ban_attempts()
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_attempts_last_hour INTEGER;
|
||||||
|
v_unique_users INTEGER;
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
COUNT(*),
|
||||||
|
COUNT(DISTINCT (metadata->>'user_id')::UUID)
|
||||||
|
INTO v_attempts_last_hour, v_unique_users
|
||||||
|
FROM system_alerts
|
||||||
|
WHERE alert_type = 'ban_attempt'
|
||||||
|
AND created_at > now() - interval '1 hour';
|
||||||
|
|
||||||
|
IF v_attempts_last_hour >= 5 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'high_ban_rate',
|
||||||
|
CASE
|
||||||
|
WHEN v_attempts_last_hour > 20 THEN 'critical'
|
||||||
|
WHEN v_attempts_last_hour > 10 THEN 'high'
|
||||||
|
ELSE 'medium'
|
||||||
|
END,
|
||||||
|
format('High ban attempt rate: %s attempts from %s users in last hour',
|
||||||
|
v_attempts_last_hour, v_unique_users),
|
||||||
|
jsonb_build_object(
|
||||||
|
'attempt_count', v_attempts_last_hour,
|
||||||
|
'unique_users', v_unique_users,
|
||||||
|
'checked_at', now()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 4. Monitor Slow Approvals
|
||||||
|
CREATE OR REPLACE FUNCTION monitor_slow_approvals()
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_slow_count INTEGER;
|
||||||
|
v_avg_duration NUMERIC;
|
||||||
|
v_max_duration NUMERIC;
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
COUNT(*),
|
||||||
|
AVG(duration_ms),
|
||||||
|
MAX(duration_ms)
|
||||||
|
INTO v_slow_count, v_avg_duration, v_max_duration
|
||||||
|
FROM approval_transaction_metrics
|
||||||
|
WHERE created_at > now() - interval '1 hour'
|
||||||
|
AND duration_ms > 30000;
|
||||||
|
|
||||||
|
IF v_slow_count >= 3 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'slow_approval',
|
||||||
|
CASE
|
||||||
|
WHEN v_max_duration > 60000 THEN 'high'
|
||||||
|
ELSE 'medium'
|
||||||
|
END,
|
||||||
|
format('Slow approval transactions detected: %s approvals >30s (avg: %sms, max: %sms)',
|
||||||
|
v_slow_count, ROUND(v_avg_duration), ROUND(v_max_duration)),
|
||||||
|
jsonb_build_object(
|
||||||
|
'slow_count', v_slow_count,
|
||||||
|
'avg_duration_ms', ROUND(v_avg_duration),
|
||||||
|
'max_duration_ms', ROUND(v_max_duration),
|
||||||
|
'checked_at', now()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 5. Drop and recreate mark_orphaned_images with escalating alerts
|
||||||
|
DROP FUNCTION IF EXISTS mark_orphaned_images();
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION mark_orphaned_images()
|
||||||
|
RETURNS TABLE(task TEXT, status TEXT, details JSONB)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
v_orphaned_count INTEGER;
|
||||||
|
BEGIN
|
||||||
|
UPDATE orphaned_images
|
||||||
|
SET marked_for_deletion_at = now()
|
||||||
|
WHERE marked_for_deletion_at IS NULL
|
||||||
|
AND uploaded_at < now() - interval '24 hours'
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM parks WHERE image_id = orphaned_images.image_id
|
||||||
|
UNION ALL
|
||||||
|
SELECT 1 FROM rides WHERE image_id = orphaned_images.image_id
|
||||||
|
);
|
||||||
|
|
||||||
|
GET DIAGNOSTICS v_orphaned_count = ROW_COUNT;
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO v_orphaned_count
|
||||||
|
FROM orphaned_images
|
||||||
|
WHERE marked_for_deletion_at IS NOT NULL;
|
||||||
|
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'mark_orphaned_images'::TEXT,
|
||||||
|
'success'::TEXT,
|
||||||
|
jsonb_build_object('count', v_orphaned_count);
|
||||||
|
|
||||||
|
IF v_orphaned_count >= 500 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'orphaned_images',
|
||||||
|
'critical',
|
||||||
|
format('CRITICAL: %s orphaned images require cleanup', v_orphaned_count),
|
||||||
|
jsonb_build_object('count', v_orphaned_count)
|
||||||
|
);
|
||||||
|
ELSIF v_orphaned_count >= 100 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'orphaned_images',
|
||||||
|
'high',
|
||||||
|
format('High number of orphaned images: %s', v_orphaned_count),
|
||||||
|
jsonb_build_object('count', v_orphaned_count)
|
||||||
|
);
|
||||||
|
ELSIF v_orphaned_count >= 50 THEN
|
||||||
|
PERFORM create_system_alert(
|
||||||
|
'orphaned_images',
|
||||||
|
'medium',
|
||||||
|
format('Moderate orphaned images detected: %s', v_orphaned_count),
|
||||||
|
jsonb_build_object('count', v_orphaned_count)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'mark_orphaned_images'::TEXT,
|
||||||
|
'error'::TEXT,
|
||||||
|
jsonb_build_object('error', SQLERRM);
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- 6. Master Monitoring Function
|
||||||
|
CREATE OR REPLACE FUNCTION run_pipeline_monitoring()
|
||||||
|
RETURNS TABLE(check_name TEXT, status TEXT, details JSONB)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
BEGIN
|
||||||
|
PERFORM monitor_failed_submissions();
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'monitor_failed_submissions'::TEXT,
|
||||||
|
'success'::TEXT,
|
||||||
|
'{}'::JSONB;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'monitor_failed_submissions'::TEXT,
|
||||||
|
'error'::TEXT,
|
||||||
|
jsonb_build_object('error', SQLERRM);
|
||||||
|
END;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
PERFORM monitor_ban_attempts();
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'monitor_ban_attempts'::TEXT,
|
||||||
|
'success'::TEXT,
|
||||||
|
'{}'::JSONB;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'monitor_ban_attempts'::TEXT,
|
||||||
|
'error'::TEXT,
|
||||||
|
jsonb_build_object('error', SQLERRM);
|
||||||
|
END;
|
||||||
|
|
||||||
|
BEGIN
|
||||||
|
PERFORM monitor_slow_approvals();
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'monitor_slow_approvals'::TEXT,
|
||||||
|
'success'::TEXT,
|
||||||
|
'{}'::JSONB;
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RETURN QUERY SELECT
|
||||||
|
'monitor_slow_approvals'::TEXT,
|
||||||
|
'error'::TEXT,
|
||||||
|
jsonb_build_object('error', SQLERRM);
|
||||||
|
END;
|
||||||
|
|
||||||
|
RETURN;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION run_pipeline_monitoring() TO authenticated;
|
||||||
|
GRANT EXECUTE ON FUNCTION monitor_failed_submissions() TO authenticated;
|
||||||
|
GRANT EXECUTE ON FUNCTION monitor_ban_attempts() TO authenticated;
|
||||||
|
GRANT EXECUTE ON FUNCTION monitor_slow_approvals() TO authenticated;
|
||||||
Reference in New Issue
Block a user