diff --git a/groupTasks/groups/production1/index.json b/groupTasks/groups/production1/index.json index a9c04e1..98c3ff2 100644 --- a/groupTasks/groups/production1/index.json +++ b/groupTasks/groups/production1/index.json @@ -18,7 +18,7 @@ "name": "Bild zu Label konvertieren", "onFinish": "pause", "undoPossible": false, - "scriptPath": "", + "scriptPath": "test1.py", "parameter": [ { "parameterName": "labelformat", @@ -39,7 +39,23 @@ "name": "Label drucken", "onFinish": "nextStep", "undoPossible": false, - "scriptPath": "", + "scriptPath": "test2.py", + "parameter": [], + "feedback": "" + }, + { + "name": "Label 1", + "onFinish": "nextStep", + "undoPossible": false, + "scriptPath": "test2.py", + "parameter": [], + "feedback": "" + }, + { + "name": "Label 2", + "onFinish": "nextStep", + "undoPossible": false, + "scriptPath": "test2.py", "parameter": [], "feedback": "" } diff --git a/groupTasks/groups/production1/test1.py b/groupTasks/groups/production1/test1.py new file mode 100644 index 0000000..8acf58b --- /dev/null +++ b/groupTasks/groups/production1/test1.py @@ -0,0 +1,11 @@ +import time + +time.sleep(3) + +print("hello this is test1") + +try: + x = 5 / 0 # Hier wird ein Fehler ausgelöst +except ZeroDivisionError: + print("Ein Fehler ist aufgetreten: Division durch Null.") + raise SystemExit(0) \ No newline at end of file diff --git a/groupTasks/groups/production1/test2.py b/groupTasks/groups/production1/test2.py new file mode 100644 index 0000000..401b0cd --- /dev/null +++ b/groupTasks/groups/production1/test2.py @@ -0,0 +1,5 @@ +import time + +time.sleep(5) + +print("hello this is test2 lul") \ No newline at end of file diff --git a/grouptasks/grouptasks.go b/grouptasks/grouptasks.go index 86b7693..4b19bf4 100644 --- a/grouptasks/grouptasks.go +++ b/grouptasks/grouptasks.go @@ -2,70 +2,23 @@ package grouptasks import ( "encoding/json" + "fmt" + "janex/admin-dashboard-backend/modules/cache" + "janex/admin-dashboard-backend/modules/database" + "janex/admin-dashboard-backend/modules/structs" + "janex/admin-dashboard-backend/modules/utils" + "janex/admin-dashboard-backend/socketclients" "os" + "os/exec" + "time" + "github.com/google/uuid" "github.com/rs/zerolog/log" ) -// read from file structure - -var CategoryGroups []CategoryGroup - -type CategoryGroup struct { - Category string `json:"category"` - Groups []Group `json:"groups"` -} - -type Group struct { - Category string `json:"category"` - Id string `json:"id"` - Name string `json:"name"` - GlobalInputs []GlobalInputs `json:"globalInputs"` - Tasks []Task `json:"tasks"` -} - -type GlobalInputs struct { - ParameterName string `json:"parameterName"` - Type string `json:"type"` - DisplayName string `json:"displayName"` -} - -type Task struct { - Name string `json:"name"` - OnFinish string `json:"onFinish"` - UndoPossible bool `json:"undoPossible"` - ScriptPath string `json:"scriptPath"` - Parameter []TaskParameter `json:"parameter"` - Feedback string `json:"feedback"` -} - -type TaskParameter struct { - ParameterName string `json:"parameterName"` - Type string `json:"type"` - DisplayName string `json:"displayName"` - Global bool `json:"global"` -} - -func addCategoryGroup(group Group) { - for index, categoryGroup := range CategoryGroups { - if categoryGroup.Category == group.Category { - CategoryGroups[index].Groups = append(CategoryGroups[index].Groups, group) - return - } - } - - categoryGroup := CategoryGroup{ - Category: group.Category, - } - - categoryGroup.Groups = append(categoryGroup.Groups, group) - - CategoryGroups = append(CategoryGroups, categoryGroup) -} +var root = "./groupTasks/groups/" func ReadGroups() { - root := "./groupTasks/groups/" - entries, err := os.ReadDir(root) if err != nil { @@ -94,7 +47,7 @@ func ReadGroups() { return } - var group Group + var group structs.Group json.Unmarshal(content, &group) @@ -102,8 +55,210 @@ func ReadGroups() { log.Info().Msgf("Group: %s", group) - addCategoryGroup(group) + cache.AddCategoryGroup(group) } } } } + +const ( + RunGroupTaskStartTypeNormal = 0 + RunGroupTaskStartTypeTryAgain = 1 +) + +type RunGroupTaskArgs struct { + StartType uint8 + GroupTaskId string + Category string + GroupId string + Step uint8 + TaskStepId string + GlobalInputs []interface{} +} + +func RunGroupTask(args RunGroupTaskArgs) { + log.Debug().Msgf("global input: %v", args.GlobalInputs) + + categoryGroup := GetCategoryGroupTaskByCategoryAndGroupId(args.Category, args.GroupId) + + log.Debug().Msgf("RunGroupTask %s", categoryGroup) + + log.Debug().Msgf("script path %s", root+categoryGroup.Id+"/"+categoryGroup.Tasks[args.Step-1].ScriptPath) + + groupTaskStep := structs.GroupTaskSteps{ + GroupTasksId: args.GroupTaskId, + Step: args.Step, + Status: structs.GroupTasksStatusRunning, + StartedAt: time.Now(), + } + + if args.StartType == RunGroupTaskStartTypeNormal { + groupTaskStep.Id = uuid.New().String() + + database.DB.Create(&groupTaskStep) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdNewGroupTaskStep, + Body: groupTaskStep, + }) + } else if args.StartType == RunGroupTaskStartTypeTryAgain { + groupTaskStep.Id = args.TaskStepId + + database.DB.Model(&structs.GroupTaskSteps{}).Where("id = ?", groupTaskStep.Id).Updates(groupTaskStep) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTaskStep, + Body: groupTaskStep, + }) + } + + // set group task to running + database.DB.Model(&structs.GroupTasks{}).Where("id = ?", groupTaskStep.GroupTasksId).Updates(structs.GroupTasks{ + Status: structs.GroupTasksStatusRunning, + }) + + var dbGroupTask structs.GroupTasks + + database.DB.First(&dbGroupTask, "id = ?", groupTaskStep.GroupTasksId) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTask, + Body: dbGroupTask, + }) + + // execute script + cmd, err := exec.Command("python3", root+categoryGroup.Id+"/"+categoryGroup.Tasks[args.Step-1].ScriptPath).Output() + + cmdLog := string(cmd) + + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + exitCode := exitErr.ExitCode() + log.Error().Msgf("exit code %d", exitCode) + cmdLog += fmt.Sprintf("\nExit code: %v", exitCode) + } + + log.Error().Msgf("error exec command %s", err.Error()) + groupTaskStep.Status = structs.GroupTasksStatusFailed + } else { + groupTaskStep.Status = structs.GroupTasksStatusFinished + } + + fmt.Println(cmdLog) + + groupTaskStep.Log = cmdLog + groupTaskStep.EndedAt = time.Now() + + /* + if args.StartType == RunGroupTaskStartTypeNormal { + database.DB.Model(&structs.GroupTaskSteps{}).Where("id = ?", groupTaskStep.Id).Updates(groupTaskStep) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTaskStep, + Body: groupTaskStep, + }) + } else if args.StartType == RunGroupTaskStartTypeTryAgain { + log.Debug().Msgf("update", groupTaskStep.Id, groupTaskStep.Status) + + database.DB.Model(&structs.GroupTaskSteps{}).Where("id = ?", groupTaskStep.Id).Updates(groupTaskStep) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTaskStep, + Body: groupTaskStep, + }) + }*/ + + database.DB.Model(&structs.GroupTaskSteps{}).Where("id = ?", groupTaskStep.Id).Updates(groupTaskStep) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTaskStep, + Body: groupTaskStep, + }) + + log.Info().Msgf("run next task") + + if int(args.Step) < len(categoryGroup.Tasks) { + if groupTaskStep.Status == structs.GroupTasksStatusFailed { + // set group task to failed + database.DB.Model(&structs.GroupTasks{}).Where("id = ?", groupTaskStep.GroupTasksId).Updates(structs.GroupTasks{ + Status: structs.GroupTasksStatusFailed, + }) + + var dbGroupTask structs.GroupTasks + + database.DB.First(&dbGroupTask, "id = ?", groupTaskStep.GroupTasksId) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTask, + Body: dbGroupTask, + }) + } else { + args.StartType = RunGroupTaskStartTypeNormal + args.Step = args.Step + 1 + + database.DB.Model(&structs.GroupTasks{}).Where("id = ?", groupTaskStep.GroupTasksId).Updates(structs.GroupTasks{ + CurrentTasksStep: args.Step, + }) + + var dbGroupTask structs.GroupTasks + + database.DB.First(&dbGroupTask, "id = ?", groupTaskStep.GroupTasksId) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTask, + Body: dbGroupTask, + }) + + log.Debug().Msgf("RUN NEXT TASK %s", groupTaskStep) + RunGroupTask(args) + } + } else { + // set group task to finished + database.DB.Model(&structs.GroupTasks{}).Where("id = ?", groupTaskStep.GroupTasksId).Updates(structs.GroupTasks{ + Status: structs.GroupTasksStatusFinished, + EndedAt: time.Now(), + }) + + var dbGroupTask structs.GroupTasks + + database.DB.First(&dbGroupTask, "id = ?", groupTaskStep.GroupTasksId) + + socketclients.BroadcastMessage(structs.SendSocketMessage{ + Cmd: utils.SentCmdUpdateGroupTask, + Body: dbGroupTask, + }) + + log.Info().Msg("SET TO FINISHED") + } + +} + +func GetAllGroupTasks() []structs.GroupTasks { + var groupTasks []structs.GroupTasks + + database.DB.Find(&groupTasks) + + return groupTasks +} + +func GetAllGroupTasksSteps() []structs.GroupTaskSteps { + var groupTaskStepsLogs []structs.GroupTaskSteps + + database.DB.Find(&groupTaskStepsLogs) + + return groupTaskStepsLogs +} + +func GetCategoryGroupTaskByCategoryAndGroupId(category string, groupId string) structs.Group { + for _, categoryGroup := range cache.GetCategoryGroups() { + if categoryGroup.Category == category { + for _, group := range categoryGroup.Groups { + if group.Id == groupId { + return group + } + } + } + } + + return structs.Group{} +} diff --git a/modules/cache/categorygroup.go b/modules/cache/categorygroup.go new file mode 100644 index 0000000..a716e2d --- /dev/null +++ b/modules/cache/categorygroup.go @@ -0,0 +1,36 @@ +package cache + +import ( + "janex/admin-dashboard-backend/modules/structs" + "sync" +) + +var CategoryGroups []structs.CategoryGroup +var cgMu sync.RWMutex + +func AddCategoryGroup(group structs.Group) { + cgMu.Lock() + defer cgMu.Unlock() + + for index, categoryGroup := range CategoryGroups { + if categoryGroup.Category == group.Category { + CategoryGroups[index].Groups = append(CategoryGroups[index].Groups, group) + return + } + } + + categoryGroup := structs.CategoryGroup{ + Category: group.Category, + } + + categoryGroup.Groups = append(categoryGroup.Groups, group) + + CategoryGroups = append(CategoryGroups, categoryGroup) +} + +func GetCategoryGroups() []structs.CategoryGroup { + cgMu.RLock() + defer cgMu.RUnlock() + + return CategoryGroups +} diff --git a/modules/cache/cache.go b/modules/cache/socketclient.go similarity index 100% rename from modules/cache/cache.go rename to modules/cache/socketclient.go diff --git a/modules/database/database.go b/modules/database/database.go index e5f485b..1a0de6c 100644 --- a/modules/database/database.go +++ b/modules/database/database.go @@ -31,7 +31,7 @@ func InitDatabase() { db.AutoMigrate(&structs.User{}) db.AutoMigrate(&structs.UserSession{}) db.AutoMigrate(&structs.GroupTasks{}) - db.AutoMigrate(&structs.GroupTaskStepsLogs{}) + db.AutoMigrate(&structs.GroupTaskSteps{}) //createUser() } diff --git a/modules/structs/grouptasks.go b/modules/structs/grouptasks.go index 9066446..2f4bbcb 100644 --- a/modules/structs/grouptasks.go +++ b/modules/structs/grouptasks.go @@ -7,10 +7,11 @@ import ( // structure for database const ( - GroupTasksStatusFinished uint8 = 0 - GroupTasksStatusRunning uint8 = 1 + GroupTasksStatusFinished uint8 = 1 + GroupTasksStatusRunning uint8 = 2 GroupTasksStatusCanceled uint8 = 3 GroupTasksStatusFailed uint8 = 4 + GroupTasksInputRequired uint8 = 5 ) type GroupTasks struct { @@ -21,11 +22,14 @@ type GroupTasks struct { CurrentTasksStep uint8 NumberOfSteps uint8 Status uint8 + GlobalInputs string `gorm:"type:json"` StartedAt time.Time + EndedAt time.Time RememberId string `gorm:"-"` // used by the web client who requested this to open the modal after the backend sent the NewGroupTaskStarted message } -type GroupTaskStepsLogs struct { +type GroupTaskSteps struct { + Id string GroupTasksId string Step uint8 Status uint8 @@ -33,3 +37,40 @@ type GroupTaskStepsLogs struct { StartedAt time.Time EndedAt time.Time } + +// read from file structure + +type CategoryGroup struct { + Category string `json:"category"` + Groups []Group `json:"groups"` +} + +type Group struct { + Category string `json:"category"` + Id string `json:"id"` + Name string `json:"name"` + GlobalInputs []GlobalInputs `json:"globalInputs"` + Tasks []Task `json:"tasks"` +} + +type GlobalInputs struct { + ParameterName string `json:"parameterName"` + Type string `json:"type"` + DisplayName string `json:"displayName"` +} + +type Task struct { + Name string `json:"name"` + OnFinish string `json:"onFinish"` + UndoPossible bool `json:"undoPossible"` + ScriptPath string `json:"scriptPath"` + Parameter []TaskParameter `json:"parameter"` + Feedback string `json:"feedback"` +} + +type TaskParameter struct { + ParameterName string `json:"parameterName"` + Type string `json:"type"` + DisplayName string `json:"displayName"` + Global bool `json:"global"` +} diff --git a/modules/structs/socket.go b/modules/structs/socket.go index a00b7e8..20de531 100644 --- a/modules/structs/socket.go +++ b/modules/structs/socket.go @@ -3,7 +3,6 @@ package structs import ( "encoding/json" "errors" - "janex/admin-dashboard-backend/grouptasks" "sync" "github.com/gofiber/websocket/v2" @@ -79,9 +78,10 @@ func (socketClient *SocketClient) writeMessage(messageType int, message SendSock } type InitUserSocketConnection struct { - User UserData - CategoryGroups []grouptasks.CategoryGroup // config specific group tasks - GroupTasks []GroupTasks // in database saved group tasks + User UserData + CategoryGroups []CategoryGroup // config specific group tasks + GroupTasks []GroupTasks // in database saved group tasks + GroupTasksSteps []GroupTaskSteps } type UserData struct { diff --git a/modules/utils/globals.go b/modules/utils/globals.go index 3c84c13..0bc2997 100644 --- a/modules/utils/globals.go +++ b/modules/utils/globals.go @@ -16,14 +16,18 @@ const ( // commands sent to web clients const ( - SentInitUserSocketConnection = 1 - SentCmdUpdateConnectedUsers = 2 - SentCmdNewGroupTaskStarted = 3 + SentCmdInitUserSocketConnection = 1 + SentCmdUpdateConnectedUsers = 2 + SentCmdNewGroupTaskStarted = 3 + SentCmdNewGroupTaskStep = 4 + SentCmdUpdateGroupTaskStep = 5 + SentCmdUpdateGroupTask = 6 ) // commands received from web clients const ( - ReceivedStartGroupTasks = 1 + ReceivedCmdStartGroupTasks = 1 + ReceivedCmdTaskFailedTryAgainRunTaskStep = 2 ) var ( diff --git a/socketserver/hub.go b/socketserver/hub.go index 3abbe1a..316e060 100644 --- a/socketserver/hub.go +++ b/socketserver/hub.go @@ -46,14 +46,15 @@ func RunHub() { database.DB.First(&user, "id = ?", userId) newSocketClient.SendMessage(structs.SendSocketMessage{ - Cmd: utils.SentInitUserSocketConnection, + Cmd: utils.SentCmdInitUserSocketConnection, Body: structs.InitUserSocketConnection{ User: structs.UserData{ Username: user.Username, Email: user.Email, }, - CategoryGroups: grouptasks.CategoryGroups, - GroupTasks: GetAllGroupTasks(), + CategoryGroups: cache.CategoryGroups, + GroupTasks: grouptasks.GetAllGroupTasks(), + GroupTasksSteps: grouptasks.GetAllGroupTasksSteps(), }, }) @@ -70,17 +71,37 @@ func RunHub() { log.Info().Msgf("Received message: %s", receivedMessage, receivedMessage.Cmd) switch receivedMessage.Cmd { - case utils.ReceivedStartGroupTasks: - log.Debug().Msgf("received start group tasks %s", receivedMessage.Body["id"], receivedMessage.Body["category"]) + case utils.ReceivedCmdStartGroupTasks: + log.Debug().Msgf("received start group tasks %s", receivedMessage.Body) + + category := receivedMessage.Body["category"].(string) + groupId := receivedMessage.Body["id"].(string) + globalInputs := receivedMessage.Body["globalInputs"] + + var globalInputsJsonString string + + if globalInputs != nil { + jsonString, err := json.Marshal(globalInputs) + + if err != nil { + log.Error().Msgf("Failed to marshal global inputs %s", err) + continue + } + + globalInputsJsonString = string(jsonString) + } + + groupTaskId := uuid.New().String() groupTasks := &structs.GroupTasks{ - Id: uuid.New().String(), - Category: receivedMessage.Body["category"].(string), - GroupId: receivedMessage.Body["id"].(string), + Id: groupTaskId, + Category: category, + GroupId: groupId, GroupName: receivedMessage.Body["groupName"].(string), CurrentTasksStep: 1, NumberOfSteps: uint8(receivedMessage.Body["numberOfSteps"].(float64)), Status: structs.GroupTasksStatusRunning, + GlobalInputs: globalInputsJsonString, StartedAt: time.Now(), RememberId: receivedMessage.Body["rememberId"].(string), } @@ -91,6 +112,26 @@ func RunHub() { Cmd: utils.SentCmdNewGroupTaskStarted, Body: groupTasks, }) + + go grouptasks.RunGroupTask(grouptasks.RunGroupTaskArgs{ + StartType: grouptasks.RunGroupTaskStartTypeNormal, + GroupTaskId: groupTaskId, + Category: category, + GroupId: groupId, + Step: 1, + TaskStepId: "", + GlobalInputs: globalInputs.([]interface{}), + }) + break + case utils.ReceivedCmdTaskFailedTryAgainRunTaskStep: + go grouptasks.RunGroupTask(grouptasks.RunGroupTaskArgs{ + StartType: grouptasks.RunGroupTaskStartTypeTryAgain, + GroupTaskId: receivedMessage.Body["groupTaskId"].(string), + Category: receivedMessage.Body["category"].(string), + GroupId: receivedMessage.Body["groupId"].(string), + Step: uint8(receivedMessage.Body["step"].(float64)), + TaskStepId: receivedMessage.Body["taskStepId"].(string), + }) break default: log.Error().Msgf("Received unknown message: %s", receivedMessage) @@ -104,11 +145,3 @@ func RunHub() { } } } - -func GetAllGroupTasks() []structs.GroupTasks { - var groupTasks []structs.GroupTasks - - database.DB.Find(&groupTasks) - - return groupTasks -}