From d01fccf28c08fe5a82f32c9ccba2eced19b8605c Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Fri, 26 Jan 2024 21:03:41 +0100 Subject: [PATCH] WIP new WebAdmin: maintenance page Signed-off-by: Nicola Murino --- go.mod | 10 +-- go.sum | 20 ++--- internal/httpd/api_maintenance.go | 5 +- internal/httpd/webadmin.go | 25 +++--- internal/util/i18n.go | 4 +- static/locales/en/translation.json | 25 +++++- static/locales/it/translation.json | 25 +++++- templates/webadmin/iplist.html | 2 +- templates/webadmin/iplists.html | 2 +- templates/webadmin/maintenance.html | 123 ++++++++++++++++------------ templates/webadmin/status.html | 39 +++++---- 11 files changed, 170 insertions(+), 110 deletions(-) diff --git a/go.mod b/go.mod index ed6efe36..c02a0fd8 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/klauspost/compress v1.17.4 github.com/lestrrat-go/jwx/v2 v2.0.19 github.com/lithammer/shortuuid/v3 v3.0.7 - github.com/mattn/go-sqlite3 v1.14.19 + github.com/mattn/go-sqlite3 v1.14.20 github.com/mhale/smtpd v0.8.2 github.com/minio/sio v0.3.1 github.com/otiai10/copy v1.14.0 @@ -74,7 +74,7 @@ require ( golang.org/x/sys v0.16.0 golang.org/x/term v0.16.0 golang.org/x/time v0.5.0 - google.golang.org/api v0.157.0 + google.golang.org/api v0.158.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -171,9 +171,9 @@ require ( golang.org/x/tools v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect google.golang.org/grpc v1.61.0 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index c2b07009..39e1e735 100644 --- a/go.sum +++ b/go.sum @@ -286,8 +286,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.20 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0= +github.com/mattn/go-sqlite3 v1.14.20/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mhale/smtpd v0.8.2 h1:rHKOMHeFoDvcq8Na9ErCbNcjlWTSyGtznOmJpWsOzuc= github.com/mhale/smtpd v0.8.2/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= @@ -522,8 +522,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20= -google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= +google.golang.org/api v0.158.0 h1:7SKwlRqzrXT2ULl6a3iESb+1pOak5IOd5F+ay5ULiV4= +google.golang.org/api v0.158.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= 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.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= @@ -531,12 +531,12 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo= +google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8= +google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/internal/httpd/api_maintenance.go b/internal/httpd/api_maintenance.go index 51cf34ab..10347de8 100644 --- a/internal/httpd/api_maintenance.go +++ b/internal/httpd/api_maintenance.go @@ -184,7 +184,10 @@ func loadData(w http.ResponseWriter, r *http.Request) { func restoreBackup(content []byte, inputFile string, scanQuota, mode int, executor, ipAddress, role string) error { dump, err := dataprovider.ParseDumpData(content) if err != nil { - return util.NewValidationError(fmt.Sprintf("invalid input_file %q", inputFile)) + return util.NewI18nError( + util.NewValidationError(fmt.Sprintf("invalid input_file %q", inputFile)), + util.I18nErrorBackupFile, + ) } if err = RestoreConfigs(dump.Configs, mode, executor, ipAddress, role); err != nil { diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 679a27e6..ec7176a5 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -100,7 +100,6 @@ const ( templateSetup = "adminsetup.html" pageEventRulesTitle = "Event rules" pageEventActionsTitle = "Event actions" - pageMaintenanceTitle = "Maintenance" pageEventsTitle = "Logs" pageConfigsTitle = "Configurations" defaultQueryLimit = 1000 @@ -236,7 +235,7 @@ type maintenancePage struct { basePage BackupPath string RestorePath string - Error string + Error *util.I18nError } type defenderHostsPage struct { @@ -451,7 +450,7 @@ func loadAdminTemplates(templatesPath string) { filepath.Join(templatesPath, templateCommonDir, templateCommonLogin), } maintenancePaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), filepath.Join(templatesPath, templateAdminDir, templateBase), filepath.Join(templatesPath, templateAdminDir, templateMaintenance), } @@ -829,12 +828,12 @@ func (s *httpdServer) renderChangePasswordPage(w http.ResponseWriter, r *http.Re renderAdminTemplate(w, templateChangePwd, data) } -func (s *httpdServer) renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) { +func (s *httpdServer) renderMaintenancePage(w http.ResponseWriter, r *http.Request, err error) { data := maintenancePage{ - basePage: s.getBasePageData(pageMaintenanceTitle, webMaintenancePath, r), + basePage: s.getBasePageData(util.I18nMaintenanceTitle, webMaintenancePath, r), BackupPath: webBackupPath, RestorePath: webRestorePath, - Error: error, + Error: getI18nError(err), } renderAdminTemplate(w, templateMaintenance, data) @@ -2737,7 +2736,7 @@ func (s *httpdServer) handleWebAdminProfilePost(w http.ResponseWriter, r *http.R func (s *httpdServer) handleWebMaintenance(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.renderMaintenancePage(w, r, "") + s.renderMaintenancePage(w, r, nil) } func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) { @@ -2749,7 +2748,7 @@ func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) { } err = r.ParseMultipartForm(MaxRestoreSize) if err != nil { - s.renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm)) return } defer r.MultipartForm.RemoveAll() //nolint:errcheck @@ -2761,17 +2760,17 @@ func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) { } restoreMode, err := strconv.Atoi(r.Form.Get("mode")) if err != nil { - s.renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err) return } scanQuota, err := strconv.Atoi(r.Form.Get("quota")) if err != nil { - s.renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, err) return } backupFile, _, err := r.FormFile("backup_file") if err != nil { - s.renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorBackupFile)) return } defer backupFile.Close() @@ -2781,12 +2780,12 @@ func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) { if len(backupContent) == 0 { err = errors.New("backup file size must be greater than 0") } - s.renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorBackupFile)) return } if err := restoreBackup(backupContent, "", scanQuota, restoreMode, claims.Username, ipAddr, claims.Role); err != nil { - s.renderMaintenancePage(w, r, err.Error()) + s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorRestore)) return } diff --git a/internal/util/i18n.go b/internal/util/i18n.go index f6d9df5e..cbe72a88 100644 --- a/internal/util/i18n.go +++ b/internal/util/i18n.go @@ -211,7 +211,7 @@ const ( I18nErrorDuplicatedName = "general.duplicated_name" I18nErrorDuplicatedIPNet = "ip_list.duplicated" I18nErrorRoleAdminPerms = "admin.role_permissions" - I18nBackupOK = "general.backup_ok" + I18nBackupOK = "maintenance.backup_ok" I18nErrorFolderTemplate = "virtual_folders.template_no_folder" I18nErrorUserTemplate = "user.template_no_user" I18nConfigsOK = "general.configs_saved" @@ -230,6 +230,8 @@ const ( I18nFTPTLSExplicit = "status.tls_explicit" I18nFTPTLSImplicit = "status.tls_implicit" I18nFTPTLSMixed = "status.tls_mixed" + I18nErrorBackupFile = "maintenance.backup_invalid_file" + I18nErrorRestore = "maintenance.restore_error" ) // NewI18nError returns a I18nError wrappring the provided error diff --git a/static/locales/en/translation.json b/static/locales/en/translation.json index 06faf88c..9a81a58c 100644 --- a/static/locales/en/translation.json +++ b/static/locales/en/translation.json @@ -224,7 +224,6 @@ "duplicated_username": "The specified username already exists", "duplicated_name": "The specified name already exists", "permissions_required": "Permissions are required", - "backup_ok": "Backup successfully restored", "configs_saved": "Configurations has been successfully updated", "protocol": "Protocol", "refresh": "Refresh", @@ -237,7 +236,8 @@ "type": "Type", "issuer": "Issuer", "data_provider": "Database", - "driver": "Driver" + "driver": "Driver", + "mode": "Mode" }, "fs": { "view_file": "View file \"{{- path}}\"", @@ -726,7 +726,6 @@ "ratelimiters_safe_list": "Rate limiters safe list", "ip_net": "IP/Network", "protocols": "Protocols", - "mode": "Mode", "any": "Any", "allow": "Allow", "deny": "Deny", @@ -746,10 +745,11 @@ "score": "Score" }, "status": { - "desc": "Status of services", + "desc": "Server status", "ssh": "SSH/SFTP server", "active": "Status: active", "disabled": "Status: disabled", + "error": "Status: error", "proxy_on": "PROXY protocol enabled", "address": "Address", "ssh_auths": "Authentication methods", @@ -772,5 +772,22 @@ "tls_mixed": "Plain and explicit (FTPES) mode", "webdav": "WebDAV server", "rate_limiters": "Rate limiters" + }, + "maintenance": { + "backup": "Backup", + "backup_do": "Backup your data", + "backup_ok": "Backup successfully restored", + "backup_invalid_file": "Invalid backup file, make sure it is a JSON file with valid content", + "restore_error": "Unable to restore your backup, check the server logs for more details", + "restore": "Restore", + "backup_file": "Backup file", + "backup_file_help": "Import data from a JSON backup file", + "restore_mode0": "Add and update", + "restore_mode1": "Add only", + "restore_mode2": "Add, update and disconnect", + "after_restore": "After restore", + "quota_mode0": "No quota update", + "quota_mode1": "Update quota", + "quota_mode2": "Update quota for users with quota limits" } } \ No newline at end of file diff --git a/static/locales/it/translation.json b/static/locales/it/translation.json index 192c3427..cd1a7062 100644 --- a/static/locales/it/translation.json +++ b/static/locales/it/translation.json @@ -224,7 +224,6 @@ "duplicated_username": "Il nome utente specificato esiste già", "duplicated_name": "Il nome specificato esiste già", "permissions_required": "I permessi sono obbligatori", - "backup_ok": "Backup ripristinato correttamente", "configs_saved": "Configurazioni aggiornate", "protocol": "Protocollo", "refresh": "Aggiorna", @@ -237,7 +236,8 @@ "type": "Tipo", "issuer": "Emittente", "data_provider": "Database", - "driver": "Driver" + "driver": "Driver", + "mode": "Modalità" }, "fs": { "view_file": "Visualizza file \"{{- path}}\"", @@ -726,7 +726,6 @@ "ratelimiters_safe_list": "Lista IP esclusi dai rate limiters", "ip_net": "IP/Rete", "protocols": "Protocolli", - "mode": "Modalità", "any": "Qualunque", "allow": "Permesso", "deny": "Non permesso", @@ -746,10 +745,11 @@ "score": "Punteggio" }, "status": { - "desc": "Stato dei servizi", + "desc": "Stato del server", "ssh": "Server SSH/SFTP", "active": "Stato: attivo", "disabled": "Stato: disabilitato", + "error": "Stato: errore", "proxy_on": "Protocollo PROXY abilitato", "address": "Indirizzo", "ssh_auths": "Metodi di autenticazione", @@ -772,5 +772,22 @@ "tls_mixed": "In chiaro e modalità esplicita (FTPES)", "webdav": "Server WebDAV", "rate_limiters": "Rate limiters" + }, + "maintenance": { + "backup": "Backup", + "backup_do": "Effettua il backup dei tuoi dati", + "backup_ok": "Backup ripristinato correttamente", + "backup_invalid_file": "File di backup non valido, assicurati che sia un file JSON con contenuto valido", + "restore_error": "Impossibile ripristinare il backup, controlla i log del server per maggiori dettagli", + "restore": "Ripristino", + "backup_file": "File di backup", + "backup_file_help": "Importa i dati da un file di backup in formato JSON", + "restore_mode0": "Aggiungi e aggiorna", + "restore_mode1": "Aggiungi", + "restore_mode2": "Aggiungi, aggiorna e disconnetti", + "after_restore": "Dopo il ripristino", + "quota_mode0": "Non aggiornare quota", + "quota_mode1": "Aggiorna quota", + "quota_mode2": "Aggiorna quota per gli utenti con limiti di quota" } } \ No newline at end of file diff --git a/templates/webadmin/iplist.html b/templates/webadmin/iplist.html index 27018256..5662108a 100644 --- a/templates/webadmin/iplist.html +++ b/templates/webadmin/iplist.html @@ -45,7 +45,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). {{- if eq .Entry.Type 2}}
- +
- - Import data from a JSON backup file - + +
+ +
-
- -
- + + +
-
- -
- + + +
- - + +
+ + +
-
-
-
Backup
+ -{{end}} + +{{- end}} + +{{- define "extra_js"}} + +{{- end}} \ No newline at end of file diff --git a/templates/webadmin/status.html b/templates/webadmin/status.html index 6fea537d..3a9ea6c9 100644 --- a/templates/webadmin/status.html +++ b/templates/webadmin/status.html @@ -142,22 +142,39 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
-

Allow list

+

Database

-

+

+ + {{if .Status.DataProvider.Error}} "{{.Status.DataProvider.Error}}"{{end}} +

+
+

+ "{{.Status.DataProvider.Driver}}" +

+
-

Defender

+

Defender

+
+
+

Allow list

+
+
+

+
+
+

Rate limiters

@@ -167,7 +184,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). {{- if .Status.RateLimiters.IsActive}}

- "{{.Status.RateLimiters.GetProtocolsAsString}}" + "{{.Status.RateLimiters.GetProtocolsAsString}}"

{{- end}} @@ -198,20 +215,6 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
-
-
-

Database

-
-
-

-
-

- "{{.Status.DataProvider.Driver}}" -

-
-
-
-
{{- end}} \ No newline at end of file