diff --git a/backend/dbRequest/dbRequest.go b/backend/dbRequest/dbRequest.go
index a5cccb3..931d061 100644
--- a/backend/dbRequest/dbRequest.go
+++ b/backend/dbRequest/dbRequest.go
@@ -1,6 +1,7 @@
package dbRequest
import (
+ "backend/models"
"fmt"
"net/http"
@@ -10,25 +11,22 @@ import (
var DBCreate string = `CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
+ role TEXT NOT NULL,
password TEXT NOT NULL
);`
-var DBNewUser string = `INSERT INTO users (username, password) VALUES (?, ?)`
-var DBQueryPassword string = `SELECT password FROM users WHERE username = ?`
+var DBNewUser string = `INSERT INTO users (username, role, password) VALUES (?, ?, ?)`
+var DBQueryPassword string = `SELECT role, password FROM users WHERE username = ?`
var DBUserLookup string = `SELECT EXISTS(SELECT 1 FROM users WHERE username = ?)`
var DBRemoveUser string = `DELETE FROM users WHERE username = $1`
func CheckDBError(c *gin.Context, username string, err error) bool {
if err != nil {
if err.Error() == "sql: no rows in result set" {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": fmt.Sprintf("no user '%s' found", username),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse(fmt.Sprintf("no user '%s' found", username)))
return true
}
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return true
}
return false
diff --git a/backend/dist/bin/tecamino-driver-artNet-linux-arm64 b/backend/dist/bin/tecamino-driver-artNet-linux-arm64
new file mode 100755
index 0000000..af94250
Binary files /dev/null and b/backend/dist/bin/tecamino-driver-artNet-linux-arm64 differ
diff --git a/backend/dist/cfg/defaultConfigurations.json b/backend/dist/cfg/defaultConfigurations.json
new file mode 100644
index 0000000..5aa5db5
--- /dev/null
+++ b/backend/dist/cfg/defaultConfigurations.json
@@ -0,0 +1,28 @@
+{
+ "drivers":[{
+ "name":"ArtNet",
+ "description":"ArtNet Driver DMX over UDP/IP",
+ "executablePath":"bin/tecamino-driver-artNet-linux-arm64",
+ "workingDirectory":"bin",
+ "arguments":[
+ "-cfg './cfg'",
+ "-serverIp '127.0.0.1'",
+ "-serverPort 8100",
+ "-port 8200"
+ ]
+ },
+ {
+ "name":"OSC Driver",
+ "description":"OSC Driver Music Mixer over UDP/IP",
+ "executablePath":"bin/tecamino-driver-osc-linux-arm64",
+ "workingDirectory":"bin",
+ "arguments":[
+ "-cfg './cfg'",
+ "-serverIp '127.0.0.1'",
+ "-serverPort 8100",
+ "-port 8300"
+ ]
+ }
+
+ ]
+}
\ No newline at end of file
diff --git a/backend/drivers/drivers.go b/backend/drivers/drivers.go
new file mode 100644
index 0000000..3556ef6
--- /dev/null
+++ b/backend/drivers/drivers.go
@@ -0,0 +1,36 @@
+package drivers
+
+import (
+ "backend/models"
+ "encoding/json"
+ "net/http"
+ "os"
+
+ "github.com/gin-gonic/gin"
+)
+
+type DriverHandler struct {
+ driversConfig string
+}
+
+func NewDriverHandler(cfgDir string) *DriverHandler {
+ return &DriverHandler{driversConfig: cfgDir}
+}
+
+func (dH *DriverHandler) GetDriverList(c *gin.Context) {
+ content, err := os.ReadFile(dH.driversConfig)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
+ return
+ }
+
+ var data struct {
+ Drivers []models.Drivers `json:"drivers"`
+ }
+ err = json.Unmarshal(content, &data)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
+ return
+ }
+ c.JSON(http.StatusOK, data.Drivers)
+}
diff --git a/backend/login/login.go b/backend/login/login.go
index 9374079..cd91d94 100644
--- a/backend/login/login.go
+++ b/backend/login/login.go
@@ -19,25 +19,19 @@ import (
func (lm *LoginManager) AddUser(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
user := models.User{}
err = json.Unmarshal(body, &user)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
if !user.IsValid() {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "user empty",
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty"))
return
}
@@ -54,20 +48,16 @@ func (lm *LoginManager) AddUser(c *gin.Context) {
}
if exists {
- c.JSON(http.StatusOK, gin.H{
- "error": fmt.Sprintf("user '%s' exists already", user.Name),
- })
+ c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(fmt.Sprintf("user '%s' exists already", user.Name)))
return
}
hash, err := utils.HashPassword(user.Password)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
- if _, err := db.Exec(dbRequest.DBNewUser, user.Name, hash); dbRequest.CheckDBError(c, user.Name, err) {
+ if _, err := db.Exec(dbRequest.DBNewUser, user.Role, user.Name, hash); dbRequest.CheckDBError(c, user.Name, err) {
return
}
@@ -79,25 +69,19 @@ func (lm *LoginManager) AddUser(c *gin.Context) {
func (lm *LoginManager) RemoveUser(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
user := models.User{}
err = json.Unmarshal(body, &user)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
if !user.IsValid() {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "user empty",
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty"))
return
}
@@ -108,14 +92,12 @@ func (lm *LoginManager) RemoveUser(c *gin.Context) {
defer db.Close()
var storedPassword string
- if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&storedPassword); dbRequest.CheckDBError(c, user.Name, err) {
+ if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&storedPassword, &user.Role); dbRequest.CheckDBError(c, user.Name, err) {
return
}
if !utils.CheckPassword(user.Password, storedPassword) {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "wrong password",
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("wrong password"))
return
}
@@ -131,27 +113,19 @@ func (lm *LoginManager) RemoveUser(c *gin.Context) {
func (lm *LoginManager) Login(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
user := models.User{}
err = json.Unmarshal(body, &user)
if err != nil {
- fmt.Println(2)
-
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
if !user.IsValid() {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "user empty",
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty"))
return
}
@@ -162,41 +136,36 @@ func (lm *LoginManager) Login(c *gin.Context) {
defer db.Close()
var storedPassword string
- if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&storedPassword); dbRequest.CheckDBError(c, user.Name, err) {
+ if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&user.Role, &storedPassword); dbRequest.CheckDBError(c, user.Name, err) {
return
}
if !utils.CheckPassword(user.Password, storedPassword) {
- fmt.Println(2, user.Password)
- c.JSON(http.StatusBadRequest, gin.H{
- "error": "wrong password",
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("wrong password"))
return
}
// Create token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": user.Name,
- "exp": time.Now().Add(time.Hour * 72).Unix(), // expires in 72h
+ "role": user.Role,
+ "exp": time.Now().Add(time.Minute * 60).Unix(), // expires in 72h
})
secret, err := utils.GenerateJWTSecret(32) // 32 bytes = 256 bits
if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{
- "error": "error generate jwt token"})
+ c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("error generate jwt token"))
return
}
// Sign and get the complete encoded token as a string
tokenString, err := token.SignedString(secret)
if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{
- "error": "Could not generate token"})
+ c.JSON(http.StatusInternalServerError, models.NewJsonErrorMessageResponse("Could not generate token"))
return
}
c.JSON(http.StatusOK, models.User{
- Name: user.Name,
Token: tokenString,
})
}
diff --git a/backend/login/manager.go b/backend/login/manager.go
index 9e7e6a0..b89f277 100644
--- a/backend/login/manager.go
+++ b/backend/login/manager.go
@@ -37,7 +37,7 @@ func NewLoginManager(dir string) (*LoginManager, error) {
if err != nil {
return nil, err
}
- _, err = db.Exec(dbRequest.DBNewUser, "admin", hash)
+ _, err = db.Exec(dbRequest.DBNewUser, "admin", "admin", hash)
if err != nil {
return nil, err
}
diff --git a/backend/main.go b/backend/main.go
index 1a7d984..cb2317f 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -1,6 +1,7 @@
package main
import (
+ "backend/drivers"
"backend/login"
"backend/models"
secenes "backend/scenes"
@@ -27,6 +28,8 @@ func main() {
flag.Var(&allowOrigins, "allowOrigin", "Allowed origin (can repeat this flag)")
spa := flag.String("spa", "./dist/spa", "quasar spa files")
+ cfgDir := flag.String("cfg", "./cfg", "config dir")
+ driversCfg := flag.String("drivers", "defaultConfigurations.json", "drivers config file name")
workingDir := flag.String("workingDirectory", ".", "quasar spa files")
ip := flag.String("ip", "0.0.0.0", "server listening ip")
port := flag.Uint("port", 9500, "server listening port")
@@ -69,6 +72,9 @@ func main() {
//new scenes handler
scenesHandler := secenes.NewScenesHandler("")
+ //new scenes handler
+ driversHandler := drivers.NewDriverHandler(filepath.Join(*cfgDir, *driversCfg))
+
// new server
s := server.NewServer()
@@ -92,6 +98,7 @@ func main() {
api := s.Routes.Group("/api")
//set routes
api.GET("/loadScenes", scenesHandler.LoadScenes)
+ api.GET("/allDrivers", driversHandler.GetDriverList)
api.POST("/login", loginManager.Login)
api.POST("/user/add", loginManager.AddUser)
api.POST("/saveScene", scenesHandler.SaveScene)
@@ -104,7 +111,7 @@ func main() {
s.Routes.NoRoute(func(c *gin.Context) {
// Disallow fallback for /api paths
if strings.HasPrefix(c.Request.URL.Path, "/api") {
- c.JSON(http.StatusNotFound, gin.H{"error": "API endpoint not found"})
+ c.JSON(http.StatusNotFound, models.NewJsonErrorMessageResponse("API endpoint not found"))
return
}
// Try to serve file from SPA directory
diff --git a/backend/models/drivers.go b/backend/models/drivers.go
new file mode 100644
index 0000000..f16ecd5
--- /dev/null
+++ b/backend/models/drivers.go
@@ -0,0 +1,9 @@
+package models
+
+type Drivers struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ ExecutablePath string `json:"executablePath,omitempty"`
+ WorkingDirectory string `json:"workingDirectory,omitempty"`
+ Arguments []string `json:"arguments,omitempty"`
+}
diff --git a/backend/models/jsonResponse.go b/backend/models/jsonResponse.go
new file mode 100644
index 0000000..00eb5c6
--- /dev/null
+++ b/backend/models/jsonResponse.go
@@ -0,0 +1,20 @@
+package models
+
+type JsonResponse struct {
+ Error bool `json:"error,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+func NewJsonErrorMessageResponse(msg string) JsonResponse {
+ return JsonResponse{
+ Error: true,
+ Message: msg,
+ }
+}
+
+func NewJsonErrorResponse(err error) JsonResponse {
+ return JsonResponse{
+ Error: true,
+ Message: err.Error(),
+ }
+}
diff --git a/backend/models/lightBar.go b/backend/models/lightBar.go
deleted file mode 100644
index c84331d..0000000
--- a/backend/models/lightBar.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package models
-
-type LightBar []Value
diff --git a/backend/models/movingHead.go b/backend/models/movingHead.go
deleted file mode 100644
index bc1e564..0000000
--- a/backend/models/movingHead.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package models
-
-type MovingHead []Value
diff --git a/backend/models/user.go b/backend/models/user.go
index 73c5855..c7dce39 100644
--- a/backend/models/user.go
+++ b/backend/models/user.go
@@ -2,6 +2,7 @@ package models
type User struct {
Name string `json:"user"`
+ Role string `json:"role"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
}
diff --git a/backend/models/values.go b/backend/models/values.go
index 57cb951..0ef29c0 100644
--- a/backend/models/values.go
+++ b/backend/models/values.go
@@ -1,7 +1,9 @@
package models
type Values struct {
- MovingHead *MovingHead `json:"movingHead"`
- LightBar *LightBar `json:"lightBar"`
- Value any `json:"value"`
+ Stagelights []Value `json:"stageLights,omitempty"`
+ LightBar []Value `json:"lightBar,omitempty"`
+ FloogLights []Value `json:"floodLights,omitempty"`
+ MovingHead []Value `json:"movingHead,omitempty"`
+ Value any `json:"value"`
}
diff --git a/backend/scenes/scenes.go b/backend/scenes/scenes.go
index d048c6b..ea26e51 100644
--- a/backend/scenes/scenes.go
+++ b/backend/scenes/scenes.go
@@ -31,45 +31,35 @@ func NewScenesHandler(dir string) *ScenesHandler {
func (sh *ScenesHandler) SaveScene(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
var scene models.Scene
err = json.Unmarshal(body, &scene)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
if _, err := os.Stat(path.Join(sh.dir)); err != nil {
err := os.MkdirAll(sh.dir, 0755)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
}
f, err := os.OpenFile(path.Join(sh.dir, scene.Name+".scene"), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
defer f.Close()
_, err = f.Write(body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
@@ -81,26 +71,20 @@ func (sh *ScenesHandler) SaveScene(c *gin.Context) {
func (sh *ScenesHandler) DeleteScene(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
var scene models.Scene
err = json.Unmarshal(body, &scene)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
err = os.Remove(path.Join(sh.dir, scene.Name+".scene"))
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
@@ -114,26 +98,20 @@ func (sh *ScenesHandler) LoadScenes(c *gin.Context) {
files, err := utils.FindAllFiles("./scenes", ".scene")
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
for _, f := range files {
content, err := os.ReadFile(f)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
var scene models.Scene
err = json.Unmarshal(content, &scene)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
sceneMap[scene.Name] = scene
@@ -160,9 +138,7 @@ func (sh *ScenesHandler) LoadScenes(c *gin.Context) {
func (sh *ScenesHandler) LoadScene(c *gin.Context) {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
@@ -170,17 +146,13 @@ func (sh *ScenesHandler) LoadScene(c *gin.Context) {
err = json.Unmarshal(body, &scene)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
files, err := utils.FindAllFiles("./scenes", ".scene")
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
@@ -190,24 +162,18 @@ func (sh *ScenesHandler) LoadScene(c *gin.Context) {
}
content, err := os.ReadFile(f)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
err = json.Unmarshal(content, &scene)
if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{
- "error": err.Error(),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err))
return
}
c.JSON(http.StatusOK, scene)
return
}
- c.JSON(http.StatusBadRequest, gin.H{
- "error": fmt.Errorf("scene '%s' not found", scene.Name),
- })
+ c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse(fmt.Sprintf("scene '%s' not found", scene.Name)))
}
diff --git a/backend/user.db b/backend/user.db
index f34db53..17f0c21 100644
Binary files a/backend/user.db and b/backend/user.db differ
diff --git a/package-lock.json b/package-lock.json
index 7f56cc4..89389cb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,18 @@
{
"name": "lightcontrol",
- "version": "0.0.14",
+ "version": "0.0.21",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lightcontrol",
- "version": "0.0.14",
+ "version": "0.0.21",
"hasInstallScript": true,
"dependencies": {
"@quasar/extras": "^1.16.4",
"axios": "^1.10.0",
+ "jwt-decode": "^4.0.0",
+ "pinia": "^3.0.3",
"quasar": "^2.16.0",
"vue": "^3.4.18",
"vue-router": "^4.0.12"
@@ -1939,6 +1941,30 @@
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
+ "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.7",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
+ "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
"node_modules/@vue/eslint-config-prettier": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz",
@@ -2378,6 +2404,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/birpc": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.5.0.tgz",
+ "integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
@@ -2998,6 +3033,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -4398,6 +4448,12 @@
"he": "bin/he"
}
},
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
"node_modules/html-minifier-terser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
@@ -4696,6 +4752,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/is-wsl": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
@@ -4819,6 +4887,15 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -5132,6 +5209,12 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
"node_modules/mlly": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz",
@@ -5560,6 +5643,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "license": "MIT"
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5579,6 +5668,36 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pinia": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
+ "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^7.7.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pinia/node_modules/@vue/devtools-api": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
+ "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.7"
+ }
+ },
"node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
@@ -5954,6 +6073,12 @@
"node": ">=0.10.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/rollup": {
"version": "4.40.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
@@ -6865,6 +6990,15 @@
"source-map": "^0.6.0"
}
},
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/stack-trace": {
"version": "1.0.0-pre2",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz",
@@ -6980,6 +7114,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/superjson": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
diff --git a/package.json b/package.json
index 88ebd69..924b20b 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,8 @@
"dependencies": {
"@quasar/extras": "^1.16.4",
"axios": "^1.10.0",
+ "jwt-decode": "^4.0.0",
+ "pinia": "^3.0.3",
"quasar": "^2.16.0",
"vue": "^3.4.18",
"vue-router": "^4.0.12"
diff --git a/quasar.config.ts b/quasar.config.ts
index dc2fa00..42fb7e0 100644
--- a/quasar.config.ts
+++ b/quasar.config.ts
@@ -11,7 +11,7 @@ export default defineConfig((/* ctx */) => {
// app boot file (/src/boot)
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
- boot: ['websocket', 'axios'],
+ boot: ['websocket', 'axios', 'auth'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css
css: ['app.scss'],
diff --git a/src/boot/auth.ts b/src/boot/auth.ts
new file mode 100644
index 0000000..7fe20e2
--- /dev/null
+++ b/src/boot/auth.ts
@@ -0,0 +1,13 @@
+import { boot } from 'quasar/wrappers';
+import { createPinia } from 'pinia';
+import { useUserStore } from 'src/vueLib/login/userStore';
+import type { QVueGlobals } from 'quasar';
+
+export default boot(({ app }) => {
+ const $q = app.config.globalProperties.$q as QVueGlobals;
+ const pinia = createPinia();
+ app.use(pinia);
+ const useStore = useUserStore();
+ useStore.initStore($q);
+ useStore.loadFromStorage();
+});
diff --git a/src/boot/axios.ts b/src/boot/axios.ts
index a14d14e..7ed3dc4 100644
--- a/src/boot/axios.ts
+++ b/src/boot/axios.ts
@@ -2,19 +2,29 @@ import { boot } from 'quasar/wrappers';
import axios from 'axios';
const host = window.location.hostname;
-const port = 8100;
+const portDbm = 8100;
+const portApp = 9500;
-const api = axios.create({
- baseURL: `http://${host}:${port}`,
+const dbmApi = axios.create({
+ baseURL: `http://${host}:${portDbm}`,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
});
+const appApi = axios.create({
+ baseURL: `http://${host}:${portApp}/api`,
+ timeout: 10000,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+});
+
export default boot(({ app }) => {
app.config.globalProperties.$axios = axios;
- app.config.globalProperties.$api = api;
+ app.config.globalProperties.$dbmApi = dbmApi;
+ app.config.globalProperties.$appApi = appApi;
});
-export { axios, api };
+export { axios, dbmApi, appApi };
diff --git a/src/components/lights/FloodPanel.vue b/src/components/lights/FloodPanel.vue
new file mode 100644
index 0000000..2179b88
--- /dev/null
+++ b/src/components/lights/FloodPanel.vue
@@ -0,0 +1,98 @@
+
+