WIP new WebAdmin: status page

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino 2024-01-25 19:26:51 +01:00
parent eec9c449d4
commit 9fcff83f8f
No known key found for this signature in database
GPG key ID: 935D2952DEC4EECF
9 changed files with 260 additions and 155 deletions

6
go.mod
View file

@ -3,7 +3,7 @@ module github.com/drakkan/sftpgo/v2
go 1.21
require (
cloud.google.com/go/storage v1.36.0
cloud.google.com/go/storage v1.37.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.1
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5
@ -13,9 +13,9 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.26.6
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/s3/manager v1.15.14
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15
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.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

12
go.sum
View file

@ -9,8 +9,8 @@ cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
cloud.google.com/go/kms v1.15.5 h1:pj1sRfut2eRbD9pFRjNnPNg/CzJPuQAzUujMIM1vVeM=
cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI=
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
cloud.google.com/go/storage v1.37.0 h1:WI8CsaFO8Q9KjPVtsZ5Cmi0dXV25zMoX0FklT7c3Jm4=
cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
@ -43,8 +43,8 @@ github.com/aws/aws-sdk-go-v2/credentials v1.16.16 h1:8q6Rliyv0aUFAVtzaldUEcS+T5g
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/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.14 h1:ogP1WgyvN/qxPJkgtFMD7G2eKb5p/61Jomx+nIHXUQ4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.14/go.mod h1:nYd/WmIrXlBHW/5QwrZP81/Gz08wKi87nV6EI1kmqx4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15 h1:2MUXyGW6dVaQz6aqycpbdLIH1NMcUI6kW6vQ0RabGYg=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15/go.mod h1:aHbhbR6WEQgHAiRj41EQ2W47yOYwNtIkWTXmcAtYqj8=
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/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
@ -63,8 +63,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10/go.mod h1:jMx5INQFYFYB3lQD9W0D8Ohgq6Wnl7NYOJ2TQndbulI=
github.com/aws/aws-sdk-go-v2/service/marketplacemetering v1.19.6 h1:JWy+uLKZQR/9a3gQ+jQa28FEJ/41Z0spdbbQodaXFeA=
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/s3 v1.48.1 h1:5XNlsBsEvBZBMO6p82y+sqpWg8j5aBCe+5C2GBFgqBQ=
github.com/aws/aws-sdk-go-v2/service/s3 v1.48.1/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/go.mod h1:qutL00aW8GSo2D0I6UEOqMvRS3ZyuBrOC1BLe5D2jPc=
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 h1:eajuO3nykDPdYicLlP3AGgOyVN3MOlFmZv7WGTuJPow=

View file

@ -228,19 +228,19 @@ func (b *Binding) HasProxy() bool {
// GetTLSDescription returns the TLS mode as string
func (b *Binding) GetTLSDescription() string {
if certMgr == nil {
return "Disabled"
return util.I18nFTPTLSDisabled
}
switch b.TLSMode {
case 1:
return "Explicit required"
return util.I18nFTPTLSExplicit
case 2:
return "Implicit"
return util.I18nFTPTLSImplicit
}
if certMgr.HasCertificate(common.DefaultTLSKeyPaidID) || certMgr.HasCertificate(b.GetAddress()) {
return "Plain and explicit"
return util.I18nFTPTLSMixed
}
return "Disabled"
return util.I18nFTPTLSDisabled
}
// PortRange defines a port range

View file

@ -98,7 +98,6 @@ const (
templateMaintenance = "maintenance.html"
templateMFA = "mfa.html"
templateSetup = "adminsetup.html"
pageStatusTitle = "Status"
pageEventRulesTitle = "Event rules"
pageEventActionsTitle = "Event actions"
pageMaintenanceTitle = "Maintenance"
@ -442,7 +441,7 @@ func loadAdminTemplates(templatesPath string) {
filepath.Join(templatesPath, templateAdminDir, templateEventAction),
}
statusPaths := []string{
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateStatus),
}
@ -3311,7 +3310,7 @@ func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Req
func (s *httpdServer) handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
data := statusPage{
basePage: s.getBasePageData(pageStatusTitle, webStatusPath, r),
basePage: s.getBasePageData(util.I18nStatusTitle, webStatusPath, r),
Status: getServicesStatus(),
}
renderAdminTemplate(w, templateStatus, data)

View file

@ -67,6 +67,7 @@ const (
I18nAddIPListTitle = "title.add_ip_list"
I18nUpdateIPListTitle = "title.update_ip_list"
I18nDefenderTitle = "title.defender"
I18nStatusTitle = "status.desc"
I18nErrorSetupInstallCode = "setup.install_code_mismatch"
I18nInvalidAuth = "general.invalid_auth_request"
I18nError429Message = "general.error429"
@ -225,6 +226,10 @@ const (
I18nErrorAdminSelfRole = "admin.self_role"
I18nErrorIpInvalid = "ip_list.ip_invalid"
I18nErrorNetInvalid = "ip_list.net_invalid"
I18nFTPTLSDisabled = "status.tls_disabled"
I18nFTPTLSExplicit = "status.tls_explicit"
I18nFTPTLSImplicit = "status.tls_implicit"
I18nFTPTLSMixed = "status.tls_mixed"
)
// NewI18nError returns a I18nError wrappring the provided error

View file

@ -189,7 +189,7 @@ type PipeWriter interface {
GetWrittenBytes() int64
}
// PipeReader defines an interface representing a SFTPGo pipe writer
// PipeReader defines an interface representing a SFTPGo pipe reader
type PipeReader interface {
io.Reader
io.ReaderAt

View file

@ -234,7 +234,10 @@
"last_login": "Last login",
"previous": "Previous",
"next": "Next",
"type": "Type"
"type": "Type",
"issuer": "Issuer",
"data_provider": "Database",
"driver": "Driver"
},
"fs": {
"view_file": "View file \"{{- path}}\"",
@ -741,5 +744,33 @@
"ip": "IP address",
"ban_time": "Blocked until",
"score": "Score"
},
"status": {
"desc": "Status of services",
"ssh": "SSH/SFTP server",
"active": "Status: active",
"disabled": "Status: disabled",
"proxy_on": "PROXY protocol enabled",
"address": "Address",
"ssh_auths": "Authentication methods",
"ssh_commands": "Accepted commands",
"host_key": "Host key",
"fingeprint": "Fingerprint",
"algorithms": "Algorithms",
"algorithm": "Algorithm",
"ssh_pub_key_algo": "Public key authentication algorithms",
"ssh_mac_algo": "Message authentication code (MAC) algorithms",
"ssh_kex_algo": "Key exchange (KEX) algorithms",
"ssh_cipher_algo": "Ciphers",
"ftp": "FTP server",
"ftp_passive_range": "Passive mode port range",
"ftp_passive_ip": "Passive IP",
"tls": "TLS",
"tls_disabled": "Disabled",
"tls_explicit": "Explicit mode required (FTPES)",
"tls_implicit": "Implicit mode (FTPS), deprecated, prefer FTPES",
"tls_mixed": "Plain and explicit (FTPES) mode",
"webdav": "WebDAV server",
"rate_limiters": "Rate limiters"
}
}

View file

@ -234,7 +234,10 @@
"last_login": "Ultimo accesso",
"previous": "Precedente",
"next": "Successivo",
"type": "Tipo"
"type": "Tipo",
"issuer": "Emittente",
"data_provider": "Database",
"driver": "Driver"
},
"fs": {
"view_file": "Visualizza file \"{{- path}}\"",
@ -741,5 +744,33 @@
"ip": "Indirizzo IP",
"ban_time": "Bloccato fino a",
"score": "Punteggio"
},
"status": {
"desc": "Stato dei servizi",
"ssh": "Server SSH/SFTP",
"active": "Stato: attivo",
"disabled": "Stato: disabilitato",
"proxy_on": "Protocollo PROXY abilitato",
"address": "Indirizzo",
"ssh_auths": "Metodi di autenticazione",
"ssh_commands": "Comandi accettati",
"host_key": "Chiave host",
"fingeprint": "Impronta",
"algorithms": "Algoritmi",
"algorithm": "Algoritmo",
"ssh_pub_key_algo": "Algoritmi per l'autenticazione con chiave pubblica",
"ssh_mac_algo": "Algoritmi MAC",
"ssh_kex_algo": "Algoritmi KEX",
"ssh_cipher_algo": "Cifrari",
"ftp": "Server FTP",
"ftp_passive_range": "Intervallo di porte in modalità passiva",
"ftp_passive_ip": "IP per FTP passivo",
"tls": "TLS",
"tls_disabled": "Disabilitato",
"tls_explicit": "Modalità esplicita richiesta (FTPES)",
"tls_implicit": "Modalità implicita (FTPS), sconsigliato, FTPES è preferibile",
"tls_mixed": "In chiaro e modalità esplicita (FTPES)",
"webdav": "Server WebDAV",
"rate_limiters": "Rate limiters"
}
}

