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 @@ + + + diff --git a/src/components/lights/FloodPanels.vue b/src/components/lights/FloodPanels.vue new file mode 100644 index 0000000..1c0917e --- /dev/null +++ b/src/components/lights/FloodPanels.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/lights/LightBarCBL.vue b/src/components/lights/LightBarCBL.vue index 348418f..9ba2043 100644 --- a/src/components/lights/LightBarCBL.vue +++ b/src/components/lights/LightBarCBL.vue @@ -56,10 +56,6 @@ color="blue" class="q-ma-sm" > -
- Settings - -
@@ -67,16 +63,14 @@ diff --git a/src/components/lights/StageLights.vue b/src/components/lights/StageLights.vue new file mode 100644 index 0000000..518ffcd --- /dev/null +++ b/src/components/lights/StageLights.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/scenes/ScenesPage.vue b/src/components/scenes/ScenesPage.vue index 89d61e0..4f5fd10 100644 --- a/src/components/scenes/ScenesPage.vue +++ b/src/components/scenes/ScenesPage.vue @@ -27,8 +27,10 @@ />
- + + +
@@ -52,6 +54,7 @@ > No scenes available ({ name: '', - movingHead: false, + stageLights: false, lightBar: false, + floodPanels: false, + movingHead: false, }); const scenes = ref([]); -const host = window.location.hostname; -const port = 9500; -const baseURL = `http://${host}:${port}`; - -const quasarApi = axios.create({ - baseURL: baseURL, - timeout: 10000, - headers: { - 'Content-Type': 'application/json', - }, -}); onMounted(() => { - quasarApi - .get('/api/loadScenes') + appApi + .get('/loadScenes') .then((resp) => { if (resp.data) { scenes.value = resp.data; @@ -166,9 +161,8 @@ function removeScene(name: string) { .then((res) => { if (res) { scenes.value = scenes.value.filter((s) => s.name !== name); - - quasarApi - .delete('/api/deleteScene', { + appApi + .delete('/deleteScene', { data: { name }, }) .then((res) => { @@ -190,8 +184,11 @@ function openDialog(dialogType: string, scene?: Scene, index?: number) { dialog.value = 'add'; dialogLabel.value = 'Add Scene'; newScene.name = ''; - newScene.movingHead = true; + newScene.stageLights = true; newScene.lightBar = true; + newScene.floodPanels = true; + newScene.movingHead = true; + break; case 'edit': if (!scene) return; @@ -205,8 +202,8 @@ function openDialog(dialogType: string, scene?: Scene, index?: number) { if (!scene) return; dialog.value = 'load'; dialogLabel.value = 'Load Scene'; - quasarApi - .post('/api/loadScene', scene) + appApi + .post('/loadScene', scene) .then((res) => { if (res.data) { Object.assign(newScene, JSON.parse(JSON.stringify(res.data))); @@ -236,9 +233,9 @@ const saveScene = async () => { return; } - if (newScene.movingHead) { + if (newScene.stageLights) { sendValues.push({ - path: 'MovingHead', + path: 'StageLights', query: { depth: 0 }, }); } @@ -250,9 +247,23 @@ const saveScene = async () => { }); } + if (newScene.floodPanels) { + sendValues.push({ + path: 'FloodPanels', + query: { depth: 0 }, + }); + } + + if (newScene.movingHead) { + sendValues.push({ + path: 'MovingHead', + query: { depth: 0 }, + }); + } + if (sendValues.length > 0) { try { - const res = await api.post('/json_data', { get: sendValues }); + const res = await dbmApi.post('/json_data', { get: sendValues }); newScene.values = res.data.get; } catch (err) { NotifyResponse(err as Error, 'error'); @@ -266,8 +277,8 @@ const saveScene = async () => { // Sort alphabetically by scene name scenes.value.sort((a, b) => a.name.localeCompare(b.name)); - quasarApi - .post('/api/saveScene', JSON.stringify(newScene)) + appApi + .post('/saveScene', JSON.stringify(newScene)) .then((res) => { if (res.data) { NotifyResponse(res.data); @@ -285,9 +296,9 @@ const saveScene = async () => { return; } - if (newScene.movingHead) { + if (newScene.stageLights) { sendValues.push({ - path: 'MovingHead', + path: 'StageLights', query: { depth: 0 }, }); } @@ -299,9 +310,23 @@ const saveScene = async () => { }); } + if (newScene.floodPanels) { + sendValues.push({ + path: 'FloodPanels', + query: { depth: 0 }, + }); + } + + if (newScene.movingHead) { + sendValues.push({ + path: 'MovingHead', + query: { depth: 0 }, + }); + } + if (sendValues.length > 0) { try { - const res = await api.post('/json_data', { get: sendValues }); + const res = await dbmApi.post('/json_data', { get: sendValues }); newScene.values = res.data.get; } catch (err) { NotifyResponse(err as Error, 'error'); @@ -313,8 +338,8 @@ const saveScene = async () => { scenes.value.sort((a, b) => a.name.localeCompare(b.name)); scenes.value = [...scenes.value]; - quasarApi - .post('/api/saveScene', JSON.stringify(newScene)) + appApi + .post('/saveScene', JSON.stringify(newScene)) .then((res) => { if (res.data) { NotifyResponse(res.data); @@ -333,11 +358,17 @@ const saveScene = async () => { newScene.values?.forEach((element) => { if (!element.path) return; - if (newScene.movingHead && element.path.includes('MovingHead')) + if (newScene.stageLights && element.path.includes('StageLights')) setPaths.push({ uuid: element.uuid, path: element.path, value: element.value }); if (newScene.lightBar && element.path.includes('LightBar')) setPaths.push({ uuid: element.uuid, path: element.path, value: element.value }); + + if (newScene.floodPanels && element.path.includes('FloodPanels')) + setPaths.push({ uuid: element.uuid, path: element.path, value: element.value }); + + if (newScene.movingHead && element.path.includes('MovingHead')) + setPaths.push({ uuid: element.uuid, path: element.path, value: element.value }); }); setValues(setPaths) diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue index df36b8a..662dfc3 100644 --- a/src/layouts/MainLayout.vue +++ b/src/layouts/MainLayout.vue @@ -10,10 +10,11 @@ /> - Light Control + {{ productName }}
Version {{ version }}
+ @@ -25,9 +26,12 @@ Scenes - + Data + + Services + @@ -38,10 +42,14 @@ diff --git a/src/router/index.ts b/src/router/index.ts index 890b7d2..4a6555c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -19,7 +19,9 @@ import routes from './routes'; export default defineRouter(function (/* { store, ssrContext } */) { const createHistory = process.env.SERVER ? createMemoryHistory - : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory); + : process.env.VUE_ROUTER_MODE === 'history' + ? createWebHistory + : createWebHashHistory; const Router = createRouter({ scrollBehavior: () => ({ left: 0, top: 0 }), @@ -30,6 +32,5 @@ export default defineRouter(function (/* { store, ssrContext } */) { // quasar.conf.js -> build -> publicPath history: createHistory(process.env.VUE_ROUTER_BASE), }); - return Router; }); diff --git a/src/router/routes.ts b/src/router/routes.ts index 5c54edb..ed8420b 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -6,8 +6,16 @@ const routes: RouteRecordRaw[] = [ component: () => import('layouts/MainLayout.vue'), children: [ { path: '', component: () => import('pages/MainPage.vue') }, - { path: '/data', component: () => import('pages/DataPage.vue') }, + { + path: '/data', + component: () => import('pages/DataPage.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + }, { path: '/scenes', component: () => import('components/scenes/ScenesPage.vue') }, + { + path: '/services', + component: () => import('pages/ServicesPage.vue'), + }, ], }, diff --git a/src/vueLib/dbm/DataTable.vue b/src/vueLib/dbm/DataTable.vue index fa64b61..ead1ffe 100644 --- a/src/vueLib/dbm/DataTable.vue +++ b/src/vueLib/dbm/DataTable.vue @@ -60,7 +60,12 @@ - + { - console.log(11, sub); if (sub.path?.split(':')[0] === 'System' && sub.path !== 'DBM') return; switch (type) { case 'type': @@ -103,7 +107,7 @@ const openDialog = (sub: Subscribe, type?: string) => { updateDialog.value?.open(ref(sub), type); break; case 'drivers': - if (sub.type === 'NONE') return; + if (sub.path === 'DBM' || sub.type === 'NONE') return; updateDriverDialog.value?.open(sub); break; } diff --git a/src/vueLib/dbm/SubMenu.vue b/src/vueLib/dbm/SubMenu.vue index 85eb24d..e25aa34 100644 --- a/src/vueLib/dbm/SubMenu.vue +++ b/src/vueLib/dbm/SubMenu.vue @@ -102,7 +102,7 @@ - + - + - {{ - props.buttonOkLabel - }} +
+ {{ + props.buttonOkLabel + }} +
@@ -132,6 +139,10 @@ const props = defineProps({ type: String, default: '300px', }, + height: { + type: String, + default: '650px', + }, }); function getDatapoint(uuid: string) { diff --git a/src/vueLib/dbm/dialog/CopyDatapoint.vue b/src/vueLib/dbm/dialog/CopyDatapoint.vue index 1ae7d0c..7206c61 100644 --- a/src/vueLib/dbm/dialog/CopyDatapoint.vue +++ b/src/vueLib/dbm/dialog/CopyDatapoint.vue @@ -1,5 +1,10 @@