mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 15:10:23 +00:00
EventManager: filter action execution based on event status
Some checks failed
Docker / Build (distroless, false, ubuntu-latest) (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Docker / Build (alpine, false, ubuntu-latest) (push) Has been cancelled
Docker / Build (alpine, true, ubuntu-latest) (push) Has been cancelled
Docker / Build (debian, false, ubuntu-latest) (push) Has been cancelled
Docker / Build (debian, true, ubuntu-latest) (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Has been cancelled
CI / Test and deploy (1.22, macos-latest, true) (push) Has been cancelled
CI / Test and deploy (1.22, ubuntu-latest, true) (push) Has been cancelled
CI / Test and deploy (1.22, windows-latest, false) (push) Has been cancelled
CI / Test build flags (push) Has been cancelled
CI / Test with PgSQL/MySQL/Cockroach (push) Has been cancelled
CI / Build Linux packages (aarch64, ubuntu18.04, go1.22.7, arm64) (push) Has been cancelled
CI / Build Linux packages (amd64, ubuntu:18.04, go1.22.7, amd64) (push) Has been cancelled
CI / Build Linux packages (armv7, ubuntu18.04, go1.22.7, arm7) (push) Has been cancelled
CI / Build Linux packages (ppc64le, ubuntu18.04, go1.22.7, ppc64le) (push) Has been cancelled
Docker / Build (debian-plugins, true, ubuntu-latest) (push) Has been cancelled
Some checks failed
Docker / Build (distroless, false, ubuntu-latest) (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Docker / Build (alpine, false, ubuntu-latest) (push) Has been cancelled
Docker / Build (alpine, true, ubuntu-latest) (push) Has been cancelled
Docker / Build (debian, false, ubuntu-latest) (push) Has been cancelled
Docker / Build (debian, true, ubuntu-latest) (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Has been cancelled
CI / Test and deploy (1.22, macos-latest, true) (push) Has been cancelled
CI / Test and deploy (1.22, ubuntu-latest, true) (push) Has been cancelled
CI / Test and deploy (1.22, windows-latest, false) (push) Has been cancelled
CI / Test build flags (push) Has been cancelled
CI / Test with PgSQL/MySQL/Cockroach (push) Has been cancelled
CI / Build Linux packages (aarch64, ubuntu18.04, go1.22.7, arm64) (push) Has been cancelled
CI / Build Linux packages (amd64, ubuntu:18.04, go1.22.7, amd64) (push) Has been cancelled
CI / Build Linux packages (armv7, ubuntu18.04, go1.22.7, arm7) (push) Has been cancelled
CI / Build Linux packages (ppc64le, ubuntu18.04, go1.22.7, ppc64le) (push) Has been cancelled
Docker / Build (debian-plugins, true, ubuntu-latest) (push) Has been cancelled
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
433d45ed87
commit
eeef23139d
11 changed files with 183 additions and 8 deletions
4
go.mod
4
go.mod
|
@ -37,7 +37,7 @@ require (
|
|||
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/klauspost/compress v1.17.10
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.1
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
github.com/mattn/go-sqlite3 v1.14.23
|
||||
|
@ -81,7 +81,7 @@ require (
|
|||
cloud.google.com/go v0.115.1 // indirect
|
||||
cloud.google.com/go/auth v0.9.4 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
cloud.google.com/go/iam v1.2.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
|
|
8
go.sum
8
go.sum
|
@ -5,8 +5,8 @@ cloud.google.com/go/auth v0.9.4 h1:DxF7imbEbiFu9+zdKC6cKBko1e8XeJnipNqIbWZ+kDI=
|
|||
cloud.google.com/go/auth v0.9.4/go.mod h1:SHia8n6//Ya940F1rLimhJCjjx7KE17t0ctFEci3HkA=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||
cloud.google.com/go/compute/metadata v0.5.1 h1:NM6oZeZNlYjiwYje+sYFjEpP0Q0zCan1bmQW/KmIrGs=
|
||||
cloud.google.com/go/compute/metadata v0.5.1/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU=
|
||||
cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g=
|
||||
cloud.google.com/go/kms v1.19.0 h1:x0OVJDl6UH1BSX4THKlMfdcFWoE4ruh90ZHuilZekrU=
|
||||
|
@ -241,8 +241,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0=
|
||||
github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
|
|
@ -2597,6 +2597,11 @@ func executeUserCheckAction(c *dataprovider.EventActionIDPAccountCheck, params *
|
|||
func executeRuleAction(action dataprovider.BaseEventAction, params *EventParams, //nolint:gocyclo
|
||||
conditions dataprovider.ConditionOptions,
|
||||
) error {
|
||||
if len(conditions.EventStatuses) > 0 && !slices.Contains(conditions.EventStatuses, params.Status) {
|
||||
eventManagerLog(logger.LevelDebug, "skipping action %s, event status %d does not match: %v",
|
||||
action.Name, params.Status, conditions.EventStatuses)
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
|
||||
switch action.Type {
|
||||
|
|
|
@ -3815,6 +3815,7 @@ func TestEventRule(t *testing.T) {
|
|||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
EventStatuses: []int{1},
|
||||
FsPaths: []dataprovider.ConditionPattern{
|
||||
{
|
||||
Pattern: "/subdir/*.dat",
|
||||
|
@ -4105,6 +4106,107 @@ func TestEventRule(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleStatues(t *testing.T) {
|
||||
smtpCfg := smtp.Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 2525,
|
||||
From: "notification@example.com",
|
||||
TemplatesPath: "templates",
|
||||
}
|
||||
err := smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
a1 := dataprovider.BaseEventAction{
|
||||
Name: "a1",
|
||||
Type: dataprovider.ActionTypeEmail,
|
||||
Options: dataprovider.BaseEventActionOptions{
|
||||
EmailConfig: dataprovider.EventActionEmailConfig{
|
||||
Recipients: []string{"test6@example.com"},
|
||||
Subject: `New "{{Event}}" error`,
|
||||
Body: "{{ErrorString}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
|
||||
r := dataprovider.EventRule{
|
||||
Name: "rule",
|
||||
Status: 1,
|
||||
Trigger: dataprovider.EventTriggerFsEvent,
|
||||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
EventStatuses: []int{3},
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: action1.Name,
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
rule, resp, err := httpdtest.AddEventRule(r, http.StatusCreated)
|
||||
assert.NoError(t, err, string(resp))
|
||||
|
||||
u := getTestUser()
|
||||
u.UploadDataTransfer = 1
|
||||
u.DownloadDataTransfer = 1
|
||||
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
|
||||
assert.NoError(t, err)
|
||||
conn, client, err := getSftpClient(user)
|
||||
if assert.NoError(t, err) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
|
||||
testFileSize := int64(999999)
|
||||
err = writeSFTPFile(testFileName, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
f, err := client.Open(testFileName)
|
||||
assert.NoError(t, err)
|
||||
contents := make([]byte, testFileSize)
|
||||
n, err := io.ReadFull(f, contents)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(testFileSize), n)
|
||||
assert.Len(t, contents, int(testFileSize))
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
lastReceivedEmail.reset()
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From == ""
|
||||
}, 600*time.Millisecond, 500*time.Millisecond)
|
||||
|
||||
err = writeSFTPFile(testFileName, testFileSize, client)
|
||||
assert.Error(t, err)
|
||||
lastReceivedEmail.reset()
|
||||
assert.Eventually(t, func() bool {
|
||||
return lastReceivedEmail.get().From != ""
|
||||
}, 3000*time.Millisecond, 100*time.Millisecond)
|
||||
email := lastReceivedEmail.get()
|
||||
assert.Len(t, email.To, 1)
|
||||
assert.True(t, slices.Contains(email.To, "test6@example.com"))
|
||||
assert.Contains(t, email.Data, `Subject: New "upload" error`)
|
||||
assert.Contains(t, email.Data, common.ErrQuotaExceeded.Error())
|
||||
}
|
||||
|
||||
_, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
||||
smtpCfg = smtp.Config{}
|
||||
err = smtpCfg.Initialize(configDir, true)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEventRuleProviderEvents(t *testing.T) {
|
||||
if runtime.GOOS == osWindows {
|
||||
t.Skip("this test is not available on Windows")
|
||||
|
|
|
@ -1336,6 +1336,7 @@ type ConditionOptions struct {
|
|||
ProviderObjects []string `json:"provider_objects,omitempty"`
|
||||
MinFileSize int64 `json:"min_size,omitempty"`
|
||||
MaxFileSize int64 `json:"max_size,omitempty"`
|
||||
EventStatuses []int `json:"event_statuses,omitempty"`
|
||||
// allow to execute scheduled tasks concurrently from multiple instances
|
||||
ConcurrentExecution bool `json:"concurrent_execution,omitempty"`
|
||||
}
|
||||
|
@ -1345,6 +1346,8 @@ func (f *ConditionOptions) getACopy() ConditionOptions {
|
|||
copy(protocols, f.Protocols)
|
||||
providerObjects := make([]string, len(f.ProviderObjects))
|
||||
copy(providerObjects, f.ProviderObjects)
|
||||
statuses := make([]int, len(f.EventStatuses))
|
||||
copy(statuses, f.EventStatuses)
|
||||
|
||||
return ConditionOptions{
|
||||
Names: cloneConditionPatterns(f.Names),
|
||||
|
@ -1355,10 +1358,20 @@ func (f *ConditionOptions) getACopy() ConditionOptions {
|
|||
ProviderObjects: providerObjects,
|
||||
MinFileSize: f.MinFileSize,
|
||||
MaxFileSize: f.MaxFileSize,
|
||||
EventStatuses: statuses,
|
||||
ConcurrentExecution: f.ConcurrentExecution,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ConditionOptions) validateStatuses() error {
|
||||
for _, status := range f.EventStatuses {
|
||||
if status < 0 || status > 3 {
|
||||
return util.NewValidationError(fmt.Sprintf("invalid event_status %d", status))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *ConditionOptions) validate() error {
|
||||
if err := validateConditionPatterns(f.Names); err != nil {
|
||||
return err
|
||||
|
@ -1389,6 +1402,9 @@ func (f *ConditionOptions) validate() error {
|
|||
util.ByteCountSI(f.MaxFileSize), util.ByteCountSI(f.MinFileSize)))
|
||||
}
|
||||
}
|
||||
if err := f.validateStatuses(); err != nil {
|
||||
return err
|
||||
}
|
||||
if config.IsShared == 0 {
|
||||
f.ConcurrentExecution = false
|
||||
}
|
||||
|
@ -1491,6 +1507,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.GroupNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.IDPLoginEvent = 0
|
||||
|
@ -1510,6 +1527,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.ProviderEvents = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Options.ProviderObjects = nil
|
||||
|
@ -1525,6 +1543,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.RoleNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Schedules = nil
|
||||
|
@ -1534,6 +1553,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.ProviderEvents = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Options.ProviderObjects = nil
|
||||
|
@ -1547,6 +1567,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.RoleNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Schedules = nil
|
||||
|
@ -1560,6 +1581,7 @@ func (c *EventConditions) validate(trigger int) error {
|
|||
c.Options.RoleNames = nil
|
||||
c.Options.FsPaths = nil
|
||||
c.Options.Protocols = nil
|
||||
c.Options.EventStatuses = nil
|
||||
c.Options.MinFileSize = 0
|
||||
c.Options.MaxFileSize = 0
|
||||
c.Schedules = nil
|
||||
|
|
|
@ -1914,7 +1914,8 @@ func TestBasicActionRulesHandling(t *testing.T) {
|
|||
Conditions: dataprovider.EventConditions{
|
||||
FsEvents: []string{"upload"},
|
||||
Options: dataprovider.ConditionOptions{
|
||||
MinFileSize: 1024 * 1024,
|
||||
EventStatuses: []int{2, 3},
|
||||
MinFileSize: 1024 * 1024,
|
||||
},
|
||||
},
|
||||
Actions: []dataprovider.EventAction{
|
||||
|
@ -2708,6 +2709,21 @@ func TestEventRuleValidation(t *testing.T) {
|
|||
_, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "sync execution is only supported for upload and pre-* events")
|
||||
|
||||
rule.Conditions.FsEvents = []string{"download"}
|
||||
rule.Conditions.Options.EventStatuses = []int{3, 2, 8}
|
||||
rule.Actions = []dataprovider.EventAction{
|
||||
{
|
||||
BaseEventAction: dataprovider.BaseEventAction{
|
||||
Name: "action",
|
||||
},
|
||||
Order: 1,
|
||||
},
|
||||
}
|
||||
_, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(resp), "invalid event_status")
|
||||
|
||||
rule.Trigger = dataprovider.EventTriggerProviderEvent
|
||||
rule.Actions = []dataprovider.EventAction{
|
||||
{
|
||||
|
|
|
@ -2555,6 +2555,13 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo
|
|||
if err != nil {
|
||||
return dataprovider.EventConditions{}, util.NewI18nError(fmt.Errorf("invalid max file size: %w", err), util.I18nErrorInvalidMaxSize)
|
||||
}
|
||||
var eventStatuses []int
|
||||
for _, s := range r.Form["fs_statuses"] {
|
||||
status, err := strconv.ParseInt(s, 10, 32)
|
||||
if err == nil {
|
||||
eventStatuses = append(eventStatuses, int(status))
|
||||
}
|
||||
}
|
||||
conditions := dataprovider.EventConditions{
|
||||
FsEvents: r.Form["fs_events"],
|
||||
ProviderEvents: r.Form["provider_events"],
|
||||
|
@ -2566,6 +2573,7 @@ func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventCo
|
|||
RoleNames: roleNames,
|
||||
FsPaths: fsPaths,
|
||||
Protocols: r.Form["fs_protocols"],
|
||||
EventStatuses: eventStatuses,
|
||||
ProviderObjects: r.Form["provider_objects"],
|
||||
MinFileSize: minFileSize,
|
||||
MaxFileSize: maxFileSize,
|
||||
|
|
|
@ -1662,7 +1662,7 @@ func compareConditionPatternOptions(expected, actual []dataprovider.ConditionPat
|
|||
return nil
|
||||
}
|
||||
|
||||
func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) error {
|
||||
func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions) error { //nolint:gocyclo
|
||||
if err := compareConditionPatternOptions(expected.Names, actual.Names); err != nil {
|
||||
return errors.New("condition names mismatch")
|
||||
}
|
||||
|
@ -1683,6 +1683,14 @@ func checkEventConditionOptions(expected, actual dataprovider.ConditionOptions)
|
|||
return errors.New("condition protocols content mismatch")
|
||||
}
|
||||
}
|
||||
if len(expected.EventStatuses) != len(actual.EventStatuses) {
|
||||
return errors.New("condition statuses mismatch")
|
||||
}
|
||||
for _, v := range expected.EventStatuses {
|
||||
if !slices.Contains(actual.EventStatuses, v) {
|
||||
return errors.New("condition statuses content mismatch")
|
||||
}
|
||||
}
|
||||
if len(expected.ProviderObjects) != len(actual.ProviderObjects) {
|
||||
return errors.New("condition provider objects mismatch")
|
||||
}
|
||||
|
|
|
@ -1090,6 +1090,7 @@
|
|||
"scheduler_help": "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",
|
||||
"status_filters": "Status 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",
|
||||
|
|
|
@ -1090,6 +1090,7 @@
|
|||
"scheduler_help": "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",
|
||||
"status_filters": "Filtro su stati",
|
||||
"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",
|
||||
|
|
|
@ -201,6 +201,18 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row trigger trigger-fs mt-10">
|
||||
<label for="idFsStatuses" data-i18n="rules.status_filters" class="col-md-3 col-form-label">Status filters</label>
|
||||
<div class="col-md-9">
|
||||
<select id="idFsStatuses" name="fs_statuses" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple aria-describedby="idFsStatusesHelp">
|
||||
<option value="1" data-i18n="general.ok" {{- range $.Rule.Conditions.Options.EventStatuses }}{{- if eq . 1}}selected{{- end}}{{- end}}>OK</option>
|
||||
<option value="2" data-i18n="general.failed" {{- range $.Rule.Conditions.Options.EventStatuses }}{{- if eq . 2}}selected{{- end}}{{- end}}>Failed</option>
|
||||
<option value="3" data-i18n="events.quota_exceeded" {{- range $.Rule.Conditions.Options.EventStatuses }}{{- if eq . 3}}selected{{- end}}{{- end}}>Quota exceeded</option>
|
||||
</select>
|
||||
<div id="idFsStatusesHelp" data-i18n="rules.no_filter" class="form-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row trigger trigger-provider mt-10">
|
||||
<label for="idProviderObjects" data-i18n="rules.object_filters" class="col-md-3 col-form-label">Object filters</label>
|
||||
<div class="col-md-9">
|
||||
|
|
Loading…
Reference in a new issue