web UIs: add branding support
Fixes #829 Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
4bea9ed760
commit
5d7f6960f3
26 changed files with 318 additions and 128 deletions
|
@ -111,7 +111,7 @@ var (
|
|||
CrossOriginOpenerPolicy: "",
|
||||
ExpectCTHeader: "",
|
||||
},
|
||||
ExtraCSS: []httpd.CustomCSS{},
|
||||
Branding: httpd.Branding{},
|
||||
}
|
||||
defaultRateLimiter = common.RateLimiterConfig{
|
||||
Average: 0,
|
||||
|
@ -1294,23 +1294,78 @@ func getHTTPDOIDCFromEnv(idx int) (httpd.OIDC, bool) {
|
|||
return result, isSet
|
||||
}
|
||||
|
||||
func getHTTPDExtraCSSFromEnv(idx int) []httpd.CustomCSS {
|
||||
var css []httpd.CustomCSS
|
||||
func getHTTPDUIBrandingFromEnv(prefix string) (httpd.UIBranding, bool) {
|
||||
var result httpd.UIBranding
|
||||
isSet := false
|
||||
|
||||
for subIdx := 0; subIdx < 10; subIdx++ {
|
||||
var customCSS httpd.CustomCSS
|
||||
|
||||
path, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__EXTRA_CSS__%v__PATH", idx, subIdx))
|
||||
if ok {
|
||||
customCSS.Path = path
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
css = append(css, customCSS)
|
||||
}
|
||||
name, ok := os.LookupEnv(fmt.Sprintf("%s__NAME", prefix))
|
||||
if ok {
|
||||
result.Name = name
|
||||
isSet = true
|
||||
}
|
||||
|
||||
return css
|
||||
shortName, ok := os.LookupEnv(fmt.Sprintf("%s__SHORT_NAME", prefix))
|
||||
if ok {
|
||||
result.ShortName = shortName
|
||||
isSet = true
|
||||
}
|
||||
|
||||
faviconPath, ok := os.LookupEnv(fmt.Sprintf("%s__FAVICON_PATH", prefix))
|
||||
if ok {
|
||||
result.FaviconPath = faviconPath
|
||||
isSet = true
|
||||
}
|
||||
|
||||
logoPath, ok := os.LookupEnv(fmt.Sprintf("%s__LOGO_PATH", prefix))
|
||||
if ok {
|
||||
result.LogoPath = logoPath
|
||||
isSet = true
|
||||
}
|
||||
|
||||
loginImagePath, ok := os.LookupEnv(fmt.Sprintf("%s__LOGIN_IMAGE_PATH", prefix))
|
||||
if ok {
|
||||
result.LoginImagePath = loginImagePath
|
||||
isSet = true
|
||||
}
|
||||
|
||||
disclaimerName, ok := os.LookupEnv(fmt.Sprintf("%s__DISCLAIMER_NAME", prefix))
|
||||
if ok {
|
||||
result.DisclaimerName = disclaimerName
|
||||
isSet = true
|
||||
}
|
||||
|
||||
disclaimerPath, ok := os.LookupEnv(fmt.Sprintf("%s__DISCLAIMER_PATH", prefix))
|
||||
if ok {
|
||||
result.DisclaimerPath = disclaimerPath
|
||||
isSet = true
|
||||
}
|
||||
|
||||
extraCSS, ok := lookupStringListFromEnv(fmt.Sprintf("%s__EXTRA_CSS", prefix))
|
||||
if ok {
|
||||
result.ExtraCSS = extraCSS
|
||||
isSet = true
|
||||
}
|
||||
|
||||
return result, isSet
|
||||
}
|
||||
|
||||
func getHTTPDBrandingFromEnv(idx int) (httpd.Branding, bool) {
|
||||
var result httpd.Branding
|
||||
isSet := false
|
||||
|
||||
webAdmin, ok := getHTTPDUIBrandingFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__BRANDING__WEB_ADMIN", idx))
|
||||
if ok {
|
||||
result.WebAdmin = webAdmin
|
||||
isSet = true
|
||||
}
|
||||
|
||||
webClient, ok := getHTTPDUIBrandingFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__BRANDING__WEB_CLIENT", idx))
|
||||
if ok {
|
||||
result.WebClient = webClient
|
||||
isSet = true
|
||||
}
|
||||
|
||||
return result, isSet
|
||||
}
|
||||
|
||||
func getHTTPDWebClientIntegrationsFromEnv(idx int) []httpd.WebClientIntegration {
|
||||
|
@ -1372,10 +1427,9 @@ func getHTTPDNestedObjectsFromEnv(idx int, binding *httpd.Binding) bool {
|
|||
isSet = true
|
||||
}
|
||||
|
||||
extraCSS := getHTTPDExtraCSSFromEnv(idx)
|
||||
if len(extraCSS) > 0 {
|
||||
binding.ExtraCSS = extraCSS
|
||||
isSet = true
|
||||
brandingConf, ok := getHTTPDBrandingFromEnv(idx)
|
||||
if ok {
|
||||
binding.Branding = brandingConf
|
||||
}
|
||||
|
||||
return isSet
|
||||
|
|
|
@ -828,7 +828,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__PORT", "8000")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS", "0")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__HIDE_LOGIN_URL", " 1")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__EXTRA_CSS__0__PATH", "")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__BRANDING__WEB_ADMIN__NAME", "Web Admin")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__1__BRANDING__WEB_CLIENT__SHORT_NAME", "WebClient")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS", "127.0.1.1")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__PORT", "9000")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__ENABLE_WEB_ADMIN", "0")
|
||||
|
@ -869,6 +870,12 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__EXPECT_CT_HEADER", `max-age=86400, enforce, report-uri="https://foo.example/report"`)
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH", "path1")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH", "path2")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__FAVICON_PATH", "favicon.ico")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__LOGO_PATH", "logo.png")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__LOGIN_IMAGE_PATH", "login_image.png")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__DISCLAIMER_NAME", "disclaimer")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__DISCLAIMER_PATH", "disclaimer.html")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__EXTRA_CSS", "1.css,2.css")
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__ADDRESS")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__0__PORT")
|
||||
|
@ -877,6 +884,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__PORT")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__ENABLE_HTTPS")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__HIDE_LOGIN_URL")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__BRANDING__WEB_ADMIN__NAME")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__BRANDING__WEB_CLIENT__SHORT_NAME")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__1__EXTRA_CSS__0__PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__ADDRESS")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__PORT")
|
||||
|
@ -918,6 +927,12 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__EXPECT_CT_HEADER")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__0__PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__EXTRA_CSS__1__PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__FAVICON_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__LOGO_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__LOGIN_IMAGE_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__DISCLAIMER_NAME")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_ADMIN__DISCLAIMER_PATH")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__BRANDING__WEB_CLIENT__EXTRA_CSS")
|
||||
})
|
||||
|
||||
configDir := ".."
|
||||
|
@ -941,7 +956,6 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||
require.False(t, bindings[1].EnableHTTPS)
|
||||
require.Equal(t, 12, bindings[0].MinTLSVersion)
|
||||
require.Len(t, bindings[0].ExtraCSS, 0)
|
||||
require.True(t, bindings[1].EnableWebAdmin)
|
||||
require.True(t, bindings[1].EnableWebClient)
|
||||
require.True(t, bindings[1].RenderOpenAPI)
|
||||
|
@ -949,7 +963,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, 1, bindings[1].HideLoginURL)
|
||||
require.Empty(t, bindings[1].OIDC.ClientID)
|
||||
require.False(t, bindings[1].Security.Enabled)
|
||||
require.Len(t, bindings[1].ExtraCSS, 0)
|
||||
require.Equal(t, "Web Admin", bindings[1].Branding.WebAdmin.Name)
|
||||
require.Equal(t, "WebClient", bindings[1].Branding.WebClient.ShortName)
|
||||
require.Equal(t, 9000, bindings[2].Port)
|
||||
require.Equal(t, "127.0.1.1", bindings[2].Address)
|
||||
require.True(t, bindings[2].EnableHTTPS)
|
||||
|
@ -997,9 +1012,14 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "fullscreen=(), geolocation=()", bindings[2].Security.PermissionsPolicy)
|
||||
require.Equal(t, "same-origin", bindings[2].Security.CrossOriginOpenerPolicy)
|
||||
require.Equal(t, `max-age=86400, enforce, report-uri="https://foo.example/report"`, bindings[2].Security.ExpectCTHeader)
|
||||
require.Len(t, bindings[2].ExtraCSS, 2)
|
||||
require.Equal(t, "path1", bindings[2].ExtraCSS[0].Path)
|
||||
require.Equal(t, "path2", bindings[2].ExtraCSS[1].Path)
|
||||
require.Equal(t, "favicon.ico", bindings[2].Branding.WebAdmin.FaviconPath)
|
||||
require.Equal(t, "logo.png", bindings[2].Branding.WebClient.LogoPath)
|
||||
require.Equal(t, "login_image.png", bindings[2].Branding.WebAdmin.LoginImagePath)
|
||||
require.Equal(t, "disclaimer", bindings[2].Branding.WebClient.DisclaimerName)
|
||||
require.Equal(t, "disclaimer.html", bindings[2].Branding.WebAdmin.DisclaimerPath)
|
||||
require.Len(t, bindings[2].Branding.WebClient.ExtraCSS, 2)
|
||||
require.Equal(t, "1.css", bindings[2].Branding.WebClient.ExtraCSS[0])
|
||||
require.Equal(t, "2.css", bindings[2].Branding.WebClient.ExtraCSS[1])
|
||||
}
|
||||
|
||||
func TestHTTPClientCertificatesFromEnv(t *testing.T) {
|
||||
|
|
|
@ -272,8 +272,15 @@ The configuration file contains the following sections:
|
|||
- `permissions_policy`, string. Allows to set the `Permissions-Policy` header value. Default: blank.
|
||||
- `cross_origin_opener_policy`, string. Allows to set the `Cross-Origin-Opener-Policy` header value. Default: blank.
|
||||
- `expect_ct_header`, string. Allows to set the `Expect-CT` header value. Default: blank.
|
||||
- `extra_css`, list of structs. Defines additional CSS files. Each struct has the following fields:
|
||||
- `path`, string. Path to the CSS file relative to `static_files_path`. For example, if you create a directory named `extra_css` inside the static dir and put the `my.css` file in it, you must set `/extra_css/my.css` as path.
|
||||
- `branding`, struct. Defines the supported customizations to suit your brand. It contains the `web_admin` and `web_client` structs that define customizations for the WebAdmin and the WebClient UIs. Each customization struct contains the following fields:
|
||||
- `name`, string. Defines the UI name
|
||||
- `short_name`, string. Define the short name to show next to the logo image
|
||||
- `favicon_path`, string. Path to the favicon relative to `static_files_path`. For example, if you create a directory named `branding` inside the static dir and put the `favicon.ico` file in it, you must set `/branding/favicon.ico` as path.
|
||||
- `logo_path`, string. Path to your logo relative to `static_files_path`. The preferred image size is 256x256 pixel
|
||||
- `login_image_path`, string. Path to a custom image to show on the login screen relative to `static_files_path`. The preferred image size is 900x900 pixel
|
||||
- `disclaimer_name`, string. Name for your optional disclaimer
|
||||
- `disclaimer_path`, string. Path to the HTML page with the disclaimer relative to `static_files_path`
|
||||
- `extra_css`, list of strings. Defines the paths, relative to `static_files_path`, to additional CSS files
|
||||
- `templates_path`, string. Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
|
||||
- `static_files_path`, string. Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir. If both `templates_path` and `static_files_path` are empty the built-in web interface will be disabled
|
||||
- `openapi_path`, string. Path to the directory that contains the OpenAPI schema and the default renderer. This can be an absolute path or a path relative to the config dir. If empty the OpenAPI schema and the renderer will not be served regardless of the `render_openapi` directive
|
||||
|
|
|
@ -314,12 +314,56 @@ func (s *SecurityConf) getHTTPSProxyHeaders() map[string]string {
|
|||
return headers
|
||||
}
|
||||
|
||||
// CustomCSS defines the configuration for custom CSS
|
||||
type CustomCSS struct {
|
||||
// Path to the CSS file relative to "static_files_path".
|
||||
// For example, if you create a directory named "extra_css" inside the static dir
|
||||
// and put the "my.css" file in it, you must set "/extra_css/my.css" as path.
|
||||
Path string `json:"path" mapstructure:"path"`
|
||||
// UIBranding defines the supported customizations for the web UIs
|
||||
type UIBranding struct {
|
||||
// Name defines the text to show at the login page and as HTML title
|
||||
Name string `json:"name" mapstructure:"name"`
|
||||
// ShortName defines the name to show next to the logo image
|
||||
ShortName string `json:"short_name" mapstructure:"short_name"`
|
||||
// Path to your logo relative to "static_files_path".
|
||||
// For example, if you create a directory named "branding" inside the static dir and
|
||||
// put the "mylogo.png" file in it, you must set "/branding/mylogo.png" as logo path.
|
||||
LogoPath string `json:"logo_path" mapstructure:"logo_path"`
|
||||
// Path to the image to show on the login screen relative to "static_files_path"
|
||||
LoginImagePath string `json:"login_image_path" mapstructure:"login_image_path"`
|
||||
// Path to your favicon relative to "static_files_path"
|
||||
FaviconPath string `json:"favicon_path" mapstructure:"favicon_path"`
|
||||
// DisclaimerName defines the name for the link to your optional disclaimer
|
||||
DisclaimerName string `json:"disclaimer_name" mapstructure:"disclaimer_name"`
|
||||
// Path to the HTML page for your disclaimer relative to "static_files_path".
|
||||
DisclaimerPath string `json:"disclaimer_path" mapstructure:"disclaimer_path"`
|
||||
// Additional CSS file paths, relative to "static_files_path", to include
|
||||
ExtraCSS []string `json:"extra_css" mapstructure:"extra_css"`
|
||||
}
|
||||
|
||||
func (b *UIBranding) check() {
|
||||
if b.LogoPath != "" {
|
||||
b.LogoPath = util.CleanPath(b.LogoPath)
|
||||
} else {
|
||||
b.LogoPath = "/img/logo.png"
|
||||
}
|
||||
if b.LoginImagePath != "" {
|
||||
b.LoginImagePath = util.CleanPath(b.LoginImagePath)
|
||||
} else {
|
||||
b.LoginImagePath = "/img/login_image.png"
|
||||
}
|
||||
if b.FaviconPath != "" {
|
||||
b.FaviconPath = util.CleanPath(b.FaviconPath)
|
||||
} else {
|
||||
b.FaviconPath = "/favicon.ico"
|
||||
}
|
||||
if b.DisclaimerPath != "" {
|
||||
b.DisclaimerPath = util.CleanPath(b.DisclaimerPath)
|
||||
}
|
||||
for idx := range b.ExtraCSS {
|
||||
b.ExtraCSS[idx] = util.CleanPath(b.ExtraCSS[idx])
|
||||
}
|
||||
}
|
||||
|
||||
// Branding defines the branding-related customizations supported
|
||||
type Branding struct {
|
||||
WebAdmin UIBranding `json:"web_admin" mapstructure:"web_admin"`
|
||||
WebClient UIBranding `json:"web_client" mapstructure:"web_client"`
|
||||
}
|
||||
|
||||
// WebClientIntegration defines the configuration for an external Web Client integration
|
||||
|
@ -379,8 +423,8 @@ type Binding struct {
|
|||
OIDC OIDC `json:"oidc" mapstructure:"oidc"`
|
||||
// Security defines security headers to add to HTTP responses and allows to restrict allowed hosts
|
||||
Security SecurityConf `json:"security" mapstructure:"security"`
|
||||
// Additional CSS
|
||||
ExtraCSS []CustomCSS `json:"extra_css" mapstructure:"extra_css"`
|
||||
// Branding defines customizations to suit your brand
|
||||
Branding Branding `json:"branding" mapstructure:"branding"`
|
||||
allowHeadersFrom []func(net.IP) bool
|
||||
}
|
||||
|
||||
|
@ -394,14 +438,21 @@ func (b *Binding) checkWebClientIntegrations() {
|
|||
b.WebClientIntegrations = integrations
|
||||
}
|
||||
|
||||
func (b *Binding) checkExtraCSS() {
|
||||
var extraCSS []CustomCSS
|
||||
for _, css := range b.ExtraCSS {
|
||||
extraCSS = append(extraCSS, CustomCSS{
|
||||
Path: path.Join("/", css.Path),
|
||||
})
|
||||
func (b *Binding) checkBranding() {
|
||||
b.Branding.WebAdmin.check()
|
||||
b.Branding.WebClient.check()
|
||||
if b.Branding.WebAdmin.Name == "" {
|
||||
b.Branding.WebAdmin.Name = "SFTPGo WebAdmin"
|
||||
}
|
||||
if b.Branding.WebAdmin.ShortName == "" {
|
||||
b.Branding.WebAdmin.ShortName = "WebAdmin"
|
||||
}
|
||||
if b.Branding.WebClient.Name == "" {
|
||||
b.Branding.WebClient.Name = "SFTPGo WebClient"
|
||||
}
|
||||
if b.Branding.WebClient.ShortName == "" {
|
||||
b.Branding.WebClient.ShortName = "WebClient"
|
||||
}
|
||||
b.ExtraCSS = extraCSS
|
||||
}
|
||||
|
||||
func (b *Binding) parseAllowedProxy() error {
|
||||
|
@ -642,7 +693,7 @@ func (c *Conf) Initialize(configDir string) error {
|
|||
return err
|
||||
}
|
||||
binding.checkWebClientIntegrations()
|
||||
binding.checkExtraCSS()
|
||||
binding.checkBranding()
|
||||
binding.Security.updateProxyHeaders()
|
||||
|
||||
go func(b Binding) {
|
||||
|
|
|
@ -301,21 +301,31 @@ func TestShouldBind(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestExtraCSSValidation(t *testing.T) {
|
||||
func TestBrandingValidation(t *testing.T) {
|
||||
b := Binding{
|
||||
ExtraCSS: []CustomCSS{
|
||||
{
|
||||
Path: "path1",
|
||||
Branding: Branding{
|
||||
WebAdmin: UIBranding{
|
||||
LogoPath: "path1",
|
||||
LoginImagePath: "login1.png",
|
||||
},
|
||||
{
|
||||
Path: "../path2",
|
||||
WebClient: UIBranding{
|
||||
FaviconPath: "favicon1.ico",
|
||||
DisclaimerPath: "../path2",
|
||||
ExtraCSS: []string{"1.css"},
|
||||
},
|
||||
},
|
||||
}
|
||||
b.checkExtraCSS()
|
||||
require.Len(t, b.ExtraCSS, 2)
|
||||
assert.Equal(t, "/path1", b.ExtraCSS[0].Path)
|
||||
assert.Equal(t, "/path2", b.ExtraCSS[1].Path)
|
||||
b.checkBranding()
|
||||
assert.Equal(t, "/favicon.ico", b.Branding.WebAdmin.FaviconPath)
|
||||
assert.Equal(t, "/path1", b.Branding.WebAdmin.LogoPath)
|
||||
assert.Equal(t, "/login1.png", b.Branding.WebAdmin.LoginImagePath)
|
||||
assert.Len(t, b.Branding.WebAdmin.ExtraCSS, 0)
|
||||
assert.Equal(t, "/favicon1.ico", b.Branding.WebClient.FaviconPath)
|
||||
assert.Equal(t, "/path2", b.Branding.WebClient.DisclaimerPath)
|
||||
assert.Equal(t, "/img/login_image.png", b.Branding.WebClient.LoginImagePath)
|
||||
if assert.Len(t, b.Branding.WebClient.ExtraCSS, 1) {
|
||||
assert.Equal(t, "/1.css", b.Branding.WebClient.ExtraCSS[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedactedConf(t *testing.T) {
|
||||
|
|
|
@ -140,7 +140,7 @@ func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, error, ip str
|
|||
Error: error,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
if s.binding.showAdminLoginURL() {
|
||||
data.AltLoginURL = webAdminLoginPath
|
||||
|
@ -516,7 +516,7 @@ func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, error, ip stri
|
|||
Error: error,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
if s.binding.showClientLoginURL() {
|
||||
data.AltLoginURL = webClientLoginPath
|
||||
|
|
|
@ -32,7 +32,7 @@ type loginPage struct {
|
|||
AltLoginURL string
|
||||
ForgotPwdURL string
|
||||
OpenIDLoginURL string
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type twoFactorPage struct {
|
||||
|
@ -42,7 +42,7 @@ type twoFactorPage struct {
|
|||
CSRFToken string
|
||||
StaticURL string
|
||||
RecoveryURL string
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type forgotPwdPage struct {
|
||||
|
@ -51,7 +51,7 @@ type forgotPwdPage struct {
|
|||
CSRFToken string
|
||||
StaticURL string
|
||||
Title string
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type resetPwdPage struct {
|
||||
|
@ -60,7 +60,7 @@ type resetPwdPage struct {
|
|||
CSRFToken string
|
||||
StaticURL string
|
||||
Title string
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
func getSliceFromDelimitedValues(values, delimiter string) []string {
|
||||
|
|
|
@ -131,7 +131,7 @@ type basePage struct {
|
|||
HasDefender bool
|
||||
HasExternalLogin bool
|
||||
LoggedAdmin *dataprovider.Admin
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type usersPage struct {
|
||||
|
@ -465,7 +465,7 @@ func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request)
|
|||
HasDefender: common.Config.DefenderConfig.Enabled,
|
||||
HasExternalLogin: isLoggedInWithOIDC(r),
|
||||
CSRFToken: csrfToken,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,7 +518,7 @@ func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, error, ip strin
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
Title: pageForgotPwdTitle,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
renderAdminTemplate(w, templateForgotPassword, data)
|
||||
}
|
||||
|
@ -530,7 +530,7 @@ func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, error, ip string
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
Title: pageResetPwdTitle,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
renderAdminTemplate(w, templateResetPassword, data)
|
||||
}
|
||||
|
@ -543,7 +543,7 @@ func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, error, ip strin
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
RecoveryURL: webAdminTwoFactorRecoveryPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
renderAdminTemplate(w, templateTwoFactor, data)
|
||||
}
|
||||
|
@ -555,7 +555,7 @@ func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, error,
|
|||
Error: error,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
renderAdminTemplate(w, templateTwoFactorRecovery, data)
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ type baseClientPage struct {
|
|||
CSRFToken string
|
||||
HasExternalLogin bool
|
||||
LoggedUser *dataprovider.User
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type dirMapping struct {
|
||||
|
@ -110,7 +110,7 @@ type viewPDFPage struct {
|
|||
Title string
|
||||
URL string
|
||||
StaticURL string
|
||||
ExtraCSS []CustomCSS
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type editFilePage struct {
|
||||
|
@ -339,7 +339,7 @@ func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Re
|
|||
CSRFToken: csrfToken,
|
||||
HasExternalLogin: isLoggedInWithOIDC(r),
|
||||
LoggedUser: getUserFromToken(r),
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,7 +350,7 @@ func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, error, ip
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
Title: pageClientForgotPwdTitle,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
renderClientTemplate(w, templateForgotPassword, data)
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, error, ip
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
Title: pageClientResetPwdTitle,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
renderClientTemplate(w, templateResetPassword, data)
|
||||
}
|
||||
|
@ -415,7 +415,7 @@ func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, error, ip
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
RecoveryURL: webClientTwoFactorRecoveryPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
renderClientTemplate(w, templateTwoFactor, data)
|
||||
}
|
||||
|
@ -427,7 +427,7 @@ func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, e
|
|||
Error: error,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
StaticURL: webStaticFilesPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
renderClientTemplate(w, templateTwoFactorRecovery, data)
|
||||
}
|
||||
|
@ -1263,7 +1263,7 @@ func (s *httpdServer) handleClientViewPDF(w http.ResponseWriter, r *http.Request
|
|||
Title: path.Base(name),
|
||||
URL: fmt.Sprintf("%v?path=%v&inline=1", webClientFilesPath, url.QueryEscape(name)),
|
||||
StaticURL: webStaticFilesPath,
|
||||
ExtraCSS: s.binding.ExtraCSS,
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
renderClientTemplate(w, templateClientViewPDF, data)
|
||||
}
|
||||
|
|
|
@ -7001,9 +7001,9 @@ func TestHashedPasswords(t *testing.T) {
|
|||
user.Password = clearPwd
|
||||
conn, client, err := getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err, "unable to login with password %#v", pwd) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
conn.Close()
|
||||
client.Close()
|
||||
}
|
||||
user.Password = pwd
|
||||
conn, client, err = getSftpClient(user, usePubKey)
|
||||
|
@ -7026,9 +7026,9 @@ func TestHashedPasswords(t *testing.T) {
|
|||
user.Password = clearPwd
|
||||
conn, client, err = getSftpClient(user, usePubKey)
|
||||
if assert.NoError(t, err, "unable to login with password %#v", pwd) {
|
||||
defer conn.Close()
|
||||
defer client.Close()
|
||||
assert.NoError(t, checkBasicSFTP(client))
|
||||
conn.Close()
|
||||
client.Close()
|
||||
}
|
||||
_, err = httpdtest.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
|
23
sftpgo.json
23
sftpgo.json
|
@ -258,7 +258,28 @@
|
|||
"cross_origin_opener_policy": "",
|
||||
"expect_ct_header": ""
|
||||
},
|
||||
"extra_css": []
|
||||
"branding": {
|
||||
"web_admin": {
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"favicon_path": "",
|
||||
"logo_path": "",
|
||||
"login_image_path": "",
|
||||
"disclaimer_name": "",
|
||||
"disclaimer_path": "",
|
||||
"extra_css": []
|
||||
},
|
||||
"web_client": {
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"favicon_path": "",
|
||||
"logo_path": "",
|
||||
"login_image_path": "",
|
||||
"disclaimer_name": "",
|
||||
"disclaimer_path": "",
|
||||
"extra_css": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"templates_path": "templates",
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
.bg-login-image {
|
||||
background-image: url(/static/img/logo.png);
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.row.login-image {
|
||||
height: 200px;
|
||||
}
|
BIN
static/img/login_image.png
Normal file
BIN
static/img/login_image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
Binary file not shown.
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 14 KiB |
|
@ -9,9 +9,9 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{{.Title}}</title>
|
||||
<title>{{.Branding.Name}} - Forgot password</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
|
@ -70,8 +70,8 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{{.Title}}</title>
|
||||
<title>{{.Branding.Name}} - Reset password</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
|
@ -70,8 +70,8 @@
|
|||
}
|
||||
</style>
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
<title>SFTPGo - Setup</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>SFTPGo Admin - {{template "title" .}}</title>
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom fonts for this template-->
|
||||
<link href="{{.StaticURL}}/vendor/fontawesome-free/css/fontawesome.min.css" rel="stylesheet" type="text/css">
|
||||
|
@ -53,8 +53,8 @@
|
|||
</style>
|
||||
{{block "extra_css" .}}{{end}}
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
@ -69,12 +69,12 @@
|
|||
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center" href="{{.UsersURL}}">
|
||||
<div class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<div class="sidebar-brand-icon">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<img src="{{.StaticURL}}{{.Branding.LogoPath}}" alt="logo" style="width: 2rem; height: auto;">
|
||||
</div>
|
||||
<div class="sidebar-brand-text mx-3" style="text-transform: none;">SFTPGo Admin</div>
|
||||
</a>
|
||||
<div class="sidebar-brand-text mx-3" style="text-transform: none;">{{.Branding.ShortName}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<hr class="sidebar-divider my-0">
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>SFTPGo Admin - {{template "title" .}}</title>
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
|
@ -70,10 +70,21 @@
|
|||
border-radius: 10rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.bg-login-image {
|
||||
background-image: url('{{.StaticURL}}{{.Branding.LoginImagePath}}');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.row.login-image {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
@ -95,9 +106,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 d-none d-lg-block bg-login-image">
|
||||
<div class="col-lg-5 d-none d-lg-block bg-login-image">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="col-lg-7">
|
||||
<div class="p-5">
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{{define "content"}}
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">SFTPGo Admin - {{.Version}}</h1>
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.Name}} - {{.Version}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
|
@ -43,4 +43,10 @@
|
|||
<a class="small" href="{{.AltLoginURL}}">Web Client</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Branding.DisclaimerName .Branding.DisclaimerPath}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="{{.Branding.DisclaimerPath}}" target="_blank">{{.Branding.DisclaimerName}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{{define "content"}}
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">SFTPGo Admin - {{.Version}}</h1>
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.Name}} - {{.Version}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
{{define "content"}}
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">SFTPGo Admin - {{.Version}}</h1>
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.Name}} - {{.Version}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>SFTPGo WebClient - {{template "title" .}}</title>
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom fonts for this template-->
|
||||
<link href="{{.StaticURL}}/vendor/fontawesome-free/css/fontawesome.min.css" rel="stylesheet" type="text/css">
|
||||
|
@ -53,8 +53,8 @@
|
|||
</style>
|
||||
{{block "extra_css" .}}{{end}}
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
@ -70,7 +70,10 @@
|
|||
|
||||
<!-- Sidebar - Brand -->
|
||||
<div class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<div style="text-transform: none;">SFTPGo WebClient</div>
|
||||
<div class="sidebar-brand-icon">
|
||||
<img src="{{.StaticURL}}{{.Branding.LogoPath}}" alt="logo" style="width: 2rem; height: auto;">
|
||||
</div>
|
||||
<div class="sidebar-brand-text mx-3" style="text-transform: none;">{{.Branding.ShortName}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>SFTPGo WebClient - {{template "title" .}}</title>
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
|
||||
|
@ -70,10 +70,21 @@
|
|||
border-radius: 10rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.bg-login-image {
|
||||
background-image: url('{{.StaticURL}}{{.Branding.LoginImagePath}}');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.row.login-image {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
@ -95,12 +106,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 d-none d-lg-block bg-login-image">
|
||||
<div class="col-lg-5 d-none d-lg-block bg-login-image">
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="col-lg-7">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">SFTPGo WebClient - {{.Version}}</h1>
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.Name}} - {{.Version}}</h1>
|
||||
</div>
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
|
|
|
@ -40,4 +40,10 @@
|
|||
<a class="small" href="{{.AltLoginURL}}">Web Admin</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Branding.DisclaimerName .Branding.DisclaimerPath}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="{{.Branding.DisclaimerPath}}" target="_blank">{{.Branding.DisclaimerName}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
|
@ -5,8 +5,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{.Title}}</title>
|
||||
|
||||
{{range .ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.Path}}" rel="stylesheet" type="text/css">
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
</head>
|
||||
|
||||
|
|
Loading…
Reference in a new issue