浏览代码

EventManager: add datetime placeholder

Signed-off-by: Nicola Murino <[email protected]>
Nicola Murino 1 年之前
父节点
当前提交
472bfac5fe

+ 1 - 1
internal/acme/acme.go

@@ -671,7 +671,7 @@ func (c *Configuration) notifyCertificateRenewal(domain string, err error) {
 	params := common.EventParams{
 	params := common.EventParams{
 		Name:      domain,
 		Name:      domain,
 		Event:     "Certificate renewal",
 		Event:     "Certificate renewal",
-		Timestamp: time.Now().UnixNano(),
+		Timestamp: time.Now(),
 	}
 	}
 	if err != nil {
 	if err != nil {
 		params.Status = 2
 		params.Status = 2

+ 8 - 5
internal/common/actions.go

@@ -91,8 +91,9 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
 	if !hasHook && !hasNotifiersPlugin && !hasRules {
 	if !hasHook && !hasNotifiersPlugin && !hasRules {
 		return 0, nil
 		return 0, nil
 	}
 	}
+	dateTime := time.Now()
 	event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
 	event = newActionNotification(&conn.User, operation, filePath, virtualPath, "", "", "",
-		conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, nil)
+		conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, openFlags, conn.getNotificationStatus(nil), 0, dateTime, nil)
 	if hasNotifiersPlugin {
 	if hasNotifiersPlugin {
 		plugin.Handler.NotifyFsEvent(event)
 		plugin.Handler.NotifyFsEvent(event)
 	}
 	}
@@ -112,7 +113,7 @@ func ExecutePreAction(conn *BaseConnection, operation, filePath, virtualPath str
 			Protocol:          event.Protocol,
 			Protocol:          event.Protocol,
 			IP:                event.IP,
 			IP:                event.IP,
 			Role:              event.Role,
 			Role:              event.Role,
-			Timestamp:         event.Timestamp,
+			Timestamp:         dateTime,
 			Email:             conn.User.Email,
 			Email:             conn.User.Email,
 			Object:            nil,
 			Object:            nil,
 		}
 		}
@@ -137,8 +138,9 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
 	if !hasHook && !hasNotifiersPlugin && !hasRules {
 	if !hasHook && !hasNotifiersPlugin && !hasRules {
 		return nil
 		return nil
 	}
 	}
+	dateTime := time.Now()
 	notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
 	notification := newActionNotification(&conn.User, operation, filePath, virtualPath, target, virtualTarget, sshCmd,
-		conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, metadata)
+		conn.protocol, conn.GetRemoteIP(), conn.ID, fileSize, 0, conn.getNotificationStatus(err), elapsed, dateTime, metadata)
 	if hasNotifiersPlugin {
 	if hasNotifiersPlugin {
 		plugin.Handler.NotifyFsEvent(notification)
 		plugin.Handler.NotifyFsEvent(notification)
 	}
 	}
