浏览代码

allow to disable event rules

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
Nicola Murino 2 年之前
父节点
当前提交
53f17b5715

+ 4 - 6
go.mod

@@ -56,7 +56,7 @@ require (
 	github.com/shirou/gopsutil/v3 v3.22.12
 	github.com/spf13/afero v1.9.3
 	github.com/spf13/cobra v1.6.1
-	github.com/spf13/viper v1.14.0
+	github.com/spf13/viper v1.15.0
 	github.com/stretchr/testify v1.8.1
 	github.com/studio-b12/gowebdav v0.0.0-20221109171924-60ec5ad56012
 	github.com/subosito/gotenv v1.4.2
@@ -73,12 +73,12 @@ require (
 	golang.org/x/sys v0.4.0
 	golang.org/x/term v0.4.0
 	golang.org/x/time v0.3.0
-	google.golang.org/api v0.107.0
+	google.golang.org/api v0.108.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 )
 
 require (
-	cloud.google.com/go v0.108.0 // indirect
+	cloud.google.com/go v0.109.0 // indirect
 	cloud.google.com/go/compute v1.15.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/iam v0.10.0 // indirect
@@ -138,7 +138,6 @@ require (
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/oklog/run v1.1.0 // indirect
-	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
@@ -158,11 +157,10 @@ require (
 	golang.org/x/tools v0.5.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5 // indirect
+	google.golang.org/genproto v0.0.0-20230117162540-28d6b9783ac4 // indirect
 	google.golang.org/grpc v1.52.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 

+ 8 - 10
go.sum

@@ -37,8 +37,8 @@ cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34h
 cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA=
 cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
 cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
-cloud.google.com/go v0.108.0 h1:xntQwnfn8oHGX0crLVinvHM+AhXvi3QHQIEcX/2hiWk=
-cloud.google.com/go v0.108.0/go.mod h1:lNUfQqusBJp0bgAg6qrHgYFYbTB+dOiob1itwnlD33Q=
+cloud.google.com/go v0.109.0 h1:38CZoKGlCnPZjGdyj0ZfpoGae0/wgNfy5F0byyxg0Gk=
+cloud.google.com/go v0.109.0/go.mod h1:2sYycXt75t/CSB5R9M2wPU1tJmire7AQZTPtITcGBVE=
 cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
 cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
 cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o=
@@ -1663,8 +1663,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
-github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
 github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
 github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
@@ -1856,8 +1854,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
-github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
-github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
+github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
+github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
 github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
 github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -2577,8 +2575,8 @@ google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91
 google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70=
 google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
 google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
-google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU=
-google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
+google.golang.org/api v0.108.0 h1:WVBc/faN0DkKtR43Q/7+tPny9ZoLZdIiAyG5Q9vFClg=
+google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -2712,8 +2710,8 @@ google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZV
 google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
 google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
 google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
-google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5 h1:wJT65XLOzhpSPCdAmmKfz94SlmnQzDzjm3Cj9k3fsXY=
-google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto v0.0.0-20230117162540-28d6b9783ac4 h1:yF0uHwqqYt2tIL2F4hxRWA1ZFX43SEunWAK8MnQiclk=
+google.golang.org/genproto v0.0.0-20230117162540-28d6b9783ac4/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
 google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

+ 1 - 1
internal/common/common.go

@@ -949,7 +949,7 @@ func (conns *ActiveConnections) Remove(connectionID string) {
 		return
 	}
 
-	logger.Warn(logSender, "", "connection id %q to remove not found!", connectionID)
+	logger.Debug(logSender, "", "connection id %q to remove not found!", connectionID)
 }
 
 // Close closes an active connection.

+ 3 - 0
internal/common/eventmanager.go

@@ -197,6 +197,9 @@ func (r *eventRulesContainer) addUpdateRuleInternal(rule dataprovider.EventRule)
 		}
 		return
 	}
+	if rule.Status != 1 {
+		return
+	}
 	switch rule.Trigger {
 	case dataprovider.EventTriggerFsEvent:
 		r.FsEvents = append(r.FsEvents, rule)

+ 4 - 0
internal/common/eventmanager_test.go

@@ -320,6 +320,7 @@ func TestEventManager(t *testing.T) {
 	assert.NoError(t, err)
 	rule := &dataprovider.EventRule{
 		Name:    "rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{operationUpload},
@@ -595,6 +596,7 @@ func TestEventManagerErrors(t *testing.T) {
 	// rule with invalid trigger
 	eventManager.addUpdateRuleInternal(dataprovider.EventRule{
 		Name:    "test rule",
+		Status:  1,
 		Trigger: -1,
 	})
 
@@ -606,6 +608,7 @@ func TestEventManagerErrors(t *testing.T) {
 	// rule with invalid cronspec
 	eventManager.addUpdateRuleInternal(dataprovider.EventRule{
 		Name:    "test rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerSchedule,
 		Conditions: dataprovider.EventConditions{
 			Schedules: []dataprovider.Schedule{
@@ -1602,6 +1605,7 @@ func TestScheduledActions(t *testing.T) {
 	assert.NoError(t, err)
 	rule := &dataprovider.EventRule{
 		Name:    "rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerSchedule,
 		Conditions: dataprovider.EventConditions{
 			Schedules: []dataprovider.Schedule{

+ 44 - 0
internal/common/protocol_test.go

@@ -22,6 +22,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/fs"
 	"math"
 	"net"
 	"net/http"
@@ -3461,6 +3462,7 @@ func TestEventRule(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "test rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -3514,6 +3516,7 @@ func TestEventRule(t *testing.T) {
 
 	r2 := dataprovider.EventRule{
 		Name:    "test rule2",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"download"},
@@ -3548,6 +3551,7 @@ func TestEventRule(t *testing.T) {
 
 	r3 := dataprovider.EventRule{
 		Name:    "test rule3",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerProviderEvent,
 		Conditions: dataprovider.EventConditions{
 			ProviderEvents: []string{"delete"},
@@ -3810,6 +3814,7 @@ func TestEventRuleProviderEvents(t *testing.T) {
 
 	r := dataprovider.EventRule{
 		Name:    "rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerProviderEvent,
 		Conditions: dataprovider.EventConditions{
 			ProviderEvents: []string{"update"},
@@ -3991,6 +3996,7 @@ func TestEventRuleFsActions(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "r1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerProviderEvent,
 		Conditions: dataprovider.EventConditions{
 			ProviderEvents: []string{"add"},
@@ -4006,6 +4012,7 @@ func TestEventRuleFsActions(t *testing.T) {
 	}
 	r2 := dataprovider.EventRule{
 		Name:    "r2",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -4030,6 +4037,7 @@ func TestEventRuleFsActions(t *testing.T) {
 	}
 	r3 := dataprovider.EventRule{
 		Name:    "r3",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"mkdir"},
@@ -4051,6 +4059,7 @@ func TestEventRuleFsActions(t *testing.T) {
 	}
 	r4 := dataprovider.EventRule{
 		Name:    "r4",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"rmdir"},
@@ -4066,6 +4075,7 @@ func TestEventRuleFsActions(t *testing.T) {
 	}
 	r5 := dataprovider.EventRule{
 		Name:    "r5",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerProviderEvent,
 		Conditions: dataprovider.EventConditions{
 			ProviderEvents: []string{"add"},
@@ -4216,6 +4226,7 @@ func TestEventRulePreDelete(t *testing.T) {
 	assert.NoError(t, err, string(resp))
 	r1 := dataprovider.EventRule{
 		Name:    "rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"pre-delete"},
@@ -4347,6 +4358,7 @@ func TestEventRulePreDownloadUpload(t *testing.T) {
 	assert.NoError(t, err, string(resp))
 	r1 := dataprovider.EventRule{
 		Name:    "rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"pre-download", "pre-upload"},
@@ -4386,7 +4398,20 @@ func TestEventRulePreDownloadUpload(t *testing.T) {
 		assert.Equal(t, int(100), n)
 		err = f.Close()
 		assert.NoError(t, err)
+		// disable the rule
+		rule1.Status = 0
+		_, _, err = httpdtest.UpdateEventRule(rule1, http.StatusOK)
+		assert.NoError(t, err)
+		err = client.RemoveDirectory(testDir)
+		assert.NoError(t, err)
+		err = client.Remove(testFileName)
+		assert.NoError(t, err)
+		err = writeSFTPFile(testFileName, 100, client)
+		assert.NoError(t, err)
+		_, err = client.Stat(testDir)
+		assert.ErrorIs(t, err, fs.ErrNotExist)
 		// now update the rule so that it will always fail
+		rule1.Status = 1
 		rule1.Actions = []dataprovider.EventAction{
 			{
 				BaseEventAction: dataprovider.BaseEventAction{
@@ -4441,6 +4466,7 @@ func TestFsActionCopy(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -4520,6 +4546,7 @@ func TestEventFsActionsGroupFilters(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -4657,6 +4684,7 @@ func TestBackupAsAttachment(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "test rule certificate",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerCertificate,
 		Actions: []dataprovider.EventAction{
 			{
@@ -4738,6 +4766,7 @@ func TestEventActionHTTPMultipart(t *testing.T) {
 	assert.NoError(t, err, string(resp))
 	r1 := dataprovider.EventRule{
 		Name:    "test http multipart",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -4813,6 +4842,7 @@ func TestEventActionCompress(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test compress",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -4981,6 +5011,7 @@ func TestEventActionCompressQuotaErrors(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test compress",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"rename"},
@@ -5126,6 +5157,7 @@ func TestEventActionCompressQuotaFolder(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test compress",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -5246,6 +5278,7 @@ func TestEventActionCompressErrors(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test compress",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -5383,6 +5416,7 @@ func TestEventActionEmailAttachments(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test email with attachment",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -5536,6 +5570,7 @@ func TestEventActionsRetentionReports(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"upload"},
@@ -5729,6 +5764,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test first upload rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"first-upload"},
@@ -5746,6 +5782,7 @@ func TestEventRuleFirstUploadDownloadActions(t *testing.T) {
 	assert.NoError(t, err)
 	r2 := dataprovider.EventRule{
 		Name:    "test first download rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"first-download"},
@@ -5859,6 +5896,7 @@ func TestEventRuleRenameEvent(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "test rename rule",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"rename"},
@@ -5948,6 +5986,7 @@ func TestEventRuleCertificate(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "test rule certificate",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerCertificate,
 		Actions: []dataprovider.EventAction{
 			{
@@ -5962,6 +6001,7 @@ func TestEventRuleCertificate(t *testing.T) {
 	assert.NoError(t, err)
 	r2 := dataprovider.EventRule{
 		Name:    "test rule 2",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerCertificate,
 		Actions: []dataprovider.EventAction{
 			{
@@ -6082,6 +6122,7 @@ func TestEventRuleIPBlocked(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "test rule ip blocked",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerIPBlocked,
 		Actions: []dataprovider.EventAction{
 			{
@@ -6096,6 +6137,7 @@ func TestEventRuleIPBlocked(t *testing.T) {
 	assert.NoError(t, err)
 	r2 := dataprovider.EventRule{
 		Name:    "test rule 2",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerIPBlocked,
 		Actions: []dataprovider.EventAction{
 			{
@@ -6214,6 +6256,7 @@ func TestEventRulePasswordExpiration(t *testing.T) {
 
 	r1 := dataprovider.EventRule{
 		Name:    "rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
 			FsEvents: []string{"mkdir"},
@@ -7017,6 +7060,7 @@ func TestSFTPLoopError(t *testing.T) {
 	assert.NoError(t, err)
 	r1 := dataprovider.EventRule{
 		Name:    "rule1",
+		Status:  1,
 		Trigger: dataprovider.EventTriggerProviderEvent,
 		Conditions: dataprovider.EventConditions{
 			ProviderEvents: []string{"update"},

+ 37 - 6
internal/dataprovider/bolt.go

@@ -35,7 +35,7 @@ import (
 )
 
 const (
-	boltDatabaseVersion = 25
+	boltDatabaseVersion = 26
 )
 
 var (
@@ -2833,10 +2833,41 @@ func (p *BoltProvider) migrateDatabase() error {
 		providerLog(logger.LevelError, "%v", err)
 		logger.ErrorToConsole("%v", err)
 		return err
-	case version == 23, version == 24:
-		logger.InfoToConsole(fmt.Sprintf("updating database schema version: %d -> 25", version))
-		providerLog(logger.LevelInfo, "updating database schema version: %d -> 25", version)
-		return updateBoltDatabaseVersion(p.dbHandle, 25)
+	case version == 23, version == 24, version == 25:
+		logger.InfoToConsole("updating database schema version: %d -> 26", version)
+		providerLog(logger.LevelInfo, "updating database schema version: %d -> 26", version)
+		err := p.dbHandle.Update(func(tx *bolt.Tx) error {
+			rules, err := p.dumpEventRules()
+			if err != nil {
+				return err
+			}
+			bucket, err := p.getRulesBucket(tx)
+			if err != nil {
+				return err
+			}
+			for _, rule := range rules {
+				rule := rule // pin
+				if rule.Status == 1 {
+					continue
+				}
+				logger.InfoToConsole("setting status to active for rule %q", rule.Name)
+				providerLog(logger.LevelInfo, "setting status to 1 for rule %q", rule.Name)
+				rule.Status = 1
+				rule.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now())
+				buf, err := json.Marshal(rule)
+				if err != nil {
+					return err
+				}
+				if err := bucket.Put([]byte(rule.Name), buf); err != nil {
+					return err
+				}
+			}
+			return nil
+		})
+		if err != nil {
+			return err
+		}
+		return updateBoltDatabaseVersion(p.dbHandle, 26)
 	default:
 		if version > boltDatabaseVersion {
 			providerLog(logger.LevelError, "database schema version %d is newer than the supported one: %d", version,
@@ -2858,7 +2889,7 @@ func (p *BoltProvider) revertDatabase(targetVersion int) error {
 		return errors.New("current version match target version, nothing to do")
 	}
 	switch dbVersion.Version {
-	case 24, 25:
+	case 24, 25, 26:
 		logger.InfoToConsole("downgrading database schema version: %d -> 23", dbVersion.Version)
 		providerLog(logger.LevelInfo, "downgrading database schema version: %d -> 23", dbVersion.Version)
 		err := p.dbHandle.Update(func(tx *bolt.Tx) error {

+ 1 - 1
internal/dataprovider/dataprovider.go

@@ -87,7 +87,7 @@ const (
 	CockroachDataProviderName = "cockroachdb"
 	// DumpVersion defines the version for the dump.
 	// For restore/load we support the current version and the previous one
-	DumpVersion = 14
+	DumpVersion = 15
 
 	argonPwdPrefix            = "$argon2id$"
 	bcryptPwdPrefix           = "$2a$"

+ 10 - 0
internal/dataprovider/eventrule.go

@@ -1313,6 +1313,8 @@ type EventRule struct {
 	ID int64 `json:"id"`
 	// Rule name
 	Name string `json:"name"`
+	// 1 enabled, 0 disabled
+	Status int `json:"status"`
 	// optional description
 	Description string `json:"description,omitempty"`
 	// Creation time as unix timestamp in milliseconds
@@ -1338,6 +1340,7 @@ func (r *EventRule) getACopy() EventRule {
 	return EventRule{
 		ID:          r.ID,
 		Name:        r.Name,
+		Status:      r.Status,
 		Description: r.Description,
 		CreatedAt:   r.CreatedAt,
 		UpdatedAt:   r.UpdatedAt,
@@ -1371,10 +1374,17 @@ func (r *EventRule) GetActionsAsString() string {
 	return strings.Join(actions, ",")
 }
 
+func (r *EventRule) isStatusValid() bool {
+	return r.Status >= 0 && r.Status <= 1
+}
+
 func (r *EventRule) validate() error {
 	if r.Name == "" {
 		return util.NewValidationError("name is mandatory")
 	}
+	if !r.isStatusValid() {
+		return util.NewValidationError(fmt.Sprintf("invalid event rule status: %d", r.Status))
+	}
 	if !isEventTriggerValid(r.Trigger) {
 		return util.NewValidationError(fmt.Sprintf("invalid event rule trigger: %d", r.Trigger))
 	}

+ 3 - 0
internal/dataprovider/memory.go

@@ -2825,6 +2825,9 @@ func (p *MemoryProvider) restoreEventRules(dump BackupData) error {
 	for _, rule := range dump.EventRules {
 		r, err := p.eventRuleExists(rule.Name)
 		rule := rule // pin
+		if dump.Version < 15 {
+			rule.Status = 1
+		}
 		if err == nil {
 			rule.ID = r.ID
 			err = UpdateEventRule(&rule, ActionExecutorSystem, "", "")

+ 36 - 1
internal/dataprovider/mysql.go

@@ -186,6 +186,9 @@ const (
 	mysqlV25SQL = "ALTER TABLE `{{users}}` ADD COLUMN `last_password_change` bigint DEFAULT 0 NOT NULL; " +
 		"ALTER TABLE `{{users}}` ALTER COLUMN `last_password_change` DROP DEFAULT; "
 	mysqlV25DownSQL = "ALTER TABLE `{{users}}` DROP COLUMN `last_password_change`; "
+	mysqlV26SQL     = "ALTER TABLE `{{events_rules}}` ADD COLUMN `status` integer DEFAULT 1 NOT NULL; " +
+		"ALTER TABLE `{{events_rules}}` ALTER COLUMN `status` DROP DEFAULT; "
+	mysqlV26DownSQL = "ALTER TABLE `{{events_rules}}` DROP COLUMN `status`; "
 )
 
 // MySQLProvider defines the auth provider for MySQL/MariaDB database
@@ -744,6 +747,8 @@ func (p *MySQLProvider) migrateDatabase() error { //nolint:dupl
 		return updateMySQLDatabaseFromV23(p.dbHandle)
 	case version == 24:
 		return updateMySQLDatabaseFromV24(p.dbHandle)
+	case version == 25:
+		return updateMySQLDatabaseFromV25(p.dbHandle)
 	default:
 		if version > sqlDatabaseVersion {
 			providerLog(logger.LevelError, "database schema version %d is newer than the supported one: %d", version,
@@ -770,6 +775,8 @@ func (p *MySQLProvider) revertDatabase(targetVersion int) error {
 		return downgradeMySQLDatabaseFromV24(p.dbHandle)
 	case 25:
 		return downgradeMySQLDatabaseFromV25(p.dbHandle)
+	case 26:
+		return downgradeMySQLDatabaseFromV26(p.dbHandle)
 	default:
 		return fmt.Errorf("database schema version not handled: %d", dbVersion.Version)
 	}
@@ -788,7 +795,14 @@ func updateMySQLDatabaseFromV23(dbHandle *sql.DB) error {
 }
 
 func updateMySQLDatabaseFromV24(dbHandle *sql.DB) error {
-	return updateMySQLDatabaseFrom24To25(dbHandle)
+	if err := updateMySQLDatabaseFrom24To25(dbHandle); err != nil {
+		return err
+	}
+	return updateMySQLDatabaseFromV25(dbHandle)
+}
+
+func updateMySQLDatabaseFromV25(dbHandle *sql.DB) error {
+	return updateMySQLDatabaseFrom25To26(dbHandle)
 }
 
 func downgradeMySQLDatabaseFromV24(dbHandle *sql.DB) error {
@@ -802,6 +816,13 @@ func downgradeMySQLDatabaseFromV25(dbHandle *sql.DB) error {
 	return downgradeMySQLDatabaseFromV24(dbHandle)
 }
 
+func downgradeMySQLDatabaseFromV26(dbHandle *sql.DB) error {
+	if err := downgradeMySQLDatabaseFrom26To25(dbHandle); err != nil {
+		return err
+	}
+	return downgradeMySQLDatabaseFromV25(dbHandle)
+}
+
 func updateMySQLDatabaseFrom23To24(dbHandle *sql.DB) error {
 	logger.InfoToConsole("updating database schema version: 23 -> 24")
 	providerLog(logger.LevelInfo, "updating database schema version: 23 -> 24")
@@ -819,6 +840,13 @@ func updateMySQLDatabaseFrom24To25(dbHandle *sql.DB) error {
 	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 25, true)
 }
 
+func updateMySQLDatabaseFrom25To26(dbHandle *sql.DB) error {
+	logger.InfoToConsole("updating database schema version: 25 -> 26")
+	providerLog(logger.LevelInfo, "updating database schema version: 25 -> 26")
+	sql := strings.ReplaceAll(mysqlV26SQL, "{{events_rules}}", sqlTableEventsRules)
+	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 26, true)
+}
+
 func downgradeMySQLDatabaseFrom24To23(dbHandle *sql.DB) error {
 	logger.InfoToConsole("downgrading database schema version: 24 -> 23")
 	providerLog(logger.LevelInfo, "downgrading database schema version: 24 -> 23")
@@ -835,3 +863,10 @@ func downgradeMySQLDatabaseFrom25To24(dbHandle *sql.DB) error {
 	sql := strings.ReplaceAll(mysqlV25DownSQL, "{{users}}", sqlTableUsers)
 	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 24, false)
 }
+
+func downgradeMySQLDatabaseFrom26To25(dbHandle *sql.DB) error {
+	logger.InfoToConsole("downgrading database schema version: 26 -> 25")
+	providerLog(logger.LevelInfo, "downgrading database schema version: 26 -> 25")
+	sql := strings.ReplaceAll(mysqlV26DownSQL, "{{events_rules}}", sqlTableEventsRules)
+	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, strings.Split(sql, ";"), 25, false)
+}

+ 41 - 1
internal/dataprovider/pgsql.go

@@ -198,6 +198,10 @@ DROP TABLE "{{roles}}" CASCADE;
 ALTER TABLE "{{users}}" ALTER COLUMN "last_password_change" DROP DEFAULT;
 `
 	pgsqlV25DownSQL = `ALTER TABLE "{{users}}" DROP COLUMN "last_password_change" CASCADE;`
+	pgsqlV26SQL     = `ALTER TABLE "{{events_rules}}" ADD COLUMN "status" integer DEFAULT 1 NOT NULL;
+ALTER TABLE "{{events_rules}}" ALTER COLUMN "status" DROP DEFAULT;
+`
+	pgsqlV26DownSQL = `ALTER TABLE "{{events_rules}}" DROP COLUMN "status" CASCADE;`
 )
 
 // PGSQLProvider defines the auth provider for PostgreSQL database
@@ -715,6 +719,8 @@ func (p *PGSQLProvider) migrateDatabase() error { //nolint:dupl
 		return updatePgSQLDatabaseFromV23(p.dbHandle)
 	case version == 24:
 		return updatePgSQLDatabaseFromV24(p.dbHandle)
+	case version == 25:
+		return updatePgSQLDatabaseFromV25(p.dbHandle)
 	default:
 		if version > sqlDatabaseVersion {
 			providerLog(logger.LevelError, "database schema version %d is newer than the supported one: %d", version,
@@ -741,6 +747,8 @@ func (p *PGSQLProvider) revertDatabase(targetVersion int) error {
 		return downgradePgSQLDatabaseFromV24(p.dbHandle)
 	case 25:
 		return downgradePgSQLDatabaseFromV25(p.dbHandle)
+	case 26:
+		return downgradePgSQLDatabaseFromV26(p.dbHandle)
 	default:
 		return fmt.Errorf("database schema version not handled: %d", dbVersion.Version)
 	}
@@ -759,7 +767,14 @@ func updatePgSQLDatabaseFromV23(dbHandle *sql.DB) error {
 }
 
 func updatePgSQLDatabaseFromV24(dbHandle *sql.DB) error {
-	return updatePgSQLDatabaseFrom24To25(dbHandle)
+	if err := updatePgSQLDatabaseFrom24To25(dbHandle); err != nil {
+		return err
+	}
+	return updatePgSQLDatabaseFromV25(dbHandle)
+}
+
+func updatePgSQLDatabaseFromV25(dbHandle *sql.DB) error {
+	return updatePgSQLDatabaseFrom25To26(dbHandle)
 }
 
 func downgradePgSQLDatabaseFromV24(dbHandle *sql.DB) error {
@@ -773,6 +788,13 @@ func downgradePgSQLDatabaseFromV25(dbHandle *sql.DB) error {
 	return downgradePgSQLDatabaseFromV24(dbHandle)
 }
 
+func downgradePgSQLDatabaseFromV26(dbHandle *sql.DB) error {
+	if err := downgradePgSQLDatabaseFrom26To25(dbHandle); err != nil {
+		return err
+	}
+	return downgradePgSQLDatabaseFromV25(dbHandle)
+}
+
 func updatePgSQLDatabaseFrom23To24(dbHandle *sql.DB) error {
 	logger.InfoToConsole("updating database schema version: 23 -> 24")
 	providerLog(logger.LevelInfo, "updating database schema version: 23 -> 24")
@@ -794,6 +816,17 @@ func updatePgSQLDatabaseFrom24To25(dbHandle *sql.DB) error {
 	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 25, true)
 }
 
+func updatePgSQLDatabaseFrom25To26(dbHandle *sql.DB) error {
+	logger.InfoToConsole("updating database schema version: 25 -> 26")
+	providerLog(logger.LevelInfo, "updating database schema version: 25 -> 26")
+	sql := pgsqlV26SQL
+	if config.Driver == CockroachDataProviderName {
+		sql = strings.ReplaceAll(sql, `ALTER TABLE "{{events_rules}}" ALTER COLUMN "status" DROP DEFAULT;`, "")
+	}
+	sql = strings.ReplaceAll(sql, "{{events_rules}}", sqlTableEventsRules)
+	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 26, true)
+}
+
 func downgradePgSQLDatabaseFrom24To23(dbHandle *sql.DB) error {
 	logger.InfoToConsole("downgrading database schema version: 24 -> 23")
 	providerLog(logger.LevelInfo, "downgrading database schema version: 24 -> 23")
@@ -810,3 +843,10 @@ func downgradePgSQLDatabaseFrom25To24(dbHandle *sql.DB) error {
 	sql := strings.ReplaceAll(pgsqlV25DownSQL, "{{users}}", sqlTableUsers)
 	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 24, false)
 }
+
+func downgradePgSQLDatabaseFrom26To25(dbHandle *sql.DB) error {
+	logger.InfoToConsole("downgrading database schema version: 26 -> 25")
+	providerLog(logger.LevelInfo, "downgrading database schema version: 26 -> 25")
+	sql := strings.ReplaceAll(pgsqlV26DownSQL, "{{events_rules}}", sqlTableEventsRules)
+	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 25, false)
+}

+ 4 - 4
internal/dataprovider/sqlcommon.go

@@ -34,7 +34,7 @@ import (
 )
 
 const (
-	sqlDatabaseVersion     = 25
+	sqlDatabaseVersion     = 26
 	defaultSQLQueryTimeout = 10 * time.Second
 	longSQLQueryTimeout    = 60 * time.Second
 )
@@ -1853,7 +1853,7 @@ func getEventRuleFromDbRow(row sqlScanner) (EventRule, error) {
 	var conditions []byte
 
 	err := row.Scan(&rule.ID, &rule.Name, &description, &rule.CreatedAt, &rule.UpdatedAt, &rule.Trigger,
-		&conditions, &rule.DeletedAt)
+		&conditions, &rule.DeletedAt, &rule.Status)
 	if err != nil {
 		if errors.Is(err, sql.ErrNoRows) {
 			return rule, util.NewRecordNotFoundError(err.Error())
@@ -3353,7 +3353,7 @@ func sqlCommonAddEventRule(rule *EventRule, dbHandle *sql.DB) error {
 		}
 		q := getAddEventRuleQuery()
 		_, err := tx.ExecContext(ctx, q, rule.Name, rule.Description, util.GetTimeAsMsSinceEpoch(time.Now()),
-			util.GetTimeAsMsSinceEpoch(time.Now()), rule.Trigger, string(conditions))
+			util.GetTimeAsMsSinceEpoch(time.Now()), rule.Trigger, string(conditions), rule.Status)
 		if err != nil {
 			return err
 		}
@@ -3375,7 +3375,7 @@ func sqlCommonUpdateEventRule(rule *EventRule, dbHandle *sql.DB) error {
 	return sqlCommonExecuteTx(ctx, dbHandle, func(tx *sql.Tx) error {
 		q := getUpdateEventRuleQuery()
 		_, err := tx.ExecContext(ctx, q, rule.Description, util.GetTimeAsMsSinceEpoch(time.Now()),
-			rule.Trigger, string(conditions), rule.Name)
+			rule.Trigger, string(conditions), rule.Status, rule.Name)
 		if err != nil {
 			return err
 		}

+ 35 - 1
internal/dataprovider/sqlite.go

@@ -177,6 +177,8 @@ DROP TABLE "{{roles}}";
 `
 	sqliteV25SQL     = `ALTER TABLE "{{users}}" ADD COLUMN "last_password_change" bigint DEFAULT 0 NOT NULL;`
 	sqliteV25DownSQL = `ALTER TABLE "{{users}}" DROP COLUMN "last_password_change";`
+	sqliteV26SQL     = `ALTER TABLE "{{events_rules}}" ADD COLUMN "status" integer DEFAULT 1 NOT NULL;`
+	sqliteV26DownSQL = `ALTER TABLE "{{events_rules}}" DROP COLUMN "status";`
 )
 
 // SQLiteProvider defines the auth provider for SQLite database
@@ -673,6 +675,8 @@ func (p *SQLiteProvider) migrateDatabase() error { //nolint:dupl
 		return updateSQLiteDatabaseFromV23(p.dbHandle)
 	case version == 24:
 		return updateSQLiteDatabaseFromV24(p.dbHandle)
+	case version == 25:
+		return updateSQLiteDatabaseFromV25(p.dbHandle)
 	default:
 		if version > sqlDatabaseVersion {
 			providerLog(logger.LevelError, "database schema version %d is newer than the supported one: %d", version,
@@ -699,6 +703,8 @@ func (p *SQLiteProvider) revertDatabase(targetVersion int) error {
 		return downgradeSQLiteDatabaseFromV24(p.dbHandle)
 	case 25:
 		return downgradeSQLiteDatabaseFromV25(p.dbHandle)
+	case 26:
+		return downgradeSQLiteDatabaseFromV26(p.dbHandle)
 	default:
 		return fmt.Errorf("database schema version not handled: %d", dbVersion.Version)
 	}
@@ -717,7 +723,14 @@ func updateSQLiteDatabaseFromV23(dbHandle *sql.DB) error {
 }
 
 func updateSQLiteDatabaseFromV24(dbHandle *sql.DB) error {
-	return updateSQLiteDatabaseFrom24To25(dbHandle)
+	if err := updateSQLiteDatabaseFrom24To25(dbHandle); err != nil {
+		return err
+	}
+	return updateSQLiteDatabaseFromV25(dbHandle)
+}
+
+func updateSQLiteDatabaseFromV25(dbHandle *sql.DB) error {
+	return updateSQLiteDatabaseFrom25To26(dbHandle)
 }
 
 func downgradeSQLiteDatabaseFromV24(dbHandle *sql.DB) error {
@@ -731,6 +744,13 @@ func downgradeSQLiteDatabaseFromV25(dbHandle *sql.DB) error {
 	return downgradeSQLiteDatabaseFromV24(dbHandle)
 }
 
+func downgradeSQLiteDatabaseFromV26(dbHandle *sql.DB) error {
+	if err := downgradeSQLiteDatabaseFrom26To25(dbHandle); err != nil {
+		return err
+	}
+	return downgradeSQLiteDatabaseFromV25(dbHandle)
+}
+
 func updateSQLiteDatabaseFrom23To24(dbHandle *sql.DB) error {
 	logger.InfoToConsole("updating database schema version: 23 -> 24")
 	providerLog(logger.LevelInfo, "updating database schema version: 23 -> 24")
@@ -748,6 +768,13 @@ func updateSQLiteDatabaseFrom24To25(dbHandle *sql.DB) error {
 	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 25, true)
 }
 
+func updateSQLiteDatabaseFrom25To26(dbHandle *sql.DB) error {
+	logger.InfoToConsole("updating database schema version: 25 -> 26")
+	providerLog(logger.LevelInfo, "updating database schema version: 25 -> 26")
+	sql := strings.ReplaceAll(sqliteV26SQL, "{{events_rules}}", sqlTableEventsRules)
+	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 26, true)
+}
+
 func downgradeSQLiteDatabaseFrom24To23(dbHandle *sql.DB) error {
 	logger.InfoToConsole("downgrading database schema version: 24 -> 23")
 	providerLog(logger.LevelInfo, "downgrading database schema version: 24 -> 23")
@@ -765,6 +792,13 @@ func downgradeSQLiteDatabaseFrom25To24(dbHandle *sql.DB) error {
 	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 24, false)
 }
 
+func downgradeSQLiteDatabaseFrom26To25(dbHandle *sql.DB) error {
+	logger.InfoToConsole("downgrading database schema version: 26 -> 25")
+	providerLog(logger.LevelInfo, "downgrading database schema version: 26 -> 25")
+	sql := strings.ReplaceAll(sqliteV26DownSQL, "{{events_rules}}", sqlTableEventsRules)
+	return sqlCommonExecSQLAndUpdateDBVersion(dbHandle, []string{sql}, 25, false)
+}
+
 /*func setPragmaFK(dbHandle *sql.DB, value string) error {
 	ctx, cancel := context.WithTimeout(context.Background(), longSQLQueryTimeout)
 	defer cancel()

+ 7 - 7
internal/dataprovider/sqlqueries.go

@@ -61,10 +61,10 @@ func getSQLQuotedName(name string) string {
 
 func getSelectEventRuleFields() string {
 	if config.Driver == MySQLDataProviderName {
-		return "id,name,description,created_at,updated_at,`trigger`,conditions,deleted_at"
+		return "id,name,description,created_at,updated_at,`trigger`,conditions,deleted_at,status"
 	}
 
-	return `id,name,description,created_at,updated_at,"trigger",conditions,deleted_at`
+	return `id,name,description,created_at,updated_at,"trigger",conditions,deleted_at,status`
 }
 
 func getCoalesceDefaultForRole(role string) string {
@@ -973,16 +973,16 @@ func getEventRulesByNameQuery() string {
 }
 
 func getAddEventRuleQuery() string {
-	return fmt.Sprintf(`INSERT INTO %s (name,description,created_at,updated_at,%s,conditions,deleted_at)
-		VALUES (%s,%s,%s,%s,%s,%s,0)`,
+	return fmt.Sprintf(`INSERT INTO %s (name,description,created_at,updated_at,%s,conditions,deleted_at,status)
+		VALUES (%s,%s,%s,%s,%s,%s,0,%s)`,
 		sqlTableEventsRules, getSQLQuotedName("trigger"), sqlPlaceholders[0], sqlPlaceholders[1], sqlPlaceholders[2],
-		sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5])
+		sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5], sqlPlaceholders[6])
 }
 
 func getUpdateEventRuleQuery() string {
-	return fmt.Sprintf(`UPDATE %s SET description=%s,updated_at=%s,%s=%s,conditions=%s WHERE name = %s`,
+	return fmt.Sprintf(`UPDATE %s SET description=%s,updated_at=%s,%s=%s,conditions=%s,status=%s WHERE name = %s`,
 		sqlTableEventsRules, sqlPlaceholders[0], sqlPlaceholders[1], getSQLQuotedName("trigger"), sqlPlaceholders[2],
-		sqlPlaceholders[3], sqlPlaceholders[4])
+		sqlPlaceholders[3], sqlPlaceholders[4], sqlPlaceholders[5])
 }
 
 func getDeleteEventRuleQuery(softDelete bool) string {

+ 7 - 2
internal/httpd/api_maintenance.go

@@ -212,7 +212,7 @@ func restoreBackup(content []byte, inputFile string, scanQuota, mode int, execut
 		return err
 	}
 
-	if err = RestoreEventRules(dump.EventRules, inputFile, mode, executor, ipAddress, role); err != nil {
+	if err = RestoreEventRules(dump.EventRules, inputFile, mode, executor, ipAddress, role, dump.Version); err != nil {
 		return err
 	}
 
@@ -331,9 +331,14 @@ func RestoreEventActions(actions []dataprovider.BaseEventAction, inputFile strin
 }
 
 // RestoreEventRules restores the specified event rules
-func RestoreEventRules(rules []dataprovider.EventRule, inputFile string, mode int, executor, ipAddress, role string) error {
+func RestoreEventRules(rules []dataprovider.EventRule, inputFile string, mode int, executor, ipAddress,
+	role string, dumpVersion int,
+) error {
 	for _, rule := range rules {
 		rule := rule // pin
+		if dumpVersion < 15 {
+			rule.Status = 1
+		}
 		r, err := dataprovider.EventRuleExists(rule.Name)
 		if err == nil {
 			if mode == 1 {

+ 49 - 9
internal/httpd/httpd_test.go

@@ -1408,6 +1408,7 @@ func TestBasicActionRulesHandling(t *testing.T) {
 
 	r := dataprovider.EventRule{
 		Name:        "test rule name",
+		Status:      1,
 		Description: "",
 		Trigger:     dataprovider.EventTriggerFsEvent,
 		Conditions: dataprovider.EventConditions{
@@ -1629,6 +1630,7 @@ func TestActionRuleRelations(t *testing.T) {
 			Order: 10,
 		},
 	}
+	rule2.Status = 1
 	rule2, _, err = httpdtest.UpdateEventRule(r2, http.StatusOK)
 	assert.NoError(t, err)
 	if assert.Len(t, rule2.Actions, 1) {
@@ -1936,6 +1938,11 @@ func TestEventRuleValidation(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Contains(t, string(resp), "name is mandatory")
 	rule.Name = "r"
+	rule.Status = 100
+	_, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
+	assert.NoError(t, err)
+	assert.Contains(t, string(resp), "invalid event rule status")
+	rule.Status = 1
 	rule.Trigger = 1000
 	_, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
 	assert.NoError(t, err)
@@ -6522,7 +6529,9 @@ func TestProviderErrors(t *testing.T) {
 	assert.NoError(t, err)
 	user = getTestUser()
 	user.ID = 1
-	backupData := dataprovider.BackupData{}
+	backupData := dataprovider.BackupData{
+		Version: dataprovider.DumpVersion,
+	}
 	backupData.Users = append(backupData.Users, user)
 	backupContent, err := json.Marshal(backupData)
 	assert.NoError(t, err)
@@ -6591,6 +6600,7 @@ func TestProviderErrors(t *testing.T) {
 				Type: dataprovider.ActionTypeFolderQuotaReset,
 			},
 		},
+		Version: dataprovider.DumpVersion,
 	}
 	backupContent, err = json.Marshal(backupData)
 	assert.NoError(t, err)
@@ -6623,6 +6633,7 @@ func TestProviderErrors(t *testing.T) {
 				},
 			},
 		},
+		Version: dataprovider.DumpVersion,
 	}
 	backupContent, err = json.Marshal(backupData)
 	assert.NoError(t, err)
@@ -6636,6 +6647,7 @@ func TestProviderErrors(t *testing.T) {
 				Name: "role1",
 			},
 		},
+		Version: dataprovider.DumpVersion,
 	}
 	backupContent, err = json.Marshal(backupData)
 	assert.NoError(t, err)
@@ -7036,7 +7048,9 @@ func TestRestoreShares(t *testing.T) {
 		UsedTokens:  8,
 		AllowFrom:   []string{"127.0.0.0/8"},
 	}
-	backupData := dataprovider.BackupData{}
+	backupData := dataprovider.BackupData{
+		Version: dataprovider.DumpVersion,
+	}
 	backupData.Shares = append(backupData.Shares, share)
 	backupContent, err := json.Marshal(backupData)
 	assert.NoError(t, err)
@@ -7088,7 +7102,9 @@ func TestLoaddataFromPostBody(t *testing.T) {
 	admin.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminChangeUsers,
 		dataprovider.PermAdminDeleteUsers, dataprovider.PermAdminViewUsers}
 	admin.Role = role.Name
-	backupData := dataprovider.BackupData{}
+	backupData := dataprovider.BackupData{
+		Version: dataprovider.DumpVersion,
+	}
 	backupData.Users = append(backupData.Users, user)
 	backupData.Groups = append(backupData.Groups, group)
 	backupData.Admins = append(backupData.Admins, admin)
@@ -7273,7 +7289,9 @@ func TestLoaddata(t *testing.T) {
 			},
 		},
 	}
-	backupData := dataprovider.BackupData{}
+	backupData := dataprovider.BackupData{
+		Version: 14,
+	}
 	backupData.Users = append(backupData.Users, user)
 	backupData.Roles = append(backupData.Roles, role)
 	backupData.Groups = append(backupData.Groups, group)
@@ -7350,6 +7368,7 @@ func TestLoaddata(t *testing.T) {
 
 	rule, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
 	assert.NoError(t, err)
+	assert.Equal(t, 1, rule.Status)
 	if assert.Len(t, rule.Actions, 1) {
 		if assert.NotNil(t, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password) {
 			assert.Equal(t, sdkkms.SecretStatusSecretBox, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetStatus())
@@ -7516,7 +7535,9 @@ func TestLoaddataMode(t *testing.T) {
 			},
 		},
 	}
-	backupData := dataprovider.BackupData{}
+	backupData := dataprovider.BackupData{
+		Version: dataprovider.DumpVersion,
+	}
 	backupData.Users = append(backupData.Users, user)
 	backupData.Groups = append(backupData.Groups, group)
 	backupData.Admins = append(backupData.Admins, admin)
@@ -7603,6 +7624,7 @@ func TestLoaddataMode(t *testing.T) {
 
 	rule, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
 	assert.NoError(t, err)
+	assert.Equal(t, 0, rule.Status)
 	oldRuleDesc := rule.Description
 	rule.Description = "new rule description"
 	rule, _, err = httpdtest.UpdateEventRule(rule, http.StatusOK)
@@ -17653,7 +17675,9 @@ func TestWebMaintenanceMock(t *testing.T) {
 		Key:   fmt.Sprintf("%v.%v", util.GenerateUniqueID(), util.GenerateUniqueID()),
 		Scope: dataprovider.APIKeyScopeAdmin,
 	}
-	backupData := dataprovider.BackupData{}
+	backupData := dataprovider.BackupData{
+		Version: dataprovider.DumpVersion,
+	}
 	backupData.Users = append(backupData.Users, user)
 	backupData.Admins = append(backupData.Admins, admin)
 	backupData.APIKeys = append(backupData.APIKeys, apiKey)
@@ -19027,7 +19051,9 @@ func TestFolderTemplateMock(t *testing.T) {
 	rr = executeRequest(req)
 	checkResponseCode(t, http.StatusOK, rr)
 
-	dump = dataprovider.BackupData{}
+	dump = dataprovider.BackupData{
+		Version: dataprovider.DumpVersion,
+	}
 	err = json.Unmarshal(rr.Body.Bytes(), &dump)
 	require.NoError(t, err)
 	require.Len(t, dump.Users, 0)
@@ -20530,6 +20556,7 @@ func TestWebEventRule(t *testing.T) {
 	assert.NoError(t, err)
 	rule := dataprovider.EventRule{
 		Name:        "test_web_rule",
+		Status:      1,
 		Description: "rule added using web API",
 		Trigger:     dataprovider.EventTriggerSchedule,
 		Conditions: dataprovider.EventConditions{
@@ -20574,13 +20601,22 @@ func TestWebEventRule(t *testing.T) {
 	form := make(url.Values)
 	form.Set("name", rule.Name)
 	form.Set("description", rule.Description)
-	form.Set("trigger", "a")
+	form.Set("status", "a")
 	req, err := http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
 	assert.NoError(t, err)
 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 	setJWTCookieForReq(req, webToken)
 	rr := executeRequest(req)
 	checkResponseCode(t, http.StatusOK, rr)
+	assert.Contains(t, rr.Body.String(), "invalid status")
+	form.Set("status", fmt.Sprintf("%d", rule.Status))
+	form.Set("trigger", "a")
+	req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
+	assert.NoError(t, err)
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+	setJWTCookieForReq(req, webToken)
+	rr = executeRequest(req)
+	checkResponseCode(t, http.StatusOK, rr)
 	assert.Contains(t, rr.Body.String(), "invalid trigger")
 	form.Set("trigger", fmt.Sprintf("%d", rule.Trigger))
 	form.Set("schedule_hour0", rule.Conditions.Schedules[0].Hours)
@@ -20668,13 +20704,15 @@ func TestWebEventRule(t *testing.T) {
 	ruleGet, _, err := httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
 	assert.NoError(t, err)
 	assert.Equal(t, rule.Trigger, ruleGet.Trigger)
+	assert.Equal(t, rule.Status, ruleGet.Status)
 	assert.Equal(t, rule.Description, ruleGet.Description)
 	assert.Equal(t, rule.Conditions, ruleGet.Conditions)
 	if assert.Len(t, ruleGet.Actions, 1) {
 		assert.Equal(t, rule.Actions[0].Name, ruleGet.Actions[0].Name)
 		assert.Equal(t, rule.Actions[0].Order, ruleGet.Actions[0].Order)
 	}
-	// change rule trigger
+	// change rule trigger and status
+	rule.Status = 0
 	rule.Trigger = dataprovider.EventTriggerFsEvent
 	rule.Conditions = dataprovider.EventConditions{
 		FsEvents: []string{"upload", "download"},
@@ -20707,6 +20745,7 @@ func TestWebEventRule(t *testing.T) {
 			MaxFileSize: 5 * 1024 * 1024,
 		},
 	}
+	form.Set("status", fmt.Sprintf("%d", rule.Status))
 	form.Set("trigger", fmt.Sprintf("%d", rule.Trigger))
 	for _, event := range rule.Conditions.FsEvents {
 		form.Add("fs_events", event)
@@ -20727,6 +20766,7 @@ func TestWebEventRule(t *testing.T) {
 	// check the rule
 	ruleGet, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
 	assert.NoError(t, err)
+	assert.Equal(t, rule.Status, ruleGet.Status)
 	assert.Equal(t, rule.Trigger, ruleGet.Trigger)
 	assert.Equal(t, rule.Description, ruleGet.Description)
 	assert.Equal(t, rule.Conditions, ruleGet.Conditions)

+ 6 - 0
internal/httpd/webadmin.go

@@ -2339,6 +2339,10 @@ func getEventRuleFromPostFields(r *http.Request) (dataprovider.EventRule, error)
 	if err != nil {
 		return dataprovider.EventRule{}, err
 	}
+	status, err := strconv.Atoi(r.Form.Get("status"))
+	if err != nil {
+		return dataprovider.EventRule{}, fmt.Errorf("invalid status: %w", err)
+	}
 	trigger, err := strconv.Atoi(r.Form.Get("trigger"))
 	if err != nil {
 		return dataprovider.EventRule{}, fmt.Errorf("invalid trigger: %w", err)
@@ -2353,6 +2357,7 @@ func getEventRuleFromPostFields(r *http.Request) (dataprovider.EventRule, error)
 	}
 	rule := dataprovider.EventRule{
 		Name:        r.Form.Get("name"),
+		Status:      status,
 		Description: r.Form.Get("description"),
 		Trigger:     trigger,
 		Conditions:  conditions,
@@ -3499,6 +3504,7 @@ func (s *httpdServer) handleWebGetEventRules(w http.ResponseWriter, r *http.Requ
 func (s *httpdServer) handleWebAddEventRuleGet(w http.ResponseWriter, r *http.Request) {
 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 	rule := dataprovider.EventRule{
+		Status:  1,
 		Trigger: dataprovider.EventTriggerFsEvent,
 	}
 	s.renderEventRulePage(w, r, rule, genericPageModeAdd, "")

+ 3 - 0
internal/httpdtest/httpdtest.go

@@ -1601,6 +1601,9 @@ func checkEventRule(expected, actual dataprovider.EventRule) error {
 	if dataprovider.ConvertName(expected.Name) != actual.Name {
 		return errors.New("name mismatch")
 	}
+	if expected.Status != actual.Status {
+		return errors.New("status mismatch")
+	}
 	if expected.Description != actual.Description {
 		return errors.New("description mismatch")
 	}

+ 2 - 1
internal/service/service.go

@@ -382,7 +382,8 @@ func (s *Service) restoreDump(dump *dataprovider.BackupData) error {
 	if err != nil {
 		return fmt.Errorf("unable to restore event actions from file %#v: %v", s.LoadDataFrom, err)
 	}
-	err = httpd.RestoreEventRules(dump.EventRules, s.LoadDataFrom, s.LoadDataMode, dataprovider.ActionExecutorSystem, "", "")
+	err = httpd.RestoreEventRules(dump.EventRules, s.LoadDataFrom, s.LoadDataMode, dataprovider.ActionExecutorSystem,
+		"", "", dump.Version)
 	if err != nil {
 		return fmt.Errorf("unable to restore event rules from file %#v: %v", s.LoadDataFrom, err)
 	}

+ 9 - 0
openapi/openapi.yaml

@@ -6748,6 +6748,15 @@ components:
         name:
           type: string
           description: unique name
+        status:
+          type: integer
+          enum:
+            - 0
+            - 1
+          description: |
+            status:
+              * `0` disabled
+              * `1` enabled
         description:
           type: string
           description: optional description

+ 10 - 10
templates/webadmin/admin.html

@@ -41,6 +41,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
                 </div>
             </div>
 
+            <div class="form-group row">
+                <label for="idStatus" class="col-sm-2 col-form-label">Status</label>
+                <div class="col-sm-10">
+                    <select class="form-control selectpicker" id="idStatus" name="status">
+                        <option value="1" {{if eq .Admin.Status 1 }}selected{{end}}>Active</option>
+                        <option value="0" {{if eq .Admin.Status 0 }}selected{{end}}>Inactive</option>
+                    </select>
+                </div>
+            </div>
+
             <div class="form-group row">
                 <label for="idEmail" class="col-sm-2 col-form-label">Email</label>
                 <div class="col-sm-10">
@@ -60,16 +70,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
                 </div>
             </div>
 
-            <div class="form-group row">
-                <label for="idStatus" class="col-sm-2 col-form-label">Status</label>
-                <div class="col-sm-10">
-                    <select class="form-control selectpicker" id="idStatus" name="status">
-                        <option value="1" {{if eq .Admin.Status 1 }}selected{{end}}>Active</option>
-                        <option value="0" {{if eq .Admin.Status 0 }}selected{{end}}>Inactive</option>
-                    </select>
-                </div>
-            </div>
-
             <div class="form-group row">
                 <label for="idPassword" class="col-sm-2 col-form-label">Password</label>
                 <div class="col-sm-10">

+ 10 - 0
templates/webadmin/eventrule.html

@@ -41,6 +41,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
                 </div>
             </div>
 
+            <div class="form-group row">
+                <label for="idStatus" class="col-sm-2 col-form-label">Status</label>
+                <div class="col-sm-10">
+                    <select class="form-control selectpicker" id="idStatus" name="status">
+                        <option value="1" {{if eq .Rule.Status 1 }}selected{{end}}>Active</option>
+                        <option value="0" {{if eq .Rule.Status 0 }}selected{{end}}>Inactive</option>
+                    </select>
+                </div>
+            </div>
+
             <div class="form-group row">
                 <label for="idDescription" class="col-sm-2 col-form-label">Description</label>
                 <div class="col-sm-10">

+ 8 - 2
templates/webadmin/eventrules.html

@@ -39,6 +39,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
                 <thead>
                     <tr>
                         <th>Name</th>
+                        <th>Status</th>
                         <th>Description</th>
                         <th>Trigger</th>
                         <th>Actions</th>
@@ -48,6 +49,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
                     {{range .Rules}}
                     <tr>
                         <td>{{.Name}}</td>
+                        <td>{{if eq .Status 1 }}Active{{else}}Inactive{{end}}</td>
                         <td>{{.Description}}</td>
                         <td>{{.GetTriggerAsString}}</td>
                         <td>{{.GetActionsAsString}}</td>
@@ -184,11 +186,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
             ],
             "columnDefs": [
                 {
-                    "targets": [0],
+                    "targets": [0,1],
                     "className": "noVis"
                 },
                 {
-                    "targets": [3],
+                    "targets": [2],
+                    "visible": false
+                },
+                {
+                    "targets": [4],
                     "render": $.fn.dataTable.render.ellipsis(100, true)
                 },
             ],