diff --git a/README.md b/README.md index f32abf72..5d3cea29 100644 --- a/README.md +++ b/README.md @@ -370,6 +370,8 @@ Thank you to [ysura](https://www.ysura.com/) for granting us stable access to a Thank you to [KeenThemes](https://keenthemes.com/) for granting us a custom license to use their amazing [Mega Bundle](https://keenthemes.com/products/templates-mega-bundle) for SFTPGo UI. +Thank you to [Incode](https://www.incode.it/) for helping us to improve the UI/UX. + ## License GNU AGPL-3.0-only diff --git a/go.mod b/go.mod index 447b7a39..1fc0f0d5 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.11 github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.6 github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.1 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2 github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cockroachdb/cockroach-go/v2 v2.3.5 @@ -36,7 +36,7 @@ require ( github.com/hashicorp/go-hclog v1.6.2 github.com/hashicorp/go-plugin v1.6.0 github.com/hashicorp/go-retryablehttp v0.7.5 - github.com/jackc/pgx/v5 v5.5.1 + github.com/jackc/pgx/v5 v5.5.2 github.com/jlaffaye/ftp v0.0.0-20201112195030-9aae4d151126 github.com/klauspost/compress v1.17.4 github.com/lestrrat-go/jwx/v2 v2.0.19 @@ -74,12 +74,12 @@ 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.155.0 + google.golang.org/api v0.156.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( - cloud.google.com/go v0.111.0 // indirect + cloud.google.com/go v0.112.0 // indirect cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.5 // indirect @@ -136,7 +136,6 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/miekg/dns v1.1.57 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -145,7 +144,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/common v0.46.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect @@ -165,11 +164,11 @@ require ( go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e // indirect + golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.1 // indirect + 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-20240108191215-35c7eff3a6b1 // indirect diff --git a/go.sum b/go.sum index a78035e3..f9877eb2 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.111.0 h1:YHLKNupSD1KqjDbQ3+LVdQ81h/UJbJyZG203cEfnQgM= -cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= @@ -65,8 +65,8 @@ github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.6 h1:JWy+uLKZQR/9 github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.6/go.mod h1:T2NcfuIuXWcuwVwg3rBIW6h1cfzCdrzSn4Hs0KltND8= github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflYDHFoZAu/sKYkwU= github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.1 h1:Sn3MAV9YeACCULaxNWWYFH1a6G4wYFwBn3/TA5MwE2Q= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.1/go.mod h1:qutL00aW8GSo2D0I6UEOqMvRS3ZyuBrOC1BLe5D2jPc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2 h1:A5sGOT/mukuU+4At1vkSIWAN8tPwPCoYZBp7aruR540= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2/go.mod h1:qutL00aW8GSo2D0I6UEOqMvRS3ZyuBrOC1BLe5D2jPc= github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac= github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4= @@ -235,8 +235,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI= -github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA= +github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -288,8 +288,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE 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/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mhale/smtpd v0.8.1 h1:O02u8O3eYAGxZCGf4E98WjyB+rA3DVFZtchEialjX4s= github.com/mhale/smtpd v0.8.1/go.mod h1:MQl+y2hwIEQCXtNhe5+55n0GZOjSmeqORDIXbqUL3x4= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= @@ -330,8 +328,8 @@ github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlk github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y= +github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -419,8 +417,8 @@ go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= @@ -430,8 +428,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus= gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e h1:723BNChdd0c2Wk6WOE320qGBiPtYx0F0Bbm1kriShfE= -golang.org/x/exp v0.0.0-20240110193028-0dcbfd608b1e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o= +golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -516,16 +514,16 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.156.0 h1:yloYcGbBtVYjLKQe4enCunxvwn3s2w/XPrrhVf6MsvQ= +google.golang.org/api v0.156.0/go.mod h1:bUSmn4KFO0Q+69zo9CNIDp4Psi6BqM0np0CbzKRSiSY= 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= diff --git a/internal/httpd/web.go b/internal/httpd/web.go index 859acd7f..0b06c2ac 100644 --- a/internal/httpd/web.go +++ b/internal/httpd/web.go @@ -152,6 +152,14 @@ func getI18NErrorString(err error, fallback string) string { return fallback } +func getI18nError(err error) *util.I18nError { + var errI18n *util.I18nError + if err != nil { + errI18n = util.NewI18nError(err, util.I18nError500Message) + } + return errI18n +} + func handlePingRequest(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) render.PlainText(w, r, "PONG") diff --git a/internal/httpd/webadmin.go b/internal/httpd/webadmin.go index 9ef67d13..aed7e5b0 100644 --- a/internal/httpd/webadmin.go +++ b/internal/httpd/webadmin.go @@ -316,7 +316,7 @@ type folderPage struct { type groupPage struct { basePage Group *dataprovider.Group - Error string + Error *util.I18nError Mode genericPageMode ValidPerms []string ValidLoginMethods []string @@ -447,10 +447,9 @@ func loadAdminTemplates(templatesPath string) { filepath.Join(templatesPath, templateAdminDir, templateGroups), } groupPaths := []string{ - filepath.Join(templatesPath, templateCommonDir, templateCommonCSS), + filepath.Join(templatesPath, templateCommonDir, templateCommonBase), filepath.Join(templatesPath, templateAdminDir, templateBase), filepath.Join(templatesPath, templateAdminDir, templateFsConfig), - filepath.Join(templatesPath, templateAdminDir, templateSharedComponents), filepath.Join(templatesPath, templateAdminDir, templateGroup), } eventRulesPaths := []string{ @@ -993,14 +992,10 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use if errGroups != nil { return } - var errI18n *util.I18nError - if err != nil { - errI18n = util.NewI18nError(err, util.I18nError500Message) - } data := userPage{ basePage: basePage, Mode: mode, - Error: errI18n, + Error: getI18nError(err), User: user, ValidPerms: dataprovider.ValidPerms, ValidLoginMethods: dataprovider.ValidLoginMethods, @@ -1067,10 +1062,10 @@ func (s *httpdServer) renderRolePage(w http.ResponseWriter, r *http.Request, rol } func (s *httpdServer) renderGroupPage(w http.ResponseWriter, r *http.Request, group dataprovider.Group, - mode genericPageMode, error string, + mode genericPageMode, err error, ) { - folders, err := s.getWebVirtualFolders(w, r, defaultQueryLimit, true) - if err != nil { + folders, errFolders := s.getWebVirtualFolders(w, r, defaultQueryLimit, true) + if errFolders != nil { return } group.SetEmptySecretsIfNil() @@ -1078,10 +1073,10 @@ func (s *httpdServer) renderGroupPage(w http.ResponseWriter, r *http.Request, gr var title, currentURL string switch mode { case genericPageModeAdd: - title = "Add a new group" + title = util.I18nAddGroupTitle currentURL = webGroupPath case genericPageModeUpdate: - title = "Update group" + title = util.I18nUpdateGroupTitle currentURL = fmt.Sprintf("%v/%v", webGroupPath, url.PathEscape(group.Name)) } group.UserSettings.FsConfig.RedactedSecret = redactedSecret @@ -1089,7 +1084,7 @@ func (s *httpdServer) renderGroupPage(w http.ResponseWriter, r *http.Request, gr data := groupPage{ basePage: s.getBasePageData(title, currentURL, r), - Error: error, + Error: getI18nError(err), Group: &group, Mode: mode, ValidPerms: dataprovider.ValidPerms, @@ -1977,7 +1972,7 @@ func getQuotaLimits(r *http.Request) (int64, int, error) { return quotaSize, quotaFiles, nil } -func updateUserFormFields(r *http.Request) { +func updateRepeaterFormFields(r *http.Request) { for k := range r.Form { if hasPrefixAndSuffix(k, "public_keys[", "][public_key]") { r.Form.Add("public_keys", r.Form.Get(k)) @@ -2029,7 +2024,9 @@ func getUserFromPostFields(r *http.Request) (dataprovider.User, error) { return user, util.NewI18nError(err, util.I18nErrorInvalidForm) } defer r.MultipartForm.RemoveAll() //nolint:errcheck - updateUserFormFields(r) + + updateRepeaterFormFields(r) + uid, err := strconv.Atoi(r.Form.Get("uid")) if err != nil { return user, fmt.Errorf("invalid uid: %w", err) @@ -2118,10 +2115,12 @@ func getGroupFromPostFields(r *http.Request) (dataprovider.Group, error) { group := dataprovider.Group{} err := r.ParseMultipartForm(maxRequestSize) if err != nil { - return group, err + return group, util.NewI18nError(err, util.I18nErrorInvalidForm) } defer r.MultipartForm.RemoveAll() //nolint:errcheck + updateRepeaterFormFields(r) + maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions")) if err != nil { return group, fmt.Errorf("invalid max sessions: %w", err) @@ -3536,7 +3535,7 @@ func (s *httpdServer) handleWebGetGroups(w http.ResponseWriter, r *http.Request) func (s *httpdServer) handleWebAddGroupGet(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) - s.renderGroupPage(w, r, dataprovider.Group{}, genericPageModeAdd, "") + s.renderGroupPage(w, r, dataprovider.Group{}, genericPageModeAdd, nil) } func (s *httpdServer) handleWebAddGroupPost(w http.ResponseWriter, r *http.Request) { @@ -3548,7 +3547,7 @@ func (s *httpdServer) handleWebAddGroupPost(w http.ResponseWriter, r *http.Reque } group, err := getGroupFromPostFields(r) if err != nil { - s.renderGroupPage(w, r, group, genericPageModeAdd, err.Error()) + s.renderGroupPage(w, r, group, genericPageModeAdd, err) return } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) @@ -3558,7 +3557,7 @@ func (s *httpdServer) handleWebAddGroupPost(w http.ResponseWriter, r *http.Reque } err = dataprovider.AddGroup(&group, claims.Username, ipAddr, claims.Role) if err != nil { - s.renderGroupPage(w, r, group, genericPageModeAdd, err.Error()) + s.renderGroupPage(w, r, group, genericPageModeAdd, err) return } http.Redirect(w, r, webGroupsPath, http.StatusSeeOther) @@ -3569,7 +3568,7 @@ func (s *httpdServer) handleWebUpdateGroupGet(w http.ResponseWriter, r *http.Req name := getURLParam(r, "name") group, err := dataprovider.GroupExists(name) if err == nil { - s.renderGroupPage(w, r, group, genericPageModeUpdate, "") + s.renderGroupPage(w, r, group, genericPageModeUpdate, nil) } else if errors.Is(err, util.ErrNotFound) { s.renderNotFoundPage(w, r, err) } else { @@ -3595,7 +3594,7 @@ func (s *httpdServer) handleWebUpdateGroupPost(w http.ResponseWriter, r *http.Re } updatedGroup, err := getGroupFromPostFields(r) if err != nil { - s.renderGroupPage(w, r, group, genericPageModeUpdate, err.Error()) + s.renderGroupPage(w, r, group, genericPageModeUpdate, err) return } ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) @@ -3616,7 +3615,7 @@ func (s *httpdServer) handleWebUpdateGroupPost(w http.ResponseWriter, r *http.Re err = dataprovider.UpdateGroup(&updatedGroup, group.Users, claims.Username, ipAddr, claims.Role) if err != nil { - s.renderGroupPage(w, r, updatedGroup, genericPageModeUpdate, err.Error()) + s.renderGroupPage(w, r, updatedGroup, genericPageModeUpdate, err) return } http.Redirect(w, r, webGroupsPath, http.StatusSeeOther) diff --git a/internal/util/i18n.go b/internal/util/i18n.go index 1c2e1a73..a25eb54f 100644 --- a/internal/util/i18n.go +++ b/internal/util/i18n.go @@ -180,6 +180,8 @@ const ( I18nErrorEndpointInvalid = "storage.endpoint_invalid" I18nErrorEndpointRequired = "storage.endpoint_required" I18nErrorFsUsernameRequired = "storage.username_required" + I18nAddGroupTitle = "title.add_group" + I18nUpdateGroupTitle = "title.update_group" ) // NewI18nError returns a I18nError wrappring the provided error diff --git a/static/locales/en/translation.json b/static/locales/en/translation.json index 15ed24c3..828ff53b 100644 --- a/static/locales/en/translation.json +++ b/static/locales/en/translation.json @@ -47,7 +47,9 @@ "status": "Status", "add_user": "Add user", "update_user": "Update user", - "template_user": "User template" + "template_user": "User template", + "add_group": "Add group", + "update_group": "Update group" }, "setup": { "desc": "To start using SFTPGo you need to create an administrator user", @@ -462,7 +464,9 @@ "disconnect_help": "This way you force the user to login again, if connected, and so to use the new configuration", "submit_generate": "Generate and save users", "submit_export": "Generate and export users", - "invalid_quota_size": "Invalid quota size" + "invalid_quota_size": "Invalid quota size", + "expires_in": "Expires in", + "expires_in_help": "Account expiration as number of days from the creation. 0 means no expiration" }, "group": { "view_manage": "View and manage groups", @@ -621,7 +625,7 @@ "is_anonymous_help": "Anonymous users are supported for FTP and WebDAV protocols and have read-only access", "disable_fs_checks": "Disable filesystem checks", "disable_fs_checks_help": "Disable checks for existence and automatic creation of home directory and virtual folders", - "api_key_auth_help": "Allow to impersonate this user, in REST API, with an API key", + "api_key_auth_help": "Allow to impersonate the user, in REST API, with an API key", "external_auth_cache_time": "External auth cache time", "external_auth_cache_time_help": "Cache time, in seconds, for users authenticated using an external auth hook. 0 means no cache" } diff --git a/static/locales/it/translation.json b/static/locales/it/translation.json index f014d4a7..5dd97326 100644 --- a/static/locales/it/translation.json +++ b/static/locales/it/translation.json @@ -47,7 +47,9 @@ "status": "Stato", "add_user": "Aggiungi utente", "update_user": "Aggiorna utente", - "template_user": "Modello utente" + "template_user": "Modello utente", + "add_group": "Aggiungi gruppo", + "update_group": "Aggiorna gruppo" }, "setup": { "desc": "Per iniziare a utilizzare SFTPGo devi creare un utente amministratore", @@ -462,7 +464,9 @@ "disconnect_help": "In questo modo si obbliga l'utente a effettuare nuovamente il login, se connesso, e quindi ad utilizzare la nuova configurazione", "submit_generate": "Genera e salva utenti", "submit_export": "Genera ed esporta utenti", - "invalid_quota_size": "Quota (dimensione) non valida" + "invalid_quota_size": "Quota (dimensione) non valida", + "expires_in": "Scadenza", + "expires_in_help": "Scadenza dell'account espressa in numero di giorni dalla creazione. 0 significa nessuna scadenza" }, "group": { "view_manage": "Visualizza e gestisci gruppi", @@ -621,7 +625,7 @@ "is_anonymous_help": "Gli utenti anonimi sono supportati per i protocolli FTP e WebDAV e hanno accesso di sola lettura", "disable_fs_checks": "Disabilita i controlli del filesystem", "disable_fs_checks_help": "Disabilita i controlli sull'esistenza e la creazione automatica della directory home e delle cartelle virtuali", - "api_key_auth_help": "Permetti di impersonare questo utente nelle API REST utilizzando una chiave API", + "api_key_auth_help": "Permetti di impersonare l'utente nelle API REST utilizzando una chiave API", "external_auth_cache_time": "Cache per autenticazione esterna", "external_auth_cache_time_help": "Tempo di memorizzazione nella cache, in secondi, per gli utenti autenticati utilizzando un hook di autenticazione esterno. 0 significa nessuna cache" } diff --git a/templates/webadmin/fsconfig.html b/templates/webadmin/fsconfig.html index 9fc862fd..67f20368 100644 --- a/templates/webadmin/fsconfig.html +++ b/templates/webadmin/fsconfig.html @@ -508,4 +508,358 @@ explicit grant from the SFTPGo Team (support@sftpgo.com). +{{- end}} + +{{- define "user_group_perms"}} +