WIP new WebAdmin: event rules
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
c85601146d
commit
ad80d4e475
11 changed files with 1264 additions and 1014 deletions
2
go.mod
2
go.mod
|
@ -54,7 +54,7 @@ require (
|
||||||
github.com/rs/xid v1.5.0
|
github.com/rs/xid v1.5.0
|
||||||
github.com/rs/zerolog v1.31.0
|
github.com/rs/zerolog v1.31.0
|
||||||
github.com/sftpgo/sdk v0.1.6-0.20240114195211-3f4916cc829c
|
github.com/sftpgo/sdk v0.1.6-0.20240114195211-3f4916cc829c
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12
|
github.com/shirou/gopsutil/v3 v3.24.1
|
||||||
github.com/spf13/afero v1.11.0
|
github.com/spf13/afero v1.11.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -352,8 +352,8 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
github.com/sftpgo/sdk v0.1.6-0.20240114195211-3f4916cc829c h1:07TYPvNbOnmKsBxjNsUr+gsILIUWflw1UYwjn1jognM=
|
github.com/sftpgo/sdk v0.1.6-0.20240114195211-3f4916cc829c h1:07TYPvNbOnmKsBxjNsUr+gsILIUWflw1UYwjn1jognM=
|
||||||
github.com/sftpgo/sdk v0.1.6-0.20240114195211-3f4916cc829c/go.mod h1:AWoY2YYe/P1ymfTlRER/meERQjCcZZTbgVPGcPQgaqc=
|
github.com/sftpgo/sdk v0.1.6-0.20240114195211-3f4916cc829c/go.mod h1:AWoY2YYe/P1ymfTlRER/meERQjCcZZTbgVPGcPQgaqc=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
|
|
@ -117,19 +117,19 @@ func isEventTriggerValid(trigger int) bool {
|
||||||
func getTriggerTypeAsString(trigger int) string {
|
func getTriggerTypeAsString(trigger int) string {
|
||||||
switch trigger {
|
switch trigger {
|
||||||
case EventTriggerFsEvent:
|
case EventTriggerFsEvent:
|
||||||
return "Filesystem event"
|
return util.I18nTriggerFsEvent
|
||||||
case EventTriggerProviderEvent:
|
case EventTriggerProviderEvent:
|
||||||
return "Provider event"
|
return util.I18nTriggerProviderEvent
|
||||||
case EventTriggerIPBlocked:
|
case EventTriggerIPBlocked:
|
||||||
return "IP blocked"
|
return util.I18nTriggerIPBlockedEvent
|
||||||
case EventTriggerCertificate:
|
case EventTriggerCertificate:
|
||||||
return "Certificate renewal"
|
return util.I18nTriggerCertificateRenewEvent
|
||||||
case EventTriggerOnDemand:
|
case EventTriggerOnDemand:
|
||||||
return "On demand"
|
return util.I18nTriggerOnDemandEvent
|
||||||
case EventTriggerIDPLogin:
|
case EventTriggerIDPLogin:
|
||||||
return "Identity Provider login"
|
return util.I18nTriggerIDPLoginEvent
|
||||||
default:
|
default:
|
||||||
return "Schedule"
|
return util.I18nTriggerScheduleEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1212,17 +1212,26 @@ func (a *EventAction) getACopy() EventAction {
|
||||||
func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error {
|
func (a *EventAction) validateAssociation(trigger int, fsEvents []string) error {
|
||||||
if a.Options.IsFailureAction {
|
if a.Options.IsFailureAction {
|
||||||
if a.Options.ExecuteSync {
|
if a.Options.ExecuteSync {
|
||||||
return util.NewValidationError("sync execution is not supported for failure actions")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("sync execution is not supported for failure actions"),
|
||||||
|
util.I18nErrorEvSyncFailureActions,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if a.Options.ExecuteSync {
|
if a.Options.ExecuteSync {
|
||||||
if trigger != EventTriggerFsEvent && trigger != EventTriggerIDPLogin {
|
if trigger != EventTriggerFsEvent && trigger != EventTriggerIDPLogin {
|
||||||
return util.NewValidationError("sync execution is only supported for some filesystem events and Identity Provider logins")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("sync execution is only supported for some filesystem events and Identity Provider logins"),
|
||||||
|
util.I18nErrorEvSyncUnsupported,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if trigger == EventTriggerFsEvent {
|
if trigger == EventTriggerFsEvent {
|
||||||
for _, ev := range fsEvents {
|
for _, ev := range fsEvents {
|
||||||
if !util.Contains(allowedSyncFsEvents, ev) {
|
if !util.Contains(allowedSyncFsEvents, ev) {
|
||||||
return util.NewValidationError("sync execution is only supported for upload and pre-* events")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("sync execution is only supported for upload and pre-* events"),
|
||||||
|
util.I18nErrorEvSyncUnsupportedFs,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1379,11 +1388,14 @@ func (c *EventConditions) getACopy() EventConditions {
|
||||||
|
|
||||||
func (c *EventConditions) validateSchedules() error {
|
func (c *EventConditions) validateSchedules() error {
|
||||||
if len(c.Schedules) == 0 {
|
if len(c.Schedules) == 0 {
|
||||||
return util.NewValidationError("at least one schedule is required")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("at least one schedule is required"),
|
||||||
|
util.I18nErrorRuleScheduleRequired,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for _, schedule := range c.Schedules {
|
for _, schedule := range c.Schedules {
|
||||||
if err := schedule.validate(); err != nil {
|
if err := schedule.validate(); err != nil {
|
||||||
return err
|
return util.NewI18nError(err, util.I18nErrorRuleScheduleInvalid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1397,7 +1409,10 @@ func (c *EventConditions) validate(trigger int) error {
|
||||||
c.Options.ProviderObjects = nil
|
c.Options.ProviderObjects = nil
|
||||||
c.IDPLoginEvent = 0
|
c.IDPLoginEvent = 0
|
||||||
if len(c.FsEvents) == 0 {
|
if len(c.FsEvents) == 0 {
|
||||||
return util.NewValidationError("at least one filesystem event is required")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("at least one filesystem event is required"),
|
||||||
|
util.I18nErrorRuleFsEventRequired,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for _, ev := range c.FsEvents {
|
for _, ev := range c.FsEvents {
|
||||||
if !util.Contains(SupportedFsEvents, ev) {
|
if !util.Contains(SupportedFsEvents, ev) {
|
||||||
|
@ -1414,7 +1429,10 @@ func (c *EventConditions) validate(trigger int) error {
|
||||||
c.Options.MaxFileSize = 0
|
c.Options.MaxFileSize = 0
|
||||||
c.IDPLoginEvent = 0
|
c.IDPLoginEvent = 0
|
||||||
if len(c.ProviderEvents) == 0 {
|
if len(c.ProviderEvents) == 0 {
|
||||||
return util.NewValidationError("at least one provider event is required")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("at least one provider event is required"),
|
||||||
|
util.I18nErrorRuleProviderEventRequired,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for _, ev := range c.ProviderEvents {
|
for _, ev := range c.ProviderEvents {
|
||||||
if !util.Contains(SupportedProviderEvents, ev) {
|
if !util.Contains(SupportedProviderEvents, ev) {
|
||||||
|
@ -1558,7 +1576,7 @@ func (r *EventRule) isStatusValid() bool {
|
||||||
|
|
||||||
func (r *EventRule) validate() error {
|
func (r *EventRule) validate() error {
|
||||||
if r.Name == "" {
|
if r.Name == "" {
|
||||||
return util.NewValidationError("name is mandatory")
|
return util.NewI18nError(util.NewValidationError("name is mandatory"), util.I18nErrorNameRequired)
|
||||||
}
|
}
|
||||||
if !r.isStatusValid() {
|
if !r.isStatusValid() {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid event rule status: %d", r.Status))
|
return util.NewValidationError(fmt.Sprintf("invalid event rule status: %d", r.Status))
|
||||||
|
@ -1570,7 +1588,7 @@ func (r *EventRule) validate() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(r.Actions) == 0 {
|
if len(r.Actions) == 0 {
|
||||||
return util.NewValidationError("at least one action is required")
|
return util.NewI18nError(util.NewValidationError("at least one action is required"), util.I18nErrorRuleActionRequired)
|
||||||
}
|
}
|
||||||
actionNames := make(map[string]bool)
|
actionNames := make(map[string]bool)
|
||||||
actionOrders := make(map[int]bool)
|
actionOrders := make(map[int]bool)
|
||||||
|
@ -1581,7 +1599,10 @@ func (r *EventRule) validate() error {
|
||||||
return util.NewValidationError(fmt.Sprintf("invalid action at position %d, name not specified", idx))
|
return util.NewValidationError(fmt.Sprintf("invalid action at position %d, name not specified", idx))
|
||||||
}
|
}
|
||||||
if actionNames[r.Actions[idx].Name] {
|
if actionNames[r.Actions[idx].Name] {
|
||||||
return util.NewValidationError(fmt.Sprintf("duplicated action %q", r.Actions[idx].Name))
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError(fmt.Sprintf("duplicated action %q", r.Actions[idx].Name)),
|
||||||
|
util.I18nErrorRuleDuplicateActions,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if actionOrders[r.Actions[idx].Order] {
|
if actionOrders[r.Actions[idx].Order] {
|
||||||
return util.NewValidationError(fmt.Sprintf("duplicated order %d for action %q",
|
return util.NewValidationError(fmt.Sprintf("duplicated order %d for action %q",
|
||||||
|
@ -1600,7 +1621,10 @@ func (r *EventRule) validate() error {
|
||||||
actionOrders[r.Actions[idx].Order] = true
|
actionOrders[r.Actions[idx].Order] = true
|
||||||
}
|
}
|
||||||
if len(r.Actions) == failureActions {
|
if len(r.Actions) == failureActions {
|
||||||
return util.NewValidationError("at least a non-failure action is required")
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError("at least a non-failure action is required"),
|
||||||
|
util.I18nErrorRuleFailureActionsOnly,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if !hasSyncAction {
|
if !hasSyncAction {
|
||||||
return r.validateMandatorySyncActions()
|
return r.validateMandatorySyncActions()
|
||||||
|
@ -1614,7 +1638,13 @@ func (r *EventRule) validateMandatorySyncActions() error {
|
||||||
}
|
}
|
||||||
for _, ev := range r.Conditions.FsEvents {
|
for _, ev := range r.Conditions.FsEvents {
|
||||||
if util.Contains(mandatorySyncFsEvents, ev) {
|
if util.Contains(mandatorySyncFsEvents, ev) {
|
||||||
return util.NewValidationError(fmt.Sprintf("event %q requires at least a sync action", ev))
|
return util.NewI18nError(
|
||||||
|
util.NewValidationError(fmt.Sprintf("event %q requires at least a sync action", ev)),
|
||||||
|
util.I18nErrorRuleSyncActionRequired,
|
||||||
|
util.I18nErrorArgs(map[string]any{
|
||||||
|
"val": ev,
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1775,6 +1775,8 @@ func (s *httpdServer) setupWebAdminRoutes() {
|
||||||
s.handleWebUpdateEventActionPost)
|
s.handleWebUpdateEventActionPost)
|
||||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader).
|
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), verifyCSRFHeader).
|
||||||
Delete(webAdminEventActionPath+"/{name}", deleteEventAction)
|
Delete(webAdminEventActionPath+"/{name}", deleteEventAction)
|
||||||
|
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), compressor.Handler, s.refreshCookie).
|
||||||
|
Get(webAdminEventRulesPath+jsonAPISuffix, getAllRules)
|
||||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||||
Get(webAdminEventRulesPath, s.handleWebGetEventRules)
|
Get(webAdminEventRulesPath, s.handleWebGetEventRules)
|
||||||
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
router.With(s.checkPerm(dataprovider.PermAdminManageEventRules), s.refreshCookie).
|
||||||
|
|
|
@ -98,7 +98,6 @@ const (
|
||||||
templateMaintenance = "maintenance.html"
|
templateMaintenance = "maintenance.html"
|
||||||
templateMFA = "mfa.html"
|
templateMFA = "mfa.html"
|
||||||
templateSetup = "adminsetup.html"
|
templateSetup = "adminsetup.html"
|
||||||
pageEventRulesTitle = "Event rules"
|
|
||||||
defaultQueryLimit = 1000
|
defaultQueryLimit = 1000
|
||||||
inversePatternType = "inverse"
|
inversePatternType = "inverse"
|
||||||
)
|
)
|
||||||
|
@ -153,11 +152,6 @@ type basePage struct {
|
||||||
Branding UIBranding
|
Branding UIBranding
|
||||||
}
|
}
|
||||||
|
|
||||||
type eventRulesPage struct {
|
|
||||||
basePage
|
|
||||||
Rules []dataprovider.EventRule
|
|
||||||
}
|
|
||||||
|
|
||||||
type statusPage struct {
|
type statusPage struct {
|
||||||
basePage
|
basePage
|
||||||
Status *ServicesStatus
|
Status *ServicesStatus
|
||||||
|
@ -312,7 +306,7 @@ type eventRulePage struct {
|
||||||
Protocols []string
|
Protocols []string
|
||||||
ProviderEvents []string
|
ProviderEvents []string
|
||||||
ProviderObjects []string
|
ProviderObjects []string
|
||||||
Error string
|
Error *util.I18nError
|
||||||
Mode genericPageMode
|
Mode genericPageMode
|
||||||
IsShared bool
|
IsShared bool
|
||||||
}
|
}
|
||||||
|
@ -1105,19 +1099,19 @@ func (s *httpdServer) renderEventActionPage(w http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpdServer) renderEventRulePage(w http.ResponseWriter, r *http.Request, rule dataprovider.EventRule,
|
func (s *httpdServer) renderEventRulePage(w http.ResponseWriter, r *http.Request, rule dataprovider.EventRule,
|
||||||
mode genericPageMode, error string,
|
mode genericPageMode, err error,
|
||||||
) {
|
) {
|
||||||
actions, err := s.getWebEventActions(w, r, defaultQueryLimit, true)
|
actions, errActions := s.getWebEventActions(w, r, defaultQueryLimit, true)
|
||||||
if err != nil {
|
if errActions != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var title, currentURL string
|
var title, currentURL string
|
||||||
switch mode {
|
switch mode {
|
||||||
case genericPageModeAdd:
|
case genericPageModeAdd:
|
||||||
title = "Add new event rules"
|
title = util.I18nAddRuleTitle
|
||||||
currentURL = webAdminEventRulePath
|
currentURL = webAdminEventRulePath
|
||||||
case genericPageModeUpdate:
|
case genericPageModeUpdate:
|
||||||
title = "Update event rules"
|
title = util.I18nUpdateRuleTitle
|
||||||
currentURL = fmt.Sprintf("%v/%v", webAdminEventRulePath, url.PathEscape(rule.Name))
|
currentURL = fmt.Sprintf("%v/%v", webAdminEventRulePath, url.PathEscape(rule.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1130,7 +1124,7 @@ func (s *httpdServer) renderEventRulePage(w http.ResponseWriter, r *http.Request
|
||||||
Protocols: dataprovider.SupportedRuleConditionProtocols,
|
Protocols: dataprovider.SupportedRuleConditionProtocols,
|
||||||
ProviderEvents: dataprovider.SupportedProviderEvents,
|
ProviderEvents: dataprovider.SupportedProviderEvents,
|
||||||
ProviderObjects: dataprovider.SupporteRuleConditionProviderObjects,
|
ProviderObjects: dataprovider.SupporteRuleConditionProviderObjects,
|
||||||
Error: error,
|
Error: getI18nError(err),
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
IsShared: s.isShared > 0,
|
IsShared: s.isShared > 0,
|
||||||
}
|
}
|
||||||
|
@ -2420,74 +2414,66 @@ func getIDPLoginEventFromPostField(r *http.Request) int {
|
||||||
func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventConditions, error) {
|
func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventConditions, error) {
|
||||||
var schedules []dataprovider.Schedule
|
var schedules []dataprovider.Schedule
|
||||||
var names, groupNames, roleNames, fsPaths []dataprovider.ConditionPattern
|
var names, groupNames, roleNames, fsPaths []dataprovider.ConditionPattern
|
||||||
for k := range r.Form {
|
|
||||||
if strings.HasPrefix(k, "schedule_hour") {
|
scheduleHours := r.Form["schedule_hour"]
|
||||||
hour := strings.TrimSpace(r.Form.Get(k))
|
scheduleDayOfWeeks := r.Form["schedule_day_of_week"]
|
||||||
if hour != "" {
|
scheduleDayOfMonths := r.Form["schedule_day_of_month"]
|
||||||
idx := strings.TrimPrefix(k, "schedule_hour")
|
scheduleMonths := r.Form["schedule_month"]
|
||||||
dayOfWeek := strings.TrimSpace(r.Form.Get(fmt.Sprintf("schedule_day_of_week%s", idx)))
|
|
||||||
dayOfMonth := strings.TrimSpace(r.Form.Get(fmt.Sprintf("schedule_day_of_month%s", idx)))
|
for idx, hour := range scheduleHours {
|
||||||
month := strings.TrimSpace(r.Form.Get(fmt.Sprintf("schedule_month%s", idx)))
|
if hour != "" {
|
||||||
schedules = append(schedules, dataprovider.Schedule{
|
schedules = append(schedules, dataprovider.Schedule{
|
||||||
Hours: hour,
|
Hours: hour,
|
||||||
DayOfWeek: dayOfWeek,
|
DayOfWeek: scheduleDayOfWeeks[idx],
|
||||||
DayOfMonth: dayOfMonth,
|
DayOfMonth: scheduleDayOfMonths[idx],
|
||||||
Month: month,
|
Month: scheduleMonths[idx],
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(k, "name_pattern") {
|
|
||||||
pattern := strings.TrimSpace(r.Form.Get(k))
|
|
||||||
if pattern != "" {
|
|
||||||
idx := strings.TrimPrefix(k, "name_pattern")
|
|
||||||
patternType := r.Form.Get(fmt.Sprintf("type_name_pattern%s", idx))
|
|
||||||
names = append(names, dataprovider.ConditionPattern{
|
|
||||||
Pattern: pattern,
|
|
||||||
InverseMatch: patternType == inversePatternType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(k, "group_name_pattern") {
|
|
||||||
pattern := strings.TrimSpace(r.Form.Get(k))
|
|
||||||
if pattern != "" {
|
|
||||||
idx := strings.TrimPrefix(k, "group_name_pattern")
|
|
||||||
patternType := r.Form.Get(fmt.Sprintf("type_group_name_pattern%s", idx))
|
|
||||||
groupNames = append(groupNames, dataprovider.ConditionPattern{
|
|
||||||
Pattern: pattern,
|
|
||||||
InverseMatch: patternType == inversePatternType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(k, "role_name_pattern") {
|
|
||||||
pattern := strings.TrimSpace(r.Form.Get(k))
|
|
||||||
if pattern != "" {
|
|
||||||
idx := strings.TrimPrefix(k, "role_name_pattern")
|
|
||||||
patternType := r.Form.Get(fmt.Sprintf("type_role_name_pattern%s", idx))
|
|
||||||
roleNames = append(roleNames, dataprovider.ConditionPattern{
|
|
||||||
Pattern: pattern,
|
|
||||||
InverseMatch: patternType == inversePatternType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(k, "fs_path_pattern") {
|
|
||||||
pattern := strings.TrimSpace(r.Form.Get(k))
|
|
||||||
if pattern != "" {
|
|
||||||
idx := strings.TrimPrefix(k, "fs_path_pattern")
|
|
||||||
patternType := r.Form.Get(fmt.Sprintf("type_fs_path_pattern%s", idx))
|
|
||||||
fsPaths = append(fsPaths, dataprovider.ConditionPattern{
|
|
||||||
Pattern: pattern,
|
|
||||||
InverseMatch: patternType == inversePatternType,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for idx, name := range r.Form["name_pattern"] {
|
||||||
|
if name != "" {
|
||||||
|
names = append(names, dataprovider.ConditionPattern{
|
||||||
|
Pattern: name,
|
||||||
|
InverseMatch: r.Form["type_name_pattern"][idx] == inversePatternType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, name := range r.Form["group_name_pattern"] {
|
||||||
|
if name != "" {
|
||||||
|
groupNames = append(groupNames, dataprovider.ConditionPattern{
|
||||||
|
Pattern: name,
|
||||||
|
InverseMatch: r.Form["type_group_name_pattern"][idx] == inversePatternType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, name := range r.Form["role_name_pattern"] {
|
||||||
|
if name != "" {
|
||||||
|
roleNames = append(roleNames, dataprovider.ConditionPattern{
|
||||||
|
Pattern: name,
|
||||||
|
InverseMatch: r.Form["type_role_name_pattern"][idx] == inversePatternType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, name := range r.Form["fs_path_pattern"] {
|
||||||
|
if name != "" {
|
||||||
|
fsPaths = append(fsPaths, dataprovider.ConditionPattern{
|
||||||
|
Pattern: name,
|
||||||
|
InverseMatch: r.Form["type_fs_path_pattern"][idx] == inversePatternType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
minFileSize, err := util.ParseBytes(r.Form.Get("fs_min_size"))
|
minFileSize, err := util.ParseBytes(r.Form.Get("fs_min_size"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dataprovider.EventConditions{}, fmt.Errorf("invalid min file size: %w", err)
|
return dataprovider.EventConditions{}, util.NewI18nError(fmt.Errorf("invalid min file size: %w", err), util.I18nErrorInvalidMinSize)
|
||||||
}
|
}
|
||||||
maxFileSize, err := util.ParseBytes(r.Form.Get("fs_max_size"))
|
maxFileSize, err := util.ParseBytes(r.Form.Get("fs_max_size"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dataprovider.EventConditions{}, fmt.Errorf("invalid max file size: %w", err)
|
return dataprovider.EventConditions{}, util.NewI18nError(fmt.Errorf("invalid max file size: %w", err), util.I18nErrorInvalidMaxSize)
|
||||||
}
|
}
|
||||||
conditions := dataprovider.EventConditions{
|
conditions := dataprovider.EventConditions{
|
||||||
FsEvents: r.Form["fs_events"],
|
FsEvents: r.Form["fs_events"],
|
||||||
|
@ -2511,38 +2497,86 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo
|
||||||
|
|
||||||
func getEventRuleActionsFromPostFields(r *http.Request) ([]dataprovider.EventAction, error) {
|
func getEventRuleActionsFromPostFields(r *http.Request) ([]dataprovider.EventAction, error) {
|
||||||
var actions []dataprovider.EventAction
|
var actions []dataprovider.EventAction
|
||||||
for k := range r.Form {
|
|
||||||
if strings.HasPrefix(k, "action_name") {
|
names := r.Form["action_name"]
|
||||||
name := strings.TrimSpace(r.Form.Get(k))
|
orders := r.Form["action_order"]
|
||||||
if name != "" {
|
|
||||||
idx := strings.TrimPrefix(k, "action_name")
|
for idx, name := range names {
|
||||||
order, err := strconv.Atoi(r.Form.Get(fmt.Sprintf("action_order%s", idx)))
|
if name != "" {
|
||||||
if err != nil {
|
order, err := strconv.Atoi(orders[idx])
|
||||||
return actions, fmt.Errorf("invalid order: %w", err)
|
if err != nil {
|
||||||
}
|
return actions, fmt.Errorf("invalid order: %w", err)
|
||||||
options := r.Form[fmt.Sprintf("action_options%s", idx)]
|
|
||||||
actions = append(actions, dataprovider.EventAction{
|
|
||||||
BaseEventAction: dataprovider.BaseEventAction{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
Order: order + 1,
|
|
||||||
Options: dataprovider.EventActionOptions{
|
|
||||||
IsFailureAction: util.Contains(options, "1"),
|
|
||||||
StopOnFailure: util.Contains(options, "2"),
|
|
||||||
ExecuteSync: util.Contains(options, "3"),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
options := r.Form["action_options"+strconv.Itoa(idx)]
|
||||||
|
actions = append(actions, dataprovider.EventAction{
|
||||||
|
BaseEventAction: dataprovider.BaseEventAction{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Order: order + 1,
|
||||||
|
Options: dataprovider.EventActionOptions{
|
||||||
|
IsFailureAction: util.Contains(options, "1"),
|
||||||
|
StopOnFailure: util.Contains(options, "2"),
|
||||||
|
ExecuteSync: util.Contains(options, "3"),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions, nil
|
return actions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateRepeaterFormRuleFields(r *http.Request) {
|
||||||
|
for k := range r.Form {
|
||||||
|
if hasPrefixAndSuffix(k, "schedules[", "][schedule_hour]") {
|
||||||
|
base, _ := strings.CutSuffix(k, "[schedule_hour]")
|
||||||
|
r.Form.Add("schedule_hour", strings.TrimSpace(r.Form.Get(k)))
|
||||||
|
r.Form.Add("schedule_day_of_week", strings.TrimSpace(r.Form.Get(base+"[schedule_day_of_week]")))
|
||||||
|
r.Form.Add("schedule_day_of_month", strings.TrimSpace(r.Form.Get(base+"[schedule_day_of_month]")))
|
||||||
|
r.Form.Add("schedule_month", strings.TrimSpace(r.Form.Get(base+"[schedule_month]")))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasPrefixAndSuffix(k, "name_filters[", "][name_pattern]") {
|
||||||
|
base, _ := strings.CutSuffix(k, "[name_pattern]")
|
||||||
|
r.Form.Add("name_pattern", strings.TrimSpace(r.Form.Get(k)))
|
||||||
|
r.Form.Add("type_name_pattern", strings.TrimSpace(r.Form.Get(base+"[type_name_pattern]")))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasPrefixAndSuffix(k, "group_name_filters[", "][group_name_pattern]") {
|
||||||
|
base, _ := strings.CutSuffix(k, "[group_name_pattern]")
|
||||||
|
r.Form.Add("group_name_pattern", strings.TrimSpace(r.Form.Get(k)))
|
||||||
|
r.Form.Add("type_group_name_pattern", strings.TrimSpace(r.Form.Get(base+"[type_group_name_pattern]")))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasPrefixAndSuffix(k, "role_name_filters[", "][role_name_pattern]") {
|
||||||
|
base, _ := strings.CutSuffix(k, "[role_name_pattern]")
|
||||||
|
r.Form.Add("role_name_pattern", strings.TrimSpace(r.Form.Get(k)))
|
||||||
|
r.Form.Add("type_role_name_pattern", strings.TrimSpace(r.Form.Get(base+"[type_role_name_pattern]")))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasPrefixAndSuffix(k, "path_filters[", "][fs_path_pattern]") {
|
||||||
|
base, _ := strings.CutSuffix(k, "[fs_path_pattern]")
|
||||||
|
r.Form.Add("fs_path_pattern", strings.TrimSpace(r.Form.Get(k)))
|
||||||
|
r.Form.Add("type_fs_path_pattern", strings.TrimSpace(r.Form.Get(base+"[type_fs_path_pattern]")))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if hasPrefixAndSuffix(k, "actions[", "][action_name]") {
|
||||||
|
base, _ := strings.CutSuffix(k, "[action_name]")
|
||||||
|
order, _ := strings.CutPrefix(k, "actions[")
|
||||||
|
order, _ = strings.CutSuffix(order, "][action_name]")
|
||||||
|
r.Form.Add("action_name", strings.TrimSpace(r.Form.Get(k)))
|
||||||
|
r.Form["action_options"+strconv.Itoa(len(r.Form["action_name"])-1)] = r.Form[base+"[action_options][]"]
|
||||||
|
r.Form.Add("action_order", order)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getEventRuleFromPostFields(r *http.Request) (dataprovider.EventRule, error) {
|
func getEventRuleFromPostFields(r *http.Request) (dataprovider.EventRule, error) {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dataprovider.EventRule{}, util.NewI18nError(err, util.I18nErrorInvalidForm)
|
return dataprovider.EventRule{}, util.NewI18nError(err, util.I18nErrorInvalidForm)
|
||||||
}
|
}
|
||||||
|
updateRepeaterFormRuleFields(r)
|
||||||
status, err := strconv.Atoi(r.Form.Get("status"))
|
status, err := strconv.Atoi(r.Form.Get("status"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return dataprovider.EventRule{}, fmt.Errorf("invalid status: %w", err)
|
return dataprovider.EventRule{}, fmt.Errorf("invalid status: %w", err)
|
||||||
|
@ -3789,31 +3823,27 @@ func (s *httpdServer) handleWebUpdateEventActionPost(w http.ResponseWriter, r *h
|
||||||
http.Redirect(w, r, webAdminEventActionsPath, http.StatusSeeOther)
|
http.Redirect(w, r, webAdminEventActionsPath, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpdServer) handleWebGetEventRules(w http.ResponseWriter, r *http.Request) {
|
func getAllRules(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||||
limit := defaultQueryLimit
|
rules := make([]dataprovider.EventRule, 0, 10)
|
||||||
if _, ok := r.URL.Query()["qlimit"]; ok {
|
|
||||||
if lim, err := strconv.Atoi(r.URL.Query().Get("qlimit")); err == nil {
|
|
||||||
limit = lim
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rules := make([]dataprovider.EventRule, 0, limit)
|
|
||||||
for {
|
for {
|
||||||
res, err := dataprovider.GetEventRules(limit, len(rules), dataprovider.OrderASC)
|
res, err := dataprovider.GetEventRules(defaultQueryLimit, len(rules), dataprovider.OrderASC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.renderInternalServerErrorPage(w, r, err)
|
sendAPIResponse(w, r, err, getI18NErrorString(err, util.I18nError500Message), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rules = append(rules, res...)
|
rules = append(rules, res...)
|
||||||
if len(res) < limit {
|
if len(res) < defaultQueryLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
render.JSON(w, r, rules)
|
||||||
|
}
|
||||||
|
|
||||||
data := eventRulesPage{
|
func (s *httpdServer) handleWebGetEventRules(w http.ResponseWriter, r *http.Request) {
|
||||||
basePage: s.getBasePageData(pageEventRulesTitle, webAdminEventRulesPath, r),
|
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||||
Rules: rules,
|
|
||||||
}
|
data := s.getBasePageData(util.I18nRulesTitle, webAdminEventRulesPath, r)
|
||||||
renderAdminTemplate(w, templateEventRules, data)
|
renderAdminTemplate(w, templateEventRules, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3823,7 +3853,7 @@ func (s *httpdServer) handleWebAddEventRuleGet(w http.ResponseWriter, r *http.Re
|
||||||
Status: 1,
|
Status: 1,
|
||||||
Trigger: dataprovider.EventTriggerFsEvent,
|
Trigger: dataprovider.EventTriggerFsEvent,
|
||||||
}
|
}
|
||||||
s.renderEventRulePage(w, r, rule, genericPageModeAdd, "")
|
s.renderEventRulePage(w, r, rule, genericPageModeAdd, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *httpdServer) handleWebAddEventRulePost(w http.ResponseWriter, r *http.Request) {
|
func (s *httpdServer) handleWebAddEventRulePost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -3835,7 +3865,7 @@ func (s *httpdServer) handleWebAddEventRulePost(w http.ResponseWriter, r *http.R
|
||||||
}
|
}
|
||||||
rule, err := getEventRuleFromPostFields(r)
|
rule, err := getEventRuleFromPostFields(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.renderEventRulePage(w, r, rule, genericPageModeAdd, err.Error())
|
s.renderEventRulePage(w, r, rule, genericPageModeAdd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||||
|
@ -3845,7 +3875,7 @@ func (s *httpdServer) handleWebAddEventRulePost(w http.ResponseWriter, r *http.R
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = dataprovider.AddEventRule(&rule, claims.Username, ipAddr, claims.Role); err != nil {
|
if err = dataprovider.AddEventRule(&rule, claims.Username, ipAddr, claims.Role); err != nil {
|
||||||
s.renderEventRulePage(w, r, rule, genericPageModeAdd, err.Error())
|
s.renderEventRulePage(w, r, rule, genericPageModeAdd, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, webAdminEventRulesPath, http.StatusSeeOther)
|
http.Redirect(w, r, webAdminEventRulesPath, http.StatusSeeOther)
|
||||||
|
@ -3856,7 +3886,7 @@ func (s *httpdServer) handleWebUpdateEventRuleGet(w http.ResponseWriter, r *http
|
||||||
name := getURLParam(r, "name")
|
name := getURLParam(r, "name")
|
||||||
rule, err := dataprovider.EventRuleExists(name)
|
rule, err := dataprovider.EventRuleExists(name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.renderEventRulePage(w, r, rule, genericPageModeUpdate, "")
|
s.renderEventRulePage(w, r, rule, genericPageModeUpdate, nil)
|
||||||
} else if errors.Is(err, util.ErrNotFound) {
|
} else if errors.Is(err, util.ErrNotFound) {
|
||||||
s.renderNotFoundPage(w, r, err)
|
s.renderNotFoundPage(w, r, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -3882,7 +3912,7 @@ func (s *httpdServer) handleWebUpdateEventRulePost(w http.ResponseWriter, r *htt
|
||||||
}
|
}
|
||||||
updatedRule, err := getEventRuleFromPostFields(r)
|
updatedRule, err := getEventRuleFromPostFields(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.renderEventRulePage(w, r, updatedRule, genericPageModeUpdate, err.Error())
|
s.renderEventRulePage(w, r, updatedRule, genericPageModeUpdate, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||||
|
@ -3894,7 +3924,7 @@ func (s *httpdServer) handleWebUpdateEventRulePost(w http.ResponseWriter, r *htt
|
||||||
updatedRule.Name = rule.Name
|
updatedRule.Name = rule.Name
|
||||||
err = dataprovider.UpdateEventRule(&updatedRule, claims.Username, ipAddr, claims.Role)
|
err = dataprovider.UpdateEventRule(&updatedRule, claims.Username, ipAddr, claims.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.renderEventRulePage(w, r, updatedRule, genericPageModeUpdate, err.Error())
|
s.renderEventRulePage(w, r, updatedRule, genericPageModeUpdate, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, webAdminEventRulesPath, http.StatusSeeOther)
|
http.Redirect(w, r, webAdminEventRulesPath, http.StatusSeeOther)
|
||||||
|
|
|
@ -69,8 +69,11 @@ const (
|
||||||
I18nDefenderTitle = "title.defender"
|
I18nDefenderTitle = "title.defender"
|
||||||
I18nEventsTitle = "title.logs"
|
I18nEventsTitle = "title.logs"
|
||||||
I18nActionsTitle = "title.event_actions"
|
I18nActionsTitle = "title.event_actions"
|
||||||
|
I18nRulesTitle = "title.event_rules"
|
||||||
I18nAddActionTitle = "title.add_action"
|
I18nAddActionTitle = "title.add_action"
|
||||||
I18nUpdateActionTitle = "title.update_action"
|
I18nUpdateActionTitle = "title.update_action"
|
||||||
|
I18nAddRuleTitle = "title.add_rule"
|
||||||
|
I18nUpdateRuleTitle = "title.update_rule"
|
||||||
I18nStatusTitle = "status.desc"
|
I18nStatusTitle = "status.desc"
|
||||||
I18nErrorSetupInstallCode = "setup.install_code_mismatch"
|
I18nErrorSetupInstallCode = "setup.install_code_mismatch"
|
||||||
I18nInvalidAuth = "general.invalid_auth_request"
|
I18nInvalidAuth = "general.invalid_auth_request"
|
||||||
|
@ -278,6 +281,26 @@ const (
|
||||||
I18nActionFsTypeCompress = "actions.fs_types.compress"
|
I18nActionFsTypeCompress = "actions.fs_types.compress"
|
||||||
I18nActionFsTypeCopy = "actions.fs_types.copy"
|
I18nActionFsTypeCopy = "actions.fs_types.copy"
|
||||||
I18nActionFsTypeCreateDirs = "actions.fs_types.create_dirs"
|
I18nActionFsTypeCreateDirs = "actions.fs_types.create_dirs"
|
||||||
|
I18nTriggerFsEvent = "rules.triggers.fs_event"
|
||||||
|
I18nTriggerProviderEvent = "rules.triggers.provider_event"
|
||||||
|
I18nTriggerIPBlockedEvent = "rules.triggers.ip_blocked"
|
||||||
|
I18nTriggerCertificateRenewEvent = "rules.triggers.certificate_renewal"
|
||||||
|
I18nTriggerOnDemandEvent = "rules.triggers.on_demand"
|
||||||
|
I18nTriggerIDPLoginEvent = "rules.triggers.idp_login"
|
||||||
|
I18nTriggerScheduleEvent = "rules.triggers.schedule"
|
||||||
|
I18nErrorInvalidMinSize = "rules.invalid_fs_min_size"
|
||||||
|
I18nErrorInvalidMaxSize = "rules.invalid_fs_max_size"
|
||||||
|
I18nErrorRuleActionRequired = "rules.action_required"
|
||||||
|
I18nErrorRuleFsEventRequired = "rules.fs_event_required"
|
||||||
|
I18nErrorRuleProviderEventRequired = "rules.provider_event_required"
|
||||||
|
I18nErrorRuleScheduleRequired = "rules.schedule_required"
|
||||||
|
I18nErrorRuleScheduleInvalid = "rules.schedule_invalid"
|
||||||
|
I18nErrorRuleDuplicateActions = "rules.duplicate_actions"
|
||||||
|
I18nErrorEvSyncFailureActions = "rules.sync_failure_actions"
|
||||||
|
I18nErrorEvSyncUnsupported = "rules.sync_unsupported"
|
||||||
|
I18nErrorEvSyncUnsupportedFs = "rules.sync_unsupported_fs_event"
|
||||||
|
I18nErrorRuleFailureActionsOnly = "rules.only_failure_actions"
|
||||||
|
I18nErrorRuleSyncActionRequired = "rules.sync_action_required"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewI18nError returns a I18nError wrappring the provided error
|
// NewI18nError returns a I18nError wrappring the provided error
|
||||||
|
|
|
@ -63,7 +63,9 @@
|
||||||
"add_ip_list": "Add IP list entry",
|
"add_ip_list": "Add IP list entry",
|
||||||
"update_ip_list": "Update IP list entry",
|
"update_ip_list": "Update IP list entry",
|
||||||
"add_action": "Add action",
|
"add_action": "Add action",
|
||||||
"update_action": "Update action"
|
"update_action": "Update action",
|
||||||
|
"add_rule": "Add rule",
|
||||||
|
"update_rule": "Update rule"
|
||||||
},
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
"desc": "To start using SFTPGo you need to create an administrator user",
|
"desc": "To start using SFTPGo you need to create an administrator user",
|
||||||
|
@ -252,7 +254,12 @@
|
||||||
"timeout": "Timeout",
|
"timeout": "Timeout",
|
||||||
"env_vars": "Environment variables",
|
"env_vars": "Environment variables",
|
||||||
"hours": "Hours",
|
"hours": "Hours",
|
||||||
"paths": "Paths"
|
"paths": "Paths",
|
||||||
|
"hour": "Hour",
|
||||||
|
"day_of_week": "Day of week",
|
||||||
|
"day_of_month": "Day of month",
|
||||||
|
"month": "Month",
|
||||||
|
"options": "Options"
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
"view_file": "View file \"{{- path}}\"",
|
"view_file": "View file \"{{- path}}\"",
|
||||||
|
@ -1003,5 +1010,63 @@
|
||||||
"metadata_string": "Cloud storage metadata for the downloaded file as JSON escaped string",
|
"metadata_string": "Cloud storage metadata for the downloaded file as JSON escaped string",
|
||||||
"uid": "Unique ID"
|
"uid": "Unique ID"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"view_manage": "View and manage rules for events",
|
||||||
|
"trigger": "Trigger",
|
||||||
|
"run_confirm": "Do you want to execute the selected rule?",
|
||||||
|
"run_confirm_btn": "Yes, run",
|
||||||
|
"run_error_generic": "Unable to run the selected rule",
|
||||||
|
"run_ok": "Rule actions started",
|
||||||
|
"run": "Run",
|
||||||
|
"invalid_fs_min_size": "Invalid min size",
|
||||||
|
"invalid_fs_max_size": "Invalid max size",
|
||||||
|
"action_required": "At least one action is required",
|
||||||
|
"fs_event_required": "At least one filesystem event is required",
|
||||||
|
"provider_event_required": "At least one provider event is required",
|
||||||
|
"schedule_required": "At least one schedule is required",
|
||||||
|
"schedule_invalid": "Invalid schedule",
|
||||||
|
"duplicate_actions": "Duplicate actions detected",
|
||||||
|
"sync_failure_actions": "Synchronous execution is not supported for failure actions",
|
||||||
|
"sync_unsupported": "Synchronous execution is only supported for some filesystem events and Identity Provider logins",
|
||||||
|
"sync_unsupported_fs_event": "Synchronous execution is only supported for upload and pre-* filesystem events",
|
||||||
|
"only_failure_actions": "At least a non-failure action is required",
|
||||||
|
"sync_action_required": "Event \"{{val}}\" requires at least a synchronous action",
|
||||||
|
"scheduler_help": "The scheduler uses UTC time. Hours: 0-23. Day of week: 0-6 (Sun-Sat). Day of month: 1-31. Month: 1-12. Asterisk (*) indicates a match for all the values of the field. e.g. every day of week, every day of month and so on",
|
||||||
|
"concurrent_run": "Allow concurrent execution from multiple instances",
|
||||||
|
"protocol_filters": "Protocol filters",
|
||||||
|
"object_filters": "Object filters",
|
||||||
|
"name_filters": "Name filters",
|
||||||
|
"name_filters_help": "Shell-like pattern filters for usernames, folder names. For example \"user*\"\" will match names starting with \"user\". For provider events, this filter is applied to the username of the admin executing the event",
|
||||||
|
"inverse_match": "Inverse match",
|
||||||
|
"group_name_filters": "Group name filters",
|
||||||
|
"group_name_filters_help": "Shell-like pattern filters for group names. For example \"group*\"\" will match group names starting with \"group\"",
|
||||||
|
"role_name_filters": "Role name filters",
|
||||||
|
"role_name_filters_help": "Shell-like pattern filters for role names. For example \"role*\"\" will match role names starting with \"role\"",
|
||||||
|
"path_filters": "Path filters",
|
||||||
|
"path_filters_help": "Shell-like pattern filters on filesystem event paths. For example \"/adir/*.txt\"\" will match paths in the \"/adir\" directory ending with \".txt\". Double asterisk is supported, for example \"/**/*.txt\" will match any file ending with \".txt\". \"/mydir/**\" will match any entry in \"/mydir\"",
|
||||||
|
"file_size_limits": "File size limits",
|
||||||
|
"file_size_limits_help": "0 means no limit. You can use MB/GB suffix",
|
||||||
|
"min_size": "Minimum size",
|
||||||
|
"max_size": "Maximum size",
|
||||||
|
"actions_help": "One or more actions to execute. The \"Execute sync\" option is supported for \"upload\" events and required for \"pre-*\" events and Identity provider login events if the action checks the account",
|
||||||
|
"option_failure_action": "Failure action",
|
||||||
|
"option_stop_on_failure": "Stop on failure",
|
||||||
|
"option_execute_sync": "Synchronous execution",
|
||||||
|
"no_filter": "No filter means always triggering events",
|
||||||
|
"action_placeholder": "Select an action",
|
||||||
|
"triggers": {
|
||||||
|
"fs_event": "Filesystem events",
|
||||||
|
"provider_event": "Provider events",
|
||||||
|
"ip_blocked": "IP blocked",
|
||||||
|
"certificate_renewal": "Certificate renewal",
|
||||||
|
"on_demand": "On demand",
|
||||||
|
"idp_login": "Identity Provider logins",
|
||||||
|
"schedule": "Schedules"
|
||||||
|
},
|
||||||
|
"idp_logins": {
|
||||||
|
"user": "User login",
|
||||||
|
"admin": "Admin login"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -63,7 +63,9 @@
|
||||||
"add_ip_list": "Aggiungi elemento a lista IP",
|
"add_ip_list": "Aggiungi elemento a lista IP",
|
||||||
"update_ip_list": "Aggiorna elemento lista IP",
|
"update_ip_list": "Aggiorna elemento lista IP",
|
||||||
"add_action": "Aggiungi azione",
|
"add_action": "Aggiungi azione",
|
||||||
"update_action": "Aggiorna azione"
|
"update_action": "Aggiorna azione",
|
||||||
|
"add_rule": "Aggiungi regola",
|
||||||
|
"update_rule": "Aggiorna regola"
|
||||||
},
|
},
|
||||||
"setup": {
|
"setup": {
|
||||||
"desc": "Per iniziare a utilizzare SFTPGo devi creare un utente amministratore",
|
"desc": "Per iniziare a utilizzare SFTPGo devi creare un utente amministratore",
|
||||||
|
@ -252,7 +254,12 @@
|
||||||
"timeout": "Timeout",
|
"timeout": "Timeout",
|
||||||
"env_vars": "Variabili d'ambiente",
|
"env_vars": "Variabili d'ambiente",
|
||||||
"hours": "Ore",
|
"hours": "Ore",
|
||||||
"paths": "Percorsi"
|
"paths": "Percorsi",
|
||||||
|
"hour": "Ora",
|
||||||
|
"day_of_week": "Giorno settimana",
|
||||||
|
"day_of_month": "Giorno mese",
|
||||||
|
"month": "Mese",
|
||||||
|
"options": "Opzioni"
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
"view_file": "Visualizza file \"{{- path}}\"",
|
"view_file": "Visualizza file \"{{- path}}\"",
|
||||||
|
@ -1003,5 +1010,63 @@
|
||||||
"metadata_string": "Metadati del Cloud Storage Provider serializzati come stringa JSON escaped per i file scaricati",
|
"metadata_string": "Metadati del Cloud Storage Provider serializzati come stringa JSON escaped per i file scaricati",
|
||||||
"uid": "ID univoco"
|
"uid": "ID univoco"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"view_manage": "Visualizza e gestisci le regole per gli eventi",
|
||||||
|
"trigger": "Attivazione",
|
||||||
|
"run_confirm": "Vuoi eseguire la regola selezionata?",
|
||||||
|
"run_confirm_btn": "Si, esegui",
|
||||||
|
"run_error_generic": "Impossibile eseguire la regola selezionata",
|
||||||
|
"run_ok": "Azioni delle regola avviate",
|
||||||
|
"run": "Esegui",
|
||||||
|
"invalid_fs_min_size": "Dimensione minima non valida",
|
||||||
|
"invalid_fs_max_size": "Dimensione massima non valida",
|
||||||
|
"action_required": "Almeno un'azione è obbligatoria",
|
||||||
|
"fs_event_required": "Almeno un evento file system è obbligatorio",
|
||||||
|
"provider_event_required": "Almeno un evento provider è obbligatorio",
|
||||||
|
"schedule_required": "Almeno una schedulazione è obbligatoria",
|
||||||
|
"schedule_invalid": "Schedulazione non valida",
|
||||||
|
"duplicate_actions": "Rilevata azioni duplicate",
|
||||||
|
"sync_failure_actions": "L'esecuzione sincrona non è supportata per le azioni su errore",
|
||||||
|
"sync_unsupported": "L'esecuzione sincrona è supportata solo per alcuni eventi del file system e per gli accessi tramite Identity Provider",
|
||||||
|
"sync_unsupported_fs_event": "L'esecuzione sincrona è supporta solo per gli eventi \"upload\" e \"pre-*\"",
|
||||||
|
"only_failure_actions": "E' richiesta almeno un'azione che non venga eseguita su errore",
|
||||||
|
"sync_action_required": "L'evento \"{{val}}\" richiede almeno un'azione da eseguire sincronamente",
|
||||||
|
"scheduler_help": "Lo scheduler utilizza l'ora UTC. Orari: 0-23. Giorno della settimana: 0-6 (dom-sab). Giorno del mese: 1-31. Mese: 1-12. L'asterisco (*) indica una corrispondenza per tutti i valori del campo. per esempio. ogni giorno della settimana, ogni giorno del mese e così via",
|
||||||
|
"concurrent_run": "Consentire l'esecuzione simultanea da più istanze",
|
||||||
|
"protocol_filters": "Filtro su protocolli",
|
||||||
|
"object_filters": "Filtro su oggetti",
|
||||||
|
"name_filters": "Filtro su nomi",
|
||||||
|
"name_filters_help": "Filtri per nomi utente e nomi di cartelle. Ad esempio, \"user*\"\" corrisponderà per i nomi che iniziano con \"user\". Per gli eventi del provider, questo filtro viene applicato al nome utente dell'amministratore che esegue l'evento",
|
||||||
|
"inverse_match": "Corrispondenza inversa",
|
||||||
|
"group_name_filters": "Filtro su nome gruppi",
|
||||||
|
"group_name_filters_help": "Filtri per nomi dei gruppi. Ad esempio \"group*\"\" corrisponderà ai nomi dei gruppi che iniziano con \"group\"",
|
||||||
|
"role_name_filters": "Filtri su nome ruoli",
|
||||||
|
"role_name_filters_help": "Filtri per nomi dei ruoli. Ad esempio \"role*\"\" corrisponderà ai nomi dei gruppi che iniziano con \"role\"",
|
||||||
|
"path_filters": "Filtri sui percorsi",
|
||||||
|
"path_filters_help": "Filtri sui percorsi degli eventi del file system. Ad esempio \"/adir/*.txt\"\" corrisponderà ai percorsi nella directory \"/adir\" che terminano con \".txt\". È supportato il doppio asterisco, ad esempio \"/**/*. txt\" corrisponderà a qualsiasi file che termina con \".txt\". \"/mydir/**\" corrisponderà a qualsiasi voce in \"/mydir\"",
|
||||||
|
"file_size_limits": "Filtri sulla dimensione file",
|
||||||
|
"file_size_limits_help": "0 significa nessun limite. È possibile utilizzare il suffisso MB/GB",
|
||||||
|
"min_size": "Dimensione min",
|
||||||
|
"max_size": "Dimensione max",
|
||||||
|
"actions_help": "Una o più azioni da eseguire. L'opzione \"Esecuzione sincrona\" è supportata per gli eventi di \"upload\" ed è richiesta per gli eventi \"pre-*\" e gli eventi di accesso tramite Identity provider se l'azione controlla l'account",
|
||||||
|
"option_failure_action": "Azione su errore",
|
||||||
|
"option_stop_on_failure": "Termina su errore",
|
||||||
|
"option_execute_sync": "Esecuzione sincrona",
|
||||||
|
"no_filter": "Nessun filtro significa attivare sempre gli eventi",
|
||||||
|
"action_placeholder": "Seleziona un'azione",
|
||||||
|
"triggers": {
|
||||||
|
"fs_event": "Eventi file system",
|
||||||
|
"provider_event": "Eventi provider",
|
||||||
|
"ip_blocked": "IP bloccato",
|
||||||
|
"certificate_renewal": "Rinnovo certificato",
|
||||||
|
"on_demand": "Su richiesta",
|
||||||
|
"idp_login": "Accessi tramite Identity Provider",
|
||||||
|
"schedule": "Schedulazioni"
|
||||||
|
},
|
||||||
|
"idp_logins": {
|
||||||
|
"user": "Accesso utente",
|
||||||
|
"admin": "Accesso amministratore"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1020,13 +1020,13 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||||
|
|
||||||
$('#idType').on("change", function(){
|
$('#idType').on("change", function(){
|
||||||
onTypeChanged(this.value);
|
onTypeChanged(this.value);
|
||||||
})
|
});
|
||||||
|
|
||||||
$('#idFsActionType').on("change", function(){
|
$('#idFsActionType').on("change", function(){
|
||||||
onFsActionChanged(this.value);
|
onFsActionChanged(this.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#role_form').submit(function (event) {
|
$('#eventaction_form').submit(function (event) {
|
||||||
let submitButton = document.querySelector('#form_submit');
|
let submitButton = document.querySelector('#form_submit');
|
||||||
submitButton.setAttribute('data-kt-indicator', 'on');
|
submitButton.setAttribute('data-kt-indicator', 'on');
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,326 +1,406 @@
|
||||||
<!--
|
<!--
|
||||||
Copyright (C) 2019 Nicola Murino
|
Copyright (C) 2024 Nicola Murino
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||||
it under the terms of the GNU Affero General Public License as published
|
|
||||||
by the Free Software Foundation, version 3.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
https://keenthemes.com/products/templates-mega-bundle
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
SFTPGo product and restricted to be used in a resealable HTML template
|
||||||
|
that can compete with KeenThemes products anyhow.
|
||||||
|
|
||||||
|
This WebUI is allowed for use only within the SFTPGo product and
|
||||||
|
therefore cannot be used in derivative works/products without an
|
||||||
|
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||||
-->
|
-->
|
||||||
{{template "base" .}}
|
{{template "base" .}}
|
||||||
|
|
||||||
{{define "title"}}{{.Title}}{{end}}
|
{{- define "extra_css"}}
|
||||||
|
<link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
{{define "extra_css"}}
|
{{- define "page_body"}}
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
|
{{- template "errmsg" ""}}
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
|
<div class="card shadow-sm">
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
|
<div class="card-header bg-light">
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
|
<h3 data-i18n="rules.view_manage" class="card-title section-title">View and manage event rules</h3>
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "page_body"}}
|
|
||||||
<div id="errorMsg" class="alert alert-warning fade show" style="display: none;" role="alert">
|
|
||||||
<span id="errorTxt"></span>
|
|
||||||
<button type="button" class="close" aria-label="Close" onclick="dismissErrorMsg();">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
function dismissErrorMsg(){
|
|
||||||
$('#errorMsg').hide();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div id="successMsg" class="card mb-4 border-left-success" style="display: none;">
|
|
||||||
<div id="successTxt" class="card-body"></div>
|
|
||||||
</div>
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">View and manage event rules</h6>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div id="card_body" class="card-body">
|
||||||
<div class="table-responsive">
|
<div id="loader" class="align-items-center text-center my-10">
|
||||||
<table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
|
<span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
|
||||||
|
<span data-i18n="general.loading" class="text-gray-700">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div id="card_content" class="d-none">
|
||||||
|
<div class="d-flex flex-stack flex-wrap mb-5">
|
||||||
|
<div class="d-flex align-items-center position-relative my-2">
|
||||||
|
<i class="ki-solid ki-magnifier fs-1 position-absolute ms-6"></i>
|
||||||
|
<input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
|
||||||
|
class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end my-2" data-table-toolbar="base">
|
||||||
|
{{- if .LoggedUser.HasPermission "manage_event_rules"}}
|
||||||
|
<a href="{{.EventRuleURL}}" class="btn btn-primary ms-5">
|
||||||
|
<i class="ki-duotone ki-plus fs-2"></i>
|
||||||
|
<span data-i18n="general.add">Add</span>
|
||||||
|
</a>
|
||||||
|
{{- end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="text-start text-muted fw-bold fs-6 gs-0">
|
||||||
<th></th>
|
<th data-i18n="general.name">Name</th>
|
||||||
<th>Name</th>
|
<th data-i18n="general.status">Status</th>
|
||||||
<th>Status</th>
|
<th data-i18n="rules.trigger">Trigger</th>
|
||||||
<th>Description</th>
|
<th data-i18n="title.event_actions">Actions</th>
|
||||||
<th>Trigger</th>
|
<th class="min-w-100px"></th>
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
|
||||||
{{range .Rules}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.Trigger}}</td>
|
|
||||||
<td>{{.Name}}</td>
|
|
||||||
<td>{{if eq .Status 1 }}Active{{else}}Inactive{{end}}</td>
|
|
||||||
<td>{{.Description}}</td>
|
|
||||||
<td>{{.GetTriggerAsString}}</td>
|
|
||||||
<td>{{.GetActionsAsString}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{- end}}
|
||||||
|
|
||||||
{{define "dialog"}}
|
{{- define "extra_js"}}
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
|
<script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
|
||||||
aria-hidden="true">
|
<script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
|
||||||
<div class="modal-dialog" role="document">
|
function deleteAction(name) {
|
||||||
<div class="modal-content">
|
ModalAlert.fire({
|
||||||
<div class="modal-header">
|
text: $.t('general.delete_confirm_generic'),
|
||||||
<h5 class="modal-title" id="deleteModalLabel">
|
icon: "warning",
|
||||||
Confirmation required
|
confirmButtonText: $.t('general.delete_confirm_btn'),
|
||||||
</h5>
|
cancelButtonText: $.t('general.cancel'),
|
||||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
customClass: {
|
||||||
<span aria-hidden="true">×</span>
|
confirmButton: "btn btn-danger",
|
||||||
</button>
|
cancelButton: 'btn btn-secondary'
|
||||||
</div>
|
}
|
||||||
<div class="modal-body">Do you want to delete the selected rule?</div>
|
}).then((result) => {
|
||||||
<div class="modal-footer">
|
if (result.isConfirmed){
|
||||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">
|
$('#loading_message').text("");
|
||||||
Cancel
|
KTApp.showPageLoading();
|
||||||
</button>
|
let path = '{{.EventRuleURL}}' + "/" + encodeURIComponent(name);
|
||||||
<a class="btn btn-warning" href="#" onclick="deleteAction()">
|
|
||||||
Delete
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal fade" id="runModal" tabindex="-1" role="dialog" aria-labelledby="runModalLabel"
|
axios.delete(path, {
|
||||||
aria-hidden="true">
|
timeout: 15000,
|
||||||
<div class="modal-dialog" role="document">
|
headers: {
|
||||||
<div class="modal-content">
|
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||||
<div class="modal-header">
|
},
|
||||||
<h5 class="modal-title" id="runModalLabel">
|
validateStatus: function (status) {
|
||||||
Confirmation required
|
return status == 200;
|
||||||
</h5>
|
}
|
||||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
}).then(function(response){
|
||||||
<span aria-hidden="true">×</span>
|
location.reload();
|
||||||
</button>
|
}).catch(function(error){
|
||||||
</div>
|
KTApp.hidePageLoading();
|
||||||
<div class="modal-body">Do you want to execute the selected rule?</div>
|
let errorMessage;
|
||||||
<div class="modal-footer">
|
if (error && error.response) {
|
||||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">
|
switch (error.response.status) {
|
||||||
Cancel
|
case 403:
|
||||||
</button>
|
errorMessage = "general.delete_error_403";
|
||||||
<a class="btn btn-warning" href="#" onclick="runAction()">
|
break;
|
||||||
Run
|
case 404:
|
||||||
</a>
|
errorMessage = "general.delete_error_404";
|
||||||
</div>
|
break;
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "extra_js"}}
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/buttons.colVis.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
|
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/ellipsis.js"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
function runAction(){
|
|
||||||
let table = $('#dataTable').DataTable();
|
|
||||||
table.button('run:name').enable(false);
|
|
||||||
let name = table.row({ selected: true }).data()[1];
|
|
||||||
let path = '{{.EventRuleURL}}' + "/run/" + fixedEncodeURIComponent(name);
|
|
||||||
$('#runModal').modal('hide');
|
|
||||||
$('#successMsg').hide();
|
|
||||||
$('#errorMsg').hide();
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: path,
|
|
||||||
type: 'POST',
|
|
||||||
dataType: 'json',
|
|
||||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
|
||||||
timeout: 15000,
|
|
||||||
success: function (result) {
|
|
||||||
$('#successTxt').text("Rule actions started");
|
|
||||||
$('#successMsg').show();
|
|
||||||
setTimeout(function () {
|
|
||||||
$('#successMsg').hide();
|
|
||||||
}, 8000);
|
|
||||||
},
|
|
||||||
error: function ($xhr, textStatus, errorThrown) {
|
|
||||||
var txt = "Unable to run the selected rule";
|
|
||||||
if ($xhr) {
|
|
||||||
var json = $xhr.responseJSON;
|
|
||||||
if (json) {
|
|
||||||
if (json.message){
|
|
||||||
txt += ": " + json.message;
|
|
||||||
} else {
|
|
||||||
txt += ": " + json.error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (!errorMessage){
|
||||||
$('#errorTxt').text(txt);
|
errorMessage = "general.delete_error_generic";
|
||||||
$('#errorMsg').show();
|
}
|
||||||
|
ModalAlert.fire({
|
||||||
|
text: $.t(errorMessage),
|
||||||
|
icon: "warning",
|
||||||
|
confirmButtonText: $.t('general.ok'),
|
||||||
|
customClass: {
|
||||||
|
confirmButton: "btn btn-primary"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteAction() {
|
function runAction(name) {
|
||||||
let table = $('#dataTable').DataTable();
|
ModalAlert.fire({
|
||||||
table.button('delete:name').enable(false);
|
text: $.t('rules.run_confirm'),
|
||||||
let name = table.row({ selected: true }).data()[1];
|
icon: "warning",
|
||||||
let path = '{{.EventRuleURL}}' + "/" + fixedEncodeURIComponent(name);
|
confirmButtonText: $.t('rules.run_confirm_btn'),
|
||||||
$('#deleteModal').modal('hide');
|
cancelButtonText: $.t('general.cancel'),
|
||||||
$('#errorMsg').hide();
|
customClass: {
|
||||||
|
confirmButton: "btn btn-danger",
|
||||||
|
cancelButton: 'btn btn-secondary'
|
||||||
|
}
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed){
|
||||||
|
$('#loading_message').text("");
|
||||||
|
KTApp.showPageLoading();
|
||||||
|
let path = '{{.EventRuleURL}}' + "/run/" + encodeURIComponent(name);
|
||||||
|
|
||||||
$.ajax({
|
axios.post(path, null, {
|
||||||
url: path,
|
timeout: 15000,
|
||||||
type: 'DELETE',
|
headers: {
|
||||||
dataType: 'json',
|
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
},
|
||||||
timeout: 15000,
|
validateStatus: function (status) {
|
||||||
success: function (result) {
|
return status == 202;
|
||||||
window.location.href = '{{.EventRulesURL}}';
|
|
||||||
},
|
|
||||||
error: function ($xhr, textStatus, errorThrown) {
|
|
||||||
var txt = "Unable to delete the selected rule";
|
|
||||||
if ($xhr) {
|
|
||||||
var json = $xhr.responseJSON;
|
|
||||||
if (json) {
|
|
||||||
if (json.message){
|
|
||||||
txt += ": " + json.message;
|
|
||||||
} else {
|
|
||||||
txt += ": " + json.error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}).then(function(response){
|
||||||
$('#errorTxt').text(txt);
|
KTApp.hidePageLoading();
|
||||||
$('#errorMsg').show();
|
ModalAlert.fire({
|
||||||
|
text: $.t("rules.run_ok"),
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonText: $.t('general.ok'),
|
||||||
|
customClass: {
|
||||||
|
confirmButton: "btn btn-primary"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(function(error){
|
||||||
|
KTApp.hidePageLoading();
|
||||||
|
ModalAlert.fire({
|
||||||
|
text: $.t("rules.run_error_generic"),
|
||||||
|
icon: "warning",
|
||||||
|
confirmButtonText: $.t('general.ok'),
|
||||||
|
customClass: {
|
||||||
|
confirmButton: "btn btn-primary"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
var datatable = function(){
|
||||||
$.fn.dataTable.ext.buttons.add = {
|
var dt;
|
||||||
text: '<i class="fas fa-plus"></i>',
|
|
||||||
name: 'add',
|
|
||||||
titleAttr: "Add",
|
|
||||||
action: function (e, dt, node, config) {
|
|
||||||
window.location.href = '{{.EventRuleURL}}';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.dataTable.ext.buttons.edit = {
|
var initDatatable = function () {
|
||||||
text: '<i class="fas fa-pen"></i>',
|
$('#errorMsg').addClass("d-none");
|
||||||
name: 'edit',
|
dt = $('#dataTable').DataTable({
|
||||||
titleAttr: "Edit",
|
ajax: {
|
||||||
action: function (e, dt, node, config) {
|
url: "{{.EventRulesURL}}/json",
|
||||||
let name = table.row({ selected: true }).data()[1];
|
dataSrc: "",
|
||||||
let path = '{{.EventRuleURL}}' + "/" + fixedEncodeURIComponent(name);
|
error: function ($xhr, textStatus, errorThrown) {
|
||||||
window.location.href = path;
|
$(".dataTables_processing").hide();
|
||||||
},
|
let txt = "";
|
||||||
enabled: false
|
if ($xhr) {
|
||||||
};
|
let json = $xhr.responseJSON;
|
||||||
|
if (json) {
|
||||||
|
if (json.message){
|
||||||
|
txt = json.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!txt){
|
||||||
|
txt = "general.error500";
|
||||||
|
}
|
||||||
|
setI18NData($('#errorTxt'), txt);
|
||||||
|
$('#errorMsg').removeClass("d-none");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
data: "name",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
return escapeHTML(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "status",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
switch (data){
|
||||||
|
case 1:
|
||||||
|
return $.t('general.active');
|
||||||
|
default:
|
||||||
|
return $.t('general.inactive');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "trigger",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
switch (data){
|
||||||
|
case 1:
|
||||||
|
return $.t('rules.triggers.fs_event');
|
||||||
|
case 2:
|
||||||
|
return $.t('rules.triggers.provider_event');
|
||||||
|
case 3:
|
||||||
|
return $.t('rules.triggers.schedule');
|
||||||
|
case 4:
|
||||||
|
return $.t('rules.triggers.ip_blocked');
|
||||||
|
case 5:
|
||||||
|
return $.t('rules.triggers.certificate_renewal');
|
||||||
|
case 6:
|
||||||
|
return $.t('rules.triggers.on_demand');
|
||||||
|
case 7:
|
||||||
|
return $.t('rules.triggers.idp_login');
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "actions",
|
||||||
|
defaultContent: [],
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
if (data){
|
||||||
|
let actions = [];
|
||||||
|
for (i = 0; i < data.length; i++){
|
||||||
|
actions.push(data[i].name);
|
||||||
|
}
|
||||||
|
return escapeHTML(actions.join(', '));
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "id",
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
className: 'text-end',
|
||||||
|
render: function (data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
let numActions = 0;
|
||||||
|
let actions = `<button class="btn btn-light btn-active-light-primary btn-flex btn-center btn-sm rotate" data-kt-menu-trigger="click" data-kt-menu-placement="bottom-end">
|
||||||
|
<span data-i18n="general.actions" class="fs-6">Actions</span>
|
||||||
|
<i class="ki-duotone ki-down fs-5 ms-1 rotate-180"></i>
|
||||||
|
</button>
|
||||||
|
<div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-700 menu-state-bg-light-primary fw-semibold fs-6 w-200px py-4" data-kt-menu="true">`;
|
||||||
|
|
||||||
$.fn.dataTable.ext.buttons.delete = {
|
//{{- if .LoggedUser.HasPermission "manage_event_rules"}}
|
||||||
text: '<i class="fas fa-trash"></i>',
|
numActions++;
|
||||||
name: 'delete',
|
actions+=`<div class="menu-item px-3">
|
||||||
titleAttr: "Delete",
|
<a data-i18n="general.edit" href="#" class="menu-link px-3" data-table-action="edit_row">Edit</a>
|
||||||
action: function (e, dt, node, config) {
|
</div>`
|
||||||
$('#deleteModal').modal('show');
|
numActions++;
|
||||||
},
|
if (row.trigger === 6){
|
||||||
enabled: false
|
actions+=`<div class="menu-item px-3">
|
||||||
};
|
<a data-i18n="rules.run" href="#" class="menu-link px-3" data-table-action="run_row">Run</a>
|
||||||
|
</div>`
|
||||||
$.fn.dataTable.ext.buttons.run = {
|
numActions++;
|
||||||
text: '<i class="fas fa-play"></i>',
|
}
|
||||||
name: 'run',
|
actions+=`<div class="menu-item px-3">
|
||||||
titleAttr: "Run",
|
<a data-i18n="general.delete" href="#" class="menu-link text-danger px-3" data-table-action="delete_row">Delete</a>
|
||||||
action: function (e, dt, node, config) {
|
</div>`
|
||||||
$('#runModal').modal('show');
|
//{{- end}}
|
||||||
},
|
if (numActions > 0){
|
||||||
enabled: false
|
actions+=`</div>`;
|
||||||
};
|
return actions;
|
||||||
|
}
|
||||||
var table = $('#dataTable').DataTable({
|
}
|
||||||
"select": {
|
return "";
|
||||||
"style": "single",
|
}
|
||||||
"blurable": true
|
},
|
||||||
},
|
],
|
||||||
"stateSave": true,
|
deferRender: true,
|
||||||
"stateDuration": 0,
|
stateSave: true,
|
||||||
"buttons": [
|
stateDuration: 0,
|
||||||
{
|
stateLoadParams: function (settings, data) {
|
||||||
"text": "Column visibility",
|
if (data.search.search){
|
||||||
"extend": "colvis",
|
const filterSearch = document.querySelector('[data-table-filter="search"]');
|
||||||
"columns": ":not(.noVis)"
|
filterSearch.value = data.search.search;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
info: $.t('datatable.info'),
|
||||||
|
infoEmpty: $.t('datatable.info_empty'),
|
||||||
|
infoFiltered: $.t('datatable.info_filtered'),
|
||||||
|
loadingRecords: "",
|
||||||
|
processing: $.t('datatable.processing'),
|
||||||
|
zeroRecords: "",
|
||||||
|
emptyTable: $.t('datatable.no_records')
|
||||||
|
},
|
||||||
|
order: [[0, 'asc']],
|
||||||
|
initComplete: function(settings, json) {
|
||||||
|
$('#loader').addClass("d-none");
|
||||||
|
$('#card_content').removeClass("d-none");
|
||||||
|
let api = $.fn.dataTable.Api(settings);
|
||||||
|
api.columns.adjust().draw("page");
|
||||||
|
drawAction();
|
||||||
}
|
}
|
||||||
],
|
});
|
||||||
"columnDefs": [
|
|
||||||
{
|
|
||||||
"targets": [0],
|
|
||||||
"visible": false,
|
|
||||||
"searchable": false,
|
|
||||||
"className": "noVis"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [1,2],
|
|
||||||
"className": "noVis"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [3],
|
|
||||||
"visible": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"targets": [5],
|
|
||||||
"render": $.fn.dataTable.render.ellipsis(100, true)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"scrollX": false,
|
|
||||||
"scrollY": false,
|
|
||||||
"responsive": true,
|
|
||||||
"language": {
|
|
||||||
"emptyTable": "No event rules defined"
|
|
||||||
},
|
|
||||||
"order": [[1, 'asc']]
|
|
||||||
});
|
|
||||||
|
|
||||||
new $.fn.dataTable.FixedHeader( table );
|
dt.on('draw', drawAction);
|
||||||
|
dt.on('column-reorder', function(e, settings, details){
|
||||||
|
drawAction();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
table.button().add(0,'run');
|
function drawAction() {
|
||||||
table.button().add(0,'delete');
|
KTMenu.createInstances();
|
||||||
table.button().add(0,'edit');
|
handleRowActions();
|
||||||
table.button().add(0,'add');
|
$('#table_body').localize();
|
||||||
|
}
|
||||||
|
|
||||||
table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
|
var handleDatatableActions = function () {
|
||||||
|
const filterSearch = $(document.querySelector('[data-table-filter="search"]'));
|
||||||
|
filterSearch.off("keyup");
|
||||||
|
filterSearch.on('keyup', function (e) {
|
||||||
|
dt.rows().deselect();
|
||||||
|
dt.search(e.target.value, true, false).draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
table.on('select deselect', function () {
|
function handleRowActions() {
|
||||||
var selectedRows = table.rows({ selected: true }).count();
|
const editButtons = document.querySelectorAll('[data-table-action="edit_row"]');
|
||||||
table.button('delete:name').enable(selectedRows == 1);
|
editButtons.forEach(d => {
|
||||||
table.button('edit:name').enable(selectedRows == 1);
|
let el = $(d);
|
||||||
if (selectedRows == 1){
|
el.off("click");
|
||||||
table.button('run:name').enable(table.row({ selected: true }).data()[0] == 6);
|
el.on("click", function(e){
|
||||||
} else {
|
e.preventDefault();
|
||||||
table.button('run:name').enable(false);
|
let rowData = dt.row(e.target.closest('tr')).data();
|
||||||
|
window.location.replace('{{.EventRuleURL}}' + "/" + encodeURIComponent(rowData['name']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteButtons = document.querySelectorAll('[data-table-action="delete_row"]');
|
||||||
|
deleteButtons.forEach(d => {
|
||||||
|
let el = $(d);
|
||||||
|
el.off("click");
|
||||||
|
el.on("click", function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
const parent = e.target.closest('tr');
|
||||||
|
deleteAction(dt.row(parent).data()['name']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const runButtons = document.querySelectorAll('[data-table-action="run_row"]');
|
||||||
|
runButtons.forEach(d => {
|
||||||
|
let el = $(d);
|
||||||
|
el.off("click");
|
||||||
|
el.on("click", function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
const parent = e.target.closest('tr');
|
||||||
|
runAction(dt.row(parent).data()['name']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function () {
|
||||||
|
initDatatable();
|
||||||
|
handleDatatableActions();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
$(document).on("i18nshow", function(){
|
||||||
|
datatable.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{- end}}
|
Loading…
Reference in a new issue