WIP new WebAdmin: maintenance page

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino
2024-01-26 21:03:41 +01:00
parent 9fcff83f8f
commit d01fccf28c
11 changed files with 170 additions and 110 deletions

10
go.mod
View File

@@ -41,7 +41,7 @@ require (
github.com/klauspost/compress v1.17.4 github.com/klauspost/compress v1.17.4
github.com/lestrrat-go/jwx/v2 v2.0.19 github.com/lestrrat-go/jwx/v2 v2.0.19
github.com/lithammer/shortuuid/v3 v3.0.7 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/mhale/smtpd v0.8.2
github.com/minio/sio v0.3.1 github.com/minio/sio v0.3.1
github.com/otiai10/copy v1.14.0 github.com/otiai10/copy v1.14.0
@@ -74,7 +74,7 @@ require (
golang.org/x/sys v0.16.0 golang.org/x/sys v0.16.0
golang.org/x/term v0.16.0 golang.org/x/term v0.16.0
golang.org/x/time v0.5.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 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
@@ -171,9 +171,9 @@ require (
golang.org/x/tools v0.17.0 // indirect golang.org/x/tools v0.17.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto 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-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect
google.golang.org/grpc v1.61.0 // indirect google.golang.org/grpc v1.61.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect

20
go.sum
View File

@@ -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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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.20 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 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 h1:rHKOMHeFoDvcq8Na9ErCbNcjlWTSyGtznOmJpWsOzuc=
github.com/mhale/smtpd v0.8.2/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4= 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= 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-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 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 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.158.0 h1:7SKwlRqzrXT2ULl6a3iESb+1pOak5IOd5F+ay5ULiV4=
google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= 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.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.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 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-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-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-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-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/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-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/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-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= 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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=

View File

@@ -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 { func restoreBackup(content []byte, inputFile string, scanQuota, mode int, executor, ipAddress, role string) error {
dump, err := dataprovider.ParseDumpData(content) dump, err := dataprovider.ParseDumpData(content)
if err != nil { 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 { if err = RestoreConfigs(dump.Configs, mode, executor, ipAddress, role); err != nil {

View File

@@ -100,7 +100,6 @@ const (
templateSetup = "adminsetup.html" templateSetup = "adminsetup.html"
pageEventRulesTitle = "Event rules" pageEventRulesTitle = "Event rules"
pageEventActionsTitle = "Event actions" pageEventActionsTitle = "Event actions"
pageMaintenanceTitle = "Maintenance"
pageEventsTitle = "Logs" pageEventsTitle = "Logs"
pageConfigsTitle = "Configurations" pageConfigsTitle = "Configurations"
defaultQueryLimit = 1000 defaultQueryLimit = 1000
@@ -236,7 +235,7 @@ type maintenancePage struct {
basePage basePage
BackupPath string BackupPath string
RestorePath string RestorePath string
Error string Error *util.I18nError
} }
type defenderHostsPage struct { type defenderHostsPage struct {
@@ -451,7 +450,7 @@ func loadAdminTemplates(templatesPath string) {
filepath.Join(templatesPath, templateCommonDir, templateCommonLogin), filepath.Join(templatesPath, templateCommonDir, templateCommonLogin),
} }
maintenancePaths := []string{ maintenancePaths := []string{
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
filepath.Join(templatesPath, templateAdminDir, templateBase), filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateMaintenance), filepath.Join(templatesPath, templateAdminDir, templateMaintenance),
} }
@@ -829,12 +828,12 @@ func (s *httpdServer) renderChangePasswordPage(w http.ResponseWriter, r *http.Re
renderAdminTemplate(w, templateChangePwd, data) 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{ data := maintenancePage{
basePage: s.getBasePageData(pageMaintenanceTitle, webMaintenancePath, r), basePage: s.getBasePageData(util.I18nMaintenanceTitle, webMaintenancePath, r),
BackupPath: webBackupPath, BackupPath: webBackupPath,
RestorePath: webRestorePath, RestorePath: webRestorePath,
Error: error, Error: getI18nError(err),
} }
renderAdminTemplate(w, templateMaintenance, data) 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) { func (s *httpdServer) handleWebMaintenance(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) 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) { 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) err = r.ParseMultipartForm(MaxRestoreSize)
if err != nil { if err != nil {
s.renderMaintenancePage(w, r, err.Error()) s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm))
return return
} }
defer r.MultipartForm.RemoveAll() //nolint:errcheck 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")) restoreMode, err := strconv.Atoi(r.Form.Get("mode"))
if err != nil { if err != nil {
s.renderMaintenancePage(w, r, err.Error()) s.renderMaintenancePage(w, r, err)
return return
} }
scanQuota, err := strconv.Atoi(r.Form.Get("quota")) scanQuota, err := strconv.Atoi(r.Form.Get("quota"))
if err != nil { if err != nil {
s.renderMaintenancePage(w, r, err.Error()) s.renderMaintenancePage(w, r, err)
return return
} }
backupFile, _, err := r.FormFile("backup_file") backupFile, _, err := r.FormFile("backup_file")
if err != nil { if err != nil {
s.renderMaintenancePage(w, r, err.Error()) s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorBackupFile))
return return
} }
defer backupFile.Close() defer backupFile.Close()
@@ -2781,12 +2780,12 @@ func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) {
if len(backupContent) == 0 { if len(backupContent) == 0 {
err = errors.New("backup file size must be greater than 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 return
} }
if err := restoreBackup(backupContent, "", scanQuota, restoreMode, claims.Username, ipAddr, claims.Role); err != nil { 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 return
} }

View File

@@ -211,7 +211,7 @@ const (
I18nErrorDuplicatedName = "general.duplicated_name" I18nErrorDuplicatedName = "general.duplicated_name"
I18nErrorDuplicatedIPNet = "ip_list.duplicated" I18nErrorDuplicatedIPNet = "ip_list.duplicated"
I18nErrorRoleAdminPerms = "admin.role_permissions" I18nErrorRoleAdminPerms = "admin.role_permissions"
I18nBackupOK = "general.backup_ok" I18nBackupOK = "maintenance.backup_ok"
I18nErrorFolderTemplate = "virtual_folders.template_no_folder" I18nErrorFolderTemplate = "virtual_folders.template_no_folder"
I18nErrorUserTemplate = "user.template_no_user" I18nErrorUserTemplate = "user.template_no_user"
I18nConfigsOK = "general.configs_saved" I18nConfigsOK = "general.configs_saved"
@@ -230,6 +230,8 @@ const (
I18nFTPTLSExplicit = "status.tls_explicit" I18nFTPTLSExplicit = "status.tls_explicit"
I18nFTPTLSImplicit = "status.tls_implicit" I18nFTPTLSImplicit = "status.tls_implicit"
I18nFTPTLSMixed = "status.tls_mixed" I18nFTPTLSMixed = "status.tls_mixed"
I18nErrorBackupFile = "maintenance.backup_invalid_file"
I18nErrorRestore = "maintenance.restore_error"
) )
// NewI18nError returns a I18nError wrappring the provided error // NewI18nError returns a I18nError wrappring the provided error

View File

@@ -224,7 +224,6 @@
"duplicated_username": "The specified username already exists", "duplicated_username": "The specified username already exists",
"duplicated_name": "The specified name already exists", "duplicated_name": "The specified name already exists",
"permissions_required": "Permissions are required", "permissions_required": "Permissions are required",
"backup_ok": "Backup successfully restored",
"configs_saved": "Configurations has been successfully updated", "configs_saved": "Configurations has been successfully updated",
"protocol": "Protocol", "protocol": "Protocol",
"refresh": "Refresh", "refresh": "Refresh",
@@ -237,7 +236,8 @@
"type": "Type", "type": "Type",
"issuer": "Issuer", "issuer": "Issuer",
"data_provider": "Database", "data_provider": "Database",
"driver": "Driver" "driver": "Driver",
"mode": "Mode"
}, },
"fs": { "fs": {
"view_file": "View file \"{{- path}}\"", "view_file": "View file \"{{- path}}\"",
@@ -726,7 +726,6 @@
"ratelimiters_safe_list": "Rate limiters safe list", "ratelimiters_safe_list": "Rate limiters safe list",
"ip_net": "IP/Network", "ip_net": "IP/Network",
"protocols": "Protocols", "protocols": "Protocols",
"mode": "Mode",
"any": "Any", "any": "Any",
"allow": "Allow", "allow": "Allow",
"deny": "Deny", "deny": "Deny",
@@ -746,10 +745,11 @@
"score": "Score" "score": "Score"
}, },
"status": { "status": {
"desc": "Status of services", "desc": "Server status",
"ssh": "SSH/SFTP server", "ssh": "SSH/SFTP server",
"active": "Status: active", "active": "Status: active",
"disabled": "Status: disabled", "disabled": "Status: disabled",
"error": "Status: error",
"proxy_on": "PROXY protocol enabled", "proxy_on": "PROXY protocol enabled",
"address": "Address", "address": "Address",
"ssh_auths": "Authentication methods", "ssh_auths": "Authentication methods",
@@ -772,5 +772,22 @@
"tls_mixed": "Plain and explicit (FTPES) mode", "tls_mixed": "Plain and explicit (FTPES) mode",
"webdav": "WebDAV server", "webdav": "WebDAV server",
"rate_limiters": "Rate limiters" "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"
} }
} }