@@ -159,7 +161,7 @@ func ExecuteActionNotification(conn *BaseConnection, operation, filePath, virtua
 			Protocol:          notification.Protocol,
 			Protocol:          notification.Protocol,
 			IP:                notification.IP,
 			IP:                notification.IP,
 			Role:              notification.Role,
 			Role:              notification.Role,
-			Timestamp:         notification.Timestamp,
+			Timestamp:         dateTime,
 			Email:             conn.User.Email,
 			Email:             conn.User.Email,
 			Object:            nil,
 			Object:            nil,
 			Metadata:          metadata,
 			Metadata:          metadata,
@@ -197,6 +199,7 @@ func newActionNotification(
 	operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
 	operation, filePath, virtualPath, target, virtualTarget, sshCmd, protocol, ip, sessionID string,
 	fileSize int64,
 	fileSize int64,
 	openFlags, status int, elapsed int64,
 	openFlags, status int, elapsed int64,
+	datetime time.Time,
 	metadata map[string]string,
 	metadata map[string]string,
 ) *notifier.FsEvent {
 ) *notifier.FsEvent {
 	var bucket, endpoint string
 	var bucket, endpoint string
@@ -238,7 +241,7 @@ func newActionNotification(
 		SessionID:         sessionID,
 		SessionID:         sessionID,
 		OpenFlags:         openFlags,
 		OpenFlags:         openFlags,
 		Role:              user.Role,
 		Role:              user.Role,
-		Timestamp:         time.Now().UnixNano(),
+		Timestamp:         datetime.UnixNano(),
 		Elapsed:           elapsed,
 		Elapsed:           elapsed,
 		Metadata:          metadata,
 		Metadata:          metadata,
 	}
 	}

+ 12 - 11
internal/common/actions_test.go

@@ -22,6 +22,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"runtime"
 	"runtime"
 	"testing"
 	"testing"
+	"time"
 
 
 	"github.com/lithammer/shortuuid/v3"
 	"github.com/lithammer/shortuuid/v3"
 	"github.com/rs/xid"
 	"github.com/rs/xid"
@@ -71,7 +72,7 @@ func TestNewActionNotification(t *testing.T) {
 	c := NewBaseConnection("id", ProtocolSSH, "", "", user)
 	c := NewBaseConnection("id", ProtocolSSH, "", "", user)
 	sessionID := xid.New().String()
 	sessionID := xid.New().String()
 	a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
 	a := newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
-		123, 0, c.getNotificationStatus(errors.New("fake error")), 0, nil)
+		123, 0, c.getNotificationStatus(errors.New("fake error")), 0, time.Now(), nil)
 	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))
@@ -79,38 +80,38 @@ func TestNewActionNotification(t *testing.T) {
 
 
 	user.FsConfig.Provider = sdk.S3FilesystemProvider
 	user.FsConfig.Provider = sdk.S3FilesystemProvider
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
-		123, 0, c.getNotificationStatus(nil), 0, nil)
+		123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
 	assert.Equal(t, "s3bucket", a.Bucket)
 	assert.Equal(t, "s3bucket", a.Bucket)
 	assert.Equal(t, "endpoint", a.Endpoint)
 	assert.Equal(t, "endpoint", a.Endpoint)
 	assert.Equal(t, 1, a.Status)
 	assert.Equal(t, 1, a.Status)
 
 
 	user.FsConfig.Provider = sdk.GCSFilesystemProvider
 	user.FsConfig.Provider = sdk.GCSFilesystemProvider
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
-		123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, nil)
+		123, 0, c.getNotificationStatus(ErrQuotaExceeded), 0, time.Now(), nil)
 	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, 3, a.Status)
 	assert.Equal(t, 3, a.Status)
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
-		123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, nil)
+		123, 0, c.getNotificationStatus(fmt.Errorf("wrapper quota error: %w", ErrQuotaExceeded)), 0, time.Now(), nil)
 	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, 3, a.Status)
 	assert.Equal(t, 3, a.Status)
 
 
 	user.FsConfig.Provider = sdk.HTTPFilesystemProvider
 	user.FsConfig.Provider = sdk.HTTPFilesystemProvider
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSSH, "", sessionID,
-		123, 0, c.getNotificationStatus(nil), 0, nil)
+		123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
 	assert.Equal(t, "httpendpoint", a.Endpoint)
 	assert.Equal(t, "httpendpoint", a.Endpoint)
 	assert.Equal(t, 1, a.Status)
 	assert.Equal(t, 1, a.Status)
 
 
 	user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
 	user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
-		123, 0, c.getNotificationStatus(nil), 0, nil)
+		123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
 	assert.Equal(t, "azcontainer", a.Bucket)
 	assert.Equal(t, "azcontainer", a.Bucket)
 	assert.Equal(t, "azendpoint", a.Endpoint)
 	assert.Equal(t, "azendpoint", a.Endpoint)
 	assert.Equal(t, 1, a.Status)
 	assert.Equal(t, 1, a.Status)
 
 
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSCP, "", sessionID,
-		123, os.O_APPEND, c.getNotificationStatus(nil), 0, nil)
+		123, os.O_APPEND, c.getNotificationStatus(nil), 0, time.Now(), nil)
 	assert.Equal(t, "azcontainer", a.Bucket)
 	assert.Equal(t, "azcontainer", a.Bucket)
 	assert.Equal(t, "azendpoint", a.Endpoint)
 	assert.Equal(t, "azendpoint", a.Endpoint)
 	assert.Equal(t, 1, a.Status)
 	assert.Equal(t, 1, a.Status)
