step: Working MQTT websocket connection, docker deploy script

This commit is contained in:
2026-05-10 00:10:09 +02:00
parent 9900d48622
commit e1a32ecd01
6 changed files with 877 additions and 36 deletions
+3
View File
@@ -0,0 +1,3 @@
docker build -t kingArthurApp:latest .
docker save kingArthurApp:latest | gzip > kingArthurApp.tar.gz
scp kingArthurApp.tar.gz youruser@your_server_ip:~/
+13
View File
@@ -0,0 +1,13 @@
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Serve stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+519 -3
View File
@@ -7,11 +7,23 @@
"": {
"name": "king-arthur",
"version": "0.0.0",
"dependencies": {
"mqtt": "^5.15.1"
},
"devDependencies": {
"typescript": "~6.0.2",
"vite": "^8.0.10"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
@@ -368,6 +380,171 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/node": {
"version": "25.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz",
"integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.19.0"
}
},
"node_modules/@types/readable-stream": {
"version": "4.0.23",
"resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz",
"integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bl": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/bl/-/bl-6.1.6.tgz",
"integrity": "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==",
"license": "MIT",
"dependencies": {
"@types/readable-stream": "^4.0.0",
"buffer": "^6.0.3",
"inherits": "^2.0.4",
"readable-stream": "^4.2.0"
}
},
"node_modules/broker-factory": {
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.14.tgz",
"integrity": "sha512-L45k5HMbPIrMid0nTOZ/UPXG/c0aRuQKVrSDFIb1zOkvfiyHgYmIjc3cSiN1KwQIvRDOtKE0tfb3I9EZ3CmpQQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"fast-unique-numbers": "^9.0.27",
"tslib": "^2.8.1",
"worker-factory": "^7.0.49"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/commist": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-3.2.0.tgz",
"integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==",
"license": "MIT"
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [
"node >= 6.0"
],
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/concat-stream/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -378,6 +555,37 @@
"node": ">=8"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/fast-unique-numbers": {
"version": "9.0.27",
"resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.27.tgz",
"integrity": "sha512-nDA9ADeINN8SA2u2wCtU+siWFTTDqQR37XvgPIDDmboWQeExz7X0mImxuaN+kJddliIqy2FpVRmnvRZ+j8i1/A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"tslib": "^2.8.1"
},
"engines": {
"node": ">=18.2.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -411,6 +619,57 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/help-me": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"license": "MIT"
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ip-address": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
"integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/js-sdsl": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/js-sdsl"
}
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
@@ -684,6 +943,70 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mqtt": {
"version": "5.15.1",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.15.1.tgz",
"integrity": "sha512-V1WnkGuJh3ec9QXzy5Iylw8OOBK+Xu1WhxcQ9mMpLThG+/JZIMV1PgLNRgIiqXhZnvnVLsuyxHl5A/3bHHbcAA==",
"license": "MIT",
"dependencies": {
"@types/readable-stream": "^4.0.21",
"@types/ws": "^8.18.1",
"commist": "^3.2.0",
"concat-stream": "^2.0.0",
"debug": "^4.4.1",
"help-me": "^5.0.0",
"lru-cache": "^10.4.3",
"minimist": "^1.2.8",
"mqtt-packet": "^9.0.2",
"number-allocator": "^1.0.14",
"readable-stream": "^4.7.0",
"rfdc": "^1.4.1",
"socks": "^2.8.6",
"split2": "^4.2.0",
"worker-timers": "^8.0.23",
"ws": "^8.18.3"
},
"bin": {
"mqtt": "build/bin/mqtt.js",
"mqtt_pub": "build/bin/pub.js",
"mqtt_sub": "build/bin/sub.js"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/mqtt-packet": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-9.0.2.tgz",
"integrity": "sha512-MvIY0B8/qjq7bKxdN1eD+nrljoeaai+qjLJgfRn3TiMuz0pamsIWY2bFODPZMSNmabsLANXsLl4EMoWvlaTZWA==",
"license": "MIT",
"dependencies": {
"bl": "^6.0.8",
"debug": "^4.3.4",
"process-nextick-args": "^2.0.1"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
@@ -703,6 +1026,16 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/number-allocator": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.14.tgz",
"integrity": "sha512-OrL44UTVAvkKdOdRQZIJpLkAdjXGTRda052sN4sO77bKEzYYqWKMBjQvrJFzqygI99gL6Z4u2xctPW1tB8ErvA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.1",
"js-sdsl": "4.3.0"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -752,6 +1085,43 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/rolldown": {
"version": "1.0.0-rc.18",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz",
@@ -786,6 +1156,50 @@
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
"integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.1.1",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -796,6 +1210,24 @@
"node": ">=0.10.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
@@ -817,9 +1249,13 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD",
"optional": true
"license": "0BSD"
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
"node_modules/typescript": {
"version": "6.0.3",
@@ -835,6 +1271,18 @@
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz",
@@ -912,6 +1360,74 @@
"optional": true
}
}
},
"node_modules/worker-factory": {
"version": "7.0.49",
"resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.49.tgz",
"integrity": "sha512-lW7tpgy6aUv2dFsQhv1yv+XFzdkCf/leoKRTGMPVK5/die6RrUjqgJHJf556qO+ZfytNG6wPXc17E8zzsOLUDw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"fast-unique-numbers": "^9.0.27",
"tslib": "^2.8.1"
}
},
"node_modules/worker-timers": {
"version": "8.0.31",
"resolved": "https://registry.npmjs.org/worker-timers/-/worker-timers-8.0.31.tgz",
"integrity": "sha512-ngkq5S6JuZyztom8tDgBzorLo9byhBMko/sXfgiUD945AuzKGg1GCgDMCC3NaYkicLpGKXutONM36wEX8UbBCA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"tslib": "^2.8.1",
"worker-timers-broker": "^8.0.16",
"worker-timers-worker": "^9.0.14"
}
},
"node_modules/worker-timers-broker": {
"version": "8.0.16",
"resolved": "https://registry.npmjs.org/worker-timers-broker/-/worker-timers-broker-8.0.16.tgz",
"integrity": "sha512-JyP3AvUGyPGbBGW7XiUewm2+0pN/aYo1QpVf5kdXAfkDZcN3p7NbWrG6XnyDEpDIvfHk/+LCnOW/NsuiU9riYA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"broker-factory": "^3.1.14",
"fast-unique-numbers": "^9.0.27",
"tslib": "^2.8.1",
"worker-timers-worker": "^9.0.14"
}
},
"node_modules/worker-timers-worker": {
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/worker-timers-worker/-/worker-timers-worker-9.0.14.tgz",
"integrity": "sha512-/qF06C60sXmSLfUl7WglvrDIbspmPOM8UrG63Dnn4bi2x4/DfqHS/+dxF5B+MdHnYO5tVuZYLHdAodrKdabTIg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.29.2",
"tslib": "^2.8.1",
"worker-factory": "^7.0.49"
}
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
+3
View File
@@ -11,5 +11,8 @@
"devDependencies": {
"typescript": "~6.0.2",
"vite": "^8.0.10"
},
"dependencies": {
"mqtt": "^5.15.1"
}
}
+176 -18
View File
@@ -1,9 +1,19 @@
import mqtt from 'mqtt'
import './style.css'
type Page = 'players' | 'start' | 'info'
type Page = 'home' | 'players' | 'start' | 'text'
type Route = 'home' | 'maintenance' | 'players' | 'start' | 'text'
const storageKey = 'players'
let players: string[] = loadPlayers()
const mqttBrokerHost = '91.99.29.8'
const mqttBrokerPort = 9001
const mqttBrokerPath = '/mqtt'
const mqttUseTls = false
const mqttTopic = 'king-arthur/page/startup'
const mqttMessage = 'start-hit'
const initialPlayers = loadPlayers()
let players: string[] = []
let currentName = ''
function loadPlayers(): string[] {
@@ -26,40 +36,125 @@ function savePlayers(): void {
localStorage.setItem(storageKey, JSON.stringify(players))
}
function getPageFromHash(): Page {
const hash = window.location.hash.replace('#', '')
if (hash === 'start' || hash === 'info' || hash === 'players') {
return hash
}
return 'players'
function publishStartHit(): void {
const protocol = mqttUseTls ? 'wss' : 'ws'
const brokerUrl = `${protocol}://${mqttBrokerHost}:${mqttBrokerPort}${mqttBrokerPath}`
const client = mqtt.connect(brokerUrl)
client.on('connect', () => {
client.publish(mqttTopic, mqttMessage, { qos: 1 }, () => {
client.end()
})
})
client.on('error', (error) => {
console.error('MQTT publish failed:', error)
client.end(true)
})
}
function getRouteFromPath(): Route {
const path = window.location.pathname
if (path === '/') return 'home'
if (path === '/maintenance') return 'maintenance'
if (path === '/players') return 'players'
if (path === '/start') return 'start'
if (path === '/text') return 'text'
if (path.startsWith('/players')) return 'players'
if (path.startsWith('/start')) return 'start'
if (path.startsWith('/text')) return 'text'
return 'home'
}
function navigateTo(path: string): void {
window.history.pushState({}, '', path)
render()
}
function render(): void {
const app = document.querySelector<HTMLDivElement>('#app')
if (!app) return
const page = getPageFromHash()
const route = getRouteFromPath()
const path = window.location.pathname
if (route === 'home') {
app.innerHTML = `
<main class="app">
<section class="page-content">
${renderHomePage()}
</section>
</main>
`
} else if (path === '/maintenance') {
const maintenancePage: Page = 'players'
app.innerHTML = `
<main class="app">
<h1>Simple Player Web App</h1>
<nav class="nav">
<a href="#players" class="nav-link ${page === 'players' ? 'active' : ''}">Page 1: Players</a>
<a href="#start" class="nav-link ${page === 'start' ? 'active' : ''}">Page 2: Start</a>
<a href="#info" class="nav-link ${page === 'info' ? 'active' : ''}">Page 3: Text</a>
<a href="/players" class="nav-link active">Page 1: Players</a>
<a href="/start" class="nav-link">Page 2: Start</a>
<a href="/text" class="nav-link">Page 3: Text</a>
</nav>
<section class="page-content">
${renderPage(maintenancePage)}
</section>
</main>
`
attachNavigationHandlers(maintenancePage)
} else {
const page = route as Page
app.innerHTML = `
<main class="app">
<section class="page-content">
${renderPage(page)}
</section>
</main>
`
attachHandlers(page)
}
}
function renderHomePage(): string {
return `
<div class="home-content">
<h1>Welcome to King Arthur</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
<div class="info-section">
<div class="info-box">
<h3>Version</h3>
<p id="version-number">1.0.0</p>
</div>
<div class="info-box">
<h3>Service Hotline</h3>
<p id="service-hotline">+1 800-123-4567</p>
</div>
</div>
<nav class="home-nav">
<a href="/players" class="nav-button">Players</a>
<a href="/start" class="nav-button">Start</a>
<a href="/text" class="nav-button">Text</a>
<a href="/maintenance" class="nav-button">Maintenance</a>
</nav>
</div>
`
}
function renderPage(page: Page): string {
if (page === 'players') {
const listItems = players.map((name) => `<li>${name}</li>`).join('')
const listItems = players.map((name, index) => `
<li class="player-item">
<span class="player-name">${name}</span>
<button class="delete-btn" data-index="${index}" title="Delete player">
<svg class="trash-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-9l-1 1H5v2h14V4z" fill="currentColor"/>
</svg>
</button>
</li>
`).join('')
return `
<h2>Add players</h2>
@@ -68,6 +163,10 @@ function renderPage(page: Page): string {
<button id="add-player" type="button">Add</button>
</div>
<ul class="player-list">${listItems}</ul>
<div class="players-actions">
<button id="clear-all-btn" type="button" class="btn-secondary">Clear All</button>
<button id="finished-btn" type="button" class="btn-primary">Finished</button>
</div>
`
}
@@ -87,10 +186,43 @@ function renderPage(page: Page): string {
`
}
function attachNavigationHandlers(page: Page): void {
const navLinks = document.querySelectorAll<HTMLAnchorElement>('.nav-link')
navLinks.forEach((link) => {
link.addEventListener('click', (e) => {
e.preventDefault()
const href = link.getAttribute('href')
if (href) {
navigateTo(href)
updateActiveNavLink(page)
}
})
})
attachHandlers(page)
}
function updateActiveNavLink(page: Page): void {
const navLinks = document.querySelectorAll<HTMLAnchorElement>('.nav-link')
navLinks.forEach((link) => {
link.classList.remove('active')
const href = link.getAttribute('href')
if (
(page === 'players' && href === '/players') ||
(page === 'start' && href === '/start') ||
(page === 'text' && href === '/text')
) {
link.classList.add('active')
}
})
}
function attachHandlers(page: Page): void {
if (page === 'players') {
const input = document.querySelector<HTMLInputElement>('#player-input')
const button = document.querySelector<HTMLButtonElement>('#add-player')
const deleteButtons = document.querySelectorAll<HTMLButtonElement>('.delete-btn')
const clearAllBtn = document.querySelector<HTMLButtonElement>('#clear-all-btn')
const finishedBtn = document.querySelector<HTMLButtonElement>('#finished-btn')
if (input && button) {
const addPlayer = () => {
@@ -110,6 +242,31 @@ function attachHandlers(page: Page): void {
}
})
}
deleteButtons.forEach((btn) => {
btn.addEventListener('click', () => {
const index = parseInt(btn.getAttribute('data-index') || '0', 10)
players.splice(index, 1)
savePlayers()
render()
})
})
if (clearAllBtn) {
clearAllBtn.addEventListener('click', () => {
if (confirm('Are you sure you want to delete all players?')) {
players = []
savePlayers()
render()
}
})
}
if (finishedBtn) {
finishedBtn.addEventListener('click', () => {
window.location.href = '/start'
})
}
}
if (page === 'start') {
@@ -118,11 +275,12 @@ function attachHandlers(page: Page): void {
if (button && selectedName) {
button.addEventListener('click', () => {
publishStartHit()
if (players.length === 0) {
currentName = 'Please add players on page 1 first.'
} else {
const randomIndex = Math.floor(Math.random() * players.length)
currentName = players[randomIndex]
currentName = (players[randomIndex])
}
selectedName.textContent = currentName
})
@@ -130,10 +288,10 @@ function attachHandlers(page: Page): void {
}
}
window.addEventListener('hashchange', render)
window.addEventListener('popstate', render)
if (!window.location.hash) {
window.location.hash = '#players'
if (window.location.pathname === '/' || !window.location.pathname.startsWith('/')) {
window.history.replaceState({}, '', '/')
}
render()
+153 -5
View File
@@ -93,13 +93,161 @@ button {
cursor: pointer;
}
.player-list {
margin: 0;
padding-left: 20px;
}
#selected-name {
margin-top: 12px;
font-size: 18px;
font-weight: 600;
}
.home-content {
text-align: center;
}
.home-content h1 {
margin: 0 0 16px;
font-size: 32px;
}
.home-content p {
margin: 0 0 24px;
line-height: 1.6;
color: #4b5563;
}
.info-section {
display: flex;
gap: 16px;
margin: 32px 0;
justify-content: center;
}
.info-box {
flex: 1;
max-width: 200px;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 16px;
background: #f9fafb;
}
.info-box h3 {
margin: 0 0 8px;
font-size: 14px;
color: #6b7280;
text-transform: uppercase;
}
.info-box p {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #111827;
}
.home-nav {
display: flex;
flex-wrap: wrap;
gap: 12px;
justify-content: center;
margin-top: 32px;
}
.nav-button {
text-decoration: none;
color: #ffffff;
background: #111827;
border: 1px solid #111827;
border-radius: 8px;
padding: 12px 24px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
.nav-button:hover {
background: #374151;
border-color: #374151;
}
.player-list {
margin: 0;
padding: 0;
list-style: none;
}
.player-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
margin: 8px 0;
background: #f9fafb;
border: 1px solid #e5e7eb;
border-radius: 6px;
}
.player-name {
flex: 1;
font-weight: 500;
color: #111827;
}
.delete-btn {
background: #ef4444;
border: 1px solid #ef4444;
color: #ffffff;
padding: 6px 10px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, border-color 0.2s;
}
.delete-btn:hover {
background: #dc2626;
border-color: #dc2626;
}
.trash-icon {
width: 16px;
height: 16px;
}
.players-actions {
display: flex;
gap: 12px;
margin-top: 16px;
justify-content: flex-end;
}
.btn-primary,
.btn-secondary {
padding: 10px 16px;
border-radius: 6px;
border: none;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
.btn-primary {
background: #111827;
color: #ffffff;
}
.btn-primary:hover {
background: #1f2937;
}
.btn-secondary {
background: #e5e7eb;
color: #111827;
border: 1px solid #d1d5db;
}
.btn-secondary:hover {
background: #d1d5db;
}