WIP new WebAdmin: connections page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
73b2573b14
commit
8648351fc7
13 changed files with 413 additions and 347 deletions
14
go.mod
14
go.mod
|
@ -10,16 +10,16 @@ require (
|
||||||
github.com/alexedwards/argon2id v1.0.0
|
github.com/alexedwards/argon2id v1.0.0
|
||||||
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
|
github.com/amoghe/go-crypt v0.0.0-20220222110647-20eada5f5964
|
||||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.26.4
|
github.com/aws/aws-sdk-go-v2/config v1.26.5
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.15
|
github.com/aws/aws-sdk-go-v2/credentials v1.16.16
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.12
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.13
|
||||||
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.6
|
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/s3 v1.48.0
|
||||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.26.2
|
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/aws/aws-sdk-go-v2/service/sts v1.26.7
|
||||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.3.5
|
github.com/cockroachdb/cockroach-go/v2 v2.3.6
|
||||||
github.com/coreos/go-oidc/v3 v3.9.0
|
github.com/coreos/go-oidc/v3 v3.9.0
|
||||||
github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8
|
github.com/drakkan/webdav v0.0.0-20230227175313-32996838bcd8
|
||||||
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
|
github.com/eikenb/pipeat v0.0.0-20210730190139-06b3e6902001
|
||||||
|
@ -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.156.0
|
google.golang.org/api v0.157.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ require (
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
|
||||||
github.com/aws/smithy-go v1.19.0 // indirect
|
github.com/aws/smithy-go v1.19.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
@ -164,7 +164,7 @@ require (
|
||||||
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
go.opentelemetry.io/otel/metric v1.22.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
go.opentelemetry.io/otel/trace v1.22.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
|
28
go.sum
28
go.sum
|
@ -37,14 +37,14 @@ github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3
|
||||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.26.4 h1:Juj7LhtxNudNUlfX22K5AnLafO+v4eq9PA3VWSCIQs4=
|
github.com/aws/aws-sdk-go-v2/config v1.26.5 h1:lodGSevz7d+kkFJodfauThRxK9mdJbyutUxGq1NNhvw=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.26.4/go.mod h1:tioqQ7wvxMYnTDpoTTLHhV3Zh+z261i/f2oz+ds8eNI=
|
github.com/aws/aws-sdk-go-v2/config v1.26.5/go.mod h1:DxHrz6diQJOc9EwDslVRh84VjjrE17g+pVZXUeSxaDU=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.15 h1:P0/m1LU08MF2kRzx4P//+7lNjiJod1z4xI2WpWhdpTQ=
|
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5gbadPbWdV1WcAddK8=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.15/go.mod h1:pgtMCf7Dx4GWw5EpHOTc2Sy17LIP0A0N2C9nQ83pQ/0=
|
github.com/aws/aws-sdk-go-v2/credentials v1.16.16/go.mod h1:UHVZrdUsv63hPXFo1H7c5fEneoVo9UXiz36QG1GEPi0=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.12 h1:0FMZy36RSYvcvVzEf1xbNdebLHZewW40QWP+P8jCMVk=
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.13 h1:8Nt4LBUEKV0FxLBO2BmRzDKax3hp2LRMKySMBwL4vMc=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.12/go.mod h1:+chyahvarkb3HibkNei9IQEM9P5cWD5w2kgXCa3Hh0I=
|
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.13/go.mod h1:t5QEDu/FBJJM4kslbQlTSpYtnhoWDNmHSsgQojIxE0o=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||||
|
@ -67,8 +67,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.48.0 h1:PJTdBMsyvra6FtED7JZtDpQrIAflY
|
||||||
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/s3 v1.48.0/go.mod h1:4qXHrG1Ne3VGIMZPCB8OjH/pLFO94sKABIusjh0KWPU=
|
||||||
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 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/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.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=
|
||||||
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/sso v1.18.7/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 h1:QPMJf+Jw8E1l7zqhZmMlFw6w1NmfkfiSK8mS4zOx3BA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0=
|
||||||
|
@ -93,8 +93,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.3.5 h1:Khtm8K6fTTz/ZCWPzU9Ne3aOW9VyAnj4qIPCJgKtwK0=
|
github.com/cockroachdb/cockroach-go/v2 v2.3.6 h1:Wlv9TzkrG9V7i6u8dEtmXPrBzvfFp+CgJNs696rAajM=
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.3.5/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8=
|
github.com/cockroachdb/cockroach-go/v2 v2.3.6/go.mod h1:1wNJ45eSXW9AnOc3skntW9ZUZz6gxrQK3cOj3rK+BC8=
|
||||||
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
|
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
|
||||||
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
|
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
|
@ -428,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 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus=
|
||||||
gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg=
|
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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 h1:hNQpMuAJe5CtcUqCXaWga3FHu+kQvCqcsoVaQgSV60o=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
||||||
golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
@ -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.156.0 h1:yloYcGbBtVYjLKQe4enCunxvwn3s2w/XPrrhVf6MsvQ=
|
google.golang.org/api v0.157.0 h1:ORAeqmbrrozeyw5NjnMxh7peHO0UzV4wWYSwZeCUb20=
|
||||||
google.golang.org/api v0.156.0/go.mod h1:bUSmn4KFO0Q+69zo9CNIDp4Psi6BqM0np0CbzKRSiSY=
|
google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g=
|
||||||
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=
|
||||||
|
|
|
@ -475,24 +475,6 @@ type ConnectionTransfer struct {
|
||||||
DLSize int64 `json:"-"`
|
DLSize int64 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ConnectionTransfer) getConnectionTransferAsString() string {
|
|
||||||
result := ""
|
|
||||||
switch t.OperationType {
|
|
||||||
case operationUpload:
|
|
||||||
result += "UL "
|
|
||||||
case operationDownload:
|
|
||||||
result += "DL "
|
|
||||||
}
|
|
||||||
result += fmt.Sprintf("%q ", t.VirtualPath)
|
|
||||||
if t.Size > 0 {
|
|
||||||
elapsed := time.Since(util.GetTimeFromMsecSinceEpoch(t.StartTime))
|
|
||||||
speed := float64(t.Size) / float64(util.GetTimeAsMsSinceEpoch(time.Now())-t.StartTime)
|
|
||||||
result += fmt.Sprintf("Size: %s Elapsed: %s Speed: \"%.1f KB/s\"", util.ByteCountIEC(t.Size),
|
|
||||||
util.GetDurationAsString(elapsed), speed)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetadataConfig defines how to handle metadata for cloud storage backends
|
// MetadataConfig defines how to handle metadata for cloud storage backends
|
||||||
type MetadataConfig struct {
|
type MetadataConfig struct {
|
||||||
// If not zero the metadata will be read before downloads and will be
|
// If not zero the metadata will be read before downloads and will be
|
||||||
|
@ -1254,6 +1236,7 @@ func (conns *ActiveConnections) GetStats(role string) []ConnectionStatus {
|
||||||
RemoteAddress: c.GetRemoteAddress(),
|
RemoteAddress: c.GetRemoteAddress(),
|
||||||
ConnectionTime: util.GetTimeAsMsSinceEpoch(c.GetConnectionTime()),
|
ConnectionTime: util.GetTimeAsMsSinceEpoch(c.GetConnectionTime()),
|
||||||
LastActivity: util.GetTimeAsMsSinceEpoch(c.GetLastActivity()),
|
LastActivity: util.GetTimeAsMsSinceEpoch(c.GetLastActivity()),
|
||||||
|
CurrentTime: util.GetTimeAsMsSinceEpoch(time.Now()),
|
||||||
Protocol: c.GetProtocol(),
|
Protocol: c.GetProtocol(),
|
||||||
Command: c.GetCommand(),
|
Command: c.GetCommand(),
|
||||||
Transfers: c.GetTransfers(),
|
Transfers: c.GetTransfers(),
|
||||||
|
@ -1279,6 +1262,8 @@ type ConnectionStatus struct {
|
||||||
ConnectionTime int64 `json:"connection_time"`
|
ConnectionTime int64 `json:"connection_time"`
|
||||||
// Last activity as unix timestamp in milliseconds
|
// Last activity as unix timestamp in milliseconds
|
||||||
LastActivity int64 `json:"last_activity"`
|
LastActivity int64 `json:"last_activity"`
|
||||||
|
// Current time as unix timestamp in milliseconds
|
||||||
|
CurrentTime int64 `json:"current_time"`
|
||||||
// Protocol for this connection
|
// Protocol for this connection
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
// active uploads/downloads
|
// active uploads/downloads
|
||||||
|
@ -1289,45 +1274,6 @@ type ConnectionStatus struct {
|
||||||
Node string `json:"node,omitempty"`
|
Node string `json:"node,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConnectionDuration returns the connection duration as string
|
|
||||||
func (c *ConnectionStatus) GetConnectionDuration() string {
|
|
||||||
elapsed := time.Since(util.GetTimeFromMsecSinceEpoch(c.ConnectionTime))
|
|
||||||
return util.GetDurationAsString(elapsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConnectionInfo returns connection info.
|
|
||||||
// Protocol,Client Version and RemoteAddress are returned.
|
|
||||||
func (c *ConnectionStatus) GetConnectionInfo() string {
|
|
||||||
var result strings.Builder
|
|
||||||
|
|
||||||
result.WriteString(fmt.Sprintf("%v. Client: %q From: %q", c.Protocol, c.ClientVersion, c.RemoteAddress))
|
|
||||||
|
|
||||||
if c.Command == "" {
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Protocol {
|
|
||||||
case ProtocolSSH, ProtocolFTP:
|
|
||||||
result.WriteString(fmt.Sprintf(". Command: %q", c.Command))
|
|
||||||
case ProtocolWebDAV:
|
|
||||||
result.WriteString(fmt.Sprintf(". Method: %q", c.Command))
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransfersAsString returns the active transfers as string
|
|
||||||
func (c *ConnectionStatus) GetTransfersAsString() string {
|
|
||||||
result := ""
|
|
||||||
for _, t := range c.Transfers {
|
|
||||||
if result != "" {
|
|
||||||
result += ". "
|
|
||||||
}
|
|
||||||
result += t.getConnectionTransferAsString()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActiveQuotaScan defines an active quota scan for a user
|
// ActiveQuotaScan defines an active quota scan for a user
|
||||||
type ActiveQuotaScan struct {
|
type ActiveQuotaScan struct {
|
||||||
// Username to which the quota scan refers
|
// Username to which the quota scan refers
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -892,23 +891,10 @@ func TestConnectionStatus(t *testing.T) {
|
||||||
assert.Len(t, stats, 3)
|
assert.Len(t, stats, 3)
|
||||||
for _, stat := range stats {
|
for _, stat := range stats {
|
||||||
assert.Equal(t, stat.Username, username)
|
assert.Equal(t, stat.Username, username)
|
||||||
assert.True(t, strings.HasPrefix(stat.GetConnectionInfo(), stat.Protocol))
|
|
||||||
assert.True(t, strings.HasPrefix(stat.GetConnectionDuration(), "00:"))
|
|
||||||
if stat.ConnectionID == "SFTP_id1" {
|
if stat.ConnectionID == "SFTP_id1" {
|
||||||
assert.Len(t, stat.Transfers, 2)
|
assert.Len(t, stat.Transfers, 2)
|
||||||
assert.Greater(t, len(stat.GetTransfersAsString()), 0)
|
|
||||||
for _, tr := range stat.Transfers {
|
|
||||||
if tr.OperationType == operationDownload {
|
|
||||||
assert.True(t, strings.HasPrefix(tr.getConnectionTransferAsString(), "DL"))
|
|
||||||
} else if tr.OperationType == operationUpload {
|
|
||||||
assert.True(t, strings.HasPrefix(tr.getConnectionTransferAsString(), "UL"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if stat.ConnectionID == "DAV_id3" {
|
} else if stat.ConnectionID == "DAV_id3" {
|
||||||
assert.Len(t, stat.Transfers, 1)
|
assert.Len(t, stat.Transfers, 1)
|
||||||
assert.Greater(t, len(stat.GetTransfersAsString()), 0)
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, 0, len(stat.GetTransfersAsString()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1710,6 +1710,8 @@ func (s *httpdServer) setupWebAdminRoutes() {
|
||||||
Delete(webGroupPath+"/{name}", deleteGroup)
|
Delete(webGroupPath+"/{name}", deleteGroup)
|
||||||
router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie).
|
router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie).
|
||||||
Get(webConnectionsPath, s.handleWebGetConnections)
|
Get(webConnectionsPath, s.handleWebGetConnections)
|
||||||
|
router.With(s.checkPerm(dataprovider.PermAdminViewConnections), s.refreshCookie).
|
||||||
|
Get(webConnectionsPath+jsonAPISuffix, getActiveConnections)
|
||||||
router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie).
|
router.With(s.checkPerm(dataprovider.PermAdminManageFolders), s.refreshCookie).
|
||||||
Get(webFoldersPath, s.handleWebGetFolders)
|
Get(webFoldersPath, s.handleWebGetFolders)
|
||||||
router.With(s.checkPerm(dataprovider.PermAdminManageFolders), compressor.Handler, s.refreshCookie).
|
router.With(s.checkPerm(dataprovider.PermAdminManageFolders), compressor.Handler, s.refreshCookie).
|
||||||
|
|
|
@ -99,7 +99,6 @@ const (
|
||||||
templateMFA = "mfa.html"
|
templateMFA = "mfa.html"
|
||||||
templateSetup = "adminsetup.html"
|
templateSetup = "adminsetup.html"
|
||||||
pageAdminsTitle = "Admins"
|
pageAdminsTitle = "Admins"
|
||||||
pageConnectionsTitle = "Connections"
|
|
||||||
pageStatusTitle = "Status"
|
pageStatusTitle = "Status"
|
||||||
pageEventRulesTitle = "Event rules"
|
pageEventRulesTitle = "Event rules"
|
||||||
pageEventActionsTitle = "Event actions"
|
pageEventActionsTitle = "Event actions"
|
||||||
|
@ -185,11 +184,6 @@ type eventActionsPage struct {
|
||||||
Actions []dataprovider.BaseEventAction
|
Actions []dataprovider.BaseEventAction
|
||||||
}
|
}
|
||||||
|
|
||||||
type connectionsPage struct {
|
|
||||||
basePage
|
|
||||||
Connections []common.ConnectionStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
type statusPage struct {
|
type statusPage struct {
|
||||||
basePage
|
basePage
|
||||||
Status *ServicesStatus
|
Status *ServicesStatus
|
||||||
|
@ -412,7 +406,7 @@ func loadAdminTemplates(templatesPath string) {
|
||||||
filepath.Join(templatesPath, templateCommonDir, templateChangePwd),
|
filepath.Join(templatesPath, templateCommonDir, templateChangePwd),
|
||||||
}
|
}
|
||||||
connectionsPaths := []string{
|
connectionsPaths := []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, templateConnections),
|
filepath.Join(templatesPath, templateAdminDir, templateConnections),
|
||||||
}
|
}
|
||||||
|
@ -3336,12 +3330,8 @@ func (s *httpdServer) handleWebGetConnections(w http.ResponseWriter, r *http.Req
|
||||||
s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
|
s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connectionStats := common.Connections.GetStats(claims.Role)
|
|
||||||
connectionStats = append(connectionStats, getNodesConnections(claims.Username, claims.Role)...)
|
data := s.getBasePageData(util.I18nSessionsTitle, webConnectionsPath, r)
|
||||||
data := connectionsPage{
|
|
||||||
basePage: s.getBasePageData(pageConnectionsTitle, webConnectionsPath, r),
|
|
||||||
Connections: connectionStats,
|
|
||||||
}
|
|
||||||
renderAdminTemplate(w, templateConnections, data)
|
renderAdminTemplate(w, templateConnections, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1077,19 +1077,6 @@ func TestCommandGetFsError(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetConnectionInfo(t *testing.T) {
|
|
||||||
c := common.ConnectionStatus{
|
|
||||||
Username: "test_user",
|
|
||||||
ConnectionID: "123",
|
|
||||||
ClientVersion: "client",
|
|
||||||
RemoteAddress: "127.0.0.1:1234",
|
|
||||||
Protocol: common.ProtocolSSH,
|
|
||||||
Command: "sha1sum /test_file_ftp.dat",
|
|
||||||
}
|
|
||||||
info := c.GetConnectionInfo()
|
|
||||||
assert.Contains(t, info, "sha1sum /test_file_ftp.dat")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSCPFileMode(t *testing.T) {
|
func TestSCPFileMode(t *testing.T) {
|
||||||
mode := getFileModeAsString(0, true)
|
mode := getFileModeAsString(0, true)
|
||||||
assert.Equal(t, "0755", mode)
|
assert.Equal(t, "0755", mode)
|
||||||
|
@ -1832,42 +1819,6 @@ func TestTransferFailingReader(t *testing.T) {
|
||||||
assert.Len(t, connection.GetTransfers(), 0)
|
assert.Len(t, connection.GetTransfers(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConnectionStatusStruct(t *testing.T) {
|
|
||||||
var transfers []common.ConnectionTransfer
|
|
||||||
transferUL := common.ConnectionTransfer{
|
|
||||||
OperationType: "upload",
|
|
||||||
StartTime: util.GetTimeAsMsSinceEpoch(time.Now()),
|
|
||||||
Size: 123,
|
|
||||||
VirtualPath: "/test.upload",
|
|
||||||
}
|
|
||||||
transferDL := common.ConnectionTransfer{
|
|
||||||
OperationType: "download",
|
|
||||||
StartTime: util.GetTimeAsMsSinceEpoch(time.Now()),
|
|
||||||
Size: 123,
|
|
||||||
VirtualPath: "/test.download",
|
|
||||||
}
|
|
||||||
transfers = append(transfers, transferUL)
|
|
||||||
transfers = append(transfers, transferDL)
|
|
||||||
c := common.ConnectionStatus{
|
|
||||||
Username: "test",
|
|
||||||
ConnectionID: "123",
|
|
||||||
ClientVersion: "fakeClient-1.0.0",
|
|
||||||
RemoteAddress: "127.0.0.1:1234",
|
|
||||||
ConnectionTime: util.GetTimeAsMsSinceEpoch(time.Now()),
|
|
||||||
LastActivity: util.GetTimeAsMsSinceEpoch(time.Now()),
|
|
||||||
Protocol: "SFTP",
|
|
||||||
Transfers: transfers,
|
|
||||||
}
|
|
||||||
durationString := c.GetConnectionDuration()
|
|
||||||
assert.NotEqual(t, 0, len(durationString))
|
|
||||||
|
|
||||||
transfersString := c.GetTransfersAsString()
|
|
||||||
assert.NotEqual(t, 0, len(transfersString))
|
|
||||||
|
|
||||||
connInfo := c.GetConnectionInfo()
|
|
||||||
assert.NotEqual(t, 0, len(connInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigsFromProvider(t *testing.T) {
|
func TestConfigsFromProvider(t *testing.T) {
|
||||||
err := dataprovider.UpdateConfigs(nil, "", "", "")
|
err := dataprovider.UpdateConfigs(nil, "", "", "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -58,6 +58,7 @@ const (
|
||||||
I18nConfigsTitle = "title.configs"
|
I18nConfigsTitle = "title.configs"
|
||||||
I18nOAuth2Title = "title.oauth2_success"
|
I18nOAuth2Title = "title.oauth2_success"
|
||||||
I18nOAuth2ErrorTitle = "title.oauth2_error"
|
I18nOAuth2ErrorTitle = "title.oauth2_error"
|
||||||
|
I18nSessionsTitle = "title.connections"
|
||||||
I18nErrorSetupInstallCode = "setup.install_code_mismatch"
|
I18nErrorSetupInstallCode = "setup.install_code_mismatch"
|
||||||
I18nInvalidAuth = "general.invalid_auth_request"
|
I18nInvalidAuth = "general.invalid_auth_request"
|
||||||
I18nError429Message = "general.error429"
|
I18nError429Message = "general.error429"
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
"groups": "Groups",
|
"groups": "Groups",
|
||||||
"folders": "Virtual folders",
|
"folders": "Virtual folders",
|
||||||
"connections": "Active sessions",
|
"connections": "Active connections",
|
||||||
"event_manager": "Event Manager",
|
"event_manager": "Event Manager",
|
||||||
"event_rules": "Rules",
|
"event_rules": "Rules",
|
||||||
"event_actions": "Actions",
|
"event_actions": "Actions",
|
||||||
|
@ -219,7 +219,9 @@
|
||||||
"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",
|
"backup_ok": "Backup successfully restored",
|
||||||
"configs_saved": "Configurations has been successfully updated"
|
"configs_saved": "Configurations has been successfully updated",
|
||||||
|
"protocol": "Protocol",
|
||||||
|
"refresh": "Refresh"
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
"view_file": "View file \"{{- path}}\"",
|
"view_file": "View file \"{{- path}}\"",
|
||||||
|
@ -670,5 +672,19 @@
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"role_permissions": "A role admin cannot have the following permissions: {{val}}"
|
"role_permissions": "A role admin cannot have the following permissions: {{val}}"
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"view_manage": "View and manage connections",
|
||||||
|
"started": "Started",
|
||||||
|
"remote_address": "Remote address",
|
||||||
|
"last_activity": "Last activity",
|
||||||
|
"disconnect_confirm_btn": "Yes, disconnect",
|
||||||
|
"disconnect_confirm": "Do you want to disconnect the selected connection? This action is irreversible",
|
||||||
|
"disconnect_ko": "Unable to disconnect the selected connection",
|
||||||
|
"upload": "UL: \"{{- path}}\"",
|
||||||
|
"download": "DL: \"{{- path}}\"",
|
||||||
|
"upload_info": "$t(connections.upload). Size: {{- size}}. Speed: {{- speed}}",
|
||||||
|
"download_info": "$t(connections.download). Size: {{- size}}. Speed: {{- speed}}",
|
||||||
|
"client": "Client: {{- val}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -31,7 +31,7 @@
|
||||||
"users": "Utenti",
|
"users": "Utenti",
|
||||||
"groups": "Gruppi",
|
"groups": "Gruppi",
|
||||||
"folders": "Cartelle virtuali",
|
"folders": "Cartelle virtuali",
|
||||||
"connections": "Sessioni attive",
|
"connections": "Connessioni attive",
|
||||||
"event_manager": "Gestione eventi",
|
"event_manager": "Gestione eventi",
|
||||||
"event_rules": "Regole",
|
"event_rules": "Regole",
|
||||||
"event_actions": "Azioni",
|
"event_actions": "Azioni",
|
||||||
|
@ -219,7 +219,9 @@
|
||||||
"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",
|
"backup_ok": "Backup ripristinato correttamente",
|
||||||
"configs_saved": "Configurazioni aggiornate"
|
"configs_saved": "Configurazioni aggiornate",
|
||||||
|
"protocol": "Protocollo",
|
||||||
|
"refresh": "Aggiorna"
|
||||||
},
|
},
|
||||||
"fs": {
|
"fs": {
|
||||||
"view_file": "Visualizza file \"{{- path}}\"",
|
"view_file": "Visualizza file \"{{- path}}\"",
|
||||||
|
@ -670,5 +672,19 @@
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"role_permissions": "Un amministratore di ruolo non può avere le seguenti autorizzazioni: {{val}}"
|
"role_permissions": "Un amministratore di ruolo non può avere le seguenti autorizzazioni: {{val}}"
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"view_manage": "Visualizza e gestisci connessioni attive",
|
||||||
|
"started": "Iniziata",
|
||||||
|
"remote_address": "Indirizzo remoto",
|
||||||
|
"last_activity": "Ultima attività",
|
||||||
|
"disconnect_confirm_btn": "Si, disconnetti",
|
||||||
|
"disconnect_confirm": "Vuoi disconnettere la connessione selezionata? Questa azione è irreversibile",
|
||||||
|
"disconnect_ko": "Impossibile disconnettere la connessione selezionata",
|
||||||
|
"upload": "UL: \"{{- path}}\"",
|
||||||
|
"download": "DL: \"{{- path}}\"",
|
||||||
|
"upload_info": "$t(connections.upload). Dimensione: {{- size}}. Velocità: {{- speed}}",
|
||||||
|
"download_info": "$t(connections.download). Dimensione: {{- size}}. Velocità: {{- speed}}",
|
||||||
|
"client": "Client: {{- val}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -81,6 +81,11 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||||
+' '+(e?'KMGTPEZY'[--e]+'iB':'Bytes')
|
+' '+(e?'KMGTPEZY'[--e]+'iB':'Bytes')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function humanizeSpeed(a,b,c,d,e){
|
||||||
|
return (b=Math,c=b.log,d=1024,e=c(a)/c(d)|0,a/b.pow(d,e)).toFixed(1)
|
||||||
|
+' '+(e?'KMGTPEZY'[--e]+'B/s':'Bytes/s')
|
||||||
|
}
|
||||||
|
|
||||||
function initRepeaterItems() {
|
function initRepeaterItems() {
|
||||||
let repeaterDeleteButtons = document.querySelectorAll('[data-repeater-delete]');
|
let repeaterDeleteButtons = document.querySelectorAll('[data-repeater-delete]');
|
||||||
let repeaterCreateButtons = document.querySelectorAll('[data-repeater-create]');
|
let repeaterCreateButtons = document.querySelectorAll('[data-repeater-create]');
|
||||||
|
|
|
@ -1,221 +1,376 @@
|
||||||
<!--
|
<!--
|
||||||
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 "extra_css"}}
|
||||||
|
<link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
{{define "extra_css"}}
|
{{- define "page_body"}}
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
|
<div class="card shadow-sm">
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
|
<div class="card-header bg-light">
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
|
<h3 data-i18n="connections.view_manage" class="card-title section-title">View and manage connections</h3>
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
|
|
||||||
<link href="{{.StaticURL}}/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "page_body"}}
|
|
||||||
<div id="errorMsg" class="alert alert-warning fade show" style="display: none;" role="alert">
|
|
||||||
<span id="errorTxt"></span>
|
|
||||||
<button type="button" class="close" aria-label="Close" onclick="dismissErrorMsg();">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
function dismissErrorMsg(){
|
|
||||||
$('#errorMsg').hide();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="card shadow mb-4">
|
|
||||||
<div class="card-header py-3">
|
|
||||||
<h6 class="m-0 font-weight-bold text-primary">View and manage connections</h6>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div id="card_body" class="card-body">
|
||||||
<div class="table-responsive">
|
<div id="loader" class="align-items-center text-center my-10">
|
||||||
<table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
|
<span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
|
||||||
|
<span data-i18n="general.loading" class="text-gray-700">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div id="card_content" class="d-none">
|
||||||
|
<div class="d-flex flex-stack flex-wrap mb-5">
|
||||||
|
<div class="d-flex align-items-center position-relative my-2">
|
||||||
|
<i class="ki-solid ki-magnifier fs-1 position-absolute ms-6"></i>
|
||||||
|
<input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
|
||||||
|
class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end my-2" data-table-toolbar="base">
|
||||||
|
<a href="{{.ConnectionsURL}}" class="btn btn-primary">
|
||||||
|
<i class="ki-solid ki-arrows-circle fs-2"></i>
|
||||||
|
<span data-i18n="general.refresh">Refresh</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="text-start text-muted fw-bold fs-6 gs-0">
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>Node</th>
|
<th>Node</th>
|
||||||
<th>Username</th>
|
<th data-i18n="login.username">Username</th>
|
||||||
<th>Time</th>
|
<th data-i18n="connections.started">Started</th>
|
||||||
<th>Info</th>
|
<th data-i18n="connections.remote_address">Remote address</th>
|
||||||
<th>Transfers</th>
|
<th data-i18n="general.protocol">Protocol</th>
|
||||||
|
<th data-i18n="connections.last_activity">Last activity</th>
|
||||||
|
<th data-i18n="general.info">Info</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
|
||||||
{{range .Connections}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.ConnectionID}}</td>
|
|
||||||
<td>{{.Node}}</td>
|
|
||||||
<td>{{.Username}}</td>
|
|
||||||
<td>{{.GetConnectionDuration}}</td>
|
|
||||||
<td>{{.GetConnectionInfo}}</td>
|
|
||||||
<td>{{.GetTransfersAsString}}</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{- end}}
|
||||||
|
|
||||||
{{define "dialog"}}
|
{{- define "extra_js"}}
|
||||||
<div class="modal fade" id="disconnectModal" tabindex="-1" role="dialog" aria-labelledby="disconnectModalLabel"
|
<script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
|
||||||
aria-hidden="true">
|
<script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="disconnectModalLabel">
|
|
||||||
Confirmation required
|
|
||||||
</h5>
|
|
||||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">Do you want to close the selected connection?</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<a class="btn btn-warning" href="#" onclick="disconnectAction()">
|
|
||||||
Disconnect
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "extra_js"}}
|
function disconnectAction(connectionID, node) {
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
|
ModalAlert.fire({
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
|
text: $.t('connections.disconnect_confirm'),
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
|
icon: "warning",
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
|
confirmButtonText: $.t('connections.disconnect_confirm_btn'),
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/buttons.colVis.min.js"></script>
|
cancelButtonText: $.t('general.cancel'),
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
|
customClass: {
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
|
confirmButton: "btn btn-danger",
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
|
cancelButton: 'btn btn-secondary'
|
||||||
<script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
|
}
|
||||||
<script type="text/javascript">
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed){
|
||||||
function disconnectAction() {
|
$('#loading_message').text("");
|
||||||
let table = $('#dataTable').DataTable();
|
KTApp.showPageLoading();
|
||||||
table.button('disconnect:name').enable(false);
|
let path = '{{.ConnectionsURL}}' + "/" + encodeURIComponent(connectionID);
|
||||||
let selectedData = table.row({ selected: true }).data()
|
if (node) {
|
||||||
let connectionID = selectedData[0];
|
path+="?node="+ encodeURIComponent(node);
|
||||||
let nodeID = selectedData[1];
|
|
||||||
let path = '{{.ConnectionsURL}}' + "/" + fixedEncodeURIComponent(connectionID)+"?node="+encodeURIComponent(nodeID);
|
|
||||||
$('#disconnectModal').modal('hide');
|
|
||||||
$('#errorMsg').hide();
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: path,
|
|
||||||
type: 'DELETE',
|
|
||||||
dataType: 'json',
|
|
||||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
|
||||||
timeout: 15000,
|
|
||||||
success: function (result) {
|
|
||||||
window.location.href = '{{.ConnectionsURL}}';
|
|
||||||
},
|
|
||||||
error: function ($xhr, textStatus, errorThrown) {
|
|
||||||
var txt = "Failed to close the selected connection";
|
|
||||||
if ($xhr) {
|
|
||||||
var json = $xhr.responseJSON;
|
|
||||||
if (json) {
|
|
||||||
if (json.message){
|
|
||||||
txt += ": " + json.message;
|
|
||||||
} else {
|
|
||||||
txt += ": " + json.error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$('#errorTxt').text(txt);
|
|
||||||
$('#errorMsg').show();
|
axios.delete(path, {
|
||||||
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||||
|
},
|
||||||
|
validateStatus: function (status) {
|
||||||
|
return status == 200;
|
||||||
|
}
|
||||||
|
}).then(function(response){
|
||||||
|
setTimeout(function() {
|
||||||
|
location.reload();
|
||||||
|
},250);
|
||||||
|
}).catch(function(error){
|
||||||
|
KTApp.hidePageLoading();
|
||||||
|
ModalAlert.fire({
|
||||||
|
text: $.t('connections.disconnect_ko'),
|
||||||
|
icon: "warning",
|
||||||
|
confirmButtonText: $.t('general.ok'),
|
||||||
|
customClass: {
|
||||||
|
confirmButton: "btn btn-primary"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
var datatable = function(){
|
||||||
$.fn.dataTable.ext.buttons.disconnect = {
|
var dt;
|
||||||
text: 'Disconnect',
|
|
||||||
name: 'disconnect',
|
|
||||||
action: function (e, dt, node, config) {
|
|
||||||
$('#disconnectModal').modal('show');
|
|
||||||
},
|
|
||||||
enabled: false
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.dataTable.ext.buttons.refresh = {
|
var initDatatable = function () {
|
||||||
text: '<i class="fas fa-sync-alt"></i>',
|
$('#errorMsg').addClass("d-none");
|
||||||
name: 'refresh',
|
dt = $('#dataTable').DataTable({
|
||||||
titleAttr: "Refresh",
|
ajax: {
|
||||||
action: function (e, dt, node, config) {
|
url: "{{.ConnectionsURL}}/json",
|
||||||
location.reload();
|
dataSrc: "",
|
||||||
}
|
error: function ($xhr, textStatus, errorThrown) {
|
||||||
};
|
$(".dataTables_processing").hide();
|
||||||
|
let txt = "";
|
||||||
var table = $('#dataTable').DataTable({
|
if ($xhr) {
|
||||||
"select": {
|
let json = $xhr.responseJSON;
|
||||||
"style": "single",
|
if (json) {
|
||||||
"blurable": true
|
if (json.message){
|
||||||
},
|
txt = json.message;
|
||||||
"buttons": [
|
}
|
||||||
{
|
}
|
||||||
"text": "Column visibility",
|
}
|
||||||
"extend": "colvis",
|
if (!txt){
|
||||||
"columns": ":not(.noVis)"
|
txt = "general.error500";
|
||||||
}
|
}
|
||||||
],
|
setI18NData($('#errorTxt'), txt);
|
||||||
"lengthChange": true,
|
$('#errorMsg').removeClass("d-none");
|
||||||
"columnDefs": [
|
}
|
||||||
{
|
|
||||||
"targets": [0, 1],
|
|
||||||
"visible": false,
|
|
||||||
"searchable": false,
|
|
||||||
"className": "noVis"
|
|
||||||
},
|
},
|
||||||
{
|
columns: [
|
||||||
"targets": [2],
|
{
|
||||||
"className": "noVis"
|
data: "connection_id",
|
||||||
|
visible: false,
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
return escapeHTML(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "node",
|
||||||
|
visible: false,
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
defaultContent: "",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
return escapeHTML(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "username",
|
||||||
|
defaultContent: "",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
return escapeHTML(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "connection_time",
|
||||||
|
searchable: false,
|
||||||
|
defaultContent: 0,
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
if (data > 0){
|
||||||
|
return $.t('general.datetime', {
|
||||||
|
val: parseInt(data, 10),
|
||||||
|
formatParams: {
|
||||||
|
val: { year: '2-digit', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "remote_address",
|
||||||
|
defaultContent: "",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
return escapeHTML(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "protocol",
|
||||||
|
defaultContent: "",
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
return escapeHTML(data);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "last_activity",
|
||||||
|
searchable: false,
|
||||||
|
defaultContent: 0,
|
||||||
|
render: function(data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
if (data > 0){
|
||||||
|
return $.t('general.datetime', {
|
||||||
|
val: parseInt(data, 10),
|
||||||
|
formatParams: {
|
||||||
|
val: { year: '2-digit', month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric' },
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "active_transfers",
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
render: function (data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
let result = "";
|
||||||
|
if (row.active_transfers && row.active_transfers.length > 0){
|
||||||
|
let transfer = row.active_transfers[0];
|
||||||
|
let path = escapeHTML(transfer.path);
|
||||||
|
let elapsed = row.current_time - transfer.start_time;
|
||||||
|
if (elapsed > 0 && transfer.size > 0){
|
||||||
|
let speed = (transfer.size*1.0) / (elapsed/1000.0);
|
||||||
|
if (transfer.operation_type === 'upload'){
|
||||||
|
result = $.t('connections.upload_info', {path: path, size: fileSizeIEC(transfer.size), speed: humanizeSpeed(speed)});
|
||||||
|
} else {
|
||||||
|
result = $.t('connections.download_info', {path: path, size: fileSizeIEC(transfer.size), speed: humanizeSpeed(speed)});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (transfer.operation_type === 'upload'){
|
||||||
|
result = $.t('connections.upload', {path: path});
|
||||||
|
} else {
|
||||||
|
result = $.t('connections.download', {path: path});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (row.client_version){
|
||||||
|
if (result){
|
||||||
|
result+= ". ";
|
||||||
|
}
|
||||||
|
result+= $.t('connections.client', {val: escapeHTML(row.client_version)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "",
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
className: 'text-end',
|
||||||
|
render: function (data, type, row) {
|
||||||
|
if (type === 'display') {
|
||||||
|
//{{- if .LoggedUser.HasPermission "close_conns"}}
|
||||||
|
return `<div class="d-flex justify-content-end">
|
||||||
|
<div class="ms-2">
|
||||||
|
<a href="#" class="btn btn-sm btn-icon btn-light-danger" data-table-action="close_conn">
|
||||||
|
<i class="ki-solid ki-cross fs-1"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
//{{- end}}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
deferRender: true,
|
||||||
|
stateSave: true,
|
||||||
|
stateDuration: 0,
|
||||||
|
colReorder: {
|
||||||
|
enable: true
|
||||||
|
},
|
||||||
|
stateLoadParams: function (settings, data) {
|
||||||
|
if (data.search.search){
|
||||||
|
const filterSearch = document.querySelector('[data-table-filter="search"]');
|
||||||
|
filterSearch.value = data.search.search;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
info: $.t('datatable.info'),
|
||||||
|
infoEmpty: $.t('datatable.info_empty'),
|
||||||
|
infoFiltered: $.t('datatable.info_filtered'),
|
||||||
|
loadingRecords: "",
|
||||||
|
processing: $.t('datatable.processing'),
|
||||||
|
zeroRecords: "",
|
||||||
|
emptyTable: $.t('datatable.no_records')
|
||||||
|
},
|
||||||
|
order: [[0, 'asc']],
|
||||||
|
initComplete: function(settings, json) {
|
||||||
|
$('#loader').addClass("d-none");
|
||||||
|
$('#card_content').removeClass("d-none");
|
||||||
|
let api = $.fn.dataTable.Api(settings);
|
||||||
|
api.columns.adjust().draw("page");
|
||||||
|
drawAction();
|
||||||
}
|
}
|
||||||
],
|
});
|
||||||
"scrollX": false,
|
|
||||||
"scrollY": false,
|
|
||||||
"responsive": true,
|
|
||||||
"language": {
|
|
||||||
"emptyTable": "No user connected"
|
|
||||||
},
|
|
||||||
"order": [[2, 'asc']]
|
|
||||||
});
|
|
||||||
|
|
||||||
new $.fn.dataTable.FixedHeader( table );
|
dt.on('draw', drawAction);
|
||||||
|
dt.on('column-reorder', function(e, settings, details){
|
||||||
|
drawAction();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
table.button().add(0, 'refresh');
|
function drawAction() {
|
||||||
//table.button().add(0,'pageLength');
|
KTMenu.createInstances();
|
||||||
|
handleRowActions();
|
||||||
|
$('#table_body').localize();
|
||||||
|
}
|
||||||
|
|
||||||
{{if .LoggedAdmin.HasPermission "close_conns"}}
|
var handleDatatableActions = function () {
|
||||||
table.button().add(0,'disconnect');
|
const filterSearch = $(document.querySelector('[data-table-filter="search"]'));
|
||||||
|
filterSearch.off("keyup");
|
||||||
|
filterSearch.on('keyup', function (e) {
|
||||||
|
dt.rows().deselect();
|
||||||
|
dt.search(e.target.value, true, false).draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
table.on('select deselect', function () {
|
function handleRowActions() {
|
||||||
var selectedRows = table.rows({ selected: true }).count();
|
const closeButtons = document.querySelectorAll('[data-table-action="close_conn"]');
|
||||||
table.button('disconnect:name').enable(selectedRows == 1);
|
closeButtons.forEach(d => {
|
||||||
});
|
let el = $(d);
|
||||||
{{end}}
|
el.off("click");
|
||||||
table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
|
el.on("click", function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
const parent = e.target.closest('tr');
|
||||||
|
let data = dt.row(parent).data();
|
||||||
|
disconnectAction(data.connection_id, data.node);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
init: function () {
|
||||||
|
initDatatable();
|
||||||
|
handleDatatableActions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
$(document).on("i18nshow", function(){
|
||||||
|
datatable.init();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{- end}}
|
|
@ -77,7 +77,6 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||||
{{- end}}
|
{{- end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
|
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -284,7 +283,6 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||||
<i class="ki-duotone ki-down fs-5 ms-1 rotate-180"></i>
|
<i class="ki-duotone ki-down fs-5 ms-1 rotate-180"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-700 menu-state-bg-light-primary fw-semibold fs-6 w-200px py-4" data-kt-menu="true">`;
|
<div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-700 menu-state-bg-light-primary fw-semibold fs-6 w-200px py-4" data-kt-menu="true">`;
|
||||||
|
|
||||||
//{{- if .LoggedUser.HasPermission "manage_folders"}}
|
//{{- if .LoggedUser.HasPermission "manage_folders"}}
|
||||||
numActions++;
|
numActions++;
|
||||||
actions+=`<div class="menu-item px-3">
|
actions+=`<div class="menu-item px-3">
|
||||||
|
|
Loading…
Reference in a new issue