@@ -118,7 +119,7 @@ func TestNewActionNotification(t *testing.T) {
 
 
 	user.FsConfig.Provider = sdk.SFTPFilesystemProvider
 	user.FsConfig.Provider = sdk.SFTPFilesystemProvider
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
 	a = newActionNotification(&user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
-		123, 0, c.getNotificationStatus(nil), 0, nil)
+		123, 0, c.getNotificationStatus(nil), 0, time.Now(), nil)
 	assert.Equal(t, "sftpendpoint", a.Endpoint)
 	assert.Equal(t, "sftpendpoint", a.Endpoint)
 }
 }
 
 
@@ -135,7 +136,7 @@ func TestActionHTTP(t *testing.T) {
 		},
 		},
 	}
 	}
 	a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
 	a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "",
-		xid.New().String(), 123, 0, 1, 0, nil)
+		xid.New().String(), 123, 0, 1, 0, time.Now(), nil)
 	status, err := actionHandler.Handle(a)
 	status, err := actionHandler.Handle(a)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, status)
 	assert.Equal(t, 1, status)
@@ -175,7 +176,7 @@ func TestActionCMD(t *testing.T) {
 	}
 	}
 	sessionID := shortuuid.New()
 	sessionID := shortuuid.New()
 	a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
 	a := newActionNotification(user, operationDownload, "path", "vpath", "target", "", "", ProtocolSFTP, "", sessionID,
-		123, 0, 1, 0, map[string]string{"key": "value"})
+		123, 0, 1, 0, time.Now(), map[string]string{"key": "value"})
 	status, err := actionHandler.Handle(a)
 	status, err := actionHandler.Handle(a)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, status)
 	assert.Equal(t, 1, status)
@@ -208,7 +209,7 @@ func TestWrongActions(t *testing.T) {
 	}
 	}
 
 
 	a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
 	a := newActionNotification(user, operationUpload, "", "", "", "", "", ProtocolSFTP, "", xid.New().String(),
-		123, 0, 1, 0, nil)
+		123, 0, 1, 0, time.Now(), nil)
 	status, err := actionHandler.Handle(a)
 	status, err := actionHandler.Handle(a)
 	assert.Error(t, err, "action with bad command must fail")
 	assert.Error(t, err, "action with bad command must fail")
 	assert.Equal(t, 1, status)
 	assert.Equal(t, 1, status)

+ 1 - 1
internal/common/defenderdb.go

@@ -110,7 +110,7 @@ func (d *dbDefender) AddEvent(ip, protocol string, event HostEvent) bool {
 			eventManager.handleIPBlockedEvent(EventParams{
 			eventManager.handleIPBlockedEvent(EventParams{
 				Event:     ipBlockedEventName,
 				Event:     ipBlockedEventName,
 				IP:        ip,
 				IP:        ip,
-				Timestamp: time.Now().UnixNano(),
+				Timestamp: time.Now(),
 				Status:    1,
 				Status:    1,
 			})
 			})
 		}
 		}

+ 1 - 1
internal/common/defendermem.go

@@ -218,7 +218,7 @@ func (d *memoryDefender) AddEvent(ip, protocol string, event HostEvent) bool {
 			eventManager.handleIPBlockedEvent(EventParams{
 			eventManager.handleIPBlockedEvent(EventParams{
 				Event:     ipBlockedEventName,
 				Event:     ipBlockedEventName,
 				IP:        ip,
 				IP:        ip,
-				Timestamp: time.Now().UnixNano(),
+				Timestamp: time.Now(),
 				Status:    1,
 				Status:    1,
 			})
 			})
 		} else {
 		} else {

+ 6 - 4
internal/common/eventmanager.go

@@ -58,6 +58,7 @@ const (
 	maxAttachmentsSize       = int64(10 * 1024 * 1024)
 	maxAttachmentsSize       = int64(10 * 1024 * 1024)
 	objDataPlaceholder       = "{{ObjectData}}"
 	objDataPlaceholder       = "{{ObjectData}}"
 	objDataPlaceholderString = "{{ObjectDataString}}"
 	objDataPlaceholderString = "{{ObjectDataString}}"
+	dateTimeMillisFormat     = "2006-01-02T15:04:05.000"
 )
 )
 
 
 // Supported IDP login events
 // Supported IDP login events
