add checkboxes for checks and add info for unavailable upstreams

This commit is contained in:
2026-06-11 18:27:37 +03:00
parent faa37edf33
commit a63bd04a7f
8 changed files with 947 additions and 42 deletions
+2
View File
@@ -0,0 +1,2 @@
VITE_DEV_SERVER_HOST=172.16.4.220
VITE_DEV_SERVER_PORT=4000
Generated
+234
View File
@@ -40,6 +40,7 @@ const RAW_RUNTIME_STATE =
["eslint-plugin-react-hooks", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:7.1.1"],\ ["eslint-plugin-react-hooks", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:7.1.1"],\
["eslint-plugin-react-refresh", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:0.5.2"],\ ["eslint-plugin-react-refresh", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:0.5.2"],\
["globals", "npm:17.6.0"],\ ["globals", "npm:17.6.0"],\
["jspdf", "npm:4.2.1"],\
["network-tools", "workspace:."],\ ["network-tools", "workspace:."],\
["react", "npm:19.2.6"],\ ["react", "npm:19.2.6"],\
["react-dom", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:19.2.6"],\ ["react-dom", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:19.2.6"],\
@@ -215,6 +216,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@babel/runtime", [\
["npm:7.29.7", {\
"packageLocation": "../../.yarn/berry/cache/@babel-runtime-npm-7.29.7-423e8cd4b6-10c0.zip/node_modules/@babel/runtime/",\
"packageDependencies": [\
["@babel/runtime", "npm:7.29.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["@babel/template", [\ ["@babel/template", [\
["npm:7.28.6", {\ ["npm:7.28.6", {\
"packageLocation": "../../.yarn/berry/cache/@babel-template-npm-7.28.6-bff3bc3923-10c0.zip/node_modules/@babel/template/",\ "packageLocation": "../../.yarn/berry/cache/@babel-template-npm-7.28.6-bff3bc3923-10c0.zip/node_modules/@babel/template/",\
@@ -769,6 +779,24 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@types/pako", [\
["npm:2.0.4", {\
"packageLocation": "../../.yarn/berry/cache/@types-pako-npm-2.0.4-ac100d59c9-10c0.zip/node_modules/@types/pako/",\
"packageDependencies": [\
["@types/pako", "npm:2.0.4"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/raf", [\
["npm:3.4.3", {\
"packageLocation": "../../.yarn/berry/cache/@types-raf-npm-3.4.3-afd3c6d2bc-10c0.zip/node_modules/@types/raf/",\
"packageDependencies": [\
["@types/raf", "npm:3.4.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["@types/react", [\ ["@types/react", [\
["npm:19.2.15", {\ ["npm:19.2.15", {\
"packageLocation": "../../.yarn/berry/cache/@types-react-npm-19.2.15-767accf7c6-10c0.zip/node_modules/@types/react/",\ "packageLocation": "../../.yarn/berry/cache/@types-react-npm-19.2.15-767accf7c6-10c0.zip/node_modules/@types/react/",\
@@ -799,6 +827,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["@types/trusted-types", [\
["npm:2.0.7", {\
"packageLocation": "../../.yarn/berry/cache/@types-trusted-types-npm-2.0.7-a07fc44f59-10c0.zip/node_modules/@types/trusted-types/",\
"packageDependencies": [\
["@types/trusted-types", "npm:2.0.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["@vitejs/plugin-react", [\ ["@vitejs/plugin-react", [\
["npm:6.0.2", {\ ["npm:6.0.2", {\
"packageLocation": "../../.yarn/berry/cache/@vitejs-plugin-react-npm-6.0.2-0f2620b1d7-10c0.zip/node_modules/@vitejs/plugin-react/",\ "packageLocation": "../../.yarn/berry/cache/@vitejs-plugin-react-npm-6.0.2-0f2620b1d7-10c0.zip/node_modules/@vitejs/plugin-react/",\
@@ -952,6 +989,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["base64-arraybuffer", [\
["npm:1.0.2", {\
"packageLocation": "../../.yarn/berry/cache/base64-arraybuffer-npm-1.0.2-18f4899892-10c0.zip/node_modules/base64-arraybuffer/",\
"packageDependencies": [\
["base64-arraybuffer", "npm:1.0.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["baseline-browser-mapping", [\ ["baseline-browser-mapping", [\
["npm:2.10.31", {\ ["npm:2.10.31", {\
"packageLocation": "../../.yarn/berry/cache/baseline-browser-mapping-npm-2.10.31-11b5c14e52-10c0.zip/node_modules/baseline-browser-mapping/",\ "packageLocation": "../../.yarn/berry/cache/baseline-browser-mapping-npm-2.10.31-11b5c14e52-10c0.zip/node_modules/baseline-browser-mapping/",\
@@ -1005,6 +1051,23 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["canvg", [\
["npm:3.0.11", {\
"packageLocation": "../../.yarn/berry/cache/canvg-npm-3.0.11-0944b07da2-10c0.zip/node_modules/canvg/",\
"packageDependencies": [\
["@babel/runtime", "npm:7.29.7"],\
["@types/raf", "npm:3.4.3"],\
["canvg", "npm:3.0.11"],\
["core-js", "npm:3.49.0"],\
["raf", "npm:3.4.1"],\
["regenerator-runtime", "npm:0.13.11"],\
["rgbcolor", "npm:1.0.1"],\
["stackblur-canvas", "npm:2.7.0"],\
["svg-pathdata", "npm:6.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["chownr", [\ ["chownr", [\
["npm:3.0.0", {\ ["npm:3.0.0", {\
"packageLocation": "../../.yarn/berry/cache/chownr-npm-3.0.0-5275e85d25-10c0.zip/node_modules/chownr/",\ "packageLocation": "../../.yarn/berry/cache/chownr-npm-3.0.0-5275e85d25-10c0.zip/node_modules/chownr/",\
@@ -1033,6 +1096,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["core-js", [\
["npm:3.49.0", {\
"packageLocation": "../../.yarn/berry/cache/core-js-npm-3.49.0-0a974f48cc-10c0.zip/node_modules/core-js/",\
"packageDependencies": [\
["core-js", "npm:3.49.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["cross-spawn", [\ ["cross-spawn", [\
["npm:7.0.6", {\ ["npm:7.0.6", {\
"packageLocation": "../../.yarn/berry/cache/cross-spawn-npm-7.0.6-264bddf921-10c0.zip/node_modules/cross-spawn/",\ "packageLocation": "../../.yarn/berry/cache/cross-spawn-npm-7.0.6-264bddf921-10c0.zip/node_modules/cross-spawn/",\
@@ -1045,6 +1117,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["css-line-break", [\
["npm:2.1.0", {\
"packageLocation": "../../.yarn/berry/cache/css-line-break-npm-2.1.0-a1fd810d69-10c0.zip/node_modules/css-line-break/",\
"packageDependencies": [\
["css-line-break", "npm:2.1.0"],\
["utrie", "npm:1.0.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["csstype", [\ ["csstype", [\
["npm:3.2.3", {\ ["npm:3.2.3", {\
"packageLocation": "../../.yarn/berry/cache/csstype-npm-3.2.3-741053244e-10c0.zip/node_modules/csstype/",\ "packageLocation": "../../.yarn/berry/cache/csstype-npm-3.2.3-741053244e-10c0.zip/node_modules/csstype/",\
@@ -1104,6 +1186,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["dompurify", [\
["npm:3.4.7", {\
"packageLocation": "../../.yarn/berry/cache/dompurify-npm-3.4.7-440dacdb20-10c0.zip/node_modules/dompurify/",\
"packageDependencies": [\
["@types/trusted-types", "npm:2.0.7"],\
["dompurify", "npm:3.4.7"]\
],\
"linkType": "HARD"\
}]\
]],\
["dunder-proto", [\ ["dunder-proto", [\
["npm:1.0.1", {\ ["npm:1.0.1", {\
"packageLocation": "../../.yarn/berry/cache/dunder-proto-npm-1.0.1-90eb6829db-10c0.zip/node_modules/dunder-proto/",\ "packageLocation": "../../.yarn/berry/cache/dunder-proto-npm-1.0.1-90eb6829db-10c0.zip/node_modules/dunder-proto/",\
@@ -1432,6 +1524,18 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["fast-png", [\
["npm:6.4.0", {\
"packageLocation": "../../.yarn/berry/cache/fast-png-npm-6.4.0-7871b2b5a5-10c0.zip/node_modules/fast-png/",\
"packageDependencies": [\
["@types/pako", "npm:2.0.4"],\
["fast-png", "npm:6.4.0"],\
["iobuffer", "npm:5.4.0"],\
["pako", "npm:2.1.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["fdir", [\ ["fdir", [\
["npm:6.5.0", {\ ["npm:6.5.0", {\
"packageLocation": "../../.yarn/berry/cache/fdir-npm-6.5.0-8814a0dec7-10c0.zip/node_modules/fdir/",\ "packageLocation": "../../.yarn/berry/cache/fdir-npm-6.5.0-8814a0dec7-10c0.zip/node_modules/fdir/",\
@@ -1454,6 +1558,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["fflate", [\
["npm:0.8.3", {\
"packageLocation": "../../.yarn/berry/cache/fflate-npm-0.8.3-35acaff861-10c0.zip/node_modules/fflate/",\
"packageDependencies": [\
["fflate", "npm:0.8.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["file-entry-cache", [\ ["file-entry-cache", [\
["npm:8.0.0", {\ ["npm:8.0.0", {\
"packageLocation": "../../.yarn/berry/cache/file-entry-cache-npm-8.0.0-5b09d19a83-10c0.zip/node_modules/file-entry-cache/",\ "packageLocation": "../../.yarn/berry/cache/file-entry-cache-npm-8.0.0-5b09d19a83-10c0.zip/node_modules/file-entry-cache/",\
@@ -1686,6 +1799,17 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["html2canvas", [\
["npm:1.4.1", {\
"packageLocation": "../../.yarn/berry/cache/html2canvas-npm-1.4.1-fa13983145-10c0.zip/node_modules/html2canvas/",\
"packageDependencies": [\
["css-line-break", "npm:2.1.0"],\
["html2canvas", "npm:1.4.1"],\
["text-segmentation", "npm:1.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["https-proxy-agent", [\ ["https-proxy-agent", [\
["npm:5.0.1", {\ ["npm:5.0.1", {\
"packageLocation": "../../.yarn/berry/cache/https-proxy-agent-npm-5.0.1-42d65f358e-10c0.zip/node_modules/https-proxy-agent/",\ "packageLocation": "../../.yarn/berry/cache/https-proxy-agent-npm-5.0.1-42d65f358e-10c0.zip/node_modules/https-proxy-agent/",\
@@ -1715,6 +1839,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["iobuffer", [\
["npm:5.4.0", {\
"packageLocation": "../../.yarn/berry/cache/iobuffer-npm-5.4.0-2cf4f91275-10c0.zip/node_modules/iobuffer/",\
"packageDependencies": [\
["iobuffer", "npm:5.4.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["is-extglob", [\ ["is-extglob", [\
["npm:2.1.1", {\ ["npm:2.1.1", {\
"packageLocation": "../../.yarn/berry/cache/is-extglob-npm-2.1.1-0870ea68b5-10c0.zip/node_modules/is-extglob/",\ "packageLocation": "../../.yarn/berry/cache/is-extglob-npm-2.1.1-0870ea68b5-10c0.zip/node_modules/is-extglob/",\
@@ -1804,6 +1937,22 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["jspdf", [\
["npm:4.2.1", {\
"packageLocation": "../../.yarn/berry/cache/jspdf-npm-4.2.1-4a36558b77-10c0.zip/node_modules/jspdf/",\
"packageDependencies": [\
["@babel/runtime", "npm:7.29.7"],\
["canvg", "npm:3.0.11"],\
["core-js", "npm:3.49.0"],\
["dompurify", "npm:3.4.7"],\
["fast-png", "npm:6.4.0"],\
["fflate", "npm:0.8.3"],\
["html2canvas", "npm:1.4.1"],\
["jspdf", "npm:4.2.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["keyv", [\ ["keyv", [\
["npm:4.5.4", {\ ["npm:4.5.4", {\
"packageLocation": "../../.yarn/berry/cache/keyv-npm-4.5.4-4c8e2cf7f7-10c0.zip/node_modules/keyv/",\ "packageLocation": "../../.yarn/berry/cache/keyv-npm-4.5.4-4c8e2cf7f7-10c0.zip/node_modules/keyv/",\
@@ -2065,6 +2214,7 @@ const RAW_RUNTIME_STATE =
["eslint-plugin-react-hooks", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:7.1.1"],\ ["eslint-plugin-react-hooks", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:7.1.1"],\
["eslint-plugin-react-refresh", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:0.5.2"],\ ["eslint-plugin-react-refresh", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:0.5.2"],\
["globals", "npm:17.6.0"],\ ["globals", "npm:17.6.0"],\
["jspdf", "npm:4.2.1"],\
["network-tools", "workspace:."],\ ["network-tools", "workspace:."],\
["react", "npm:19.2.6"],\ ["react", "npm:19.2.6"],\
["react-dom", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:19.2.6"],\ ["react-dom", "virtual:1bef36b42a34c769545e238167cf96a97395f203a4be67468ec9e7c364af0538d9297399544e3b7eb227555c0667de3f416c95893ffd698b011448edf0cd18b9#npm:19.2.6"],\
@@ -2147,6 +2297,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["pako", [\
["npm:2.1.0", {\
"packageLocation": "../../.yarn/berry/cache/pako-npm-2.1.0-78df11948c-10c0.zip/node_modules/pako/",\
"packageDependencies": [\
["pako", "npm:2.1.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["path-exists", [\ ["path-exists", [\
["npm:4.0.0", {\ ["npm:4.0.0", {\
"packageLocation": "../../.yarn/berry/cache/path-exists-npm-4.0.0-e9e4f63eb0-10c0.zip/node_modules/path-exists/",\ "packageLocation": "../../.yarn/berry/cache/path-exists-npm-4.0.0-e9e4f63eb0-10c0.zip/node_modules/path-exists/",\
@@ -2165,6 +2324,15 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["performance-now", [\
["npm:2.1.0", {\
"packageLocation": "../../.yarn/berry/cache/performance-now-npm-2.1.0-45e3ce7e49-10c0.zip/node_modules/performance-now/",\
"packageDependencies": [\
["performance-now", "npm:2.1.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["picocolors", [\ ["picocolors", [\
["npm:1.1.1", {\ ["npm:1.1.1", {\
"packageLocation": "../../.yarn/berry/cache/picocolors-npm-1.1.1-4fede47cf1-10c0.zip/node_modules/picocolors/",\ "packageLocation": "../../.yarn/berry/cache/picocolors-npm-1.1.1-4fede47cf1-10c0.zip/node_modules/picocolors/",\
@@ -2231,6 +2399,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["raf", [\
["npm:3.4.1", {\
"packageLocation": "../../.yarn/berry/cache/raf-npm-3.4.1-c25d48d76e-10c0.zip/node_modules/raf/",\
"packageDependencies": [\
["performance-now", "npm:2.1.0"],\
["raf", "npm:3.4.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["react", [\ ["react", [\
["npm:19.2.6", {\ ["npm:19.2.6", {\
"packageLocation": "../../.yarn/berry/cache/react-npm-19.2.6-f326db5d50-10c0.zip/node_modules/react/",\ "packageLocation": "../../.yarn/berry/cache/react-npm-19.2.6-f326db5d50-10c0.zip/node_modules/react/",\
@@ -2263,6 +2441,24 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["regenerator-runtime", [\
["npm:0.13.11", {\
"packageLocation": "../../.yarn/berry/cache/regenerator-runtime-npm-0.13.11-90bf536060-10c0.zip/node_modules/regenerator-runtime/",\
"packageDependencies": [\
["regenerator-runtime", "npm:0.13.11"]\
],\
"linkType": "HARD"\
}]\
]],\
["rgbcolor", [\
["npm:1.0.1", {\
"packageLocation": "../../.yarn/berry/cache/rgbcolor-npm-1.0.1-5d8cee2258-10c0.zip/node_modules/rgbcolor/",\
"packageDependencies": [\
["rgbcolor", "npm:1.0.1"]\
],\
"linkType": "HARD"\
}]\
]],\
["rolldown", [\ ["rolldown", [\
["npm:1.0.1", {\ ["npm:1.0.1", {\
"packageLocation": "../../.yarn/berry/cache/rolldown-npm-1.0.1-8adafc3459-10c0.zip/node_modules/rolldown/",\ "packageLocation": "../../.yarn/berry/cache/rolldown-npm-1.0.1-8adafc3459-10c0.zip/node_modules/rolldown/",\
@@ -2366,6 +2562,24 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["stackblur-canvas", [\
["npm:2.7.0", {\
"packageLocation": "../../.yarn/berry/cache/stackblur-canvas-npm-2.7.0-69df5c1ce4-10c0.zip/node_modules/stackblur-canvas/",\
"packageDependencies": [\
["stackblur-canvas", "npm:2.7.0"]\
],\
"linkType": "HARD"\
}]\
]],\
["svg-pathdata", [\
["npm:6.0.3", {\
"packageLocation": "../../.yarn/berry/cache/svg-pathdata-npm-6.0.3-b9a9612535-10c0.zip/node_modules/svg-pathdata/",\
"packageDependencies": [\
["svg-pathdata", "npm:6.0.3"]\
],\
"linkType": "HARD"\
}]\
]],\
["tar", [\ ["tar", [\
["npm:7.5.15", {\ ["npm:7.5.15", {\
"packageLocation": "../../.yarn/berry/cache/tar-npm-7.5.15-f915901510-10c0.zip/node_modules/tar/",\ "packageLocation": "../../.yarn/berry/cache/tar-npm-7.5.15-f915901510-10c0.zip/node_modules/tar/",\
@@ -2380,6 +2594,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["text-segmentation", [\
["npm:1.0.3", {\
"packageLocation": "../../.yarn/berry/cache/text-segmentation-npm-1.0.3-634201c349-10c0.zip/node_modules/text-segmentation/",\
"packageDependencies": [\
["text-segmentation", "npm:1.0.3"],\
["utrie", "npm:1.0.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["tinyglobby", [\ ["tinyglobby", [\
["npm:0.2.16", {\ ["npm:0.2.16", {\
"packageLocation": "../../.yarn/berry/cache/tinyglobby-npm-0.2.16-102914a73b-10c0.zip/node_modules/tinyglobby/",\ "packageLocation": "../../.yarn/berry/cache/tinyglobby-npm-0.2.16-102914a73b-10c0.zip/node_modules/tinyglobby/",\
@@ -2453,6 +2677,16 @@ const RAW_RUNTIME_STATE =
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
]],\ ]],\
["utrie", [\
["npm:1.0.2", {\
"packageLocation": "../../.yarn/berry/cache/utrie-npm-1.0.2-909cbd985c-10c0.zip/node_modules/utrie/",\
"packageDependencies": [\
["base64-arraybuffer", "npm:1.0.2"],\
["utrie", "npm:1.0.2"]\
],\
"linkType": "HARD"\
}]\
]],\
["vite", [\ ["vite", [\
["npm:8.0.13", {\ ["npm:8.0.13", {\
"packageLocation": "../../.yarn/berry/cache/vite-npm-8.0.13-f2ad0579c2-10c0.zip/node_modules/vite/",\ "packageLocation": "../../.yarn/berry/cache/vite-npm-8.0.13-f2ad0579c2-10c0.zip/node_modules/vite/",\
Binary file not shown.
+1
View File
@@ -11,6 +11,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",
"jspdf": "^4.2.1",
"react": "^19.2.6", "react": "^19.2.6",
"react-dom": "^19.2.6", "react-dom": "^19.2.6",
"socket.io-client": "^4.6.1" "socket.io-client": "^4.6.1"
+170 -2
View File
@@ -117,6 +117,36 @@ body {
border-radius: 10px; border-radius: 10px;
} }
.results-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.btn-export {
background: #fff;
color: var(--accent);
border: 1.5px solid var(--accent);
border-radius: var(--radius);
padding: 6px 14px;
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s, color 0.15s;
}
.btn-export:hover:not(:disabled) {
background: var(--accent);
color: #fff;
}
.btn-export:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* ── URL input ──────────────────────────────────────────────────────────────── */ /* ── URL input ──────────────────────────────────────────────────────────────── */
.host-editor { display: flex; flex-direction: column; gap: 8px; } .host-editor { display: flex; flex-direction: column; gap: 8px; }
@@ -139,6 +169,70 @@ body {
} }
.url-input::placeholder { color: var(--muted); } .url-input::placeholder { color: var(--muted); }
/* ── Checks ─────────────────────────────────────────────────────────────────── */
.checks-editor {
display: flex;
flex-direction: column;
gap: 8px;
}
.checks-toolbar {
display: flex;
gap: 8px;
}
.checks-action {
background: #fff;
border: 1px solid var(--border2);
border-radius: 4px;
padding: 2px 8px;
font-family: var(--font-mono);
font-size: 10px;
color: var(--muted);
cursor: pointer;
}
.checks-action:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.checks-action:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.checks-list {
display: flex;
flex-direction: column;
gap: 6px;
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 12px;
box-shadow: var(--shadow);
}
.check-item {
display: flex;
align-items: center;
gap: 8px;
font-family: var(--font-mono);
font-size: 12px;
color: var(--text);
cursor: pointer;
user-select: none;
}
.check-item input {
accent-color: var(--accent);
width: 14px;
height: 14px;
cursor: pointer;
}
.check-item input:disabled {
cursor: not-allowed;
}
/* ── Run button ─────────────────────────────────────────────────────────────── */ /* ── Run button ─────────────────────────────────────────────────────────────── */
.btn-run { .btn-run {
background: var(--accent); background: var(--accent);
@@ -380,11 +474,23 @@ pre {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
min-width: 0;
}
.server-group-names {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
} }
.server-host-sub { .server-host-sub {
font-size: 0.75rem; font-family: var(--font-mono);
opacity: 0.6; font-size: 10px;
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.server-status-dot { .server-status-dot {
@@ -398,6 +504,68 @@ pre {
.group-ok { border-color: #22c55e44; } .group-ok { border-color: #22c55e44; }
.group-err { border-color: #ef444444; } .group-err { border-color: #ef444444; }
.group-unavailable { border-color: #ef4444; border-left: 3px solid var(--err); }
.server-unavailable-badge {
font-family: var(--font-mono);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--err);
background: #fdeaee;
padding: 2px 8px;
border-radius: 4px;
}
.card-unavailable {
border-left: 3px solid var(--err);
}
.upstream-unavailable-block {
padding: 8px 12px;
}
.upstream-unavailable {
font-family: var(--font-mono);
display: flex;
flex-direction: column;
gap: 2px;
}
.upstream-head {
display: flex;
align-items: baseline;
gap: 8px;
}
.upstream-head strong {
font-size: 12px;
color: var(--text);
}
.upstream-status {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.4px;
font-weight: 600;
color: var(--err);
}
.upstream-line {
font-size: 10px;
line-height: 1.3;
color: var(--muted);
word-break: break-word;
}
.upstream-meta {
color: #6a7490;
}
.upstream-error {
color: var(--err);
}
.server-group-results { .server-group-results {
padding: 8px; padding: 8px;
+329 -40
View File
@@ -1,14 +1,29 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from "react"; import React, { useState, useEffect, useRef, useCallback, useMemo } from "react";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import axios from "axios"; import axios from "axios";
import { jsPDF } from "jspdf";
import "./App.css"; import "./App.css";
const BASE = (import.meta.env.BASE_URL ?? "/").replace(/\/$/, ""); const BASE = (import.meta.env.BASE_URL ?? "/").replace(/\/$/, "");
const API_BASE = `${BASE}/api`; const IS_DEV = import.meta.env.DEV;
const SOCKET_IO_PATH = `${BASE}/socket.io`; const DEV_HOST = import.meta.env.VITE_DEV_SERVER_HOST ?? "172.16.4.220";
const DEV_PORT = import.meta.env.VITE_DEV_SERVER_PORT ?? "4000";
const DEV_SERVER_URL = `http://${DEV_HOST}:${DEV_PORT}`;
const API_BASE = IS_DEV ? `${DEV_SERVER_URL}/api` : `${BASE}/api`;
const SOCKET_URL = IS_DEV ? DEV_SERVER_URL : undefined;
const SOCKET_IO_PATH = IS_DEV ? "/socket.io" : `${BASE}/socket.io`;
const DEFAULT_HOSTS = [{ url: "", method: "GET" }]; const DEFAULT_HOSTS = [{ url: "", method: "GET" }];
const CHECK_OPTIONS = [
{ id: "ping", label: "Ping" },
{ id: "curl", label: "Curl" },
{ id: "traceroute", label: "Traceroute" },
];
const DEFAULT_CHECKS = CHECK_OPTIONS.map((c) => c.id);
function getToolBody(result) { function getToolBody(result) {
if (!result) return null; if (!result) return null;
const d = result.data; const d = result.data;
@@ -40,6 +55,122 @@ function isResultError(result) {
return false; return false;
} }
function isUpstreamUnavailable(result) {
if (!result || result.type !== "error") return false;
if (result.code === "ECONNREFUSED") return true;
return /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|EHOSTUNREACH/i.test(
result.error || "",
);
}
function isToolError(result) {
return (
result?.type === "error" &&
(result.endpoint || result.serverName || result.host)
);
}
function appendResult(prev, entry, index) {
if (typeof index === "number") {
const next = prev.length ? [...prev] : [];
while (next.length <= index) next.push(null);
next[index] = entry;
return next;
}
if (!prev.length) return [entry];
const emptyIdx = prev.findIndex((r) => r == null);
if (emptyIdx >= 0) {
const next = [...prev];
next[emptyIdx] = entry;
return next;
}
return [...prev, entry];
}
function getResultOutput(result) {
if (!result) return "";
if (result.type === "error") return result.error || "Error";
const body = getToolBody(result);
if (!body) return "";
const parts = [body.stdout, body.trace, body.body].filter(Boolean);
if (parts.length) {
const text = parts.join("\n");
return body.stderr ? `${text}\n\nstderr:\n${body.stderr}` : text;
}
if (body.stderr) return body.stderr;
return JSON.stringify(body, null, 2);
}
function exportResultsToPdf(groupedResults, targetUrl) {
const doc = new jsPDF({ unit: "pt", format: "a4" });
const margin = 40;
const maxWidth = 515;
const pageHeight = doc.internal.pageSize.getHeight();
const lineHeight = 12;
let y = margin;
const ensureSpace = (needed = lineHeight) => {
if (y + needed > pageHeight - margin) {
doc.addPage();
y = margin;
}
};
const addLines = (text, { bold = false, size = 10, gap = 0 } = {}) => {
doc.setFont("helvetica", bold ? "bold" : "normal");
doc.setFontSize(size);
const lines = doc.splitTextToSize(String(text), maxWidth);
for (const line of lines) {
ensureSpace();
doc.text(line, margin, y);
y += lineHeight;
}
y += gap;
};
addLines("Network tools — diagnostic report", { bold: true, size: 14, gap: 4 });
addLines(`Generated: ${new Date().toLocaleString()}`, { size: 9, gap: 2 });
if (targetUrl) addLines(`Target URL: ${targetUrl}`, { size: 9, gap: 10 });
for (const { serverName, items, errors } of groupedResults) {
addLines(`${serverName}${errors} error(s) / ${items.length} total`, {
bold: true,
size: 11,
gap: 6,
});
for (const { result } of items) {
const failed = isResultError(result);
const status =
getToolBody(result)?.returncode ?? result.status ?? (failed ? "FAIL" : "OK");
const header = [
result.endpoint || "request",
result.elapsed != null ? `${result.elapsed}ms` : null,
`exit ${status}`,
]
.filter(Boolean)
.join(" · ");
addLines(header, { bold: true, size: 10, gap: 4 });
doc.setFont("courier", "normal");
doc.setFontSize(9);
const output = getResultOutput(result) || "(no output)";
const lines = doc.splitTextToSize(output, maxWidth);
for (const line of lines) {
ensureSpace();
doc.text(line, margin, y);
y += lineHeight;
}
y += 8;
}
y += 4;
}
const stamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-");
doc.save(`diagnostic-report-${stamp}.pdf`);
}
// ─── StatusBadge ────────────────────────────────────────────────────────────── // ─── StatusBadge ──────────────────────────────────────────────────────────────
function StatusBadge({ status, ok }) { function StatusBadge({ status, ok }) {
if (status === undefined) return <span className="badge pending">PENDING</span>; if (status === undefined) return <span className="badge pending">PENDING</span>;
@@ -51,6 +182,66 @@ function StatusBadge({ status, ok }) {
} }
// ─── ResultCard ─────────────────────────────────────────────────────────────── // ─── ResultCard ───────────────────────────────────────────────────────────────
function UpstreamUnavailableBlock({
serverName,
host,
metadata = {},
error,
code,
}) {
const meta = [
metadata.location,
metadata.provider,
metadata.country,
metadata.tags?.length && metadata.tags.join(", "),
]
.filter(Boolean)
.join(" · ");
return (
<div className="card card-err card-unavailable upstream-unavailable-block">
<div className="upstream-unavailable">
<div className="upstream-head">
<strong>{serverName}</strong>
<span className="upstream-status">unavailable</span>
</div>
{host && <div className="upstream-line">{host}</div>}
{meta && <div className="upstream-line upstream-meta">{meta}</div>}
{(code || error) && (
<div className="upstream-line upstream-error">
{[code, error].filter(Boolean).join(": ")}
</div>
)}
</div>
</div>
);
}
function ToolErrorCard({ result, index }) {
return (
<div className="card card-err">
<div className="card-header">
<span className="card-index">#{index + 1}</span>
<span className="card-url" title={result.endpoint}>
{result.endpoint || "request"}
</span>
<div className="card-meta">
{result.elapsed != null && (
<span className="elapsed">{result.elapsed}ms</span>
)}
<span className="badge err">ERROR</span>
</div>
</div>
<div className="card-body tool-output">
<div className="upstream-unavailable">
<strong>Request failed</strong>
<p>{result.error}</p>
</div>
</div>
</div>
);
}
function ToolResultCard({ result, index }) { function ToolResultCard({ result, index }) {
const body = getToolBody(result); const body = getToolBody(result);
const processOk = body.returncode === 0; const processOk = body.returncode === 0;
@@ -104,6 +295,10 @@ function ResultCard({ result, index }) {
const isError = result?.type === "error"; const isError = result?.type === "error";
const isPending = !result; const isPending = !result;
if (isToolError(result) && !isUpstreamUnavailable(result)) {
return <ToolErrorCard result={result} index={index} />;
}
if (isToolResult(result)) { if (isToolResult(result)) {
return <ToolResultCard result={result} index={index} />; return <ToolResultCard result={result} index={index} />;
} }
@@ -173,6 +368,53 @@ function HostEditor({ hosts, setHosts }) {
); );
} }
function ChecksEditor({ checks, setChecks, disabled }) {
const toggle = (id) => {
setChecks((prev) =>
prev.includes(id) ? prev.filter((c) => c !== id) : [...prev, id],
);
};
const selectAll = () => setChecks(DEFAULT_CHECKS);
const clearAll = () => setChecks([]);
return (
<div className="checks-editor">
<div className="checks-toolbar">
<button
type="button"
className="checks-action"
onClick={selectAll}
disabled={disabled}
>
все
</button>
<button
type="button"
className="checks-action"
onClick={clearAll}
disabled={disabled}
>
снять
</button>
</div>
<div className="checks-list">
{CHECK_OPTIONS.map(({ id, label }) => (
<label key={id} className="check-item">
<input
type="checkbox"
checked={checks.includes(id)}
onChange={() => toggle(id)}
disabled={disabled}
/>
<span>{label}</span>
</label>
))}
</div>
</div>
);
}
// ─── App ────────────────────────────────────────────────────────────────────── // ─── App ──────────────────────────────────────────────────────────────────────
export default function App() { export default function App() {
const socketRef = useRef(null); const socketRef = useRef(null);
@@ -180,6 +422,7 @@ export default function App() {
const [connected, setConnected] = useState(false); const [connected, setConnected] = useState(false);
const [hosts, setHosts] = useState(DEFAULT_HOSTS); const [hosts, setHosts] = useState(DEFAULT_HOSTS);
const [checks, setChecks] = useState(DEFAULT_CHECKS);
const [results, setResults] = useState([]); const [results, setResults] = useState([]);
const [progress, setProgress] = useState(null); // { done, total } const [progress, setProgress] = useState(null); // { done, total }
const [running, setRunning] = useState(false); const [running, setRunning] = useState(false);
@@ -192,7 +435,7 @@ export default function App() {
// ── Connect socket once ────────────────────────────────────────────────── // ── Connect socket once ──────────────────────────────────────────────────
useEffect(() => { useEffect(() => {
const socket = io({ path: SOCKET_IO_PATH }); const socket = io(SOCKET_URL, { path: SOCKET_IO_PATH });
socketRef.current = socket; socketRef.current = socket;
socket.on("connect", () => { socket.on("connect", () => {
@@ -227,22 +470,7 @@ export default function App() {
socket.on("tool:response", (payload) => { socket.on("tool:response", (payload) => {
console.log("[socket] tool:response", payload); console.log("[socket] tool:response", payload);
const entry = { ...payload, type: "tool" }; const entry = { ...payload, type: "tool" };
setResults((prev) => { setResults((prev) => appendResult(prev, entry, payload.index));
if (typeof payload.index === "number") {
const next = prev.length ? [...prev] : [];
while (next.length <= payload.index) next.push(null);
next[payload.index] = entry;
return next;
}
if (!prev.length) return [entry];
const emptyIdx = prev.findIndex((r) => r == null);
if (emptyIdx >= 0) {
const next = [...prev];
next[emptyIdx] = entry;
return next;
}
return [...prev, entry];
});
const idx = typeof payload.index === "number" ? payload.index : null; const idx = typeof payload.index === "number" ? payload.index : null;
setProgress((p) => p && { ...p, done: Math.min((p.done ?? 0) + 1, p.total) }); setProgress((p) => p && { ...p, done: Math.min((p.done ?? 0) + 1, p.total) });
const body = getToolBody(payload); const body = getToolBody(payload);
@@ -254,13 +482,14 @@ export default function App() {
socket.on("tool:error", (payload) => { socket.on("tool:error", (payload) => {
console.log("[socket] tool:error", payload); console.log("[socket] tool:error", payload);
setResults((prev) => { const entry = { ...payload, type: "error" };
const next = [...prev]; setResults((prev) => appendResult(prev, entry, payload.index));
next[payload.index] = { ...payload, type: "error" }; const idx = typeof payload.index === "number" ? payload.index : null;
return next; setProgress((p) => p && { ...p, done: Math.min((p.done ?? 0) + 1, p.total) });
}); const label = payload.serverName || payload.host || payload.endpoint || "?";
setProgress((p) => p && { ...p, done: p.done + 1 }); pushLog(
pushLog(`#${payload.index + 1} ${payload.url} → ERROR: ${payload.error}`); `${idx != null ? `#${idx + 1} ` : ""}${label} → ERROR: ${payload.error}`,
);
}); });
socket.on("batch:done", (data) => { socket.on("batch:done", (data) => {
@@ -278,13 +507,13 @@ export default function App() {
if (!socketId || running) return; if (!socketId || running) return;
const url = hosts[0]?.url?.trim(); const url = hosts[0]?.url?.trim();
if (!url) return; if (!url || checks.length === 0) return;
setRunning(true); setRunning(true);
setResults([]); setResults([]);
setProgress(null); setProgress(null);
const payload = { socketId, url }; const payload = { socketId, url, checks };
console.log("sending:", JSON.stringify(payload)); console.log("sending:", JSON.stringify(payload));
try { try {
@@ -308,8 +537,13 @@ export default function App() {
}); });
return Array.from(groups.entries()).map(([serverName, data]) => ({ return Array.from(groups.entries()).map(([serverName, data]) => ({
serverName, serverName,
host: data.items[0]?.result?.host || "",
metadata: data.items[0]?.result?.metadata || {},
items: data.items, items: data.items,
errors: data.errors, errors: data.errors,
unavailable: data.items.filter(({ result }) =>
isUpstreamUnavailable(result),
).length,
})); }));
}, [results]); }, [results]);
@@ -332,10 +566,17 @@ export default function App() {
<h2 className="section-title">задаём URL</h2> <h2 className="section-title">задаём URL</h2>
<HostEditor hosts={hosts} setHosts={setHosts} /> <HostEditor hosts={hosts} setHosts={setHosts} />
<h2 className="section-title">проверки</h2>
<ChecksEditor
checks={checks}
setChecks={setChecks}
disabled={running}
/>
<button <button
className={`btn-run ${running ? "running" : ""}`} className={`btn-run ${running ? "running" : ""}`}
onClick={run} onClick={run}
disabled={running || !connected} disabled={running || !connected || checks.length === 0}
> >
{running ? ( {running ? (
<> <>
@@ -368,14 +609,26 @@ export default function App() {
{/* ── Right panel ────────────────────────────────────────────────── */} {/* ── Right panel ────────────────────────────────────────────────── */}
<section className="panel panel-right"> <section className="panel panel-right">
<h2 className="section-title"> <div className="results-toolbar">
Results <h2 className="section-title">
{results.length > 0 && ( Results
<span className="result-count"> {results.length > 0 && (
{results.filter(Boolean).length}/{results.length} <span className="result-count">
</span> {results.filter(Boolean).length}/{results.length}
)} </span>
</h2> )}
</h2>
<button
type="button"
className="btn-export"
disabled={groupedResults.length === 0}
onClick={() =>
exportResultsToPdf(groupedResults, hosts[0]?.url?.trim() || "")
}
>
Export PDF
</button>
</div>
{results.length === 0 && !running && ( {results.length === 0 && !running && (
<div className="empty-state"> <div className="empty-state">
@@ -385,12 +638,33 @@ export default function App() {
<div className="results-list"> <div className="results-list">
{groupedResults.map((group) => { {groupedResults.map((group) => {
const { serverName, items, errors } = group; const { serverName, host, metadata, items, errors, unavailable } =
group;
const allUnavailable =
unavailable > 0 && unavailable === items.length;
if (allUnavailable) {
const sample = items.find(({ result: r }) =>
isUpstreamUnavailable(r),
)?.result;
return (
<UpstreamUnavailableBlock
key={serverName}
serverName={serverName}
host={host}
metadata={metadata}
error={sample?.error}
code={sample?.code}
/>
);
}
const isCollapsed = collapsedGroups.has(serverName); const isCollapsed = collapsedGroups.has(serverName);
const hasErrors = errors > 0; const hasErrors = errors > 0;
const groupClass = `server-group ${ const groupClass = `server-group ${
hasErrors ? "group-err" : "group-ok" hasErrors ? "group-err" : "group-ok"
}`; }`;
return ( return (
<div className={groupClass} key={serverName}> <div className={groupClass} key={serverName}>
<div <div
@@ -408,7 +682,18 @@ export default function App() {
<span className="group-chevron"> <span className="group-chevron">
{isCollapsed ? "▶" : "▼"} {isCollapsed ? "▶" : "▼"}
</span> </span>
<strong>{serverName}</strong> <div className="server-group-names">
<strong>{serverName}</strong>
{host && (
<span className="server-host-sub">{host}</span>
)}
{metadata?.location && (
<span className="server-host-sub">
{metadata.location}
{metadata.provider ? ` · ${metadata.provider}` : ""}
</span>
)}
</div>
</div> </div>
<div className="server-group-meta"> <div className="server-group-meta">
<span className="server-errors"> <span className="server-errors">
@@ -420,7 +705,11 @@ export default function App() {
{!isCollapsed && ( {!isCollapsed && (
<div className="server-group-results"> <div className="server-group-results">
{items.map(({ result, index }) => ( {items.map(({ result, index }) => (
<ResultCard key={index} result={result} index={index} /> <ResultCard
key={index}
result={result}
index={index}
/>
))} ))}
</div> </div>
)} )}
+3
View File
@@ -9,4 +9,7 @@ export default defineConfig({
react(), react(),
babel({ presets: [reactCompilerPreset()] }) babel({ presets: [reactCompilerPreset()] })
], ],
server: {
host: true,
},
}) })
+208
View File
@@ -144,6 +144,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.28.6":
version: 7.29.7
resolution: "@babel/runtime@npm:7.29.7"
checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e
languageName: node
linkType: hard
"@babel/template@npm:^7.28.6": "@babel/template@npm:^7.28.6":
version: 7.28.6 version: 7.28.6
resolution: "@babel/template@npm:7.28.6" resolution: "@babel/template@npm:7.28.6"
@@ -572,6 +579,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/pako@npm:^2.0.3":
version: 2.0.4
resolution: "@types/pako@npm:2.0.4"
checksum: 10c0/5765bf8bc7e77ee141c454118f03e544b8f6cb51eb257d82dc5830feeab8cd00818af3a1eabefdfbe8dd3ae9916ed5403937bf1031a0ee51deea27fdf4dccdfb
languageName: node
linkType: hard
"@types/raf@npm:^3.4.0":
version: 3.4.3
resolution: "@types/raf@npm:3.4.3"
checksum: 10c0/dea835f0daa399c51db9137f5337dc08a2b4a5f61f645658966ecabaebbbd0fd59551f384a1141e14e22a1cc5a591da7d4d88c60a525ad1399108b6dd2641d75
languageName: node
linkType: hard
"@types/react-dom@npm:^19.2.3": "@types/react-dom@npm:^19.2.3":
version: 19.2.3 version: 19.2.3
resolution: "@types/react-dom@npm:19.2.3" resolution: "@types/react-dom@npm:19.2.3"
@@ -590,6 +611,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/trusted-types@npm:^2.0.7":
version: 2.0.7
resolution: "@types/trusted-types@npm:2.0.7"
checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c
languageName: node
linkType: hard
"@vitejs/plugin-react@npm:^6.0.1": "@vitejs/plugin-react@npm:^6.0.1":
version: 6.0.2 version: 6.0.2
resolution: "@vitejs/plugin-react@npm:6.0.2" resolution: "@vitejs/plugin-react@npm:6.0.2"
@@ -703,6 +731,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"base64-arraybuffer@npm:^1.0.2":
version: 1.0.2
resolution: "base64-arraybuffer@npm:1.0.2"
checksum: 10c0/3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f
languageName: node
linkType: hard
"baseline-browser-mapping@npm:^2.10.12": "baseline-browser-mapping@npm:^2.10.12":
version: 2.10.31 version: 2.10.31
resolution: "baseline-browser-mapping@npm:2.10.31" resolution: "baseline-browser-mapping@npm:2.10.31"
@@ -753,6 +788,22 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"canvg@npm:^3.0.11":
version: 3.0.11
resolution: "canvg@npm:3.0.11"
dependencies:
"@babel/runtime": "npm:^7.12.5"
"@types/raf": "npm:^3.4.0"
core-js: "npm:^3.8.3"
raf: "npm:^3.4.1"
regenerator-runtime: "npm:^0.13.7"
rgbcolor: "npm:^1.0.1"
stackblur-canvas: "npm:^2.0.0"
svg-pathdata: "npm:^6.0.3"
checksum: 10c0/03ea5b69e97a4a476ef5fc8455d0da2718bd68cd8ace3e42718176bf4b7dfd80eb1d0eba47dcd04b34ed9a0c5c8ed849ead0f364dad0e35369254a45d5e098c4
languageName: node
linkType: hard
"chownr@npm:^3.0.0": "chownr@npm:^3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "chownr@npm:3.0.0" resolution: "chownr@npm:3.0.0"
@@ -776,6 +827,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"core-js@npm:^3.6.0, core-js@npm:^3.8.3":
version: 3.49.0
resolution: "core-js@npm:3.49.0"
checksum: 10c0/2e42edb47eda38fd5368380131623c8aa5d4a6b42164125b17744bdc08fa5ebbbdd06b4b4aa6ca3663470a560b0f2fba48e18f142dfe264b0039df85bc625694
languageName: node
linkType: hard
"cross-spawn@npm:^7.0.6": "cross-spawn@npm:^7.0.6":
version: 7.0.6 version: 7.0.6
resolution: "cross-spawn@npm:7.0.6" resolution: "cross-spawn@npm:7.0.6"
@@ -787,6 +845,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"css-line-break@npm:^2.1.0":
version: 2.1.0
resolution: "css-line-break@npm:2.1.0"
dependencies:
utrie: "npm:^1.0.2"
checksum: 10c0/b2222d99d5daf7861ecddc050244fdce296fad74b000dcff6bdfb1eb16dc2ef0b9ffe2c1c965e3239bd05ebe9eadb6d5438a91592fa8648d27a338e827cf9048
languageName: node
linkType: hard
"csstype@npm:^3.2.2": "csstype@npm:^3.2.2":
version: 3.2.3 version: 3.2.3
resolution: "csstype@npm:3.2.3" resolution: "csstype@npm:3.2.3"
@@ -827,6 +894,18 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"dompurify@npm:^3.3.1":
version: 3.4.7
resolution: "dompurify@npm:3.4.7"
dependencies:
"@types/trusted-types": "npm:^2.0.7"
dependenciesMeta:
"@types/trusted-types":
optional: true
checksum: 10c0/b39940e95259bfaea658cb2bec819d47f271f9fe47e4e3cfff1fb7876089264d7c21e36ea09dad0d9a0f70add502ce6787ba9e7467f2c7b996382eb1d8daa375
languageName: node
linkType: hard
"dunder-proto@npm:^1.0.1": "dunder-proto@npm:^1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "dunder-proto@npm:1.0.1" resolution: "dunder-proto@npm:1.0.1"
@@ -1087,6 +1166,17 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-png@npm:^6.2.0":
version: 6.4.0
resolution: "fast-png@npm:6.4.0"
dependencies:
"@types/pako": "npm:^2.0.3"
iobuffer: "npm:^5.3.2"
pako: "npm:^2.1.0"
checksum: 10c0/bca27a09d56a5ead536b11c1ddccf4fe44c7c0af88a8faefab6a397527d9c71b00c09b2055d0108807c438c50719dd2acbd7217ef88785b500888db26fa0358a
languageName: node
linkType: hard
"fdir@npm:^6.5.0": "fdir@npm:^6.5.0":
version: 6.5.0 version: 6.5.0
resolution: "fdir@npm:6.5.0" resolution: "fdir@npm:6.5.0"
@@ -1099,6 +1189,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fflate@npm:^0.8.1":
version: 0.8.3
resolution: "fflate@npm:0.8.3"
checksum: 10c0/eab181ca37f5348ae76d4b6f840e0026e30220e33153289ac942222d8b9638237d486507dbcc09878d724095bd354993a2ee48bbee99c8f2c6440d4448719aa7
languageName: node
linkType: hard
"file-entry-cache@npm:^8.0.0": "file-entry-cache@npm:^8.0.0":
version: 8.0.0 version: 8.0.0
resolution: "file-entry-cache@npm:8.0.0" resolution: "file-entry-cache@npm:8.0.0"
@@ -1300,6 +1397,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"html2canvas@npm:^1.0.0-rc.5":
version: 1.4.1
resolution: "html2canvas@npm:1.4.1"
dependencies:
css-line-break: "npm:^2.1.0"
text-segmentation: "npm:^1.0.3"
checksum: 10c0/6de86f75762b00948edf2ea559f16da0a1ec3facc4a8a7d3f35fcec59bb0c5970463478988ae3d9082152e0173690d46ebf4082e7ac803dd4817bae1d355c0db
languageName: node
linkType: hard
"https-proxy-agent@npm:^5.0.1": "https-proxy-agent@npm:^5.0.1":
version: 5.0.1 version: 5.0.1
resolution: "https-proxy-agent@npm:5.0.1" resolution: "https-proxy-agent@npm:5.0.1"
@@ -1324,6 +1431,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"iobuffer@npm:^5.3.2":
version: 5.4.0
resolution: "iobuffer@npm:5.4.0"
checksum: 10c0/1b3f9a5ea158bc63f038b374b42832acdb9db13ea9cfa4ed78723f30894986442f6c033dff4ae2fdf34724bf0fe432e3b86c11ca82049568c58b8d7fb47a6ac2
languageName: node
linkType: hard
"is-extglob@npm:^2.1.1": "is-extglob@npm:^2.1.1":
version: 2.1.1 version: 2.1.1
resolution: "is-extglob@npm:2.1.1" resolution: "is-extglob@npm:2.1.1"
@@ -1400,6 +1514,30 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"jspdf@npm:^4.2.1":
version: 4.2.1
resolution: "jspdf@npm:4.2.1"
dependencies:
"@babel/runtime": "npm:^7.28.6"
canvg: "npm:^3.0.11"
core-js: "npm:^3.6.0"
dompurify: "npm:^3.3.1"
fast-png: "npm:^6.2.0"
fflate: "npm:^0.8.1"
html2canvas: "npm:^1.0.0-rc.5"
dependenciesMeta:
canvg:
optional: true
core-js:
optional: true
dompurify:
optional: true
html2canvas:
optional: true
checksum: 10c0/d905809b374de1060ab39ba4b1603935fcced314bef8f63f7fd272d3f96eff0b50a96e48d3c2063ac8e51b84a1442464ff2b037af13f54b082e456416da470b7
languageName: node
linkType: hard
"keyv@npm:^4.5.4": "keyv@npm:^4.5.4":
version: 4.5.4 version: 4.5.4
resolution: "keyv@npm:4.5.4" resolution: "keyv@npm:4.5.4"
@@ -1644,6 +1782,7 @@ __metadata:
eslint-plugin-react-hooks: "npm:^7.1.1" eslint-plugin-react-hooks: "npm:^7.1.1"
eslint-plugin-react-refresh: "npm:^0.5.2" eslint-plugin-react-refresh: "npm:^0.5.2"
globals: "npm:^17.6.0" globals: "npm:^17.6.0"
jspdf: "npm:^4.2.1"
react: "npm:^19.2.6" react: "npm:^19.2.6"
react-dom: "npm:^19.2.6" react-dom: "npm:^19.2.6"
socket.io-client: "npm:^4.6.1" socket.io-client: "npm:^4.6.1"
@@ -1721,6 +1860,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"pako@npm:^2.1.0":
version: 2.1.0
resolution: "pako@npm:2.1.0"
checksum: 10c0/8e8646581410654b50eb22a5dfd71159cae98145bd5086c9a7a816ec0370b5f72b4648d08674624b3870a521e6a3daffd6c2f7bc00fdefc7063c9d8232ff5116
languageName: node
linkType: hard
"path-exists@npm:^4.0.0": "path-exists@npm:^4.0.0":
version: 4.0.0 version: 4.0.0
resolution: "path-exists@npm:4.0.0" resolution: "path-exists@npm:4.0.0"
@@ -1735,6 +1881,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"performance-now@npm:^2.1.0":
version: 2.1.0
resolution: "performance-now@npm:2.1.0"
checksum: 10c0/22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9
languageName: node
linkType: hard
"picocolors@npm:^1.1.1": "picocolors@npm:^1.1.1":
version: 1.1.1 version: 1.1.1
resolution: "picocolors@npm:1.1.1" resolution: "picocolors@npm:1.1.1"
@@ -1788,6 +1941,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"raf@npm:^3.4.1":
version: 3.4.1
resolution: "raf@npm:3.4.1"
dependencies:
performance-now: "npm:^2.1.0"
checksum: 10c0/337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e
languageName: node
linkType: hard
"react-dom@npm:^19.2.6": "react-dom@npm:^19.2.6":
version: 19.2.6 version: 19.2.6
resolution: "react-dom@npm:19.2.6" resolution: "react-dom@npm:19.2.6"
@@ -1806,6 +1968,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"regenerator-runtime@npm:^0.13.7":
version: 0.13.11
resolution: "regenerator-runtime@npm:0.13.11"
checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24
languageName: node
linkType: hard
"rgbcolor@npm:^1.0.1":
version: 1.0.1
resolution: "rgbcolor@npm:1.0.1"
checksum: 10c0/13af06c523351bac2854b85a22d1dfafd9310efd898e9bd96c8706f9aa09a3ddc8392ab00ae03d12950782164a97677f21834ffd84ffebf76ae106add319f956
languageName: node
linkType: hard
"rolldown@npm:1.0.1": "rolldown@npm:1.0.1":
version: 1.0.1 version: 1.0.1
resolution: "rolldown@npm:1.0.1" resolution: "rolldown@npm:1.0.1"
@@ -1934,6 +2110,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"stackblur-canvas@npm:^2.0.0":
version: 2.7.0
resolution: "stackblur-canvas@npm:2.7.0"
checksum: 10c0/df290d0629056d5bb43d37548d0b24cb8593c79d742650e68489abf61013db578c9980724c2508bb738d107204f2e2494ab94c3cf69d6b725caa9c63b8c7e272
languageName: node
linkType: hard
"svg-pathdata@npm:^6.0.3":
version: 6.0.3
resolution: "svg-pathdata@npm:6.0.3"
checksum: 10c0/1ba4ad2fa81e86df37d6e78d3be9e664bbedf97773b725a863a85db384285be32dc37d9c0d61e477d89594ee95b967d2c53d6bee2d76420aab670ab4124a38b9
languageName: node
linkType: hard
"tar@npm:^7.5.4": "tar@npm:^7.5.4":
version: 7.5.15 version: 7.5.15
resolution: "tar@npm:7.5.15" resolution: "tar@npm:7.5.15"
@@ -1947,6 +2137,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"text-segmentation@npm:^1.0.3":
version: 1.0.3
resolution: "text-segmentation@npm:1.0.3"
dependencies:
utrie: "npm:^1.0.2"
checksum: 10c0/8b9ae8524e3a332371060d0ca62f10ad49a13e954719ea689a6c3a8b8c15c8a56365ede2bb91c322fb0d44b6533785f0da603e066b7554d052999967fb72d600
languageName: node
linkType: hard
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.16": "tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.16":
version: 0.2.16 version: 0.2.16
resolution: "tinyglobby@npm:0.2.16" resolution: "tinyglobby@npm:0.2.16"
@@ -2003,6 +2202,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"utrie@npm:^1.0.2":
version: 1.0.2
resolution: "utrie@npm:1.0.2"
dependencies:
base64-arraybuffer: "npm:^1.0.2"
checksum: 10c0/eaffe645bd81a39e4bc3abb23df5895e9961dbdd49748ef3b173529e8b06ce9dd1163e9705d5309a1c61ee41ffcb825e2043bc0fd1659845ffbdf4b1515dfdb4
languageName: node
linkType: hard
"vite@npm:^8.0.12": "vite@npm:^8.0.12":
version: 8.0.13 version: 8.0.13
resolution: "vite@npm:8.0.13" resolution: "vite@npm:8.0.13"