Преглед изворни кода

plugins: improve notifier and searcher

Nicola Murino пре 3 година
родитељ
комит
97d0a48557

+ 5 - 5
common/actions.go

@@ -54,7 +54,7 @@ func InitializeActionHandler(handler ActionHandler) {
 func ExecutePreAction(user *dataprovider.User, operation, filePath, virtualPath, protocol, ip string, fileSize int64,
 func ExecutePreAction(user *dataprovider.User, operation, filePath, virtualPath, protocol, ip string, fileSize int64,
 	openFlags int,
 	openFlags int,
 ) error {
 ) error {
-	plugin.Handler.NotifyFsEvent(time.Now(), operation, user.Username, filePath, "", "", protocol, ip, virtualPath, "", fileSize, nil)
+	plugin.Handler.NotifyFsEvent(time.Now().UnixNano(), operation, user.Username, filePath, "", "", protocol, ip, virtualPath, "", fileSize, nil)
 	if !util.IsStringInSlice(operation, Config.Actions.ExecuteOn) {
 	if !util.IsStringInSlice(operation, Config.Actions.ExecuteOn) {
 		// for pre-delete we execute the internal handling on error, so we must return errUnconfiguredAction.
 		// for pre-delete we execute the internal handling on error, so we must return errUnconfiguredAction.
 		// Other pre action will deny the operation on error so if we have no configuration we must return
 		// Other pre action will deny the operation on error so if we have no configuration we must return
@@ -73,7 +73,7 @@ func ExecutePreAction(user *dataprovider.User, operation, filePath, virtualPath,
 func ExecuteActionNotification(user *dataprovider.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
 func ExecuteActionNotification(user *dataprovider.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
 	protocol, ip string, fileSize int64, err error,
 	protocol, ip string, fileSize int64, err error,
 ) {
 ) {
-	plugin.Handler.NotifyFsEvent(time.Now(), operation, user.Username, filePath, target, sshCmd, protocol, ip, virtualPath,
+	plugin.Handler.NotifyFsEvent(time.Now().UnixNano(), operation, user.Username, filePath, target, sshCmd, protocol, ip, virtualPath,
 		virtualTarget, fileSize, err)
 		virtualTarget, fileSize, err)
 	notification := newActionNotification(user, operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol,
 	notification := newActionNotification(user, operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol,
 		ip, fileSize, 0, err)
 		ip, fileSize, 0, err)
@@ -139,9 +139,9 @@ func newActionNotification(
 	}
 	}
 
 
 	if err == ErrQuotaExceeded {
 	if err == ErrQuotaExceeded {
-		status = 2
+		status = 3
 	} else if err != nil {
 	} else if err != nil {
-		status = 0
+		status = 2
 	}
 	}
 
 
 	return &ActionNotification{
 	return &ActionNotification{
@@ -160,7 +160,7 @@ func newActionNotification(
 		Protocol:          protocol,
 		Protocol:          protocol,
 		IP:                ip,
 		IP:                ip,
 		OpenFlags:         openFlags,
 		OpenFlags:         openFlags,
-		Timestamp:         util.GetTimeAsMsSinceEpoch(time.Now()),
+		Timestamp:         time.Now().UnixNano(),
 	}
 	}
 }
 }
 
 

+ 2 - 2
common/actions_test.go

@@ -49,7 +49,7 @@ func TestNewActionNotification(t *testing.T) {
 	assert.Equal(t, user.Username, a.Username)
 	assert.Equal(t, user.Username, a.Username)
 	assert.Equal(t, 0, len(a.Bucket))
 	assert.Equal(t, 0, len(a.Bucket))
 	assert.Equal(t, 0, len(a.Endpoint))
 	assert.Equal(t, 0, len(a.Endpoint))
-	assert.Equal(t, 0, a.Status)
+	assert.Equal(t, 2, a.Status)
 
 
 	user.FsConfig.Provider = sdk.S3FilesystemProvider
 	user.FsConfig.Provider = sdk.S3FilesystemProvider
 	a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", 123, 0, nil)
 	a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", 123, 0, nil)
@@ -61,7 +61,7 @@ func TestNewActionNotification(t *testing.T) {
 	a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", 123, 0, ErrQuotaExceeded)
 	a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", 123, 0, ErrQuotaExceeded)
 	assert.Equal(t, "gcsbucket", a.Bucket)
 	assert.Equal(t, "gcsbucket", a.Bucket)
 	assert.Equal(t, 0, len(a.Endpoint))
 	assert.Equal(t, 0, len(a.Endpoint))
-	assert.Equal(t, 2, a.Status)
+	assert.Equal(t, 3, a.Status)
 
 
 	user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
 	user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
 	a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", 123, 0, nil)
 	a = newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", 123, 0, nil)

+ 2 - 2
dataprovider/actions.go

@@ -32,7 +32,7 @@ const (
 )
 )
 
 
 func executeAction(operation, executor, ip, objectType, objectName string, object plugin.Renderer) {
 func executeAction(operation, executor, ip, objectType, objectName string, object plugin.Renderer) {
-	plugin.Handler.NotifyProviderEvent(time.Now(), operation, executor, objectType, objectName, ip, object)
+	plugin.Handler.NotifyProviderEvent(time.Now().UnixNano(), operation, executor, objectType, objectName, ip, object)
 	if config.Actions.Hook == "" {
 	if config.Actions.Hook == "" {
 		return
 		return
 	}
 	}
@@ -60,7 +60,7 @@ func executeAction(operation, executor, ip, objectType, objectName string, objec
 			q.Add("ip", ip)
 			q.Add("ip", ip)
 			q.Add("object_type", objectType)
 			q.Add("object_type", objectType)
 			q.Add("object_name", objectName)
 			q.Add("object_name", objectName)
-			q.Add("timestamp", fmt.Sprintf("%v", util.GetTimeAsMsSinceEpoch(time.Now())))
+			q.Add("timestamp", fmt.Sprintf("%v", time.Now().UnixNano()))
 			url.RawQuery = q.Encode()
 			url.RawQuery = q.Encode()
 			startTime := time.Now()
 			startTime := time.Now()
 			resp, err := httpclient.RetryablePost(url.String(), "application/json", bytes.NewBuffer(dataAsJSON))
 			resp, err := httpclient.RetryablePost(url.String(), "application/json", bytes.NewBuffer(dataAsJSON))

+ 1 - 1
dataprovider/mysql.go

@@ -105,7 +105,7 @@ func getMySQLConnectionString(redactedPwd bool) string {
 		if redactedPwd {
 		if redactedPwd {
 			password = "[redacted]"
 			password = "[redacted]"
 		}
 		}
-		connectionString = fmt.Sprintf("%v:%v@tcp([%v]:%v)/%v?charset=utf8&interpolateParams=true&timeout=10s&tls=%v&writeTimeout=10s&readTimeout=10s",
+		connectionString = fmt.Sprintf("%v:%v@tcp([%v]:%v)/%v?charset=utf8mb4&interpolateParams=true&timeout=10s&parseTime=true&tls=%v&writeTimeout=10s&readTimeout=10s",
 			config.Username, password, config.Host, config.Port, config.Name, getSSLMode())
 			config.Username, password, config.Host, config.Port, config.Name, getSSLMode())
 	} else {
 	} else {
 		connectionString = config.ConnectionString
 		connectionString = config.ConnectionString

+ 5 - 5
docs/custom-actions.md

@@ -42,11 +42,11 @@ If the `hook` defines a path to an external program, then this program can read
 - `SFTPGO_ACTION_FS_PROVIDER`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
 - `SFTPGO_ACTION_FS_PROVIDER`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
 - `SFTPGO_ACTION_BUCKET`, non-empty for S3, GCS and Azure backends
 - `SFTPGO_ACTION_BUCKET`, non-empty for S3, GCS and Azure backends
 - `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured. For Azure this is the endpoint, if configured
 - `SFTPGO_ACTION_ENDPOINT`, non-empty for S3, SFTP and Azure backend if configured. For Azure this is the endpoint, if configured
-- `SFTPGO_ACTION_STATUS`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 0 means a generic error occurred. 1 means no error, 2 means quota exceeded error
+- `SFTPGO_ACTION_STATUS`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 1 means no error, 2 means a generic error occurred, 3 means quota exceeded error
 - `SFTPGO_ACTION_PROTOCOL`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`, `DataRetention`
 - `SFTPGO_ACTION_PROTOCOL`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`, `DataRetention`
 - `SFTPGO_ACTION_IP`, the action was executed from this IP address
 - `SFTPGO_ACTION_IP`, the action was executed from this IP address
 - `SFTPGO_ACTION_OPEN_FLAGS`, integer. File open flags, can be non-zero for `pre-upload` action. If `SFTPGO_ACTION_FILE_SIZE` is greater than zero and `SFTPGO_ACTION_OPEN_FLAGS&512 == 0` the target file will not be truncated
 - `SFTPGO_ACTION_OPEN_FLAGS`, integer. File open flags, can be non-zero for `pre-upload` action. If `SFTPGO_ACTION_FILE_SIZE` is greater than zero and `SFTPGO_ACTION_OPEN_FLAGS&512 == 0` the target file will not be truncated
-- `SFTPGO_ACTION_TIMESTAMP`, int64. Event timestamp as milliseconds since epoch
+- `SFTPGO_ACTION_TIMESTAMP`, int64. Event timestamp as nanoseconds since epoch
 
 
 Previous global environment variables aren't cleared when the script is called.
 Previous global environment variables aren't cleared when the script is called.
 The program must finish within 30 seconds.
 The program must finish within 30 seconds.
@@ -64,11 +64,11 @@ If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. Th
 - `fs_provider`, integer, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
 - `fs_provider`, integer, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
 - `bucket`, string, inlcuded for S3, GCS and Azure backends
 - `bucket`, string, inlcuded for S3, GCS and Azure backends
 - `endpoint`, string, included for S3, SFTP and Azure backend if configured
 - `endpoint`, string, included for S3, SFTP and Azure backend if configured
-- `status`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 0 means a generic error occurred. 1 means no error, 2 means quota exceeded error
+- `status`, integer. Status for `upload`, `download` and `ssh_cmd` actions. 1 means no error, 2 means a generic error occurred, 3 means quota exceeded error
 - `protocol`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`, `DataRetention`
 - `protocol`, string. Possible values are `SSH`, `SFTP`, `SCP`, `FTP`, `DAV`, `HTTP`, `DataRetention`
 - `ip`, string. The action was executed from this IP address
 - `ip`, string. The action was executed from this IP address
 - `open_flags`, integer. File open flags, can be non-zero for `pre-upload` action. If `file_size` is greater than zero and `file_size&512 == 0` the target file will not be truncated
 - `open_flags`, integer. File open flags, can be non-zero for `pre-upload` action. If `file_size` is greater than zero and `file_size&512 == 0` the target file will not be truncated
-- `timestamp`, int64. Event timestamp as milliseconds since epoch
+- `timestamp`, int64. Event timestamp as nanoseconds since epoch
 
 
 The HTTP hook will use the global configuration for HTTP clients and will respect the retry configurations.
 The HTTP hook will use the global configuration for HTTP clients and will respect the retry configurations.
 
 
@@ -93,7 +93,7 @@ If the `hook` defines a path to an external program, then this program can read
 - `SFTPGO_PROVIDER_OBJECT_NAME`, unique identifier for the affected object, for example username or key id
 - `SFTPGO_PROVIDER_OBJECT_NAME`, unique identifier for the affected object, for example username or key id
 - `SFTPGO_PROVIDER_USERNAME`, the username that executed the action. There are two special usernames: `__self__` identifies a user/admin that updates itself and `__system__` identifies an action that does not have an explicit executor associated with it, for example users/admins can be added/updated by loading them from initial data
 - `SFTPGO_PROVIDER_USERNAME`, the username that executed the action. There are two special usernames: `__self__` identifies a user/admin that updates itself and `__system__` identifies an action that does not have an explicit executor associated with it, for example users/admins can be added/updated by loading them from initial data
 - `SFTPGO_PROVIDER_IP`, the action was executed from this IP address
 - `SFTPGO_PROVIDER_IP`, the action was executed from this IP address
-- `SFTPGO_PROVIDER_TIMESTAMP`, event timestamp as milliseconds since epoch
+- `SFTPGO_PROVIDER_TIMESTAMP`, event timestamp as nanoseconds since epoch
 - `SFTPGO_PROVIDER_OBJECT`, object serialized as JSON with sensitive fields removed
 - `SFTPGO_PROVIDER_OBJECT`, object serialized as JSON with sensitive fields removed
 
 
 Previous global environment variables aren't cleared when the script is called.
 Previous global environment variables aren't cleared when the script is called.

+ 7 - 7
go.mod

@@ -3,11 +3,11 @@ module github.com/drakkan/sftpgo/v2
 go 1.17
 go 1.17
 
 
 require (
 require (
-	cloud.google.com/go/storage v1.18.1
+	cloud.google.com/go/storage v1.18.2
 	github.com/Azure/azure-storage-blob-go v0.14.0
 	github.com/Azure/azure-storage-blob-go v0.14.0
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
 	github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
 	github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
 	github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
-	github.com/aws/aws-sdk-go v1.41.4
+	github.com/aws/aws-sdk-go v1.41.6
 	github.com/cockroachdb/cockroach-go/v2 v2.2.1
 	github.com/cockroachdb/cockroach-go/v2 v2.2.1
 	github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
 	github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
 	github.com/fatih/color v1.13.0 // indirect
 	github.com/fatih/color v1.13.0 // indirect
@@ -33,7 +33,7 @@ require (
 	github.com/lib/pq v1.10.3
 	github.com/lib/pq v1.10.3
 	github.com/lithammer/shortuuid/v3 v3.0.7
 	github.com/lithammer/shortuuid/v3 v3.0.7
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
-	github.com/mattn/go-sqlite3 v1.14.8
+	github.com/mattn/go-sqlite3 v1.14.9
 	github.com/mhale/smtpd v0.8.0
 	github.com/mhale/smtpd v0.8.0
 	github.com/miekg/dns v1.1.43 // indirect
 	github.com/miekg/dns v1.1.43 // indirect
 	github.com/minio/sio v0.3.0
 	github.com/minio/sio v0.3.0
@@ -44,7 +44,7 @@ require (
 	github.com/pkg/sftp v1.13.4
 	github.com/pkg/sftp v1.13.4
 	github.com/pquerna/otp v1.3.0
 	github.com/pquerna/otp v1.3.0
 	github.com/prometheus/client_golang v1.11.0
 	github.com/prometheus/client_golang v1.11.0
-	github.com/prometheus/common v0.31.1 // indirect
+	github.com/prometheus/common v0.32.0 // indirect
 	github.com/rs/cors v1.8.0
 	github.com/rs/cors v1.8.0
 	github.com/rs/xid v1.3.0
 	github.com/rs/xid v1.3.0
 	github.com/rs/zerolog v1.25.0
 	github.com/rs/zerolog v1.25.0
@@ -63,10 +63,10 @@ require (
 	gocloud.dev v0.24.0
 	gocloud.dev v0.24.0
 	golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
 	golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
 	golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
 	golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
-	golang.org/x/sys v0.0.0-20211015200801-69063c4bb744
+	golang.org/x/sys v0.0.0-20211020154033-fcb26fe61c20
 	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
 	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
-	google.golang.org/api v0.58.0
-	google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect
+	google.golang.org/api v0.59.0
+	google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a // indirect
 	google.golang.org/grpc v1.41.0
 	google.golang.org/grpc v1.41.0
 	google.golang.org/protobuf v1.27.1
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0
 	gopkg.in/natefinch/lumberjack.v2 v2.0.0

+ 17 - 13
go.sum

@@ -60,8 +60,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4=
 cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4=
-cloud.google.com/go/storage v1.18.1 h1:hfXX1gE2mvuwoS/weBfgH1Vr+efX0uLdxX2ETJ9g1JQ=
-cloud.google.com/go/storage v1.18.1/go.mod h1:FPalGc7eWSCKf26f2JwjrZhVibDE6pCDP19d1Pe2lB8=
+cloud.google.com/go/storage v1.18.2 h1:5NQw6tOn3eMm0oE8vTkfjau18kjL79FlMjy/CHTpmoY=
+cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM=
 cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
 cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
 contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ=
@@ -137,8 +137,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
 github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
-github.com/aws/aws-sdk-go v1.41.4 h1:5xRzZp8LfBFfowMPxmoNsxLBZOY/NTH4EeI7q2F5eWE=
-github.com/aws/aws-sdk-go v1.41.4/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
+github.com/aws/aws-sdk-go v1.41.6 h1:ojO1jWhE3lkJlTFQOq0rlWZ11q18LIdsZNtGJ07FFEA=
+github.com/aws/aws-sdk-go v1.41.6/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
 github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
 github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
 github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
 github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
 github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
 github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
@@ -592,8 +592,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
-github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
+github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mhale/smtpd v0.8.0 h1:5JvdsehCg33PQrZBvFyDMMUDQmvbzVpZgKob7eYBJc0=
 github.com/mhale/smtpd v0.8.0 h1:5JvdsehCg33PQrZBvFyDMMUDQmvbzVpZgKob7eYBJc0=
@@ -688,8 +688,8 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
-github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
+github.com/prometheus/common v0.32.0 h1:HRmM4uANZDAjdvbsdfOoqI5UDbjz0faKeMs/cGPKKI0=
+github.com/prometheus/common v0.32.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@@ -964,8 +964,9 @@ golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 h1:KzbpndAYEM+4oHRp9JmB2ewj0NHHxO3Z0g7Gus2O1kk=
-golang.org/x/sys v0.0.0-20211015200801-69063c4bb744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211020154033-fcb26fe61c20 h1:NPtIbR2t8Mhg6UCbkTQMNMejkPpH6IvV4PdZnh1Mqpk=
+golang.org/x/sys v0.0.0-20211020154033-fcb26fe61c20/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1090,8 +1091,9 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6
 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
 google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
 google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
-google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0=
 google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
 google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
+google.golang.org/api v0.59.0 h1:fPfFO7gttlXYo2ALuD3HxJzh8vaF++4youI0BkFL6GE=
+google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1165,8 +1167,10 @@ google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEc
 google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 h1:NBxB1XxiWpGqkPUiJ9PoBXkHV5A9+GohMOA+EmWoPbU=
-google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a h1:8maMHMQp9NroHXhc3HelFX9Ay2lWlXLcdH5mw5Biz0s=
+google.golang.org/genproto v0.0.0-20211020151524-b7c3a969101a/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=

+ 354 - 2
httpd/schema/openapi.yaml

@@ -10,12 +10,13 @@ tags:
   - name: quota
   - name: quota
   - name: folders
   - name: folders
   - name: users
   - name: users
-  - name: users API
   - name: data retention
   - name: data retention
+  - name: events
+  - name: users API
 info:
 info:
   title: SFTPGo
   title: SFTPGo
   description: |
   description: |
-    SFTPGo allows to securely share your files over SFTP and optionally FTP/S and WebDAV too.
+    SFTPGo allows to securely share your files over SFTP and optionally FTP/S and WebDAV as well.
     Several storage backends are supported and they are configurable per user, so you can serve a local directory for a user and an S3 bucket (or part of it) for another one.
     Several storage backends are supported and they are configurable per user, so you can serve a local directory for a user and an S3 bucket (or part of it) for another one.
     SFTPGo also supports virtual folders, a virtual folder can use any of the supported storage backends. So you can have, for example, an S3 user that exposes a GCS bucket (or part of it) on a specified path and an encrypted local filesystem on another one.
     SFTPGo also supports virtual folders, a virtual folder can use any of the supported storage backends. So you can have, for example, an S3 user that exposes a GCS bucket (or part of it) on a specified path and an encrypted local filesystem on another one.
     Virtual folders can be private or shared among multiple users, for shared virtual folders you can define different quota limits for each user.
     Virtual folders can be private or shared among multiple users, for shared virtual folders you can define different quota limits for each user.
@@ -1485,6 +1486,251 @@ paths:
           $ref: '#/components/responses/InternalServerError'
           $ref: '#/components/responses/InternalServerError'
         default:
         default:
           $ref: '#/components/responses/DefaultResponse'
           $ref: '#/components/responses/DefaultResponse'
+  /events/fs:
+    get:
+      tags:
+        - events
+      summary: Get filesystem events
+      description: 'Returns an array with one or more filesystem events applying the specified filters. This API is only available if you configure an "eventsearcher" plugin'
+      operationId: get_fs_events
+      parameters:
+        - in: query
+          name: start_timestamp
+          schema:
+            type: integer
+            format: int64
+            minimum: 0
+            default: 0
+          required: false
+          description: 'the event timestamp, unix timestamp in nanoseconds, must be greater than or equal to the specified one. 0 or missing means omit this filter'
+        - in: query
+          name: end_timestamp
+          schema:
+            type: integer
+            format: int64
+            minimum: 0
+            default: 0
+          required: false
+          description: 'the event timestamp, unix timestamp in nanoseconds, must be less than or equal to the specified one. 0 or missing means omit this filter'
+        - in: query
+          name: actions
+          schema:
+            type: array
+            items:
+              $ref: '#/components/schemas/FsEventAction'
+          description: 'the event action must be included among those specified. Empty or missing means omit this filter. Actions must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: username
+          schema:
+            type: string
+          description: 'the event username must be the same as the one specified. Empty or missing means omit this filter'
+          required: false
+        - in: query
+          name: ip
+          schema:
+            type: string
+          description: 'the event IP must be the same as the one specified. Empty or missing means omit this filter'
+          required: false
+        - in: query
+          name: ssh_cmd
+          schema:
+            type: string
+          description: 'the event SSH command must be the same as the one specified. Empty or missing means omit this filter'
+          required: false
+        - in: query
+          name: protocols
+          schema:
+            type: array
+            items:
+              $ref: '#/components/schemas/EventProtocols'
+          description: 'the event protocol must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: statuses
+          schema:
+            type: array
+            items:
+              $ref: '#/components/schemas/FsEventStatus'
+          description: 'the event status must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: instance_ids
+          schema:
+            type: array
+            items:
+              type: string
+          description: 'the event instance id must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: exclude_ids
+          schema:
+            type: array
+            items:
+              type: string
+          description: 'the event id must not be included among those specified. This is useful for cursor based pagination. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: limit
+          schema:
+            type: integer
+            minimum: 1
+            maximum: 1000
+            default: 100
+          required: false
+          description: 'The maximum number of items to return. Max value is 500, default is 100'
+        - in: query
+          name: order
+          required: false
+          description: Ordering events by timestamp. Default DESC
+          schema:
+            type: string
+            enum:
+              - ASC
+              - DESC
+            example: DESC
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/FsEvent'
+        '400':
+          $ref: '#/components/responses/BadRequest'
+        '401':
+          $ref: '#/components/responses/Unauthorized'
+        '403':
+          $ref: '#/components/responses/Forbidden'
+        '500':
+          $ref: '#/components/responses/InternalServerError'
+        default:
+          $ref: '#/components/responses/DefaultResponse'
+  /events/provider:
+    get:
+      tags:
+        - events
+      summary: Get provider events
+      description: 'Returns an array with one or more provider events applying the specified filters. This API is only available if you configure an "eventsearcher" plugin'
+      operationId: get_provider_events
+      parameters:
+        - in: query
+          name: start_timestamp
+          schema:
+            type: integer
+            format: int64
+            minimum: 0
+            default: 0
+          required: false
+          description: 'the event timestamp, unix timestamp in nanoseconds, must be greater than or equal to the specified one. 0 or missing means omit this filter'
+        - in: query
+          name: end_timestamp
+          schema:
+            type: integer
+            format: int64
+            minimum: 0
+            default: 0
+          required: false
+          description: 'the event timestamp, unix timestamp in nanoseconds, must be less than or equal to the specified one. 0 or missing means omit this filter'
+        - in: query
+          name: actions
+          schema:
+            type: array
+            items:
+              $ref: '#/components/schemas/ProviderEventAction'
+          description: 'the event action must be included among those specified. Empty or missing means omit this filter. Actions must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: username
+          schema:
+            type: string
+          description: 'the event username must be the same as the one specified. Empty or missing means omit this filter'
+          required: false
+        - in: query
+          name: ip
+          schema:
+            type: string
+          description: 'the event IP must be the same as the one specified. Empty or missing means omit this filter'
+          required: false
+        - in: query
+          name: object_name
+          schema:
+            type: string
+          description: 'the event object name must be the same as the one specified. Empty or missing means omit this filter'
+          required: false
+        - in: query
+          name: object_types
+          schema:
+            type: array
+            items:
+              $ref: '#/components/schemas/ProviderEventObjectType'
+          description: 'the event object type must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: instance_ids
+          schema:
+            type: array
+            items:
+              type: string
+          description: 'the event instance id must be included among those specified. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: exclude_ids
+          schema:
+            type: array
+            items:
+              type: string
+          description: 'the event id must not be included among those specified. This is useful for cursor based pagination. Empty or missing means omit this filter. Values must be specified comma separated'
+          explode: false
+          required: false
+        - in: query
+          name: limit
+          schema:
+            type: integer
+            minimum: 1
+            maximum: 1000
+            default: 100
+          required: false
+          description: 'The maximum number of items to return. Max value is 500, default is 100'
+        - in: query
+          name: order
+          required: false
+          description: Ordering events by timestamp. Default DESC
+          schema:
+            type: string
+            enum:
+              - ASC
+              - DESC
+            example: DESC
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/ProviderEvent'
+        '400':
+          $ref: '#/components/responses/BadRequest'
+        '401':
+          $ref: '#/components/responses/Unauthorized'
+        '403':
+          $ref: '#/components/responses/Forbidden'
+        '500':
+          $ref: '#/components/responses/InternalServerError'
+        default:
+          $ref: '#/components/responses/DefaultResponse'
   /apikeys:
   /apikeys:
     get:
     get:
       security:
       security:
@@ -3214,6 +3460,24 @@ components:
           * `SSH` - includes both SFTP and SSH commands
           * `SSH` - includes both SFTP and SSH commands
           * `FTP` - plain FTP and FTPES/FTPS
           * `FTP` - plain FTP and FTPES/FTPS
           * `HTTP` - WebClient/REST API
           * `HTTP` - WebClient/REST API
+    EventProtocols:
+      type: string
+      enum:
+        - SSH
+        - SFTP
+        - SCP
+        - FTP
+        - DAV
+        - HTTP
+        - DataRetention
+      description: |
+        Protocols:
+          * `SSH` - SSH commands
+          * `SFTP` - SFTP protocol
+          * `FTP` - plain FTP and FTPES/FTPS
+          * `DAV` - WebDAV
+          * `HTTP` - WebClient/REST API
+          * `DataRetention` - the event is generated by a data retention check
     WebClientOptions:
     WebClientOptions:
       type: string
       type: string
       enum:
       enum:
@@ -3263,6 +3527,39 @@ components:
         - LDAPUser
         - LDAPUser
         - OSUser
         - OSUser
       description: This is an hint for authentication plugins. It is ignored when using SFTPGo internal authentication
       description: This is an hint for authentication plugins. It is ignored when using SFTPGo internal authentication
+    FsEventStatus:
+      type: integer
+      enum:
+        - 1
+        - 2
+        - 3
+      description: >
+        Event status:
+          * `1` - no error
+          * `2` - generic error
+          * `3` - quota exceeded error
+    FsEventAction:
+      type: string
+      enum:
+        - download
+        - upload
+        - delete
+        - rename
+        - mkdir
+        - rmdir
+        - ssh_cmd
+    ProviderEventAction:
+      type: string
+      enum:
+        - add
+        - update
+        - delete
+    ProviderEventObjectType:
+      type: string
+      enum:
+        - user
+        - admin
+        - api_key
     TOTPConfig:
     TOTPConfig:
       type: object
       type: object
       properties:
       properties:
@@ -4254,6 +4551,61 @@ components:
         last_modified:
         last_modified:
           type: string
           type: string
           format: date-time
           format: date-time
+    FsEvent:
+      type: object
+      properties:
+        id:
+          type: string
+        timestamp:
+          type: integer
+          format: int64
+          description: 'unix timestamp in nanoseconds'
+        action:
+          $ref: '#/components/schemas/FsEventAction'
+        username:
+          type: string
+        fs_path:
+          type: string
+        fs_target_path:
+          type: string
+        virtual_path:
+          type: string
+        virtual_target_path:
+          type: string
+        ssh_cmd:
+          type: string
+        file_size:
+          type: integer
+          format: int64
+        status:
+          $ref: '#/components/schemas/FsEventStatus'
+        protocol:
+          $ref: '#/components/schemas/EventProtocols'
+        ip:
+          type: string
+        instance_id:
+          type: string
+    ProviderEvent:
+      type: object
+      properties:
+        id:
+          type: string
+        timestamp:
+          type: integer
+          format: int64
+          description: 'unix timestamp in nanoseconds'
+        action:
+          $ref: '#/components/schemas/ProviderEventAction'
+        username:
+          type: string
+        ip:
+          type: string
+        object_type:
+          $ref: '#/components/schemas/ProviderEventObjectType'
+        object_name:
+          type: string
+        instance_id:
+          type: string
     ApiResponse:
     ApiResponse:
       type: object
       type: object
       properties:
       properties:

+ 4 - 5
sdk/plugin/eventsearcher/eventsearcher.go

@@ -4,7 +4,6 @@ package eventsearcher
 
 
 import (
 import (
 	"context"
 	"context"
-	"time"
 
 
 	"github.com/hashicorp/go-plugin"
 	"github.com/hashicorp/go-plugin"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
@@ -31,10 +30,10 @@ var PluginMap = map[string]plugin.Plugin{
 
 
 // Searcher defines the interface for events search plugins
 // Searcher defines the interface for events search plugins
 type Searcher interface {
 type Searcher interface {
-	SearchFsEvents(startTimestamp, endTimestamp time.Time, action, username, ip, sshCmd, protocol,
-		instanceID, continuationToken string, status, limit int) (string, []byte, error)
-	SearchProviderEvents(startTimestamp, endTimestamp time.Time, action, username, ip, objectType,
-		objectName, instanceID, continuationToken string, limit int) (string, []byte, error)
+	SearchFsEvents(startTimestamp, endTimestamp int64, username, ip, sshCmd string, actions, protocols,
+		instanceIDs, excludeIDs []string, statuses []int32, limit, order int) ([]byte, []string, []string, error)
+	SearchProviderEvents(startTimestamp, endTimestamp int64, username, ip, objectName string,
+		limit, order int, actions, objectTypes, instanceIDs, excludeIDs []string) ([]byte, []string, []string, error)
 }
 }
 
 
 // Plugin defines the implementation to serve/connect to a notifier plugin
 // Plugin defines the implementation to serve/connect to a notifier plugin

+ 46 - 42
sdk/plugin/eventsearcher/grpc.go

@@ -4,13 +4,11 @@ import (
 	"context"
 	"context"
 	"time"
 	"time"
 
 
-	"google.golang.org/protobuf/types/known/timestamppb"
-
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher/proto"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/eventsearcher/proto"
 )
 )
 
 
 const (
 const (
-	rpcTimeout = 30 * time.Second
+	rpcTimeout = 20 * time.Second
 )
 )
 
 
 // GRPCClient is an implementation of Notifier interface that talks over RPC.
 // GRPCClient is an implementation of Notifier interface that talks over RPC.
@@ -19,54 +17,58 @@ type GRPCClient struct {
 }
 }
 
 
 // SearchFsEvents implements the Searcher interface
 // SearchFsEvents implements the Searcher interface
-func (c *GRPCClient) SearchFsEvents(startTimestamp, endTimestamp time.Time, action, username, ip, sshCmd, protocol,
-	instanceID, continuationToken string, status, limit int) (string, []byte, error) {
+func (c *GRPCClient) SearchFsEvents(startTimestamp, endTimestamp int64, username, ip, sshCmd string, actions,
+	protocols, instanceIDs, excludeIDs []string, statuses []int32, limit, order int,
+) ([]byte, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	defer cancel()
 	defer cancel()
 
 
 	resp, err := c.client.SearchFsEvents(ctx, &proto.FsEventsFilter{
 	resp, err := c.client.SearchFsEvents(ctx, &proto.FsEventsFilter{
-		StartTimestamp:    timestamppb.New(startTimestamp),
-		EndTimestamp:      timestamppb.New(endTimestamp),
-		Action:            action,
-		Username:          username,
-		Ip:                ip,
-		SshCmd:            sshCmd,
-		Protocol:          protocol,
-		InstanceId:        instanceID,
-		ContinuationToken: continuationToken,
-		Status:            int32(status),
-		Limit:             int32(limit),
+		StartTimestamp: startTimestamp,
+		EndTimestamp:   endTimestamp,
+		Actions:        actions,
+		Username:       username,
+		Ip:             ip,
+		SshCmd:         sshCmd,
+		Protocols:      protocols,
+		InstanceIds:    instanceIDs,
+		Statuses:       statuses,
+		Limit:          int32(limit),
+		Order:          proto.FsEventsFilter_Order(order),
+		ExcludeIds:     excludeIDs,
 	})
 	})
 
 
 	if err != nil {
 	if err != nil {
-		return "", nil, err
+		return nil, err
 	}
 	}
-	return resp.ContinuationToken, resp.ResponseData, nil
+	return resp.Data, nil
 }
 }
 
 
 // SearchProviderEvents implements the Searcher interface
 // SearchProviderEvents implements the Searcher interface
-func (c *GRPCClient) SearchProviderEvents(startTimestamp, endTimestamp time.Time, action, username, ip, objectType,
-	objectName, instanceID, continuationToken string, limit int) (string, []byte, error) {
+func (c *GRPCClient) SearchProviderEvents(startTimestamp, endTimestamp int64, username, ip, objectName string,
+	limit, order int, actions, objectTypes, instanceIDs, excludeIDs []string,
+) ([]byte, error) {
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	defer cancel()
 	defer cancel()
 
 
 	resp, err := c.client.SearchProviderEvents(ctx, &proto.ProviderEventsFilter{
 	resp, err := c.client.SearchProviderEvents(ctx, &proto.ProviderEventsFilter{
-		StartTimestamp:    timestamppb.New(startTimestamp),
-		EndTimestamp:      timestamppb.New(endTimestamp),
-		Action:            action,
-		Username:          username,
-		Ip:                ip,
-		ObjectType:        objectType,
-		ObjectName:        objectName,
-		InstanceId:        instanceID,
-		ContinuationToken: continuationToken,
-		Limit:             int32(limit),
+		StartTimestamp: startTimestamp,
+		EndTimestamp:   endTimestamp,
+		Actions:        actions,
+		Username:       username,
+		Ip:             ip,
+		ObjectTypes:    objectTypes,
+		ObjectName:     objectName,
+		InstanceIds:    instanceIDs,
+		Limit:          int32(limit),
+		Order:          proto.ProviderEventsFilter_Order(order),
+		ExcludeIds:     excludeIDs,
 	})
 	})
 
 
 	if err != nil {
 	if err != nil {
-		return "", nil, err
+		return nil, err
 	}
 	}
-	return resp.ContinuationToken, resp.ResponseData, nil
+	return resp.Data, nil
 }
 }
 
 
 // GRPCServer defines the gRPC server that GRPCClient talks to.
 // GRPCServer defines the gRPC server that GRPCClient talks to.
@@ -76,24 +78,26 @@ type GRPCServer struct {
 
 
 // SearchFsEvents implement the server side fs events search method
 // SearchFsEvents implement the server side fs events search method
 func (s *GRPCServer) SearchFsEvents(ctx context.Context, req *proto.FsEventsFilter) (*proto.SearchResponse, error) {
 func (s *GRPCServer) SearchFsEvents(ctx context.Context, req *proto.FsEventsFilter) (*proto.SearchResponse, error) {
-	continuationToken, responseData, err := s.Impl.SearchFsEvents(req.StartTimestamp.AsTime(),
-		req.EndTimestamp.AsTime(), req.Action, req.Username, req.Ip, req.SshCmd, req.Protocol, req.InstanceId,
-		req.ContinuationToken, int(req.Status), int(req.Limit))
+	responseData, sameTsAtStart, sameTsAtEnd, err := s.Impl.SearchFsEvents(req.StartTimestamp,
+		req.EndTimestamp, req.Username, req.Ip, req.SshCmd, req.Actions, req.Protocols, req.InstanceIds,
+		req.ExcludeIds, req.Statuses, int(req.Limit), int(req.Order))
 
 
 	return &proto.SearchResponse{
 	return &proto.SearchResponse{
-		ContinuationToken: continuationToken,
-		ResponseData:      responseData,
+		Data:          responseData,
+		SameTsAtStart: sameTsAtStart,
+		SameTsAtEnd:   sameTsAtEnd,
 	}, err
 	}, err
 }
 }
 
 
 // SearchProviderEvents implement the server side provider events search method
 // SearchProviderEvents implement the server side provider events search method
 func (s *GRPCServer) SearchProviderEvents(ctx context.Context, req *proto.ProviderEventsFilter) (*proto.SearchResponse, error) {
 func (s *GRPCServer) SearchProviderEvents(ctx context.Context, req *proto.ProviderEventsFilter) (*proto.SearchResponse, error) {
-	continuationToken, responseData, err := s.Impl.SearchProviderEvents(req.StartTimestamp.AsTime(),
-		req.EndTimestamp.AsTime(), req.Action, req.Username, req.Ip, req.ObjectType, req.ObjectName,
-		req.InstanceId, req.ContinuationToken, int(req.Limit))
+	responseData, sameTsAtStart, sameTsAtEnd, err := s.Impl.SearchProviderEvents(req.StartTimestamp,
+		req.EndTimestamp, req.Username, req.Ip, req.ObjectName, int(req.Limit),
+		int(req.Order), req.Actions, req.ObjectTypes, req.InstanceIds, req.ExcludeIds)
 
 
 	return &proto.SearchResponse{
 	return &proto.SearchResponse{
-		ContinuationToken: continuationToken,
-		ResponseData:      responseData,
+		Data:          responseData,
+		SameTsAtStart: sameTsAtStart,
+		SameTsAtEnd:   sameTsAtEnd,
 	}, err
 	}, err
 }
 }

+ 270 - 152
sdk/plugin/eventsearcher/proto/search.pb.go

@@ -13,7 +13,6 @@ import (
 	status "google.golang.org/grpc/status"
 	status "google.golang.org/grpc/status"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
 	reflect "reflect"
 	reflect "reflect"
 	sync "sync"
 	sync "sync"
 )
 )
@@ -25,22 +24,115 @@ const (
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 )
 
 
+type FsEventsFilter_Order int32
+
+const (
+	FsEventsFilter_DESC FsEventsFilter_Order = 0
+	FsEventsFilter_ASC  FsEventsFilter_Order = 1
+)
+
+// Enum value maps for FsEventsFilter_Order.
+var (
+	FsEventsFilter_Order_name = map[int32]string{
+		0: "DESC",
+		1: "ASC",
+	}
+	FsEventsFilter_Order_value = map[string]int32{
+		"DESC": 0,
+		"ASC":  1,
+	}
+)
+
+func (x FsEventsFilter_Order) Enum() *FsEventsFilter_Order {
+	p := new(FsEventsFilter_Order)
+	*p = x
+	return p
+}
+
+func (x FsEventsFilter_Order) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FsEventsFilter_Order) Descriptor() protoreflect.EnumDescriptor {
+	return file_eventsearcher_proto_search_proto_enumTypes[0].Descriptor()
+}
+
+func (FsEventsFilter_Order) Type() protoreflect.EnumType {
+	return &file_eventsearcher_proto_search_proto_enumTypes[0]
+}
+
+func (x FsEventsFilter_Order) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use FsEventsFilter_Order.Descriptor instead.
+func (FsEventsFilter_Order) EnumDescriptor() ([]byte, []int) {
+	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{0, 0}
+}
+
+type ProviderEventsFilter_Order int32
+
+const (
+	ProviderEventsFilter_DESC ProviderEventsFilter_Order = 0
+	ProviderEventsFilter_ASC  ProviderEventsFilter_Order = 1
+)
+
+// Enum value maps for ProviderEventsFilter_Order.
+var (
+	ProviderEventsFilter_Order_name = map[int32]string{
+		0: "DESC",
+		1: "ASC",
+	}
+	ProviderEventsFilter_Order_value = map[string]int32{
+		"DESC": 0,
+		"ASC":  1,
+	}
+)
+
+func (x ProviderEventsFilter_Order) Enum() *ProviderEventsFilter_Order {
+	p := new(ProviderEventsFilter_Order)
+	*p = x
+	return p
+}
+
+func (x ProviderEventsFilter_Order) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ProviderEventsFilter_Order) Descriptor() protoreflect.EnumDescriptor {
+	return file_eventsearcher_proto_search_proto_enumTypes[1].Descriptor()
+}
+
+func (ProviderEventsFilter_Order) Type() protoreflect.EnumType {
+	return &file_eventsearcher_proto_search_proto_enumTypes[1]
+}
+
+func (x ProviderEventsFilter_Order) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ProviderEventsFilter_Order.Descriptor instead.
+func (ProviderEventsFilter_Order) EnumDescriptor() ([]byte, []int) {
+	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{1, 0}
+}
+
 type FsEventsFilter struct {
 type FsEventsFilter struct {
 	state         protoimpl.MessageState
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 
 
-	StartTimestamp    *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"`
-	EndTimestamp      *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"`
-	Action            string                 `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"`
-	Username          string                 `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
-	Ip                string                 `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"`
-	SshCmd            string                 `protobuf:"bytes,6,opt,name=ssh_cmd,json=sshCmd,proto3" json:"ssh_cmd,omitempty"`
-	Protocol          string                 `protobuf:"bytes,7,opt,name=protocol,proto3" json:"protocol,omitempty"`
-	Status            int32                  `protobuf:"varint,8,opt,name=status,proto3" json:"status,omitempty"`
-	InstanceId        string                 `protobuf:"bytes,9,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"`
-	Limit             int32                  `protobuf:"varint,10,opt,name=limit,proto3" json:"limit,omitempty"`
-	ContinuationToken string                 `protobuf:"bytes,11,opt,name=continuation_token,json=continuationToken,proto3" json:"continuation_token,omitempty"`
+	StartTimestamp int64                `protobuf:"varint,1,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"`
+	EndTimestamp   int64                `protobuf:"varint,2,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"`
+	Actions        []string             `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"`
+	Username       string               `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
+	Ip             string               `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"`
+	SshCmd         string               `protobuf:"bytes,6,opt,name=ssh_cmd,json=sshCmd,proto3" json:"ssh_cmd,omitempty"`
+	Protocols      []string             `protobuf:"bytes,7,rep,name=protocols,proto3" json:"protocols,omitempty"`
+	Statuses       []int32              `protobuf:"varint,8,rep,packed,name=statuses,proto3" json:"statuses,omitempty"`
+	InstanceIds    []string             `protobuf:"bytes,9,rep,name=instance_ids,json=instanceIds,proto3" json:"instance_ids,omitempty"`
+	Limit          int32                `protobuf:"varint,10,opt,name=limit,proto3" json:"limit,omitempty"`
+	ExcludeIds     []string             `protobuf:"bytes,11,rep,name=exclude_ids,json=excludeIds,proto3" json:"exclude_ids,omitempty"`
+	Order          FsEventsFilter_Order `protobuf:"varint,12,opt,name=order,proto3,enum=proto.FsEventsFilter_Order" json:"order,omitempty"`
 }
 }
 
 
 func (x *FsEventsFilter) Reset() {
 func (x *FsEventsFilter) Reset() {
@@ -75,25 +167,25 @@ func (*FsEventsFilter) Descriptor() ([]byte, []int) {
 	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{0}
 	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{0}
 }
 }
 
 
-func (x *FsEventsFilter) GetStartTimestamp() *timestamppb.Timestamp {
+func (x *FsEventsFilter) GetStartTimestamp() int64 {
 	if x != nil {
 	if x != nil {
 		return x.StartTimestamp
 		return x.StartTimestamp
 	}
 	}
-	return nil
+	return 0
 }
 }
 
 
-func (x *FsEventsFilter) GetEndTimestamp() *timestamppb.Timestamp {
+func (x *FsEventsFilter) GetEndTimestamp() int64 {
 	if x != nil {
 	if x != nil {
 		return x.EndTimestamp
 		return x.EndTimestamp
 	}
 	}
-	return nil
+	return 0
 }
 }
 
 
-func (x *FsEventsFilter) GetAction() string {
+func (x *FsEventsFilter) GetActions() []string {
 	if x != nil {
 	if x != nil {
-		return x.Action
+		return x.Actions
 	}
 	}
-	return ""
+	return nil
 }
 }
 
 
 func (x *FsEventsFilter) GetUsername() string {
 func (x *FsEventsFilter) GetUsername() string {
@@ -117,25 +209,25 @@ func (x *FsEventsFilter) GetSshCmd() string {
 	return ""
 	return ""
 }
 }
 
 
-func (x *FsEventsFilter) GetProtocol() string {
+func (x *FsEventsFilter) GetProtocols() []string {
 	if x != nil {
 	if x != nil {
-		return x.Protocol
+		return x.Protocols
 	}
 	}
-	return ""
+	return nil
 }
 }
 
 
-func (x *FsEventsFilter) GetStatus() int32 {
+func (x *FsEventsFilter) GetStatuses() []int32 {
 	if x != nil {
 	if x != nil {
-		return x.Status
+		return x.Statuses
 	}
 	}
-	return 0
+	return nil
 }
 }
 
 
-func (x *FsEventsFilter) GetInstanceId() string {
+func (x *FsEventsFilter) GetInstanceIds() []string {
 	if x != nil {
 	if x != nil {
-		return x.InstanceId
+		return x.InstanceIds
 	}
 	}
-	return ""
+	return nil
 }
 }
 
 
 func (x *FsEventsFilter) GetLimit() int32 {
 func (x *FsEventsFilter) GetLimit() int32 {
@@ -145,11 +237,18 @@ func (x *FsEventsFilter) GetLimit() int32 {
 	return 0
 	return 0
 }
 }
 
 
-func (x *FsEventsFilter) GetContinuationToken() string {
+func (x *FsEventsFilter) GetExcludeIds() []string {
 	if x != nil {
 	if x != nil {
-		return x.ContinuationToken
+		return x.ExcludeIds
 	}
 	}
-	return ""
+	return nil
+}
+
+func (x *FsEventsFilter) GetOrder() FsEventsFilter_Order {
+	if x != nil {
+		return x.Order
+	}
+	return FsEventsFilter_DESC
 }
 }
 
 
 type ProviderEventsFilter struct {
 type ProviderEventsFilter struct {
@@ -157,16 +256,17 @@ type ProviderEventsFilter struct {
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 
 
-	StartTimestamp    *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"`
-	EndTimestamp      *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"`
-	Action            string                 `protobuf:"bytes,3,opt,name=action,proto3" json:"action,omitempty"`
-	Username          string                 `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
-	Ip                string                 `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"`
-	ObjectType        string                 `protobuf:"bytes,6,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"`
-	ObjectName        string                 `protobuf:"bytes,7,opt,name=object_name,json=objectName,proto3" json:"object_name,omitempty"`
-	InstanceId        string                 `protobuf:"bytes,8,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"`
-	Limit             int32                  `protobuf:"varint,9,opt,name=limit,proto3" json:"limit,omitempty"`
-	ContinuationToken string                 `protobuf:"bytes,10,opt,name=continuation_token,json=continuationToken,proto3" json:"continuation_token,omitempty"`
+	StartTimestamp int64                      `protobuf:"varint,1,opt,name=start_timestamp,json=startTimestamp,proto3" json:"start_timestamp,omitempty"`
+	EndTimestamp   int64                      `protobuf:"varint,2,opt,name=end_timestamp,json=endTimestamp,proto3" json:"end_timestamp,omitempty"`
+	Actions        []string                   `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"`
+	Username       string                     `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
+	Ip             string                     `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"`
+	ObjectTypes    []string                   `protobuf:"bytes,6,rep,name=object_types,json=objectTypes,proto3" json:"object_types,omitempty"`
+	ObjectName     string                     `protobuf:"bytes,7,opt,name=object_name,json=objectName,proto3" json:"object_name,omitempty"`
+	InstanceIds    []string                   `protobuf:"bytes,8,rep,name=instance_ids,json=instanceIds,proto3" json:"instance_ids,omitempty"`
+	Limit          int32                      `protobuf:"varint,9,opt,name=limit,proto3" json:"limit,omitempty"`
+	Order          ProviderEventsFilter_Order `protobuf:"varint,10,opt,name=order,proto3,enum=proto.ProviderEventsFilter_Order" json:"order,omitempty"`
+	ExcludeIds     []string                   `protobuf:"bytes,11,rep,name=exclude_ids,json=excludeIds,proto3" json:"exclude_ids,omitempty"`
 }
 }
 
 
 func (x *ProviderEventsFilter) Reset() {
 func (x *ProviderEventsFilter) Reset() {
@@ -201,25 +301,25 @@ func (*ProviderEventsFilter) Descriptor() ([]byte, []int) {
 	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{1}
 	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{1}
 }
 }
 
 
-func (x *ProviderEventsFilter) GetStartTimestamp() *timestamppb.Timestamp {
+func (x *ProviderEventsFilter) GetStartTimestamp() int64 {
 	if x != nil {
 	if x != nil {
 		return x.StartTimestamp
 		return x.StartTimestamp
 	}
 	}
-	return nil
+	return 0
 }
 }
 
 
-func (x *ProviderEventsFilter) GetEndTimestamp() *timestamppb.Timestamp {
+func (x *ProviderEventsFilter) GetEndTimestamp() int64 {
 	if x != nil {
 	if x != nil {
 		return x.EndTimestamp
 		return x.EndTimestamp
 	}
 	}
-	return nil
+	return 0
 }
 }
 
 
-func (x *ProviderEventsFilter) GetAction() string {
+func (x *ProviderEventsFilter) GetActions() []string {
 	if x != nil {
 	if x != nil {
-		return x.Action
+		return x.Actions
 	}
 	}
-	return ""
+	return nil
 }
 }
 
 
 func (x *ProviderEventsFilter) GetUsername() string {
 func (x *ProviderEventsFilter) GetUsername() string {
@@ -236,11 +336,11 @@ func (x *ProviderEventsFilter) GetIp() string {
 	return ""
 	return ""
 }
 }
 
 
-func (x *ProviderEventsFilter) GetObjectType() string {
+func (x *ProviderEventsFilter) GetObjectTypes() []string {
 	if x != nil {
 	if x != nil {
-		return x.ObjectType
+		return x.ObjectTypes
 	}
 	}
-	return ""
+	return nil
 }
 }
 
 
 func (x *ProviderEventsFilter) GetObjectName() string {
 func (x *ProviderEventsFilter) GetObjectName() string {
@@ -250,11 +350,11 @@ func (x *ProviderEventsFilter) GetObjectName() string {
 	return ""
 	return ""
 }
 }
 
 
-func (x *ProviderEventsFilter) GetInstanceId() string {
+func (x *ProviderEventsFilter) GetInstanceIds() []string {
 	if x != nil {
 	if x != nil {
-		return x.InstanceId
+		return x.InstanceIds
 	}
 	}
-	return ""
+	return nil
 }
 }
 
 
 func (x *ProviderEventsFilter) GetLimit() int32 {
 func (x *ProviderEventsFilter) GetLimit() int32 {
@@ -264,11 +364,18 @@ func (x *ProviderEventsFilter) GetLimit() int32 {
 	return 0
 	return 0
 }
 }
 
 
-func (x *ProviderEventsFilter) GetContinuationToken() string {
+func (x *ProviderEventsFilter) GetOrder() ProviderEventsFilter_Order {
 	if x != nil {
 	if x != nil {
-		return x.ContinuationToken
+		return x.Order
 	}
 	}
-	return ""
+	return ProviderEventsFilter_DESC
+}
+
+func (x *ProviderEventsFilter) GetExcludeIds() []string {
+	if x != nil {
+		return x.ExcludeIds
+	}
+	return nil
 }
 }
 
 
 type SearchResponse struct {
 type SearchResponse struct {
@@ -276,8 +383,9 @@ type SearchResponse struct {
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 
 
-	ContinuationToken string `protobuf:"bytes,1,opt,name=continuation_token,json=continuationToken,proto3" json:"continuation_token,omitempty"`
-	ResponseData      []byte `protobuf:"bytes,2,opt,name=response_data,json=responseData,proto3" json:"response_data,omitempty"` // JSON serialized response to return
+	Data          []byte   `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` // JSON serialized response to return
+	SameTsAtStart []string `protobuf:"bytes,2,rep,name=same_ts_at_start,json=sameTsAtStart,proto3" json:"same_ts_at_start,omitempty"`
+	SameTsAtEnd   []string `protobuf:"bytes,3,rep,name=same_ts_at_end,json=sameTsAtEnd,proto3" json:"same_ts_at_end,omitempty"`
 }
 }
 
 
 func (x *SearchResponse) Reset() {
 func (x *SearchResponse) Reset() {
@@ -312,16 +420,23 @@ func (*SearchResponse) Descriptor() ([]byte, []int) {
 	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{2}
 	return file_eventsearcher_proto_search_proto_rawDescGZIP(), []int{2}
 }
 }
 
 
-func (x *SearchResponse) GetContinuationToken() string {
+func (x *SearchResponse) GetData() []byte {
 	if x != nil {
 	if x != nil {
-		return x.ContinuationToken
+		return x.Data
 	}
 	}
-	return ""
+	return nil
+}
+
+func (x *SearchResponse) GetSameTsAtStart() []string {
+	if x != nil {
+		return x.SameTsAtStart
+	}
+	return nil
 }
 }
 
 
-func (x *SearchResponse) GetResponseData() []byte {
+func (x *SearchResponse) GetSameTsAtEnd() []string {
 	if x != nil {
 	if x != nil {
-		return x.ResponseData
+		return x.SameTsAtEnd
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -331,77 +446,79 @@ var File_eventsearcher_proto_search_proto protoreflect.FileDescriptor
 var file_eventsearcher_proto_search_proto_rawDesc = []byte{
 var file_eventsearcher_proto_search_proto_rawDesc = []byte{
 	0x0a, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x65, 0x72, 0x2f,
 	0x0a, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x65, 0x72, 0x2f,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-	0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73,
-	0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8d, 0x03, 0x0a, 0x0e, 0x46,
-	0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x43, 0x0a,
-	0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-	0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-	0x6d, 0x70, 0x12, 0x3f, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
-	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75,
-	0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75,
-	0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x73, 0x73, 0x68, 0x5f, 0x63,
-	0x6d, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x73, 0x68, 0x43, 0x6d, 0x64,
-	0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x07, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06,
-	0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x74,
-	0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
-	0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61,
-	0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a,
-	0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x63,
-	0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65,
-	0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x88, 0x03, 0x0a, 0x14, 0x50,
-	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c,
-	0x74, 0x65, 0x72, 0x12, 0x43, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
-	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54,
-	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x3f, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f,
-	0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-	0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x65, 0x6e, 0x64,
-	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74,
-	0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f,
-	0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a,
-	0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, 0x0a,
-	0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f,
-	0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12,
-	0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x08,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64,
+	0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0, 0x03, 0x0a, 0x0e, 0x46, 0x73,
+	0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f,
+	0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65,
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d,
+	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x65, 0x6e,
+	0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65,
+	0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70,
+	0x12, 0x17, 0x0a, 0x07, 0x73, 0x73, 0x68, 0x5f, 0x63, 0x6d, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x73, 0x73, 0x68, 0x43, 0x6d, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75,
+	0x73, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x05, 0x52, 0x08, 0x73, 0x74, 0x61, 0x74, 0x75,
+	0x73, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f,
+	0x69, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61,
+	0x6e, 0x63, 0x65, 0x49, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18,
+	0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1f, 0x0a, 0x0b,
+	0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x64, 0x73, 0x12, 0x31, 0x0a,
+	0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c,
+	0x74, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,
+	0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x45, 0x53,
+	0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x43, 0x10, 0x01, 0x22, 0x9d, 0x03, 0x0a,
+	0x14, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46,
+	0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74,
+	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e,
+	0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x23,
+	0x0a, 0x0d, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
+	0x61, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a,
+	0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x6f, 0x62, 0x6a,
+	0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
+	0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a,
+	0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x08, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x73,
 	0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52,
 	0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e,
-	0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x64, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x6f, 0x6e, 0x74, 0x69,
-	0x6e, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x72,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x32, 0x96, 0x01, 0x0a, 0x08,
-	0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72,
-	0x63, 0x68, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x2e, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65,
-	0x72, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72,
-	0x63, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
-	0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
-	0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x15, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x42, 0x20, 0x5a, 0x1e, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x6c, 0x75, 0x67,
-	0x69, 0x6e, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x65, 0x72,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x37, 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18,
+	0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74,
+	0x65, 0x72, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12,
+	0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x0b,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x49, 0x64, 0x73,
+	0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x45, 0x53,
+	0x43, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x43, 0x10, 0x01, 0x22, 0x72, 0x0a, 0x0e,
+	0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61,
+	0x74, 0x61, 0x12, 0x27, 0x0a, 0x10, 0x73, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x73, 0x5f, 0x61, 0x74,
+	0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x61,
+	0x6d, 0x65, 0x54, 0x73, 0x41, 0x74, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x0e, 0x73,
+	0x61, 0x6d, 0x65, 0x5f, 0x74, 0x73, 0x5f, 0x61, 0x74, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x65, 0x54, 0x73, 0x41, 0x74, 0x45, 0x6e, 0x64,
+	0x32, 0x96, 0x01, 0x0a, 0x08, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x65, 0x72, 0x12, 0x3e, 0x0a,
+	0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12,
+	0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73,
+	0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53,
+	0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a,
+	0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45,
+	0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72,
+	0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x46, 0x69, 0x6c, 0x74,
+	0x65, 0x72, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63,
+	0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x20, 0x5a, 0x1e, 0x73, 0x64, 0x6b,
+	0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x61,
+	0x72, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
 }
 }
 
 
 var (
 var (
@@ -416,27 +533,27 @@ func file_eventsearcher_proto_search_proto_rawDescGZIP() []byte {
 	return file_eventsearcher_proto_search_proto_rawDescData
 	return file_eventsearcher_proto_search_proto_rawDescData
 }
 }
 
 
+var file_eventsearcher_proto_search_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
 var file_eventsearcher_proto_search_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_eventsearcher_proto_search_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_eventsearcher_proto_search_proto_goTypes = []interface{}{
 var file_eventsearcher_proto_search_proto_goTypes = []interface{}{
-	(*FsEventsFilter)(nil),        // 0: proto.FsEventsFilter
-	(*ProviderEventsFilter)(nil),  // 1: proto.ProviderEventsFilter
-	(*SearchResponse)(nil),        // 2: proto.SearchResponse
-	(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
+	(FsEventsFilter_Order)(0),       // 0: proto.FsEventsFilter.Order
+	(ProviderEventsFilter_Order)(0), // 1: proto.ProviderEventsFilter.Order
+	(*FsEventsFilter)(nil),          // 2: proto.FsEventsFilter
+	(*ProviderEventsFilter)(nil),    // 3: proto.ProviderEventsFilter
+	(*SearchResponse)(nil),          // 4: proto.SearchResponse
 }
 }
 var file_eventsearcher_proto_search_proto_depIdxs = []int32{
 var file_eventsearcher_proto_search_proto_depIdxs = []int32{
-	3, // 0: proto.FsEventsFilter.start_timestamp:type_name -> google.protobuf.Timestamp
-	3, // 1: proto.FsEventsFilter.end_timestamp:type_name -> google.protobuf.Timestamp
-	3, // 2: proto.ProviderEventsFilter.start_timestamp:type_name -> google.protobuf.Timestamp
-	3, // 3: proto.ProviderEventsFilter.end_timestamp:type_name -> google.protobuf.Timestamp
-	0, // 4: proto.Searcher.SearchFsEvents:input_type -> proto.FsEventsFilter
-	1, // 5: proto.Searcher.SearchProviderEvents:input_type -> proto.ProviderEventsFilter
-	2, // 6: proto.Searcher.SearchFsEvents:output_type -> proto.SearchResponse
-	2, // 7: proto.Searcher.SearchProviderEvents:output_type -> proto.SearchResponse
-	6, // [6:8] is the sub-list for method output_type
-	4, // [4:6] is the sub-list for method input_type
-	4, // [4:4] is the sub-list for extension type_name
-	4, // [4:4] is the sub-list for extension extendee
-	0, // [0:4] is the sub-list for field type_name
+	0, // 0: proto.FsEventsFilter.order:type_name -> proto.FsEventsFilter.Order
+	1, // 1: proto.ProviderEventsFilter.order:type_name -> proto.ProviderEventsFilter.Order
+	2, // 2: proto.Searcher.SearchFsEvents:input_type -> proto.FsEventsFilter
+	3, // 3: proto.Searcher.SearchProviderEvents:input_type -> proto.ProviderEventsFilter
+	4, // 4: proto.Searcher.SearchFsEvents:output_type -> proto.SearchResponse
+	4, // 5: proto.Searcher.SearchProviderEvents:output_type -> proto.SearchResponse
+	4, // [4:6] is the sub-list for method output_type
+	2, // [2:4] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
 }
 }
 
 
 func init() { file_eventsearcher_proto_search_proto_init() }
 func init() { file_eventsearcher_proto_search_proto_init() }
@@ -487,13 +604,14 @@ func file_eventsearcher_proto_search_proto_init() {
 		File: protoimpl.DescBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_eventsearcher_proto_search_proto_rawDesc,
 			RawDescriptor: file_eventsearcher_proto_search_proto_rawDesc,
-			NumEnums:      0,
+			NumEnums:      2,
 			NumMessages:   3,
 			NumMessages:   3,
 			NumExtensions: 0,
 			NumExtensions: 0,
 			NumServices:   1,
 			NumServices:   1,
 		},
 		},
 		GoTypes:           file_eventsearcher_proto_search_proto_goTypes,
 		GoTypes:           file_eventsearcher_proto_search_proto_goTypes,
 		DependencyIndexes: file_eventsearcher_proto_search_proto_depIdxs,
 		DependencyIndexes: file_eventsearcher_proto_search_proto_depIdxs,
+		EnumInfos:         file_eventsearcher_proto_search_proto_enumTypes,
 		MessageInfos:      file_eventsearcher_proto_search_proto_msgTypes,
 		MessageInfos:      file_eventsearcher_proto_search_proto_msgTypes,
 	}.Build()
 	}.Build()
 	File_eventsearcher_proto_search_proto = out.File
 	File_eventsearcher_proto_search_proto = out.File

+ 26 - 17
sdk/plugin/eventsearcher/proto/search.proto

@@ -1,40 +1,49 @@
 syntax = "proto3";
 syntax = "proto3";
 package proto;
 package proto;
 
 
-import "google/protobuf/timestamp.proto";
-
 option go_package = "sdk/plugin/eventsearcher/proto";
 option go_package = "sdk/plugin/eventsearcher/proto";
 
 
 message FsEventsFilter {
 message FsEventsFilter {
-    google.protobuf.Timestamp start_timestamp = 1;
-    google.protobuf.Timestamp end_timestamp = 2;
-    string action = 3;
+    int64 start_timestamp = 1;
+    int64 end_timestamp = 2;
+    repeated string actions = 3;
     string username = 4;
     string username = 4;
     string ip = 5;
     string ip = 5;
     string ssh_cmd = 6;
     string ssh_cmd = 6;
-    string protocol = 7;
-    int32 status = 8;
-    string instance_id = 9;
+    repeated string protocols = 7;
+    repeated int32 statuses = 8;
+    repeated string instance_ids = 9;
     int32 limit = 10;
     int32 limit = 10;
-    string continuation_token = 11;
+    repeated string exclude_ids = 11;
+    enum Order {
+        DESC = 0;
+        ASC = 1;
+    }
+    Order order = 12;
 }
 }
 
 
 message ProviderEventsFilter {
 message ProviderEventsFilter {
-    google.protobuf.Timestamp start_timestamp = 1;
-    google.protobuf.Timestamp end_timestamp = 2;
-    string action = 3;
+    int64 start_timestamp = 1;
+    int64 end_timestamp = 2;
+    repeated string actions = 3;
     string username = 4;
     string username = 4;
     string ip = 5;
     string ip = 5;
-    string object_type = 6;
+    repeated string object_types = 6;
     string object_name = 7;
     string object_name = 7;
-    string instance_id = 8;
+    repeated string instance_ids = 8;
     int32 limit = 9;
     int32 limit = 9;
-    string continuation_token = 10;
+    enum Order {
+        DESC = 0;
+        ASC = 1;
+    }
+    Order order = 10;
+    repeated string exclude_ids = 11;
 }
 }
 
 
 message SearchResponse {
 message SearchResponse {
-    string continuation_token = 1;
-    bytes response_data = 2; // JSON serialized response to return
+    bytes data = 1; // JSON serialized response to return
+    repeated string same_ts_at_start = 2;
+    repeated string same_ts_at_end = 3;
 }
 }
 
 
 service Searcher {
 service Searcher {

+ 12 - 13
sdk/plugin/notifier.go

@@ -10,7 +10,6 @@ import (
 
 
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-hclog"
 	"github.com/hashicorp/go-plugin"
 	"github.com/hashicorp/go-plugin"
-	"google.golang.org/protobuf/types/known/timestamppb"
 
 
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/logger"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier"
@@ -43,14 +42,14 @@ type eventsQueue struct {
 	providerEvents []*proto.ProviderEvent
 	providerEvents []*proto.ProviderEvent
 }
 }
 
 
-func (q *eventsQueue) addFsEvent(timestamp time.Time, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip string,
+func (q *eventsQueue) addFsEvent(timestamp int64, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip string,
 	fileSize int64, status int,
 	fileSize int64, status int,
 ) {
 ) {
 	q.Lock()
 	q.Lock()
 	defer q.Unlock()
 	defer q.Unlock()
 
 
 	q.fsEvents = append(q.fsEvents, &proto.FsEvent{
 	q.fsEvents = append(q.fsEvents, &proto.FsEvent{
-		Timestamp:    timestamppb.New(timestamp),
+		Timestamp:    timestamp,
 		Action:       action,
 		Action:       action,
 		Username:     username,
 		Username:     username,
 		FsPath:       fsPath,
 		FsPath:       fsPath,
@@ -63,14 +62,14 @@ func (q *eventsQueue) addFsEvent(timestamp time.Time, action, username, fsPath,
 	})
 	})
 }
 }
 
 
-func (q *eventsQueue) addProviderEvent(timestamp time.Time, action, username, objectType, objectName, ip string,
+func (q *eventsQueue) addProviderEvent(timestamp int64, action, username, objectType, objectName, ip string,
 	objectAsJSON []byte,
 	objectAsJSON []byte,
 ) {
 ) {
 	q.Lock()
 	q.Lock()
 	defer q.Unlock()
 	defer q.Unlock()
 
 
 	q.providerEvents = append(q.providerEvents, &proto.ProviderEvent{
 	q.providerEvents = append(q.providerEvents, &proto.ProviderEvent{
-		Timestamp:  timestamppb.New(timestamp),
+		Timestamp:  timestamp,
 		Action:     action,
 		Action:     action,
 		ObjectType: objectType,
 		ObjectType: objectType,
 		Username:   username,
 		Username:   username,
@@ -191,11 +190,11 @@ func (p *notifierPlugin) initialize() error {
 	return nil
 	return nil
 }
 }
 
 
-func (p *notifierPlugin) canQueueEvent(timestamp time.Time) bool {
+func (p *notifierPlugin) canQueueEvent(timestamp int64) bool {
 	if p.config.NotifierOptions.RetryMaxTime == 0 {
 	if p.config.NotifierOptions.RetryMaxTime == 0 {
 		return false
 		return false
 	}
 	}
-	if time.Now().After(timestamp.Add(time.Duration(p.config.NotifierOptions.RetryMaxTime) * time.Second)) {
+	if time.Now().After(util.GetTimeFromMsecSinceEpoch(timestamp).Add(time.Duration(p.config.NotifierOptions.RetryMaxTime) * time.Second)) {
 		return false
 		return false
 	}
 	}
 	if p.config.NotifierOptions.RetryQueueMaxSize > 0 {
 	if p.config.NotifierOptions.RetryQueueMaxSize > 0 {
@@ -204,7 +203,7 @@ func (p *notifierPlugin) canQueueEvent(timestamp time.Time) bool {
 	return true
 	return true
 }
 }
 
 
-func (p *notifierPlugin) notifyFsAction(timestamp time.Time, action, username, fsPath, fsTargetPath, sshCmd,
+func (p *notifierPlugin) notifyFsAction(timestamp int64, action, username, fsPath, fsTargetPath, sshCmd,
 	protocol, ip, virtualPath, virtualTargetPath string, fileSize int64, errAction error) {
 	protocol, ip, virtualPath, virtualTargetPath string, fileSize int64, errAction error) {
 	if !util.IsStringInSlice(action, p.config.NotifierOptions.FsEvents) {
 	if !util.IsStringInSlice(action, p.config.NotifierOptions.FsEvents) {
 		return
 		return
@@ -220,7 +219,7 @@ func (p *notifierPlugin) notifyFsAction(timestamp time.Time, action, username, f
 	}()
 	}()
 }
 }
 
 
-func (p *notifierPlugin) notifyProviderAction(timestamp time.Time, action, username, objectType, objectName, ip string,
+func (p *notifierPlugin) notifyProviderAction(timestamp int64, action, username, objectType, objectName, ip string,
 	object Renderer,
 	object Renderer,
 ) {
 ) {
 	if !util.IsStringInSlice(action, p.config.NotifierOptions.ProviderEvents) ||
 	if !util.IsStringInSlice(action, p.config.NotifierOptions.ProviderEvents) ||
@@ -238,7 +237,7 @@ func (p *notifierPlugin) notifyProviderAction(timestamp time.Time, action, usern
 	}()
 	}()
 }
 }
 
 
-func (p *notifierPlugin) sendFsEvent(timestamp time.Time, action, username, fsPath, fsTargetPath, sshCmd,
+func (p *notifierPlugin) sendFsEvent(timestamp int64, action, username, fsPath, fsTargetPath, sshCmd,
 	protocol, ip, virtualPath, virtualTargetPath string, fileSize int64, status int) {
 	protocol, ip, virtualPath, virtualTargetPath string, fileSize int64, status int) {
 	if err := p.notifier.NotifyFsEvent(timestamp, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
 	if err := p.notifier.NotifyFsEvent(timestamp, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
 		virtualPath, virtualTargetPath, fileSize, status); err != nil {
 		virtualPath, virtualTargetPath, fileSize, status); err != nil {
@@ -249,7 +248,7 @@ func (p *notifierPlugin) sendFsEvent(timestamp time.Time, action, username, fsPa
 	}
 	}
 }
 }
 
 
-func (p *notifierPlugin) sendProviderEvent(timestamp time.Time, action, username, objectType, objectName, ip string,
+func (p *notifierPlugin) sendProviderEvent(timestamp int64, action, username, objectType, objectName, ip string,
 	objectAsJSON []byte,
 	objectAsJSON []byte,
 ) {
 ) {
 	if err := p.notifier.NotifyProviderEvent(timestamp, action, username, objectType, objectName, ip, objectAsJSON); err != nil {
 	if err := p.notifier.NotifyProviderEvent(timestamp, action, username, objectType, objectName, ip, objectAsJSON); err != nil {
@@ -268,14 +267,14 @@ func (p *notifierPlugin) sendQueuedEvents() {
 	logger.Debug(logSender, "", "check queued events for notifier %#v, events size: %v", p.config.Cmd, queueSize)
 	logger.Debug(logSender, "", "check queued events for notifier %#v, events size: %v", p.config.Cmd, queueSize)
 	fsEv := p.queue.popFsEvent()
 	fsEv := p.queue.popFsEvent()
 	for fsEv != nil {
 	for fsEv != nil {
-		go p.sendFsEvent(fsEv.Timestamp.AsTime(), fsEv.Action, fsEv.Username, fsEv.FsPath, fsEv.FsTargetPath,
+		go p.sendFsEvent(fsEv.Timestamp, fsEv.Action, fsEv.Username, fsEv.FsPath, fsEv.FsTargetPath,
 			fsEv.SshCmd, fsEv.Protocol, fsEv.Ip, fsEv.VirtualPath, fsEv.VirtualTargetPath, fsEv.FileSize, int(fsEv.Status))
 			fsEv.SshCmd, fsEv.Protocol, fsEv.Ip, fsEv.VirtualPath, fsEv.VirtualTargetPath, fsEv.FileSize, int(fsEv.Status))
 		fsEv = p.queue.popFsEvent()
 		fsEv = p.queue.popFsEvent()
 	}
 	}
 
 
 	providerEv := p.queue.popProviderEvent()
 	providerEv := p.queue.popProviderEvent()
 	for providerEv != nil {
 	for providerEv != nil {
-		go p.sendProviderEvent(providerEv.Timestamp.AsTime(), providerEv.Action, providerEv.Username, providerEv.ObjectType,
+		go p.sendProviderEvent(providerEv.Timestamp, providerEv.Action, providerEv.Username, providerEv.ObjectType,
 			providerEv.ObjectName, providerEv.Ip, providerEv.ObjectData)
 			providerEv.ObjectName, providerEv.Ip, providerEv.ObjectData)
 		providerEv = p.queue.popProviderEvent()
 		providerEv = p.queue.popProviderEvent()
 	}
 	}

+ 6 - 7
sdk/plugin/notifier/grpc.go

@@ -5,7 +5,6 @@ import (
 	"time"
 	"time"
 
 
 	"google.golang.org/protobuf/types/known/emptypb"
 	"google.golang.org/protobuf/types/known/emptypb"
-	"google.golang.org/protobuf/types/known/timestamppb"
 
 
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier/proto"
 	"github.com/drakkan/sftpgo/v2/sdk/plugin/notifier/proto"
 )
 )
@@ -20,14 +19,14 @@ type GRPCClient struct {
 }
 }
 
 
 // NotifyFsEvent implements the Notifier interface
 // NotifyFsEvent implements the Notifier interface
-func (c *GRPCClient) NotifyFsEvent(timestamp time.Time, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
+func (c *GRPCClient) NotifyFsEvent(timestamp int64, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
 	virtualPath, virtualTargetPath string, fileSize int64, status int,
 	virtualPath, virtualTargetPath string, fileSize int64, status int,
 ) error {
 ) error {
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	defer cancel()
 	defer cancel()
 
 
 	_, err := c.client.SendFsEvent(ctx, &proto.FsEvent{
 	_, err := c.client.SendFsEvent(ctx, &proto.FsEvent{
-		Timestamp:         timestamppb.New(timestamp),
+		Timestamp:         timestamp,
 		Action:            action,
 		Action:            action,
 		Username:          username,
 		Username:          username,
 		FsPath:            fsPath,
 		FsPath:            fsPath,
@@ -45,12 +44,12 @@ func (c *GRPCClient) NotifyFsEvent(timestamp time.Time, action, username, fsPath
 }
 }
 
 
 // NotifyProviderEvent implements the Notifier interface
 // NotifyProviderEvent implements the Notifier interface
-func (c *GRPCClient) NotifyProviderEvent(timestamp time.Time, action, username, objectType, objectName, ip string, object []byte) error {
+func (c *GRPCClient) NotifyProviderEvent(timestamp int64, action, username, objectType, objectName, ip string, object []byte) error {
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	ctx, cancel := context.WithTimeout(context.Background(), rpcTimeout)
 	defer cancel()
 	defer cancel()
 
 
 	_, err := c.client.SendProviderEvent(ctx, &proto.ProviderEvent{
 	_, err := c.client.SendProviderEvent(ctx, &proto.ProviderEvent{
-		Timestamp:  timestamppb.New(timestamp),
+		Timestamp:  timestamp,
 		Action:     action,
 		Action:     action,
 		ObjectType: objectType,
 		ObjectType: objectType,
 		Username:   username,
 		Username:   username,
@@ -69,14 +68,14 @@ type GRPCServer struct {
 
 
 // SendFsEvent implements the serve side fs notify method
 // SendFsEvent implements the serve side fs notify method
 func (s *GRPCServer) SendFsEvent(ctx context.Context, req *proto.FsEvent) (*emptypb.Empty, error) {
 func (s *GRPCServer) SendFsEvent(ctx context.Context, req *proto.FsEvent) (*emptypb.Empty, error) {
-	err := s.Impl.NotifyFsEvent(req.Timestamp.AsTime(), req.Action, req.Username, req.FsPath, req.FsTargetPath, req.SshCmd,
+	err := s.Impl.NotifyFsEvent(req.Timestamp, req.Action, req.Username, req.FsPath, req.FsTargetPath, req.SshCmd,
 		req.Protocol, req.Ip, req.VirtualPath, req.VirtualTargetPath, req.FileSize, int(req.Status))
 		req.Protocol, req.Ip, req.VirtualPath, req.VirtualTargetPath, req.FileSize, int(req.Status))
 	return &emptypb.Empty{}, err
 	return &emptypb.Empty{}, err
 }
 }
 
 
 // SendProviderEvent implements the serve side provider event notify method
 // SendProviderEvent implements the serve side provider event notify method
 func (s *GRPCServer) SendProviderEvent(ctx context.Context, req *proto.ProviderEvent) (*emptypb.Empty, error) {
 func (s *GRPCServer) SendProviderEvent(ctx context.Context, req *proto.ProviderEvent) (*emptypb.Empty, error) {
-	err := s.Impl.NotifyProviderEvent(req.Timestamp.AsTime(), req.Action, req.Username, req.ObjectType, req.ObjectName,
+	err := s.Impl.NotifyProviderEvent(req.Timestamp, req.Action, req.Username, req.ObjectType, req.ObjectName,
 		req.Ip, req.ObjectData)
 		req.Ip, req.ObjectData)
 	return &emptypb.Empty{}, err
 	return &emptypb.Empty{}, err
 }
 }

+ 2 - 3
sdk/plugin/notifier/notifier.go

@@ -6,7 +6,6 @@ package notifier
 
 
 import (
 import (
 	"context"
 	"context"
-	"time"
 
 
 	"github.com/hashicorp/go-plugin"
 	"github.com/hashicorp/go-plugin"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
@@ -33,9 +32,9 @@ var PluginMap = map[string]plugin.Plugin{
 
 
 // Notifier defines the interface for notifiers plugins
 // Notifier defines the interface for notifiers plugins
 type Notifier interface {
 type Notifier interface {
-	NotifyFsEvent(timestamp time.Time, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
+	NotifyFsEvent(timestamp int64, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
 		virtualPath, virtualTargetPath string, fileSize int64, status int) error
 		virtualPath, virtualTargetPath string, fileSize int64, status int) error
-	NotifyProviderEvent(timestamp time.Time, action, username, objectType, objectName, ip string, object []byte) error
+	NotifyProviderEvent(timestamp int64, action, username, objectType, objectName, ip string, object []byte) error
 }
 }
 
 
 // Plugin defines the implementation to serve/connect to a notifier plugin
 // Plugin defines the implementation to serve/connect to a notifier plugin

+ 84 - 94
sdk/plugin/notifier/proto/notifier.pb.go

@@ -14,7 +14,6 @@ import (
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	emptypb "google.golang.org/protobuf/types/known/emptypb"
 	emptypb "google.golang.org/protobuf/types/known/emptypb"
-	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
 	reflect "reflect"
 	reflect "reflect"
 	sync "sync"
 	sync "sync"
 )
 )
@@ -31,18 +30,18 @@ type FsEvent struct {
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 
 
-	Timestamp         *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
-	Action            string                 `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`
-	Username          string                 `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
-	FsPath            string                 `protobuf:"bytes,4,opt,name=fs_path,json=fsPath,proto3" json:"fs_path,omitempty"`
-	FsTargetPath      string                 `protobuf:"bytes,5,opt,name=fs_target_path,json=fsTargetPath,proto3" json:"fs_target_path,omitempty"`
-	SshCmd            string                 `protobuf:"bytes,6,opt,name=ssh_cmd,json=sshCmd,proto3" json:"ssh_cmd,omitempty"`
-	FileSize          int64                  `protobuf:"varint,7,opt,name=file_size,json=fileSize,proto3" json:"file_size,omitempty"`
-	Protocol          string                 `protobuf:"bytes,8,opt,name=protocol,proto3" json:"protocol,omitempty"`
-	Status            int32                  `protobuf:"varint,9,opt,name=status,proto3" json:"status,omitempty"`
-	Ip                string                 `protobuf:"bytes,10,opt,name=ip,proto3" json:"ip,omitempty"`
-	VirtualPath       string                 `protobuf:"bytes,11,opt,name=virtual_path,json=virtualPath,proto3" json:"virtual_path,omitempty"`
-	VirtualTargetPath string                 `protobuf:"bytes,12,opt,name=virtual_target_path,json=virtualTargetPath,proto3" json:"virtual_target_path,omitempty"`
+	Timestamp         int64  `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+	Action            string `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`
+	Username          string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
+	FsPath            string `protobuf:"bytes,4,opt,name=fs_path,json=fsPath,proto3" json:"fs_path,omitempty"`
+	FsTargetPath      string `protobuf:"bytes,5,opt,name=fs_target_path,json=fsTargetPath,proto3" json:"fs_target_path,omitempty"`
+	SshCmd            string `protobuf:"bytes,6,opt,name=ssh_cmd,json=sshCmd,proto3" json:"ssh_cmd,omitempty"`
+	FileSize          int64  `protobuf:"varint,7,opt,name=file_size,json=fileSize,proto3" json:"file_size,omitempty"`
+	Protocol          string `protobuf:"bytes,8,opt,name=protocol,proto3" json:"protocol,omitempty"`
+	Status            int32  `protobuf:"varint,9,opt,name=status,proto3" json:"status,omitempty"`
+	Ip                string `protobuf:"bytes,10,opt,name=ip,proto3" json:"ip,omitempty"`
+	VirtualPath       string `protobuf:"bytes,11,opt,name=virtual_path,json=virtualPath,proto3" json:"virtual_path,omitempty"`
+	VirtualTargetPath string `protobuf:"bytes,12,opt,name=virtual_target_path,json=virtualTargetPath,proto3" json:"virtual_target_path,omitempty"`
 }
 }
 
 
 func (x *FsEvent) Reset() {
 func (x *FsEvent) Reset() {
@@ -77,11 +76,11 @@ func (*FsEvent) Descriptor() ([]byte, []int) {
 	return file_notifier_proto_notifier_proto_rawDescGZIP(), []int{0}
 	return file_notifier_proto_notifier_proto_rawDescGZIP(), []int{0}
 }
 }
 
 
-func (x *FsEvent) GetTimestamp() *timestamppb.Timestamp {
+func (x *FsEvent) GetTimestamp() int64 {
 	if x != nil {
 	if x != nil {
 		return x.Timestamp
 		return x.Timestamp
 	}
 	}
-	return nil
+	return 0
 }
 }
 
 
 func (x *FsEvent) GetAction() string {
 func (x *FsEvent) GetAction() string {
@@ -166,13 +165,13 @@ type ProviderEvent struct {
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 
 
-	Timestamp  *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
-	Action     string                 `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`
-	ObjectType string                 `protobuf:"bytes,3,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"`
-	Username   string                 `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
-	Ip         string                 `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"`
-	ObjectName string                 `protobuf:"bytes,6,opt,name=object_name,json=objectName,proto3" json:"object_name,omitempty"`
-	ObjectData []byte                 `protobuf:"bytes,7,opt,name=object_data,json=objectData,proto3" json:"object_data,omitempty"` // object JSON serialized
+	Timestamp  int64  `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+	Action     string `protobuf:"bytes,2,opt,name=action,proto3" json:"action,omitempty"`
+	ObjectType string `protobuf:"bytes,3,opt,name=object_type,json=objectType,proto3" json:"object_type,omitempty"`
+	Username   string `protobuf:"bytes,4,opt,name=username,proto3" json:"username,omitempty"`
+	Ip         string `protobuf:"bytes,5,opt,name=ip,proto3" json:"ip,omitempty"`
+	ObjectName string `protobuf:"bytes,6,opt,name=object_name,json=objectName,proto3" json:"object_name,omitempty"`
+	ObjectData []byte `protobuf:"bytes,7,opt,name=object_data,json=objectData,proto3" json:"object_data,omitempty"` // object JSON serialized
 }
 }
 
 
 func (x *ProviderEvent) Reset() {
 func (x *ProviderEvent) Reset() {
@@ -207,11 +206,11 @@ func (*ProviderEvent) Descriptor() ([]byte, []int) {
 	return file_notifier_proto_notifier_proto_rawDescGZIP(), []int{1}
 	return file_notifier_proto_notifier_proto_rawDescGZIP(), []int{1}
 }
 }
 
 
-func (x *ProviderEvent) GetTimestamp() *timestamppb.Timestamp {
+func (x *ProviderEvent) GetTimestamp() int64 {
 	if x != nil {
 	if x != nil {
 		return x.Timestamp
 		return x.Timestamp
 	}
 	}
-	return nil
+	return 0
 }
 }
 
 
 func (x *ProviderEvent) GetAction() string {
 func (x *ProviderEvent) GetAction() string {
@@ -261,61 +260,55 @@ var File_notifier_proto_notifier_proto protoreflect.FileDescriptor
 var file_notifier_proto_notifier_proto_rawDesc = []byte{
 var file_notifier_proto_notifier_proto_rawDesc = []byte{
 	0x0a, 0x1d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x0a, 0x1d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
 	0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
-	0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-	0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x22, 0x83, 0x03, 0x0a, 0x07, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74,
-	0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
-	0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63,
-	0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69,
-	0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17,
-	0x0a, 0x07, 0x66, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x06, 0x66, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x73, 0x5f, 0x74, 0x61,
-	0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x0c, 0x66, 0x73, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x17, 0x0a,
-	0x07, 0x73, 0x73, 0x68, 0x5f, 0x63, 0x6d, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
-	0x73, 0x73, 0x68, 0x43, 0x6d, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x73,
-	0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x53,
-	0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18,
-	0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12,
-	0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52,
-	0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x0a, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x69, 0x72, 0x74, 0x75,
-	0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76,
-	0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x76, 0x69,
-	0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x74,
-	0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c,
-	0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x22, 0xf0, 0x01, 0x0a, 0x0d, 0x50,
-	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09,
-	0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
-	0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f,
-	0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12,
-	0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69,
-	0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x6f,
-	0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b,
-	0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28,
-	0x0c, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x44, 0x61, 0x74, 0x61, 0x32, 0x84, 0x01,
-	0x0a, 0x08, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0b, 0x53, 0x65,
-	0x6e, 0x64, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x2e, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
-	0x79, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
-	0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50,
-	0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x16, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
-	0x6d, 0x70, 0x74, 0x79, 0x42, 0x1b, 0x5a, 0x19, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x6c, 0x75, 0x67,
-	0x69, 0x6e, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x22, 0xe7, 0x02, 0x0a, 0x07, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12,
+	0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a,
+	0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61,
+	0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
+	0x65, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x06, 0x66, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x73,
+	0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0c, 0x66, 0x73, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68,
+	0x12, 0x17, 0x0a, 0x07, 0x73, 0x73, 0x68, 0x5f, 0x63, 0x6d, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x73, 0x73, 0x68, 0x43, 0x6d, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c,
+	0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x66, 0x69,
+	0x6c, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
+	0x6f, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63,
+	0x6f, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x09, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70,
+	0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x69,
+	0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0b, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x2e, 0x0a,
+	0x13, 0x76, 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
+	0x70, 0x61, 0x74, 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x76, 0x69, 0x72, 0x74,
+	0x75, 0x61, 0x6c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x61, 0x74, 0x68, 0x22, 0xd4, 0x01,
+	0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12,
+	0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a,
+	0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61,
+	0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f,
+	0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65,
+	0x63, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,
+	0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4e,
+	0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x61,
+	0x74, 0x61, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
+	0x44, 0x61, 0x74, 0x61, 0x32, 0x84, 0x01, 0x0a, 0x08, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x65,
+	0x72, 0x12, 0x35, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74,
+	0x12, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74,
+	0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x41, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64,
+	0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x45, 0x76,
+	0x65, 0x6e, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x1b, 0x5a, 0x19, 0x73,
+	0x64, 0x6b, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69,
+	0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 }
 
 
 var (
 var (
@@ -332,23 +325,20 @@ func file_notifier_proto_notifier_proto_rawDescGZIP() []byte {
 
 
 var file_notifier_proto_notifier_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_notifier_proto_notifier_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_notifier_proto_notifier_proto_goTypes = []interface{}{
 var file_notifier_proto_notifier_proto_goTypes = []interface{}{
-	(*FsEvent)(nil),               // 0: proto.FsEvent
-	(*ProviderEvent)(nil),         // 1: proto.ProviderEvent
-	(*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp
-	(*emptypb.Empty)(nil),         // 3: google.protobuf.Empty
+	(*FsEvent)(nil),       // 0: proto.FsEvent
+	(*ProviderEvent)(nil), // 1: proto.ProviderEvent
+	(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
 }
 }
 var file_notifier_proto_notifier_proto_depIdxs = []int32{
 var file_notifier_proto_notifier_proto_depIdxs = []int32{
-	2, // 0: proto.FsEvent.timestamp:type_name -> google.protobuf.Timestamp
-	2, // 1: proto.ProviderEvent.timestamp:type_name -> google.protobuf.Timestamp
-	0, // 2: proto.Notifier.SendFsEvent:input_type -> proto.FsEvent
-	1, // 3: proto.Notifier.SendProviderEvent:input_type -> proto.ProviderEvent
-	3, // 4: proto.Notifier.SendFsEvent:output_type -> google.protobuf.Empty
-	3, // 5: proto.Notifier.SendProviderEvent:output_type -> google.protobuf.Empty
-	4, // [4:6] is the sub-list for method output_type
-	2, // [2:4] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	0, // 0: proto.Notifier.SendFsEvent:input_type -> proto.FsEvent
+	1, // 1: proto.Notifier.SendProviderEvent:input_type -> proto.ProviderEvent
+	2, // 2: proto.Notifier.SendFsEvent:output_type -> google.protobuf.Empty
+	2, // 3: proto.Notifier.SendProviderEvent:output_type -> google.protobuf.Empty
+	2, // [2:4] is the sub-list for method output_type
+	0, // [0:2] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
 }
 }
 
 
 func init() { file_notifier_proto_notifier_proto_init() }
 func init() { file_notifier_proto_notifier_proto_init() }

+ 2 - 3
sdk/plugin/notifier/proto/notifier.proto

@@ -1,13 +1,12 @@
 syntax = "proto3";
 syntax = "proto3";
 package proto;
 package proto;
 
 
-import "google/protobuf/timestamp.proto";
 import "google/protobuf/empty.proto";
 import "google/protobuf/empty.proto";
 
 
 option go_package = "sdk/plugin/notifier/proto";
 option go_package = "sdk/plugin/notifier/proto";
 
 
 message FsEvent {
 message FsEvent {
-    google.protobuf.Timestamp timestamp = 1;
+    int64 timestamp = 1;
     string action = 2;
     string action = 2;
     string username = 3;
     string username = 3;
     string fs_path = 4;
     string fs_path = 4;
@@ -22,7 +21,7 @@ message FsEvent {
 }
 }
 
 
 message ProviderEvent {
 message ProviderEvent {
-    google.protobuf.Timestamp timestamp = 1;
+    int64 timestamp = 1;
     string action = 2;
     string action = 2;
     string object_type = 3;
     string object_type = 3;
     string username = 4;
     string username = 4;

+ 16 - 15
sdk/plugin/plugin.go

@@ -28,7 +28,8 @@ var (
 	// Handler defines the plugins manager
 	// Handler defines the plugins manager
 	Handler         Manager
 	Handler         Manager
 	pluginsLogLevel = hclog.Debug
 	pluginsLogLevel = hclog.Debug
-	errNoSearcher   = errors.New("no events searcher plugin defined")
+	// ErrNoSearcher defines the error to return for events searches if no plugin is configured
+	ErrNoSearcher = errors.New("no events searcher plugin defined")
 )
 )
 
 
 // Renderer defines the interface for generic objects rendering
 // Renderer defines the interface for generic objects rendering
@@ -184,7 +185,7 @@ func (m *Manager) validateConfigs() error {
 }
 }
 
 
 // NotifyFsEvent sends the fs event notifications using any defined notifier plugins
 // NotifyFsEvent sends the fs event notifications using any defined notifier plugins
-func (m *Manager) NotifyFsEvent(timestamp time.Time, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
+func (m *Manager) NotifyFsEvent(timestamp int64, action, username, fsPath, fsTargetPath, sshCmd, protocol, ip,
 	virtualPath, virtualTargetPath string, fileSize int64, err error,
 	virtualPath, virtualTargetPath string, fileSize int64, err error,
 ) {
 ) {
 	m.notifLock.RLock()
 	m.notifLock.RLock()
@@ -197,7 +198,7 @@ func (m *Manager) NotifyFsEvent(timestamp time.Time, action, username, fsPath, f
 }
 }
 
 
 // NotifyProviderEvent sends the provider event notifications using any defined notifier plugins
 // NotifyProviderEvent sends the provider event notifications using any defined notifier plugins
-func (m *Manager) NotifyProviderEvent(timestamp time.Time, action, username, objectType, objectName, ip string,
+func (m *Manager) NotifyProviderEvent(timestamp int64, action, username, objectType, objectName, ip string,
 	object Renderer,
 	object Renderer,
 ) {
 ) {
 	m.notifLock.RLock()
 	m.notifLock.RLock()
@@ -210,34 +211,34 @@ func (m *Manager) NotifyProviderEvent(timestamp time.Time, action, username, obj
 
 
 // SearchFsEvents returns the filesystem events matching the specified filter and a continuation token
 // SearchFsEvents returns the filesystem events matching the specified filter and a continuation token
 // to use for cursor based pagination
 // to use for cursor based pagination
-func (m *Manager) SearchFsEvents(startTimestamp, endTimestamp time.Time, action, username, ip, sshCmd, protocol,
-	instanceID, continuationToken string, status, limit int) (string, []byte, error,
-) {
+func (m *Manager) SearchFsEvents(startTimestamp, endTimestamp int64, username, ip, sshCmd string, actions,
+	protocols, instanceIDs, excludeIDs []string, statuses []int32, limit, order int,
+) ([]byte, []string, []string, error) {
 	if !m.hasSearcher {
 	if !m.hasSearcher {
-		return "", nil, errNoSearcher
+		return nil, nil, nil, ErrNoSearcher
 	}
 	}
 	m.searcherLock.RLock()
 	m.searcherLock.RLock()
 	plugin := m.searcher
 	plugin := m.searcher
 	m.searcherLock.RUnlock()
 	m.searcherLock.RUnlock()
 
 
-	return plugin.searchear.SearchFsEvents(startTimestamp, endTimestamp, action, username, ip, sshCmd, protocol,
-		instanceID, continuationToken, status, limit)
+	return plugin.searchear.SearchFsEvents(startTimestamp, endTimestamp, username, ip, sshCmd, actions, protocols,
+		instanceIDs, excludeIDs, statuses, limit, order)
 }
 }
 
 
 // SearchProviderEvents returns the provider events matching the specified filter and a continuation token
 // SearchProviderEvents returns the provider events matching the specified filter and a continuation token
 // to use for cursor based pagination
 // to use for cursor based pagination
-func (m *Manager) SearchProviderEvents(startTimestamp, endTimestamp time.Time, action, username, ip, objectType,
-	objectName, instanceID, continuationToken string, limit int,
-) (string, []byte, error) {
+func (m *Manager) SearchProviderEvents(startTimestamp, endTimestamp int64, username, ip, objectName string,
+	limit, order int, actions, objectTypes, instanceIDs, excludeIDs []string,
+) ([]byte, []string, []string, error) {
 	if !m.hasSearcher {
 	if !m.hasSearcher {
-		return "", nil, errNoSearcher
+		return nil, nil, nil, ErrNoSearcher
 	}
 	}
 	m.searcherLock.RLock()
 	m.searcherLock.RLock()
 	plugin := m.searcher
 	plugin := m.searcher
 	m.searcherLock.RUnlock()
 	m.searcherLock.RUnlock()
 
 
-	return plugin.searchear.SearchProviderEvents(startTimestamp, endTimestamp, action, username, ip, objectType, objectName,
-		instanceID, continuationToken, limit)
+	return plugin.searchear.SearchProviderEvents(startTimestamp, endTimestamp, username, ip, objectName, limit,
+		order, actions, objectTypes, instanceIDs, excludeIDs)
 }
 }
 
 
 func (m *Manager) kmsEncrypt(secret kms.BaseSecret, url string, masterKey string, kmsID int) (string, string, int32, error) {
 func (m *Manager) kmsEncrypt(secret kms.BaseSecret, url string, masterKey string, kmsID int) (string, string, int32, error) {