Compare commits
2 Commits
6c43af50c0
...
3f0e821d0d
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f0e821d0d | |||
| 2bb5125254 |
@@ -1,3 +1,15 @@
|
||||
docker build -t kingArthurApp:latest .
|
||||
docker save kingArthurApp:latest | gzip > kingArthurApp.tar.gz
|
||||
scp kingArthurApp.tar.gz youruser@your_server_ip:~/
|
||||
# docker build -t kingArthurApp:latest .
|
||||
# docker save kingArthurApp:latest | gzip > kingArthurApp.tar.gz
|
||||
# scp kingArthurApp.tar.gz youruser@your_server_ip:~/
|
||||
|
||||
# add login key first
|
||||
# > docker login git.nerdyssey.de
|
||||
# > Username: dockerLogin
|
||||
# > Passwrord: appkey
|
||||
# [DOMAIN]/[OWNER]/[IMAGE_NAME]:[TAG]
|
||||
docker build -t git.nerdyssey.de/exodus/burg-stett-king-arth-web:latest .
|
||||
docker push git.nerdyssey.de/exodus/burg-stett-king-arth-web:latest
|
||||
|
||||
#
|
||||
# docker compose pull
|
||||
# docker compose up -d
|
||||
@@ -0,0 +1,46 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
gitea-server:
|
||||
image: gitea/gitea:latest
|
||||
container_name: gitea
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
# Database connection
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea-db:5432
|
||||
- GITEA__database__NAME=gitea
|
||||
- GITEA__database__USER=gitea
|
||||
- GITEA__database__PASSWD=###SomePassword###
|
||||
# Web / Domain settings
|
||||
- GITEA__server__DOMAIN=git.nerdyssey.de
|
||||
- GITEA__server__ROOT_URL=https://git.nerdyssey.de/
|
||||
- GITEA__server__SSH_DOMAIN=git.nerdyssey.de
|
||||
- GITEA__server__SSH_PORT=2222
|
||||
# Security
|
||||
- GITEA__service__DISABLE_REGISTRATION=true
|
||||
- GITEA__service__REQUIRE_SIGNIN_VIEW=false
|
||||
volumes:
|
||||
- ./data:/data
|
||||
ports:
|
||||
- "2222:22" # This allows Git over SSH
|
||||
depends_on:
|
||||
- gitea-db
|
||||
|
||||
gitea-db:
|
||||
image: postgres:15-alpine
|
||||
container_name: gitea-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=gitea
|
||||
- POSTGRES_PASSWORD=###SomePassword###
|
||||
- POSTGRES_DB=gitea
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: proxy_net
|
||||
external: true
|
||||
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
mosquitto:
|
||||
image: eclipse-mosquitto:latest
|
||||
container_name: mosquitto
|
||||
restart: unless-stopped
|
||||
# Power are managed via private subnet "proxy_net"
|
||||
# ports:
|
||||
# - "1883:1883"
|
||||
# - "9001:9001"
|
||||
volumes:
|
||||
- ./config:/mosquitto/config
|
||||
- ./data:/mosquitto/data
|
||||
- ./log:/mosquitto/log
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: proxy_net
|
||||
external: true
|
||||
@@ -0,0 +1,20 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
nginx-proxy-manager:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
container_name: nginx-proxy-manager
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '80:80' # Public HTTP Port (Required for Certbot challenges)
|
||||
- '443:443' # Public HTTPS Port
|
||||
- '81:81' # Admin Web Dashboard
|
||||
- '8883:8883' # Added for Secure MQTT (MQTTS)
|
||||
volumes:
|
||||
- ./data:/data
|
||||
- ./letsencrypt:/etc/letsencrypt
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: proxy_net
|
||||
external: true
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# MQTT Commands
|
||||
|
||||
## simple
|
||||
|
||||
```sh
|
||||
mosquitto_sub -h 91.99.29.8 -t outdoor/king-arthur/sword/run
|
||||
```
|
||||
|
||||
## safe
|
||||
|
||||
```sh
|
||||
mosquitto_sub -L mqtts://nerdyssey.de:8883/outdoor/king-arthur/sword/run -d
|
||||
mosquitto_sub -L mqtts://nerdyssey.de:8883/outdoor/king-arthur/sword/int -d
|
||||
|
||||
mosquitto_pub -L mqtts://nerdyssey.de:8883 -t outdoor/king-arthur/sword/run -m "SetMaintanance"
|
||||
```
|
||||
|
||||
- `+` Single-level wildcard: `t1/+/sub2/sub3`
|
||||
- `#` Multi-level wildcard: `t1/#/sub3`, `t1/#`
|
||||
|
||||
```sh
|
||||
# Publishing topic with message
|
||||
# -h: Host (optional)
|
||||
# -t: Topic
|
||||
# -m: Message
|
||||
# -q: Quality of service
|
||||
# -r: Retain (keep this after failed connection)
|
||||
# -h: Print help info
|
||||
mosquitto_pub -t someTopic -m 123 -q 1
|
||||
mosquitto_pub -r -t someTopic -m "on"
|
||||
mosquitto_pub -h 91.99.29.8 -t someTopic -m 123 -q 1
|
||||
|
||||
# Subscribing to
|
||||
# -h: Host (optional)
|
||||
# -t: Topic s
|
||||
# + is wildcard
|
||||
# # is wildcard for full subtree
|
||||
# -T: Exclude topic
|
||||
# -q: Quality of service
|
||||
mosquitto_sub -t someTopic -q 1
|
||||
mosquitto_sub -t someTopic/+/subTopic
|
||||
mosquitto_sub -t bbc/# -T bbc/radio3
|
||||
mosquitto_sub -h 91.99.29.8 -t someTopic -q 1
|
||||
```
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
```sh
|
||||
# install docker
|
||||
sudo apt install ca-certificates curl -y
|
||||
# Add Docker's official GPG key:
|
||||
sudo apt update
|
||||
sudo apt install ca-certificates curl
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||||
# Add the repository to Apt sources:
|
||||
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
|
||||
Types: deb
|
||||
URIs: https://download.docker.com/linux/ubuntu
|
||||
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
|
||||
Components: stable
|
||||
Architectures: $(dpkg --print-architecture)
|
||||
Signed-By: /etc/apt/keyrings/docker.asc
|
||||
EOF
|
||||
# install and test
|
||||
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
sudo systemctl status docker
|
||||
sudo docker run hello-world
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
docker run hello-world
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo apt install ufw -y
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow ssh
|
||||
sudo ufw enable
|
||||
sudo ufw allow ssh
|
||||
sudo ufw status verbose
|
||||
sudo nano /etc/ssh/sshd_config
|
||||
sudo systemctl restart ssh
|
||||
sudo ufw allow 80/tcp
|
||||
sudo ufw allow 8080/tcp
|
||||
sudo ufw allow 1883/tcp # mqtt
|
||||
sudo ufw allow 9001/tcp # mqtt ws
|
||||
sudo ufw allow 8883/tcp # secure mqtt
|
||||
sudo ufw allow 2222/tcp # secure mqtt
|
||||
```
|
||||
|
||||
```sh
|
||||
mkdir -p ~/mosquitto/config
|
||||
mkdir -p ~/mosquitto/data
|
||||
mkdir -p ~/mosquitto/log
|
||||
nano ~/mosquitto/config/mosquitto.conf
|
||||
nano ~/mosquitto/docker-compose.yml
|
||||
docker compose up -d
|
||||
```
|
||||
todo add also config files
|
||||
|
||||
```sh
|
||||
docker load <~/my-vite-app.tar.gz
|
||||
docker images
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
# Secure Shell
|
||||
|
||||
```sh
|
||||
ssh -o PubkeyAuthentication=no gustice@91.99.29.8
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
exodus-burg-stett-king-arth-web:
|
||||
image: git.nerdyssey.de/exodus/burg-stett-king-arth-web:latest
|
||||
container_name: exodus-burg-stett-king-arth-web
|
||||
restart: unless-stopped
|
||||
# ports:
|
||||
# - "80:80"
|
||||
# - "443:443"
|
||||
# environment:
|
||||
# - VITE_APP_ENV=production
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: proxy_net
|
||||
external: true
|
||||
Generated
+9
-6
@@ -14,7 +14,7 @@
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.1.0",
|
||||
"vue-tsc": "^3.3.5"
|
||||
@@ -465,16 +465,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
|
||||
"version": "6.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz",
|
||||
"integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rolldown/pluginutils": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.0.0 || ^6.0.0",
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.1.0",
|
||||
"vue-tsc": "^3.3.5"
|
||||
|
||||
+1
-2
@@ -5,5 +5,4 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -5,6 +5,8 @@ import Players from '../views/Players.vue'
|
||||
import Start from '../views/Start.vue'
|
||||
import Text from '../views/Text.vue'
|
||||
|
||||
const routeBase: string = "nerdyssey.de/exodus/king-arthur"
|
||||
|
||||
const routes: any[] = [
|
||||
{
|
||||
path: '/',
|
||||
|
||||
+144
-8
@@ -1,26 +1,34 @@
|
||||
import mqtt from 'mqtt'
|
||||
|
||||
const mqttBrokerHost = '91.99.29.8'
|
||||
const mqttBrokerPort = 9001
|
||||
const mqttBrokerHost = 'mqtt.nerdyssey.de'
|
||||
const mqttBrokerPort = 443 // 9001 if not secure
|
||||
const mqttBrokerPath = '/mqtt'
|
||||
const mqttUseTls = false
|
||||
const mqttTopic = 'king-arthur/page/startup'
|
||||
const mqttUseTls = true
|
||||
const mqttTopicRoot = 'outdoor/king-arthur/sword/'
|
||||
const mqttMessage = 'start-hit'
|
||||
|
||||
let client: any = null
|
||||
|
||||
function getClient(): mqtt.MqttClient {
|
||||
const protocol = mqttUseTls ? 'wss' : 'ws'
|
||||
const brokerUrl = `${protocol}://${mqttBrokerHost}:${mqttBrokerPort}${mqttBrokerPath}`
|
||||
const tempClient = mqtt.connect(brokerUrl)
|
||||
console.log(`MQTT Connection string ${brokerUrl}`)
|
||||
return tempClient
|
||||
}
|
||||
|
||||
export function getMqttClient(): any {
|
||||
return client
|
||||
}
|
||||
|
||||
export function publishStartHit(): void {
|
||||
const protocol = mqttUseTls ? 'wss' : 'ws'
|
||||
const brokerUrl = `${protocol}://${mqttBrokerHost}:${mqttBrokerPort}${mqttBrokerPath}`
|
||||
const tempClient = mqtt.connect(brokerUrl)
|
||||
const tempClient = getClient()
|
||||
|
||||
const mqttTopic = mqttTopicRoot + 'run'
|
||||
|
||||
tempClient.on('connect', () => {
|
||||
tempClient.publish(mqttTopic, mqttMessage, { qos: 1 }, () => {
|
||||
tempClient.end()
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,3 +37,131 @@ export function publishStartHit(): void {
|
||||
tempClient.end(true)
|
||||
})
|
||||
}
|
||||
|
||||
export function subscribeToIntTopic(callback: (response: IntTopicResponse) => void): mqtt.MqttClient {
|
||||
const tempClient = getClient()
|
||||
const mqttTopic = mqttTopicRoot + 'int'
|
||||
|
||||
tempClient.on('connect', () => {
|
||||
tempClient.subscribe(mqttTopic, { qos: 1 }, (error) => {
|
||||
if (error) {
|
||||
console.error('MQTT subscribe failed:', error)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
tempClient.on('message', (topic: string, message: Buffer) => {
|
||||
if (topic === mqttTopic) {
|
||||
const parsedMessage = parseIntMessage(message.toString())
|
||||
callback(parsedMessage)
|
||||
}
|
||||
})
|
||||
|
||||
tempClient.on('error', (error: any) => {
|
||||
console.error('MQTT connection error:', error)
|
||||
})
|
||||
|
||||
return tempClient
|
||||
}
|
||||
|
||||
interface IntTopicResponse {
|
||||
version: string
|
||||
mode: number
|
||||
sE: boolean
|
||||
sS: boolean
|
||||
sP: boolean
|
||||
lE: boolean
|
||||
}
|
||||
|
||||
export function parseIntMessage(message: string): IntTopicResponse {
|
||||
const result: IntTopicResponse = {
|
||||
version: '',
|
||||
mode: 0,
|
||||
sE: false, // EndPoint
|
||||
sS: false, // StartPoint
|
||||
sP: false, // PullSensor
|
||||
lE: false // LatchEnable
|
||||
}
|
||||
|
||||
const versionMatch = message.match(/T([\d.]+)/)
|
||||
if (versionMatch) {
|
||||
result.version = versionMatch[1]
|
||||
}
|
||||
const modeMatch = message.match(/Mode=(\d+)/)
|
||||
if (modeMatch) {
|
||||
result.mode = parseInt(modeMatch[1], 10)
|
||||
}
|
||||
const sEMatch = message.match(/sE:(true|false)/)
|
||||
if (sEMatch) {
|
||||
result.sE = sEMatch[1] === 'true'
|
||||
}
|
||||
const sSMatch = message.match(/sS:(true|false)/)
|
||||
if (sSMatch) {
|
||||
result.sS = sSMatch[1] === 'true'
|
||||
}
|
||||
const sPMatch = message.match(/sP:(true|false)/)
|
||||
if (sPMatch) {
|
||||
result.sP = sPMatch[1] === 'true'
|
||||
}
|
||||
const lEMatch = message.match(/lE:(true|false)/)
|
||||
if (lEMatch) {
|
||||
result.lE = lEMatch[1] === 'true'
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function enterMaintananceMode(
|
||||
callback: (sE: number, sS: number, sP: number, lE: number) => void)
|
||||
: mqtt.MqttClient {
|
||||
const client = getClient()
|
||||
|
||||
const mqttTopic = mqttTopicRoot + 'stat'
|
||||
|
||||
client.on('connect', () => {
|
||||
publicMaintananceCmd(client, "SetMaintanance")
|
||||
client.subscribe(mqttTopic, { qos: 1 }, (error) => {
|
||||
if (error) {
|
||||
console.error('MQTT subscribe failed:', error)
|
||||
}
|
||||
})
|
||||
})
|
||||
client.on('error', (error: any) => {
|
||||
console.error('MQTT operation failed:', error)
|
||||
client.end(true)
|
||||
})
|
||||
|
||||
client.on('message', (topic: string, message: Buffer) => {
|
||||
if (topic === mqttTopic) {
|
||||
const messageStr = message.toString()
|
||||
const status = parseIntMessage(message.toString())
|
||||
const sE = status.sE ? 1 : 0
|
||||
const sS = status.sS ? 1 : 0
|
||||
const sP = status.sP ? 1 : 0
|
||||
const lE = status.lE ? 1 : 0
|
||||
callback(sE, sS, sP, lE)
|
||||
}
|
||||
})
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
export function exitMaintananceMode(client: mqtt.MqttClient): void {
|
||||
publicMaintananceCmd(client, "Exit")
|
||||
const mqttTopic = mqttTopicRoot + 'stat'
|
||||
client.unsubscribe(mqttTopic, (error) => {
|
||||
if (error) {
|
||||
console.error('MQTT unsubscribe failed:', error)
|
||||
}
|
||||
})
|
||||
client.end()
|
||||
}
|
||||
|
||||
export function publicMaintananceCmd(client: mqtt.MqttClient, message : string): void {
|
||||
const mqttTopic = mqttTopicRoot + 'run'
|
||||
console.log(`MQTT: Publish ${mqttTopic}: ${message}`)
|
||||
client.publish(mqttTopic, message, { qos: 1 }, (error) => {
|
||||
if (error) {
|
||||
console.error('MQTT publish failed:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,9 +2,12 @@
|
||||
<main class="app">
|
||||
<nav class="nav">
|
||||
<router-link to="/" class="nav-link">Home</router-link>
|
||||
<router-link to="/players" class="nav-link" :class="{ active: route.path === '/players' }">Page 1: Players</router-link>
|
||||
<router-link to="/start" class="nav-link" :class="{ active: route.path === '/start' }">Page 2: Start</router-link>
|
||||
<router-link to="/text" class="nav-link" :class="{ active: route.path === '/text' }">Page 3: Text</router-link>
|
||||
<router-link to="/players" class="nav-link" :class="{ active: route.path === '/players' }">
|
||||
Page 1: Players</router-link>
|
||||
<router-link to="/start" class="nav-link" :class="{ active: route.path === '/start' }">
|
||||
Page 2: Start</router-link>
|
||||
<router-link to="/text" class="nav-link" :class="{ active: route.path === '/text' }">
|
||||
Page 3: Text</router-link>
|
||||
</nav>
|
||||
<section class="page-content">
|
||||
<Players v-if="route.path === '/players'" />
|
||||
|
||||
+184
-4
@@ -1,10 +1,52 @@
|
||||
<template>
|
||||
<main class="app">
|
||||
<h1>Simple Player Web App</h1>
|
||||
<h1>Maintanance View</h1>
|
||||
|
||||
<h2>Sword Status</h2>
|
||||
<div class="status-container">
|
||||
<h3>Inputs</h3>
|
||||
<div class="status-frame">
|
||||
<div class="status-item" :class="{ active: endSensStatus === 1 }">
|
||||
<span class="status-label">EndSens:</span>
|
||||
<span class="status-value">{{ endSensStatus }}</span>
|
||||
</div>
|
||||
<div class="status-item" :class="{ active: startSensStatus === 1 }">
|
||||
<span class="status-label">StartSens:</span>
|
||||
<span class="status-value">{{ startSensStatus }}</span>
|
||||
</div>
|
||||
<div class="status-item" :class="{ active: pullSensStatus === 1 }">
|
||||
<span class="status-label">PullSens:</span>
|
||||
<span class="status-value">{{ pullSensStatus }}</span>
|
||||
</div>
|
||||
<div class="status-item" :class="{ active: lockStatus === 1 }">
|
||||
<span class="status-label">Lock:</span>
|
||||
<span class="status-value">{{ lockStatus }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Outputs</h3>
|
||||
<div class="status-frame">
|
||||
<div class="control-buttons">
|
||||
<button @click="handleLock" class="btn-lock">Lock</button>
|
||||
<button @click="handleUnlock" class="btn-unlock">Unlock</button>
|
||||
<button @click="handlePlayX(1)" class="btn-unlock">Play sound 1</button>
|
||||
<button @click="handlePlayX(2)" class="btn-unlock">Play sound 2</button>
|
||||
<button @click="handlePlayX(3)" class="btn-unlock">Play sound 3</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Status</h3>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Game Views</h2>
|
||||
<nav class="nav">
|
||||
<router-link to="/maintenance/players" class="nav-link" :class="{ active: isActive('/maintenance/players') }">Page 1: Players</router-link>
|
||||
<router-link to="/maintenance/start" class="nav-link" :class="{ active: isActive('/maintenance/start') }">Page 2: Start</router-link>
|
||||
<router-link to="/maintenance/text" class="nav-link" :class="{ active: isActive('/maintenance/text') }">Page 3: Text</router-link>
|
||||
<router-link to="/maintenance/players" class="nav-link" :class="{ active: isActive('/maintenance/players') }">Page
|
||||
1: Players</router-link>
|
||||
<router-link to="/maintenance/start" class="nav-link" :class="{ active: isActive('/maintenance/start') }">Page 2:
|
||||
Start</router-link>
|
||||
<router-link to="/maintenance/text" class="nav-link" :class="{ active: isActive('/maintenance/text') }">Page 3:
|
||||
Text</router-link>
|
||||
</nav>
|
||||
<section class="page-content">
|
||||
<router-view></router-view>
|
||||
@@ -13,11 +55,149 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { enterMaintananceMode, exitMaintananceMode, publicMaintananceCmd } from '../services/mqtt'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import type mqtt from 'mqtt'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const endSensStatus = ref<number>(0)
|
||||
const startSensStatus = ref<number>(0)
|
||||
const pullSensStatus = ref<number>(0)
|
||||
const lockStatus = ref<number>(0)
|
||||
|
||||
let statTopicClient: mqtt.MqttClient | null = null
|
||||
|
||||
function isActive(path: string): boolean {
|
||||
return route.path === path
|
||||
}
|
||||
|
||||
function cleanup(): void {
|
||||
if (statTopicClient) {
|
||||
exitMaintananceMode(statTopicClient)
|
||||
}
|
||||
}
|
||||
|
||||
function handleLock(): void {
|
||||
if (statTopicClient) {
|
||||
publicMaintananceCmd(statTopicClient, "ServiceClose")
|
||||
}
|
||||
}
|
||||
|
||||
function handleUnlock(): void {
|
||||
if (statTopicClient) {
|
||||
publicMaintananceCmd(statTopicClient, "ServiceOpen")
|
||||
}
|
||||
}
|
||||
|
||||
function handlePlayX(sound: number): void {
|
||||
if (statTopicClient) {
|
||||
if (sound === 1) {
|
||||
publicMaintananceCmd(statTopicClient, "ServicePlay1")
|
||||
} else if (sound === 2) {
|
||||
publicMaintananceCmd(statTopicClient, "ServicePlay2")
|
||||
} else if (sound === 3) {
|
||||
publicMaintananceCmd(statTopicClient, "ServicePlay3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
statTopicClient = enterMaintananceMode((endSens, startSens, pullSens, lock) => {
|
||||
endSensStatus.value = endSens
|
||||
startSensStatus.value = startSens
|
||||
pullSensStatus.value = pullSens
|
||||
lockStatus.value = lock
|
||||
})
|
||||
window.addEventListener('beforeunload', cleanup)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('beforeunload', cleanup)
|
||||
cleanup()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.status-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-frame {
|
||||
border: 2px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background: #f9fafb;
|
||||
margin-bottom: 16px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
background: #e5e7eb;
|
||||
color: #6b7280;
|
||||
transition: background 0.3s, color 0.3s;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-item.active {
|
||||
background: #10b981;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.control-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-lock,
|
||||
.btn-unlock {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.btn-lock {
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-lock:hover {
|
||||
background: #1f2937;
|
||||
}
|
||||
|
||||
.btn-unlock {
|
||||
background: #e5e7eb;
|
||||
color: #111827;
|
||||
border: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.btn-unlock:hover {
|
||||
background: #d1d5db;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,12 +2,7 @@
|
||||
<div>
|
||||
<h2>Add players</h2>
|
||||
<div class="row">
|
||||
<input
|
||||
v-model="playerInput"
|
||||
type="text"
|
||||
placeholder="Enter player name"
|
||||
@keydown.enter="addPlayer"
|
||||
/>
|
||||
<input v-model="playerInput" type="text" placeholder="Enter player name" @keydown.enter="addPlayer" />
|
||||
<button @click="addPlayer" type="button">Add</button>
|
||||
</div>
|
||||
<ul class="player-list">
|
||||
@@ -15,7 +10,8 @@
|
||||
<span class="player-name">{{ player }}</span>
|
||||
<button class="delete-btn" @click="deletePlayer(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"/>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user