diff --git a/cmd/messages.go b/cmd/messages.go
index 5f38c358..0c6406f7 100644
--- a/cmd/messages.go
+++ b/cmd/messages.go
@@ -173,6 +173,5 @@ func handleSendMessage(r *fastglue.Request) error {
// Evaluate automation rules.
app.automation.EvaluateConversationUpdateRules(cuuid, models.EventConversationMessageOutgoing)
}
-
return r.SendEnvelope(true)
}
diff --git a/cmd/sla.go b/cmd/sla.go
index c58d264d..c6f2fb2e 100644
--- a/cmd/sla.go
+++ b/cmd/sla.go
@@ -29,7 +29,7 @@ func handleGetSLA(r *fastglue.Request) error {
)
id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string))
if err != nil || id == 0 {
- return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "SLA `id`"), nil, envelope.InputError)
+ return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
}
sla, err := app.sla.Get(id)
@@ -54,7 +54,7 @@ func handleCreateSLA(r *fastglue.Request) error {
return sendErrorEnvelope(r, err)
}
- if err := app.sla.Create(sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.Notifications); err != nil {
+ if err := app.sla.Create(sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.NextResponseTime, sla.Notifications); err != nil {
return sendErrorEnvelope(r, err)
}
@@ -70,7 +70,7 @@ func handleUpdateSLA(r *fastglue.Request) error {
id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string))
if err != nil || id == 0 {
- return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "SLA `id`"), nil, envelope.InputError)
+ return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
}
if err := r.Decode(&sla, "json"); err != nil {
@@ -81,11 +81,11 @@ func handleUpdateSLA(r *fastglue.Request) error {
return sendErrorEnvelope(r, err)
}
- if err := app.sla.Update(id, sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.Notifications); err != nil {
+ if err := app.sla.Update(id, sla.Name, sla.Description, sla.FirstResponseTime, sla.ResolutionTime, sla.NextResponseTime, sla.Notifications); err != nil {
return sendErrorEnvelope(r, err)
}
- return r.SendEnvelope("SLA updated successfully.")
+ return r.SendEnvelope(true)
}
// handleDeleteSLA deletes the SLA with the given ID.
@@ -95,7 +95,7 @@ func handleDeleteSLA(r *fastglue.Request) error {
)
id, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string))
if err != nil || id == 0 {
- return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "SLA `id`"), nil, envelope.InputError)
+ return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError)
}
if err = app.sla.Delete(id); err != nil {
@@ -108,51 +108,79 @@ func handleDeleteSLA(r *fastglue.Request) error {
// validateSLA validates the SLA policy and returns an envelope.Error if any validation fails.
func validateSLA(app *App, sla *smodels.SLAPolicy) error {
if sla.Name == "" {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA `name`"), nil)
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`name`"), nil)
}
- if sla.FirstResponseTime == "" {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA `first_response_time`"), nil)
- }
- if sla.ResolutionTime == "" {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA `resolution_time`"), nil)
+ if sla.FirstResponseTime.String == "" && sla.NextResponseTime.String == "" && sla.ResolutionTime.String == "" {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "At least one of `first_response_time`, `next_response_time`, or `resolution_time` must be provided."), nil)
}
- // Validate notifications if any
+ // Validate notifications if any.
for _, n := range sla.Notifications {
if n.Type == "" {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA notification `type`"), nil)
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`type`"), nil)
}
if n.TimeDelayType == "" {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA notification `time_delay_type`"), nil)
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`time_delay_type`"), nil)
+ }
+ if n.Metric == "" {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`metric`"), nil)
}
if n.TimeDelayType != "immediately" {
if n.TimeDelay == "" {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA notification `time_delay`"), nil)
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`time_delay`"), nil)
+ }
+ // Validate time delay duration.
+ td, err := time.ParseDuration(n.TimeDelay)
+ if err != nil {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`time_delay`"), nil)
+ }
+ if td.Minutes() < 1 {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`time_delay`"), nil)
}
}
if len(n.Recipients) == 0 {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "SLA notification `recipients`"), nil)
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.empty", "name", "`recipients`"), nil)
}
}
- // Validate time duration strings
- frt, err := time.ParseDuration(sla.FirstResponseTime)
- if err != nil {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`first_response_time`"), nil)
- }
- if frt.Minutes() < 1 {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`first_response_time`"), nil)
+ // Validate first response time duration string if not empty.
+ if sla.FirstResponseTime.String != "" {
+ frt, err := time.ParseDuration(sla.FirstResponseTime.String)
+ if err != nil {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`first_response_time`"), nil)
+ }
+ if frt.Minutes() < 1 {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`first_response_time`"), nil)
+ }
}
- rt, err := time.ParseDuration(sla.ResolutionTime)
- if err != nil {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`resolution_time`"), nil)
+ // Validate resolution time duration string if not empty.
+ if sla.ResolutionTime.String != "" {
+ rt, err := time.ParseDuration(sla.ResolutionTime.String)
+ if err != nil {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`resolution_time`"), nil)
+ }
+ if rt.Minutes() < 1 {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`resolution_time`"), nil)
+ }
+ // Compare with first response time if both are present.
+ if sla.FirstResponseTime.String != "" {
+ frt, _ := time.ParseDuration(sla.FirstResponseTime.String)
+ if frt > rt {
+ return envelope.NewError(envelope.InputError, app.i18n.T("sla.firstResponseTimeAfterResolution"), nil)
+ }
+ }
}
- if rt.Minutes() < 1 {
- return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`resolution_time`"), nil)
- }
- if frt > rt {
- return envelope.NewError(envelope.InputError, app.i18n.T("sla.firstResponseTimeAfterResolution"), nil)
+
+ // Validate next response time duration string if not empty.
+ if sla.NextResponseTime.String != "" {
+ nrt, err := time.ParseDuration(sla.NextResponseTime.String)
+ if err != nil {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`next_response_time`"), nil)
+ }
+ if nrt.Minutes() < 1 {
+ return envelope.NewError(envelope.InputError, app.i18n.Ts("globals.messages.invalid", "name", "`next_response_time`"), nil)
+ }
}
return nil
diff --git a/frontend/src/components/sidebar/Sidebar.vue b/frontend/src/components/sidebar/Sidebar.vue
index 552e14ad..f6e6a1f8 100644
--- a/frontend/src/components/sidebar/Sidebar.vue
+++ b/frontend/src/components/sidebar/Sidebar.vue
@@ -340,7 +340,7 @@ const viewInboxOpen = useStorage('viewInboxOpen', true)
- {{ notification.type === 'warning' ? 'Pre-breach alert' : 'Post-breach action' }} + {{ notification.type === 'warning' ? 'Pre-breach alert' : 'Post-breach alert' }}