View File

@@ -224,7 +224,6 @@
"duplicated_username": "Il nome utente specificato esiste già", "duplicated_username": "Il nome utente specificato esiste già",
"duplicated_name": "Il nome specificato esiste già", "duplicated_name": "Il nome specificato esiste già",
"permissions_required": "I permessi sono obbligatori", "permissions_required": "I permessi sono obbligatori",
"backup_ok": "Backup ripristinato correttamente",
"configs_saved": "Configurazioni aggiornate", "configs_saved": "Configurazioni aggiornate",
"protocol": "Protocollo", "protocol": "Protocollo",
"refresh": "Aggiorna", "refresh": "Aggiorna",
@@ -237,7 +236,8 @@
"type": "Tipo", "type": "Tipo",
"issuer": "Emittente", "issuer": "Emittente",
"data_provider": "Database", "data_provider": "Database",
"driver": "Driver" "driver": "Driver",
"mode": "Modalità"
}, },
"fs": { "fs": {
"view_file": "Visualizza file \"{{- path}}\"", "view_file": "Visualizza file \"{{- path}}\"",
@@ -726,7 +726,6 @@
"ratelimiters_safe_list": "Lista IP esclusi dai rate limiters", "ratelimiters_safe_list": "Lista IP esclusi dai rate limiters",
"ip_net": "IP/Rete", "ip_net": "IP/Rete",
"protocols": "Protocolli", "protocols": "Protocolli",
"mode": "Modalità",
"any": "Qualunque", "any": "Qualunque",
"allow": "Permesso", "allow": "Permesso",
"deny": "Non permesso", "deny": "Non permesso",
@@ -746,10 +745,11 @@
"score": "Punteggio" "score": "Punteggio"
}, },
"status": { "status": {
"desc": "Stato dei servizi", "desc": "Stato del server",
"ssh": "Server SSH/SFTP", "ssh": "Server SSH/SFTP",
"active": "Stato: attivo", "active": "Stato: attivo",
"disabled": "Stato: disabilitato", "disabled": "Stato: disabilitato",
"error": "Stato: errore",
"proxy_on": "Protocollo PROXY abilitato", "proxy_on": "Protocollo PROXY abilitato",
"address": "Indirizzo", "address": "Indirizzo",
"ssh_auths": "Metodi di autenticazione", "ssh_auths": "Metodi di autenticazione",
@@ -772,5 +772,22 @@
"tls_mixed": "In chiaro e modalità esplicita (FTPES)", "tls_mixed": "In chiaro e modalità esplicita (FTPES)",
"webdav": "Server WebDAV", "webdav": "Server WebDAV",
"rate_limiters": "Rate limiters" "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"
} }
} }

