Compare commits
26 Commits
0da5b85173
...
0fb5a16b8e
Author | SHA1 | Date |
---|---|---|
|
0fb5a16b8e | |
|
87c158ebfe | |
|
cdf7131b16 | |
|
98a5dbe7f3 | |
|
a6d68f1120 | |
|
80947adc51 | |
|
301b2a20f4 | |
|
8b2ecae6e3 | |
|
442e40d87a | |
|
70f96701f7 | |
|
acd6a68507 | |
|
d25f9882fc | |
|
3b674f413a | |
|
84699708d5 | |
|
e8ec4c2a86 | |
|
5e2da17c28 | |
|
ae34cf6436 | |
|
5fa4860bfb | |
|
c9660633f0 | |
|
143d6c634b | |
|
5c0f5b2127 | |
|
93add0c315 | |
|
292c73e757 | |
|
20d4fa12d8 | |
|
342a5a2436 | |
|
2af0986d67 |
|
@ -0,0 +1,22 @@
|
||||||
|
@_show-dir:
|
||||||
|
echo "Current Working Directory:"
|
||||||
|
pwd
|
||||||
|
echo
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf "server/bin"
|
||||||
|
rm -rf "server/obj"
|
||||||
|
rm -rf "server.test/bin"
|
||||||
|
rm -rf "server.test/obj"
|
||||||
|
|
||||||
|
[working-directory: "server"]
|
||||||
|
publish: _show-dir
|
||||||
|
dotnet publish --self-contained false -t:PublishAllRids
|
||||||
|
|
||||||
|
[working-directory: "server"]
|
||||||
|
run-server: _show-dir
|
||||||
|
dotnet run
|
||||||
|
|
||||||
|
[working-directory: "server.test"]
|
||||||
|
test-server: _show-dir
|
||||||
|
dotnet test --logger "console;verbosity=detailed"
|
585
bun.lock
585
bun.lock
|
@ -1,585 +0,0 @@
|
||||||
{
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"workspaces": {
|
|
||||||
"": {
|
|
||||||
"name": "fpga-weblab",
|
|
||||||
"dependencies": {
|
|
||||||
"@trpc/client": "^10.45.2",
|
|
||||||
"@trpc/server": "^10.45.2",
|
|
||||||
"@types/lodash": "^4.17.16",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"log-symbols": "^7.0.0",
|
|
||||||
"pinia": "^3.0.1",
|
|
||||||
"tinypool": "^1.0.2",
|
|
||||||
"trpc-bun-adapter": "^1.2.2",
|
|
||||||
"ts-log": "^2.2.7",
|
|
||||||
"ts-results-es": "^5.0.1",
|
|
||||||
"vue": "^3.5.13",
|
|
||||||
"vue-router": "4",
|
|
||||||
"zod": "^3.24.2",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/postcss": "^4.0.12",
|
|
||||||
"@tsconfig/node22": "^22.0.0",
|
|
||||||
"@types/bun": "^1.2.5",
|
|
||||||
"@types/node": "^22.13.4",
|
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
|
||||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
|
||||||
"@vue/tsconfig": "^0.7.0",
|
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"bun-plugin-tailwind": "^0.0.15",
|
|
||||||
"daisyui": "^5.0.0",
|
|
||||||
"npm-run-all2": "^7.0.2",
|
|
||||||
"postcss": "^8.5.3",
|
|
||||||
"tailwindcss": "^4.0.12",
|
|
||||||
"typescript": "~5.7.3",
|
|
||||||
"vite": "^6.1.0",
|
|
||||||
"vite-plugin-vue-devtools": "^7.7.2",
|
|
||||||
"vue-tsc": "^2.2.2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"packages": {
|
|
||||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
|
||||||
|
|
||||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
|
||||||
|
|
||||||
"@antfu/utils": ["@antfu/utils@0.7.10", "", {}, "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww=="],
|
|
||||||
|
|
||||||
"@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
|
|
||||||
|
|
||||||
"@babel/compat-data": ["@babel/compat-data@7.26.8", "", {}, "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="],
|
|
||||||
|
|
||||||
"@babel/core": ["@babel/core@7.26.9", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.9", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.9", "@babel/parser": "^7.26.9", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.9", "@babel/types": "^7.26.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw=="],
|
|
||||||
|
|
||||||
"@babel/generator": ["@babel/generator@7.26.9", "", { "dependencies": { "@babel/parser": "^7.26.9", "@babel/types": "^7.26.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg=="],
|
|
||||||
|
|
||||||
"@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g=="],
|
|
||||||
|
|
||||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.26.5", "", { "dependencies": { "@babel/compat-data": "^7.26.5", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA=="],
|
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.26.9", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/helper-replace-supers": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/traverse": "^7.26.9", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg=="],
|
|
||||||
|
|
||||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ=="],
|
|
||||||
|
|
||||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="],
|
|
||||||
|
|
||||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="],
|
|
||||||
|
|
||||||
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.25.9", "", { "dependencies": { "@babel/types": "^7.25.9" } }, "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ=="],
|
|
||||||
|
|
||||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="],
|
|
||||||
|
|
||||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.26.5", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.25.9", "@babel/helper-optimise-call-expression": "^7.25.9", "@babel/traverse": "^7.26.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg=="],
|
|
||||||
|
|
||||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA=="],
|
|
||||||
|
|
||||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="],
|
|
||||||
|
|
||||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="],
|
|
||||||
|
|
||||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="],
|
|
||||||
|
|
||||||
"@babel/helpers": ["@babel/helpers@7.26.9", "", { "dependencies": { "@babel/template": "^7.26.9", "@babel/types": "^7.26.9" } }, "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA=="],
|
|
||||||
|
|
||||||
"@babel/parser": ["@babel/parser@7.26.9", "", { "dependencies": { "@babel/types": "^7.26.9" }, "bin": "./bin/babel-parser.js" }, "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A=="],
|
|
||||||
|
|
||||||
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.25.9", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "@babel/plugin-syntax-decorators": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g=="],
|
|
||||||
|
|
||||||
"@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg=="],
|
|
||||||
|
|
||||||
"@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.26.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A=="],
|
|
||||||
|
|
||||||
"@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
|
|
||||||
|
|
||||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA=="],
|
|
||||||
|
|
||||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.26.8", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", "@babel/helper-create-class-features-plugin": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", "@babel/plugin-syntax-typescript": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw=="],
|
|
||||||
|
|
||||||
"@babel/template": ["@babel/template@7.26.9", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.26.9", "@babel/types": "^7.26.9" } }, "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA=="],
|
|
||||||
|
|
||||||
"@babel/traverse": ["@babel/traverse@7.26.9", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.9", "@babel/parser": "^7.26.9", "@babel/template": "^7.26.9", "@babel/types": "^7.26.9", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg=="],
|
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.26.9", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw=="],
|
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ=="],
|
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.0", "", { "os": "android", "cpu": "arm" }, "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g=="],
|
|
||||||
|
|
||||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.0", "", { "os": "android", "cpu": "arm64" }, "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g=="],
|
|
||||||
|
|
||||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.0", "", { "os": "android", "cpu": "x64" }, "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg=="],
|
|
||||||
|
|
||||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw=="],
|
|
||||||
|
|
||||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg=="],
|
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w=="],
|
|
||||||
|
|
||||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.0", "", { "os": "linux", "cpu": "arm" }, "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.0", "", { "os": "linux", "cpu": "none" }, "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.0", "", { "os": "linux", "cpu": "none" }, "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.0", "", { "os": "linux", "cpu": "none" }, "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.0", "", { "os": "linux", "cpu": "x64" }, "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw=="],
|
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.0", "", { "os": "none", "cpu": "arm64" }, "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw=="],
|
|
||||||
|
|
||||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.0", "", { "os": "none", "cpu": "x64" }, "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA=="],
|
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw=="],
|
|
||||||
|
|
||||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg=="],
|
|
||||||
|
|
||||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ=="],
|
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
|
||||||
|
|
||||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
|
||||||
|
|
||||||
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
|
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
|
|
||||||
|
|
||||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.28", "", {}, "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw=="],
|
|
||||||
|
|
||||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.9", "", { "os": "android", "cpu": "arm" }, "sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.9", "", { "os": "android", "cpu": "arm64" }, "sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.9", "", { "os": "linux", "cpu": "arm" }, "sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.9", "", { "os": "linux", "cpu": "arm" }, "sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.9", "", { "os": "linux", "cpu": "none" }, "sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.9", "", { "os": "linux", "cpu": "none" }, "sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.9", "", { "os": "linux", "cpu": "x64" }, "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.9", "", { "os": "linux", "cpu": "x64" }, "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.9", "", { "os": "win32", "cpu": "x64" }, "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw=="],
|
|
||||||
|
|
||||||
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
|
||||||
|
|
||||||
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
|
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.0.12", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.12" } }, "sha512-a6J11K1Ztdln9OrGfoM75/hChYPcHYGNYimqciMrvKXRmmPaS8XZTHhdvb5a3glz4Kd4ZxE1MnuFE2c0fGGmtg=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.12", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.12", "@tailwindcss/oxide-darwin-arm64": "4.0.12", "@tailwindcss/oxide-darwin-x64": "4.0.12", "@tailwindcss/oxide-freebsd-x64": "4.0.12", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.12", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.12", "@tailwindcss/oxide-linux-arm64-musl": "4.0.12", "@tailwindcss/oxide-linux-x64-gnu": "4.0.12", "@tailwindcss/oxide-linux-x64-musl": "4.0.12", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.12", "@tailwindcss/oxide-win32-x64-msvc": "4.0.12" } }, "sha512-DWb+myvJB9xJwelwT9GHaMc1qJj6MDXRDR0CS+T8IdkejAtu8ctJAgV4r1drQJLPeS7mNwq2UHW2GWrudTf63A=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.12", "", { "os": "android", "cpu": "arm64" }, "sha512-dAXCaemu3mHLXcA5GwGlQynX8n7tTdvn5i1zAxRvZ5iC9fWLl5bGnjZnzrQqT7ttxCvRwdVf3IHUnMVdDBO/kQ=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vPNI+TpJQ7sizselDXIJdYkx9Cu6JBdtmRWujw9pVIxW8uz3O2PjgGGzL/7A0sXI8XDjSyRChrUnEW9rQygmJQ=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-RL/9jM41Fdq4Efr35C5wgLx98BirnrfwuD+zgMFK6Ir68HeOSqBhW9jsEeC7Y/JcGyPd3MEoJVIU4fAb7YLg7A=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-7WzWiax+LguJcMEimY0Q4sBLlFXu1tYxVka3+G2M9KmU/3m84J3jAIV4KZWnockbHsbb2XgrEjtlJKVwHQCoRA=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.12", "", { "os": "linux", "cpu": "arm" }, "sha512-X9LRC7jjE1QlfIaBbXjY0PGeQP87lz5mEfLSVs2J1yRc9PSg1tEPS9NBqY4BU9v5toZgJgzKeaNltORyTs22TQ=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-i24IFNq2402zfDdoWKypXz0ZNS2G4NKaA82tgBlE2OhHIE+4mg2JDb5wVfyP6R+MCm5grgXvurcIcKWvo44QiQ=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-LmOdshJBfAGIBG0DdBWhI0n5LTMurnGGJCHcsm9F//ISfsHtCnnYIKgYQui5oOz1SUCkqsMGfkAzWyNKZqbGNw=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.12", "", { "os": "linux", "cpu": "x64" }, "sha512-OSK667qZRH30ep8RiHbZDQfqkXjnzKxdn0oRwWzgCO8CoTxV+MvIkd0BWdQbYtYuM1wrakARV/Hwp0eA/qzdbw=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uylhWq6OWQ8krV8Jk+v0H/3AZKJW6xYMgNMyNnUbbYXWi7hIVdxRKNUB5UvrlC3RxtgsK5EAV2i1CWTRsNcAnA=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-XDLnhMoXZEEOir1LK43/gHHwK84V1GlV8+pAncUAIN2wloeD+nNciI9WRIY/BeFTqES22DhTIGoilSO39xDb2g=="],
|
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.12", "", { "os": "win32", "cpu": "x64" }, "sha512-I/BbjCLpKDQucvtn6rFuYLst1nfFwSMYyPzkx/095RE+tuzk5+fwXuzQh7T3fIBTcbn82qH/sFka7yPGA50tLw=="],
|
|
||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.0.12", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.0.12", "@tailwindcss/oxide": "4.0.12", "lightningcss": "^1.29.1", "postcss": "^8.4.41", "tailwindcss": "4.0.12" } }, "sha512-r59Sdr8djCW4dL3kvc4aWU8PHdUAVM3O3te2nbYzXsWwKLlHPCuUoZAc9FafXb/YyNDZOMI7sTbKTKFmwOrMjw=="],
|
|
||||||
|
|
||||||
"@trpc/client": ["@trpc/client@10.45.2", "", { "peerDependencies": { "@trpc/server": "10.45.2" } }, "sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg=="],
|
|
||||||
|
|
||||||
"@trpc/server": ["@trpc/server@10.45.2", "", {}, "sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg=="],
|
|
||||||
|
|
||||||
"@tsconfig/node22": ["@tsconfig/node22@22.0.0", "", {}, "sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg=="],
|
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.5", "", { "dependencies": { "bun-types": "1.2.5" } }, "sha512-w2OZTzrZTVtbnJew1pdFmgV99H0/L+Pvw+z1P67HaR18MHOzYnTYOi6qzErhK8HyT+DB782ADVPPE92Xu2/Opg=="],
|
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
|
||||||
|
|
||||||
"@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="],
|
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
|
||||||
|
|
||||||
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
|
|
||||||
|
|
||||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.1", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ=="],
|
|
||||||
|
|
||||||
"@vitejs/plugin-vue-jsx": ["@vitejs/plugin-vue-jsx@4.1.1", "", { "dependencies": { "@babel/core": "^7.26.0", "@babel/plugin-transform-typescript": "^7.25.9", "@vue/babel-plugin-jsx": "^1.2.5" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.0.0" } }, "sha512-uMJqv/7u1zz/9NbWAD3XdjaY20tKTf17XVfQ9zq4wY1BjsB/PjpJPMe2xiG39QpP4ZdhYNhm4Hvo66uJrykNLA=="],
|
|
||||||
|
|
||||||
"@volar/language-core": ["@volar/language-core@2.4.12", "", { "dependencies": { "@volar/source-map": "2.4.12" } }, "sha512-RLrFdXEaQBWfSnYGVxvR2WrO6Bub0unkdHYIdC31HzIEqATIuuhRRzYu76iGPZ6OtA4Au1SnW0ZwIqPP217YhA=="],
|
|
||||||
|
|
||||||
"@volar/source-map": ["@volar/source-map@2.4.12", "", {}, "sha512-bUFIKvn2U0AWojOaqf63ER0N/iHIBYZPpNGogfLPQ68F5Eet6FnLlyho7BS0y2HJ1jFhSif7AcuTx1TqsCzRzw=="],
|
|
||||||
|
|
||||||
"@volar/typescript": ["@volar/typescript@2.4.12", "", { "dependencies": { "@volar/language-core": "2.4.12", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-HJB73OTJDgPc80K30wxi3if4fSsZZAOScbj2fcicMuOPoOkcf9NNAINb33o+DzhBdF9xTKC1gnPmIRDous5S0g=="],
|
|
||||||
|
|
||||||
"@vue/babel-helper-vue-transform-on": ["@vue/babel-helper-vue-transform-on@1.3.0", "", {}, "sha512-vrNyYNQcz1gfc87uuN+Z+On9fFOBQTYRlTUEDovpeCmjuwH83lAm6YM0VBvTx6eRTHg3SU5jP2CD+kSXY30PGg=="],
|
|
||||||
|
|
||||||
"@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.3.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/plugin-syntax-jsx": "^7.25.9", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.9", "@babel/types": "^7.26.9", "@vue/babel-helper-vue-transform-on": "1.3.0", "@vue/babel-plugin-resolve-type": "1.3.0", "@vue/shared": "^3.5.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-ODZSs93FCxLMOiMFAGJXe7QMJp1tk8hkMbk84OcHOTVwYU2cFwFu1z7jjrRv44wCCfPNkflqn6hnexVprb+G7A=="],
|
|
||||||
|
|
||||||
"@vue/babel-plugin-resolve-type": ["@vue/babel-plugin-resolve-type@1.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "@babel/parser": "^7.26.9", "@vue/compiler-sfc": "^3.5.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3SmusE11QKNKtnVfbsKegUEArpf1fXE85Dzi/Q6lvaz3MA3tmL8BXyq/vA7GJeZ183XeNpLIZHrHDdUh9V348A=="],
|
|
||||||
|
|
||||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q=="],
|
|
||||||
|
|
||||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.13", "", { "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA=="],
|
|
||||||
|
|
||||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.13", "", { "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", "@vue/compiler-dom": "3.5.13", "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ=="],
|
|
||||||
|
|
||||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA=="],
|
|
||||||
|
|
||||||
"@vue/compiler-vue2": ["@vue/compiler-vue2@2.7.16", "", { "dependencies": { "de-indent": "^1.0.2", "he": "^1.2.0" } }, "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A=="],
|
|
||||||
|
|
||||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.2", "", { "dependencies": { "@vue/devtools-kit": "^7.7.2" } }, "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA=="],
|
|
||||||
|
|
||||||
"@vue/devtools-core": ["@vue/devtools-core@7.7.2", "", { "dependencies": { "@vue/devtools-kit": "^7.7.2", "@vue/devtools-shared": "^7.7.2", "mitt": "^3.0.1", "nanoid": "^5.0.9", "pathe": "^2.0.2", "vite-hot-client": "^0.2.4" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-lexREWj1lKi91Tblr38ntSsy6CvI8ba7u+jmwh2yruib/ltLUcsIzEjCnrkh1yYGGIKXbAuYV2tOG10fGDB9OQ=="],
|
|
||||||
|
|
||||||
"@vue/devtools-kit": ["@vue/devtools-kit@7.7.2", "", { "dependencies": { "@vue/devtools-shared": "^7.7.2", "birpc": "^0.2.19", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.1" } }, "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ=="],
|
|
||||||
|
|
||||||
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.2", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA=="],
|
|
||||||
|
|
||||||
"@vue/language-core": ["@vue/language-core@2.2.8", "", { "dependencies": { "@volar/language-core": "~2.4.11", "@vue/compiler-dom": "^3.5.0", "@vue/compiler-vue2": "^2.7.16", "@vue/shared": "^3.5.0", "alien-signals": "^1.0.3", "minimatch": "^9.0.3", "muggle-string": "^0.4.1", "path-browserify": "^1.0.1" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ=="],
|
|
||||||
|
|
||||||
"@vue/reactivity": ["@vue/reactivity@3.5.13", "", { "dependencies": { "@vue/shared": "3.5.13" } }, "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg=="],
|
|
||||||
|
|
||||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" } }, "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw=="],
|
|
||||||
|
|
||||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.13", "", { "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog=="],
|
|
||||||
|
|
||||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.13", "", { "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "vue": "3.5.13" } }, "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA=="],
|
|
||||||
|
|
||||||
"@vue/shared": ["@vue/shared@3.5.13", "", {}, "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="],
|
|
||||||
|
|
||||||
"@vue/tsconfig": ["@vue/tsconfig@0.7.0", "", { "peerDependencies": { "typescript": "5.x", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg=="],
|
|
||||||
|
|
||||||
"alien-signals": ["alien-signals@1.0.4", "", {}, "sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw=="],
|
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
|
||||||
|
|
||||||
"autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="],
|
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
|
||||||
|
|
||||||
"birpc": ["birpc@0.2.19", "", {}, "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ=="],
|
|
||||||
|
|
||||||
"brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
|
|
||||||
|
|
||||||
"browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="],
|
|
||||||
|
|
||||||
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.5", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-3oO6LVGGRRKI4kHINx5PIdIgnLRb7l/SprhzqXapmoYkFl5m4j6EvALvbDVuuBFaamB46Ap6HCUxIXNLCGy+tg=="],
|
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001702", "", {}, "sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA=="],
|
|
||||||
|
|
||||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
|
||||||
|
|
||||||
"copy-anything": ["copy-anything@3.0.5", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w=="],
|
|
||||||
|
|
||||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
|
||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
|
||||||
|
|
||||||
"daisyui": ["daisyui@5.0.0", "", {}, "sha512-U0K9Bac3Bi3zZGm6ojrw12F0vBHTpEgf46zv/BYxLe07hF0Xnx7emIQliwaRBgJuYhY0BhwQ6wSnq5cJXHA2yA=="],
|
|
||||||
|
|
||||||
"de-indent": ["de-indent@1.0.2", "", {}, "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="],
|
|
||||||
|
|
||||||
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
|
|
||||||
|
|
||||||
"default-browser": ["default-browser@5.2.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg=="],
|
|
||||||
|
|
||||||
"default-browser-id": ["default-browser-id@5.0.0", "", {}, "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA=="],
|
|
||||||
|
|
||||||
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
|
|
||||||
|
|
||||||
"detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
|
|
||||||
|
|
||||||
"electron-to-chromium": ["electron-to-chromium@1.5.113", "", {}, "sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg=="],
|
|
||||||
|
|
||||||
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
|
|
||||||
|
|
||||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
|
||||||
|
|
||||||
"error-stack-parser-es": ["error-stack-parser-es@0.1.5", "", {}, "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg=="],
|
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.25.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.0", "@esbuild/android-arm": "0.25.0", "@esbuild/android-arm64": "0.25.0", "@esbuild/android-x64": "0.25.0", "@esbuild/darwin-arm64": "0.25.0", "@esbuild/darwin-x64": "0.25.0", "@esbuild/freebsd-arm64": "0.25.0", "@esbuild/freebsd-x64": "0.25.0", "@esbuild/linux-arm": "0.25.0", "@esbuild/linux-arm64": "0.25.0", "@esbuild/linux-ia32": "0.25.0", "@esbuild/linux-loong64": "0.25.0", "@esbuild/linux-mips64el": "0.25.0", "@esbuild/linux-ppc64": "0.25.0", "@esbuild/linux-riscv64": "0.25.0", "@esbuild/linux-s390x": "0.25.0", "@esbuild/linux-x64": "0.25.0", "@esbuild/netbsd-arm64": "0.25.0", "@esbuild/netbsd-x64": "0.25.0", "@esbuild/openbsd-arm64": "0.25.0", "@esbuild/openbsd-x64": "0.25.0", "@esbuild/sunos-x64": "0.25.0", "@esbuild/win32-arm64": "0.25.0", "@esbuild/win32-ia32": "0.25.0", "@esbuild/win32-x64": "0.25.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw=="],
|
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
|
||||||
|
|
||||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
|
||||||
|
|
||||||
"execa": ["execa@9.5.2", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.0", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.0.0" } }, "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q=="],
|
|
||||||
|
|
||||||
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
|
|
||||||
|
|
||||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
|
||||||
|
|
||||||
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
|
||||||
|
|
||||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
|
||||||
|
|
||||||
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
|
||||||
|
|
||||||
"globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
|
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
|
||||||
|
|
||||||
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
|
|
||||||
|
|
||||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
|
||||||
|
|
||||||
"human-signals": ["human-signals@8.0.0", "", {}, "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA=="],
|
|
||||||
|
|
||||||
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
|
|
||||||
|
|
||||||
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
|
|
||||||
|
|
||||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
|
||||||
|
|
||||||
"is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
|
|
||||||
|
|
||||||
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
|
||||||
|
|
||||||
"is-what": ["is-what@4.1.16", "", {}, "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A=="],
|
|
||||||
|
|
||||||
"is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
|
|
||||||
|
|
||||||
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
|
||||||
|
|
||||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
|
||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
|
||||||
|
|
||||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
|
||||||
|
|
||||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="],
|
|
||||||
|
|
||||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
|
||||||
|
|
||||||
"jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
|
|
||||||
|
|
||||||
"kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="],
|
|
||||||
|
|
||||||
"lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="],
|
|
||||||
|
|
||||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="],
|
|
||||||
|
|
||||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="],
|
|
||||||
|
|
||||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg=="],
|
|
||||||
|
|
||||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.2", "", { "os": "linux", "cpu": "arm" }, "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg=="],
|
|
||||||
|
|
||||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ=="],
|
|
||||||
|
|
||||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ=="],
|
|
||||||
|
|
||||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg=="],
|
|
||||||
|
|
||||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w=="],
|
|
||||||
|
|
||||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw=="],
|
|
||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
|
|
||||||
|
|
||||||
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
|
|
||||||
|
|
||||||
"log-symbols": ["log-symbols@7.0.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-zrc91EDk2M+2AXo/9BTvK91pqb7qrPg2nX/Hy+u8a5qQlbaOflCKO+6SqgZ+M+xUFxGdKTgwnGiL96b1W3ikRA=="],
|
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
|
|
||||||
|
|
||||||
"memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
|
|
||||||
|
|
||||||
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
|
||||||
|
|
||||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
|
||||||
|
|
||||||
"muggle-string": ["muggle-string@0.4.1", "", {}, "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ=="],
|
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.9", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-SppoicMGpZvbF1l3z4x7No3OlIjP7QJvC9XR7AhZr1kL133KHnKPztkKDc+Ir4aJ/1VhTySrtKhrsycmrMQfvg=="],
|
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
|
||||||
|
|
||||||
"normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="],
|
|
||||||
|
|
||||||
"npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="],
|
|
||||||
|
|
||||||
"npm-run-all2": ["npm-run-all2@7.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "minimatch": "^9.0.0", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", "which": "^5.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA=="],
|
|
||||||
|
|
||||||
"npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
|
|
||||||
|
|
||||||
"open": ["open@10.1.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw=="],
|
|
||||||
|
|
||||||
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
|
||||||
|
|
||||||
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
|
||||||
|
|
||||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
|
||||||
|
|
||||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
|
||||||
|
|
||||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
|
||||||
|
|
||||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
|
||||||
|
|
||||||
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
|
|
||||||
|
|
||||||
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
|
||||||
|
|
||||||
"pinia": ["pinia@3.0.1", "", { "dependencies": { "@vue/devtools-api": "^7.7.2" }, "peerDependencies": { "typescript": ">=4.4.4", "vue": "^2.7.0 || ^3.5.11" }, "optionalPeers": ["typescript"] }, "sha512-WXglsDzztOTH6IfcJ99ltYZin2mY8XZCXujkYWVIJlBjqsP6ST7zw+Aarh63E1cDVYeyUcPCxPHzJpEOmzB6Wg=="],
|
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
|
|
||||||
|
|
||||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
|
||||||
|
|
||||||
"pretty-ms": ["pretty-ms@9.2.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg=="],
|
|
||||||
|
|
||||||
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
|
|
||||||
|
|
||||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
|
||||||
|
|
||||||
"rollup": ["rollup@4.34.9", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.9", "@rollup/rollup-android-arm64": "4.34.9", "@rollup/rollup-darwin-arm64": "4.34.9", "@rollup/rollup-darwin-x64": "4.34.9", "@rollup/rollup-freebsd-arm64": "4.34.9", "@rollup/rollup-freebsd-x64": "4.34.9", "@rollup/rollup-linux-arm-gnueabihf": "4.34.9", "@rollup/rollup-linux-arm-musleabihf": "4.34.9", "@rollup/rollup-linux-arm64-gnu": "4.34.9", "@rollup/rollup-linux-arm64-musl": "4.34.9", "@rollup/rollup-linux-loongarch64-gnu": "4.34.9", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.9", "@rollup/rollup-linux-riscv64-gnu": "4.34.9", "@rollup/rollup-linux-s390x-gnu": "4.34.9", "@rollup/rollup-linux-x64-gnu": "4.34.9", "@rollup/rollup-linux-x64-musl": "4.34.9", "@rollup/rollup-win32-arm64-msvc": "4.34.9", "@rollup/rollup-win32-ia32-msvc": "4.34.9", "@rollup/rollup-win32-x64-msvc": "4.34.9", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ=="],
|
|
||||||
|
|
||||||
"run-applescript": ["run-applescript@7.0.0", "", {}, "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A=="],
|
|
||||||
|
|
||||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
|
||||||
|
|
||||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
|
||||||
|
|
||||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
|
||||||
|
|
||||||
"shell-quote": ["shell-quote@1.8.2", "", {}, "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA=="],
|
|
||||||
|
|
||||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
|
||||||
|
|
||||||
"sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="],
|
|
||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
|
||||||
|
|
||||||
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
|
||||||
|
|
||||||
"strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
|
|
||||||
|
|
||||||
"superjson": ["superjson@2.2.2", "", { "dependencies": { "copy-anything": "^3.0.2" } }, "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q=="],
|
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.0.12", "", {}, "sha512-bT0hJo91FtncsAMSsMzUkoo/iEU0Xs5xgFgVC9XmdM9bw5MhZuQFjPNl6wxAE0SiQF/YTZJa+PndGWYSDtuxAg=="],
|
|
||||||
|
|
||||||
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
|
|
||||||
|
|
||||||
"tinypool": ["tinypool@1.0.2", "", {}, "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA=="],
|
|
||||||
|
|
||||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
|
||||||
|
|
||||||
"trpc-bun-adapter": ["trpc-bun-adapter@1.2.2", "", { "peerDependencies": { "@trpc/server": "^11.0.0-rc.566" } }, "sha512-TVhZEDXZvhIM2lfNTVCx9u5fu7/86b7RuhQSM0CUs4vlIan64Sfko5m0stbjqSHNgvLBsvXKtUD8FeQOQGLfpg=="],
|
|
||||||
|
|
||||||
"ts-log": ["ts-log@2.2.7", "", {}, "sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg=="],
|
|
||||||
|
|
||||||
"ts-results-es": ["ts-results-es@5.0.1", "", {}, "sha512-HjX/7HxQe2bXkbp8pHTjy4Ir9eHIDnDDsLDphhGqy6I9iZ/vD4QXWEIlrVRZsEX+kS2jIiiF/mnl0nKnPTiYFw=="],
|
|
||||||
|
|
||||||
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
|
||||||
|
|
||||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
|
||||||
|
|
||||||
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
|
|
||||||
|
|
||||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
|
||||||
|
|
||||||
"update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
|
|
||||||
|
|
||||||
"vite": ["vite@6.2.1", "", { "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q=="],
|
|
||||||
|
|
||||||
"vite-hot-client": ["vite-hot-client@0.2.4", "", { "peerDependencies": { "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" } }, "sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA=="],
|
|
||||||
|
|
||||||
"vite-plugin-inspect": ["vite-plugin-inspect@0.8.9", "", { "dependencies": { "@antfu/utils": "^0.7.10", "@rollup/pluginutils": "^5.1.3", "debug": "^4.3.7", "error-stack-parser-es": "^0.1.5", "fs-extra": "^11.2.0", "open": "^10.1.0", "perfect-debounce": "^1.0.0", "picocolors": "^1.1.1", "sirv": "^3.0.0" }, "peerDependencies": { "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" } }, "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A=="],
|
|
||||||
|
|
||||||
"vite-plugin-vue-devtools": ["vite-plugin-vue-devtools@7.7.2", "", { "dependencies": { "@vue/devtools-core": "^7.7.2", "@vue/devtools-kit": "^7.7.2", "@vue/devtools-shared": "^7.7.2", "execa": "^9.5.1", "sirv": "^3.0.0", "vite-plugin-inspect": "0.8.9", "vite-plugin-vue-inspector": "^5.3.1" }, "peerDependencies": { "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" } }, "sha512-5V0UijQWiSBj32blkyPEqIbzc6HO9c1bwnBhx+ay2dzU0FakH+qMdNUT8nF9BvDE+i6I1U8CqCuJiO20vKEdQw=="],
|
|
||||||
|
|
||||||
"vite-plugin-vue-inspector": ["vite-plugin-vue-inspector@5.3.1", "", { "dependencies": { "@babel/core": "^7.23.0", "@babel/plugin-proposal-decorators": "^7.23.0", "@babel/plugin-syntax-import-attributes": "^7.22.5", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-transform-typescript": "^7.22.15", "@vue/babel-plugin-jsx": "^1.1.5", "@vue/compiler-dom": "^3.3.4", "kolorist": "^1.8.0", "magic-string": "^0.30.4" }, "peerDependencies": { "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" } }, "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A=="],
|
|
||||||
|
|
||||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
|
||||||
|
|
||||||
"vue": ["vue@3.5.13", "", { "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/compiler-sfc": "3.5.13", "@vue/runtime-dom": "3.5.13", "@vue/server-renderer": "3.5.13", "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ=="],
|
|
||||||
|
|
||||||
"vue-router": ["vue-router@4.5.0", "", { "dependencies": { "@vue/devtools-api": "^6.6.4" }, "peerDependencies": { "vue": "^3.2.0" } }, "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w=="],
|
|
||||||
|
|
||||||
"vue-tsc": ["vue-tsc@2.2.8", "", { "dependencies": { "@volar/typescript": "~2.4.11", "@vue/language-core": "2.2.8" }, "peerDependencies": { "typescript": ">=5.0.0" }, "bin": { "vue-tsc": "./bin/vue-tsc.js" } }, "sha512-jBYKBNFADTN+L+MdesNX/TB3XuDSyaWynKMDgR+yCSln0GQ9Tfb7JS2lr46s2LiFUT1WsmfWsSvIElyxzOPqcQ=="],
|
|
||||||
|
|
||||||
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
|
||||||
|
|
||||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
|
||||||
|
|
||||||
"yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="],
|
|
||||||
|
|
||||||
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
|
||||||
|
|
||||||
"@vue/devtools-core/nanoid": ["nanoid@5.1.3", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ=="],
|
|
||||||
|
|
||||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
|
||||||
|
|
||||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
|
||||||
|
|
||||||
"vue-router/@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="],
|
|
||||||
|
|
||||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
[serve.static]
|
|
||||||
plugins = ["bun-plugin-tailwind"]
|
|
|
@ -7,7 +7,10 @@
|
||||||
let
|
let
|
||||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||||
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
|
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config.permittedInsecurePackages = ["dotnet-sdk-6.0.428"];
|
||||||
|
};
|
||||||
});
|
});
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
@ -15,7 +18,7 @@
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
# Frontend
|
# Frontend
|
||||||
bun
|
nodejs
|
||||||
sqlite
|
sqlite
|
||||||
sqls
|
sqls
|
||||||
sql-studio
|
sql-studio
|
||||||
|
@ -26,6 +29,7 @@
|
||||||
dotnetCorePackages.sdk_8_0
|
dotnetCorePackages.sdk_8_0
|
||||||
])
|
])
|
||||||
nuget
|
nuget
|
||||||
|
# msbuild
|
||||||
omnisharp-roslyn
|
omnisharp-roslyn
|
||||||
csharpier
|
csharpier
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
|
@ -5,22 +5,20 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./server/index.ts",
|
"main": "./server/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bunx --bun vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "bunx --bun vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "bunx --bun vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "bunx --bun vue-tsc --build",
|
"type-check": "vue-tsc --build",
|
||||||
"server": "bun run --watch ./server/index.ts"
|
"server": "run --watch ./server/index.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trpc/client": "^10.45.2",
|
|
||||||
"@trpc/server": "^10.45.2",
|
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
|
"all": "^0.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"log-symbols": "^7.0.0",
|
"log-symbols": "^7.0.0",
|
||||||
"pinia": "^3.0.1",
|
"pinia": "^3.0.1",
|
||||||
"tinypool": "^1.0.2",
|
"tinypool": "^1.0.2",
|
||||||
"trpc-bun-adapter": "^1.2.2",
|
|
||||||
"ts-log": "^2.2.7",
|
"ts-log": "^2.2.7",
|
||||||
"ts-results-es": "^5.0.1",
|
"ts-results-es": "^5.0.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
|
@ -30,13 +28,11 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.0.12",
|
"@tailwindcss/postcss": "^4.0.12",
|
||||||
"@tsconfig/node22": "^22.0.0",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
"@types/bun": "^1.2.5",
|
|
||||||
"@types/node": "^22.13.4",
|
"@types/node": "^22.13.4",
|
||||||
"@vitejs/plugin-vue": "^5.2.1",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||||
"@vue/tsconfig": "^0.7.0",
|
"@vue/tsconfig": "^0.7.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bun-plugin-tailwind": "^0.0.15",
|
|
||||||
"daisyui": "^5.0.0",
|
"daisyui": "^5.0.0",
|
||||||
"npm-run-all2": "^7.0.2",
|
"npm-run-all2": "^7.0.2",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
bin
|
||||||
|
obj
|
||||||
|
bitstream
|
|
@ -0,0 +1,162 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using Common;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace server.test;
|
||||||
|
|
||||||
|
public sealed class RepeatAttribute : Xunit.Sdk.DataAttribute
|
||||||
|
{
|
||||||
|
private readonly int count;
|
||||||
|
|
||||||
|
public RepeatAttribute(int count)
|
||||||
|
{
|
||||||
|
if (count < 1)
|
||||||
|
{
|
||||||
|
throw new System.ArgumentOutOfRangeException(
|
||||||
|
paramName: nameof(count),
|
||||||
|
message: "Repeat count must be greater than 0."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override System.Collections.Generic.IEnumerable<object[]> GetData(System.Reflection.MethodInfo testMethod)
|
||||||
|
{
|
||||||
|
foreach (var iterationNumber in Enumerable.Range(start: 1, count: this.count))
|
||||||
|
{
|
||||||
|
yield return new object[] { iterationNumber };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UDPServerTest
|
||||||
|
{
|
||||||
|
const string address = "127.0.0.1";
|
||||||
|
const int port = 33000;
|
||||||
|
private static readonly UDPServer udpServer = new UDPServer(port);
|
||||||
|
|
||||||
|
private readonly ITestOutputHelper output;
|
||||||
|
|
||||||
|
public UDPServerTest(ITestOutputHelper output)
|
||||||
|
{
|
||||||
|
this.output = output;
|
||||||
|
udpServer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UDPDataDeepClone()
|
||||||
|
{
|
||||||
|
var udpData = new UDPData()
|
||||||
|
{
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
Address = "127.0.0.1",
|
||||||
|
Port = 1234,
|
||||||
|
Data = new byte[] { 0xf0, 00, 00, 00 },
|
||||||
|
HasRead = false
|
||||||
|
};
|
||||||
|
var cloneUdpData = udpData.DeepClone();
|
||||||
|
|
||||||
|
Assert.Equal(udpData.DateTime, cloneUdpData.DateTime);
|
||||||
|
Assert.Equal(udpData.Address, cloneUdpData.Address);
|
||||||
|
Assert.Equal(udpData.Port, cloneUdpData.Port);
|
||||||
|
Assert.Equal(udpData.Data, cloneUdpData.Data);
|
||||||
|
Assert.Equal(udpData.HasRead, cloneUdpData.HasRead);
|
||||||
|
|
||||||
|
udpData.DateTime = DateTime.Now;
|
||||||
|
udpData.Address = "192.168.1.1";
|
||||||
|
udpData.Port = 33000;
|
||||||
|
udpData.Data = new byte[] { 0xFF, 00, 00, 00 };
|
||||||
|
udpData.HasRead = true;
|
||||||
|
|
||||||
|
Assert.NotNull(cloneUdpData.DateTime);
|
||||||
|
Assert.NotNull(cloneUdpData.Address);
|
||||||
|
Assert.NotNull(cloneUdpData.Port);
|
||||||
|
Assert.NotNull(cloneUdpData.Data);
|
||||||
|
Assert.NotNull(cloneUdpData.HasRead);
|
||||||
|
|
||||||
|
Assert.NotEqual(udpData.DateTime, cloneUdpData.DateTime);
|
||||||
|
Assert.NotEqual(udpData.Address, cloneUdpData.Address);
|
||||||
|
Assert.NotEqual(udpData.Port, cloneUdpData.Port);
|
||||||
|
Assert.NotEqual(udpData.Data, cloneUdpData.Data);
|
||||||
|
Assert.NotEqual(udpData.HasRead, cloneUdpData.HasRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new object[] { new string[] { "Hello World!", "Hello Server!", "What is your problem?" } })]
|
||||||
|
public async Task UDPServerFindString(string[] textArray)
|
||||||
|
{
|
||||||
|
Assert.True(udpServer.IsRunning);
|
||||||
|
|
||||||
|
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
|
||||||
|
foreach (var text in textArray)
|
||||||
|
{
|
||||||
|
Assert.True(await UDPClientPool.SendStringAsync(serverEP, [text]));
|
||||||
|
var ret = await udpServer.FindDataAsync(address);
|
||||||
|
Assert.True(ret.HasValue);
|
||||||
|
var data = ret.Value;
|
||||||
|
Assert.Equal(address, data.Address);
|
||||||
|
Assert.Equal(text, Encoding.ASCII.GetString(data.Data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new object[] { new UInt32[] { 0xF0_00_00_00, 0xFF_00_00_00, 0xFF_FF_FF_FF } })]
|
||||||
|
public async Task UDPServerFindBytes(UInt32[] bytesArray)
|
||||||
|
{
|
||||||
|
Assert.True(udpServer.IsRunning);
|
||||||
|
|
||||||
|
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
|
||||||
|
foreach (var number in bytesArray)
|
||||||
|
{
|
||||||
|
Assert.True(await UDPClientPool.SendBytesAsync(serverEP, Number.NumberToBytes(number, 4).Value));
|
||||||
|
var ret = await udpServer.FindDataAsync(address);
|
||||||
|
Assert.True(ret.HasValue);
|
||||||
|
var data = ret.Value;
|
||||||
|
Assert.Equal(address, data.Address);
|
||||||
|
Assert.Equal(number, Number.BytesToNumber(data.Data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new object[] { new UInt32[] { 0xF0_00_00_00, 0xF0_01_00_00 } })]
|
||||||
|
public async Task UDPServerWaitResp(UInt32[] bytesArray)
|
||||||
|
{
|
||||||
|
Assert.True(udpServer.IsRunning);
|
||||||
|
|
||||||
|
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
|
||||||
|
foreach (var number in bytesArray)
|
||||||
|
{
|
||||||
|
Assert.True(await UDPClientPool.SendBytesAsync(serverEP, Number.NumberToBytes(number, 4).Value));
|
||||||
|
|
||||||
|
var ret = await udpServer.WaitForAckAsync(address);
|
||||||
|
Assert.True(ret.IsSuccessful);
|
||||||
|
var data = ret.Value;
|
||||||
|
Assert.True(data.IsSuccessful);
|
||||||
|
Assert.Equal(number, Number.BytesToNumber(data.ToBytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new object[] { new UInt64[] { 0x0F_00_00_00_01_02_02_02, 0x0F_01_00_00_FF_FF_FF_FF } })]
|
||||||
|
public async Task UDPServerWaitData(UInt64[] bytesArray)
|
||||||
|
{
|
||||||
|
Assert.True(udpServer.IsRunning);
|
||||||
|
|
||||||
|
var serverEP = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
|
||||||
|
foreach (var number in bytesArray)
|
||||||
|
{
|
||||||
|
Assert.True(await UDPClientPool.SendBytesAsync(serverEP, Number.NumberToBytes(number, 8).Value));
|
||||||
|
|
||||||
|
var ret = await udpServer.WaitForDataAsync(address);
|
||||||
|
Assert.True(ret.IsSuccessful);
|
||||||
|
var data = ret.Value;
|
||||||
|
Assert.True(data.IsSuccessful);
|
||||||
|
Assert.Equal(number, Number.BytesToNumber(data.ToBytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\server\server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"showLiveOutput": true
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
obj
|
obj
|
||||||
bin
|
bin
|
||||||
|
bitstream
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,113 @@
|
||||||
using System.Net;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.OpenApi.Models;
|
using Newtonsoft.Json;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Web;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
// Early init of NLog to allow startup and exception logging, before host is built
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
var logger = NLog.LogManager.Setup()
|
||||||
builder.Services.AddSwaggerGen(c =>
|
.LoadConfigurationFromAppSettings()
|
||||||
|
.GetCurrentClassLogger();
|
||||||
|
logger.Debug("Init Main...");
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
c.SwaggerDoc("v1", new OpenApiInfo
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Services Settings
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddControllersWithViews();
|
||||||
|
|
||||||
|
// NLog: Setup NLog for Dependency injection
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Host.UseNLog();
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|
||||||
|
builder.Services.Configure<FormOptions>(options =>
|
||||||
{
|
{
|
||||||
|
options.MultipartBodyLengthLimit = 32 * 1024 * 1024;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Json.Net Serializer
|
||||||
|
builder.Services.AddControllersWithViews().AddNewtonsoftJson(options =>
|
||||||
|
{
|
||||||
|
// Configure Newtonsoft.Json options here
|
||||||
|
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
||||||
|
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Swagger
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddOpenApiDocument(options =>
|
||||||
|
{
|
||||||
|
options.PostProcess = document =>
|
||||||
|
{
|
||||||
|
document.Info = new NSwag.OpenApiInfo
|
||||||
|
{
|
||||||
|
Version = "v1",
|
||||||
Title = "FPGA Web Lab API",
|
Title = "FPGA Web Lab API",
|
||||||
Description = "Use FPGA in the cloud",
|
Description = "Use FPGA in the cloud",
|
||||||
Version = "v1"
|
// TermsOfService = "https://example.com/terms",
|
||||||
|
// Contact = new NSwag.OpenApiContact
|
||||||
|
// {
|
||||||
|
// Name = "Example Contact",
|
||||||
|
// Url = "https://example.com/contact"
|
||||||
|
// },
|
||||||
|
// License = new NSwag.OpenApiLicense
|
||||||
|
// {
|
||||||
|
// Name = "Example License",
|
||||||
|
// Url = "https://example.com/license"
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
};
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
var app = builder.Build();
|
// Application Settings
|
||||||
if (app.Environment.IsDevelopment())
|
var app = builder.Build();
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
// app.UseExceptionHandler(new ExceptionHandlerOptions()
|
||||||
|
// {
|
||||||
|
// AllowStatusCode404Response = true,
|
||||||
|
// ExceptionHandlingPath = "/error"
|
||||||
|
// });
|
||||||
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
|
app.UseHsts();
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
app.UseRouting();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
// if (app.Environment.IsDevelopment())
|
||||||
|
// {
|
||||||
|
app.UseOpenApi();
|
||||||
|
app.UseSwaggerUi();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Setup Program
|
||||||
|
MsgBus.Init();
|
||||||
|
|
||||||
|
// Router
|
||||||
|
// API Get
|
||||||
|
app.MapGet("/", () => Results.Redirect("/swagger"));
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run("http://localhost:5000");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
app.UseSwagger();
|
// NLog: catch setup errors
|
||||||
app.UseSwaggerUI(c =>
|
logger.Error(exception, "Stopped program because of exception");
|
||||||
{
|
throw;
|
||||||
c.SwaggerEndpoint("/swagger/v1/swagger.json", "FPAG WebLab API V1");
|
}
|
||||||
});
|
finally
|
||||||
|
{
|
||||||
|
// Close UDP Server
|
||||||
|
logger.Info("Program is Closing now...");
|
||||||
|
|
||||||
|
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
|
||||||
|
NLog.LogManager.Shutdown();
|
||||||
|
|
||||||
|
// Close Program
|
||||||
|
MsgBus.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup UDP Server
|
|
||||||
var udpServer = new UDPServer(33000);
|
|
||||||
udpServer.Start();
|
|
||||||
|
|
||||||
// Router
|
|
||||||
app.MapGet("/", () => "Hello World!");
|
|
||||||
app.MapPut("/api/SendString", Router.API.SendString);
|
|
||||||
app.MapPut("/api/SendAddrPackage", Router.API.SendAddrPackage);
|
|
||||||
app.MapPut("/api/SendDataPackage", Router.API.SendDataPackage);
|
|
||||||
|
|
||||||
app.Run("http://localhost:5000");
|
|
||||||
|
|
||||||
// Close UDP Server
|
|
||||||
Console.WriteLine("UDP Server is Closing now...");
|
|
||||||
udpServer.Stop();
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<Project DefaultTargets="Build">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||||
|
|
||||||
|
<!-- Enable roll-forward to latest patch. This allows one restore operation
|
||||||
|
to apply to all of the self-contained publish operations. -->
|
||||||
|
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="PublishAllRids">
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Transform RuntimeIdentifiers property to item -->
|
||||||
|
<RuntimeIdentifierForPublish Include="$(RuntimeIdentifiers)" />
|
||||||
|
|
||||||
|
<!-- Transform RuntimeIdentifierForPublish items to project items to pass to MSBuild task -->
|
||||||
|
<ProjectToPublish Include="@(RuntimeIdentifierForPublish->'$(MSBuildProjectFullPath)')">
|
||||||
|
<AdditionalProperties>RuntimeIdentifier=%(RuntimeIdentifierForPublish.Identity)</AdditionalProperties>
|
||||||
|
</ProjectToPublish>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<MSBuild Projects="@(ProjectToPublish)"
|
||||||
|
Targets="Publish"
|
||||||
|
BuildInParallel="true"
|
||||||
|
/>
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -2,8 +2,79 @@
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.AspNetCore": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
},
|
||||||
|
"NLog": {
|
||||||
|
"RemoveLoggerFactoryFilter": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"NLog": {
|
||||||
|
"extensions": [
|
||||||
|
{
|
||||||
|
"assembly": "NLog.Web.AspNetCore"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"throwConfigExceptions": true,
|
||||||
|
"targets": {
|
||||||
|
"async": true,
|
||||||
|
"AllFile": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "./log/all-${shortdate}.log"
|
||||||
|
},
|
||||||
|
"WebAllFile": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "./log/web-all-${shortdate}.log"
|
||||||
|
},
|
||||||
|
"EachFile": {
|
||||||
|
"type": "File",
|
||||||
|
"fileName": "./log/${logger}-${shortdate}.log"
|
||||||
|
},
|
||||||
|
"Console": {
|
||||||
|
"type": "ColoredConsole"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"logger": "*",
|
||||||
|
"finalMinLevel": "Trace",
|
||||||
|
"writeTo": "AllFile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "*",
|
||||||
|
"finalMinLevel": "Trace",
|
||||||
|
"writeTo": "WebAllFile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "System.*",
|
||||||
|
"finalMinLevel": "Warn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "Microsoft.*",
|
||||||
|
"finalMinLevel": "Warn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "Microsoft.Hosting.Lifetime*",
|
||||||
|
"finalMinLevel": "Info",
|
||||||
|
"writeTo": "Console"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "server",
|
||||||
|
"finalMinLevel": "Trace",
|
||||||
|
"writeTo": "EachFile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "server.*",
|
||||||
|
"finalMinLevel": "Trace",
|
||||||
|
"writeTo": "EachFile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"logger": "server.*",
|
||||||
|
"finalMinLevel": "Debug",
|
||||||
|
"writeTo": "Console"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
<Import Project="PublishAllRids.targets" />
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<RuntimeIdentifiers>win-x64;linux-x64;</RuntimeIdentifiers>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DotNext" Version="5.19.1" />
|
<PackageReference Include="DotNext" Version="5.19.1" />
|
||||||
|
<PackageReference Include="DotNext.Threading" Version="5.19.1" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.4" />
|
||||||
<PackageReference Include="Microsoft.OpenApi" Version="1.6.23" />
|
<PackageReference Include="Microsoft.OpenApi" Version="1.6.23" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.0.0" />
|
<PackageReference Include="NLog" Version="5.4.0" />
|
||||||
|
<PackageReference Include="NLog.Web.AspNetCore" Version="5.4.0" />
|
||||||
|
<PackageReference Include="NSwag.AspNetCore" Version="14.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -2,16 +2,26 @@ using DotNext;
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
class NumberProcessor
|
/// <summary>
|
||||||
|
/// 数字处理工具
|
||||||
|
/// </summary>
|
||||||
|
public class Number
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 整数转成二进制字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="num">整数</param>
|
||||||
|
/// <param name="length">整数长度</param>
|
||||||
|
/// <param name="isRightHigh">是否高位在右边</param>
|
||||||
|
/// <returns>二进制字节数组</returns>
|
||||||
public static Result<byte[]> NumberToBytes(ulong num, uint length, bool isRightHigh = false)
|
public static Result<byte[]> NumberToBytes(ulong num, uint length, bool isRightHigh = false)
|
||||||
{
|
{
|
||||||
if (length > 8)
|
if (length > 8)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
return new(new ArgumentException(
|
||||||
"Unsigned long number can't over 8 bytes(64 bits).",
|
"Unsigned long number can't over 8 bytes(64 bits).",
|
||||||
nameof(length)
|
nameof(length)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
var arr = new byte[length];
|
var arr = new byte[length];
|
||||||
|
@ -27,44 +37,129 @@ namespace Common
|
||||||
{
|
{
|
||||||
for (var i = 0; i < length; i++)
|
for (var i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
arr[i] = Convert.ToByte((num >> (i << 3)) & (0xFF));
|
arr[i] = Convert.ToByte((num >> ((int)(length - 1 - i) << 3)) & (0xFF));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<ulong> BytesToNumber(byte[] bytes, bool isRightLeft = false)
|
/// <summary>
|
||||||
|
/// 二进制字节数组转成整数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">二进制字节数组</param>
|
||||||
|
/// <param name="isRightHigh">是否高位在右边</param>
|
||||||
|
/// <returns>整数</returns>
|
||||||
|
public static Result<ulong> BytesToNumber(byte[] bytes, bool isRightHigh = false)
|
||||||
{
|
{
|
||||||
if (bytes.Length > 8)
|
if (bytes.Length > 8)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
return new(new ArgumentException(
|
||||||
"Unsigned long number can't over 8 bytes(64 bits).",
|
"Unsigned long number can't over 8 bytes(64 bits).",
|
||||||
nameof(bytes)
|
nameof(bytes)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong num = 0;
|
ulong num = 0;
|
||||||
int len = bytes.Length;
|
int len = bytes.Length;
|
||||||
|
|
||||||
if (isRightLeft)
|
if (isRightHigh)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < len; i++)
|
for (var i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
num += Convert.ToUInt64(bytes[len - 1 - i] << (i << 3));
|
num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (var i = 0; i < len; i++)
|
for (var i = 0; i < len; i++)
|
||||||
{
|
{
|
||||||
num += Convert.ToUInt64(bytes[i] << (i << 3));
|
num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return num;
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<byte[]> StringToBytes(string str, int numBase = 16)
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比特合并成二进制字节
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bits1">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits1Len">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits2">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits2Len">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public static Result<byte[]> MultiBitsToBytes(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len)
|
||||||
|
{
|
||||||
|
return NumberToBytes(MultiBitsToNumber(bits1, bits1Len, bits2, bits2Len).Value,
|
||||||
|
(bits1Len + bits2Len) % 8 != 0 ? (bits1Len + bits2Len) / 8 + 1 : (bits1Len + bits2Len) / 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比特合并成整型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bits1">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits1Len">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits2">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits2Len">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public static Result<ulong> MultiBitsToNumber(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len)
|
||||||
|
{
|
||||||
|
if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits"));
|
||||||
|
|
||||||
|
ulong num = (bits1 << Convert.ToInt32(bits2Len)) | bits2;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比特合并成整型
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bits1">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits1Len">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits2">[TODO:parameter]</param>
|
||||||
|
/// <param name="bits2Len">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public static Result<uint> MultiBitsToNumber(uint bits1, uint bits1Len, uint bits2, uint bits2Len)
|
||||||
|
{
|
||||||
|
if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits"));
|
||||||
|
|
||||||
|
uint num = (bits1 << Convert.ToInt32(bits2Len)) | bits2;
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比特位检查
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="srcBits">[TODO:parameter]</param>
|
||||||
|
/// <param name="dstBits">[TODO:parameter]</param>
|
||||||
|
/// <param name="mask">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public static bool BitsCheck(ulong srcBits, ulong dstBits, ulong mask = 0xFFFF_FFFF_FFFF_FFFF)
|
||||||
|
{
|
||||||
|
return (srcBits & mask) == dstBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 比特位检查
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="srcBits">[TODO:parameter]</param>
|
||||||
|
/// <param name="dstBits">[TODO:parameter]</param>
|
||||||
|
/// <param name="mask">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public static bool BitsCheck(uint srcBits, uint dstBits, uint mask = 0xFFFF_FFFF)
|
||||||
|
{
|
||||||
|
return (srcBits & mask) == dstBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 字符串转二进制字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="str">[TODO:parameter]</param>
|
||||||
|
/// <param name="numBase">[TODO:parameter]</param>
|
||||||
|
/// <returns>[TODO:return]</returns>
|
||||||
|
public static byte[] StringToBytes(string str, int numBase = 16)
|
||||||
{
|
{
|
||||||
var len = str.Length;
|
var len = str.Length;
|
||||||
var bytesLen = len / 2;
|
var bytesLen = len / 2;
|
||||||
|
|
|
@ -0,0 +1,296 @@
|
||||||
|
using System.Net;
|
||||||
|
using Common;
|
||||||
|
using DotNext;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using WebProtocol;
|
||||||
|
|
||||||
|
namespace server.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UDP API
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class UDPController : ControllerBase
|
||||||
|
{
|
||||||
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private const string LOCALHOST = "127.0.0.1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 页面
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public string Index()
|
||||||
|
{
|
||||||
|
return "This is UDP Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">IPV4 或者 IPV6 地址</param>
|
||||||
|
/// <param name="port">设备端口号</param>
|
||||||
|
/// <param name="text">发送的文本</param>
|
||||||
|
/// <response code="200">发送成功</response>
|
||||||
|
/// <response code="500">发送失败</response>
|
||||||
|
[HttpPost("SendString")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> SendString(string address = LOCALHOST, int port = 1234, string text = "Hello Server!")
|
||||||
|
{
|
||||||
|
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
var ret = await UDPClientPool.SendStringAsync(endPoint, [text]);
|
||||||
|
|
||||||
|
if (ret) { return TypedResults.Ok(); }
|
||||||
|
else { return TypedResults.InternalServerError(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送二进制数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address" example="127.0.0.1">IPV4 或者 IPV6 地址</param>
|
||||||
|
/// <param name="port" example="1234">设备端口号</param>
|
||||||
|
/// <param name="bytes" example="FFFFAAAA">16进制文本</param>
|
||||||
|
[HttpPost("SendBytes")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> SendBytes(string address, int port, string bytes)
|
||||||
|
{
|
||||||
|
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
var ret = await UDPClientPool.SendBytesAsync(endPoint, Number.StringToBytes(bytes));
|
||||||
|
|
||||||
|
if (ret) { return TypedResults.Ok(); }
|
||||||
|
else { return TypedResults.InternalServerError(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">IP地址</param>
|
||||||
|
/// <param name="port">UDP 端口号</param>
|
||||||
|
/// <param name="opts">地址包选项</param>
|
||||||
|
[HttpPost("SendAddrPackage")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> SendAddrPackage(
|
||||||
|
string address,
|
||||||
|
int port,
|
||||||
|
[FromBody] SendAddrPackOptions opts)
|
||||||
|
{
|
||||||
|
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
var ret = await UDPClientPool.SendAddrPackAsync(endPoint, new WebProtocol.SendAddrPackage(opts));
|
||||||
|
|
||||||
|
if (ret) { return TypedResults.Ok(); }
|
||||||
|
else { return TypedResults.InternalServerError(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送数据包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">IP地址</param>
|
||||||
|
/// <param name="port">UDP 端口号</param>
|
||||||
|
/// <param name="data">16进制数据</param>
|
||||||
|
[HttpPost("SendDataPackage")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> SendDataPackage(string address, int port, string data)
|
||||||
|
{
|
||||||
|
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
var ret = await UDPClientPool.SendDataPackAsync(endPoint,
|
||||||
|
new WebProtocol.SendDataPackage(Number.StringToBytes(data)));
|
||||||
|
|
||||||
|
if (ret) { return TypedResults.Ok(); }
|
||||||
|
else { return TypedResults.InternalServerError(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定IP地址接受的数据列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">IP地址</param>
|
||||||
|
[HttpGet("GetRecvDataArray")]
|
||||||
|
[ProducesResponseType(typeof(List<UDPData>), StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> GetRecvDataArray(string address)
|
||||||
|
{
|
||||||
|
var ret = await MsgBus.UDPServer.GetDataArrayAsync(address);
|
||||||
|
|
||||||
|
if (ret.HasValue)
|
||||||
|
{
|
||||||
|
var dataJson = JsonConvert.SerializeObject(ret.Value);
|
||||||
|
logger.Debug($"Get Receive Successfully: {dataJson}");
|
||||||
|
|
||||||
|
return TypedResults.Ok(ret.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Debug("Get Receive Failed");
|
||||||
|
return TypedResults.InternalServerError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag API
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class JtagController : ControllerBase
|
||||||
|
{
|
||||||
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 页面
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet]
|
||||||
|
public string Index()
|
||||||
|
{
|
||||||
|
return "This is Jtag Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行一个Jtag命令
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"> 设备地址 </param>
|
||||||
|
/// <param name="port"> 设备端口 </param>
|
||||||
|
/// <param name="hexDevAddr"> 16进制设备目的地址(Jtag) </param>
|
||||||
|
/// <param name="hexCmd"> 16进制命令 </param>
|
||||||
|
[HttpPost("RunCommand")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> RunCommand(string address, int port, string hexDevAddr, string hexCmd)
|
||||||
|
{
|
||||||
|
var jtagCtrl = new JtagClient.Jtag(address, port);
|
||||||
|
var ret = await jtagCtrl.WriteFIFO(Convert.ToUInt32(hexDevAddr, 16), Convert.ToUInt32(hexCmd, 16));
|
||||||
|
|
||||||
|
if (ret.IsSuccessful) { return TypedResults.Ok(ret.Value); }
|
||||||
|
else { return TypedResults.InternalServerError(ret.Error); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取Jtag ID Code
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"> 设备地址 </param>
|
||||||
|
/// <param name="port"> 设备端口 </param>
|
||||||
|
[HttpGet("GetDeviceIDCode")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> GetDeviceIDCode(string address, int port)
|
||||||
|
{
|
||||||
|
var jtagCtrl = new JtagClient.Jtag(address, port);
|
||||||
|
var ret = await jtagCtrl.ReadIDCode();
|
||||||
|
|
||||||
|
if (ret.IsSuccessful)
|
||||||
|
{
|
||||||
|
logger.Info($"Get device {address} ID code: 0x{ret.Value:X4}");
|
||||||
|
return TypedResults.Ok(ret.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Error(ret.Error);
|
||||||
|
return TypedResults.InternalServerError(ret.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上传比特流文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"> 设备地址 </param>
|
||||||
|
/// <param name="file">比特流文件</param>
|
||||||
|
[HttpPost("UploadBitstream")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
public async ValueTask<IResult> UploadBitstream(string address, IFormFile file)
|
||||||
|
{
|
||||||
|
if (file == null || file.Length == 0)
|
||||||
|
return TypedResults.BadRequest("未选择文件");
|
||||||
|
|
||||||
|
// 生成安全的文件名(避免路径遍历攻击)
|
||||||
|
var fileName = Path.GetRandomFileName();
|
||||||
|
var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"bitstream/{address}");
|
||||||
|
|
||||||
|
// 如果存在文件,则删除原文件再上传
|
||||||
|
if (Directory.Exists(uploadsFolder))
|
||||||
|
{
|
||||||
|
Directory.Delete(uploadsFolder, true);
|
||||||
|
}
|
||||||
|
Directory.CreateDirectory(uploadsFolder);
|
||||||
|
|
||||||
|
var filePath = Path.Combine(uploadsFolder, fileName);
|
||||||
|
|
||||||
|
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||||
|
{
|
||||||
|
await file.CopyToAsync(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info($"Device {address} Upload Bitstream Successfully");
|
||||||
|
return TypedResults.Ok("Bitstream Upload Successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过Jtag下载比特流文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"> 设备地址 </param>
|
||||||
|
/// <param name="port"> 设备端口 </param>
|
||||||
|
[HttpPost("DownloadBitstream")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
|
public async ValueTask<IResult> DownloadBitstream(string address, int port)
|
||||||
|
{
|
||||||
|
// 检查文件
|
||||||
|
var fileDir = Path.Combine(Environment.CurrentDirectory, $"bitstream/{address}");
|
||||||
|
if (!Directory.Exists(fileDir))
|
||||||
|
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 读取文件
|
||||||
|
var filePath = Directory.GetFiles(fileDir)[0];
|
||||||
|
var fileStream = System.IO.File.Open(filePath, System.IO.FileMode.Open);
|
||||||
|
if (fileStream is null || fileStream.Length <= 0)
|
||||||
|
return TypedResults.BadRequest("Wrong bitstream, Please upload it again");
|
||||||
|
|
||||||
|
// 定义缓冲区大小: 32KB
|
||||||
|
byte[] buffer = new byte[32 * 1024];
|
||||||
|
long totalBytesRead = 0;
|
||||||
|
|
||||||
|
// 使用异步流读取文件
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||||
|
{
|
||||||
|
await memoryStream.WriteAsync(buffer, 0, bytesRead);
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将所有数据转换为字节数组(注意:如果文件非常大,可能不适合完全加载到内存)
|
||||||
|
var fileBytes = memoryStream.ToArray();
|
||||||
|
|
||||||
|
// 下载比特流
|
||||||
|
var jtagCtrl = new JtagClient.Jtag(address, port);
|
||||||
|
var ret = await jtagCtrl.DownloadBitstream(fileBytes);
|
||||||
|
|
||||||
|
if (ret.IsSuccessful)
|
||||||
|
{
|
||||||
|
logger.Info($"Device {address} dowload bitstream successfully");
|
||||||
|
return TypedResults.Ok(ret.Value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Error(ret.Error);
|
||||||
|
return TypedResults.InternalServerError(ret.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
return TypedResults.InternalServerError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,616 @@
|
||||||
|
using System.Net;
|
||||||
|
using DotNext;
|
||||||
|
using WebProtocol;
|
||||||
|
|
||||||
|
namespace JtagClient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Global Constant Jtag Address
|
||||||
|
/// </summary>
|
||||||
|
public static class JtagAddr
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag State Reg
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 STATE = 0x10_00_00_00;
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag Read Reg
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 READ_DATA = 0x10_00_00_01;
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag Write Reg
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_DATA = 0x10_00_00_02;
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag Write Command
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_CMD = 0x10_00_00_03;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag 状态寄存器的掩码及其确认函数
|
||||||
|
/// </summary>
|
||||||
|
public static class JtagState
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
[0] 移位数据读fifo清空,1为有效,清空后自动置0
|
||||||
|
[1] 移位数据fifo读入口-空标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
[2] 移位数据fifo读入口-满标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
[7:3] 保留
|
||||||
|
|
||||||
|
[8] 移位数据写fifo清空,1为有效,清空后自动置0
|
||||||
|
[9] 移位数据fifo写入口-空标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
[10] 移位数据fifo写入口-满标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
[15:11]保留
|
||||||
|
|
||||||
|
[16] 移位命令写fifo清空,1为有效,清空后自动置0
|
||||||
|
[17] 移位命令fifo写入口-空标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
[18] 移位命令fifo写入口-满标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
[23:19]保留
|
||||||
|
|
||||||
|
[24] CMD执行完毕标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据读fifo清空,1为有效,清空后自动置0,实际一直为零
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 READ_DATA_FIFO_CLEAR = 0b0001 << 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据fifo读入口-空标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 READ_DATA_FIFO_EMPTY = 0b0010 << 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据fifo读入口-满标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 READ_DATA_FIFO_AFULL = 0b0100 << 0;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据读fifo
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 READ_DATA_FIFO = 0b0111 << 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据写fifo清空,1为有效,清空后自动置0
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_DATA_FIFO_CLEAR = 0b0001 << 8;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据fifo写入口-空标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_DATA_FIFO_EMPTY = 0b0010 << 8;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据fifo写入口-满标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_DATA_FIFO_AFULL = 0b0100 << 8;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位数据写fifo
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_DATA_FIFO = 0b0111 << 8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移位命令写fifo清空,1为有效,清空后自动置0
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_CMD_FIFO_CLEAR = 0b0001 << 16;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位命令fifo写入口-空标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_CMD_FIFO_EMPTY = 0b0010 << 16;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位命令fifo写入口-满标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_CMD_FIFO_AFULL = 0b0100 << 16;
|
||||||
|
/// <summary>
|
||||||
|
/// 移位命令写fifo
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 WRITE_CMD_FIFO = 0b0111 << 16;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CMD执行完毕标识,只读,对JTAG_STATE_REG的写不改变其值
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_EXEC_FINISH = 0b0001 << 24;
|
||||||
|
/// <summary>
|
||||||
|
/// 全部FIFO
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 ALL_FIFO = READ_DATA_FIFO | WRITE_DATA_FIFO | WRITE_CMD_FIFO;
|
||||||
|
/// <summary>
|
||||||
|
/// 全部寄存器
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 ALL_REG = READ_DATA_FIFO | WRITE_DATA_FIFO | WRITE_CMD_FIFO | CMD_EXEC_FINISH;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Command bits of Jtag
|
||||||
|
/// </summary>
|
||||||
|
public static class JtagCmd
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length of JTAG_DR_XXXX
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 LEN_JTAG_DR = 10;
|
||||||
|
/// <summary>
|
||||||
|
/// 旁路指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_BYPASS = 0b1111111111;
|
||||||
|
/// <summary>
|
||||||
|
/// 采样指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_SAMPLE = 0b1010000000;
|
||||||
|
/// <summary>
|
||||||
|
/// 预装指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_PRELOAD = 0b1010000000;
|
||||||
|
/// <summary>
|
||||||
|
/// 外测试指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_EXTEST = 0b1010000001;
|
||||||
|
/// <summary>
|
||||||
|
/// 内测试指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_INTEST = 0b1010000010;
|
||||||
|
/// <summary>
|
||||||
|
/// 标识指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_IDCODE = 0b1010000011;
|
||||||
|
/// <summary>
|
||||||
|
/// 高阻指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_HIGHZ = 0b1010000101;
|
||||||
|
/// <summary>
|
||||||
|
/// 复位指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_JRST = 0b1010001010;
|
||||||
|
/// <summary>
|
||||||
|
/// 配置指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_CFGI = 0b1010001011;
|
||||||
|
/// <summary>
|
||||||
|
/// 回读指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_CFGO = 0b1010001100;
|
||||||
|
/// <summary>
|
||||||
|
/// 唤醒指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_JWAKEUP = 0b1010001101;
|
||||||
|
/// <summary>
|
||||||
|
/// 读UID指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_READ_UID = 0b0101001100;
|
||||||
|
/// <summary>
|
||||||
|
/// 读状态寄存器指令
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 JTAG_DR_RDSR = 0b0101011001;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The length of CMD_JTAG_XXXX_XXXX
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 LEN_CMD_JTAG = 4;
|
||||||
|
/// <summary>
|
||||||
|
/// 设定JTAG默认状态为TEST_LOGIC_RESET态 (JTAG复位)
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_JTAG_CLOSE_TEST = 0b0000;
|
||||||
|
/// <summary>
|
||||||
|
/// 设定JTAG默认状态为RUN_TEST_IDLE态 (JTAG空闲)
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_JTAG_RUN_TEST = 0b0001;
|
||||||
|
/// <summary>
|
||||||
|
/// JTAG进入SHIFTIR循环cycle_num次后回到设定的默认状态
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_JTAG_LOAD_IR = 0b0010;
|
||||||
|
/// <summary>
|
||||||
|
/// JTAG进入SHIFTDR循环cycle_num次后回到设定的默认状态,同时输入shift_in
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_JTAG_LOAD_DR_CAREI = 0b0011;
|
||||||
|
/// <summary>
|
||||||
|
/// JTAG进入SHIFTDR循环cycle_num次后回到设定的默认状态,同时输出shift_out
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_JTAG_LOAD_DR_CAREO = 0b0100;
|
||||||
|
/// <summary>
|
||||||
|
/// JTAG进入RUN_TEST_IDLE态循环cycle_num次
|
||||||
|
/// </summary>
|
||||||
|
public const UInt32 CMD_JTAG_IDLE_DELAY = 0b0101;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag控制器
|
||||||
|
/// </summary>
|
||||||
|
public class Jtag
|
||||||
|
{
|
||||||
|
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
readonly int timeout;
|
||||||
|
|
||||||
|
readonly int port;
|
||||||
|
readonly string address;
|
||||||
|
private IPEndPoint ep;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Jtag构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">目标IP地址</param>
|
||||||
|
/// <param name="port">目标UDP端口</param>
|
||||||
|
/// <param name="timeout">超时时间</param>
|
||||||
|
public Jtag(string address, int port, int timeout = 2000)
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<uint>> ReadFIFO(uint devAddr)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
var opts = new SendAddrPackOptions();
|
||||||
|
|
||||||
|
opts.BurstType = BurstType.FixedBurst;
|
||||||
|
opts.BurstLength = 0;
|
||||||
|
opts.CommandID = 0;
|
||||||
|
opts.Address = devAddr;
|
||||||
|
|
||||||
|
// Read Jtag State Register
|
||||||
|
opts.IsWrite = false;
|
||||||
|
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||||
|
if (!ret) return new(new Exception("Send Address Package Failed!"));
|
||||||
|
|
||||||
|
// Wait for Read Ack
|
||||||
|
if (!MsgBus.IsRunning)
|
||||||
|
return new(new Exception("Message Bus not Working!"));
|
||||||
|
|
||||||
|
var retPack = await MsgBus.UDPServer.WaitForDataAsync(address, port);
|
||||||
|
if (!retPack.IsSuccessful || !retPack.Value.IsSuccessful)
|
||||||
|
return new(new Exception("Send address package failed"));
|
||||||
|
|
||||||
|
var retPackOpts = retPack.Value.Options;
|
||||||
|
if (retPackOpts.Data is null)
|
||||||
|
return new(new Exception($"Data is Null, package: {retPackOpts.ToString()}"));
|
||||||
|
|
||||||
|
var retPackLen = retPackOpts.Data.Length;
|
||||||
|
if (retPackLen != 4)
|
||||||
|
return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes"));
|
||||||
|
|
||||||
|
return Convert.ToUInt32(Common.Number.BytesToNumber(retPackOpts.Data).Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<RecvDataPackage>> WriteFIFO(UInt32 devAddr, UInt32 data)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
var opts = new SendAddrPackOptions();
|
||||||
|
|
||||||
|
|
||||||
|
opts.BurstType = BurstType.FixedBurst;
|
||||||
|
opts.BurstLength = 0;
|
||||||
|
opts.CommandID = 0;
|
||||||
|
opts.Address = devAddr;
|
||||||
|
|
||||||
|
// Write Jtag State Register
|
||||||
|
opts.IsWrite = true;
|
||||||
|
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||||
|
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
||||||
|
// Send Data Package
|
||||||
|
ret = await UDPClientPool.SendDataPackAsync(ep,
|
||||||
|
new SendDataPackage(Common.Number.NumberToBytes(data, 4).Value));
|
||||||
|
if (!ret) return new(new Exception("Send data package failed!"));
|
||||||
|
|
||||||
|
// Check Msg Bus
|
||||||
|
if (!MsgBus.IsRunning)
|
||||||
|
return new(new Exception("Message bus not working!"));
|
||||||
|
// Wait for Write Ack
|
||||||
|
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(address, port);
|
||||||
|
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
||||||
|
else if (!udpWriteAck.Value.IsSuccessful)
|
||||||
|
return new(new Exception("Send address package failed"));
|
||||||
|
|
||||||
|
// Read Jtag State Register
|
||||||
|
opts.IsWrite = false;
|
||||||
|
opts.Address = JtagAddr.STATE;
|
||||||
|
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||||
|
if (!ret) return new(new Exception("Send 2rd Address Package Failed!"));
|
||||||
|
// Wait for Read Data
|
||||||
|
var udpDataResp = await MsgBus.UDPServer.WaitForDataAsync(address, port);
|
||||||
|
if (!udpDataResp.IsSuccessful) return new(udpDataResp.Error);
|
||||||
|
else if (!udpDataResp.Value.IsSuccessful)
|
||||||
|
return new(new Exception("Send address package failed"));
|
||||||
|
|
||||||
|
return udpDataResp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<RecvDataPackage>> WriteFIFO(UInt32 devAddr, byte[] dataArray)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
var opts = new SendAddrPackOptions();
|
||||||
|
|
||||||
|
|
||||||
|
opts.BurstType = BurstType.FixedBurst;
|
||||||
|
opts.CommandID = 0;
|
||||||
|
opts.Address = devAddr;
|
||||||
|
|
||||||
|
// Check Msg Bus
|
||||||
|
if (!MsgBus.IsRunning)
|
||||||
|
return new(new Exception("Message bus not working!"));
|
||||||
|
|
||||||
|
var writeTimes = dataArray.Length / (256 * (32 / 8)) + 1;
|
||||||
|
for (var i = 0; i < writeTimes; i++)
|
||||||
|
{
|
||||||
|
var isLastData = i == writeTimes - 1;
|
||||||
|
// Write Jtag State Register
|
||||||
|
opts.IsWrite = true;
|
||||||
|
opts.BurstLength = isLastData ? (byte)(dataArray.Length - i * (256 * (32 / 8)) - 1) : (byte)255;
|
||||||
|
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||||
|
if (!ret) return new(new Exception("Send 1st address package failed!"));
|
||||||
|
// Send Data Package
|
||||||
|
ret = await UDPClientPool.SendDataPackAsync(ep,
|
||||||
|
new SendDataPackage(
|
||||||
|
isLastData ?
|
||||||
|
dataArray[(i * (256 * (32 / 8)))..] :
|
||||||
|
dataArray[(i * (256 * (32 / 8)))..((i + 1) * (256 * (32 / 8)) - 1)]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (!ret) return new(new Exception("Send data package failed!"));
|
||||||
|
|
||||||
|
// Wait for Write Ack
|
||||||
|
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(address, port);
|
||||||
|
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
|
||||||
|
else if (!udpWriteAck.Value.IsSuccessful)
|
||||||
|
return new(new Exception("Send address package failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Jtag State Register
|
||||||
|
opts.IsWrite = false;
|
||||||
|
opts.BurstLength = 0;
|
||||||
|
opts.Address = JtagAddr.STATE;
|
||||||
|
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
|
||||||
|
if (!ret) return new(new Exception("Send 2rd Address Package Failed!"));
|
||||||
|
// Wait for Read Data
|
||||||
|
var udpDataResp = await MsgBus.UDPServer.WaitForDataAsync(address, port);
|
||||||
|
if (!udpDataResp.IsSuccessful) return new(udpDataResp.Error);
|
||||||
|
else if (!udpDataResp.Value.IsSuccessful)
|
||||||
|
return new(new Exception("Send address package failed"));
|
||||||
|
|
||||||
|
return udpDataResp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> WriteFIFO
|
||||||
|
(UInt32 devAddr, UInt32 data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
var retPack = await WriteFIFO(devAddr, data);
|
||||||
|
if (!retPack.IsSuccessful) return new(retPack.Error);
|
||||||
|
|
||||||
|
if (retPack.Value.Options.Data is null)
|
||||||
|
return new(new Exception($"Data is Null, package: {retPack.Value.Options.ToString()}"));
|
||||||
|
|
||||||
|
var retPackLen = retPack.Value.Options.Data.Length;
|
||||||
|
if (retPackLen != 4)
|
||||||
|
return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes"));
|
||||||
|
|
||||||
|
if (Common.Number.BitsCheck(
|
||||||
|
Common.Number.BytesToNumber(retPack.Value.Options.Data).Value, result, resultMask))
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> WriteFIFO
|
||||||
|
(UInt32 devAddr, byte[] data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF)
|
||||||
|
{
|
||||||
|
var ret = false;
|
||||||
|
var retPack = await WriteFIFO(devAddr, data);
|
||||||
|
|
||||||
|
if (retPack.Value.Options.Data is null)
|
||||||
|
return new(new Exception($"Data is Null, package: {retPack.Value.Options.ToString()}"));
|
||||||
|
|
||||||
|
var retPackLen = retPack.Value.Options.Data.Length;
|
||||||
|
if (retPackLen != 4)
|
||||||
|
return new(new Exception($"RecvDataPackage BodyData Length not Equal to 4: Total {retPackLen} bytes"));
|
||||||
|
|
||||||
|
if (Common.Number.BitsCheck(
|
||||||
|
Common.Number.BytesToNumber(retPack.Value.Options.Data).Value, result, resultMask))
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> ClearAllRegisters()
|
||||||
|
{
|
||||||
|
return await WriteFIFO(JtagAddr.STATE, 0xFF_FF_FF_FF, 0x01_02_02_02, JtagState.ALL_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> ClearWriteDataReg()
|
||||||
|
{
|
||||||
|
return await WriteFIFO(JtagAddr.STATE, 0x00_00_11_00, 0x01_00_02_00, JtagState.WRITE_DATA_FIFO | JtagState.CMD_EXEC_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> CloseTest()
|
||||||
|
{
|
||||||
|
return await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_CMD,
|
||||||
|
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_CLOSE_TEST, JtagCmd.LEN_CMD_JTAG, 0, 28).Value,
|
||||||
|
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> RunTest()
|
||||||
|
{
|
||||||
|
return await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_CMD,
|
||||||
|
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_RUN_TEST, JtagCmd.LEN_CMD_JTAG, 0, 28).Value,
|
||||||
|
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> IdleDelay(UInt32 milliseconds)
|
||||||
|
{
|
||||||
|
if (milliseconds > Math.Pow(2, 28)) return new(new Exception("Timespan is over 2^28 milliseconds"));
|
||||||
|
|
||||||
|
return await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_CMD,
|
||||||
|
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_IDLE_DELAY, JtagCmd.LEN_CMD_JTAG, milliseconds, 28).Value,
|
||||||
|
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> ExecRDCmd(uint command)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
var ret = await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_DATA,
|
||||||
|
Common.Number.MultiBitsToNumber(0, 22, command, JtagCmd.LEN_JTAG_DR).Value, 0, 0);
|
||||||
|
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Write JTAG_DR_IDCODE Failed"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var ret = await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_CMD,
|
||||||
|
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_IR, JtagCmd.LEN_CMD_JTAG, 10, 28).Value,
|
||||||
|
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_IR Failed"));
|
||||||
|
}
|
||||||
|
return await ClearWriteDataReg();
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<bool>> LoadDRCareInput(byte[] bytesArray)
|
||||||
|
{
|
||||||
|
var bytesLen = ((uint)(bytesArray.Length * 8));
|
||||||
|
if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)"));
|
||||||
|
|
||||||
|
{
|
||||||
|
var ret = await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_CMD,
|
||||||
|
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_DR_CAREI, JtagCmd.LEN_CMD_JTAG, bytesLen, 28).Value,
|
||||||
|
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Write CMD_JTAG_LOAD_DR_CAREI Failed"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var ret = await WriteFIFO(JtagAddr.WRITE_DATA, bytesArray, 0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Write Data Failed"));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<Result<UInt32>> LoadDRCareOutput(UInt32 bytesLen)
|
||||||
|
{
|
||||||
|
if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)"));
|
||||||
|
|
||||||
|
var ret = await WriteFIFO(
|
||||||
|
JtagAddr.WRITE_CMD,
|
||||||
|
Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_DR_CAREO, JtagCmd.LEN_CMD_JTAG, 8 * bytesLen, 28).Value,
|
||||||
|
0x01_00_00_00, JtagState.CMD_EXEC_FINISH);
|
||||||
|
|
||||||
|
if (ret.Value)
|
||||||
|
return await ReadFIFO(JtagAddr.READ_DATA);
|
||||||
|
else
|
||||||
|
return new(new Exception("LoadDRCareo Failed!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<uint>> ReadIDCode()
|
||||||
|
{
|
||||||
|
// Clear Data
|
||||||
|
await MsgBus.UDPServer.ClearUDPData(this.address);
|
||||||
|
|
||||||
|
logger.Trace($"Clear up udp server {this.address} receive data");
|
||||||
|
|
||||||
|
Result<bool> ret;
|
||||||
|
|
||||||
|
ret = await ClearAllRegisters();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Clear All Registers Failed"));
|
||||||
|
|
||||||
|
ret = await RunTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
|
|
||||||
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_IDCODE);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_IDCODE Failed"));
|
||||||
|
|
||||||
|
ret = await ClearWriteDataReg();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Clear Write Registers Failed"));
|
||||||
|
|
||||||
|
var retData = await LoadDRCareOutput(4);
|
||||||
|
if (!retData.IsSuccessful)
|
||||||
|
{
|
||||||
|
return new(new Exception("Get ID Code Failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retData.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream)
|
||||||
|
{
|
||||||
|
// Clear Data
|
||||||
|
await MsgBus.UDPServer.ClearUDPData(this.address);
|
||||||
|
|
||||||
|
logger.Trace($"Clear up udp server {this.address} receive data");
|
||||||
|
|
||||||
|
Result<bool> ret;
|
||||||
|
|
||||||
|
ret = await CloseTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||||
|
|
||||||
|
ret = await RunTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
|
|
||||||
|
logger.Trace("Jtag initialize");
|
||||||
|
|
||||||
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JRST);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed"));
|
||||||
|
|
||||||
|
ret = await RunTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
|
|
||||||
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_CFGI);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_CFGI Failed"));
|
||||||
|
|
||||||
|
logger.Trace("Jtag ready to write bitstream");
|
||||||
|
|
||||||
|
ret = await IdleDelay(75000);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
||||||
|
|
||||||
|
ret = await LoadDRCareInput(bitstream);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Load Data Failed"));
|
||||||
|
|
||||||
|
logger.Trace("Jtag write bitstream");
|
||||||
|
|
||||||
|
ret = await CloseTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||||
|
|
||||||
|
ret = await RunTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Run Test Failed"));
|
||||||
|
|
||||||
|
ret = await ExecRDCmd(JtagCmd.JTAG_DR_JWAKEUP);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JWAKEUP Failed"));
|
||||||
|
|
||||||
|
logger.Trace("Jtag reset device");
|
||||||
|
|
||||||
|
ret = await IdleDelay(1000);
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag IDLE Delay Failed"));
|
||||||
|
|
||||||
|
ret = await CloseTest();
|
||||||
|
if (!ret.IsSuccessful) return new(ret.Error);
|
||||||
|
else if (!ret.Value) return new(new Exception("Jtag Close Test Failed"));
|
||||||
|
logger.Trace("Jtag download bitstream successfully");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/// <summary>
|
||||||
|
/// 多线程通信总线
|
||||||
|
/// </summary>
|
||||||
|
public static class MsgBus
|
||||||
|
{
|
||||||
|
private static readonly UDPServer udpServer = new UDPServer(1234);
|
||||||
|
/// <summary>
|
||||||
|
/// 获取UDP服务器
|
||||||
|
/// </summary>
|
||||||
|
public static UDPServer UDPServer { get { return udpServer; } }
|
||||||
|
|
||||||
|
private static bool isRunning = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 获取通信总线运行状态
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsRunning { get { return isRunning; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通信总线初始化
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>无</returns>
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
udpServer.Start();
|
||||||
|
isRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 关闭通信总线
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>无</returns>
|
||||||
|
public static void Exit()
|
||||||
|
{
|
||||||
|
udpServer.Stop();
|
||||||
|
isRunning = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,41 +0,0 @@
|
||||||
using System.Net;
|
|
||||||
using Common;
|
|
||||||
|
|
||||||
namespace Router
|
|
||||||
{
|
|
||||||
class API
|
|
||||||
{
|
|
||||||
public static void SendString(string address, int port, string text)
|
|
||||||
{
|
|
||||||
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
|
||||||
UDPClientPool.AsyncSendString(endPoint, [text]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SendAddrPackage(
|
|
||||||
string address,
|
|
||||||
int port,
|
|
||||||
WebProtocol.BurstType burstType,
|
|
||||||
byte commandID,
|
|
||||||
bool isWrite,
|
|
||||||
byte burstLength,
|
|
||||||
UInt32 devAddress)
|
|
||||||
{
|
|
||||||
WebProtocol.SendAddrPackOptions opts;
|
|
||||||
opts.burstType = burstType;
|
|
||||||
opts.commandID = commandID;
|
|
||||||
opts.isWrite = isWrite;
|
|
||||||
opts.burstLength = burstLength;
|
|
||||||
opts.address = devAddress;
|
|
||||||
|
|
||||||
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
|
||||||
UDPClientPool.SendAddrPack(endPoint, new WebProtocol.SendAddrPackage(opts));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SendDataPackage(string address, int port, string data)
|
|
||||||
{
|
|
||||||
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
|
|
||||||
UDPClientPool.SendDataPack(endPoint,
|
|
||||||
new WebProtocol.SendDataPackage(NumberProcessor.StringToBytes(data).Value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,78 +2,163 @@ using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
class UDPClientPool
|
/// <summary>
|
||||||
|
/// UDP客户端发送池
|
||||||
|
/// </summary>
|
||||||
|
public class UDPClientPool
|
||||||
{
|
{
|
||||||
private static IPAddress localhost = IPAddress.Parse("127.0.0.1");
|
private static IPAddress localhost = IPAddress.Parse("127.0.0.1");
|
||||||
|
|
||||||
public static void SendString(IPEndPoint endPoint, string[] stringArray)
|
/// <summary>
|
||||||
|
/// 发送字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="stringArray">字符串数组</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool SendString(IPEndPoint endPoint, string[] stringArray)
|
||||||
{
|
{
|
||||||
|
var isSuccessful = true;
|
||||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
|
|
||||||
byte[] sendbuf = Encoding.ASCII.GetBytes(stringArray[0]);
|
foreach (var str in stringArray)
|
||||||
|
|
||||||
socket.SendTo(sendbuf, endPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async static void AsyncSendString(IPEndPoint endPoint, string[] stringArray)
|
|
||||||
{
|
{
|
||||||
await Task.Run(() => { SendString(endPoint, stringArray); });
|
byte[] sendbuf = Encoding.ASCII.GetBytes(str);
|
||||||
|
var sendLen = socket.SendTo(sendbuf, endPoint);
|
||||||
|
if (str.Length != sendLen) isSuccessful = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendBytes(IPEndPoint endPoint, byte[] buf)
|
socket.Close();
|
||||||
|
|
||||||
|
if (isSuccessful) { return true; }
|
||||||
|
else { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步发送字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="stringArray">字符串数组</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public async static ValueTask<bool> SendStringAsync(IPEndPoint endPoint, string[] stringArray)
|
||||||
|
{
|
||||||
|
return await Task.Run(() => { return SendString(endPoint, stringArray); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送二进制字节
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="buf">二进制字节</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool SendBytes(IPEndPoint endPoint, byte[] buf)
|
||||||
{
|
{
|
||||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
socket.SendTo(buf, endPoint);
|
var sendLen = socket.SendTo(buf, endPoint);
|
||||||
|
socket.Close();
|
||||||
|
|
||||||
|
if (sendLen == buf.Length) { return true; }
|
||||||
|
else { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public async static void AsyncSendBytes(IPEndPoint endPoint, byte[] buf)
|
/// <summary>
|
||||||
|
/// 异步发送二进制字节
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="buf">二进制字节</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public async static ValueTask<bool> SendBytesAsync(IPEndPoint endPoint, byte[] buf)
|
||||||
{
|
{
|
||||||
await Task.Run(() => { SendBytes(endPoint, buf); });
|
return await Task.Run(() => { return SendBytes(endPoint, buf); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendAddrPack(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
|
/// <summary>
|
||||||
|
/// 发送地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="pkg">地址包</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool SendAddrPack(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
|
||||||
{
|
{
|
||||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
socket.SendTo(pkg.ToBytes(), endPoint);
|
var sendBytes = pkg.ToBytes();
|
||||||
|
var sendLen = socket.SendTo(sendBytes, endPoint);
|
||||||
|
socket.Close();
|
||||||
|
|
||||||
|
if (sendLen == sendBytes.Length) { return true; }
|
||||||
|
else { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public async static void AsyncSendAddrPack(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
|
/// <summary>
|
||||||
|
/// 异步发送地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="pkg">地址包</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public async static ValueTask<bool> SendAddrPackAsync(IPEndPoint endPoint, WebProtocol.SendAddrPackage pkg)
|
||||||
{
|
{
|
||||||
await Task.Run(() => { SendAddrPack(endPoint, pkg); });
|
return await Task.Run(() => { return SendAddrPack(endPoint, pkg); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendDataPack(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送数据包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="pkg">数据包</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool SendDataPack(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
|
||||||
{
|
{
|
||||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||||
socket.SendTo(pkg.ToBytes(), endPoint);
|
var sendBytes = pkg.ToBytes();
|
||||||
|
var sendLen = socket.SendTo(sendBytes, endPoint);
|
||||||
|
socket.Close();
|
||||||
|
|
||||||
|
if (sendLen == sendBytes.Length) { return true; }
|
||||||
|
else { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public async static void AsyncSendDataPack(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
|
/// <summary>
|
||||||
|
/// 异步发送数据包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endPoint">IP端点(IP地址与端口)</param>
|
||||||
|
/// <param name="pkg">数据包</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public async static ValueTask<bool> SendDataPackAsync(IPEndPoint endPoint, WebProtocol.SendDataPackage pkg)
|
||||||
{
|
{
|
||||||
await Task.Run(() => { SendDataPack(endPoint, pkg); });
|
return await Task.Run(() => { return SendDataPack(endPoint, pkg); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SendLocalHost(int port, string[] stringArray)
|
/// <summary>
|
||||||
|
/// 发送字符串到本地
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">端口</param>
|
||||||
|
/// <param name="stringArray">字符串数组</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool SendStringLocalHost(int port, string[] stringArray)
|
||||||
{
|
{
|
||||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
return SendString(new IPEndPoint(localhost, port), stringArray);
|
||||||
|
|
||||||
byte[] sendbuf = Encoding.ASCII.GetBytes(stringArray[0]);
|
|
||||||
IPEndPoint ep = new IPEndPoint(localhost, port);
|
|
||||||
|
|
||||||
socket.SendTo(sendbuf, ep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CycleSendLocalHost(int times, int sleepMilliSeconds, int port, string[] stringArray)
|
/// <summary>
|
||||||
|
/// 循环发送字符串到本地
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="times">发送总次数</param>
|
||||||
|
/// <param name="sleepMilliSeconds">间隔时间</param>
|
||||||
|
/// <param name="port">端口</param>
|
||||||
|
/// <param name="stringArray">字符串数组</param>
|
||||||
|
/// <returns>是否成功</returns>
|
||||||
|
public static bool CycleSendStringLocalHost(int times, int sleepMilliSeconds, int port, string[] stringArray)
|
||||||
{
|
{
|
||||||
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
var isSuccessful = true;
|
||||||
byte[] sendbuf = Encoding.ASCII.GetBytes(stringArray[0]);
|
|
||||||
IPEndPoint ep = new IPEndPoint(localhost, port);
|
|
||||||
|
|
||||||
while (times-- >= 0)
|
while (times-- >= 0)
|
||||||
{
|
{
|
||||||
socket.SendTo(sendbuf, ep);
|
isSuccessful = SendStringLocalHost(port, stringArray);
|
||||||
|
if (!isSuccessful) break;
|
||||||
|
|
||||||
Thread.Sleep(sleepMilliSeconds);
|
Thread.Sleep(sleepMilliSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return isSuccessful;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,97 @@
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using DotNext;
|
||||||
|
using DotNext.Threading;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using WebProtocol;
|
||||||
|
|
||||||
|
/// <summary> UDP接受数据包格式 </summary>
|
||||||
|
public class UDPData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 接受到的时间
|
||||||
|
/// </summary>
|
||||||
|
public required DateTime DateTime { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 发送来源的IP地址
|
||||||
|
/// </summary>
|
||||||
|
public required string Address { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 发送来源的端口号
|
||||||
|
/// </summary>
|
||||||
|
public required int Port { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 接受到的数据
|
||||||
|
/// </summary>
|
||||||
|
public required byte[] Data { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// 是否被读取过
|
||||||
|
/// </summary>
|
||||||
|
public required bool HasRead { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 深度拷贝对象
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>UDPData</returns>
|
||||||
|
public UDPData DeepClone()
|
||||||
|
{
|
||||||
|
var cloneData = new byte[this.Data.Length];
|
||||||
|
Buffer.BlockCopy(this.Data, 0, cloneData, 0, this.Data.Length);
|
||||||
|
return new UDPData()
|
||||||
|
{
|
||||||
|
DateTime = this.DateTime,
|
||||||
|
Address = new string(this.Address),
|
||||||
|
Port = this.Port,
|
||||||
|
Data = cloneData,
|
||||||
|
HasRead = this.HasRead
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将UDP Data 转化为Json 格式字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>json字符串</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UDP 服务器
|
||||||
|
/// </summary>
|
||||||
public class UDPServer
|
public class UDPServer
|
||||||
{
|
{
|
||||||
|
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private static Dictionary<string, Queue<UDPData>> udpData = new Dictionary<string, Queue<UDPData>>();
|
||||||
|
|
||||||
private int listenPort;
|
private int listenPort;
|
||||||
private UdpClient listener;
|
private UdpClient listener;
|
||||||
private IPEndPoint groupEP;
|
private IPEndPoint groupEP;
|
||||||
|
|
||||||
|
private bool isRunning = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 是否正在工作
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRunning { get { return isRunning; } }
|
||||||
|
|
||||||
|
/// <summary> UDP 服务器的错误代码 </summary>
|
||||||
|
public enum ErrorCode
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
GetNoneAfterTimeout,
|
||||||
|
ResponseWrong,
|
||||||
|
NotRecvDataPackage,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Construct a udp server with fixed port
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port"> Device UDP Port </param>
|
||||||
|
/// <returns> UDPServer class </returns>
|
||||||
public UDPServer(int port)
|
public UDPServer(int port)
|
||||||
{
|
{
|
||||||
// Construction
|
// Construction
|
||||||
|
@ -21,20 +105,243 @@ public class UDPServer
|
||||||
{
|
{
|
||||||
Console.WriteLine(e.ToString());
|
Console.WriteLine(e.ToString());
|
||||||
throw new ArgumentException(
|
throw new ArgumentException(
|
||||||
$"Not currect port num: {port}",
|
$"Failed to set up server with this port: {port}",
|
||||||
nameof(port)
|
nameof(port)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步寻找目标发送的内容
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ipAddr"> 目标IP地址 </param>
|
||||||
|
/// <param name="timeout">超时时间</param>
|
||||||
|
/// <param name="callerName">调用函数名称</param>
|
||||||
|
/// <param name="callerLineNum">调用函数位置</param>
|
||||||
|
/// <returns>
|
||||||
|
/// 异步Optional 数据包:
|
||||||
|
/// Optional 为空时,表明找不到数据;
|
||||||
|
/// Optional 存在时,为最先收到的数据
|
||||||
|
/// </returns>
|
||||||
|
public async ValueTask<Optional<UDPData>> FindDataAsync(
|
||||||
|
string ipAddr, int timeout = 1000,
|
||||||
|
[CallerMemberName] string callerName = "",
|
||||||
|
[CallerLineNumber] int callerLineNum = 0)
|
||||||
|
{
|
||||||
|
UDPData? data = null;
|
||||||
|
|
||||||
|
logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr} UDP Data");
|
||||||
|
|
||||||
|
var startTime = DateTime.Now;
|
||||||
|
var isTimeout = false;
|
||||||
|
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||||
|
while (!isTimeout)
|
||||||
|
{
|
||||||
|
var elapsed = DateTime.Now - startTime;
|
||||||
|
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||||
|
if (isTimeout) break;
|
||||||
|
|
||||||
|
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||||
|
using (await udpData.AcquireWriteLockAsync(timeleft))
|
||||||
|
{
|
||||||
|
if (udpData.ContainsKey(ipAddr) &&
|
||||||
|
udpData.TryGetValue(ipAddr, out var dataQueue) &&
|
||||||
|
dataQueue.Count > 0)
|
||||||
|
{
|
||||||
|
data = dataQueue.Dequeue();
|
||||||
|
logger.Debug($"Find UDP Data: {data.ToString()}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
logger.Trace("Get nothing even after time out");
|
||||||
|
return new(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new(data.DeepClone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取还未被读取的数据列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ipAddr">IP地址</param>
|
||||||
|
/// <param name="timeout">超时时间</param>
|
||||||
|
/// <returns>数据列表</returns>
|
||||||
|
public async ValueTask<Optional<List<UDPData>>> GetDataArrayAsync(string ipAddr, int timeout = 1000)
|
||||||
|
{
|
||||||
|
List<UDPData>? data = null;
|
||||||
|
|
||||||
|
var startTime = DateTime.Now;
|
||||||
|
var isTimeout = false;
|
||||||
|
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||||
|
while (!isTimeout)
|
||||||
|
{
|
||||||
|
var elapsed = DateTime.Now - startTime;
|
||||||
|
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||||
|
if (isTimeout) break;
|
||||||
|
|
||||||
|
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||||
|
using (await udpData.AcquireReadLockAsync(timeleft))
|
||||||
|
{
|
||||||
|
if (udpData.ContainsKey(ipAddr) &&
|
||||||
|
udpData.TryGetValue(ipAddr, out var dataQueue) &&
|
||||||
|
dataQueue.Count > 0)
|
||||||
|
{
|
||||||
|
data = dataQueue.ToList();
|
||||||
|
logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data is null)
|
||||||
|
{
|
||||||
|
logger.Trace("Get nothing even after time out");
|
||||||
|
return new(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步等待写响应
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">IP地址</param>
|
||||||
|
/// <param name="port">UDP 端口</param>
|
||||||
|
/// <param name="timeout">超时时间范围</param>
|
||||||
|
/// <returns>接收响应包</returns>
|
||||||
|
public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
|
||||||
|
(string address, int port = -1, int timeout = 1000)
|
||||||
|
{
|
||||||
|
var data = await FindDataAsync(address, timeout);
|
||||||
|
if (!data.HasValue)
|
||||||
|
return new(new Exception("Get None even after time out!"));
|
||||||
|
|
||||||
|
var recvData = data.Value;
|
||||||
|
if (recvData.Address != address || (port > 0 && recvData.Port != port))
|
||||||
|
return new(new Exception("Receive Data From Wrong Board!"));
|
||||||
|
|
||||||
|
var retPack = WebProtocol.RecvRespPackage.FromBytes(recvData.Data);
|
||||||
|
if (!retPack.IsSuccessful)
|
||||||
|
return new(new Exception("Not RecvDataPackage!", retPack.Error));
|
||||||
|
|
||||||
|
return retPack.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步等待数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">IP地址</param>
|
||||||
|
/// <param name="port">UDP 端口</param>
|
||||||
|
/// <param name="timeout">超时时间范围</param>
|
||||||
|
/// <returns>接收数据包</returns>
|
||||||
|
public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync
|
||||||
|
(string address, int port = -1, int timeout = 1000)
|
||||||
|
{
|
||||||
|
var data = await FindDataAsync(address, timeout);
|
||||||
|
if (!data.HasValue)
|
||||||
|
return new(new Exception("Get None even after time out!"));
|
||||||
|
|
||||||
|
var recvData = data.Value;
|
||||||
|
if (recvData.Address != address || (port >= 0 && recvData.Port != port))
|
||||||
|
return new(new Exception("Receive Data From Wrong Board!"));
|
||||||
|
|
||||||
|
var retPack = WebProtocol.RecvDataPackage.FromBytes(recvData.Data);
|
||||||
|
if (!retPack.IsSuccessful)
|
||||||
|
return new(new Exception("Not RecvDataPackage!", retPack.Error));
|
||||||
|
|
||||||
|
return retPack.Value;
|
||||||
|
}
|
||||||
|
|
||||||
private void ReceiveHandler(IAsyncResult res)
|
private void ReceiveHandler(IAsyncResult res)
|
||||||
{
|
{
|
||||||
|
logger.Trace("Enter handler");
|
||||||
var remoteEP = new IPEndPoint(IPAddress.Any, listenPort);
|
var remoteEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||||||
byte[] bytes = listener.EndReceive(res, ref remoteEP);
|
byte[] bytes = listener.EndReceive(res, ref remoteEP);
|
||||||
|
|
||||||
var sign = bytes[0];
|
// Handle RemoteEP
|
||||||
|
if (remoteEP is null)
|
||||||
|
{
|
||||||
|
logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
|
||||||
|
logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||||
|
goto BEGIN_RECEIVE;
|
||||||
|
}
|
||||||
|
|
||||||
string recvData;
|
|
||||||
|
// Handle Package
|
||||||
|
var udpData = RecordUDPData(bytes, remoteEP);
|
||||||
|
PrintData(udpData);
|
||||||
|
|
||||||
|
BEGIN_RECEIVE:
|
||||||
|
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SendBytes(IPEndPoint endPoint, byte[] buf)
|
||||||
|
{
|
||||||
|
var sendLen = listener.Send(buf, endPoint);
|
||||||
|
|
||||||
|
if (sendLen == buf.Length) { return true; }
|
||||||
|
else { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SendString(IPEndPoint endPoint, string text)
|
||||||
|
{
|
||||||
|
byte[] buf = Encoding.ASCII.GetBytes(text);
|
||||||
|
var sendLen = listener.Send(buf, endPoint);
|
||||||
|
|
||||||
|
if (sendLen == buf.Length) { return true; }
|
||||||
|
else { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP)
|
||||||
|
{
|
||||||
|
var remoteAddress = remoteEP.Address.ToString();
|
||||||
|
var remotePort = remoteEP.Port;
|
||||||
|
var data = new UDPData()
|
||||||
|
{
|
||||||
|
Address = remoteAddress,
|
||||||
|
Port = remotePort,
|
||||||
|
Data = bytes,
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
HasRead = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
using (udpData.AcquireWriteLock())
|
||||||
|
{
|
||||||
|
// Record UDP Receive Data
|
||||||
|
if (udpData.ContainsKey(remoteAddress) && udpData.TryGetValue(remoteAddress, out var dataQueue))
|
||||||
|
{
|
||||||
|
dataQueue.Enqueue(data);
|
||||||
|
logger.Trace("Receive data from old client");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var queue = new Queue<UDPData>();
|
||||||
|
queue.Enqueue(data);
|
||||||
|
udpData.Add(remoteAddress, queue);
|
||||||
|
logger.Trace("Receive data from new client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出UDP Data到log中
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">UDP数据</param>
|
||||||
|
public void PrintData(UDPData data)
|
||||||
|
{
|
||||||
|
var bytes = data.Data;
|
||||||
|
var sign = bytes[0];
|
||||||
|
string recvData = "";
|
||||||
if (sign == (byte)WebProtocol.PackSign.SendAddr)
|
if (sign == (byte)WebProtocol.PackSign.SendAddr)
|
||||||
{
|
{
|
||||||
var resData = WebProtocol.SendAddrPackage.FromBytes(bytes);
|
var resData = WebProtocol.SendAddrPackage.FromBytes(bytes);
|
||||||
|
@ -43,37 +350,81 @@ public class UDPServer
|
||||||
else
|
else
|
||||||
recvData = resData.Error.ToString();
|
recvData = resData.Error.ToString();
|
||||||
}
|
}
|
||||||
else if (sign == (byte)WebProtocol.PackSign.SendData)
|
else if (sign == (byte)WebProtocol.PackSign.SendData) { }
|
||||||
{
|
|
||||||
recvData = "";
|
|
||||||
}
|
|
||||||
else if (sign == (byte)WebProtocol.PackSign.RecvData)
|
else if (sign == (byte)WebProtocol.PackSign.RecvData)
|
||||||
{
|
{
|
||||||
recvData = "";
|
var resData = WebProtocol.RecvDataPackage.FromBytes(bytes);
|
||||||
|
if (resData.IsSuccessful)
|
||||||
|
recvData = resData.Value.Options.ToString();
|
||||||
|
else
|
||||||
|
recvData = resData.Error.ToString();
|
||||||
}
|
}
|
||||||
else if (sign == (byte)WebProtocol.PackSign.RecvResp)
|
else if (sign == (byte)WebProtocol.PackSign.RecvResp)
|
||||||
{
|
{
|
||||||
recvData = "";
|
var resData = WebProtocol.RecvRespPackage.FromBytes(bytes);
|
||||||
|
if (resData.IsSuccessful)
|
||||||
|
recvData = resData.Value.Options.ToString();
|
||||||
|
else
|
||||||
|
recvData = resData.Error.ToString();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
|
recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
string remoteStr = (remoteEP is null) ? "Unknown" : $"{remoteEP.Address.ToString()}:{remoteEP.Port.ToString()}";
|
logger.Debug($"Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:");
|
||||||
Console.WriteLine($"Receive Data from {remoteStr} at {DateTime.Now.ToString()}:");
|
logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||||
Console.WriteLine($"Original Data: {BitConverter.ToString(bytes).Replace("-", " ")}");
|
if (recvData.Length != 0) logger.Debug($" Decoded Data : {recvData}");
|
||||||
if (recvData.Length != 0) Console.WriteLine(recvData);
|
|
||||||
Console.WriteLine();
|
|
||||||
|
|
||||||
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将所有数据输出到log中
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> void </returns>
|
||||||
|
public void PrintAllData()
|
||||||
|
{
|
||||||
|
using (udpData.AcquireReadLock())
|
||||||
|
{
|
||||||
|
logger.Debug("Ready Data:");
|
||||||
|
|
||||||
|
foreach (var ip in udpData)
|
||||||
|
{
|
||||||
|
foreach (var data in ip.Value)
|
||||||
|
{
|
||||||
|
logger.Debug(data.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清空指定IP地址的数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ipAddr">IP地址</param>
|
||||||
|
/// <returns>无</returns>
|
||||||
|
public async Task ClearUDPData(string ipAddr)
|
||||||
|
{
|
||||||
|
using (await udpData.AcquireWriteLockAsync())
|
||||||
|
{
|
||||||
|
if (udpData.ContainsKey(ipAddr) &&
|
||||||
|
udpData.TryGetValue(ipAddr, out var dataQueue) &&
|
||||||
|
dataQueue.Count > 0)
|
||||||
|
{
|
||||||
|
dataQueue.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start UDP Server
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>None</returns>
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
this.listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -81,13 +432,18 @@ public class UDPServer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
this.isRunning = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Close UDP Server
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>None</returns>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
listener.Close();
|
this.listener.Close();
|
||||||
|
this.isRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,41 +3,123 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace WebProtocol
|
namespace WebProtocol
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary> The Sign of Package </summary>
|
||||||
public enum PackSign
|
public enum PackSign
|
||||||
{
|
{
|
||||||
|
/// <summary> Package: Send Read or Write Address to Device </summary>
|
||||||
SendAddr = 0x00,
|
SendAddr = 0x00,
|
||||||
|
/// <summary> Package: Send Data Which Update the flash of Device</summary>
|
||||||
SendData = 0xFF,
|
SendData = 0xFF,
|
||||||
|
/// <summary> </summary>
|
||||||
RecvData = 0x0F,
|
RecvData = 0x0F,
|
||||||
|
/// <summary> </summary>
|
||||||
RecvResp = 0xF0,
|
RecvResp = 0xF0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Package Burst Type </summary>
|
||||||
public enum BurstType
|
public enum BurstType
|
||||||
{
|
{
|
||||||
|
/// <summary> Extended Type </summary>
|
||||||
ExtendBurst = 0b00,
|
ExtendBurst = 0b00,
|
||||||
|
/// <summary> Fixed Type </summary>
|
||||||
FixedBurst = 0b01,
|
FixedBurst = 0b01,
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SendAddrPackOptions
|
|
||||||
{
|
|
||||||
public BurstType burstType;
|
|
||||||
public byte commandID;
|
|
||||||
public bool isWrite;
|
|
||||||
public byte burstLength;
|
|
||||||
public UInt32 address;
|
|
||||||
|
|
||||||
|
/// <summary> Package options which to send address to read or write </summary>
|
||||||
|
public class SendAddrPackOptions
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 突发类型
|
||||||
|
/// </summary>
|
||||||
|
/// <example>0</example>
|
||||||
|
public BurstType BurstType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务ID
|
||||||
|
/// </summary>
|
||||||
|
/// <example>1</example>
|
||||||
|
public byte CommandID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标识写入还是读取
|
||||||
|
/// </summary>
|
||||||
|
/// <example>true</example>
|
||||||
|
public bool IsWrite { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 突发长度:0是32bits,255是32bits x 256
|
||||||
|
/// </summary>
|
||||||
|
/// <example>255</example>
|
||||||
|
public byte BurstLength { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标地址
|
||||||
|
/// </summary>
|
||||||
|
/// <example>0</example>
|
||||||
|
public UInt32 Address { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 转换为Json格式字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Json String</returns>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return JsonConvert.SerializeObject(this);
|
return JsonConvert.SerializeObject(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RecvPackOptions
|
/// <summary> Package Options which to receive from boards </summary>
|
||||||
|
public class RecvPackOptions
|
||||||
{
|
{
|
||||||
public byte commandID;
|
/// <summary> 数据包类型 </summary>
|
||||||
public bool isSuccess;
|
public enum PackType
|
||||||
|
{
|
||||||
|
/// <summary> 读响应包 </summary>
|
||||||
|
ReadResp,
|
||||||
|
/// <summary> 写响应包 </summary>
|
||||||
|
WriteResp
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 数据包类型
|
||||||
|
/// </summary>
|
||||||
|
/// <example>0</example>
|
||||||
|
public PackType Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Task ID
|
||||||
|
/// </summary>
|
||||||
|
/// <example>0</example>
|
||||||
|
public byte CommandID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether is succeed to finish command
|
||||||
|
/// </summary>
|
||||||
|
/// <example>true</example>
|
||||||
|
public bool IsSuccess { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return Data
|
||||||
|
/// </summary>
|
||||||
|
/// <example>[]</example>
|
||||||
|
public byte[]? Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert to Json String
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> Json String </returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return JsonConvert.SerializeObject(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SendAddrPackage
|
/// <summary> Server->FPGA 地址包 </summary>
|
||||||
|
public class SendAddrPackage
|
||||||
{
|
{
|
||||||
readonly byte sign = (byte)PackSign.SendAddr;
|
readonly byte sign = (byte)PackSign.SendAddr;
|
||||||
readonly byte commandType;
|
readonly byte commandType;
|
||||||
|
@ -45,24 +127,44 @@ namespace WebProtocol
|
||||||
readonly byte _reserved = 0;
|
readonly byte _reserved = 0;
|
||||||
readonly UInt32 address;
|
readonly UInt32 address;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用地址包选项构造地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opts"> 地址包选项 </param>
|
||||||
public SendAddrPackage(SendAddrPackOptions opts)
|
public SendAddrPackage(SendAddrPackOptions opts)
|
||||||
{
|
{
|
||||||
byte byteBurstType = Convert.ToByte((byte)opts.burstType << 5);
|
byte byteBurstType = Convert.ToByte((byte)opts.BurstType << 6);
|
||||||
byte byteCommandID = Convert.ToByte((opts.commandID & 0x03) << 3);
|
byte byteCommandID = Convert.ToByte((opts.CommandID & 0x03) << 4);
|
||||||
byte byteIsWrite = (opts.isWrite ? (byte)0x01 : (byte)0x00);
|
byte byteIsWrite = (opts.IsWrite ? (byte)0x01 : (byte)0x00);
|
||||||
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
|
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
|
||||||
this.burstLength = opts.burstLength;
|
this.burstLength = opts.BurstLength;
|
||||||
this.address = opts.address;
|
this.address = opts.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendAddrPackage(BurstType burstType, byte commandID, bool isWrite)
|
/// <summary>
|
||||||
|
/// 使用完整参数构造地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="burstType"> 突发类型 </param>
|
||||||
|
/// <param name="commandID"> 任务ID </param>
|
||||||
|
/// <param name="isWrite"> 是否是写数据 </param>
|
||||||
|
/// <param name="burstLength"> 突发长度 </param>
|
||||||
|
/// <param name="address"> 设备地址 </param>
|
||||||
|
public SendAddrPackage(BurstType burstType, byte commandID, bool isWrite, byte burstLength, UInt32 address)
|
||||||
{
|
{
|
||||||
byte byteBurstType = Convert.ToByte((byte)burstType << 5);
|
byte byteBurstType = Convert.ToByte((byte)burstType << 6);
|
||||||
byte byteCommandID = Convert.ToByte((commandID & 0x03) << 3);
|
byte byteCommandID = Convert.ToByte((commandID & 0x03) << 4);
|
||||||
byte byteIsWrite = (isWrite ? (byte)0x01 : (byte)0x00);
|
byte byteIsWrite = (isWrite ? (byte)0x01 : (byte)0x00);
|
||||||
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
|
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
|
||||||
|
this.burstLength = burstLength;
|
||||||
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 使用二进制参数构建地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandType">二进制命令类型</param>
|
||||||
|
/// <param name="burstLength">突发长度</param>
|
||||||
|
/// <param name="address">写入或读取的地址</param>
|
||||||
public SendAddrPackage(byte commandType, byte burstLength, UInt32 address)
|
public SendAddrPackage(byte commandType, byte burstLength, UInt32 address)
|
||||||
{
|
{
|
||||||
this.commandType = commandType;
|
this.commandType = commandType;
|
||||||
|
@ -70,6 +172,28 @@ namespace WebProtocol
|
||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取对应地址包选项
|
||||||
|
/// </summary>
|
||||||
|
public SendAddrPackOptions Options
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new SendAddrPackOptions()
|
||||||
|
{
|
||||||
|
Address = this.address,
|
||||||
|
BurstLength = this.burstLength,
|
||||||
|
BurstType = (BurstType)(this.commandType >> 6),
|
||||||
|
CommandID = Convert.ToByte((this.commandType >> 4) & 0b11),
|
||||||
|
IsWrite = Convert.ToBoolean(this.commandType & 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将地址包转化为字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> 字节数组 </returns>
|
||||||
public byte[] ToBytes()
|
public byte[] ToBytes()
|
||||||
{
|
{
|
||||||
var arr = new byte[8];
|
var arr = new byte[8];
|
||||||
|
@ -78,58 +202,82 @@ namespace WebProtocol
|
||||||
arr[2] = burstLength;
|
arr[2] = burstLength;
|
||||||
arr[3] = _reserved;
|
arr[3] = _reserved;
|
||||||
|
|
||||||
var bytesAddr = Common.NumberProcessor.NumberToBytes(address, 4).Value;
|
var bytesAddr = Common.Number.NumberToBytes(address, 4).Value;
|
||||||
Array.Copy(bytesAddr, 0, arr, 4, bytesAddr.Length);
|
Array.Copy(bytesAddr, 0, arr, 4, bytesAddr.Length);
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 讲地址包转化为Json格式的地址包选项
|
||||||
|
/// </summary>
|
||||||
|
/// <returns> 字符串 </returns>
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
SendAddrPackOptions opts;
|
var opts = new SendAddrPackOptions();
|
||||||
opts.burstType = (BurstType)(commandType >> 5);
|
opts.BurstType = (BurstType)(commandType >> 6);
|
||||||
opts.commandID = Convert.ToByte((commandType >> 3) & 0b0011);
|
opts.CommandID = Convert.ToByte((commandType >> 4) & 0b0011);
|
||||||
opts.isWrite = Convert.ToBoolean(commandType & 0x01);
|
opts.IsWrite = Convert.ToBoolean(commandType & 0x01);
|
||||||
opts.burstLength = burstLength;
|
opts.BurstLength = burstLength;
|
||||||
opts.address = address;
|
opts.Address = address;
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(opts);
|
return JsonConvert.SerializeObject(opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据字节数组构建地址包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">字节数组</param>
|
||||||
|
/// <param name="checkSign">是否校验地址包包头</param>
|
||||||
|
/// <returns>地址包</returns>
|
||||||
public static Result<SendAddrPackage> FromBytes(byte[] bytes, bool checkSign = true)
|
public static Result<SendAddrPackage> FromBytes(byte[] bytes, bool checkSign = true)
|
||||||
{
|
{
|
||||||
if (bytes.Length != 8)
|
if (bytes.Length != 8)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
return new(new ArgumentException(
|
||||||
"Bytes are not equal to 8 bytes.",
|
"Bytes are not equal to 8 bytes.",
|
||||||
nameof(bytes)
|
nameof(bytes)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkSign && bytes[0] != (byte)PackSign.SendAddr)
|
if (checkSign && bytes[0] != (byte)PackSign.SendAddr)
|
||||||
{
|
{
|
||||||
throw new ArgumentException(
|
return new(new ArgumentException(
|
||||||
"The sign of bytes is not SendAddr Package, but you can disable it by set checkSign false.",
|
"The sign of bytes is not SendAddr Package, but you can disable it by set checkSign false.",
|
||||||
nameof(bytes)
|
nameof(bytes)
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
var address = Common.NumberProcessor.BytesToNumber(bytes[4..]).Value;
|
var address = Common.Number.BytesToNumber(bytes[4..]).Value;
|
||||||
return new SendAddrPackage(bytes[1], bytes[2], Convert.ToUInt32(address));
|
return new SendAddrPackage(bytes[1], bytes[2], Convert.ToUInt32(address));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> 数据包 </summary>
|
||||||
public struct SendDataPackage
|
public struct SendDataPackage
|
||||||
{
|
{
|
||||||
readonly byte sign = (byte)PackSign.SendData;
|
readonly byte sign = (byte)PackSign.SendData;
|
||||||
readonly byte[] _reserved = new byte[3];
|
readonly byte[] _reserved = new byte[3];
|
||||||
readonly byte[] bodyData;
|
readonly byte[] bodyData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据数据内容构建数据包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bodyData">数据</param>
|
||||||
public SendDataPackage(byte[] bodyData)
|
public SendDataPackage(byte[] bodyData)
|
||||||
{
|
{
|
||||||
|
if (bodyData.Length > 256 * (32 / 8))
|
||||||
|
throw new Exception("The data of SendDataPackage can't over 256 * 32bits");
|
||||||
|
|
||||||
this.bodyData = bodyData;
|
this.bodyData = bodyData;
|
||||||
|
|
||||||
|
_ = _reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将数据包转化为字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>字节数组</returns>
|
||||||
public byte[] ToBytes()
|
public byte[] ToBytes()
|
||||||
{
|
{
|
||||||
var bodyDataLen = bodyData.Length;
|
var bodyDataLen = bodyData.Length;
|
||||||
|
@ -145,6 +293,7 @@ namespace WebProtocol
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> FPGA->Server 读响应包 </summary>
|
||||||
public struct RecvDataPackage
|
public struct RecvDataPackage
|
||||||
{
|
{
|
||||||
readonly byte sign = (byte)PackSign.RecvData;
|
readonly byte sign = (byte)PackSign.RecvData;
|
||||||
|
@ -153,23 +302,109 @@ namespace WebProtocol
|
||||||
readonly byte _reserved = 0;
|
readonly byte _reserved = 0;
|
||||||
readonly byte[] bodyData;
|
readonly byte[] bodyData;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FPGA->Server 读响应包
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandID"> 任务ID号 </param>
|
||||||
|
/// <param name="resp"> 读响应包响应 </param>
|
||||||
|
/// <param name="bodyData"> 数据 </param>
|
||||||
public RecvDataPackage(byte commandID, byte resp, byte[] bodyData)
|
public RecvDataPackage(byte commandID, byte resp, byte[] bodyData)
|
||||||
{
|
{
|
||||||
this.commandID = commandID;
|
this.commandID = commandID;
|
||||||
this.resp = resp;
|
this.resp = resp;
|
||||||
this.bodyData = bodyData;
|
this.bodyData = bodyData;
|
||||||
|
|
||||||
|
_ = this.sign;
|
||||||
|
_ = this._reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecvPackOptions Options()
|
/// <summary>
|
||||||
|
/// FPGA->Server 读响应包
|
||||||
|
/// 构造函数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandID"> 任务ID号 </param>
|
||||||
|
/// <param name="isSuccess">是否读取成功</param>
|
||||||
|
/// <param name="bodyData"> 数据 </param>
|
||||||
|
public RecvDataPackage(byte commandID, bool isSuccess, byte[] bodyData)
|
||||||
{
|
{
|
||||||
RecvPackOptions opts;
|
this.commandID = commandID;
|
||||||
opts.commandID = commandID;
|
this.resp = Convert.ToByte(isSuccess);
|
||||||
opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? true : false);
|
this.bodyData = bodyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过接受包选项构建读响应包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opts">接收包(读响应包和写响应包)选项</param>
|
||||||
|
public RecvDataPackage(RecvPackOptions opts)
|
||||||
|
{
|
||||||
|
this.commandID = opts.CommandID;
|
||||||
|
this.resp = Convert.ToByte(opts.IsSuccess ? 0b10 : 0b00);
|
||||||
|
this.bodyData = opts.Data ?? (byte[])[0, 0, 0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取读响应包选项
|
||||||
|
/// </summary>
|
||||||
|
public RecvPackOptions Options
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var opts = new RecvPackOptions();
|
||||||
|
opts.Type = RecvPackOptions.PackType.ReadResp;
|
||||||
|
opts.CommandID = commandID;
|
||||||
|
opts.IsSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? false : true);
|
||||||
|
opts.Data = bodyData;
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取是否成功
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuccessful
|
||||||
|
{
|
||||||
|
get { return Convert.ToBoolean((resp >> 1) == 0b01 ? false : true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从字节数组构建读响应包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">字节数组</param>
|
||||||
|
/// <returns>读响应包</returns>
|
||||||
|
public static Result<RecvDataPackage> FromBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (bytes[0] != (byte)PackSign.RecvData)
|
||||||
|
return new(new ArgumentException(
|
||||||
|
$"The sign of bytes is not RecvData Package, Sign: 0x{BitConverter.ToString([bytes[0]])}",
|
||||||
|
nameof(bytes)
|
||||||
|
));
|
||||||
|
return new RecvDataPackage(bytes[1], bytes[2], bytes[4..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将数据包转化为字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>字节数组</returns>
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
var bodyDataLen = bodyData.Length;
|
||||||
|
var arr = new byte[4 + bodyDataLen];
|
||||||
|
|
||||||
|
arr[0] = this.sign;
|
||||||
|
arr[1] = this.commandID;
|
||||||
|
arr[2] = this.resp;
|
||||||
|
|
||||||
|
Array.Copy(bodyData, 0, arr, 4, bodyDataLen);
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> 写响应包 </summary>
|
||||||
public struct RecvRespPackage
|
public struct RecvRespPackage
|
||||||
{
|
{
|
||||||
readonly byte sign = (byte)PackSign.RecvResp;
|
readonly byte sign = (byte)PackSign.RecvResp;
|
||||||
|
@ -177,19 +412,94 @@ namespace WebProtocol
|
||||||
readonly byte resp;
|
readonly byte resp;
|
||||||
readonly byte _reserved = 0;
|
readonly byte _reserved = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建写响应包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandID">任务ID</param>
|
||||||
|
/// <param name="resp">写响应</param>
|
||||||
public RecvRespPackage(byte commandID, byte resp)
|
public RecvRespPackage(byte commandID, byte resp)
|
||||||
{
|
{
|
||||||
this.commandID = commandID;
|
this.commandID = commandID;
|
||||||
this.resp = resp;
|
this.resp = resp;
|
||||||
|
|
||||||
|
_ = this.sign;
|
||||||
|
_ = this._reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecvPackOptions Options()
|
/// <summary>
|
||||||
|
/// 构建写响应包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandID">任务ID</param>
|
||||||
|
/// <param name="isSuccess">是否写成功</param>
|
||||||
|
public RecvRespPackage(byte commandID, bool isSuccess)
|
||||||
{
|
{
|
||||||
RecvPackOptions opts;
|
this.commandID = commandID;
|
||||||
opts.commandID = commandID;
|
this.resp = Convert.ToByte(isSuccess);
|
||||||
opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? true : false);
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过接受包选项构建写响应包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="opts">接收包(读响应包和写响应包)选项</param>
|
||||||
|
public RecvRespPackage(RecvPackOptions opts)
|
||||||
|
{
|
||||||
|
this.commandID = opts.CommandID;
|
||||||
|
this.resp = Convert.ToByte(opts.IsSuccess ? 0b10 : 0b00);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取写响应包选项
|
||||||
|
/// </summary>
|
||||||
|
public RecvPackOptions Options
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var opts = new RecvPackOptions();
|
||||||
|
opts.Type = RecvPackOptions.PackType.WriteResp;
|
||||||
|
opts.CommandID = commandID;
|
||||||
|
opts.IsSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? false : true);
|
||||||
|
opts.Data = null;
|
||||||
|
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入是否成功
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuccessful
|
||||||
|
{
|
||||||
|
get { return Convert.ToBoolean((resp >> 1) == 0b01 ? false : true); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从字节数组构建写响应包
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bytes">字节数组</param>
|
||||||
|
/// <returns>写响应包</returns>
|
||||||
|
public static Result<RecvRespPackage> FromBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
if (bytes[0] != (byte)PackSign.RecvResp)
|
||||||
|
return new(new ArgumentException(
|
||||||
|
$"The sign of bytes is not RecvResp Package, Sign: 0x{BitConverter.ToString([bytes[0]])}",
|
||||||
|
nameof(bytes)
|
||||||
|
));
|
||||||
|
return new RecvRespPackage(bytes[1], bytes[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将数据包转化为字节数组
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>字节数组</returns>
|
||||||
|
public byte[] ToBytes()
|
||||||
|
{
|
||||||
|
var arr = new byte[4];
|
||||||
|
|
||||||
|
arr[0] = this.sign;
|
||||||
|
arr[1] = this.commandID;
|
||||||
|
arr[2] = this.resp;
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
16
src/App.vue
16
src/App.vue
|
@ -1,17 +1,23 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import iconMenu from "./assets/menu.svg";
|
||||||
|
import Sidebar from "./components/Sidebar.vue";
|
||||||
import { useThemeStore } from "./stores/theme";
|
import { useThemeStore } from "./stores/theme";
|
||||||
|
|
||||||
const theme = useThemeStore();
|
const theme = useThemeStore();
|
||||||
|
const items = [
|
||||||
|
{ id: 1, icon: iconMenu, text: "用户界面", page: "/user" },
|
||||||
|
{ id: 2, icon: iconMenu, text: "ComponentTest", page: "/test" },
|
||||||
|
{ id: 3, icon: iconMenu, text: "JtagTest", page: "/test/jtag" },
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :data-theme="theme.currentTheme">
|
<div :data-theme="theme.currentTheme">
|
||||||
<header>
|
<header class="relative">
|
||||||
<RouterLink to="/user"> Go to User</RouterLink>
|
<div class="fixed left-0 top-0 z-50">
|
||||||
<RouterLink to="/login"> Go to Login</RouterLink>
|
<Sidebar :items="items" />
|
||||||
<router-link to="/test"> Go to Test</router-link>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div></div>
|
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
|
||||||
import type { AppRouter } from '../server/router';
|
|
||||||
|
|
||||||
export const client = createTRPCProxyClient<AppRouter>({
|
|
||||||
links: [
|
|
||||||
httpBatchLink({
|
|
||||||
url: 'http://localhost:3002',
|
|
||||||
// You can pass any HTTP headers you wish here
|
|
||||||
async headers() {
|
|
||||||
return {
|
|
||||||
authorization: document.cookie,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<template>
|
||||||
|
<button class="btn transition-transform duration-150 ease-in-out hover:scale-120">
|
||||||
|
Button A
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<style scoped lang="postcss">
|
||||||
|
@import "@/assets/main.css";
|
||||||
|
</style>
|
|
@ -1,123 +1,162 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="[
|
<div
|
||||||
'card-dash',
|
class="card card-dash shadow-xl h-screen"
|
||||||
'sidebar-base',
|
:class="[sidebar.isClose ? 'w-31' : 'w-80']"
|
||||||
'transition-all',
|
>
|
||||||
'duration-500',
|
<div
|
||||||
'ease-in-out',
|
class="card-body flex relative transition-all duration-500 ease-in-out"
|
||||||
isClose ? 'sidebar-close' : 'sidebar-open',
|
>
|
||||||
]">
|
|
||||||
<div class="card-body flex transition-all duration-500 ease-in-out">
|
|
||||||
<!-- Avatar and Name -->
|
<!-- Avatar and Name -->
|
||||||
<div :class="['flex', 'items-center', isClose ? 'flex-col' : '']">
|
<div class="relative" :class="sidebar.isClose ? 'h-50' : 'h-20'">
|
||||||
<!-- Img -->
|
<!-- Img -->
|
||||||
<div class="avatar h-10">
|
<div
|
||||||
|
class="avatar h-10 fixed top-10"
|
||||||
|
:class="sidebar.isClose ? 'left-10' : 'left-7'"
|
||||||
|
>
|
||||||
<div class="rounded-full">
|
<div class="rounded-full">
|
||||||
<img src="../assets/user.svg" alt="User" />
|
<img src="../assets/user.svg" alt="User" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<div v-if="!isClose" class="mx-5 grow">
|
<Transition>
|
||||||
|
<div v-if="!sidebar.isClose" class="mx-5 grow fixed left-20 top-11">
|
||||||
<label class="text-2xl">用户名</label>
|
<label class="text-2xl">用户名</label>
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Toggle Button -->
|
<!-- Toggle Button -->
|
||||||
<button class="btn btn-square rounded-lg p-2 m-3" @click="toggleSidebar">
|
<button
|
||||||
<img src="../assets/left.svg" alt="Menu Button" class="opacity-50">
|
class="btn btn-square rounded-lg p-2 m-3 fixed"
|
||||||
|
:class="sidebar.isClose ? 'left-7 top-23' : 'left-60 top-7'"
|
||||||
|
@click="sidebar.toggleSidebar"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
t="1741694970690"
|
||||||
|
:class="sidebar.isClose ? 'rotate-0' : 'rotate-540'"
|
||||||
|
class="icon"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="4546"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M803.758 514.017c-0.001-0.311-0.013-0.622-0.018-0.933-0.162-23.974-9.386-47.811-27.743-65.903-0.084-0.082-0.172-0.157-0.256-0.239-0.154-0.154-0.296-0.315-0.451-0.468L417.861 94.096c-37.685-37.153-99.034-37.476-136.331-0.718-37.297 36.758-36.979 97.231 0.707 134.384l290.361 286.257-290.362 286.257c-37.685 37.153-38.004 97.625-0.707 134.383 37.297 36.758 98.646 36.435 136.331-0.718l357.43-352.378c0.155-0.153 0.297-0.314 0.451-0.468 0.084-0.082 0.172-0.157 0.256-0.239 18.354-18.089 27.578-41.922 27.743-65.892 0.004-0.315 0.017-0.631 0.018-0.947z"
|
||||||
|
:fill="theme.isLightTheme() ? '#828282' : '#C0C3C8'"
|
||||||
|
p-id="4547"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<ul class="menu h-full w-full">
|
<ul class="menu h-full w-full">
|
||||||
<li v-for="item in items" class="text-lg my-1">
|
<li v-for="item in props.items" class="text-xl my-1">
|
||||||
<a>
|
<a @click="router.push(item.page)">
|
||||||
<img class="h-[1.5em] opacity-50 mx-1" :src="item.icon" alt="An icon" />
|
<svg
|
||||||
<p v-if="!isClose">{{ item.msg }}</p>
|
t="1741694797806"
|
||||||
|
class="icon h-[1.5em] w-[1.5em] opacity-50 mx-1"
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
p-id="2622"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
width="200"
|
||||||
|
height="200"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z"
|
||||||
|
p-id="2623"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z"
|
||||||
|
p-id="2624"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z"
|
||||||
|
p-id="2625"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z"
|
||||||
|
p-id="2626"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<Transition>
|
||||||
|
<p class="break-keep" v-if="!sidebar.isClose">{{ item.text }}</p>
|
||||||
|
</Transition>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<ul class="menu w-full">
|
<ul class="menu w-full">
|
||||||
<li class="mb-5">
|
<li>
|
||||||
<a @click="theme.toggleTheme" class="text-xl">
|
<a @click="theme.toggleTheme" class="text-xl">
|
||||||
<ThemeControlButton />
|
<ThemeControlButton />
|
||||||
<p v-if="!isClose">改变主题</p>
|
<Transition>
|
||||||
<ThemeControlToggle v-if="!isClose" />
|
<p v-if="!sidebar.isClose" class="break-keep">改变主题</p>
|
||||||
|
</Transition>
|
||||||
|
<Transition>
|
||||||
|
<ThemeControlToggle v-if="!sidebar.isClose" />
|
||||||
|
</Transition>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import iconMenu from "../assets/menu.svg"
|
import iconMenu from "../assets/menu.svg";
|
||||||
|
import "../router";
|
||||||
import { useThemeStore } from "@/stores/theme";
|
import { useThemeStore } from "@/stores/theme";
|
||||||
import { computed, ref } from "vue";
|
import { useSidebarStore } from "@/stores/sidebar";
|
||||||
import ThemeControlButton from "./ThemeControlButton.vue";
|
import ThemeControlButton from "./ThemeControlButton.vue";
|
||||||
import ThemeControlToggle from "./ThemeControlToggle.vue";
|
import ThemeControlToggle from "./ThemeControlToggle.vue";
|
||||||
|
import router from "../router";
|
||||||
|
|
||||||
const theme = useThemeStore()
|
const theme = useThemeStore();
|
||||||
const isClose = ref(false)
|
const sidebar = useSidebarStore();
|
||||||
|
|
||||||
const items = [
|
type Item = {
|
||||||
{ id: 1, icon: iconMenu, msg: "btn1" },
|
id: number;
|
||||||
{ id: 2, icon: iconMenu, msg: "btn2" },
|
icon: string;
|
||||||
{ id: 3, icon: iconMenu, msg: "btn3" },
|
text: string;
|
||||||
{ id: 4, icon: iconMenu, msg: "btn4" },
|
page: string;
|
||||||
]
|
};
|
||||||
|
|
||||||
const themeSidebar = computed(() => {
|
interface Props {
|
||||||
return [
|
items?: Array<Item>;
|
||||||
"card-dash",
|
|
||||||
"sidebar-base",
|
|
||||||
"transition",
|
|
||||||
"duration-300",
|
|
||||||
"ease-in-out",
|
|
||||||
isClose.value ? "sidebar-close" : "sidebar-open",
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
function closeSidebar() {
|
|
||||||
isClose.value = true;
|
|
||||||
console.info("Close sidebar")
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSidebar() {
|
|
||||||
isClose.value = false;
|
|
||||||
console.info("Open sidebar")
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSidebar() {
|
|
||||||
if (isClose.value) {
|
|
||||||
openSidebar()
|
|
||||||
// themeSidebar.value = "card-dash sidebar-base sidebar-open"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
closeSidebar()
|
|
||||||
// themeSidebar.value = "card-dash sidebar-base sidebar-close"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
items: () => [
|
||||||
|
{ id: 1, icon: iconMenu, text: "btn1", page: "/" },
|
||||||
|
{ id: 2, icon: iconMenu, text: "btn2", page: "/" },
|
||||||
|
{ id: 3, icon: iconMenu, text: "btn3", page: "/" },
|
||||||
|
{ id: 4, icon: iconMenu, text: "btn4", page: "/" },
|
||||||
|
],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="postcss">
|
<style scoped lang="postcss">
|
||||||
@reference "../assets/main.css";
|
@reference "../assets/main.css";
|
||||||
|
|
||||||
.sidebar-base {
|
* {
|
||||||
@apply card shadow-xl h-screen;
|
@apply transition-all duration-500 ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-open {
|
.v-enter-active,
|
||||||
@apply w-80
|
.v-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-close {
|
.v-enter-from,
|
||||||
@apply w-31
|
.v-leave-to {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card card-dash shadow-xl w-90 h-60">
|
<div class="card card-dash shadow-xl w-90 h-60">
|
||||||
<div class="card-body flex">
|
<div class="card-body flex">
|
||||||
|
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<h1 class="card-title place-self-center font-bold text-2xl">上传比特流文件</h1>
|
<h1 class="card-title place-self-center font-bold text-2xl">
|
||||||
|
上传比特流文件
|
||||||
|
</h1>
|
||||||
|
|
||||||
<!-- Input File -->
|
<!-- Input File -->
|
||||||
<fieldset class="fieldset w-full">
|
<fieldset class="fieldset w-full">
|
||||||
<legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
|
<legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
|
||||||
<input type="file" class="file-input" @change="handleFileChange" />
|
<input type="file" class="file-input" @change="handleFileChange" />
|
||||||
<label class="fieldset-label">Max size 2MB</label>
|
<label class="fieldset-label">文件最大容量: 2MB</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<!-- Upload Button -->
|
<!-- Upload Button -->
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<button @click="uploadBitStream" class="btn btn-primary grow">上传</button>
|
<button @click="uploadBitStream" class="btn btn-primary grow">
|
||||||
|
上传
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { client } from '@/client';
|
|
||||||
import { TRPCClientError } from '@trpc/client';
|
|
||||||
|
|
||||||
var bitstream = null;
|
var bitstream = null;
|
||||||
|
|
||||||
function handleFileChange(event: Event): void {
|
function handleFileChange(event: Event): void {
|
||||||
|
@ -32,7 +31,7 @@ function handleFileChange(event: Event): void {
|
||||||
const file = target.files?.[0]; // 获取选中的第一个文件
|
const file = target.files?.[0]; // 获取选中的第一个文件
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
console.error('未选择文件');
|
console.error("未选择文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,26 +40,9 @@ function handleFileChange(event: Event): void {
|
||||||
console.log(bitstream);
|
console.log(bitstream);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadBitStream() {
|
async function uploadBitStream() {}
|
||||||
try {
|
|
||||||
const serverStatus = await client.api.status.query();
|
|
||||||
if (serverStatus != "OK") {
|
|
||||||
throw new Error("Server Busy...")
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
function checkFileType(file: File) {}
|
||||||
if (error instanceof TRPCClientError) {
|
|
||||||
console.error("Can't connect to Server!")
|
|
||||||
} else {
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkFileType(file: File) {
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="postcss">
|
<style scoped lang="postcss">
|
||||||
|
|
|
@ -46,30 +46,31 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
width?: string | number
|
width?: string | number;
|
||||||
height?: string | number
|
height?: string | number;
|
||||||
}
|
}
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
width: 160,
|
width: 160,
|
||||||
height: 160,
|
height: 160,
|
||||||
})
|
});
|
||||||
|
|
||||||
const btnStatus = ref([false, false, false, false, false, false])
|
const btnStatus = ref([false, false, false, false, false, false]);
|
||||||
const btnLocation = computed(() => {
|
const btnLocation = computed(() => {
|
||||||
return btnStatus.value.map((status) => { return status ? 7.025 : 8.325 })
|
return btnStatus.value.map((status) => {
|
||||||
})
|
return status ? 7.025 : 8.325;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function setBtnStatus(btnNum: number, isOn: boolean): void {
|
function setBtnStatus(btnNum: number, isOn: boolean): void {
|
||||||
btnStatus.value[btnNum] = isOn
|
btnStatus.value[btnNum] = isOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleBtnStatus(btnNum: number): void {
|
function toggleBtnStatus(btnNum: number): void {
|
||||||
btnStatus.value[btnNum] = !btnStatus.value[btnNum]
|
btnStatus.value[btnNum] = !btnStatus.value[btnNum];
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="postcss">
|
<style scoped lang="postcss">
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import { createMemoryHistory, createRouter } from 'vue-router'
|
import { createMemoryHistory, createRouter } from "vue-router";
|
||||||
import LoginView from "@/views/LoginView.vue"
|
import LoginView from "../views/LoginView.vue";
|
||||||
import UserView from '@/views/UserView.vue'
|
import UserView from "../views/UserView.vue";
|
||||||
import TestView from '@/views/TestView.vue'
|
import TestView from "../views/TestView.vue";
|
||||||
|
import JtagTest from "../views/JtagTest.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: "/", redirect: "/user" },
|
{ path: "/", redirect: "/user" },
|
||||||
{ path: "/login", name: "Login", component: LoginView },
|
{ path: "/login", name: "Login", component: LoginView },
|
||||||
{ path: "/user", name: "User", component: UserView },
|
{ path: "/user", name: "User", component: UserView },
|
||||||
{ path: "/test", name: "Test", component: TestView },
|
{ path: "/test", name: "Test", component: TestView },
|
||||||
]
|
{ path: "/test/jtag", name:"JtagTest", component: JtagTest}
|
||||||
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createMemoryHistory(),
|
history: createMemoryHistory(),
|
||||||
routes,
|
routes,
|
||||||
})
|
});
|
||||||
|
|
||||||
export default router
|
export default router;
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useSidebarStore = defineStore('sidebar', () => {
|
||||||
|
const isClose = ref(false);
|
||||||
|
|
||||||
|
function closeSidebar() {
|
||||||
|
isClose.value = true;
|
||||||
|
console.info("Close sidebar");
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSidebar() {
|
||||||
|
isClose.value = false;
|
||||||
|
console.info("Open sidebar");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSidebar() {
|
||||||
|
if (isClose.value) {
|
||||||
|
openSidebar();
|
||||||
|
// themeSidebar.value = "card-dash sidebar-base sidebar-open"
|
||||||
|
} else {
|
||||||
|
closeSidebar();
|
||||||
|
// themeSidebar.value = "card-dash sidebar-base sidebar-close"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isClose, closeSidebar, openSidebar, toggleSidebar }
|
||||||
|
})
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-screen w-screen flex justify-center">
|
||||||
|
<div class="h-full w-32"></div>
|
||||||
|
|
||||||
|
<div class="h-full w-[70%] shadow-2xl flex">
|
||||||
|
|
||||||
|
<button class="btn btn-primary h-10 w-30">获取ID Code</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup></script>
|
||||||
|
|
||||||
|
<style scoped lang="postcss">
|
||||||
|
@import "../assets/main.css";
|
||||||
|
</style>
|
|
@ -7,7 +7,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LoginCard from '@/components/LoginCard.vue';
|
import LoginCard from "@/components/LoginCard.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
<div class="w-screen h-screen">
|
<div class="w-screen h-screen">
|
||||||
<!-- <Switch width="720" height="720" /> -->
|
<!-- <Switch width="720" height="720" /> -->
|
||||||
<MechanicalButton width="720" height="720" />
|
<MechanicalButton width="720" height="720" />
|
||||||
|
<PopButton></PopButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MechanicalButton from '@/components/equipments/MechanicalButton.vue';
|
import PopButton from "@/components/PopButton.vue";
|
||||||
import Switch from '@/components/equipments/Switch.vue';
|
import MechanicalButton from "@/components/equipments/MechanicalButton.vue";
|
||||||
|
// import Switch from "@/components/equipments/Switch.vue";
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="postcss">
|
<style scoped lang="postcss">
|
||||||
@import "../assets/main.css"
|
@import "../assets/main.css";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<header></header>
|
||||||
</header>
|
|
||||||
<main class="relative">
|
<main class="relative">
|
||||||
<div class="fixed left-0 top-0 z-50">
|
|
||||||
<Sidebar />
|
|
||||||
</div>
|
|
||||||
<div class="w-screen h-screen flex items-center justify-center">
|
<div class="w-screen h-screen flex items-center justify-center">
|
||||||
<UploadCard />
|
<UploadCard />
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,10 +8,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import UploadCard from '@/components/UploadCard.vue';
|
import UploadCard from "@/components/UploadCard.vue";
|
||||||
import Sidebar from '../components/Sidebar.vue'
|
import Sidebar from "../components/Sidebar.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@import "../assets/main.css"
|
@import "../assets/main.css";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,9 +6,6 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.app.json"
|
"path": "./tsconfig.app.json"
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.bun.json"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue