admin-dashboard-backend/modules/grouptasks/grouptasks.go

1095 lines
30 KiB
Go

package grouptasks
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"jannex/admin-dashboard-backend/modules/cache"
"jannex/admin-dashboard-backend/modules/config"
"jannex/admin-dashboard-backend/modules/database"
"jannex/admin-dashboard-backend/modules/logger"
"jannex/admin-dashboard-backend/modules/structs"
"jannex/admin-dashboard-backend/modules/systempermissions"
"jannex/admin-dashboard-backend/modules/utils"
"jannex/admin-dashboard-backend/socketclients"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
func InitLoadCategoryGroups() {
entries, err := os.ReadDir(config.Cfg.FolderPaths.GroupTasksGroups)
if err != nil {
panic(err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
files, err := os.ReadDir(config.Cfg.FolderPaths.GroupTasksGroups + entry.Name())
log.Debug().Msgf("entry %v", entry.Name())
if err != nil {
log.Error().Msg("Failed to read groups directory files, error: " + err.Error())
return
}
for _, file := range files {
if file.Name() == "index.json" {
content, err := os.ReadFile(config.Cfg.FolderPaths.GroupTasksGroups + entry.Name() + "/index.json")
if err != nil {
log.Error().Msg("Failed to read file content, error: " + err.Error())
return
}
var group structs.Group
json.Unmarshal(content, &group)
group.Id = entry.Name()
cache.AddCategoryGroup(group)
}
}
}
}
func ReloadCategoryGroups(category string) {
entries, err := os.ReadDir(config.Cfg.FolderPaths.GroupTasksGroups)
if err != nil {
panic(err)
}
cache.RemoveAllCategoryGroupsByCategory(category)
var categoryGroups []structs.Group
// looping through groups directory
for _, entry := range entries {
if !entry.IsDir() {
continue
}
files, err := os.ReadDir(config.Cfg.FolderPaths.GroupTasksGroups + entry.Name())
if err != nil {
log.Error().Msg("Failed to read groups directory files, error: " + err.Error())
return
}
for _, file := range files {
if file.Name() == "index.json" {
content, err := os.ReadFile(config.Cfg.FolderPaths.GroupTasksGroups + entry.Name() + "/index.json")
if err != nil {
log.Error().Msg("Failed to read file content, error: " + err.Error())
return
}
var group structs.Group
json.Unmarshal(content, &group)
group.Id = entry.Name()
if group.Category == category {
cache.AddCategoryGroup(group)
categoryGroups = append(categoryGroups, group)
}
}
}
}
if len(categoryGroups) > 0 {
socketclients.BroadcastMessage(structs.SendSocketMessage{
Cmd: utils.SentCmdGroupTasksReloaded,
Body: struct {
Category string
CategoryGroups []structs.Group
}{
Category: category,
CategoryGroups: categoryGroups,
},
})
} else { // category was removed
socketclients.BroadcastMessage(structs.SendSocketMessage{
Cmd: utils.SentCmdGroupTasksReloaded,
Body: struct {
RemovedCategory string
RemovedPermissions []string
}{
RemovedCategory: category,
RemovedPermissions: systempermissions.RemoveDynamicGroupTasksPermissionsByCategory(category),
},
})
}
}
func LookingForCategoryGroupChanges(userId string) {
entries, err := os.ReadDir(config.Cfg.FolderPaths.GroupTasksGroups)
if err != nil {
panic(err)
}
var foundCategoryGroups []structs.Group
// looping through groups directory
for _, entry := range entries {
if !entry.IsDir() {
continue
}
files, err := os.ReadDir(config.Cfg.FolderPaths.GroupTasksGroups + entry.Name())
if err != nil {
log.Error().Msg("Failed to read groups directory files, error: " + err.Error())
return
}
for _, file := range files {
if file.Name() == "index.json" {
content, err := os.ReadFile(config.Cfg.FolderPaths.GroupTasksGroups + entry.Name() + "/index.json")
if err != nil {
log.Error().Msg("Failed to read file content, error: " + err.Error())
return
}
var group structs.Group
json.Unmarshal(content, &group)
group.Id = entry.Name()
foundCategoryGroups = append(foundCategoryGroups, group)
}
}
}
cachedCategoryGroups := cache.GetCategoryGroups()
var addedGroupTasksPermissions []string
var newCategories []string
var addedCategoryGroups []structs.CategoryGroup
for _, foundCategoryGroup := range foundCategoryGroups {
// check for new added category groups
if !existsCategoryGroup(foundCategoryGroup.Category, cachedCategoryGroups) {
cache.AddCategoryGroup(foundCategoryGroup)
if !isInList(foundCategoryGroup.Category, newCategories) {
newCategories = append(newCategories, foundCategoryGroup.Category)
}
}
}
var logResultAddedCategoryGroups []string
var logResultRemovedCategoryGroups []string
for _, newCategory := range newCategories {
addedGroupTasksPermissions = append(addedGroupTasksPermissions, systempermissions.AddDynamicGroupTasksPermissionsByCategory(newCategory)...)
cGroup := cache.GetCategoryGroupByCategory(newCategory)
addedCategoryGroups = append(addedCategoryGroups, cGroup)
logResultAddedCategoryGroups = append(logResultAddedCategoryGroups, newCategory)
}
var removedCategoryGroups []string
var removedGroupTasksPermissions []string
for _, cachedCategoryGroup := range cachedCategoryGroups {
if !existsGroup(cachedCategoryGroup.Category, foundCategoryGroups) {
removedCategoryGroups = append(removedCategoryGroups, cachedCategoryGroup.Category)
cache.RemoveAllCategoryGroupsByCategory(cachedCategoryGroup.Category)
removedGroupTasksPermissions = append(removedGroupTasksPermissions, systempermissions.RemoveDynamicGroupTasksPermissionsByCategory(cachedCategoryGroup.Category)...)
logResultRemovedCategoryGroups = append(logResultRemovedCategoryGroups, cachedCategoryGroup.Category)
}
}
result := make(map[string]interface{})
if len(addedGroupTasksPermissions) > 0 {
result["AddedPermissions"] = addedGroupTasksPermissions
result["MasterRoleId"] = systempermissions.GetMasterRoleId()
}
if len(addedCategoryGroups) > 0 {
result["AddedCategoryGroups"] = addedCategoryGroups
}
if len(removedCategoryGroups) > 0 {
result["RemovedCategoryGroups"] = removedCategoryGroups
}
if len(removedGroupTasksPermissions) > 0 {
result["RemovedPermissions"] = removedGroupTasksPermissions
}
socketclients.BroadcastMessage(structs.SendSocketMessage{
Cmd: utils.SentCmdGroupTasksCategoryGroupChanges,
Body: result,
})
logger.AddGroupTasksLog(structs.LogMessage{
Id: 7,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
{Type: "result", Value: utils.MarshalJson(struct {
AddedCategoryGroups []string
RemovedCategoryGroups []string
}{
AddedCategoryGroups: logResultAddedCategoryGroups,
RemovedCategoryGroups: logResultRemovedCategoryGroups,
})},
},
})
}
func isInList(value string, list []string) bool {
for _, x := range list {
if x == value {
return true
}
}
return false
}
func existsCategoryGroup(category string, categoryGroupsList []structs.CategoryGroup) bool {
for _, categoryGroup := range categoryGroupsList {
if categoryGroup.Category == category {
return true
}
}
return false
}
func existsGroup(category string, groupsList []structs.Group) bool {
for _, group := range groupsList {
if group.Category == category {
return true
}
}
return false
}
const (
RunGroupTaskStartTypeNormal = 0
RunGroupTaskStartTypeTryAgain = 1
RunGroupTaskStartTypeUndo = 2
)
type RunGroupTaskArgs struct {
CreatorUserId string
StartType uint8
GroupTaskId string
Category string
GroupId string
Step uint8
TaskStepId string
GlobalInputs string
TaskInputs string
}
type InputParameters struct {
ParameterName string `json:"parameterName"`
Value string `json:"value"`
}
type FoundFile struct {
Path string
FileName string
}
const (
UserActionTaskStepResume = 0
UserActionTaskStepUndo = 1
UserActionTaskStepRepeat = 2
)
func HandleUserActionTaskStep(userId string, body map[string]interface{}) {
action := uint8(body["action"].(float64))
groupTaskId := body["groupTaskId"].(string)
category := body["category"].(string)
groupId := body["groupId"].(string)
step := uint8(body["step"].(float64))
taskStepId := body["taskStepId"].(string)
var dbGroupTaskStep structs.GroupTaskSteps
database.DB.Where("id = ?", taskStepId).Find(&dbGroupTaskStep)
groupTaskStep := structs.GroupTaskSteps{
Id: taskStepId,
CreatorUserId: userId,
GroupTasksId: groupTaskId,
Step: step,
}
nextStep := step
if action == UserActionTaskStepResume {
groupTaskStep.Status = utils.GroupTasksStatusFinished
groupTaskStep.Inputs = dbGroupTaskStep.Inputs
groupTaskStep.Log = dbGroupTaskStep.Log
groupTaskStep.Files = dbGroupTaskStep.Files
groupTaskStep.StartedAt = dbGroupTaskStep.StartedAt
groupTaskStep.EndedAt = dbGroupTaskStep.EndedAt
nextStep = step + 1
logger.AddGroupTasksLog(structs.LogMessage{
Id: 8,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
{Type: "action", Value: "resume"},
{Type: "taskStepId", Value: taskStepId},
{Type: "groupTaskId", Value: groupTaskId},
},
})
} else {
groupTaskStep.Status = utils.GroupTasksStatusRunning
}
updateGroupTaskSteps(groupTaskStep)
categoryGroup := GetCategoryGroupTaskByCategoryAndGroupId(category, groupId)
// last step onFinish was set to pause so the resume is the confirmation to finish the group task
if int(nextStep) > len(categoryGroup.Tasks) {
groupTaskFinished(groupTaskId)
return
}
updateGroupTask(groupTaskId, structs.GroupTasks{
CurrentTasksStep: nextStep,
})
runGroupTaskArgs := RunGroupTaskArgs{
CreatorUserId: userId,
StartType: RunGroupTaskStartTypeNormal,
GroupTaskId: groupTaskId,
Category: category,
GroupId: groupId,
Step: nextStep,
TaskStepId: taskStepId,
}
if action == UserActionTaskStepRepeat {
runGroupTaskArgs.StartType = RunGroupTaskStartTypeTryAgain
logger.AddGroupTasksLog(structs.LogMessage{
Id: 8,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
{Type: "action", Value: "repeat"},
{Type: "taskStepId", Value: taskStepId},
{Type: "groupTaskId", Value: groupTaskId},
},
})
} else if action == UserActionTaskStepUndo {
runGroupTaskArgs.StartType = RunGroupTaskStartTypeUndo
runGroupTaskArgs.TaskInputs = dbGroupTaskStep.Inputs
logger.AddGroupTasksLog(structs.LogMessage{
Id: 8,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
{Type: "action", Value: "undo"},
{Type: "taskStepId", Value: taskStepId},
{Type: "groupTaskId", Value: groupTaskId},
},
})
}
// run next task step
go RunGroupTask(runGroupTaskArgs)
}
func RunGroupTask(args RunGroupTaskArgs) {
categoryGroup := GetCategoryGroupTaskByCategoryAndGroupId(args.Category, args.GroupId)
groupTaskStep := structs.GroupTaskSteps{
CreatorUserId: args.CreatorUserId,
GroupTasksId: args.GroupTaskId,
Step: args.Step,
Status: utils.GroupTasksStatusRunning,
Inputs: args.TaskInputs,
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 {
groupTaskStep.Id = args.TaskStepId
updateGroupTaskSteps(groupTaskStep)
}
// set group task to running
dbGroupTask := updateGroupTask(groupTaskStep.GroupTasksId, structs.GroupTasks{
Status: utils.GroupTasksStatusRunning,
})
// global inputs
var globalInputParameters []InputParameters
if len(args.GlobalInputs) > 0 { // global inputs given in args because the group task was just created
if err := json.Unmarshal([]byte(args.GlobalInputs), &globalInputParameters); err != nil {
log.Error().Msgf("err unmarshalling global inputs %s", err.Error())
}
} else { // global inputs not given in args - fetch it from the database
if err := json.Unmarshal([]byte(dbGroupTask.GlobalInputs), &globalInputParameters); err != nil {
log.Error().Msgf("err unmarshalling global inputs %s", err.Error())
}
}
// task parameters
log.Debug().Msgf("script path %s", categoryGroup.Tasks[args.Step-1].ScriptPath)
//commandArgs := []string{root + categoryGroup.Id + "/" + categoryGroup.Tasks[args.Step-1].ScriptPath}
commandArgs := []string{categoryGroup.Tasks[args.Step-1].ScriptPath}
if len(categoryGroup.Tasks[args.Step-1].Parameters) != 0 && len(args.TaskInputs) == 0 {
updateGroupTask(groupTaskStep.GroupTasksId, structs.GroupTasks{
Status: utils.GroupTasksStatusInputRequired,
})
groupTaskStep.Status = utils.GroupTasksStatusInputRequired
updateGroupTaskSteps(groupTaskStep)
logger.AddGroupTasksLog(structs.LogMessage{
Id: 5,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "taskStepId", Value: groupTaskStep.Id},
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "input required"},
},
})
return
} else if len(args.TaskInputs) > 0 {
var taskParameterInputs []InputParameters
if err := json.Unmarshal([]byte(args.TaskInputs), &taskParameterInputs); err != nil {
log.Error().Msgf("err unmarshalling task parameter inputs %s", err.Error())
}
for _, taskParameterInput := range taskParameterInputs {
commandArgs = append(commandArgs, taskParameterInput.Value)
}
// append undo as last arg to script
if args.StartType == RunGroupTaskStartTypeUndo {
commandArgs = append(commandArgs, "--undo")
}
}
// create running task folder
if _, err := os.Stat(config.Cfg.FolderPaths.GroupTasksRunningTasks + groupTaskStep.GroupTasksId); errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(config.Cfg.FolderPaths.GroupTasksRunningTasks+groupTaskStep.GroupTasksId, os.ModePerm); err != nil {
log.Error().Msgf("Error creating running tasks folder %s", err.Error())
}
// copy scripts to group tasks folder
err := filepath.Walk(config.Cfg.FolderPaths.GroupTasksGroups+categoryGroup.Id, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Error().Msgf("Error walk %s", err.Error())
return err
}
if !info.IsDir() && filepath.Ext(path) == ".py" {
bytesRead, err := os.ReadFile(path)
if err != nil {
log.Error().Msgf("Error reading file %s", err.Error())
}
err = ioutil.WriteFile(config.Cfg.FolderPaths.GroupTasksRunningTasks+groupTaskStep.GroupTasksId+"/"+info.Name(), bytesRead, 0644)
if err != nil {
log.Error().Msgf("Error writing file %s", err.Error())
}
}
return nil
})
if err != nil {
log.Error().Msgf("Failed to walk through task folder %s", err.Error())
}
}
// execute command
cmd := exec.Command("python3", commandArgs...)
// path needs to be set here as the python scripts will use the path as base path to create files for example
cmd.Dir = config.Cfg.FolderPaths.GroupTasksRunningTasks + groupTaskStep.GroupTasksId + "/"
out, err := cmd.CombinedOutput()
//cmdLog := string(cmd)
cmdLog := string(out)
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 = utils.GroupTasksStatusFailed
logger.AddGroupTasksLog(structs.LogMessage{
Id: 5,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "taskStepId", Value: groupTaskStep.Id},
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "failed"},
},
})
} else {
groupTaskStep.Status = utils.GroupTasksStatusFinished
logger.AddGroupTasksLog(structs.LogMessage{
Id: 5,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "taskStepId", Value: groupTaskStep.Id},
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "finished"},
},
})
}
fmt.Println(cmdLog)
groupTaskStep.Log = cmdLog
groupTaskStep.EndedAt = time.Now()
if args.StartType != RunGroupTaskStartTypeUndo {
// looking for files which are created by the scripts
var foundFiles []FoundFile
if err = filepath.Walk(config.Cfg.FolderPaths.GroupTasksRunningTasks+groupTaskStep.GroupTasksId+"/", func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Error().Msgf("Error walk %s", err.Error())
return err
}
if !info.IsDir() && filepath.Ext(path) != ".py" && !strings.Contains(path, "/oldFiles/") {
foundFiles = append(foundFiles, FoundFile{Path: path, FileName: info.Name()})
}
return nil
}); err != nil {
log.Error().Msgf("Failed look for created files %s", err.Error())
}
if len(foundFiles) > 0 {
// creating folder for the old files which can be used by the python scripts for the next steps
if _, err := os.Stat(config.Cfg.FolderPaths.GroupTasksRunningTasks + groupTaskStep.GroupTasksId + "/oldFiles"); errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(config.Cfg.FolderPaths.GroupTasksRunningTasks+groupTaskStep.GroupTasksId+"/oldFiles", os.ModePerm); err != nil {
log.Error().Msgf("Error creating old files folder %s", err.Error())
}
}
// copy files to public directory to access it via web ui
if _, err := os.Stat(config.Cfg.FolderPaths.PublicStatic + "grouptasks/" + groupTaskStep.GroupTasksId); errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(config.Cfg.FolderPaths.PublicStatic+"grouptasks/"+groupTaskStep.GroupTasksId, os.ModePerm); err != nil {
log.Error().Msgf("Error creating task public folder %s", err.Error())
}
}
var taskFiles []structs.GroupTaskStepFile
for _, foundFile := range foundFiles {
bytesRead, err := ioutil.ReadFile(foundFile.Path)
if err != nil {
log.Error().Msgf("Error reading file %s", err.Error())
}
err = ioutil.WriteFile(config.Cfg.FolderPaths.GroupTasksRunningTasks+groupTaskStep.GroupTasksId+"/oldFiles/"+foundFile.FileName, bytesRead, 0644)
if err != nil {
log.Error().Msgf("Error writing file %s", err.Error())
}
systemFileName := uuid.New().String() + filepath.Ext(foundFile.FileName)
err = ioutil.WriteFile(config.Cfg.FolderPaths.PublicStatic+"grouptasks/"+groupTaskStep.GroupTasksId+"/"+systemFileName, bytesRead, 0644)
if err != nil {
log.Error().Msgf("Error writing file %s", err.Error())
}
err = os.Remove(config.Cfg.FolderPaths.GroupTasksRunningTasks + groupTaskStep.GroupTasksId + "/" + foundFile.FileName)
if err != nil {
log.Error().Msgf("Failed to delete created file by task %s", err.Error())
}
taskFiles = append(taskFiles, structs.GroupTaskStepFile{
OriginalFileName: foundFile.FileName,
SystemFileName: systemFileName,
})
}
marshaledTaskFiles, err := json.Marshal(taskFiles)
if err != nil {
log.Error().Msgf("Failed to marshal task files %s", err.Error())
} else {
groupTaskStep.Files = string(marshaledTaskFiles)
}
}
}
// update group task step
updateGroupTaskSteps(groupTaskStep)
if groupTaskStep.Status == utils.GroupTasksStatusFailed {
// set group task to failed
updateGroupTask(groupTaskStep.GroupTasksId, structs.GroupTasks{
Status: utils.GroupTasksStatusFailed,
})
logger.AddGroupTasksLog(structs.LogMessage{
Id: 2,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "failed"},
},
})
} else {
prevStartType := args.StartType
args.StartType = RunGroupTaskStartTypeNormal
if categoryGroup.Tasks[args.Step-1].OnFinish == "pause" {
status := utils.GroupTasksStatusPaused
if prevStartType == RunGroupTaskStartTypeUndo {
status = utils.GroupTasksStatusUndoEnded
groupTaskStep.Inputs = " "
groupTaskStep.Files = " "
logger.AddGroupTasksLog(structs.LogMessage{
Id: 2,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "Undo ended"},
},
})
logger.AddGroupTasksLog(structs.LogMessage{
Id: 5,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "taskStepId", Value: groupTaskStep.Id},
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "Undo ended"},
},
})
} else {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 2,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "paused"},
},
})
logger.AddGroupTasksLog(structs.LogMessage{
Id: 5,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "taskStepId", Value: groupTaskStep.Id},
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "status", Value: "paused"},
},
})
}
groupTaskStep.Status = status
updateGroupTaskSteps(groupTaskStep)
updateGroupTask(groupTaskStep.GroupTasksId, structs.GroupTasks{
Status: status,
})
} else {
args.Step = args.Step + 1
if int(args.Step-1) < len(categoryGroup.Tasks) {
// clear task parameters, because otherwise the next task would have the parameters from the previous task
args.TaskInputs = ""
updateGroupTask(groupTaskStep.GroupTasksId, structs.GroupTasks{
CurrentTasksStep: args.Step,
})
logger.AddGroupTasksLog(structs.LogMessage{
Id: 9,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "groupTaskId", Value: groupTaskStep.GroupTasksId},
{Type: "step", Value: utils.MarshalJson(args.Step)},
},
})
RunGroupTask(args)
} else {
groupTaskFinished(groupTaskStep.GroupTasksId)
}
}
}
}
func groupTaskFinished(groupTasksId string) {
updateGroupTask(groupTasksId, structs.GroupTasks{
Status: utils.GroupTasksStatusFinished,
EndedAt: time.Now(),
})
if err := os.RemoveAll(config.Cfg.FolderPaths.GroupTasksRunningTasks + groupTasksId + "/"); err != nil {
log.Error().Msgf("Failed to delete running task folder %s", err.Error())
}
logger.AddGroupTasksLog(structs.LogMessage{
Id: 2,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "groupTaskId", Value: groupTasksId},
{Type: "status", Value: "finished"},
},
})
}
// Updates group task and send it to websocket users
func updateGroupTask(groupTasksId string, updates structs.GroupTasks) structs.GroupTasks {
database.DB.Model(&structs.GroupTasks{}).Where("id = ?", groupTasksId).Updates(updates)
var dbGroupTask structs.GroupTasks
database.DB.First(&dbGroupTask, "id = ?", groupTasksId)
socketclients.BroadcastMessage(structs.SendSocketMessage{
Cmd: utils.SentCmdUpdateGroupTask,
Body: dbGroupTask,
})
return dbGroupTask
}
// Updates group task steps and send it to websocket users
func updateGroupTaskSteps(groupTaskStep structs.GroupTaskSteps) {
database.DB.Model(&structs.GroupTaskSteps{}).Where("id = ?", groupTaskStep.Id).Updates(groupTaskStep)
socketclients.BroadcastMessage(structs.SendSocketMessage{
Cmd: utils.SentCmdUpdateGroupTaskStep,
Body: groupTaskStep,
})
}
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)
lockedGroupTaskSteps := cache.GetLockedGroupTaskSteps()
groupTaskStepsInputs := cache.GetGroupTaskStepsInputs()
for i, groupTaskStep := range groupTaskStepsLogs {
if groupTaskStep.Status == utils.GroupTasksStatusInputRequired {
groupTaskStepsLogs[i].Inputs = cache.GetGroupTaskStepsInputsValue(groupTaskStepsInputs, groupTaskStep.GroupTasksId, groupTaskStep.Step)
for _, lockedGroupTaskStep := range lockedGroupTaskSteps {
if groupTaskStep.GroupTasksId == lockedGroupTaskStep.GroupTaskId {
groupTaskStepsLogs[i].LockedByUserId = lockedGroupTaskStep.LockedByUserId
}
}
}
}
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{}
}
func StartUnlockLockedGroupTaskStepsTicker() {
ticker := time.NewTicker(1 * time.Second)
for range ticker.C {
for index, taskStep := range cache.GetLockedGroupTaskSteps() {
if time.Since(taskStep.LockedAt).Seconds() > utils.GroupTaskLockedTime {
cache.RemoveLockedGroupTaskStep(index)
socketclients.BroadcastMessage(
structs.SendSocketMessage{
Cmd: utils.SentCmdTaskUnlocked,
Body: struct {
GroupTaskId string
Step uint8
RememberId string
}{
GroupTaskId: taskStep.GroupTaskId,
Step: taskStep.Step,
RememberId: taskStep.RememberId,
},
})
}
}
}
}
func StartGroupTask(userId string, groupTask structs.GroupTasks) {
groupTask.Id = uuid.New().String()
groupTask.CreatorUserId = userId
groupTask.CurrentTasksStep = 1
groupTask.Status = utils.GroupTasksStatusRunning
groupTask.StartedAt = time.Now()
database.DB.Create(&groupTask)
socketclients.BroadcastMessage(structs.SendSocketMessage{
Cmd: utils.SentCmdNewGroupTaskStarted,
Body: groupTask,
})
go RunGroupTask(RunGroupTaskArgs{
CreatorUserId: userId,
StartType: RunGroupTaskStartTypeNormal,
GroupTaskId: groupTask.Id,
Category: groupTask.Category,
GroupId: groupTask.GroupId,
Step: 1,
TaskStepId: "",
GlobalInputs: groupTask.GlobalInputs,
})
logger.AddGroupTasksLog(structs.LogMessage{
Id: 0,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
{Type: "groupTaskId", Value: groupTask.Id},
{Type: "groupTaskName", Value: groupTask.GroupName},
},
})
}
func InstallGlobalPythonPackages(userId string) {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 13,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
utils.PermissionGroupTasksInstallGlobalPythonPackages,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingGlobalPythonPackages,
})
// check if requirements.txt exists
if _, err := os.Stat(config.Cfg.FolderPaths.GroupTasksGroups + "requirements.txt"); errors.Is(err, os.ErrNotExist) {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 14,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "error", Value: "requirements.txt not found"},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
utils.PermissionGroupTasksInstallGlobalPythonPackages,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingGlobalPythonPackagesFailed,
})
return
}
// install python dependencies
cmd := exec.Command("pip3", "install", "-r", config.Cfg.FolderPaths.GroupTasksGroups+"requirements.txt")
out, err := cmd.CombinedOutput()
if err != nil {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 14,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "error", Value: err.Error()},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
utils.PermissionGroupTasksInstallGlobalPythonPackages,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingGlobalPythonPackagesFailed,
})
return
}
logger.AddGroupTasksLog(structs.LogMessage{
Id: 15,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "log", Value: string(out)},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
utils.PermissionGroupTasksInstallGlobalPythonPackages,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingGlobalPythonPackagesFinished,
})
}
func InstallPythonPackages(userId string, category, groupId string) {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 10,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "userId", Value: userId},
{Type: "category", Value: category},
{Type: "groupId", Value: groupId},
},
})
messageBody := struct {
Category string
GroupId string
}{
Category: category,
GroupId: groupId,
}
convertedXYPermission := systempermissions.ConvertXYPermission(utils.PermissionGroupTasksOverviewXYInstallPythonPackages, category)
socketclients.BroadcastMessageToUsersWithPermission(
convertedXYPermission,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingPythonPackages,
Body: messageBody,
})
// check if requirements.txt exists
if _, err := os.Stat(config.Cfg.FolderPaths.GroupTasksGroups + groupId + "/requirements.txt"); errors.Is(err, os.ErrNotExist) {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 11,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "category", Value: category},
{Type: "groupId", Value: groupId},
{Type: "error", Value: "requirements.txt not found"},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
convertedXYPermission,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingPythonPackagesFailed,
Body: messageBody,
})
return
}
// install python dependencies
cmd := exec.Command("pip3", "install", "-r", config.Cfg.FolderPaths.GroupTasksGroups+groupId+"/requirements.txt")
out, err := cmd.CombinedOutput()
if err != nil {
logger.AddGroupTasksLog(structs.LogMessage{
Id: 11,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "category", Value: category},
{Type: "groupId", Value: groupId},
{Type: "error", Value: err.Error()},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
convertedXYPermission,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingPythonPackages,
Body: messageBody,
})
return
}
logger.AddGroupTasksLog(structs.LogMessage{
Id: 12,
Type: utils.LogTypeInfo,
Messages: []structs.LogData{
{Type: "category", Value: category},
{Type: "groupId", Value: groupId},
{Type: "log", Value: string(out)},
},
})
socketclients.BroadcastMessageToUsersWithPermission(
convertedXYPermission,
structs.SendSocketMessage{
Cmd: utils.SentCmdInstallingPythonPackagesFinished,
Body: messageBody,
})
}