@@ -89,7 +90,7 @@ func init() {
 				ObjectType: objectType,
 				ObjectType: objectType,
 				IP:         ip,
 				IP:         ip,
 				Role:       role,
 				Role:       role,
-				Timestamp:  time.Now().UnixNano(),
+				Timestamp:  time.Now(),
 				Object:     object,
 				Object:     object,
 			}
 			}
 			if u, ok := object.(*dataprovider.User); ok {
 			if u, ok := object.(*dataprovider.User); ok {
@@ -556,7 +557,7 @@ type EventParams struct {
 	IP                    string
 	IP                    string
 	Role                  string
 	Role                  string
 	Email                 string
 	Email                 string
-	Timestamp             int64
+	Timestamp             time.Time
 	UID                   string
 	UID                   string
 	IDPCustomFields       *map[string]string
 	IDPCustomFields       *map[string]string
 	Object                plugin.Renderer
 	Object                plugin.Renderer
@@ -640,7 +641,7 @@ func (p *EventParams) setBackupParams(backupPath string) {
 	p.FsPath = backupPath
 	p.FsPath = backupPath
 	p.ObjectName = filepath.Base(backupPath)
 	p.ObjectName = filepath.Base(backupPath)
 	p.VirtualPath = "/" + p.ObjectName
 	p.VirtualPath = "/" + p.ObjectName
-	p.Timestamp = time.Now().UnixNano()
+	p.Timestamp = time.Now()
 	info, err := os.Stat(backupPath)
 	info, err := os.Stat(backupPath)
 	if err == nil {
 	if err == nil {
 		p.FileSize = info.Size()
 		p.FileSize = info.Size()
@@ -790,7 +791,8 @@ func (p *EventParams) getStringReplacements(addObjectData, jsonEscaped bool) []s
 		"{{IP}}", p.IP,
 		"{{IP}}", p.IP,
 		"{{Role}}", p.getStringReplacement(p.Role, jsonEscaped),
 		"{{Role}}", p.getStringReplacement(p.Role, jsonEscaped),
 		"{{Email}}", p.getStringReplacement(p.Email, jsonEscaped),
 		"{{Email}}", p.getStringReplacement(p.Email, jsonEscaped),
-		"{{Timestamp}}", strconv.FormatInt(p.Timestamp, 10),
+		"{{Timestamp}}", strconv.FormatInt(p.Timestamp.UnixNano(), 10),
+		"{{DateTime}}", p.Timestamp.UTC().Format(dateTimeMillisFormat),
 		"{{StatusString}}", p.getStatusString(),
 		"{{StatusString}}", p.getStatusString(),
 		"{{UID}}", p.getStringReplacement(p.UID, jsonEscaped),
 		"{{UID}}", p.getStringReplacement(p.UID, jsonEscaped),
 		"{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped),
 		"{{Ext}}", p.getStringReplacement(p.Extension, jsonEscaped),

+ 11 - 0
internal/common/eventmanager_test.go

@@ -800,6 +800,17 @@ func TestEventManagerErrors(t *testing.T) {
 	stopEventScheduler()
 	stopEventScheduler()
 }
 }
 
 
+func TestDateTimePlaceholder(t *testing.T) {
+	dateTime := time.Now()
+	params := EventParams{
+		Timestamp: dateTime,
+	}
+	replacements := params.getStringReplacements(false, false)
+	r := strings.NewReplacer(replacements...)
+	res := r.Replace("{{DateTime}}")
+	assert.Equal(t, dateTime.UTC().Format(dateTimeMillisFormat), res)
+}
+
 func TestEventRuleActions(t *testing.T) {
 func TestEventRuleActions(t *testing.T) {
 	actionName := "test rule action"
 	actionName := "test rule action"
 	action := dataprovider.BaseEventAction{
 	action := dataprovider.BaseEventAction{

+ 7 - 5
internal/common/protocol_test.go

@@ -5400,7 +5400,7 @@ func TestBackupAsAttachment(t *testing.T) {
 
 
 	common.HandleCertificateEvent(common.EventParams{
 	common.HandleCertificateEvent(common.EventParams{
 		Name:      "example.com",
 		Name:      "example.com",
-		Timestamp: time.Now().UnixNano(),
+		Timestamp: time.Now(),
 		Status:    1,
 		Status:    1,
 		Event:     renewalEvent,
 		Event:     renewalEvent,
 	})
 	})
@@ -7077,7 +7077,7 @@ func TestEventRuleCertificate(t *testing.T) {
 				Recipients:  []string{"[email protected]"},
 				Recipients:  []string{"[email protected]"},
 				Subject:     `"{{Event}} {{StatusString}}"`,
 				Subject:     `"{{Event}} {{StatusString}}"`,
 				ContentType: 0,
 				ContentType: 0,
-				Body:        "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}}",
+				Body:        "Domain: {{Name}} Timestamp: {{Timestamp}} {{ErrorString}} Date time: {{DateTime}}",
 			},
 			},
 		},
 		},
 	}
 	}
@@ -7132,7 +7132,7 @@ func TestEventRuleCertificate(t *testing.T) {
 
 
 	common.HandleCertificateEvent(common.EventParams{
 	common.HandleCertificateEvent(common.EventParams{
 		Name:      "example.com",
 		Name:      "example.com",
-		Timestamp: time.Now().UnixNano(),
+		Timestamp: time.Now(),
 		Status:    1,
 		Status:    1,
 		Event:     renewalEvent,
 		Event:     renewalEvent,
 	})
 	})
@@ -7147,9 +7147,10 @@ func TestEventRuleCertificate(t *testing.T) {
 	assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
 	assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
 
 
 	lastReceivedEmail.reset()
 	lastReceivedEmail.reset()
+	dateTime := time.Now()
 	params := common.EventParams{
 	params := common.EventParams{
 		Name:      "example.com",
 		Name:      "example.com",
-		Timestamp: time.Now().UnixNano(),
+		Timestamp: dateTime,
 		Status:    2,
 		Status:    2,
 		Event:     renewalEvent,
 		Event:     renewalEvent,
 	}
 	}
@@ -7164,6 +7165,7 @@ func TestEventRuleCertificate(t *testing.T) {
 	assert.True(t, util.Contains(email.To, "[email protected]"))
 	assert.True(t, util.Contains(email.To, "[email protected]"))
 	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
 	assert.Contains(t, email.Data, fmt.Sprintf(`Subject: "%s KO"`, renewalEvent))
 	assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
 	assert.Contains(t, email.Data, `Domain: example.com Timestamp`)
+	assert.Contains(t, email.Data, dateTime.UTC().Format("2006-01-02T15:04:05.000"))
 	assert.Contains(t, email.Data, errRenew.Error())
 	assert.Contains(t, email.Data, errRenew.Error())
 
 
 	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
 	_, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
@@ -7177,7 +7179,7 @@ func TestEventRuleCertificate(t *testing.T) {
 	// ignored no more certificate rules
 	// ignored no more certificate rules
 	common.HandleCertificateEvent(common.EventParams{
 	common.HandleCertificateEvent(common.EventParams{
 		Name:      "example.com",
 		Name:      "example.com",
-		Timestamp: time.Now().UnixNano(),
+		Timestamp: time.Now(),
 		Status:    1,
 		Status:    1,
 		Event:     renewalEvent,
 		Event:     renewalEvent,
 	})
 	})

+ 1 - 1
internal/httpd/oidc.go

@@ -407,7 +407,7 @@ func (t *oidcToken) getUser(r *http.Request) error {
 		Name:      t.Username,
 		Name:      t.Username,
 		IP:        ipAddr,
 		IP:        ipAddr,
 		Protocol:  common.ProtocolOIDC,
 		Protocol:  common.ProtocolOIDC,
-		Timestamp: time.Now().UnixNano(),
+		Timestamp: time.Now(),
 		Status:    1,
 		Status:    1,
 	}
 	}
 	if t.isAdmin() {
 	if t.isAdmin() {

+ 1 - 0
static/locales/en/translation.json

@@ -1041,6 +1041,7 @@
             "ip": "Client IP address",
             "ip": "Client IP address",
             "role": "User or admin role",
             "role": "User or admin role",
             "timestamp": "Event timestamp as nanoseconds since epoch",
             "timestamp": "Event timestamp as nanoseconds since epoch",
+            "datetime": "Event timestamp formatted as YYYY-MM-DDTHH:MM:SS.ZZZ",
             "email": "For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases",
             "email": "For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases",
             "object_data": "Provider object data serialized as JSON with sensitive fields removed",
             "object_data": "Provider object data serialized as JSON with sensitive fields removed",
             "object_data_string": "Provider object data as JSON escaped string with sensitive fields removed",
             "object_data_string": "Provider object data as JSON escaped string with sensitive fields removed",

+ 1 - 0
static/locales/it/translation.json

@@ -1041,6 +1041,7 @@
             "ip": "Indirizzo IP del client",
             "ip": "Indirizzo IP del client",
             "role": "Ruolo dell'utente o dell'amministratore",
             "role": "Ruolo dell'utente o dell'amministratore",
             "timestamp": "Timestamp dell'evento in nanosecondi dall'epoch time",
             "timestamp": "Timestamp dell'evento in nanosecondi dall'epoch time",
+            "datetime": "Timestamp dell'evento formattato come YYYY-MM-DDTHH:MM:SS.ZZZ",
             "email": "Per gli eventi del file system, questa è l'e-mail associata all'utente che esegue l'azione. Per gli eventi del provider, si tratta dell'e-mail associata all'utente o all'amministratore interessato. Vuoto in tutti gli altri casi",
             "email": "Per gli eventi del file system, questa è l'e-mail associata all'utente che esegue l'azione. Per gli eventi del provider, si tratta dell'e-mail associata all'utente o all'amministratore interessato. Vuoto in tutti gli altri casi",
             "object_data": "Dati dell'oggetto provider serializzati come JSON con campi sensibili rimossi",
             "object_data": "Dati dell'oggetto provider serializzati come JSON con campi sensibili rimossi",
             "object_data_string": "Dati dell'oggetto provider serializzati come stringa JSON escaped con campi sensibili rimossi",
             "object_data_string": "Dati dell'oggetto provider serializzati come stringa JSON escaped con campi sensibili rimossi",

+ 3 - 0
templates/webadmin/eventaction.html

@@ -929,6 +929,9 @@ explicit grant from the SFTPGo Team ([email protected]).
                 <p>
                 <p>
                     <span class="shortcut">{{`{{Timestamp}}`}}</span> => <span data-i18n="actions.placeholders_modal.timestamp">Event timestamp as nanoseconds since epoch.</span>
                     <span class="shortcut">{{`{{Timestamp}}`}}</span> => <span data-i18n="actions.placeholders_modal.timestamp">Event timestamp as nanoseconds since epoch.</span>
                 </p>
                 </p>
+                <p>
+                    <span class="shortcut">{{`{{DateTime}}`}}</span> => <span data-i18n="actions.placeholders_modal.datetime">Timestamp formatted as YYYY-MM-DDTHH:MM:SS.ZZZ.</span>
+                </p>
                 <p>
                 <p>
                     <span class="shortcut">{{`{{Email}}`}}</span> => <span data-i18n="actions.placeholders_modal.email">For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases.</span>
                     <span class="shortcut">{{`{{Email}}`}}</span> => <span data-i18n="actions.placeholders_modal.email">For filesystem events, this is the email associated with the user performing the action. For the provider events, this is the email associated with the affected user or admin. Blank in all other cases.</span>
                 </p>
                 </p>