View File

@@ -45,7 +45,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
{{- if eq .Entry.Type 2}} {{- if eq .Entry.Type 2}}
<div class="form-group row mt-10"> <div class="form-group row mt-10">
<label for="idMode" data-i18n="ip_list.mode" class="col-md-3 col-form-label">Mode</label> <label for="idMode" data-i18n="general.mode" class="col-md-3 col-form-label">Mode</label>
<div class="col-md-9"> <div class="col-md-9">
<select id="idMode" name="mode" class="form-select" data-control="i18n-select2" data-hide-search="true"> <select id="idMode" name="mode" class="form-select" data-control="i18n-select2" data-hide-search="true">
<option value="2" data-i18n="ip_list.deny" {{if eq .Entry.Mode 2 }}selected{{end}}>Deny</option> <option value="2" data-i18n="ip_list.deny" {{if eq .Entry.Mode 2 }}selected{{end}}>Deny</option>

View File

@@ -62,7 +62,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
<tr class="text-start text-muted fw-bold fs-6 gs-0"> <tr class="text-start text-muted fw-bold fs-6 gs-0">
<th data-i18n="ip_list.ip_net">IP/Network</th> <th data-i18n="ip_list.ip_net">IP/Network</th>
<th data-i18n="ip_list.protocols">Protocols</th> <th data-i18n="ip_list.protocols">Protocols</th>
<th data-i18n="ip_list.mode">Mode</th> <th data-i18n="general.mode">Mode</th>
<th data-i18n="general.description">Description</th> <th data-i18n="general.description">Description</th>
<th class="min-w-100px"></th> <th class="min-w-100px"></th>
</tr> </tr>