View file

@ -1,178 +1,217 @@
<!--
Copyright (C) 2019 Nicola Murino
Copyright (C) 2024 Nicola Murino
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, version 3.
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
This program is distributed in the hope that it will be useful,
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.
https://keenthemes.com/products/templates-mega-bundle
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
KeenThemes HTML/CSS/JS components are allowed for use only within the
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" .}}
{{define "title"}}{{.Title}}{{end}}
{{define "page_body"}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Services</h6>
{{- define "page_body"}}
<div class="card shadow-sm">
<div class="card-header bg-light">
<h3 data-i18n="{{.Title}}" class="card-title section-title"></h3>
</div>
<div class="card-body">
<div class="card mb-4 {{ if .Status.SSH.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card">
<div class="card-header bg-light">
<h3 data-i18n="status.ssh" class="card-title section-title-inner">SSH/SFTP server</h3>
</div>
<div class="card-body">
<h6 class="card-title font-weight-bold">SFTP/SSH server</h6>
<p class="card-text">
Status: {{ if .Status.SSH.IsActive}}"Started"{{else}}"Stopped"{{end}}
{{if .Status.SSH.IsActive}}
<br>
{{range .Status.SSH.Bindings}}
<br>
Address: "{{.GetAddress}}" {{if .HasProxy}}Proxy: ON{{end}}
<br>
{{end}}
Accepted authentications: "{{.Status.SSH.GetSupportedAuthsAsString}}"
<br>
Accepted commands: "{{.Status.SSH.GetSSHCommandsAsString}}"
<br>
{{range .Status.SSH.HostKeys}}
<br>
Host Key: "{{.Path}}"
<br>
Fingerprint: "{{.Fingerprint}}"
<br>
Algorithms: "{{.GetAlgosAsString}}"
<br>
{{end}}
<br>
Public key authentication algorithms: "{{.Status.SSH.GetPublicKeysAlgosAsString}}"
<br><br>
Message authentication algorithms: "{{.Status.SSH.GetMACsAsString}}"
<br><br>
Key exchange algorithms: "{{.Status.SSH.GetKEXsAsString}}"
<br><br>
Ciphers: "{{.Status.SSH.GetCiphersAsString}}"
<br>
{{end}}
<p class="fs-3 fw-semibold mb-4" {{if .Status.SSH.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
{{- if .Status.SSH.IsActive}}
<div class="d-flex flex-column">
{{- range .Status.SSH.Bindings}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.address"></span> "{{.GetAddress}}"
</p>
{{- if .HasProxy}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.proxy_on"></span>
</p>
{{- end}}
{{- end}}
</div>
{{- range .Status.SSH.HostKeys}}
<div class="d-flex flex-column mt-10">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.host_key"></span> "{{.Path}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.fingeprint"></span> "{{.Fingerprint}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.algorithms"></span> "{{.GetAlgosAsString}}"
</p>
</div>
</div>
<div class="card mb-4 {{ if .Status.FTP.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card-body">
<h6 class="card-title font-weight-bold">FTP server</h6>
<p class="card-text">
Status: {{ if .Status.FTP.IsActive}}"Started"{{else}}"Stopped"{{end}}
{{if .Status.FTP.IsActive}}
<br>
{{range .Status.FTP.Bindings}}
<br>
Address: "{{.GetAddress}}" {{if .HasProxy}}Proxy: ON{{end}}
<br>
TLS: "{{.GetTLSDescription}}"
{{if .ForcePassiveIP}}
<br>
Passive IP: {{.ForcePassiveIP}}
{{end}}
<br>
{{range .PassiveIPOverrides}}
Passive IP: {{.IP}} for networks: {{.GetNetworksAsString}}
<br>
{{end}}
{{end}}
<br>
Passive port range: "{{.Status.FTP.PassivePortRange.Start}}-{{.Status.FTP.PassivePortRange.End}}"
{{end}}
{{- end}}
<div class="d-flex flex-column mt-10">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ssh_commands"></span> "{{.Status.SSH.GetSSHCommandsAsString}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ssh_auths"></span> "{{.Status.SSH.GetSupportedAuthsAsString}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ssh_pub_key_algo"></span> "{{.Status.SSH.GetPublicKeysAlgosAsString}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ssh_mac_algo"></span> "{{.Status.SSH.GetMACsAsString}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ssh_kex_algo"></span> "{{.Status.SSH.GetKEXsAsString}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ssh_cipher_algo"></span> "{{.Status.SSH.GetCiphersAsString}}"
</p>
</div>
{{- end}}
</div>
</div>
<div class="card mb-4 {{ if .Status.WebDAV.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="status.ftp" class="card-title section-title-inner">FTP server</h3>
</div>
<div class="card-body">
<h6 class="card-title font-weight-bold">WebDAV server</h6>
<p class="card-text">
Status: {{ if .Status.WebDAV.IsActive}}"Started"{{else}}"Stopped"{{end}}
{{if .Status.WebDAV.IsActive}}
<br>
{{range .Status.WebDAV.Bindings}}
<br>
Address: "{{.GetAddress}}"
<br>
Protocol: {{if .EnableHTTPS}} HTTPS {{else}} HTTP {{end}}
<br>
{{end}}
{{end}}
<p class="fs-3 fw-semibold mb-4" {{if .Status.FTP.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
{{- if .Status.FTP.IsActive}}
<div class="d-flex flex-column">
{{- range .Status.FTP.Bindings}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.address"></span> "{{.GetAddress}}"
</p>
{{- if .HasProxy}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.proxy_on"></span>
</p>
{{- end}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.tls"></span>&nbsp;<span data-i18n="{{.GetTLSDescription}}"></span>
</p>
{{- if .ForcePassiveIP}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ftp_passive_ip"></span> "{{.ForcePassiveIP}}"
</p>
{{- end}}
{{- range .PassiveIPOverrides}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ftp_passive_ip"></span> "{{.IP}} ({{.GetNetworksAsString}})"
</p>
{{- end}}
{{- end}}
</div>
<div class="d-flex flex-column mt-10">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.ftp_passive_range"></span> "{{.Status.FTP.PassivePortRange.Start}}-{{.Status.FTP.PassivePortRange.End}}"
</p>
</div>
{{- end}}
</div>
</div>
<div class="card mb-4 {{ if .Status.AllowList.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="status.webdav" class="card-title section-title-inner">WebDAV server</h3>
</div>
<div class="card-body">
<h6 class="card-title font-weight-bold">Allow list</h6>
<p class="card-text">
Status: {{ if .Status.AllowList.IsActive}}"Enabled"{{else}}"Disabled"{{end}}
<p class="fs-3 fw-semibold mb-4" {{if .Status.WebDAV.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
{{- if .Status.WebDAV.IsActive}}
<div class="d-flex flex-column">
{{- range .Status.WebDAV.Bindings}}
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.address"></span> "{{.GetAddress}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="general.protocol"></span> {{if .EnableHTTPS}} HTTPS {{else}} HTTP {{end}}
</p>
{{- end}}
</div>
{{- end}}
</div>
</div>
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="iplist.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-header bg-light">
<h3 data-i18n="iplist.defender_list" class="card-title section-title-inner">Defender</h3>
</div>
<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>
</div>
</div>
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="status.rate_limiters" class="card-title section-title-inner">Rate limiters</h3>
</div>
<div class="card-body">
<p class="fs-3 fw-semibold mb-4" {{if .Status.RateLimiters.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
{{- if .Status.RateLimiters.IsActive}}
<div class="d-flex flex-column">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="iplist.protocols"></span> "{{.Status.RateLimiters.GetProtocolsAsString}}"
</p>
</div>
</div>
<div class="card mb-4 {{ if .Status.Defender.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card-body">
<h6 class="card-title font-weight-bold">Defender</h6>
<p class="card-text">
Status: {{ if .Status.Defender.IsActive}}"Enabled"{{else}}"Disabled"{{end}}
</p>
{{- end}}
</div>
</div>
<div class="card mb-4 {{ if .Status.RateLimiters.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card-body">
<h6 class="card-title font-weight-bold">Rate limiters</h6>
<p class="card-text">
Status: {{ if .Status.RateLimiters.IsActive}}"Enabled"{{else}}"Disabled"{{end}}
{{if .Status.RateLimiters.IsActive}}
<br>
Protocols: {{.Status.RateLimiters.GetProtocolsAsString}}
{{end}}
</p>
<div class="card mt-10">
<div class="card-header bg-light">
<h3 data-i18n="title.two_factor_auth" class="card-title section-title-inner">Two-factor authentication</h3>
</div>
</div>
<div class="card mb-4 {{ if .Status.MFA.IsActive}}border-left-success{{else}}border-left-info{{end}}">
<div class="card-body">
<h6 class="card-title font-weight-bold">Multi-factor authentication</h6>
<p class="card-text">
Status: {{ if .Status.MFA.IsActive}}"Enabled"{{else}}"Disabled"{{end}}
{{ if .Status.MFA.IsActive}}
<br>
Time-based one time passwords (RFC 6238) configurations:
<br>
<ul>
<p class="fs-3 fw-semibold mb-4" {{if .Status.MFA.IsActive}}data-i18n="status.active"{{else}}data-i18n="status.disabled"{{end}}></p>
{{- if .Status.MFA.IsActive}}
{{range .Status.MFA.TOTPConfigs}}
<li>Name: "{{.Name}}", issuer: "{{.Issuer}}", HMAC algorithm: "{{.Algo}}"</li>
{{end}}
</ul>
{{end}}
<div class="d-flex flex-column">
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="general.configuration"></span> "{{.Name}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="general.issuer"></span> "{{.Issuer}}"
</p>
<p class="fs-5 fw-semibold">
<span class="text-success" data-i18n="status.algorithm"></span> "{{.Algo}}"
</p>
</div>
{{- end}}
{{- end}}
</div>
</div>
<div class="card mb-2 {{ if .Status.DataProvider.IsActive}}border-left-success{{else}}border-left-warning{{end}}">
<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">
<h6 class="card-title font-weight-bold">Data provider</h6>
<p class="card-text">
Status: {{ if .Status.DataProvider.IsActive}}"OK"{{else}}"{{.Status.DataProvider.Error}}"{{end}}
<br>
Driver: "{{.Status.DataProvider.Driver}}"
<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>
{{end}}
{{- end}}