diff --git a/api/projects/schedules.go b/api/projects/schedules.go new file mode 100644 index 00000000..1a0f45a9 --- /dev/null +++ b/api/projects/schedules.go @@ -0,0 +1,149 @@ +package projects + +import ( + log "github.com/Sirupsen/logrus" + "github.com/ansible-semaphore/semaphore/api/helpers" + "github.com/ansible-semaphore/semaphore/db" + "github.com/gorilla/context" + "net/http" + "strconv" +) + +// SchedulesMiddleware ensures a template exists and loads it to the context +func SchedulesMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + project := context.Get(r, "project").(db.Project) + scheduleID, err := helpers.GetIntParam("schedule_id", w, r) + if err != nil { + return + } + + schedule, err := helpers.Store(r).GetSchedule(project.ID, scheduleID) + + if err != nil { + helpers.WriteError(w, err) + return + } + + context.Set(r, "schedule", schedule) + next.ServeHTTP(w, r) + }) +} + +// GetSchedule returns single template by ID +func GetSchedule(w http.ResponseWriter, r *http.Request) { + schedule := context.Get(r, "schedule").(db.Schedule) + helpers.WriteJSON(w, http.StatusOK, schedule) +} + +// AddSchedule adds a template to the database +func AddSchedule(w http.ResponseWriter, r *http.Request) { + project := context.Get(r, "project").(db.Project) + + var schedule db.Schedule + if !helpers.Bind(w, r, &schedule) { + return + } + + schedule.ProjectID = project.ID + schedule, err := helpers.Store(r).CreateSchedule(schedule) + + if err != nil { + helpers.WriteError(w, err) + return + } + + user := context.Get(r, "user").(*db.User) + objType := "schedule" + desc := "Schedule ID " + strconv.Itoa(schedule.ID) + " created" + + _, err = helpers.Store(r).CreateEvent(db.Event{ + UserID: &user.ID, + ProjectID: &project.ID, + ObjectType: &objType, + ObjectID: &schedule.ID, + Description: &desc, + }) + + if err != nil { + log.Error(err) + } + + helpers.WriteJSON(w, http.StatusCreated, schedule) +} + +// UpdateSchedule writes a schedule to an existing key in the database +func UpdateSchedule(w http.ResponseWriter, r *http.Request) { + oldSchedule := context.Get(r, "schedule").(db.Schedule) + + var schedule db.Schedule + if !helpers.Bind(w, r, &schedule) { + return + } + + // project ID and schedule ID in the body and the path must be the same + + if schedule.ID != oldSchedule.ID { + helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{ + "error": "schedule id in URL and in body must be the same", + }) + return + } + + if schedule.ProjectID != oldSchedule.ProjectID { + helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{ + "error": "You can not move schedule to other project", + }) + return + } + + err := helpers.Store(r).UpdateSchedule(schedule) + if err != nil { + helpers.WriteError(w, err) + return + } + + user := context.Get(r, "user").(*db.User) + + desc := "Schedule ID " + strconv.Itoa(schedule.ID) + " updated" + objType := "schedule" + + _, err = helpers.Store(r).CreateEvent(db.Event{ + UserID: &user.ID, + ProjectID: &schedule.ProjectID, + Description: &desc, + ObjectID: &schedule.ID, + ObjectType: &objType, + }) + + if err != nil { + log.Error(err) + } + + w.WriteHeader(http.StatusNoContent) +} + +// RemoveSchedule deletes a schedule from the database +func RemoveSchedule(w http.ResponseWriter, r *http.Request) { + schedule := context.Get(r, "schedule").(db.Schedule) + + err := helpers.Store(r).DeleteSchedule(schedule.ProjectID, schedule.ID) + if err != nil { + helpers.WriteError(w, err) + return + } + + user := context.Get(r, "user").(*db.User) + desc := "Schedule ID " + strconv.Itoa(schedule.ID) + " deleted" + _, err = helpers.Store(r).CreateEvent(db.Event{ + UserID: &user.ID, + ProjectID: &schedule.ProjectID, + Description: &desc, + }) + + if err != nil { + log.Error(err) + } + + w.WriteHeader(http.StatusNoContent) +} \ No newline at end of file diff --git a/api/projects/templates.go b/api/projects/templates.go index a193e690..066d3e84 100644 --- a/api/projects/templates.go +++ b/api/projects/templates.go @@ -4,10 +4,9 @@ import ( log "github.com/Sirupsen/logrus" "github.com/ansible-semaphore/semaphore/api/helpers" "github.com/ansible-semaphore/semaphore/db" + "github.com/gorilla/context" "net/http" "strconv" - - "github.com/gorilla/context" ) // TemplatesMiddleware ensures a template exists and loads it to the context @@ -42,7 +41,7 @@ func GetTemplates(w http.ResponseWriter, r *http.Request) { project := context.Get(r, "project").(db.Project) params := db.RetrieveQueryParams{ - SortBy: r.URL.Query().Get("sort"), + SortBy: r.URL.Query().Get("sort"), SortInverted: r.URL.Query().Get("order") == desc, } @@ -102,9 +101,17 @@ func UpdateTemplate(w http.ResponseWriter, r *http.Request) { } // project ID and template ID in the body and the path must be the same - if template.ID != oldTemplate.ID || template.ProjectID != oldTemplate.ProjectID { + + if template.ID != oldTemplate.ID { helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{ - "error": "You can not move ", + "error": "template id in URL and in body must be the same", + }) + return + } + + if template.ProjectID != oldTemplate.ProjectID { + helpers.WriteJSON(w, http.StatusBadRequest, map[string]string{ + "error": "You can not move template to other project", }) return } @@ -125,7 +132,7 @@ func UpdateTemplate(w http.ResponseWriter, r *http.Request) { objType := "template" _, err = helpers.Store(r).CreateEvent(db.Event{ - UserID: &user.ID, + UserID: &user.ID, ProjectID: &template.ProjectID, Description: &desc, ObjectID: &template.ID, @@ -162,4 +169,4 @@ func RemoveTemplate(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(http.StatusNoContent) -} +} \ No newline at end of file diff --git a/api/router.go b/api/router.go index 976864a6..5c56b204 100644 --- a/api/router.go +++ b/api/router.go @@ -189,6 +189,7 @@ func Route() *mux.Router { projectTmplManagement.HandleFunc("/{template_id}", projects.GetTemplate).Methods("GET") projectTmplManagement.HandleFunc("/{template_id}/tasks", tasks.GetAllTasks).Methods("GET") projectTmplManagement.HandleFunc("/{template_id}/tasks/last", tasks.GetLastTasks).Methods("GET") + projectTmplManagement.HandleFunc("/{template_id}/schedules", projects.GetSchedule).Methods("GET") projectTaskManagement := projectUserAPI.PathPrefix("/tasks").Subrouter() projectTaskManagement.Use(tasks.GetTaskMiddleware) @@ -198,6 +199,13 @@ func Route() *mux.Router { projectTaskManagement.HandleFunc("/{task_id}", tasks.RemoveTask).Methods("DELETE") projectTaskManagement.HandleFunc("/{task_id}/stop", tasks.StopTask).Methods("POST") + + projectScheduleManagement := projectUserAPI.PathPrefix("/schedules").Subrouter() + projectScheduleManagement.Use(projects.SchedulesMiddleware) + projectScheduleManagement.HandleFunc("/{schedule_id}", projects.GetSchedule).Methods("GET", "HEAD") + projectScheduleManagement.HandleFunc("/{schedule_id}", projects.UpdateSchedule).Methods("PUT") + projectScheduleManagement.HandleFunc("/{schedule_id}", projects.RemoveSchedule).Methods("DELETE") + if os.Getenv("DEBUG") == "1" { defer debugPrintRoutes(r) } diff --git a/api/schedules/pool.go b/api/schedules/pool.go new file mode 100644 index 00000000..ee705b8e --- /dev/null +++ b/api/schedules/pool.go @@ -0,0 +1,63 @@ +package schedules + +import ( + "github.com/ansible-semaphore/semaphore/db" + "github.com/robfig/cron/v3" +) + +type templateRunner struct { + schedule db.Schedule +} + +func (r templateRunner) Run() { + // TODO: add task to tasks pool +} + +type schedulePool struct { + cron *cron.Cron + jobs []cron.EntryID +} + +func (p *schedulePool) init() { + p.cron = cron.New() +} + +func (p *schedulePool) loadData(d db.Store) { + schedules, err := d.GetSchedules() + + if err != nil { + // TODO: log error + return + } + + for _, schedule := range schedules { + err := p.addSchedule(schedule) + if err != nil { + // TODO: log error + } + } +} + +func (p *schedulePool) addSchedule(schedule db.Schedule) error { + id, err := p.cron.AddJob(schedule.CronFormat, templateRunner{ + schedule: schedule, + }) + if err != nil { + return err + } + p.jobs = append(p.jobs, id) + return nil +} + +func (p *schedulePool) run() { + p.cron.Run() +} + +var pool = schedulePool{} + +// StartRunner begins the schedule pool, used as a goroutine +func StartRunner(d db.Store) { + pool.init() + pool.loadData(d) + pool.run() +} diff --git a/api/tasks/runner.go b/api/tasks/runner.go index c1f9b49b..4e52c315 100644 --- a/api/tasks/runner.go +++ b/api/tasks/runner.go @@ -4,7 +4,11 @@ import ( "bytes" "encoding/json" "fmt" + log "github.com/Sirupsen/logrus" + "github.com/ansible-semaphore/semaphore/api/helpers" "github.com/ansible-semaphore/semaphore/api/sockets" + "github.com/ansible-semaphore/semaphore/db" + "github.com/ansible-semaphore/semaphore/util" "io/ioutil" "os" "os/exec" @@ -12,13 +16,6 @@ import ( "strconv" "strings" "time" - - "github.com/ansible-semaphore/semaphore/api/helpers" - "github.com/ansible-semaphore/semaphore/db" - - log "github.com/Sirupsen/logrus" - - "github.com/ansible-semaphore/semaphore/util" ) const ( @@ -32,19 +29,19 @@ const ( ) type task struct { - store db.Store - task db.Task - template db.Template - inventory db.Inventory - repository db.Repository - environment db.Environment - users []int - projectID int - hosts []string - alertChat string - alert bool - prepared bool - process *os.Process + store db.Store + task db.Task + template db.Template + inventory db.Inventory + repository db.Repository + environment db.Environment + users []int + projectID int + hosts []string + alertChat string + alert bool + prepared bool + process *os.Process } func (t *task) getRepoName() string { diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 3e2df162..09f586a3 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -4,6 +4,7 @@ import ( "fmt" log "github.com/Sirupsen/logrus" "github.com/ansible-semaphore/semaphore/api" + "github.com/ansible-semaphore/semaphore/api/schedules" "github.com/ansible-semaphore/semaphore/api/sockets" "github.com/ansible-semaphore/semaphore/api/tasks" "github.com/ansible-semaphore/semaphore/db" @@ -74,6 +75,7 @@ func runService() { go sockets.StartWS() go tasks.StartRunner() + go schedules.StartRunner(store) route := api.Route() diff --git a/db/Schedule.go b/db/Schedule.go new file mode 100644 index 00000000..0c6054f9 --- /dev/null +++ b/db/Schedule.go @@ -0,0 +1,8 @@ +package db + +type Schedule struct { + ID int `db:"id" json:"id"` + ProjectID int `db:"project_id" json:"project_id"` + TemplateID int `db:"template_id" json:"template_id"` + CronFormat string `db:"cron_format" json:"cron_format"` +} diff --git a/db/Store.go b/db/Store.go index c6ab1618..e082493b 100644 --- a/db/Store.go +++ b/db/Store.go @@ -43,6 +43,8 @@ func ValidateUsername(login string) error { return nil } +type Transaction interface {} + type Store interface { Connect() error Close() error @@ -107,6 +109,13 @@ type Store interface { GetTemplate(projectID int, templateID int) (Template, error) DeleteTemplate(projectID int, templateID int) error + GetSchedules() ([]Schedule, error) + GetTemplateSchedules(projectID int, templateID int) ([]Schedule, error) + CreateSchedule(schedule Schedule) (Schedule, error) + UpdateSchedule(schedule Schedule) error + GetSchedule(projectID int, scheduleID int) (Schedule, error) + DeleteSchedule(projectID int, scheduleID int) error + GetProjectUsers(projectID int, params RetrieveQueryParams) ([]User, error) CreateProjectUser(projectUser ProjectUser) (ProjectUser, error) DeleteProjectUser(projectID int, userID int) error @@ -274,6 +283,11 @@ var TemplateProps = ObjectProperties{ PrimaryColumnName: "id", } +var ScheduleProps = ObjectProperties{ + TableName: "project__schedule", + PrimaryColumnName: "id", +} + var ProjectUserProps = ObjectProperties{ TableName: "project__user", PrimaryColumnName: "user_id", @@ -310,5 +324,4 @@ var TaskProps = ObjectProperties{ var TaskOutputProps = ObjectProperties{ TableName: "task__output", - PrimaryColumnName: "", } diff --git a/db/Template.go b/db/Template.go index c7dc7a1c..c129414c 100644 --- a/db/Template.go +++ b/db/Template.go @@ -24,4 +24,4 @@ type Template struct { VaultPassID *int `db:"vault_pass_id" json:"vault_pass_id"` VaultPass AccessKey `db:"-" json:"-"` -} +} \ No newline at end of file diff --git a/db/bolt/schedule.go b/db/bolt/schedule.go new file mode 100644 index 00000000..3aee2258 --- /dev/null +++ b/db/bolt/schedule.go @@ -0,0 +1,48 @@ +package bolt + +import "github.com/ansible-semaphore/semaphore/db" + +func (d *BoltDb) GetSchedules() (schedules []db.Schedule, err error) { + var allProjects []db.Project + + err = d.getObjects(0, db.ProjectProps, db.RetrieveQueryParams{}, nil, &allProjects) + + if err != nil { + return + } + + for _, proj := range allProjects { + var projSchedules []db.Schedule + projSchedules, err = d.GetProjectSchedules(proj.ID) + if err != nil { + return + } + schedules = append(schedules, projSchedules...) + } + + return +} + +func (d *BoltDb) GetProjectSchedules(projectID int) (schedules []db.Schedule, err error) { + err = d.getObjects(projectID, db.ScheduleProps, db.RetrieveQueryParams{}, nil, &schedules) + return +} + + +func (d *BoltDb) GetTemplateSchedules(projectID int, templateID int) (schedule db.Schedule, err error) { + projSchedules, err := d.GetProjectSchedules(projectID) + if err != nil { + return + } + + for _, s := range projSchedules { + if s.TemplateID == templateID { + schedule = s + return + } + } + + err = db.ErrNotFound + return +} + diff --git a/db/sql/Version.go b/db/sql/Version.go index 31f1f0ec..692554bf 100644 --- a/db/sql/Version.go +++ b/db/sql/Version.go @@ -81,5 +81,6 @@ func init() { {Major: 2, Minor: 7, Patch: 9}, {Major: 2, Minor: 7, Patch: 10}, {Major: 2, Minor: 7, Patch: 12}, + {Major: 2, Minor: 7, Patch: 13}, } } diff --git a/db/sql/migration.go b/db/sql/migration.go index fc73bbb7..297ca1c0 100644 --- a/db/sql/migration.go +++ b/db/sql/migration.go @@ -71,7 +71,8 @@ func (d *SqlDb) applyMigration(version *Version) error { } q := d.prepareMigration(query) - if _, err := tx.Exec(q); err != nil { + _, err = tx.Exec(q) + if err != nil { handleRollbackError(tx.Rollback()) log.Warnf("\n ERR! Query: %v\n\n", q) return err diff --git a/db/sql/migrations/v2.7.13.sql b/db/sql/migrations/v2.7.13.sql new file mode 100644 index 00000000..f6aa247f --- /dev/null +++ b/db/sql/migrations/v2.7.13.sql @@ -0,0 +1,3 @@ +alter table project__template_schedule rename to project__schedule; +alter table `project__schedule` add `id` integer primary key autoincrement; +alter table `project__schedule` add `project_id` int not null references project(`id`); diff --git a/db/sql/schedule.go b/db/sql/schedule.go new file mode 100644 index 00000000..3a864393 --- /dev/null +++ b/db/sql/schedule.go @@ -0,0 +1,67 @@ +package sql + +import ( + "database/sql" + "github.com/ansible-semaphore/semaphore/db" +) + +func (d *SqlDb) CreateSchedule(schedule db.Schedule) (newSchedule db.Schedule, err error) { + insertID, err := d.insert( + "id", + "insert into project__schedule (project_id, template_id, cron_format)" + + "values (?, ?, ?)", + schedule.ProjectID, + schedule.TemplateID, + schedule.CronFormat) + + if err != nil { + return + } + + newSchedule = schedule + newSchedule.ID = insertID + + return +} + +func (d *SqlDb) UpdateSchedule(schedule db.Schedule) error { + _, err := d.exec("update project__schedule set cron_format=? where project_id=? and id=?", + schedule.CronFormat, + schedule.ProjectID, + schedule.ID) + + return err +} + +func (d *SqlDb) GetSchedule(projectID int, scheduleID int) (template db.Schedule, err error) { + err = d.selectOne( + &template, + "select * from project__schedule where project_id=? and id=?", + projectID, + scheduleID) + + if err == sql.ErrNoRows { + err = db.ErrNotFound + } + + return +} + +func (d *SqlDb) DeleteSchedule(projectID int, scheduleID int) error { + _, err := d.exec("delete project__schedule where project_id=? and id=?", projectID, scheduleID) + + return err +} + +func (d *SqlDb) GetSchedules() (schedules []db.Schedule, err error) { + _, err = d.selectAll(&schedules, "select * from project__template_schedule where cron_format != ''") + return +} + +func (d *SqlDb) GetTemplateSchedules(projectID int, templateID int) (schedules []db.Schedule, err error) { + _, err = d.selectAll(&schedules, + "select * from project__template_schedule where project_id=? and template_id=?", + projectID, + templateID) + return +} diff --git a/db/sql/template.go b/db/sql/template.go index 4ee5050c..bbacf78a 100644 --- a/db/sql/template.go +++ b/db/sql/template.go @@ -24,15 +24,21 @@ func (d *SqlDb) CreateTemplate(template db.Template) (newTemplate db.Template, e return } + err = db.FillTemplate(d, &newTemplate) + + if err != nil { + return + } + newTemplate = template newTemplate.ID = insertID - err = db.FillTemplate(d, &newTemplate) + return } func (d *SqlDb) UpdateTemplate(template db.Template) error { _, err := d.exec("update project__template set inventory_id=?, repository_id=?, environment_id=?, alias=?, " + - "playbook=?, arguments=?, override_args=? where removed = false and id=?", + "playbook=?, arguments=?, override_args=? where removed = false and id=? and project_id=?", template.InventoryID, template.RepositoryID, template.EnvironmentID, @@ -40,8 +46,33 @@ func (d *SqlDb) UpdateTemplate(template db.Template) error { template.Playbook, template.Arguments, template.OverrideArguments, - template.ID) - + template.ID, + template.ProjectID) + + //if err != nil { + // return err + //} + // + //if template.CronFormat == "" { + // _, err = d.exec( + // "delete from project__template_schedule where project_id =? and template_id=?", + // template.ProjectID, + // template.ID) + //} else { + // _, err = d.GetTemplateSchedules(template.ProjectID, template.ID) + // if err == nil { + // _, err = d.exec( + // "update project__template_schedule set cron_format=? where project_id =? and template_id=?", + // template.CronFormat, + // template.ProjectID, + // template.ID) + // } else if err == db.ErrNotFound { + // _, err = d.exec( + // "insert into project__template_schedule (template_id, cron_format) values (?, ?)", + // template.ID, + // template.CronFormat) + // } + //} return err } @@ -117,11 +148,4 @@ func (d *SqlDb) DeleteTemplate(projectID int, templateID int) error { _, err := d.exec("update project__template set removed=true where project_id=? and id=?", projectID, templateID) return err - - //res, err := d.exec( - // "delete from project__template where project_id=? and id=?", - // projectID, - // templateID) - - //return validateMutationResult(res, err) } diff --git a/go.mod b/go.mod index 3a01d7dd..f9b31cf2 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( github.com/onsi/ginkgo v1.12.0 // indirect github.com/onsi/gomega v1.9.0 // indirect github.com/radovskyb/watcher v1.0.7 // indirect + github.com/robfig/cron/v3 v3.0.1 github.com/russross/blackfriday v1.5.2 // indirect github.com/sirupsen/logrus v1.4.2 // indirect github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa diff --git a/go.sum b/go.sum index 01c71b6a..0f4aeb29 100644 --- a/go.sum +++ b/go.sum @@ -379,6 +379,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= diff --git a/web2/src/App.vue b/web2/src/App.vue index 1fef8cd8..0f775c93 100644 --- a/web2/src/App.vue +++ b/web2/src/App.vue @@ -762,11 +762,11 @@ export default { if (!this.isAuthenticated) { return; } - this.user = (await axios({ - method: 'get', - url: '/api/user', - responseType: 'json', - })).data; + this.user = (await axios({ + method: 'get', + url: '/api/user', + responseType: 'json', + })).data; }, getProjectColor(projectData) { diff --git a/web2/src/components/ItemFormBase.js b/web2/src/components/ItemFormBase.js index 3b9ed6e2..27b444c2 100644 --- a/web2/src/components/ItemFormBase.js +++ b/web2/src/components/ItemFormBase.js @@ -79,6 +79,10 @@ export default { throw new Error('Not implemented'); // must me implemented in template }, + afterSave() { + + }, + getNewItem() { return {}; }, @@ -142,6 +146,8 @@ export default { ...(this.getRequestOptions()), })).data; + await this.afterSave(); + this.$emit('save', { item: item || this.item, action: this.isNew ? 'new' : 'edit', diff --git a/web2/src/components/TemplateForm.vue b/web2/src/components/TemplateForm.vue index 8434c79a..74dadfe7 100644 --- a/web2/src/components/TemplateForm.vue +++ b/web2/src/components/TemplateForm.vue @@ -67,6 +67,13 @@ :disabled="formSaving" > + @@ -134,6 +141,8 @@ export default { inventory: null, repositories: null, environment: null, + schedules: null, + cronFormat: null, }; }, @@ -177,6 +186,14 @@ export default { url: `/api/project/${this.projectId}/environment`, responseType: 'json', })).data; + this.schedules = (await axios({ + keys: 'get', + url: `/api/project/${this.projectId}/templates/${this.sourceItemId}/schedules`, + responseType: 'json', + })).data; + if (this.schedules.length === 1) { + this.cronFormat = this.schedules[0].cron_format; + } }, computed: { @@ -189,7 +206,8 @@ export default { && this.repositories != null && this.inventory != null && this.environment != null - && this.item != null; + && this.item != null + && this.schedules != null; }, loginPasswordKeys() { @@ -208,6 +226,45 @@ export default { getSingleItemUrl() { return `/api/project/${this.projectId}/templates/${this.itemId}`; }, + + async afterSave(newItem) { + if (newItem || this.schedules.length === 0) { + if (this.cronFormat !== '') { + // new schedule + await axios({ + method: 'post', + url: `/api/project/${this.projectId}/schedules`, + responseType: 'json', + data: { + project_id: this.projectId, + template_id: newItem ? newItem.id : this.itemId, + cron_format: this.cronFormat, + }, + }); + } + } else if (this.schedules.length > 1) { + // do nothing + } else if (this.schedules === '') { + // drop schedule + await axios({ + method: 'delete', + url: `/api/project/${this.projectId}/schedules/${this.schedules[0].id}`, + responseType: 'json', + }); + } else { + // update schedule + await axios({ + method: 'put', + url: `/api/project/${this.projectId}/schedules/${this.schedules[0].id}`, + responseType: 'json', + data: { + project_id: this.projectId, + template_id: this.itemId, + cron_format: this.cronFormat, + }, + }); + } + }, }, };