View File

@@ -1,79 +1,98 @@
<!-- <!--
Copyright (C) 2019 Nicola Murino Copyright (C) 2024 Nicola Murino
This program is free software: you can redistribute it and/or modify This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3.
This program is distributed in the hope that it will be useful, https://keenthemes.com/products/templates-mega-bundle
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License KeenThemes HTML/CSS/JS components are allowed for use only within the
along with this program. If not, see <https://www.gnu.org/licenses/>. SFTPGo product and restricted to be used in a resealable HTML template
that can compete with KeenThemes products anyhow.
This WebUI is allowed for use only within the SFTPGo product and
therefore cannot be used in derivative works/products without an
explicit grant from the SFTPGo Team (support@sftpgo.com).
--> -->
{{template "base" .}} {{template "base" .}}
{{define "title"}}{{.Title}}{{end}} {{- define "page_body"}}
<div class="card shadow-sm">
{{define "page_body"}} <div class="card-header bg-light">
<div class="card shadow mb-4"> <h3 data-i18n="maintenance.restore" class="card-title section-title">Restore</h3>
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Import</h6>
</div> </div>
<div class="card-body"> <div class="card-body">
{{if .Error}} {{- template "errmsg" .Error}}
<div class="alert alert-warning alert-dismissible fade show" role="alert">
{{.Error}}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{{end}}
<form id="restore_form" enctype="multipart/form-data" action="{{.RestorePath}}" method="POST"> <form id="restore_form" enctype="multipart/form-data" action="{{.RestorePath}}" method="POST">
<div class="form-group row"> <div class="form-group row">
<label for="idBackupFile" class="col-sm-2 col-form-label">Backup file</label> <label for="idBackupFile" data-i18n="maintenance.backup_file" class="col-md-3 col-form-label">Backup file</label>
<div class="col-sm-10"> <div class="col-md-9">
<input type="file" class="form-control-file" id="idBackupFile" name="backup_file" <input id="idBackupFile" type="file" accept="application/json" required class="form-control" name="backup_file" aria-describedby="idBackupFileHelp" />
aria-describedby="BackupFileHelpBlock"> <div id="idBackupFileHelp" class="form-text" data-i18n="maintenance.backup_file_help"></div>
<small id="BackupFileHelpBlock" class="form-text text-muted">
Import data from a JSON backup file
</small>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="idMode" class="col-sm-2 col-form-label">Mode</label> <div class="form-group row mt-10">
<div class="col-sm-10"> <label for="idMode" data-i18n="general.mode" class="col-md-3 col-form-label">Mode</label>
<select class="form-control" id="idMode" name="mode"> <div class="col-md-9">
<option value="1">add only</option> <select id="idMode" name="mode" class="form-select" data-control="i18n-select2" data-hide-search="true">
<option value="0">add and update</option> <option data-i18n="maintenance.restore_mode1" value="1">add only</option>
<option value="2">add, update and disconnect</option> <option data-i18n="maintenance.restore_mode0" value="0">add and update</option>
<option data-i18n="maintenance.restore_mode2" value="2">add, update and disconnect</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="idQuota" class="col-sm-2 col-form-label">After restore</label> <div class="form-group row mt-10">
<div class="col-sm-10"> <label for="idQuota" data-i18n="maintenance.after_restore" class="col-md-3 col-form-label">After restore</label>
<select class="form-control" id="idQuota" name="quota"> <div class="col-md-9">
<option value="0">no quota update</option> <select id="idQuota" name="quota" class="form-select" data-control="i18n-select2" data-hide-search="true">
<option value="1">update quota</option> <option data-i18n="maintenance.quota_mode0" value="0">no quota update</option>
<option value="2">update quota if the user has quota restrictions</option> <option data-i18n="maintenance.quota_mode1" value="1">update quota</option>
<option data-i18n="maintenance.quota_mode2" value="2">update quota if the user has quota restrictions</option>
</select> </select>
</div> </div>
</div> </div>
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Import</button> <div class="d-flex justify-content-end mt-12">
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" id="form_submit" class="btn btn-primary px-10" name="form_action" value="submit">
<span data-i18n="maintenance.restore" class="indicator-label">
Restore
</span>
<span data-i18n="general.wait" class="indicator-progress">
Please wait...
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
</span>
</button>
</div>
</form> </form>
</div> </div>
</div> </div>
<div class="card shadow mb-4"> <div class="card shadow-sm mt-10">
<div class="card-header py-3"> <div class="card-header bg-light">
<h6 class="m-0 font-weight-bold text-primary">Backup</h6> <h3 data-i18n="maintenance.backup" class="card-title section-title">Backup</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<a class="btn btn-primary" href="{{.BackupPath}}?output-data=1" target="_blank">Backup your data</a> <div>
<a href="{{.BackupPath}}?output-data=1" target="_blank" class="btn btn-primary btn-block">
<span data-i18n="maintenance.backup_do">Backup your data</span>
</a>
</div>
</div> </div>
</div> </div>
{{end}}
{{- end}}
{{- define "extra_js"}}
<script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
$(document).on("i18nshow", function(){
$('#restore_form').submit(function (event) {
let submitButton = document.querySelector('#form_submit');
submitButton.setAttribute('data-kt-indicator', 'on');
submitButton.disabled = true;
});
});
</script>
{{- end}}

