From 0e222ebd70c03336b036e197608f11c43d7ee584 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Thu, 26 Sep 2024 15:36:18 +0200 Subject: [PATCH 01/69] Bump dependencies --- backend/package-lock.json | 77 ++++----- backend/package.json | 6 +- frontend/package-lock.json | 310 ++++++++++++++++++------------------- frontend/package.json | 6 +- package-lock.json | 14 +- package.json | 2 +- 6 files changed, 210 insertions(+), 205 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 695899f..09ac327 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,11 +19,11 @@ "devDependencies": { "@tsoa/cli": "^6.4.0", "@types/debug": "^4.1.12", - "@types/express": "^4.17.21", - "@types/node": "^22.5.5", + "@types/express": "^5.0.0", + "@types/node": "^22.7.2", "@types/steamid": "^2.0.3", "@types/ws": "^8.5.12", - "nodemon": "^3.1.6", + "nodemon": "^3.1.7", "ts-node": "^10.9.2", "typescript": "^5.6.2" } @@ -632,20 +632,22 @@ } }, "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "license": "MIT", "dependencies": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", + "@types/express-serve-static-core": "^5.0.0", "@types/qs": "*", "@types/serve-static": "*" } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -711,28 +713,31 @@ } }, "node_modules/@types/node": { - "version": "22.5.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", - "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "version": "22.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.2.tgz", + "integrity": "sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } }, "node_modules/@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -1790,9 +1795,9 @@ "dev": true }, "node_modules/nodemon": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.6.tgz", - "integrity": "sha512-C8ymJbXpTTinxjWuMfMxw0rZhTn/r7ypSGldQyqPEgDEaVwAthqC0aodsMwontnAInN9TuPwRLeBoyhmfv+iSA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3207,20 +3212,20 @@ } }, "@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", "requires": { "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", + "@types/express-serve-static-core": "^5.0.0", "@types/qs": "*", "@types/serve-static": "*" } }, "@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz", + "integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -3286,17 +3291,17 @@ } }, "@types/node": { - "version": "22.5.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", - "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "version": "22.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.2.tgz", + "integrity": "sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==", "requires": { "undici-types": "~6.19.2" } }, "@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==" }, "@types/range-parser": { "version": "1.2.7", @@ -4075,9 +4080,9 @@ "dev": true }, "nodemon": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.6.tgz", - "integrity": "sha512-C8ymJbXpTTinxjWuMfMxw0rZhTn/r7ypSGldQyqPEgDEaVwAthqC0aodsMwontnAInN9TuPwRLeBoyhmfv+iSA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", "dev": true, "requires": { "chokidar": "^3.5.2", diff --git a/backend/package.json b/backend/package.json index 2f81cce..73bbedd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,11 +21,11 @@ "devDependencies": { "@tsoa/cli": "^6.4.0", "@types/debug": "^4.1.12", - "@types/express": "^4.17.21", - "@types/node": "^22.5.5", + "@types/express": "^5.0.0", + "@types/node": "^22.7.2", "@types/steamid": "^2.0.3", "@types/ws": "^8.5.12", - "nodemon": "^3.1.6", + "nodemon": "^3.1.7", "ts-node": "^10.9.2", "typescript": "^5.6.2" } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5dab38d..7bd1ca4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,10 +16,10 @@ "autoprefixer": "^10.4.20", "daisyui": "^4.12.10", "postcss": "^8.4.47", - "solid-js": "^1.8.22", - "tailwindcss": "^3.4.12", + "solid-js": "^1.9.1", + "tailwindcss": "^3.4.13", "typescript": "^5.6.2", - "vite": "^5.4.7", + "vite": "^5.4.8", "vite-plugin-solid": "^2.10.2" } }, @@ -818,9 +818,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", - "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "cpu": [ "arm" ], @@ -832,9 +832,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", - "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "cpu": [ "arm64" ], @@ -846,9 +846,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", - "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "cpu": [ "arm64" ], @@ -860,9 +860,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", - "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "cpu": [ "x64" ], @@ -874,9 +874,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", - "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "cpu": [ "arm" ], @@ -888,9 +888,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", - "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "cpu": [ "arm" ], @@ -902,9 +902,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", - "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "cpu": [ "arm64" ], @@ -916,9 +916,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", - "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "cpu": [ "arm64" ], @@ -930,9 +930,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", - "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "cpu": [ "ppc64" ], @@ -944,9 +944,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", - "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "cpu": [ "riscv64" ], @@ -958,9 +958,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", - "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "cpu": [ "s390x" ], @@ -972,9 +972,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", - "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "cpu": [ "x64" ], @@ -986,9 +986,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", - "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "cpu": [ "x64" ], @@ -1000,9 +1000,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", - "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "cpu": [ "arm64" ], @@ -1014,9 +1014,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", - "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "cpu": [ "ia32" ], @@ -1028,9 +1028,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", - "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "cpu": [ "x64" ], @@ -2299,9 +2299,9 @@ } }, "node_modules/rollup": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", - "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "license": "MIT", "dependencies": { @@ -2315,22 +2315,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.2", - "@rollup/rollup-android-arm64": "4.21.2", - "@rollup/rollup-darwin-arm64": "4.21.2", - "@rollup/rollup-darwin-x64": "4.21.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", - "@rollup/rollup-linux-arm-musleabihf": "4.21.2", - "@rollup/rollup-linux-arm64-gnu": "4.21.2", - "@rollup/rollup-linux-arm64-musl": "4.21.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", - "@rollup/rollup-linux-riscv64-gnu": "4.21.2", - "@rollup/rollup-linux-s390x-gnu": "4.21.2", - "@rollup/rollup-linux-x64-gnu": "4.21.2", - "@rollup/rollup-linux-x64-musl": "4.21.2", - "@rollup/rollup-win32-arm64-msvc": "4.21.2", - "@rollup/rollup-win32-ia32-msvc": "4.21.2", - "@rollup/rollup-win32-x64-msvc": "4.21.2", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "fsevents": "~2.3.2" } }, @@ -2388,9 +2388,9 @@ } }, "node_modules/solid-js": { - "version": "1.8.22", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.22.tgz", - "integrity": "sha512-VBzN5j+9Y4rqIKEnK301aBk+S7fvFSTs9ljg+YEdFxjNjH0hkjXPiQRcws9tE5fUzMznSS6KToL5hwMfHDgpLA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.1.tgz", + "integrity": "sha512-Gd6QWRFfO2XKKZqVK4YwbhWZkr0jWw1dYHOt+VYebomeyikGP0SuMflf42XcDuU9HAEYDArFJIYsBNjlE7iZsw==", "dev": true, "license": "MIT", "dependencies": { @@ -2470,9 +2470,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", - "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", "dev": true, "license": "MIT", "dependencies": { @@ -2612,9 +2612,9 @@ "dev": true }, "node_modules/vite": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", - "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3211,114 +3211,114 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", - "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", - "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", - "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", - "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", - "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", - "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", - "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", - "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", "dev": true, "optional": true }, "@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", - "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", - "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", - "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", - "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", - "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", - "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", - "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", - "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", "dev": true, "optional": true }, @@ -4205,27 +4205,27 @@ "dev": true }, "rollup": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", - "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", "dev": true, "requires": { - "@rollup/rollup-android-arm-eabi": "4.21.2", - "@rollup/rollup-android-arm64": "4.21.2", - "@rollup/rollup-darwin-arm64": "4.21.2", - "@rollup/rollup-darwin-x64": "4.21.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", - "@rollup/rollup-linux-arm-musleabihf": "4.21.2", - "@rollup/rollup-linux-arm64-gnu": "4.21.2", - "@rollup/rollup-linux-arm64-musl": "4.21.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", - "@rollup/rollup-linux-riscv64-gnu": "4.21.2", - "@rollup/rollup-linux-s390x-gnu": "4.21.2", - "@rollup/rollup-linux-x64-gnu": "4.21.2", - "@rollup/rollup-linux-x64-musl": "4.21.2", - "@rollup/rollup-win32-arm64-msvc": "4.21.2", - "@rollup/rollup-win32-ia32-msvc": "4.21.2", - "@rollup/rollup-win32-x64-msvc": "4.21.2", + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", "@types/estree": "1.0.5", "fsevents": "~2.3.2" } @@ -4259,9 +4259,9 @@ "requires": {} }, "solid-js": { - "version": "1.8.22", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.22.tgz", - "integrity": "sha512-VBzN5j+9Y4rqIKEnK301aBk+S7fvFSTs9ljg+YEdFxjNjH0hkjXPiQRcws9tE5fUzMznSS6KToL5hwMfHDgpLA==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.1.tgz", + "integrity": "sha512-Gd6QWRFfO2XKKZqVK4YwbhWZkr0jWw1dYHOt+VYebomeyikGP0SuMflf42XcDuU9HAEYDArFJIYsBNjlE7iZsw==", "dev": true, "requires": { "csstype": "^3.1.0", @@ -4317,9 +4317,9 @@ "dev": true }, "tailwindcss": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz", - "integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz", + "integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==", "dev": true, "requires": { "@alloc/quick-lru": "^5.2.0", @@ -4414,9 +4414,9 @@ "dev": true }, "vite": { - "version": "5.4.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.7.tgz", - "integrity": "sha512-5l2zxqMEPVENgvzTuBpHer2awaetimj2BGkhBPdnwKbPNOlHsODU+oiazEZzLK7KhAnOrO+XGYJYn4ZlUhDtDQ==", + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "dev": true, "requires": { "esbuild": "^0.21.3", diff --git a/frontend/package.json b/frontend/package.json index 3736ee4..3500092 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,10 +17,10 @@ "autoprefixer": "^10.4.20", "daisyui": "^4.12.10", "postcss": "^8.4.47", - "solid-js": "^1.8.22", - "tailwindcss": "^3.4.12", + "solid-js": "^1.9.1", + "tailwindcss": "^3.4.13", "typescript": "^5.6.2", - "vite": "^5.4.7", + "vite": "^5.4.8", "vite-plugin-solid": "^2.10.2" } } diff --git a/package-lock.json b/package-lock.json index 9321b18..b04def7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.6" + "prettier-plugin-tailwindcss": "^0.6.8" } }, "node_modules/prettier": { @@ -29,9 +29,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.6.tgz", - "integrity": "sha512-OPva5S7WAsPLEsOuOWXATi13QrCKACCiIonFgIR6V4lYv4QLp++UXVhZSzRbZxXGimkQtQT86CC6fQqTOybGng==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz", + "integrity": "sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==", "dev": true, "license": "MIT", "engines": { @@ -116,9 +116,9 @@ "dev": true }, "prettier-plugin-tailwindcss": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.6.tgz", - "integrity": "sha512-OPva5S7WAsPLEsOuOWXATi13QrCKACCiIonFgIR6V4lYv4QLp++UXVhZSzRbZxXGimkQtQT86CC6fQqTOybGng==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz", + "integrity": "sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==", "dev": true, "requires": {} } diff --git a/package.json b/package.json index d9aad32..63131d8 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,6 @@ }, "devDependencies": { "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.6" + "prettier-plugin-tailwindcss": "^0.6.8" } } From b25281ef4164186350551bb5158509c0089d48d4 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Fri, 4 Oct 2024 10:37:37 +0200 Subject: [PATCH 02/69] Fix whitespace in docs --- examples/README.md | 92 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/examples/README.md b/examples/README.md index 7a639a0..bfbb8ff 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,53 +5,53 @@ Template (remove json comments before usage): ```json5 { "passthrough": "", - "mapPool": [ // map pool can contain any number of maps, but must be big enough for the given election steps, see below for more information + "mapPool": [ // map pool can contain any number of maps, but must be big enough for the given election steps, see below for more information "de_ancient", // use the name of the map file - "de_anubis/anubis", // or add a friendly name for players when picking/banning maps - "de_inferno", - "de_mirage", - "de_nuke", - "de_overpass", - "de_vertigo", + "de_anubis/anubis", // or add a friendly name for players when picking/banning maps + "de_inferno", + "de_mirage", + "de_nuke", + "de_overpass", + "de_vertigo", "3070923343/fy_pool_day" // workshop maps consists of numbers only (their workshop id), consider adding a friendly name for players when picking/banning maps ], - "teamA": { - "name": "Alpha", - "passthrough": "", + "teamA": { + "name": "Alpha", + "passthrough": "", "advantage": 0 - }, - "teamB": { - "name": "Bravo", - "passthrough": "", + }, + "teamB": { + "name": "Bravo", + "passthrough": "", "advantage": 0 - }, - "electionSteps": [ + }, + "electionSteps": [ // see below for templates - ], - "gameServer": { // can be `null` instead of object if game servers are managed by TMT itself - "ip": "localhost", - "port": 27016, - "rconPassword": "blob" - }, - "rconCommands": { - "init": [ // these rcon commands will be executed only once: when the match is created + ], + "gameServer": { // can be `null` instead of object if game servers are managed by TMT itself + "ip": "localhost", + "port": 27016, + "rconPassword": "blob" + }, + "rconCommands": { + "init": [ // these rcon commands will be executed only once: when the match is created "game_type 0; game_mode 1; sv_game_mode_flags 0; sv_skirmish_id 0", // normal competitive, see below for other examples - "say > RCON INIT LOADED <" - ], - "knife": [ // these rcon commands will only be executed at the start of a knife round - "mp_give_player_c4 0; mp_startmoney 0; mp_ct_default_secondary \"\"; mp_t_default_secondary \"\"", - "say > SPECIAL KNIFE CONFIG LOADED <" - ], - "match": [ // these rcon commands will be executed at the start of each match map (after knife or when both teams are ready) - "mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary \"weapon_hkp2000\"; mp_t_default_secondary \"weapon_glock\"", + "say > RCON INIT LOADED <" + ], + "knife": [ // these rcon commands will only be executed at the start of a knife round + "mp_give_player_c4 0; mp_startmoney 0; mp_ct_default_secondary \"\"; mp_t_default_secondary \"\"", + "say > SPECIAL KNIFE CONFIG LOADED <" + ], + "match": [ // these rcon commands will be executed at the start of each match map (after knife or when both teams are ready) + "mp_give_player_c4 1; mp_startmoney 800; mp_ct_default_secondary \"weapon_hkp2000\"; mp_t_default_secondary \"weapon_glock\"", "mp_overtime_enable 1", "say > MATCH CONFIG LOADED <" - ], - "end": [ // these rcon commands will be executed only once: after the end of the last map, or when the match has been stopped (by api) + ], + "end": [ // these rcon commands will be executed only once: after the end of the last map, or when the match has been stopped (by api) "say > MATCH END RCON LOADED <" ] - }, - "tmtLogAddress": "http://localhost:8080" // tmt's http address the game server must send the logs to + }, + "tmtLogAddress": "http://localhost:8080" // tmt's http address the game server must send the logs to } ``` @@ -298,16 +298,16 @@ Possible side configurations: // BO3, but max. two maps, because of advantage for one team { "electionSteps": [ - { "map": { "mode": "BAN", "who": "TEAM_A" } }, - { "map": { "mode": "BAN", "who": "TEAM_B" } }, - { - "map": { "mode": "PICK", "who": "TEAM_A" }, - "side": { "mode": "PICK", "who": "TEAM_B" } - }, - { - "map": { "mode": "PICK", "who": "TEAM_B" }, - "side": { "mode": "PICK", "who": "TEAM_A" } - } + { "map": { "mode": "BAN", "who": "TEAM_A" } }, + { "map": { "mode": "BAN", "who": "TEAM_B" } }, + { + "map": { "mode": "PICK", "who": "TEAM_A" }, + "side": { "mode": "PICK", "who": "TEAM_B" } + }, + { + "map": { "mode": "PICK", "who": "TEAM_B" }, + "side": { "mode": "PICK", "who": "TEAM_A" } + } ] } ``` From ad43aed7ef71d9db6ffe423addcc51ea4b8c673d Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Sun, 6 Oct 2024 19:16:49 +0200 Subject: [PATCH 03/69] Fix copy to clipboard not working for match share link --- frontend/src/components/MatchCard.tsx | 5 +++-- frontend/src/utils/copyToClipboard.ts | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/MatchCard.tsx b/frontend/src/components/MatchCard.tsx index 7ee700d..a91c47c 100644 --- a/frontend/src/components/MatchCard.tsx +++ b/frontend/src/components/MatchCard.tsx @@ -27,6 +27,7 @@ export const MatchCard: Component<{ const l = window.location; const shareLink = l.protocol + '//' + l.host + l.pathname + '?secret=' + props.match.tmtSecret; let modalRef: HTMLDialogElement | undefined; + let divRef: HTMLDivElement | undefined; const goToEditPage = () => navigate( @@ -74,13 +75,13 @@ export const MatchCard: Component<{

{t('Copy & share the link below.')}

-
+
-
diff --git a/frontend/src/utils/copyToClipboard.ts b/frontend/src/utils/copyToClipboard.ts index 624bd4b..40227ef 100644 --- a/frontend/src/utils/copyToClipboard.ts +++ b/frontend/src/utils/copyToClipboard.ts @@ -1,5 +1,10 @@ -// from https://stackoverflow.com/a/65996386 -export const copyToClipboard = async (textToCopy: string) => { +/** + * Based on https://stackoverflow.com/a/65996386 + * + * @param textToCopy + * @param node Set this to a HTML Element. Needed if copy button is in a modal. + */ +export const copyToClipboard = async (textToCopy: string, node?: HTMLElement) => { // Navigator clipboard api needs a secure context (https) if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(textToCopy); @@ -12,7 +17,7 @@ export const copyToClipboard = async (textToCopy: string) => { textArea.style.position = 'absolute'; textArea.style.left = '-999999px'; - document.body.prepend(textArea); + (node ?? document.body).prepend(textArea); textArea.select(); try { From 125ae33796a9fa1fe266253de0ef576504503e71 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Sun, 6 Oct 2024 19:44:46 +0200 Subject: [PATCH 04/69] Improve (rcon and log) connections between game server and TMT, especially when game server has crashed/changed. --- backend/src/match.ts | 57 ++++++++++++--------------- backend/src/routes.ts | 2 - backend/swagger.json | 6 --- common/types/match.ts | 2 - frontend/src/components/CardMenu.tsx | 4 +- frontend/src/components/MatchCard.tsx | 32 ++++++++++----- 6 files changed, 50 insertions(+), 53 deletions(-) diff --git a/backend/src/match.ts b/backend/src/match.ts index b2c21fe..6c85802 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -117,7 +117,7 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS }; try { const match = await createFromData(data); - await init(match); + await execRconCommands(match, 'init'); return match; } catch (err) { if (!dto.gameServer) { @@ -156,8 +156,7 @@ const connectToGameServer = async (match: Match): Promise => { match.rconConnection = gameServer; previous?.end().catch(() => {}); match.log(`Connect rcon successful ${addr}`); - await setup(match); - await registerLogAddress(match); + await ensureLogAddressIsRegistered(match); }; const onRconConnectionEnd = async (match: Match) => { @@ -181,15 +180,6 @@ const onRconConnectionEnd = async (match: Match) => { } }; -/** - * Executed only once per match (at creation). - */ -const init = async (match: Match) => { - match.log('Init match...'); - await execRconCommands(match, 'init'); - match.log('Init match finished'); -}; - /** * Executed every time after the connection to the game server is established. */ @@ -232,24 +222,35 @@ export const checkAndNormalizeLogAddress = (url: string): string | null => { } }; -const registerLogAddress = async (match: Match) => { +const ensureLogAddressIsRegistered = async (match: Match) => { const logAddress = `${match.data.tmtLogAddress || TMT_LOG_ADDRESS}/api/matches/${ match.data.id }/server/log/${match.data.logSecret}`; - const logAddressList = await execRcon(match, 'logaddress_list_http'); + + let logAddressList: string; + try { + logAddressList = await GameServer.exec(match, 'logaddress_list_http', false); + } catch (err) { + return; + } + const existing = logAddressList .trim() .split('\n') .map((line) => line.trim()) .find((line) => line.startsWith(logAddress)); - if (!existing) { - match.data.parseIncomingLogs = false; - match.log('Register log address'); - await execRcon(match, `logaddress_add_http "${logAddress}"`); - MatchService.scheduleSave(match); + if (existing) { + return; } + match.data.parseIncomingLogs = false; + match.log('Register log address'); + await execRcon(match, 'logaddress_delall_http'); + await execRcon(match, `logaddress_add_http "${logAddress}"`); + + MatchService.scheduleSave(match); + // delay parsing of incoming log lines (because we don't care about the initial big batch) sleep(2000) .then(async () => { @@ -257,7 +258,8 @@ const registerLogAddress = async (match: Match) => { match.log('Enable parsing of incoming log'); match.data.parseIncomingLogs = true; MatchService.scheduleSave(match); - await say(match, 'ONLINE'); + await say(match, 'TMT IS ONLINE'); + await setup(match); if (match.data.state === 'ELECTION') { await Election.auto(match); } @@ -321,6 +323,8 @@ const periodicJob = async (match: Match) => { resetPeriodicJobTimer(match); match.periodicJobCounter = match.periodicJobCounter + 1; + await ensureLogAddressIsRegistered(match); + const sv_password = await getConfigVar(match, 'sv_password'); if (sv_password && sv_password !== match.data.serverPassword) { match.data.serverPassword = sv_password; @@ -1046,7 +1050,7 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { const previous = match.data.logSecret; match.data.logSecret = dto.logSecret; try { - await registerLogAddress(match); + await ensureLogAddressIsRegistered(match); } catch (err) { match.data.logSecret = previous; throw err; @@ -1066,8 +1070,7 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { ); } match.data.tmtLogAddress = addr; - await execRcon(match, 'logaddress_delall_http'); - await registerLogAddress(match); + await ensureLogAddressIsRegistered(match); } if (dto.currentMap !== undefined && dto.currentMap !== match.data.currentMap) { @@ -1118,14 +1121,6 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { await restartElection(match); } - if (dto._init) { - await init(match); - } - - if (dto._setup) { - await setup(match); - } - if (dto._execRconCommandsInit) { await execRconCommands(match, 'init'); } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 37d8694..9ab4731 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1088,8 +1088,6 @@ const models: TsoaRoute.Models = { logSecret: { dataType: 'string' }, currentMap: { dataType: 'double' }, _restartElection: { dataType: 'boolean' }, - _init: { dataType: 'boolean' }, - _setup: { dataType: 'boolean' }, _execRconCommandsInit: { dataType: 'boolean' }, _execRconCommandsKnife: { dataType: 'boolean' }, _execRconCommandsMatch: { dataType: 'boolean' }, diff --git a/backend/swagger.json b/backend/swagger.json index e9db29a..c936858 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -2048,12 +2048,6 @@ "_restartElection": { "type": "boolean" }, - "_init": { - "type": "boolean" - }, - "_setup": { - "type": "boolean" - }, "_execRconCommandsInit": { "type": "boolean" }, diff --git a/common/types/match.ts b/common/types/match.ts index 5d8c327..5a9db94 100644 --- a/common/types/match.ts +++ b/common/types/match.ts @@ -179,8 +179,6 @@ export interface IMatchUpdateDto extends Partial { currentMap?: number; _restartElection?: boolean; - _init?: boolean; - _setup?: boolean; _execRconCommandsInit?: boolean; _execRconCommandsKnife?: boolean; _execRconCommandsMatch?: boolean; diff --git a/frontend/src/components/CardMenu.tsx b/frontend/src/components/CardMenu.tsx index e08dfc7..81e687f 100644 --- a/frontend/src/components/CardMenu.tsx +++ b/frontend/src/components/CardMenu.tsx @@ -47,9 +47,9 @@ export const CardMenu: Component<{
    diff --git a/frontend/src/components/MatchCard.tsx b/frontend/src/components/MatchCard.tsx index a91c47c..eedc9e9 100644 --- a/frontend/src/components/MatchCard.tsx +++ b/frontend/src/components/MatchCard.tsx @@ -22,8 +22,6 @@ export const MatchCard: Component<{ const stop = () => fetcher('DELETE', `/api/matches/${props.match.id}`).finally(() => location.reload()); const restartElection = () => patchMatch({ _restartElection: true }); - const init = () => patchMatch({ _init: true }); - const setup = () => patchMatch({ _setup: true }); const l = window.location; const shareLink = l.protocol + '//' + l.host + l.pathname + '?secret=' + props.match.tmtSecret; let modalRef: HTMLDialogElement | undefined; @@ -42,16 +40,30 @@ export const MatchCard: Component<{ entries={ props.match.isLive ? [ - [t('stop'), mustConfirm(stop)], - [t('restart election'), mustConfirm(restartElection)], - [t('init'), init], - [t('setup'), setup], - [t('share match with token'), () => modalRef?.showModal()], - [t('edit match'), goToEditPage], + [t('Stop Match'), mustConfirm(stop)], + [t('Restart Match'), mustConfirm(restartElection)], + [ + t('Execute Rcon Init Commands'), + () => patchMatch({ _execRconCommandsInit: true }), + ], + [ + t('Execute Rcon Knife Commands'), + () => patchMatch({ _execRconCommandsKnife: true }), + ], + [ + t('Execute Rcon Match Commands'), + () => patchMatch({ _execRconCommandsMatch: true }), + ], + [ + t('Execute Rcon End Commands'), + () => patchMatch({ _execRconCommandsEnd: true }), + ], + [t('Share Match with Token'), () => modalRef?.showModal()], + [t('Edit Match'), goToEditPage], ] : [ - [t('share match with token'), () => modalRef?.showModal()], - [t('edit match'), goToEditPage], + [t('Share Match with Token'), () => modalRef?.showModal()], + [t('Edit Match'), goToEditPage], ] } /> From 5bf3f8ed1df9d08c5c4817073897fe213b2102ca Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Sun, 6 Oct 2024 19:48:03 +0200 Subject: [PATCH 05/69] Add workaround for CS2 getting stuck after loading round backup --- backend/src/match.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/match.ts b/backend/src/match.ts index 6c85802..d2220e1 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -387,6 +387,7 @@ export const getRoundBackups = async (match: Match, count: number = 5) => { }; export const loadRoundBackup = async (match: Match, file: string) => { + await execRcon(match, 'mp_warmup_end'); // CS2 could be stuck (frozen cam, no timer, no pause indication) if game was currently in warmup await execRcon(match, 'mp_backup_restore_load_autopause 1'); await execRcon(match, 'mp_pause_match'); // mp_backup_restore_load_autopause doesn't work currently in CS2 await execRconCommands(match, 'match'); From a2711a11050783cd500ab4fbaed50841b8f0b975 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Sun, 6 Oct 2024 20:17:25 +0200 Subject: [PATCH 06/69] Add explanation to game server page --- frontend/src/components/Inputs.tsx | 13 ++++++++++++- frontend/src/pages/create.tsx | 1 - frontend/src/pages/gameServers.tsx | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Inputs.tsx b/frontend/src/components/Inputs.tsx index 80e86a2..cae066e 100644 --- a/frontend/src/components/Inputs.tsx +++ b/frontend/src/components/Inputs.tsx @@ -3,10 +3,16 @@ import { Component, ComponentProps, JSXElement, Show, splitProps } from 'solid-j export const ToggleInput: Component< ComponentProps<'input'> & { label?: string; + labelBottomLeft?: string; labelTopRight?: string; } > = (props) => { - const [local, others] = splitProps(props, ['label', 'class', 'labelTopRight']); + const [local, others] = splitProps(props, [ + 'label', + 'class', + 'labelBottomLeft', + 'labelTopRight', + ]); return (
    @@ -16,6 +22,11 @@ export const ToggleInput: Component< + + +
    ); }; diff --git a/frontend/src/pages/create.tsx b/frontend/src/pages/create.tsx index c258463..37ef9a2 100644 --- a/frontend/src/pages/create.tsx +++ b/frontend/src/pages/create.tsx @@ -33,7 +33,6 @@ const DEFAULT_RCON_END = ['say > MATCH END RCON LOADED <']; export const CreatePage: Component = () => { const navigate = useNavigate(); const fetcher = createFetcher(); - const [errorMessage, setErrorMessage] = createSignal(''); return ( diff --git a/frontend/src/pages/gameServers.tsx b/frontend/src/pages/gameServers.tsx index 47e92e4..92ba8dd 100644 --- a/frontend/src/pages/gameServers.tsx +++ b/frontend/src/pages/gameServers.tsx @@ -79,6 +79,21 @@ export const GameServersPage: Component = () => {
    +
    +

    + {t( + 'Game servers managed by TMT can be used to automatically assign them to new matches.' + )}{' '} + {t( + 'Anonymous (not logged in) users can then also use them for their games.' + )} +

    +
    + +
    +

    {t('Add Game Server')}

    +
    + { /> setCanBeUsed(e.currentTarget.checked)} /> From dba9e77a54e767456456e8db5ccfbc3a8af9785c Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Sun, 6 Oct 2024 20:28:29 +0200 Subject: [PATCH 07/69] Bump dependencies --- backend/package-lock.json | 14 +++++++------- backend/package.json | 2 +- frontend/package-lock.json | 29 +++++++++++++++-------------- frontend/package.json | 4 ++-- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 09ac327..244dac3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -20,7 +20,7 @@ "@tsoa/cli": "^6.4.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", - "@types/node": "^22.7.2", + "@types/node": "^22.7.4", "@types/steamid": "^2.0.3", "@types/ws": "^8.5.12", "nodemon": "^3.1.7", @@ -713,9 +713,9 @@ } }, "node_modules/@types/node": { - "version": "22.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.2.tgz", - "integrity": "sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==", + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -3291,9 +3291,9 @@ } }, "@types/node": { - "version": "22.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.2.tgz", - "integrity": "sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==", + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", "requires": { "undici-types": "~6.19.2" } diff --git a/backend/package.json b/backend/package.json index 73bbedd..edd8bd5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,7 +22,7 @@ "@tsoa/cli": "^6.4.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", - "@types/node": "^22.7.2", + "@types/node": "^22.7.4", "@types/steamid": "^2.0.3", "@types/ws": "^8.5.12", "nodemon": "^3.1.7", diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7bd1ca4..3fb71b8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,10 +11,10 @@ "devDependencies": { "@formkit/auto-animate": "^0.8.2", "@solid-primitives/scheduled": "^1.4.3", - "@solidjs/router": "^0.14.5", + "@solidjs/router": "^0.14.7", "@tailwindcss/typography": "^0.5.15", "autoprefixer": "^10.4.20", - "daisyui": "^4.12.10", + "daisyui": "^4.12.12", "postcss": "^8.4.47", "solid-js": "^1.9.1", "tailwindcss": "^3.4.13", @@ -1051,9 +1051,9 @@ } }, "node_modules/@solidjs/router": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.14.5.tgz", - "integrity": "sha512-J8ZMntnkDvNCSO9n4HyhlQlGdxYa1mandQLt5LMd0YiWAXGlYzjj+bB+OVtzsH1woWfoEJlVnBnGkMpf2lY/ig==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.14.7.tgz", + "integrity": "sha512-agLf8AUz5XnW6+F64a4Iq+QQQobI5zGHQ/gUYd/WHSwcbnFpavbsiwRLob3YhWMXVX3sQyn4ekUN+uchMCRupw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1476,10 +1476,11 @@ } }, "node_modules/daisyui": { - "version": "4.12.10", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.10.tgz", - "integrity": "sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==", + "version": "4.12.12", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.12.tgz", + "integrity": "sha512-xmCZ4piuWOjhNyB0VDKczB5vKFCipTA7UxaZNOzCz6cT8kvWgv5BDtUo+Hk9gOFufByOlfuBdzLpfhY5GsebTQ==", "dev": true, + "license": "MIT", "dependencies": { "css-selector-tokenizer": "^0.8", "culori": "^3", @@ -3330,9 +3331,9 @@ "requires": {} }, "@solidjs/router": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.14.5.tgz", - "integrity": "sha512-J8ZMntnkDvNCSO9n4HyhlQlGdxYa1mandQLt5LMd0YiWAXGlYzjj+bB+OVtzsH1woWfoEJlVnBnGkMpf2lY/ig==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.14.7.tgz", + "integrity": "sha512-agLf8AUz5XnW6+F64a4Iq+QQQobI5zGHQ/gUYd/WHSwcbnFpavbsiwRLob3YhWMXVX3sQyn4ekUN+uchMCRupw==", "dev": true, "requires": {} }, @@ -3640,9 +3641,9 @@ "dev": true }, "daisyui": { - "version": "4.12.10", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.10.tgz", - "integrity": "sha512-jp1RAuzbHhGdXmn957Z2XsTZStXGHzFfF0FgIOZj3Wv9sH7OZgLfXTRZNfKVYxltGUOBsG1kbWAdF5SrqjebvA==", + "version": "4.12.12", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.12.tgz", + "integrity": "sha512-xmCZ4piuWOjhNyB0VDKczB5vKFCipTA7UxaZNOzCz6cT8kvWgv5BDtUo+Hk9gOFufByOlfuBdzLpfhY5GsebTQ==", "dev": true, "requires": { "css-selector-tokenizer": "^0.8", diff --git a/frontend/package.json b/frontend/package.json index 3500092..1316f54 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,10 +12,10 @@ "devDependencies": { "@formkit/auto-animate": "^0.8.2", "@solid-primitives/scheduled": "^1.4.3", - "@solidjs/router": "^0.14.5", + "@solidjs/router": "^0.14.7", "@tailwindcss/typography": "^0.5.15", "autoprefixer": "^10.4.20", - "daisyui": "^4.12.10", + "daisyui": "^4.12.12", "postcss": "^8.4.47", "solid-js": "^1.9.1", "tailwindcss": "^3.4.13", From d019727c0ce454059513b51890b52aed76952226 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Sun, 6 Oct 2024 20:40:04 +0200 Subject: [PATCH 08/69] Use node+tini instead of npm in Dockerfile --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f9ce317..01c709f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,9 @@ COPY frontend/vite.config.mts . RUN npm run build FROM node:20 +ENV TINI_VERSION=v0.19.0 +ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini +RUN chmod +x /tini COPY --from=backend_build_image /app/backend/package.json /app/backend/swagger.json /app/backend/ COPY --from=backend_build_image /app/backend/dist /app/backend/dist COPY --from=backend_build_image /app/backend/node_modules /app/backend/node_modules @@ -34,4 +37,4 @@ EXPOSE 8080 ARG COMMIT_SHA ENV COMMIT_SHA=${COMMIT_SHA} WORKDIR /app/backend -CMD ["npm", "start"] +CMD ["/tini", "node", "./dist/backend/src/index.js"] From 2f26bd521e57fe74c2e71441c7cff385a6c0d813 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 18:51:27 +0200 Subject: [PATCH 09/69] Add connection details to game server page --- frontend/src/components/GameServerCard.tsx | 21 +++++++++++++++----- frontend/src/pages/gameServer.tsx | 23 ++++++++++++++++++++-- frontend/src/pages/match.tsx | 4 ++-- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/GameServerCard.tsx b/frontend/src/components/GameServerCard.tsx index 01ede3a..f774b1a 100644 --- a/frontend/src/components/GameServerCard.tsx +++ b/frontend/src/components/GameServerCard.tsx @@ -6,13 +6,13 @@ import { Card } from './Card'; import { copyToClipboard } from '../utils/copyToClipboard'; export const GameServerCard: Component<{ - match: IMatchResponse; + ipPort: string; + serverPassword: string; }> = (props) => { - const ipPort = () => `${props.match.gameServer.ip}:${props.match.gameServer.port}`; - const steamUrl = () => `steam://connect/${ipPort()}?appid=730/${props.match.serverPassword}`; + const steamUrl = () => `steam://connect/${props.ipPort}?appid=730/${props.serverPassword}`; const command = () => - (props.match.serverPassword ? `password "${props.match.serverPassword}"; ` : '') + - `connect ${ipPort()}`; + (props.serverPassword ? `password "${props.serverPassword}"; ` : '') + + `connect ${props.ipPort}`; return ( @@ -30,3 +30,14 @@ export const GameServerCard: Component<{ ); }; + +export const MatchGameServerCard: Component<{ + match: IMatchResponse; +}> = (props) => { + return ( + + ); +}; diff --git a/frontend/src/pages/gameServer.tsx b/frontend/src/pages/gameServer.tsx index 078efe5..8328390 100644 --- a/frontend/src/pages/gameServer.tsx +++ b/frontend/src/pages/gameServer.tsx @@ -1,7 +1,9 @@ import { useParams } from '@solidjs/router'; -import { Component } from 'solid-js'; +import { Component, createSignal, onMount } from 'solid-js'; import { ErrorComponent } from '../components/ErrorComponent'; +import { GameServerCard } from '../components/GameServerCard'; import { RconServer } from '../components/Rcon'; +import { createFetcher } from '../utils/fetcher'; import { t } from '../utils/locale'; export const GameServerPage: Component = () => { @@ -9,10 +11,27 @@ export const GameServerPage: Component = () => { const parts = params.ipPort.split(':', 2); const ip = parts[0]; const port = Number.parseInt(parts[1]); + const [serverPassword, setServerPassword] = createSignal(''); + const fetcher = createFetcher(); + + onMount(() => { + fetcher('POST', `/api/gameservers/${ip}/${port}`, ['sv_password']).then( + (response) => { + const configVarPattern = new RegExp(`^sv_password = (.*)`); + const configVarMatch = response?.[0]?.match(configVarPattern); + if (configVarMatch) { + setServerPassword(configVarMatch[1]); + } + } + ); + }); return Number.isNaN(port) ? ( ) : ( - +
    + + +
    ); }; diff --git a/frontend/src/pages/match.tsx b/frontend/src/pages/match.tsx index 9392b77..c19892b 100644 --- a/frontend/src/pages/match.tsx +++ b/frontend/src/pages/match.tsx @@ -3,7 +3,7 @@ import { Component, createEffect, For, onCleanup, onMount, Show } from 'solid-js import { createStore } from 'solid-js/store'; import { ChatEvent, escapeRconSayString, Event, IMatchResponse, LogEvent } from '../../../common'; import { Chat } from '../components/Chat'; -import { GameServerCard } from '../components/GameServerCard'; +import { MatchGameServerCard } from '../components/GameServerCard'; import { Loader } from '../components/Loader'; import { LogViewer } from '../components/LogViewer'; import { MatchCard } from '../components/MatchCard'; @@ -90,7 +90,7 @@ export const MatchPage: Component = () => { {(map, i) => } - + From 6d2010d969757fa381445670cf6448dca3866dca Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 19:28:19 +0200 Subject: [PATCH 10/69] Make presets usable for logged in or all users (configurable per preset) --- backend/src/presets.ts | 2 + backend/src/presetsController.ts | 3 +- backend/src/routes.ts | 4 +- common/types/preset.ts | 1 + frontend/src/components/CreateUpdateMatch.tsx | 102 ++++++++++-------- 5 files changed, 68 insertions(+), 44 deletions(-) diff --git a/backend/src/presets.ts b/backend/src/presets.ts index b91db54..a2c4894 100644 --- a/backend/src/presets.ts +++ b/backend/src/presets.ts @@ -7,6 +7,7 @@ const DEFAULT_PRESETS: IPreset[] = [ { id: 'rUqWHsdPuA1pCqEfseVpRn', name: '5on5 Competitive', + isPublic: true, data: { teamA: { name: 'Team A', @@ -96,6 +97,7 @@ const DEFAULT_PRESETS: IPreset[] = [ { id: '5yNVDnvcFHzVRaMnpjLYMv', name: '2on2 Wingman', + isPublic: true, data: { teamA: { name: 'Team A', diff --git a/backend/src/presetsController.ts b/backend/src/presetsController.ts index fb952de..15040b2 100644 --- a/backend/src/presetsController.ts +++ b/backend/src/presetsController.ts @@ -21,8 +21,9 @@ export class PresetsController extends Controller { * Get all configured presets. */ @Get() + @Security('bearer_token_optional') async getPresets(@Request() req: ExpressRequest): Promise { - return Presets.getAll(); + return Presets.getAll().filter((preset) => preset.isPublic || req.user.type === 'GLOBAL'); } /** diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 9ab4731..fa8c560 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1219,6 +1219,7 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { name: { dataType: 'string', required: true }, + isPublic: { dataType: 'boolean' }, data: { ref: 'IMatchCreateDto', required: true }, id: { dataType: 'string', required: true }, }, @@ -1229,6 +1230,7 @@ const models: TsoaRoute.Models = { dataType: 'refObject', properties: { name: { dataType: 'string', required: true }, + isPublic: { dataType: 'boolean' }, data: { ref: 'IMatchCreateDto', required: true }, }, additionalProperties: false, @@ -2119,7 +2121,7 @@ export function RegisterRoutes(app: Router) { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa app.get( '/api/presets', - authenticateMiddleware([{ bearer_token: [] }]), + authenticateMiddleware([{ bearer_token_optional: [] }]), ...fetchMiddlewares(PresetsController), ...fetchMiddlewares(PresetsController.prototype.getPresets), diff --git a/common/types/preset.ts b/common/types/preset.ts index e01023e..b4d6240 100644 --- a/common/types/preset.ts +++ b/common/types/preset.ts @@ -2,6 +2,7 @@ import { IMatchCreateDto } from './match'; export interface IPresetCreateDto { name: string; + isPublic?: boolean; data: IMatchCreateDto; } diff --git a/frontend/src/components/CreateUpdateMatch.tsx b/frontend/src/components/CreateUpdateMatch.tsx index a36500f..dd28ef4 100644 --- a/frontend/src/components/CreateUpdateMatch.tsx +++ b/frontend/src/components/CreateUpdateMatch.tsx @@ -38,13 +38,10 @@ const Presets: Component<{ const fetcher = createFetcher(); const [presets, setPresets] = createSignal([]); const [presetName, setPresetName] = createSignal(''); + const [presetIsPublic, setPresetIsPublic] = createSignal(true); const [selectedPresetId, setSelectedPresetId] = createSignal(''); - createEffect(() => { - if (isLoggedIn()) { - refreshPresets(); - } - }); + onMount(() => refreshPresets()); const refreshPresets = () => { fetcher('GET', `/api/presets`).then((presets) => { @@ -54,11 +51,12 @@ const Presets: Component<{ }); }; const addPreset = () => { - if (!presetName()) { + if (!presetName() || !isLoggedIn()) { return; } const presetCreateDto: IPresetCreateDto = { name: presetName(), + isPublic: presetIsPublic(), data: props.matchCreateDto, }; fetcher('POST', `/api/presets`, presetCreateDto).then((preset) => { @@ -66,16 +64,18 @@ const Presets: Component<{ setPresets((presets) => [...presets, preset]); setSelectedPresetId(preset.id); setPresetName(preset.name); + setPresetIsPublic(preset.isPublic ?? false); } }); }; const updatePreset = () => { - if (!selectedPresetId() || !presetName()) { + if (!selectedPresetId() || !presetName() || !isLoggedIn()) { return; } const updatePresetDto: IPreset = { id: selectedPresetId(), name: presetName(), + isPublic: presetIsPublic(), data: props.matchCreateDto, }; fetcher('PUT', `/api/presets`, updatePresetDto).then(() => { @@ -88,12 +88,13 @@ const Presets: Component<{ }; const deletePreset = () => { const presetIdToDelete = selectedPresetId(); - if (!presetIdToDelete) { + if (!presetIdToDelete || !isLoggedIn()) { return; } fetcher('DELETE', `/api/presets/${presetIdToDelete}`).then(() => { setPresets((presets) => presets.filter((preset) => preset.id !== presetIdToDelete)); setSelectedPresetId(''); + setPresetIsPublic(true); setPresetName(''); }); }; @@ -108,16 +109,18 @@ const Presets: Component<{ const getPresetById = (presetId: string) => { return presets().find((preset) => preset.id === presetId); }; + return ( <>
    { setSelectedPresetId(e.currentTarget.value); - setPresetName(getPresetById(e.currentTarget.value)?.name ?? ''); + const preset = getPresetById(e.currentTarget.value); + setPresetName(preset?.name ?? ''); + setPresetIsPublic(preset?.isPublic ?? false); setMatchDataFromPreset(e.currentTarget.value); }} disabled={presets().length === 0} @@ -135,40 +138,55 @@ const Presets: Component<{
    - + + +
    -
    -
    - setPresetName(e.currentTarget.value)} - /> + +
    +
    + setPresetName(e.currentTarget.value)} + /> +
    + setPresetIsPublic(e.currentTarget.value === 'true')} + > + + + + +
    - - -
    + ); }; From 8d5ddaeeb0c3f5dc3f3b34de9d75094e2ed5eae4 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 19:33:09 +0200 Subject: [PATCH 11/69] Update 2on2 wingman default preset (active duty map pool & election steps) --- backend/src/presets.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/backend/src/presets.ts b/backend/src/presets.ts index a2c4894..6059532 100644 --- a/backend/src/presets.ts +++ b/backend/src/presets.ts @@ -108,14 +108,7 @@ const DEFAULT_PRESETS: IPreset[] = [ advantage: 0, }, gameServer: null, - mapPool: [ - 'de_assembly', - 'de_inferno', - 'de_memento', - 'de_nuke', - 'de_overpass', - 'de_vertigo', - ], + mapPool: ['de_inferno', 'de_nuke', 'de_vertigo'], electionSteps: [ { map: { @@ -129,19 +122,12 @@ const DEFAULT_PRESETS: IPreset[] = [ who: 'TEAM_B', }, }, - { - map: { - mode: 'BAN', - who: 'TEAM_A', - }, - }, { map: { mode: 'RANDOM_PICK', }, side: { - mode: 'PICK', - who: 'TEAM_B', + mode: 'KNIFE', }, }, ], From b6e2ea72a938c96c62d5176f0a76b22de477569f Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 20:46:58 +0200 Subject: [PATCH 12/69] Remove unused code --- frontend/src/utils/sleep.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 frontend/src/utils/sleep.ts diff --git a/frontend/src/utils/sleep.ts b/frontend/src/utils/sleep.ts deleted file mode 100644 index 0970467..0000000 --- a/frontend/src/utils/sleep.ts +++ /dev/null @@ -1 +0,0 @@ -export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); From a869323f983d11a63ce63f74f08dfe0a1e76f2ef Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 20:55:33 +0200 Subject: [PATCH 13/69] Fix issue that matches are not supervised after restarting TMT --- backend/src/match.ts | 23 ++++++++++++----------- backend/src/matchService.ts | 17 ++++++++++------- backend/src/matchesController.ts | 2 +- backend/src/settings.ts | 8 +++++++- backend/src/storage.ts | 6 +++--- backend/src/webSocket.ts | 4 ++-- backend/swagger.json | 8 +++++++- frontend/src/pages/matches.tsx | 4 ++-- frontend/src/utils/fetcher.ts | 15 +++++++++------ frontend/src/utils/locale.ts | 4 ++-- 10 files changed, 55 insertions(+), 36 deletions(-) diff --git a/backend/src/match.ts b/backend/src/match.ts index d2220e1..2158732 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -45,7 +45,7 @@ export interface Match { export class GameServerInUseError extends Error {} -export const createFromData = async (data: IMatch) => { +export const createFromData = async (data: IMatch, logMessage?: string) => { const match: Match = { data: data, periodicJobCounter: 0, @@ -55,6 +55,9 @@ export const createFromData = async (data: IMatch) => { }; match.data = addChangeListener(data, createOnDataChangeHandler(match)); match.log = createLogger(match); + if (logMessage) { + match.log(logMessage); + } // http log address checks if (match.data.tmtLogAddress) { @@ -116,7 +119,7 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS mode: dto.mode ?? 'SINGLE', }; try { - const match = await createFromData(data); + const match = await createFromData(data, 'Create new match'); await execRconCommands(match, 'init'); return match; } catch (err) { @@ -157,6 +160,7 @@ const connectToGameServer = async (match: Match): Promise => { previous?.end().catch(() => {}); match.log(`Connect rcon successful ${addr}`); await ensureLogAddressIsRegistered(match); + resetPeriodicJobTimer(match); }; const onRconConnectionEnd = async (match: Match) => { @@ -240,15 +244,13 @@ const ensureLogAddressIsRegistered = async (match: Match) => { .map((line) => line.trim()) .find((line) => line.startsWith(logAddress)); - if (existing) { - return; + if (!existing) { + match.data.parseIncomingLogs = false; + match.log('Register log address'); + await execRcon(match, 'logaddress_delall_http'); + await execRcon(match, `logaddress_add_http "${logAddress}"`); } - match.data.parseIncomingLogs = false; - match.log('Register log address'); - await execRcon(match, 'logaddress_delall_http'); - await execRcon(match, `logaddress_add_http "${logAddress}"`); - MatchService.scheduleSave(match); // delay parsing of incoming log lines (because we don't care about the initial big batch) @@ -263,7 +265,6 @@ const ensureLogAddressIsRegistered = async (match: Match) => { if (match.data.state === 'ELECTION') { await Election.auto(match); } - await periodicJob(match); } }) .catch((err) => { @@ -316,7 +317,7 @@ export const resetPeriodicJobTimer = (match: Match) => { } catch (err) { match.log(`Error in periodicJob: ${err}`); } - }, Settings.PERIODIC_MESSAGE_FREQUENCY); + }, Settings.PERIODIC_JOB_FREQUENCY); }; const periodicJob = async (match: Match) => { diff --git a/backend/src/matchService.ts b/backend/src/matchService.ts index 1d37269..f2c1b62 100644 --- a/backend/src/matchService.ts +++ b/backend/src/matchService.ts @@ -31,9 +31,9 @@ export const setup = async () => { const matchData = matchesFromStorage[i]!; if (matchData.state !== 'FINISHED' && !matchData.isStopped) { try { - await loadMatchFromStorage(matchData); + await loadMatchFromStorage(matchData, '(TMT restarted)'); } catch (err) { - console.error(`error creating match ${matchData.id} from storage: ${err}`); + console.error(`Error creating match ${matchData.id} from storage: ${err}`); if (err instanceof Match.GameServerInUseError) { matchData.isStopped = true; await save(matchData); @@ -45,12 +45,15 @@ export const setup = async () => { periodicSaver(); }; -const loadMatchFromStorage = async (matchData: IMatch) => { +const loadMatchFromStorage = async (matchData: IMatch, logMessageSuffix?: string) => { try { - console.info(`load match ${matchData.id} from storage`); + console.info(`Load match ${matchData.id} from storage`); startingMatches.add(matchData.id); matchData.parseIncomingLogs = false; - const match = await Match.createFromData(matchData); + const match = await Match.createFromData( + matchData, + `Load match from storage ${logMessageSuffix ?? ''}`.trim() + ); matches.set(match.data.id, match); await save(match.data); } finally { @@ -80,7 +83,7 @@ export const create = async (dto: IMatchCreateDto, isLoggedIn: boolean) => { Events.onMatchCreate(match); return match; } catch (err) { - console.error(`error creating new match: ${err}`); + console.error(`Error creating new match: ${err}`); throw err; } finally { startingMatches.delete(id); @@ -183,7 +186,7 @@ export const revive = async (id: string) => { return false; } matchFromStorage.isStopped = false; - await loadMatchFromStorage(matchFromStorage); + await loadMatchFromStorage(matchFromStorage, '(revive)'); return true; }; diff --git a/backend/src/matchesController.ts b/backend/src/matchesController.ts index 26968b9..853a28d 100644 --- a/backend/src/matchesController.ts +++ b/backend/src/matchesController.ts @@ -272,7 +272,7 @@ export class MatchesController extends Controller { this.setStatus(200); } else { // 410 tells the cs2 server to stop send logs - console.info(`return 410 to game server (match id: ${id})`); + console.info(`Return 410 to game server (match id: ${id})`); this.setStatus(410); } } diff --git a/backend/src/settings.ts b/backend/src/settings.ts index e6f6e9f..0c85aab 100644 --- a/backend/src/settings.ts +++ b/backend/src/settings.ts @@ -2,8 +2,14 @@ import { EventType } from '../../common'; export const Settings = { COMMAND_PREFIXES: ['.', '!'], - PERIODIC_MESSAGE_FREQUENCY: 30000, + /** + * Unit: ms + */ + PERIODIC_JOB_FREQUENCY: 30000, SAY_PREFIX: process.env['TMT_SAY_PREFIX'] ?? '[TMT] ', + /** + * Unit: ms + */ MATCH_END_ACTION_DELAY: 60000, WEBHOOK_EVENTS: [ 'CHAT', diff --git a/backend/src/storage.ts b/backend/src/storage.ts index b9f2cf8..6bebb23 100644 --- a/backend/src/storage.ts +++ b/backend/src/storage.ts @@ -27,7 +27,7 @@ export const read: TRead = async (fileName: string, fallback?: T) => { const content = await fsp.readFile(fullPath, { encoding: 'utf-8' }); return JSON.parse(content); } catch (err) { - console.warn(`error storage read ${fileName}: ${err}. Use fallback.`); + console.warn(`Error storage read ${fileName}: ${err}. Use fallback.`); return fallback; } }; @@ -36,7 +36,7 @@ export const appendLine = async (fileName: string, content: any) => { try { await fsp.appendFile(path.join(STORAGE_FOLDER, fileName), JSON.stringify(content) + '\n'); } catch (err) { - console.warn(`error storage appendLine ${fileName}: ${err}`); + console.warn(`Error storage appendLine ${fileName}: ${err}`); } }; @@ -57,7 +57,7 @@ export const readLines = async ( .map((line) => JSON.parse(line)) .slice(-(numberLastOfLines ?? 0)); } catch (err) { - console.warn(`error storage readLines ${fileName}: ${err}. Use fallback.`); + console.warn(`Error storage readLines ${fileName}: ${err}. Use fallback.`); return fallback; } }; diff --git a/backend/src/webSocket.ts b/backend/src/webSocket.ts index 4de7b0c..d229beb 100644 --- a/backend/src/webSocket.ts +++ b/backend/src/webSocket.ts @@ -68,7 +68,7 @@ const subscribe = async (ws: WebSocket, msg: SubscribeMessage | SubscribeSysMess const matchId = msg.type === 'SUBSCRIBE' ? msg.matchId : undefined; const authResponse = await Auth.isAuthorized(msg.token, matchId); if (!authResponse) { - console.warn(`prevent subscribing: not authorized, payload: ${JSON.stringify(msg)}`); + console.warn(`Prevent subscribing: not authorized, payload: ${JSON.stringify(msg)}`); return; } const wsData = WS_CLIENTS.get(ws); @@ -80,7 +80,7 @@ const subscribe = async (ws: WebSocket, msg: SubscribeMessage | SubscribeSysMess } } } catch (err) { - console.error(`subscribe error: ${err}`); + console.error(`Subscribe error: ${err}`); } }; diff --git a/backend/swagger.json b/backend/swagger.json index c936858..c9b14d5 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -2314,6 +2314,9 @@ "name": { "type": "string" }, + "isPublic": { + "type": "boolean" + }, "data": { "$ref": "#/components/schemas/IMatchCreateDto" }, @@ -2334,6 +2337,9 @@ "name": { "type": "string" }, + "isPublic": { + "type": "boolean" + }, "data": { "$ref": "#/components/schemas/IMatchCreateDto" } @@ -3223,7 +3229,7 @@ "description": "Get all configured presets.", "security": [ { - "bearer_token": [] + "bearer_token_optional": [] } ], "parameters": [] diff --git a/frontend/src/pages/matches.tsx b/frontend/src/pages/matches.tsx index fae2b9e..bef1211 100644 --- a/frontend/src/pages/matches.tsx +++ b/frontend/src/pages/matches.tsx @@ -152,14 +152,14 @@ export const MatchesPage: Component = () => { // unsubscribe subs.forEach((matchId) => { if (!data.matches?.find((match) => match.id === matchId)) { - console.info(`unsub from ${matchId}`); + console.info(`Unsub from ${matchId}`); unsubscribe(matchId); } }); // subscribe data.matches?.forEach((match) => { if (!subs.includes(match.id)) { - console.info(`sub to ${match.id}`); + console.info(`Sub to ${match.id}`); subscribe({ matchId: match.id, token: match.tmtSecret, diff --git a/frontend/src/utils/fetcher.ts b/frontend/src/utils/fetcher.ts index 935f48f..bb9195a 100644 --- a/frontend/src/utils/fetcher.ts +++ b/frontend/src/utils/fetcher.ts @@ -74,14 +74,17 @@ export const createFetcher = (token?: string) => { ) { const errRespObj = await response.json(); if (errRespObj.name === 'ValidateError' && errRespObj.fields) { - let errString = [] as string[]; + const errorStrings = [] as string[]; Object.entries(errRespObj.fields as Record).forEach( - ([key, { message }]) => errString.push(key + ': ' + message) + ([key, { message }]) => errorStrings.push(key + ': ' + message) + ); + const errorString = errorStrings.join(', '); + console.error('Fetcher error:', errorString); + throw ( + (errRespObj.fields.length === 1 ? t('Error') : t('Errors')) + + ': ' + + errorStrings ); - console.log(errString); - throw errRespObj.fields.length === 1 - ? t('Error') + ': ' - : t('Errors') + ': ' + errString.join(', '); } else { throw JSON.stringify(errRespObj); } diff --git a/frontend/src/utils/locale.ts b/frontend/src/utils/locale.ts index 09719dd..f7efaad 100644 --- a/frontend/src/utils/locale.ts +++ b/frontend/src/utils/locale.ts @@ -6,7 +6,7 @@ export const t = (key: string) => { const l = getCurrentLocale() as { [key: string]: string }; const value = l[key]; if (!value) { - console.debug(`no entry for "${key}" in locale "${currentLocale()}"`); + console.debug(`No entry for "${key}" in locale "${currentLocale()}"`); } return value ?? key; }; @@ -45,7 +45,7 @@ const getCurrentLocale = () => { } else if (cl === 'en') { return en; } else { - console.warn(`locale ${cl} is not available, use "en" instead`); + console.warn(`Locale ${cl} is not available, use "en" instead`); return en; } }; From a603e14ae3ccdf8d81130648f5fd60aa739dcee5 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 21:30:47 +0200 Subject: [PATCH 14/69] Fix redirect from edit match page back to match page --- frontend/src/pages/matchEdit.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/matchEdit.tsx b/frontend/src/pages/matchEdit.tsx index 0b93edb..7fd1c6e 100644 --- a/frontend/src/pages/matchEdit.tsx +++ b/frontend/src/pages/matchEdit.tsx @@ -69,9 +69,12 @@ export const MatchEditPage: Component = () => { }); }); + const matchLink = () => + `/matches/${params.id}` + (searchParams.secret ? `?secret=${searchParams.secret}` : ''); + return ( <> - + {t('Back to the Match')} @@ -92,7 +95,7 @@ export const MatchEditPage: Component = () => { `/api/matches/${params.id}`, getUpdateDto(match(), dto) ); - navigate(`/matches/${params.id}`); + navigate(matchLink()); }} getFinalDto={(dto) => JSON.stringify(getUpdateDto(match(), dto), undefined, 4) From 21ca728027f830e1b1df81021a7586a910b393fe Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 21:53:29 +0200 Subject: [PATCH 15/69] Improve usability for anonymous users --- backend/src/events.ts | 2 +- backend/src/match.ts | 3 ++- backend/src/matchService.ts | 23 +++++++----------- backend/src/matchesController.ts | 41 ++++++++++++++++++++++++++------ 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/backend/src/events.ts b/backend/src/events.ts index ac7fd52..803b280 100644 --- a/backend/src/events.ts +++ b/backend/src/events.ts @@ -234,7 +234,7 @@ export const onMatchCreate = (match: Match.Match) => { const data: MatchCreateEvent = { ...getBaseEvent(match, 'MATCH_CREATE'), match: { - ...MatchService.hideRconPassword(match.data), + ...MatchService.hideRconPassword(match.data, false), isLive: true, }, }; diff --git a/backend/src/match.ts b/backend/src/match.ts index 2158732..88a700d 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -327,7 +327,8 @@ const periodicJob = async (match: Match) => { await ensureLogAddressIsRegistered(match); const sv_password = await getConfigVar(match, 'sv_password'); - if (sv_password && sv_password !== match.data.serverPassword) { + if (sv_password !== match.data.serverPassword) { + match.log('Server password updated'); match.data.serverPassword = sv_password; } diff --git a/backend/src/matchService.ts b/backend/src/matchService.ts index f2c1b62..1c45d17 100644 --- a/backend/src/matchService.ts +++ b/backend/src/matchService.ts @@ -61,20 +61,9 @@ const loadMatchFromStorage = async (matchData: IMatch, logMessageSuffix?: string } }; -export const create = async (dto: IMatchCreateDto, isLoggedIn: boolean) => { +export const create = async (dto: IMatchCreateDto) => { const id = shortUuid(); try { - if ( - isLoggedIn === false && - [ - ...(dto.rconCommands?.init ?? []), - ...(dto.rconCommands?.knife ?? []), - ...(dto.rconCommands?.match ?? []), - ...(dto.rconCommands?.end ?? []), - ].find((cmd) => cmd.toLowerCase().includes('password')) - ) { - throw 'not allowed to set passwords (text "password" found in rcon commands)'; - } const logSecret = shortUuid(); startingMatches.add(id); const match = await Match.createFromCreateDto(dto, id, logSecret); @@ -212,12 +201,18 @@ export const isStartingMatch = (id: string) => { return startingMatches.has(id); }; -export const hideRconPassword = (match: T): T => { +export const hideRconPassword = ( + match: T, + isLoggedIn: boolean +): T => { return { ...match, gameServer: { ...match.gameServer, - rconPassword: match.gameServer.hideRconPassword ? '' : match.gameServer.rconPassword, + rconPassword: + match.gameServer.hideRconPassword && !isLoggedIn + ? '' + : match.gameServer.rconPassword, }, }; }; diff --git a/backend/src/matchesController.ts b/backend/src/matchesController.ts index 853a28d..f05945f 100644 --- a/backend/src/matchesController.ts +++ b/backend/src/matchesController.ts @@ -26,6 +26,26 @@ import * as Match from './match'; import * as MatchMap from './matchMap'; import * as MatchService from './matchService'; +const checkRconCommands = ( + rconCommands: IMatchCreateDto['rconCommands'] | IMatchUpdateDto['rconCommands'] | string[], + isLoggedIn: boolean +) => { + if (isLoggedIn) { + return; + } + const allRconCommands = Array.isArray(rconCommands) + ? rconCommands + : [ + ...(rconCommands?.init ?? []), + ...(rconCommands?.knife ?? []), + ...(rconCommands?.match ?? []), + ...(rconCommands?.end ?? []), + ]; + if (allRconCommands.find((cmd) => cmd.toLowerCase().includes('rcon_password'))) { + throw 'not allowed to set rcon_password (text "rcon_password" found in rcon commands)'; + } +}; + @Route('/api/matches') @Security('bearer_token') export class MatchesController extends Controller { @@ -39,7 +59,10 @@ export class MatchesController extends Controller { @Body() requestBody: IMatchCreateDto, @Request() req: ExpressRequest ): Promise { - const match = await MatchService.create(requestBody, req.user.type === 'GLOBAL'); + if (requestBody.gameServer === null) { + checkRconCommands(requestBody.rconCommands, req.user.type === 'GLOBAL'); + } + const match = await MatchService.create(requestBody); this.setHeader('Location', `/api/matches/${match.data.id}`); this.setStatus(201); return match.data; @@ -73,7 +96,7 @@ export class MatchesController extends Controller { ) .filter((m) => isStopped === undefined || m.isStopped === isStopped) .filter((m) => isLive === undefined || m.isLive === isLive) - .map((m) => MatchService.hideRconPassword(m)); + .map((m) => MatchService.hideRconPassword(m, req.user.type === 'GLOBAL')); } /** @@ -87,7 +110,7 @@ export class MatchesController extends Controller { const match = MatchService.get(id); if (match) { return { - ...MatchService.hideRconPassword(match.data), + ...MatchService.hideRconPassword(match.data, req.user.type === 'GLOBAL'), isLive: true, }; } @@ -95,7 +118,7 @@ export class MatchesController extends Controller { const matchFromStorage = await MatchService.getFromStorage(id); if (matchFromStorage) { return { - ...MatchService.hideRconPassword(matchFromStorage), + ...MatchService.hideRconPassword(matchFromStorage, req.user.type === 'GLOBAL'), isLive: false, }; } @@ -174,12 +197,17 @@ export class MatchesController extends Controller { ): Promise { const match = MatchService.get(id); if (match) { + if (match.data.gameServer.hideRconPassword) { + checkRconCommands(requestBody.rconCommands, req.user.type === 'GLOBAL'); + } await Match.update(match, requestBody); } else if (requestBody.gameServer) { // for offline matches only allow to update game server to get match running again const offlineMatch = await MatchService.getFromStorage(id); if (offlineMatch) { + const hideRconPassword = offlineMatch.gameServer.hideRconPassword; offlineMatch.gameServer = requestBody.gameServer; + offlineMatch.gameServer.hideRconPassword = hideRconPassword; await MatchService.save(offlineMatch); } } else { @@ -246,9 +274,8 @@ export class MatchesController extends Controller { this.setStatus(404); return; } - if (match.data.gameServer.hideRconPassword && req.user.type === 'MATCH') { - this.setStatus(400); - throw 'cannot execute rcon commands on this server'; + if (match.data.gameServer.hideRconPassword) { + checkRconCommands(requestBody, req.user.type === 'GLOBAL'); } return await Match.execManyRcon(match, requestBody); } From fcda56307cd76bbfbf34122e3f89f71a86f9999c Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 22:28:48 +0200 Subject: [PATCH 16/69] Improve dev container --- README.md | 91 +++++++++++++++++++++++++++++------------ dev-container-init.ps1 | 3 -- dev-container-init.sh | 5 --- dev-container-start.ps1 | 6 --- dev-container-start.sh | 8 ---- dev-container.ps1 | 6 +++ dev-container.sh | 8 ++++ package.json | 2 + 8 files changed, 80 insertions(+), 49 deletions(-) delete mode 100644 dev-container-init.ps1 delete mode 100644 dev-container-init.sh delete mode 100644 dev-container-start.ps1 delete mode 100644 dev-container-start.sh create mode 100644 dev-container.ps1 create mode 100755 dev-container.sh diff --git a/README.md b/README.md index c62d316..31e9daa 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,8 @@ Table of Contents: - [match specific access tokens](#match-specific-access-tokens) - [Development](#development) - [Docker](#docker) - - [NodeJS](#nodejs) + - [Install dependencies and run application in development mode](#install-dependencies-and-run-application-in-development-mode) + - [Build docker image](#build-docker-image) ## Getting Started @@ -65,7 +66,9 @@ Run it with: docker run --name tmt2 -d -p 8080:8080 jensforstmann/tmt2 ``` -Data will be written within the container to `/app/backend/storage`. To keep the files with different containers you can either specify a docker volume or a path on the local system: +Data will be written within the container to `/app/backend/storage`. +To keep the files with different containers +you can either specify a docker volume or a path on the local system: ```sh # docker volume @@ -79,7 +82,8 @@ The matches which are neither finished nor stopped will be loaded on application ### Create you first Match -After running the container you can open the web frontend: http://localhost:8080 (or at whatever ip/server your docker container runs on.) +After running the container you can open the web frontend: http://localhost:8080 +(or at whatever ip/server your docker container runs on.) @@ -88,14 +92,19 @@ _Example screenshot: Create a new match from the web frontend (both dark and lig -Even without an admin token you can create and manage matches (but only your own ones). If you want to know your admin token (a random one is generated at startup) either take a look at the `access_tokens.json` file or take a look at the first lines of the log output (`docker logs tmt2`). +Even without an admin token you can create and manage matches (but only your own ones). +If you want to know your admin token (a random one is generated at startup) +either take a look at the `access_tokens.json` file +or take a look at the first lines of the log output (`docker logs tmt2`). ### Ingame Chat Commands While TMT watches a match the player ingame can use chat commands to communicate with TMT: -- `.team a` or `.team b` - you need to choose a team before you can execute any other commands, check the response in the chat to be sure you've joined the right one, also check the scoreboard (team names are visible there) if you're on the right side (CT/T) +- `.team a` or `.team b` - you need to choose a team before you can execute any other commands, + check the response in the chat to be sure you've joined the right one, + also check the scoreboard (team names are visible there) if you're on the right side (CT/T) - during the map election process: - `.ban` - ban a map from the map pool - `.pick` - pick a map to be played @@ -113,7 +122,8 @@ While TMT watches a match the player ingame can use chat commands to communicate - during the match - `.pause` - pause the match at the next freezetime (alias `.tech`) - `.unpause` - set your team as ready (alias `.ready` & `.rdy`) - - `.tac` - like pause, but uses up a tactical timeout for that team (same as calling an ingame vote for a tactical timeout) + - `.tac` - like pause, but uses up a tactical timeout for that team + (same as calling an ingame vote for a tactical timeout) @@ -139,7 +149,8 @@ TMT_SAY_PREFIX="[TMT] " ## API -See [`backend/swagger.json`](backend/swagger.json). You might want to copy its content and paste it into https://editor.swagger.io/. +See [`backend/swagger.json`](backend/swagger.json). +You might want to copy its content and paste it into https://editor.swagger.io/. See also the [`examples`](examples) folder. @@ -201,41 +212,67 @@ After starting the dev processes you can reach the backend & frontend at: ## Docker -Docker is recommended as it's easy to use and doesn't require any other software to be installed (if docker is already set up). +Docker is recommended as it's easy to use and doesn't require any other software to be installed +(if docker is already set up). -> Note for windows user: It's recommended to have docker installed **directly within** WSL (not using Windows Docker from WSL) or to run a Linux VM. +> *Note for windows user:* +> +> For better performance it's recommended to have docker (and this repo) +> installed **directly within** WSL (not using Windows Docker from WSL) or to run a Linux VM. -Init the dev environment: - - ./dev-container-init.sh - -Start a docker container with port forwarding and hot reloading: - - ./dev-container-start.sh +```shell +# Linux: +./dev-container.sh +``` +```powershell +# Windows +.\dev-container.ps1 +``` -## NodeJS -If you don't want to use docker or want to use NodeJS directly, you can do the following to setup a dev environment: +## Install dependencies and run application in development mode Install dependencies: - npm install - cd backend - npm install - cd ../frontend - npm install +```sh +npm install +cd backend +npm install +cd ../frontend +npm install +``` Run backend with hot reloading: - cd backend - npm run dev +```sh +cd backend +npm run dev +``` Run frontend with hot reloading: - cd frontend - npm run dev +```sh +cd frontend +npm run dev +``` + +Run both backend and frontend with hot reloading: + +```sh +npm run dev +``` + + + +## Build docker image + +To build a docker image locally: + +```sh +docker build -t tmt2 . +``` diff --git a/dev-container-init.ps1 b/dev-container-init.ps1 deleted file mode 100644 index 299ade2..0000000 --- a/dev-container-init.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -Set-Location $PSScriptRoot - -docker run --rm -it -v .:/app node:20 sh -c "cd /app && npm install && cd /app/backend && npm install && cd /app/frontend && npm install && mkdir -p /app/frontend/dist" diff --git a/dev-container-init.sh b/dev-container-init.sh deleted file mode 100644 index bc187ee..0000000 --- a/dev-container-init.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -cd "$(dirname "$0")" - -docker run --rm -it -v .:/app node:20 sh -c "cd /app && npm install && cd /app/backend && npm install && cd /app/frontend && npm install && mkdir -p /app/frontend/dist" diff --git a/dev-container-start.ps1 b/dev-container-start.ps1 deleted file mode 100644 index 53f83fe..0000000 --- a/dev-container-start.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -Set-Location $PSScriptRoot - -$TMT_PORT_BACKEND = $env:TMT_PORT_BACKEND ?? 8080 -$TMT_PORT_FRONTEND = $env:TMT_PORT_FRONTEND ?? 5173 - -docker run --rm -it -v .:/app -p ${TMT_PORT_BACKEND}:8080 -p ${TMT_PORT_FRONTEND}:5173 node:20 sh -c "cd /app/frontend && npm run dev & cd /app/backend && npm run dev" diff --git a/dev-container-start.sh b/dev-container-start.sh deleted file mode 100644 index 26223b6..0000000 --- a/dev-container-start.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -cd "$(dirname "$0")" - -TMT_PORT_BACKEND="${TMT_PORT_BACKEND:-8080}" -TMT_PORT_FRONTEND="${TMT_PORT_FRONTEND:-5173}" - -docker run --rm -it -v .:/app -p $TMT_PORT_BACKEND:8080 -p $TMT_PORT_FRONTEND:5173 node:20 sh -c "cd /app/frontend && npm run dev & cd /app/backend && npm run dev" diff --git a/dev-container.ps1 b/dev-container.ps1 new file mode 100644 index 0000000..543b5b5 --- /dev/null +++ b/dev-container.ps1 @@ -0,0 +1,6 @@ +Set-Location $PSScriptRoot + +$TMT_PORT_BACKEND = $env:TMT_PORT_BACKEND ?? 8080 +$TMT_PORT_FRONTEND = $env:TMT_PORT_FRONTEND ?? 5173 + +docker run --name tmt2-dev-container -h tmt2-dev-container --rm -it -v .:/app -w /app -p ${TMT_PORT_BACKEND}:8080 -p ${TMT_PORT_FRONTEND}:5173 node:20 bash diff --git a/dev-container.sh b/dev-container.sh new file mode 100755 index 0000000..c8cda66 --- /dev/null +++ b/dev-container.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +cd "$(dirname "$0")" + +TMT_PORT_BACKEND="${TMT_PORT_BACKEND:-8080}" +TMT_PORT_FRONTEND="${TMT_PORT_FRONTEND:-5173}" + +docker run --name tmt2-dev-container -h tmt2-dev-container --rm -it -v .:/app -w /app -p ${TMT_PORT_BACKEND}:8080 -p ${TMT_PORT_FRONTEND}:5173 node:20 bash diff --git a/package.json b/package.json index 63131d8..1dd2610 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "author": "Jens Forstmann", "license": "MIT", "scripts": { + "build": "cd backend && npm run build && cd ../frontend && npm run build", + "dev": "cd backend && npm run dev & cd frontend && npm run dev", "clean": "prettier --write .", "prettier-check": "prettier --check ." }, From 86f01e210f2d0d6ab900efc73b3ca6b6e55ff07c Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 22:45:56 +0200 Subject: [PATCH 17/69] Add docker compose file --- .prettierrc.yml | 2 +- README.md | 2 +- compose.yaml | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 compose.yaml diff --git a/.prettierrc.yml b/.prettierrc.yml index 604facb..862f746 100644 --- a/.prettierrc.yml +++ b/.prettierrc.yml @@ -6,7 +6,7 @@ singleQuote: true printWidth: 100 useTabs: true overrides: - - files: '**/*.yml' + - files: ['**/*.yaml', '**/*.yml'] options: tabWidth: 2 useTabs: false diff --git a/README.md b/README.md index 31e9daa..3a9af53 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Table of Contents: TMT2 is available on docker hub: https://hub.docker.com/r/jensforstmann/tmt2 -Run it with: +You can either use a [docker compose file (compose.yaml)](compose.yaml) or run it directly with `docker run`: ```sh docker run --name tmt2 -d -p 8080:8080 jensforstmann/tmt2 diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..d90d826 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,8 @@ +services: + tmt2: + image: jensforstmann/tmt2:latest + volumes: + - ./storage:/app/backend/storage + restart: unless-stopped + ports: + - 8080:8080/tcp From 68e597d2bd52adde2215fc8d1f6ea7aa8d9834fb Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Mon, 7 Oct 2024 22:55:39 +0200 Subject: [PATCH 18/69] Fix invalid swagger definition --- backend/src/gameServersController.ts | 13 ++++++++++++- backend/swagger.json | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/src/gameServersController.ts b/backend/src/gameServersController.ts index acaa6d9..1d33979 100644 --- a/backend/src/gameServersController.ts +++ b/backend/src/gameServersController.ts @@ -1,4 +1,14 @@ -import { Body, Controller, Delete, Get, Patch, Post, Route, Security } from '@tsoa/runtime'; +import { + Body, + Controller, + Delete, + Get, + OperationId, + Patch, + Post, + Route, + Security, +} from '@tsoa/runtime'; import { IManagedGameServer, IManagedGameServerCreateDto, @@ -63,6 +73,7 @@ export class GameServersController extends Controller { * Execute a rcon command on the game server. */ @Post('{ip}/{port}') + @OperationId('GameServerRcon') async rcon(ip: string, port: number, @Body() requestBody: string[]): Promise { const managedGameServers = ManagedGameServers.get(ip, port); if (!managedGameServers) { diff --git a/backend/swagger.json b/backend/swagger.json index c9b14d5..1219c69 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -3079,7 +3079,7 @@ ] }, "post": { - "operationId": "Rcon", + "operationId": "GameServerRcon", "responses": { "200": { "description": "Ok", From 195f887ad3a441b48a631a993529afef8208daef Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Tue, 8 Oct 2024 08:22:47 +0200 Subject: [PATCH 19/69] Tag docker container with a correct semver version (2.9.0, 2.9, 2 instead of v2.8, v2) --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9861b4d..f769a15 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: uses: olegtarasov/get-tag@v2.1 id: tagName with: - tagRegex: "(?.*)\\.(?.*)" + tagRegex: "v(?.*)\\.(?.*)\\.(?.*)" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 @@ -33,3 +33,4 @@ jobs: tags: | jensforstmann/tmt2:${{ steps.tagName.outputs.major }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }} + jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }}.${{ steps.tagName.outputs.patch }} From 0ddb7f81bcc1b30070959df713b5cc0e51b7fe2b Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Tue, 8 Oct 2024 08:54:23 +0200 Subject: [PATCH 20/69] Re-add previous docker tag schema with leading "v" for backwards compatibility (e.g. v2) --- .github/workflows/release.yml | 1 + README.md | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f769a15..7f8fedc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,3 +34,4 @@ jobs: jensforstmann/tmt2:${{ steps.tagName.outputs.major }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }}.${{ steps.tagName.outputs.patch }} + jensforstmann/tmt2:v${{ steps.tagName.outputs.major }} diff --git a/README.md b/README.md index 3a9af53..2746784 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ # TMT2 - Tournament Match Tracker 2 +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JensForstmann/tmt2/docker.yml?label=docker%20build)](https://github.com/JensForstmann/tmt2/actions/workflows/docker.yml) [![Docker Pulls](https://img.shields.io/docker/pulls/jensforstmann/tmt2)](https://hub.docker.com/r/jensforstmann/tmt2) -[![Docker Image Size (tag)](https://img.shields.io/docker/image-size/jensforstmann/tmt2/latest)](https://hub.docker.com/r/jensforstmann/tmt2) +[![Docker Image Size](https://img.shields.io/docker/image-size/jensforstmann/tmt2/latest?label=docker%20image%20size)](https://hub.docker.com/r/jensforstmann/tmt2) + + TMT is a tool that tracks/watches/observes a Counter-Strike 2 match. @@ -56,6 +59,8 @@ Table of Contents: - [Install dependencies and run application in development mode](#install-dependencies-and-run-application-in-development-mode) - [Build docker image](#build-docker-image) + + ## Getting Started TMT2 is available on docker hub: https://hub.docker.com/r/jensforstmann/tmt2 @@ -80,6 +85,8 @@ docker run --name tmt2 -d -p 8080:8080 -v /home/tmt2/storage:/app/backend/storag The matches which are neither finished nor stopped will be loaded on application start. + + ### Create you first Match After running the container you can open the web frontend: http://localhost:8080 @@ -98,6 +105,7 @@ either take a look at the `access_tokens.json` file or take a look at the first lines of the log output (`docker logs tmt2`). + ### Ingame Chat Commands While TMT watches a match the player ingame can use chat commands to communicate with TMT: @@ -147,6 +155,7 @@ TMT_SAY_PREFIX="[TMT] " ``` + ## API See [`backend/swagger.json`](backend/swagger.json). @@ -154,6 +163,8 @@ You might want to copy its content and paste it into https://editor.swagger.io/. See also the [`examples`](examples) folder. + + ## Security / Authentication There are two types of authentication: @@ -169,6 +180,8 @@ Both are used in client requests in the Authorization header with a "Bearer "-pr Authorization: Bearer 2Mgog6ATqAs495NtUQUsph ... + + ### global access tokens Global access tokens are persisted in the storage folder in the file `access_tokens.json`. @@ -193,6 +206,8 @@ Example: If the file does not exist at startup a new one with a single auto generated global access token will be created. + + ### match specific access tokens Every match will have a `tmtSecret` property. This can be used in the same way as a global access token. @@ -210,6 +225,8 @@ After starting the dev processes you can reach the backend & frontend at: - Backend: http://localhost:8080 - Frontend: http://localhost:5173 + + ## Docker Docker is recommended as it's easy to use and doesn't require any other software to be installed @@ -278,4 +295,6 @@ docker build -t tmt2 . --- + + > This project is a complete rewrite of the former [TMT](https://github.com/JensForstmann/CSGO-PHP-TournamentMatchTracker). From 68acb6872da27a8afde66fac4686eef3bd62171c Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Tue, 8 Oct 2024 09:07:13 +0200 Subject: [PATCH 21/69] Bump docker actions --- .github/workflows/docker.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index b6a2604..08b21f9 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,17 +28,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 if: ${{ github.event_name == 'push' }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64 push: ${{ github.event_name == 'push' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7f8fedc..e37632b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,16 +15,16 @@ jobs: tagRegex: "v(?.*)\\.(?.*)\\.(?.*)" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64 push: true From 472d1a5280a2b2ee0b6c90cc480bdbfc3846cce5 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Tue, 8 Oct 2024 09:25:36 +0200 Subject: [PATCH 22/69] Update docker tag order to set deprecated v2 to the end (on the docker hub page) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e37632b..511b3aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: build-args: | COMMIT_SHA=${{ github.sha }} tags: | + jensforstmann/tmt2:v${{ steps.tagName.outputs.major }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }}.${{ steps.tagName.outputs.patch }} - jensforstmann/tmt2:v${{ steps.tagName.outputs.major }} From 70dc0271dacceb5a8573b33b53d283037bc757fd Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Tue, 8 Oct 2024 09:38:31 +0200 Subject: [PATCH 23/69] Reconfigure prettier --- .github/workflows/docker.yml | 2 +- .prettierignore | 12 - backend/swagger.json | 429 +++++++---------------------------- package.json | 4 +- 4 files changed, 83 insertions(+), 364 deletions(-) delete mode 100644 .prettierignore diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 08b21f9..3394fc0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -22,7 +22,7 @@ jobs: run: npm ci - name: Check syntax (prettier) - run: npx prettier --check . + run: npm run prettier-check build: runs-on: ubuntu-latest diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 5ff6e90..0000000 --- a/.prettierignore +++ /dev/null @@ -1,12 +0,0 @@ -lib -dist -doc -coverage -.yarn -.pnp.js -node_modules -.nyc_output -public -**/*.md -src/routes.ts -swagger.json diff --git a/backend/swagger.json b/backend/swagger.json index 1219c69..bb1becc 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -9,11 +9,7 @@ "schemas": { "TMatchState": { "type": "string", - "enum": [ - "ELECTION", - "MATCH_MAP", - "FINISHED" - ], + "enum": ["ELECTION", "MATCH_MAP", "FINISHED"], "description": "Possible match states." }, "ITeam": { @@ -33,10 +29,7 @@ "description": "Advantage in map wins, useful for double elemination tournament finals." } }, - "required": [ - "name", - "advantage" - ], + "required": ["name", "advantage"], "type": "object", "additionalProperties": false }, @@ -45,9 +38,7 @@ "properties": { "mode": { "type": "string", - "enum": [ - "FIXED" - ], + "enum": ["FIXED"], "nullable": false }, "fixed": { @@ -55,40 +46,27 @@ "description": "The name of the map, e.g. de_anubis." } }, - "required": [ - "mode", - "fixed" - ], + "required": ["mode", "fixed"], "type": "object", "additionalProperties": false }, "TWho": { "type": "string", - "enum": [ - "TEAM_A", - "TEAM_B", - "TEAM_X", - "TEAM_Y" - ] + "enum": ["TEAM_A", "TEAM_B", "TEAM_X", "TEAM_Y"] }, "IPickMap": { "description": "Pick a map from the map pool.", "properties": { "mode": { "type": "string", - "enum": [ - "PICK" - ], + "enum": ["PICK"], "nullable": false }, "who": { "$ref": "#/components/schemas/TWho" } }, - "required": [ - "mode", - "who" - ], + "required": ["mode", "who"], "type": "object", "additionalProperties": false }, @@ -97,15 +75,10 @@ "properties": { "mode": { "type": "string", - "enum": [ - "RANDOM_PICK", - "AGREE" - ] + "enum": ["RANDOM_PICK", "AGREE"] } }, - "required": [ - "mode" - ], + "required": ["mode"], "type": "object", "additionalProperties": false }, @@ -127,19 +100,14 @@ "properties": { "mode": { "type": "string", - "enum": [ - "FIXED" - ], + "enum": ["FIXED"], "nullable": false }, "fixed": { "$ref": "#/components/schemas/TSideFixed" } }, - "required": [ - "mode", - "fixed" - ], + "required": ["mode", "fixed"], "type": "object", "additionalProperties": false }, @@ -148,19 +116,14 @@ "properties": { "mode": { "type": "string", - "enum": [ - "PICK" - ], + "enum": ["PICK"], "nullable": false }, "who": { "$ref": "#/components/schemas/TWho" } }, - "required": [ - "mode", - "who" - ], + "required": ["mode", "who"], "type": "object", "additionalProperties": false }, @@ -169,15 +132,10 @@ "properties": { "mode": { "type": "string", - "enum": [ - "RANDOM", - "KNIFE" - ] + "enum": ["RANDOM", "KNIFE"] } }, - "required": [ - "mode" - ], + "required": ["mode"], "type": "object", "additionalProperties": false }, @@ -211,10 +169,7 @@ ] } }, - "required": [ - "map", - "side" - ], + "required": ["map", "side"], "type": "object", "additionalProperties": false }, @@ -223,15 +178,11 @@ "properties": { "mode": { "type": "string", - "enum": [ - "RANDOM_BAN" - ], + "enum": ["RANDOM_BAN"], "nullable": false } }, - "required": [ - "mode" - ], + "required": ["mode"], "type": "object", "additionalProperties": false }, @@ -240,19 +191,14 @@ "properties": { "mode": { "type": "string", - "enum": [ - "BAN" - ], + "enum": ["BAN"], "nullable": false }, "who": { "$ref": "#/components/schemas/TWho" } }, - "required": [ - "mode", - "who" - ], + "required": ["mode", "who"], "type": "object", "additionalProperties": false }, @@ -270,9 +216,7 @@ ] } }, - "required": [ - "map" - ], + "required": ["map"], "type": "object", "additionalProperties": false }, @@ -289,25 +233,15 @@ }, "TElectionState": { "type": "string", - "enum": [ - "NOT_STARTED", - "IN_PROGRESS", - "FINISHED" - ] + "enum": ["NOT_STARTED", "IN_PROGRESS", "FINISHED"] }, "TTeamAB": { "type": "string", - "enum": [ - "TEAM_A", - "TEAM_B" - ] + "enum": ["TEAM_A", "TEAM_B"] }, "TStep": { "type": "string", - "enum": [ - "MAP", - "SIDE" - ] + "enum": ["MAP", "SIDE"] }, "IElection": { "properties": { @@ -351,10 +285,7 @@ "nullable": true } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object", "description": "Holds the wanted maps of each team." }, @@ -367,10 +298,7 @@ "type": "boolean" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object", "description": "The election process can be restarted if both teams vote for it." } @@ -403,11 +331,7 @@ "description": "If plebs (client without an admin token) create a match the hideRconPassword attribute is set to true.\nThis will prevent executing rcon commands from the frontend by the (unauthorized) user." } }, - "required": [ - "ip", - "port", - "rconPassword" - ], + "required": ["ip", "port", "rconPassword"], "type": "object", "additionalProperties": false }, @@ -454,10 +378,7 @@ "type": "boolean" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object" }, "knifeRestart": { @@ -469,10 +390,7 @@ "type": "boolean" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object" }, "score": { @@ -486,10 +404,7 @@ "format": "double" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object", "description": "Current score of both teams." }, @@ -525,26 +440,17 @@ }, "TMatchEndAction": { "type": "string", - "enum": [ - "KICK_ALL", - "QUIT_SERVER", - "NONE" - ] + "enum": ["KICK_ALL", "QUIT_SERVER", "NONE"] }, "TLogType": { "type": "string", - "enum": [ - "CHAT", - "SYSTEM" - ] + "enum": ["CHAT", "SYSTEM"] }, "ILogChat": { "properties": { "type": { "type": "string", - "enum": [ - "CHAT" - ], + "enum": ["CHAT"], "nullable": false }, "timestamp": { @@ -561,32 +467,19 @@ "type": "string" } }, - "required": [ - "type", - "timestamp", - "isTeamChat", - "steamId64", - "message" - ], + "required": ["type", "timestamp", "isTeamChat", "steamId64", "message"], "type": "object", "additionalProperties": false }, "TSystemLogCategory": { "type": "string", - "enum": [ - "ERROR", - "WARN", - "INFO", - "DEBUG" - ] + "enum": ["ERROR", "WARN", "INFO", "DEBUG"] }, "ILogSystem": { "properties": { "type": { "type": "string", - "enum": [ - "SYSTEM" - ], + "enum": ["SYSTEM"], "nullable": false }, "timestamp": { @@ -600,12 +493,7 @@ "type": "string" } }, - "required": [ - "type", - "timestamp", - "category", - "message" - ], + "required": ["type", "timestamp", "category", "message"], "type": "object", "additionalProperties": false }, @@ -621,10 +509,7 @@ }, "TTeamSides": { "type": "string", - "enum": [ - "CT", - "T" - ] + "enum": ["CT", "T"] }, "IPlayer": { "description": "Player.", @@ -655,19 +540,13 @@ "description": "Player currently on the game server (online)?" } }, - "required": [ - "steamId64", - "name" - ], + "required": ["steamId64", "name"], "type": "object", "additionalProperties": false }, "TMatchMode": { "type": "string", - "enum": [ - "SINGLE", - "LOOP" - ], + "enum": ["SINGLE", "LOOP"], "description": "Possible match modes." }, "IMatch": { @@ -777,12 +656,7 @@ "description": "executed exactly once on match init" } }, - "required": [ - "end", - "match", - "knife", - "init" - ], + "required": ["end", "match", "knife", "init"], "type": "object" }, "canClinch": { @@ -881,9 +755,7 @@ "description": "Advantage in map wins, useful for double elemination tournament finals." } }, - "required": [ - "name" - ], + "required": ["name"], "type": "object", "additionalProperties": false }, @@ -991,13 +863,7 @@ "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" } }, - "required": [ - "mapPool", - "teamA", - "teamB", - "electionSteps", - "gameServer" - ], + "required": ["mapPool", "teamA", "teamB", "electionSteps", "gameServer"], "type": "object", "additionalProperties": false }, @@ -1108,12 +974,7 @@ "description": "executed exactly once on match init" } }, - "required": [ - "end", - "match", - "knife", - "init" - ], + "required": ["end", "match", "knife", "init"], "type": "object" }, "canClinch": { @@ -1203,13 +1064,7 @@ }, "TTeamString": { "type": "string", - "enum": [ - "Unassigned", - "CT", - "TERRORIST", - "", - "Spectator" - ], + "enum": ["Unassigned", "CT", "TERRORIST", "", "Spectator"], "description": "Possible ingame sides of a player." }, "EventType": { @@ -1245,9 +1100,7 @@ }, "type": { "type": "string", - "enum": [ - "CHAT" - ], + "enum": ["CHAT"], "nullable": false }, "player": { @@ -1306,9 +1159,7 @@ }, "type": { "type": "string", - "enum": [ - "MAP_ELECTION_END" - ], + "enum": ["MAP_ELECTION_END"], "nullable": false }, "mapNames": { @@ -1318,13 +1169,7 @@ "type": "array" } }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type", - "mapNames" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type", "mapNames"], "type": "object", "additionalProperties": false }, @@ -1343,9 +1188,7 @@ }, "type": { "type": "string", - "enum": [ - "ROUND_END" - ], + "enum": ["ROUND_END"], "nullable": false }, "mapIndex": { @@ -1402,9 +1245,7 @@ }, "type": { "type": "string", - "enum": [ - "MAP_END" - ], + "enum": ["MAP_END"], "nullable": false }, "mapIndex": { @@ -1467,9 +1308,7 @@ }, "type": { "type": "string", - "enum": [ - "MATCH_END" - ], + "enum": ["MATCH_END"], "nullable": false }, "wonMapsTeamA": { @@ -1515,12 +1354,7 @@ "type": "string" } }, - "required": [ - "winnerTeam", - "scoreTeamB", - "scoreTeamA", - "mapName" - ], + "required": ["winnerTeam", "scoreTeamB", "scoreTeamA", "mapName"], "type": "object" }, "type": "array", @@ -1561,9 +1395,7 @@ }, "type": { "type": "string", - "enum": [ - "KNIFE_END" - ], + "enum": ["KNIFE_END"], "nullable": false }, "mapIndex": { @@ -1610,9 +1442,7 @@ }, "type": { "type": "string", - "enum": [ - "MAP_START" - ], + "enum": ["MAP_START"], "nullable": false }, "mapIndex": { @@ -1655,35 +1485,20 @@ }, "type": { "type": "string", - "enum": [ - "LOG" - ], + "enum": ["LOG"], "nullable": false }, "message": { "type": "string" } }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type", - "message" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type", "message"], "type": "object", "additionalProperties": false }, "TMapMode": { "type": "string", - "enum": [ - "FIXED", - "PICK", - "RANDOM_PICK", - "AGREE", - "BAN", - "RANDOM_BAN" - ], + "enum": ["FIXED", "PICK", "RANDOM_PICK", "AGREE", "BAN", "RANDOM_BAN"], "description": "Possible map modes for a election step." }, "ElectionMapStep": { @@ -1701,9 +1516,7 @@ }, "type": { "type": "string", - "enum": [ - "ELECTION_MAP_STEP" - ], + "enum": ["ELECTION_MAP_STEP"], "nullable": false }, "mode": { @@ -1716,25 +1529,13 @@ "$ref": "#/components/schemas/ITeam" } }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type", - "mode", - "mapName" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type", "mode", "mapName"], "type": "object", "additionalProperties": false }, "TSideMode": { "type": "string", - "enum": [ - "KNIFE", - "FIXED", - "PICK", - "RANDOM" - ], + "enum": ["KNIFE", "FIXED", "PICK", "RANDOM"], "description": "Possible side modes to determine the starting sides of each team." }, "ElectionSideStep": { @@ -1752,9 +1553,7 @@ }, "type": { "type": "string", - "enum": [ - "ELECTION_SIDE_STEP" - ], + "enum": ["ELECTION_SIDE_STEP"], "nullable": false }, "mode": { @@ -1773,13 +1572,7 @@ "$ref": "#/components/schemas/ITeam" } }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type", - "mode" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type", "mode"], "type": "object", "additionalProperties": false }, @@ -1798,22 +1591,14 @@ }, "type": { "type": "string", - "enum": [ - "MATCH_CREATE" - ], + "enum": ["MATCH_CREATE"], "nullable": false }, "match": { "$ref": "#/components/schemas/IMatchResponse" } }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type", - "match" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type", "match"], "type": "object", "additionalProperties": false }, @@ -1832,9 +1617,7 @@ }, "type": { "type": "string", - "enum": [ - "MATCH_UPDATE" - ], + "enum": ["MATCH_UPDATE"], "nullable": false }, "path": { @@ -1853,14 +1636,7 @@ }, "value": {} }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type", - "path", - "value" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type", "path", "value"], "type": "object", "additionalProperties": false }, @@ -1879,18 +1655,11 @@ }, "type": { "type": "string", - "enum": [ - "MATCH_STOP" - ], + "enum": ["MATCH_STOP"], "nullable": false } }, - "required": [ - "timestamp", - "matchId", - "matchPassthrough", - "type" - ], + "required": ["timestamp", "matchId", "matchPassthrough", "type"], "type": "object", "additionalProperties": false }, @@ -2019,11 +1788,7 @@ }, "matchEndAction": { "type": "string", - "enum": [ - "KICK_ALL", - "QUIT_SERVER", - "NONE" - ], + "enum": ["KICK_ALL", "QUIT_SERVER", "NONE"], "description": "defaults to NONE" }, "tmtLogAddress": { @@ -2094,10 +1859,7 @@ "type": "boolean" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object" }, "knifeRestart": { @@ -2109,10 +1871,7 @@ "type": "boolean" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object" }, "score": { @@ -2126,10 +1885,7 @@ "format": "double" } }, - "required": [ - "teamB", - "teamA" - ], + "required": ["teamB", "teamA"], "type": "object", "description": "Current score of both teams." }, @@ -2185,13 +1941,7 @@ "description": "Match id which is currently using this managed game server." } }, - "required": [ - "ip", - "port", - "rconPassword", - "canBeUsed", - "usedBy" - ], + "required": ["ip", "port", "rconPassword", "canBeUsed", "usedBy"], "type": "object", "additionalProperties": false }, @@ -2216,11 +1966,7 @@ "description": "Can the server be used for new matches?" } }, - "required": [ - "ip", - "port", - "rconPassword" - ], + "required": ["ip", "port", "rconPassword"], "type": "object", "additionalProperties": false }, @@ -2246,10 +1992,7 @@ "description": "Set or delete the link to a match. If it's null and `canBeUsed` is true, the game server is available." } }, - "required": [ - "ip", - "port" - ], + "required": ["ip", "port"], "type": "object", "additionalProperties": false }, @@ -2303,9 +2046,7 @@ "nullable": true } }, - "required": [ - "tmtLogAddress" - ], + "required": ["tmtLogAddress"], "type": "object", "additionalProperties": false }, @@ -2324,11 +2065,7 @@ "type": "string" } }, - "required": [ - "name", - "data", - "id" - ], + "required": ["name", "data", "id"], "type": "object", "additionalProperties": false }, @@ -2344,10 +2081,7 @@ "$ref": "#/components/schemas/IMatchCreateDto" } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "type": "object", "additionalProperties": false } @@ -2684,10 +2418,7 @@ "type": "array" } }, - "required": [ - "total", - "latestFiles" - ], + "required": ["total", "latestFiles"], "type": "object" }, {} @@ -3324,4 +3055,4 @@ "url": "/" } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 1dd2610..d86cb66 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "scripts": { "build": "cd backend && npm run build && cd ../frontend && npm run build", "dev": "cd backend && npm run dev & cd frontend && npm run dev", - "clean": "prettier --write .", - "prettier-check": "prettier --check ." + "clean": "prettier '!**/*md' . --write", + "prettier-check": "prettier '!**/*md' . --check" }, "devDependencies": { "prettier": "^3.3.3", From daadd5d8274507297d5f0bed913e4cf27107741f Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Wed, 9 Oct 2024 09:43:39 +0200 Subject: [PATCH 24/69] Bump dependencies --- backend/package-lock.json | 59 +++++++++++++++++++------------------- backend/package.json | 6 ++-- frontend/package-lock.json | 28 +++++++++--------- frontend/package.json | 4 +-- 4 files changed, 49 insertions(+), 48 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 244dac3..72e59f3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@tsoa/runtime": "^6.4.0", - "express": "^4.21.0", + "express": "^4.21.1", "short-uuid": "^5.2.0", "steamid": "^2.1.0", "typed-emitter": "^2.1.0", @@ -20,12 +20,12 @@ "@tsoa/cli": "^6.4.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", - "@types/node": "^22.7.4", + "@types/node": "^22.7.5", "@types/steamid": "^2.0.3", "@types/ws": "^8.5.12", "nodemon": "^3.1.7", "ts-node": "^10.9.2", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } }, "node_modules/@cspotcode/source-map-support": { @@ -713,9 +713,9 @@ } }, "node_modules/@types/node": { - "version": "22.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", - "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -1044,9 +1044,10 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1211,9 +1212,9 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -1221,7 +1222,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -2446,9 +2447,9 @@ } }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3291,9 +3292,9 @@ } }, "@types/node": { - "version": "22.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", - "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "requires": { "undici-types": "~6.19.2" } @@ -3554,9 +3555,9 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -3667,16 +3668,16 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -4525,9 +4526,9 @@ } }, "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true }, "uglify-js": { diff --git a/backend/package.json b/backend/package.json index edd8bd5..a57c5c3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@tsoa/runtime": "^6.4.0", - "express": "^4.21.0", + "express": "^4.21.1", "short-uuid": "^5.2.0", "steamid": "^2.1.0", "typed-emitter": "^2.1.0", @@ -22,11 +22,11 @@ "@tsoa/cli": "^6.4.0", "@types/debug": "^4.1.12", "@types/express": "^5.0.0", - "@types/node": "^22.7.4", + "@types/node": "^22.7.5", "@types/steamid": "^2.0.3", "@types/ws": "^8.5.12", "nodemon": "^3.1.7", "ts-node": "^10.9.2", - "typescript": "^5.6.2" + "typescript": "^5.6.3" } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3fb71b8..dc2129e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,9 +16,9 @@ "autoprefixer": "^10.4.20", "daisyui": "^4.12.12", "postcss": "^8.4.47", - "solid-js": "^1.9.1", + "solid-js": "^1.9.2", "tailwindcss": "^3.4.13", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "vite": "^5.4.8", "vite-plugin-solid": "^2.10.2" } @@ -2389,9 +2389,9 @@ } }, "node_modules/solid-js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.1.tgz", - "integrity": "sha512-Gd6QWRFfO2XKKZqVK4YwbhWZkr0jWw1dYHOt+VYebomeyikGP0SuMflf42XcDuU9HAEYDArFJIYsBNjlE7iZsw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.2.tgz", + "integrity": "sha512-fe/K03nV+kMFJYhAOE8AIQHcGxB4rMIEoEyrulbtmf217NffbbwBqJnJI4ovt16e+kaIt0czE2WA7mP/pYN9yg==", "dev": true, "license": "MIT", "dependencies": { @@ -2557,9 +2557,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4260,9 +4260,9 @@ "requires": {} }, "solid-js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.1.tgz", - "integrity": "sha512-Gd6QWRFfO2XKKZqVK4YwbhWZkr0jWw1dYHOt+VYebomeyikGP0SuMflf42XcDuU9HAEYDArFJIYsBNjlE7iZsw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.2.tgz", + "integrity": "sha512-fe/K03nV+kMFJYhAOE8AIQHcGxB4rMIEoEyrulbtmf217NffbbwBqJnJI4ovt16e+kaIt0czE2WA7mP/pYN9yg==", "dev": true, "requires": { "csstype": "^3.1.0", @@ -4387,9 +4387,9 @@ "dev": true }, "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", - "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true }, "update-browserslist-db": { diff --git a/frontend/package.json b/frontend/package.json index 1316f54..eab68ac 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,9 +17,9 @@ "autoprefixer": "^10.4.20", "daisyui": "^4.12.12", "postcss": "^8.4.47", - "solid-js": "^1.9.1", + "solid-js": "^1.9.2", "tailwindcss": "^3.4.13", - "typescript": "^5.6.2", + "typescript": "^5.6.3", "vite": "^5.4.8", "vite-plugin-solid": "^2.10.2" } From 2c3bd3b39ab03adaea9ac7655a6016cc2cb1b7ae Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Wed, 9 Oct 2024 11:01:53 +0200 Subject: [PATCH 25/69] Add documentation to match update properties --- backend/swagger.json | 9 ++++++--- common/types/match.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/backend/swagger.json b/backend/swagger.json index bb1becc..ca665a4 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -1800,7 +1800,8 @@ "description": "Match mode (single: stops when match is finished, loop: starts again after match is finished)" }, "state": { - "$ref": "#/components/schemas/TMatchState" + "$ref": "#/components/schemas/TMatchState", + "description": "Overwrite the match state.\nOnly sets the state. Does not execute any code/logic." }, "logSecret": { "type": "string", @@ -1808,10 +1809,12 @@ }, "currentMap": { "type": "number", - "format": "double" + "format": "double", + "description": "Change to this match map (0-based index)." }, "_restartElection": { - "type": "boolean" + "type": "boolean", + "description": "Restart the complete match.\nWill restart the election process as well.\nMust be executed when the election steps were changed after the match was created." }, "_execRconCommandsInit": { "type": "boolean" diff --git a/common/types/match.ts b/common/types/match.ts index 5a9db94..af44a17 100644 --- a/common/types/match.ts +++ b/common/types/match.ts @@ -173,11 +173,23 @@ export interface IMatchCreateDto { } export interface IMatchUpdateDto extends Partial { + /** + * Overwrite the match state. + * Only sets the state. Does not execute any code/logic. + */ state?: TMatchState; /** updates the server's log address automatically */ logSecret?: string; + /** + * Change to this match map (0-based index). + */ currentMap?: number; + /** + * Restart the complete match. + * Will restart the election process as well. + * Must be executed when the election steps were changed after the match was created. + */ _restartElection?: boolean; _execRconCommandsInit?: boolean; _execRconCommandsKnife?: boolean; From 73e5115f40dcb3895dff7161c6d52659eccf2975 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Wed, 9 Oct 2024 11:30:53 +0200 Subject: [PATCH 26/69] Add game server crash detection to automatically execute rcon init commands and change to current match map --- backend/src/match.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/backend/src/match.ts b/backend/src/match.ts index 88a700d..044944f 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -120,7 +120,6 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS }; try { const match = await createFromData(data, 'Create new match'); - await execRconCommands(match, 'init'); return match; } catch (err) { if (!dto.gameServer) { @@ -159,10 +158,37 @@ const connectToGameServer = async (match: Match): Promise => { match.rconConnection = gameServer; previous?.end().catch(() => {}); match.log(`Connect rcon successful ${addr}`); + await setup(match); + await init(match); await ensureLogAddressIsRegistered(match); resetPeriodicJobTimer(match); }; +const init = async (match: Match) => { + const aliasKey = 'TMT_MATCH_ID'; + const aliasResponse = await execRcon(match, 'alias'); + const aliasMatchId = aliasResponse + .trim() + .split('\n') + .map((line) => line.trim()) + .find((line) => line.startsWith(`${aliasKey} : `)) + ?.substring(aliasKey.length + 3); // 3 for " : " + if (aliasMatchId !== match.data.id) { + // server was restarted or TMT was never connected before + await execRcon(match, `alias ${aliasKey} ${match.data.id}`); + await execRconCommands(match, 'init'); + + // if match is already in progress (server crashed) -> load match map + const currentMatchMap = getCurrentMatchMap(match); + if (currentMatchMap) { + match.log( + `Match is already in progress (assume server crash): load match map ${currentMatchMap.name}` + ); + await MatchMap.loadMap(match, currentMatchMap, true); + } + } +}; + const onRconConnectionEnd = async (match: Match) => { const addr = `${match.rconConnection?.config.host}:${match.rconConnection?.config.port}`; match.log(`Rcon connection lost: ${addr}`); @@ -246,13 +272,12 @@ const ensureLogAddressIsRegistered = async (match: Match) => { if (!existing) { match.data.parseIncomingLogs = false; + MatchService.scheduleSave(match); match.log('Register log address'); await execRcon(match, 'logaddress_delall_http'); await execRcon(match, `logaddress_add_http "${logAddress}"`); } - MatchService.scheduleSave(match); - // delay parsing of incoming log lines (because we don't care about the initial big batch) sleep(2000) .then(async () => { @@ -261,7 +286,6 @@ const ensureLogAddressIsRegistered = async (match: Match) => { match.data.parseIncomingLogs = true; MatchService.scheduleSave(match); await say(match, 'TMT IS ONLINE'); - await setup(match); if (match.data.state === 'ELECTION') { await Election.auto(match); } From 218ac9d07133dcfdb77576531844d26d726d4141 Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Wed, 9 Oct 2024 12:02:44 +0200 Subject: [PATCH 27/69] Remove color codes from logs --- backend/src/gameServer.ts | 5 +++++ backend/src/match.ts | 2 ++ 2 files changed, 7 insertions(+) diff --git a/backend/src/gameServer.ts b/backend/src/gameServer.ts index b9a5cdb..7f77b2d 100644 --- a/backend/src/gameServer.ts +++ b/backend/src/gameServer.ts @@ -16,6 +16,11 @@ export const colors = { orange: '\u0010', // #E4AF3A }; +export const removeColors = (string: string) => { + Object.values(colors).forEach((colorCode) => (string = string.replaceAll(colorCode, ''))); + return string; +}; + export const create = async (dto: IGameServer, log: (msg: string) => void): Promise => { const rcon = new Rcon({ host: dto.ip, diff --git a/backend/src/match.ts b/backend/src/match.ts index 044944f..de25f2d 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -132,6 +132,7 @@ export const createFromCreateDto = async (dto: IMatchCreateDto, id: string, logS const createLogger = (match: Match) => (msg: string) => { const ds = new Date().toISOString(); + msg = GameServer.removeColors(msg); Storage.appendLine(STORAGE_LOGS_PREFIX + match.data.id + STORAGE_LOGS_SUFFIX, `${ds} | ${msg}`); console.info(`${ds} [${match.data.id}] ${msg}`); Events.onLog(match, msg); @@ -703,6 +704,7 @@ const onPlayerSay = async ( const onConsoleSay = async (match: Match, message: string) => { message = message.trim(); if (!message.startsWith(SAY_PREFIX)) { + message = GameServer.removeColors(message); Events.onConsoleSay(match, message); } }; From 145dca67dfb446eba860ed3d2f67c144abb67e2a Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Wed, 9 Oct 2024 14:29:47 +0200 Subject: [PATCH 28/69] Bake TMT version and build timestamp into docker container --- .github/workflows/docker.yml | 2 +- .github/workflows/release.yml | 3 ++- Dockerfile | 7 ++++-- backend/src/debugController.ts | 4 +++- backend/src/index.ts | 40 ++++++++++++++++++++++++---------- backend/src/match.ts | 7 ++++-- backend/src/routes.ts | 10 +++++++++ backend/swagger.json | 10 +++++++++ common/types/debug.ts | 2 ++ 9 files changed, 66 insertions(+), 19 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3394fc0..a300c80 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -43,7 +43,7 @@ jobs: platforms: linux/amd64,linux/arm64 push: ${{ github.event_name == 'push' }} build-args: | - COMMIT_SHA=${{ github.sha }} + TMT_COMMIT_SHA=${{ github.sha }} tags: | jensforstmann/tmt2:${{ github.sha }} jensforstmann/tmt2:latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 511b3aa..9ce9031 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,8 @@ jobs: platforms: linux/amd64,linux/arm64 push: true build-args: | - COMMIT_SHA=${{ github.sha }} + TMT_COMMIT_SHA=${{ github.sha }} + TMT_VERSION=${{ steps.tagName.outputs.major }}.${{ steps.tagName.outputs.minor }}.${{ steps.tagName.outputs.patch }} tags: | jensforstmann/tmt2:v${{ steps.tagName.outputs.major }} jensforstmann/tmt2:${{ steps.tagName.outputs.major }} diff --git a/Dockerfile b/Dockerfile index 01c709f..7caa71f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,10 @@ COPY --from=backend_build_image /app/backend/node_modules /app/backend/node_modu COPY --from=frontend_build_image /app/frontend/dist /app/frontend/dist VOLUME /app/backend/storage EXPOSE 8080 -ARG COMMIT_SHA -ENV COMMIT_SHA=${COMMIT_SHA} +ARG TMT_COMMIT_SHA +ENV TMT_COMMIT_SHA=${TMT_COMMIT_SHA} +ARG TMT_VERSION +ENV TMT_VERSION=${TMT_VERSION} +RUN date -u +"%Y-%m-%dT%H:%M:%SZ" > /app/.TMT_IMAGE_BUILD_TIMESTAMP WORKDIR /app/backend CMD ["/tini", "node", "./dist/backend/src/index.js"] diff --git a/backend/src/debugController.ts b/backend/src/debugController.ts index 6a04bd1..11e9022 100644 --- a/backend/src/debugController.ts +++ b/backend/src/debugController.ts @@ -1,5 +1,5 @@ import { Controller, Get, Route, Security } from '@tsoa/runtime'; -import { PORT, TMT_LOG_ADDRESS, VERSION } from '.'; +import { COMMIT_SHA, IMAGE_BUILD_TIMESTAMP, PORT, TMT_LOG_ADDRESS, VERSION } from '.'; import { IDebugResponse } from '../../common'; import { Settings } from './settings'; import { STORAGE_FOLDER } from './storage'; @@ -20,6 +20,8 @@ export class DebugController extends Controller { async getInfos(): Promise { return { tmtVersion: VERSION, + tmtCommitSha: COMMIT_SHA, + tmtImageBuildTimestamp: IMAGE_BUILD_TIMESTAMP, tmtStorageFolder: STORAGE_FOLDER, tmtPort: PORT, tmtLogAddress: TMT_LOG_ADDRESS, diff --git a/backend/src/index.ts b/backend/src/index.ts index 73dcf9c..51e9642 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,16 +1,16 @@ import { ValidateError } from '@tsoa/runtime'; import express, { ErrorRequestHandler } from 'express'; -import { existsSync } from 'fs'; +import { existsSync, readFileSync } from 'fs'; import http from 'http'; import path from 'path'; import * as Auth from './auth'; import * as Election from './election'; import * as ManagedGameServers from './managedGameServers'; -import * as Presets from './presets'; import * as Match from './match'; import { checkAndNormalizeLogAddress } from './match'; import * as MatchMap from './matchMap'; import * as MatchService from './matchService'; +import * as Presets from './presets'; import { RegisterRoutes } from './routes'; import * as Storage from './storage'; import * as WebSocket from './webSocket'; @@ -28,18 +28,31 @@ export const TMT_LOG_ADDRESS: string | null = (() => { return addr; })(); -const STATIC_PATH = (() => { - if (existsSync(path.join(__dirname, '../../frontend/dist'))) { - return path.join(__dirname, '../../frontend/dist'); +const APP_DIR = (() => { + if (__dirname.endsWith(path.join('/backend/dist/backend/src'))) { + // in production: __dirname = /app/backend/dist/backend/src + return path.join(__dirname, '../../../..'); } - if (existsSync(path.join(__dirname, '../../../../frontend/dist'))) { - return path.join(__dirname, '../../../../frontend/dist'); + if (__dirname.endsWith(path.join('/backend/src'))) { + // in development: __dirname = /app/backend/src + return path.join(__dirname, '../..'); } - throw 'Could not determine static path'; + console.error(`__dirname is ${__dirname}`); + throw 'Could not determine APP_DIR'; })(); +const FRONTEND_DIR = path.join(APP_DIR, '/frontend/dist'); + export const PORT = process.env['TMT_PORT'] || 8080; -export const VERSION = process.env['COMMIT_SHA'] || null; +export const VERSION = process.env['TMT_VERSION'] || null; +export const COMMIT_SHA = process.env['TMT_COMMIT_SHA'] || null; +export const IMAGE_BUILD_TIMESTAMP = (() => { + const file = path.join(APP_DIR, '.TMT_IMAGE_BUILD_TIMESTAMP'); + if (existsSync(file)) { + return readFileSync(file).toString().trim(); + } + return null; +})(); const app = express(); const httpServer = http.createServer(app); @@ -103,11 +116,14 @@ app.get('/api', (req, res) => { res.sendFile('swagger.json', { root: '.' }); }); -app.get('*', express.static(STATIC_PATH)); -app.get('*', (req, res) => res.sendFile(path.join(STATIC_PATH, 'index.html'))); +app.get('*', express.static(FRONTEND_DIR)); +app.get('*', (req, res) => res.sendFile(path.join(FRONTEND_DIR, 'index.html'))); const main = async () => { - console.info(`Start TMT (version ${VERSION ? VERSION : 'unknown'})`); + console.info( + `Start TMT (version ${VERSION ?? 'unknown'}, commit ${COMMIT_SHA ?? 'unknown'}, build timestamp ${IMAGE_BUILD_TIMESTAMP ?? 'unknown'})` + ); + console.info(`App dir: ${APP_DIR}, frontend dir: ${FRONTEND_DIR}`); await Storage.setup(); await Auth.setup(); await WebSocket.setup(httpServer); diff --git a/backend/src/match.ts b/backend/src/match.ts index de25f2d..0fca173 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -1,6 +1,6 @@ import { ValidateError } from '@tsoa/runtime'; import { generate as shortUuid } from 'short-uuid'; -import { TMT_LOG_ADDRESS, VERSION } from '.'; +import { COMMIT_SHA, IMAGE_BUILD_TIMESTAMP, TMT_LOG_ADDRESS, VERSION } from '.'; import { IMatch, IMatchCreateDto, @@ -716,7 +716,10 @@ export const registerCommandHandlers = () => { }; const onVersionCommand: commands.CommandHandler = async (e) => { - await say(e.match, `TMT version: ${VERSION ?? 'unknown'}`); + await say( + e.match, + `TMT: version ${VERSION ?? 'unknown'}, commit ${COMMIT_SHA ?? 'unknown'}, build timestamp ${IMAGE_BUILD_TIMESTAMP ?? 'unknown'}` + ); }; const onEveryCommand: commands.CommandHandler = async (e) => { diff --git a/backend/src/routes.ts b/backend/src/routes.ts index fa8c560..9eb4c84 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -1186,6 +1186,16 @@ const models: TsoaRoute.Models = { subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], required: true, }, + tmtCommitSha: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, + tmtImageBuildTimestamp: { + dataType: 'union', + subSchemas: [{ dataType: 'string' }, { dataType: 'enum', enums: [null] }], + required: true, + }, tmtStorageFolder: { dataType: 'string', required: true }, tmtPort: { dataType: 'union', diff --git a/backend/swagger.json b/backend/swagger.json index ca665a4..f646e0f 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -2005,6 +2005,14 @@ "type": "string", "nullable": true }, + "tmtCommitSha": { + "type": "string", + "nullable": true + }, + "tmtImageBuildTimestamp": { + "type": "string", + "nullable": true + }, "tmtStorageFolder": { "type": "string" }, @@ -2033,6 +2041,8 @@ }, "required": [ "tmtVersion", + "tmtCommitSha", + "tmtImageBuildTimestamp", "tmtStorageFolder", "tmtPort", "tmtLogAddress", diff --git a/common/types/debug.ts b/common/types/debug.ts index 604cc09..b917575 100644 --- a/common/types/debug.ts +++ b/common/types/debug.ts @@ -1,5 +1,7 @@ export interface IDebugResponse { tmtVersion: string | null; + tmtCommitSha: string | null; + tmtImageBuildTimestamp: string | null; tmtStorageFolder: string; tmtPort: string | number; tmtLogAddress: string | null; From c8039213aacc19c5bdcecf4c1ccd03eb1383689f Mon Sep 17 00:00:00 2001 From: JensForstmann Date: Fri, 11 Oct 2024 21:55:12 +0200 Subject: [PATCH 29/69] Add possibility to force players by their steam ids into a specific team. --- backend/src/match.ts | 25 ++++++--- backend/src/player.ts | 35 ++++++++++-- backend/src/routes.ts | 2 + backend/swagger.json | 16 +++++- common/types/player.ts | 6 ++- common/types/team.ts | 4 ++ frontend/src/components/CreateUpdateMatch.tsx | 54 ++++++++++++++++++- frontend/src/utils/copyObject.ts | 2 +- 8 files changed, 130 insertions(+), 14 deletions(-) diff --git a/backend/src/match.ts b/backend/src/match.ts index 0fca173..203271b 100644 --- a/backend/src/match.ts +++ b/backend/src/match.ts @@ -447,7 +447,13 @@ export const onLog = async (match: Match, body: string) => { // logBuffer was empty before -> no other onLogLine is in progress right now while (match.logBuffer.length > 0) { const oldestLine = match.logBuffer[0]!; - await onLogLine(match, oldestLine); + try { + await onLogLine(match, oldestLine); + } catch (err) { + match.log(`Error processing incoming log line from game server: ${oldestLine}`); + match.log(`Failed line: ${oldestLine}`); + match.log(`Message: ${err}`); + } match.logBuffer.splice(0, 1); } } @@ -586,10 +592,10 @@ const onPlayerLogLine = async ( const steamId64 = Player.getSteamID64(steamId); player = match.data.players.find((p) => p.steamId64 === steamId64); if (!player) { - player = Player.create(steamId, name); + player = Player.create(match, steamId, name); match.log(`Player ${player.steamId64} (${name}) created`); match.data.players.push(player); - player = match.data.players[match.data.players.length - 1]!; + player = match.data.players[match.data.players.length - 1]!; // re-assign to work nicely with changeListener (ProxyHandler) MatchService.scheduleSave(match); } if (player.name !== name) { @@ -828,10 +834,14 @@ export const sayWhatTeamToJoin = async (match: Match) => { const onTeamCommand: commands.CommandHandler = async ({ match, player, parameters }) => { const firstParameter = parameters[0]?.toUpperCase(); if (firstParameter === 'A' || firstParameter === 'B') { + if (Player.getForcedTeam(match, player.steamId64)) { + await say(match, `PLAYER ${escapeRconString(player.name)} CANNOT CHANGE THEIR TEAM`); + return; + } player.team = firstParameter === 'A' ? 'TEAM_A' : 'TEAM_B'; MatchService.scheduleSave(match); const team = getTeamByAB(match, player.team); - say( + await say( match, `PLAYER ${escapeRconString(player.name)} JOINED TEAM ${escapeRconString(team.name)}` ); @@ -839,14 +849,14 @@ const onTeamCommand: commands.CommandHandler = async ({ match, player, parameter } else { const playerTeam = player.team; if (playerTeam) { - say( + await say( match, - `YOU ARE IN TEAM ${playerTeam === 'TEAM_A' ? 'A' : 'B'}: ${escapeRconString( + `PLAYER ${escapeRconString(player.name)} IS IN TEAM ${playerTeam === 'TEAM_A' ? 'A' : 'B'}: ${escapeRconString( playerTeam === 'TEAM_A' ? match.data.teamA.name : match.data.teamB.name )}` ); } else { - say(match, `YOU HAVE NO TEAM`); + await say(match, `PLAYER ${escapeRconString(player.name)} HAS NO TEAM`); } } }; @@ -1049,6 +1059,7 @@ export const update = async (match: Match, dto: IMatchUpdateDto) => { } if (dto.teamA || dto.teamB) { + Player.forcePlayerIntoTeams(match); await setTeamNames(match); } diff --git a/backend/src/player.ts b/backend/src/player.ts index df96305..9116123 100644 --- a/backend/src/player.ts +++ b/backend/src/player.ts @@ -1,10 +1,13 @@ import SteamID from 'steamid'; -import { IPlayer, TTeamSides, TTeamString } from '../../common'; +import { IPlayer, TTeamAB, TTeamSides, TTeamString } from '../../common'; +import * as Match from './match'; -export const create = (steamId: string, name: string): IPlayer => { +export const create = (match: Match.Match, steamId: string, name: string): IPlayer => { + const steamId64 = getSteamID64(steamId); return { name: name, - steamId64: getSteamID64(steamId), + steamId64: steamId64, + team: getForcedTeam(match, steamId64), }; }; @@ -12,6 +15,32 @@ export const getSteamID64 = (steamId: string) => { return new SteamID(steamId).getSteamID64(); }; +export const getForcedTeam = (match: Match.Match, steamId64: string): TTeamAB | undefined => { + const isTeamA = match.data.teamA.playerSteamIds64?.includes(steamId64); + const isTeamB = match.data.teamB.playerSteamIds64?.includes(steamId64); + if (isTeamA === isTeamB) { + // either: configured for no teams + // or: configured for both teams + return undefined; + } + return isTeamA ? 'TEAM_A' : 'TEAM_B'; +}; + +export const forcePlayerIntoTeams = (match: Match.Match) => { + match.data.players.forEach((player, index) => { + const prevTeamAB = player.team; + const newTeamAB = getForcedTeam(match, player.steamId64); + if (newTeamAB && prevTeamAB !== newTeamAB) { + const fromTeam = prevTeamAB + ? ` from ${prevTeamAB} (${Match.getTeamByAB(match, prevTeamAB).name})` + : ''; + const toTeam = ` into team ${newTeamAB} (${Match.getTeamByAB(match, newTeamAB).name})`; + match.log(`Force player ${player.name}${fromTeam}${toTeam}`); + match.data.players[index]!.team = newTeamAB; + } + }); +}; + export const getSideFromTeamString = (teamString: TTeamString): TTeamSides | null => { switch (teamString) { case 'CT': diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 9eb4c84..f3d0ff4 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -47,6 +47,7 @@ const models: TsoaRoute.Models = { passthrough: { dataType: 'string' }, name: { dataType: 'string', required: true }, advantage: { dataType: 'double', required: true }, + playerSteamIds64: { dataType: 'array', array: { dataType: 'string' } }, }, additionalProperties: false, }, @@ -545,6 +546,7 @@ const models: TsoaRoute.Models = { name: { dataType: 'string', required: true }, passthrough: { dataType: 'string' }, advantage: { dataType: 'double' }, + playerSteamIds64: { dataType: 'array', array: { dataType: 'string' } }, }, additionalProperties: false, }, diff --git a/backend/swagger.json b/backend/swagger.json index f646e0f..65889e5 100644 --- a/backend/swagger.json +++ b/backend/swagger.json @@ -27,6 +27,13 @@ "type": "number", "format": "double", "description": "Advantage in map wins, useful for double elemination tournament finals." + }, + "playerSteamIds64": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Steam ids of players in \"Steam ID 64\" format. Will be forced into this team." } }, "required": ["name", "advantage"], @@ -524,7 +531,7 @@ }, "team": { "$ref": "#/components/schemas/TTeamAB", - "description": "Current team as they joined with `.team`." + "description": "Current team as they joined with `.team`.\nIf the player's steam id is in the team's `playerSteamIds64`\nthis cannot be changed and is always set to the team." }, "side": { "allOf": [ @@ -753,6 +760,13 @@ "type": "number", "format": "double", "description": "Advantage in map wins, useful for double elemination tournament finals." + }, + "playerSteamIds64": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Steam ids of players in \"Steam ID 64\" format. Will be forced into this team." } }, "required": ["name"], diff --git a/common/types/player.ts b/common/types/player.ts index af99bb1..f26cfa5 100644 --- a/common/types/player.ts +++ b/common/types/player.ts @@ -9,7 +9,11 @@ export interface IPlayer { steamId64: string; /** Name. */ name: string; - /** Current team as they joined with `.team`. */ + /** + * Current team as they joined with `.team`. + * If the player's steam id is in the team's `playerSteamIds64` + * this cannot be changed and is always set to the team. + */ team?: TTeamAB; /** Current ingame side. */ side?: TTeamSides | null; diff --git a/common/types/team.ts b/common/types/team.ts index 495beea..8c51774 100644 --- a/common/types/team.ts +++ b/common/types/team.ts @@ -11,6 +11,8 @@ export interface ITeam { name: string; /** Advantage in map wins, useful for double elemination tournament finals. */ advantage: number; + /** Steam ids of players in "Steam ID 64" format. Will be forced into this team.*/ + playerSteamIds64?: string[]; } /** @@ -25,6 +27,8 @@ export interface ITeamCreateDto { passthrough?: string; /** Advantage in map wins, useful for double elemination tournament finals. */ advantage?: number; + /** Steam ids of players in "Steam ID 64" format. Will be forced into this team.*/ + playerSteamIds64?: string[]; } /** Possible ingame sides of a player. */ diff --git a/frontend/src/components/CreateUpdateMatch.tsx b/frontend/src/components/CreateUpdateMatch.tsx index dd28ef4..0b37394 100644 --- a/frontend/src/components/CreateUpdateMatch.tsx +++ b/frontend/src/components/CreateUpdateMatch.tsx @@ -32,7 +32,7 @@ import { SelectInput, TextArea, TextInput, ToggleInput } from './Inputs'; import { Modal } from './Modal'; const Presets: Component<{ - onSelect: (preset: IPreset) => void; + onSelect: (preset: IMatchCreateDto) => void; matchCreateDto: IMatchCreateDto; }> = (props) => { const fetcher = createFetcher(); @@ -235,6 +235,10 @@ const minifyMapPool = (maps: string[]) => { return maps.map((map) => map.trim()).filter((l) => l.length > 0); }; +const minifyPlayerSteamIds64 = (steamIds: string[]) => { + return steamIds.map((steamId) => steamId.trim()).filter((l) => l.length > 0); +}; + export const CreateUpdateMatch: Component< ( | { @@ -265,6 +269,12 @@ export const CreateUpdateMatch: Component< try { const tempDto = copyObject(dto); tempDto.mapPool = minifyMapPool(tempDto.mapPool); + tempDto.teamA.playerSteamIds64 = minifyPlayerSteamIds64( + tempDto.teamA.playerSteamIds64 ?? [] + ); + tempDto.teamB.playerSteamIds64 = minifyPlayerSteamIds64( + tempDto.teamB.playerSteamIds64 ?? [] + ); setJson(props.getFinalDto?.(tempDto) ?? JSON.stringify(tempDto, undefined, 4)); } catch (err) { setJson('ERROR!\n' + err); @@ -812,6 +822,27 @@ export const CreateUpdateMatch: Component< )} onInput={(e) => setDto('teamA', 'passthrough', e.currentTarget.value)} /> +