View File

@@ -142,22 +142,39 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
<div class="card mt-10"> <div class="card mt-10">
<div class="card-header bg-light"> <div class="card-header bg-light">
<h3 data-i18n="iplist.allow_list" class="card-title section-title-inner">Allow list</h3> <h3 data-i18n="general.data_provider" class="card-title section-title-inner">Database</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="fs-3 fw-semibold mb-4" {{if .Status.AllowList.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p> <p class="fs-3 fw-semibold mb-4">
<span {{if .Status.DataProvider.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.error" class="text-warning"{{end}}></span>
{{if .Status.DataProvider.Error}}&nbsp;<span class="text-warning">"{{.Status.DataProvider.Error}}"</span>{{end}}
</p>
<div class="d-flex flex-column">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="general.driver"></span> "{{.Status.DataProvider.Driver}}"
</p>
</div>
</div> </div>
</div> </div>
<div class="card mt-10"> <div class="card mt-10">
<div class="card-header bg-light"> <div class="card-header bg-light">
<h3 data-i18n="iplist.defender_list" class="card-title section-title-inner">Defender</h3> <h3 data-i18n="ip_list.defender_list" class="card-title section-title-inner">Defender</h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="fs-3 fw-semibold mb-4" {{if .Status.Defender.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p> <p class="fs-3 fw-semibold mb-4" {{if .Status.Defender.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
</div> </div>
</div> </div>
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="ip_list.allow_list" class="card-title section-title-inner">Allow list</h3>
</div>
<div class="card-body">
<p class="fs-3 fw-semibold mb-4" {{if .Status.AllowList.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
</div>
</div>
<div class="card mt-10"> <div class="card mt-10">
<div class="card-header bg-light"> <div class="card-header bg-light">
<h3 data-i18n="status.rate_limiters" class="card-title section-title-inner">Rate limiters</h3> <h3 data-i18n="status.rate_limiters" class="card-title section-title-inner">Rate limiters</h3>
@@ -167,7 +184,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
{{- if .Status.RateLimiters.IsActive}} {{- if .Status.RateLimiters.IsActive}}
<div class="d-flex flex-column"> <div class="d-flex flex-column">
<p class="fs-5 fw-semibold"> <p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="iplist.protocols"></span> "{{.Status.RateLimiters.GetProtocolsAsString}}" <span class="text-success" data-i18n="ip_list.protocols"></span> "{{.Status.RateLimiters.GetProtocolsAsString}}"
</p> </p>
</div> </div>
{{- end}} {{- end}}
@@ -198,20 +215,6 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
</div> </div>
</div> </div>
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="general.data_provider" class="card-title section-title-inner">Database</h3>
</div>
<div class="card-body">
<p class="fs-3 fw-semibold mb-4" {{if .Status.DataProvider.IsActive}}data-i18n="status.active"{{else}}{{.Status.DataProvider.Error}}{{end}}></p>
<div class="d-flex flex-column">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="general.driver"></span> "{{.Status.DataProvider.Driver}}"
</p>
</div>
</div>
</div>
</div> </div>
</div> </div>
{{- end}} {{- end}}