web admin: make base url configurable

This commit is contained in:
Nicola Murino 2021-04-09 22:02:48 +02:00
parent 5acf29dae6
commit 0bc4db9950
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
14 changed files with 241 additions and 143 deletions

View file

@ -218,8 +218,11 @@ func Init() {
TemplatesPath: "templates",
StaticFilesPath: "static",
BackupsPath: "backups",
WebAdminRoot: "",
CertificateFile: "",
CertificateKeyFile: "",
CACertificates: nil,
CARevocationLists: nil,
},
HTTPConfig: httpclient.Config{
Timeout: 20,
@ -857,6 +860,7 @@ func setViperDefaults() {
viper.SetDefault("httpd.templates_path", globalConf.HTTPDConfig.TemplatesPath)
viper.SetDefault("httpd.static_files_path", globalConf.HTTPDConfig.StaticFilesPath)
viper.SetDefault("httpd.backups_path", globalConf.HTTPDConfig.BackupsPath)
viper.SetDefault("httpd.web_admin_root", globalConf.HTTPDConfig.WebAdminRoot)
viper.SetDefault("httpd.certificate_file", globalConf.HTTPDConfig.CertificateFile)
viper.SetDefault("httpd.certificate_key_file", globalConf.HTTPDConfig.CertificateKeyFile)
viper.SetDefault("httpd.ca_certificates", globalConf.HTTPDConfig.CACertificates)

View file

@ -204,6 +204,7 @@ The configuration file contains the following sections:
- `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
- `backups_path`, string. Path to the backup directory. This can be an absolute path or a path relative to the config dir. We don't allow backups in arbitrary paths for security reasons
- `web_admin_root`, string. Defines a base URL for the web admin. If empty web admin resources will be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored
- `certificate_file`, string. Certificate for HTTPS. This can be an absolute path or a path relative to the config dir.
- `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If both the certificate and the private key are provided, the server will expect HTTPS connections. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
- `ca_certificates`, list of strings. Set of root certificate authorities to be used to verify client certificates.

View file

@ -8,6 +8,7 @@ import (
"fmt"
"net/http"
"net/url"
"path"
"path/filepath"
"runtime"
"strings"
@ -27,46 +28,47 @@ import (
)
const (
logSender = "httpd"
tokenPath = "/api/v2/token"
logoutPath = "/api/v2/logout"
activeConnectionsPath = "/api/v2/connections"
quotaScanPath = "/api/v2/quota-scans"
quotaScanVFolderPath = "/api/v2/folder-quota-scans"
userPath = "/api/v2/users"
versionPath = "/api/v2/version"
folderPath = "/api/v2/folders"
serverStatusPath = "/api/v2/status"
dumpDataPath = "/api/v2/dumpdata"
loadDataPath = "/api/v2/loaddata"
updateUsedQuotaPath = "/api/v2/quota-update"
updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
defenderBanTime = "/api/v2/defender/bantime"
defenderUnban = "/api/v2/defender/unban"
defenderScore = "/api/v2/defender/score"
adminPath = "/api/v2/admins"
adminPwdPath = "/api/v2/changepwd/admin"
healthzPath = "/healthz"
webBasePath = "/web"
webLoginPath = "/web/login"
webLogoutPath = "/web/logout"
webUsersPath = "/web/users"
webUserPath = "/web/user"
webConnectionsPath = "/web/connections"
webFoldersPath = "/web/folders"
webFolderPath = "/web/folder"
webStatusPath = "/web/status"
webAdminsPath = "/web/admins"
webAdminPath = "/web/admin"
webMaintenancePath = "/web/maintenance"
webBackupPath = "/web/backup"
webRestorePath = "/web/restore"
webScanVFolderPath = "/web/folder-quota-scans"
webQuotaScanPath = "/web/quota-scans"
webChangeAdminPwdPath = "/web/changepwd/admin"
webTemplateUser = "/web/template/user"
webTemplateFolder = "/web/template/folder"
webStaticFilesPath = "/static"
logSender = "httpd"
tokenPath = "/api/v2/token"
logoutPath = "/api/v2/logout"
activeConnectionsPath = "/api/v2/connections"
quotaScanPath = "/api/v2/quota-scans"
quotaScanVFolderPath = "/api/v2/folder-quota-scans"
userPath = "/api/v2/users"
versionPath = "/api/v2/version"
folderPath = "/api/v2/folders"
serverStatusPath = "/api/v2/status"
dumpDataPath = "/api/v2/dumpdata"
loadDataPath = "/api/v2/loaddata"
updateUsedQuotaPath = "/api/v2/quota-update"
updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
defenderBanTime = "/api/v2/defender/bantime"
defenderUnban = "/api/v2/defender/unban"
defenderScore = "/api/v2/defender/score"
adminPath = "/api/v2/admins"
adminPwdPath = "/api/v2/changepwd/admin"
healthzPath = "/healthz"
webRootPathDefault = "/"
webBasePathDefault = "/web"
webLoginPathDefault = "/web/login"
webLogoutPathDefault = "/web/logout"
webUsersPathDefault = "/web/users"
webUserPathDefault = "/web/user"
webConnectionsPathDefault = "/web/connections"
webFoldersPathDefault = "/web/folders"
webFolderPathDefault = "/web/folder"
webStatusPathDefault = "/web/status"
webAdminsPathDefault = "/web/admins"
webAdminPathDefault = "/web/admin"
webMaintenancePathDefault = "/web/maintenance"
webBackupPathDefault = "/web/backup"
webRestorePathDefault = "/web/restore"
webScanVFolderPathDefault = "/web/folder-quota-scans"
webQuotaScanPathDefault = "/web/quota-scans"
webChangeAdminPwdPathDefault = "/web/changepwd/admin"
webTemplateUserDefault = "/web/template/user"
webTemplateFolderDefault = "/web/template/folder"
webStaticFilesPathDefault = "/static"
// MaxRestoreSize defines the max size for the loaddata input file
MaxRestoreSize = 10485760 // 10 MB
maxRequestSize = 1048576 // 1MB
@ -80,8 +82,33 @@ var (
jwtTokensCleanupDone chan bool
invalidatedJWTTokens sync.Map
csrfTokenAuth *jwtauth.JWTAuth
webRootPath string
webBasePath string
webLoginPath string
webLogoutPath string
webUsersPath string
webUserPath string
webConnectionsPath string
webFoldersPath string
webFolderPath string
webStatusPath string
webAdminsPath string
webAdminPath string
webMaintenancePath string
webBackupPath string
webRestorePath string
webScanVFolderPath string
webQuotaScanPath string
webChangeAdminPwdPath string
webTemplateUser string
webTemplateFolder string
webStaticFilesPath string
)
func init() {
updateWebAdminURLs("")
}
// Binding defines the configuration for a network listener
type Binding struct {
// The address to listen on. A blank value means listen on all available network interfaces.
@ -153,6 +180,9 @@ type Conf struct {
StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
// Path to the backup directory. This can be an absolute path or a path relative to the config dir
BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
// Defines a base URL for the web admin. If empty web admin resources will be available at the
// root ("/") URI. If defined it must be an absolute URI or it will be ignored.
WebAdminRoot string `json:"web_admin_root" mapstructure:"web_admin_root"`
// If files containing a certificate and matching private key for the server are provided the server will expect
// HTTPS connections.
// Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
@ -199,6 +229,7 @@ func (c *Conf) Initialize(configDir string) error {
certificateFile := getConfigPath(c.CertificateFile, configDir)
certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
if enableWebAdmin {
updateWebAdminURLs(c.WebAdminRoot)
loadTemplates(templatesPath)
} else {
logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it")
@ -298,6 +329,33 @@ func fileServer(r chi.Router, path string, root http.FileSystem) {
})
}
func updateWebAdminURLs(baseURL string) {
if !path.IsAbs(baseURL) {
baseURL = "/"
}
webRootPath = path.Join(baseURL, webRootPathDefault)
webBasePath = path.Join(baseURL, webBasePathDefault)
webLoginPath = path.Join(baseURL, webLoginPathDefault)
webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
webUsersPath = path.Join(baseURL, webUsersPathDefault)
webUserPath = path.Join(baseURL, webUserPathDefault)
webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
webFolderPath = path.Join(baseURL, webFolderPathDefault)
webStatusPath = path.Join(baseURL, webStatusPathDefault)
webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
webAdminPath = path.Join(baseURL, webAdminPathDefault)
webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
webBackupPath = path.Join(baseURL, webBackupPathDefault)
webRestorePath = path.Join(baseURL, webRestorePathDefault)
webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
}
// GetHTTPRouter returns an HTTP handler suitable to use for test cases
func GetHTTPRouter() http.Handler {
b := Binding{

View file

@ -257,6 +257,7 @@ func (s *httpdServer) initializeRouter() {
s.router.Use(saveConnectionAddress)
s.router.Use(middleware.GetHead)
s.router.Use(middleware.StripSlashes)
s.router.Group(func(r chi.Router) {
r.Get(healthzPath, func(w http.ResponseWriter, r *http.Request) {
@ -334,7 +335,7 @@ func (s *httpdServer) initializeRouter() {
})
if s.enableWebAdmin {
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
router.Get(webRootPath, func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, webLoginPath, http.StatusMovedPermanently)
})

View file

@ -96,6 +96,7 @@ type basePage struct {
FolderQuotaScanURL string
StatusURL string
MaintenanceURL string
StaticURL string
UsersTitle string
AdminsTitle string
ConnectionsTitle string
@ -182,6 +183,7 @@ type loginPage struct {
Version string
Error string
CSRFToken string
StaticURL string
}
type userTemplateFields struct {
@ -290,6 +292,7 @@ func getBasePageData(title, currentURL string, r *http.Request) basePage {
StatusURL: webStatusPath,
FolderQuotaScanURL: webScanVFolderPath,
MaintenanceURL: webMaintenancePath,
StaticURL: webStaticFilesPath,
UsersTitle: pageUsersTitle,
AdminsTitle: pageAdminsTitle,
ConnectionsTitle: pageConnectionsTitle,
@ -1030,6 +1033,7 @@ func renderLoginPage(w http.ResponseWriter, error string) {
Version: version.Get().Version,
Error: error,
CSRFToken: createCSRFToken(),
StaticURL: webStaticFilesPath,
}
renderTemplate(w, templateLogin, data)
}

View file

@ -165,6 +165,7 @@
"templates_path": "templates",
"static_files_path": "static",
"backups_path": "backups",
"web_admin_root": "",
"certificate_file": "",
"certificate_key_file": "",
"ca_certificates": [],

View file

@ -1,20 +0,0 @@
@font-face {
font-family: 'Roboto';
src: url('/static/vendor/fonts/Roboto-Bold-webfont.woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('/static/vendor/fonts/Roboto-Regular-webfont.woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('/static/vendor/fonts/Roboto-Light-webfont.woff');
font-weight: 300;
font-style: normal;
}

View file

@ -3,11 +3,11 @@
{{define "title"}}{{.Title}}{{end}}
{{define "extra_css"}}
<link href="/static/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<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"}}
@ -83,15 +83,15 @@
{{end}}
{{define "extra_js"}}
<script src="/static/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="/static/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="/static/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="/static/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="/static/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.select.min.js"></script>
<script src="/static/vendor/datatables/ellipsis.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/ellipsis.js"></script>
<script type="text/javascript">
function deleteAction() {

View file

@ -12,16 +12,36 @@
<title>SFTPGo - {{template "title" .}}</title>
<link rel="shortcut icon" href="/static/favicon.ico" />
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
<!-- Custom fonts for this template-->
<link href="/static/vendor/fontawesome-free/css/fontawesome.min.css" rel="stylesheet" type="text/css">
<link href="/static/vendor/fontawesome-free/css/solid.min.css" rel="stylesheet" type="text/css">
<link href="/static/css/fonts.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/fontawesome-free/css/fontawesome.min.css" rel="stylesheet" type="text/css">
<link href="{{.StaticURL}}/vendor/fontawesome-free/css/solid.min.css" rel="stylesheet" type="text/css">
<!-- Custom styles for this template-->
<link href="/static/css/sb-admin-2.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
<style>
@font-face {
font-family: 'Roboto';
src: url('{{.StaticURL}}/vendor/fonts/Roboto-Bold-webfont.woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('{{.StaticURL}}/vendor/fonts/Roboto-Regular-webfont.woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('{{.StaticURL}}/vendor/fonts/Roboto-Light-webfont.woff');
font-weight: 300;
font-style: normal;
}
div.dt-buttons {
margin-bottom: 1em;
}
@ -212,14 +232,14 @@
{{block "dialog" .}}{{end}}
<!-- Bootstrap core JavaScript-->
<script src="/static/vendor/jquery/jquery.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="/static/js/sb-admin-2.min.js"></script>
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
<script type="text/javascript">
function fixedEncodeURIComponent(str) {

View file

@ -3,11 +3,11 @@
{{define "title"}}{{.Title}}{{end}}
{{define "extra_css"}}
<link href="/static/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<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"}}
@ -20,7 +20,6 @@
<h6 class="m-0 font-weight-bold text-primary">View and manage connections</h6>
</div>
<div class="card-body">
{{if .Connections}}
<div class="table-responsive">
<table class="table table-striped table-bordered nowrap" id="dataTable" width="100%" cellspacing="0">
<thead>
@ -45,11 +44,6 @@
</tbody>
</table>
</div>
{{else}}
<div class="card mb-2 border-left-info">
<div class="card-body">No user connected</div>
</div>
{{end}}
</div>
</div>
{{end}}
@ -82,14 +76,14 @@
{{end}}
{{define "extra_js"}}
<script src="/static/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="/static/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="/static/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="/static/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="/static/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.select.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
<script type="text/javascript">
function disconnectAction() {
@ -151,6 +145,9 @@
"scrollX": false,
"scrollY": false,
"responsive": true,
"language": {
"emptyTable": "No user connected"
},
"order": [[1, 'asc']]
});

View file

@ -3,11 +3,11 @@
{{define "title"}}{{.Title}}{{end}}
{{define "extra_css"}}
<link href="/static/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<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"}}
@ -81,15 +81,15 @@
{{end}}
{{define "extra_js"}}
<script src="/static/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="/static/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="/static/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="/static/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="/static/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.select.min.js"></script>
<script src="/static/vendor/datatables/ellipsis.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/ellipsis.js"></script>
<script type="text/javascript">
function deleteAction() {
@ -241,6 +241,9 @@ function deleteAction() {
"scrollX": false,
"scrollY": false,
"responsive": true,
"language": {
"emptyTable": "No folder defined"
},
"order": [[0, 'asc']]
});

View file

@ -11,14 +11,40 @@
<title>SFTPGo - Login</title>
<link rel="shortcut icon" href="/static/favicon.ico" />
<!-- Custom fonts for this template-->
<link href="/static/css/fonts.css" rel="stylesheet">
<link rel="shortcut icon" href="{{.StaticURL}}/favicon.ico" />
<!-- Custom styles for this template-->
<link href="/static/css/sb-admin-2.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/css/sb-admin-2.min.css" rel="stylesheet">
<style>
@font-face {
font-family: 'Roboto';
src: url('{{.StaticURL}}/vendor/fonts/Roboto-Bold-webfont.woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('{{.StaticURL}}/vendor/fonts/Roboto-Regular-webfont.woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Roboto';
src: url('{{.StaticURL}}/vendor/fonts/Roboto-Light-webfont.woff');
font-weight: 300;
font-style: normal;
}
div.dt-buttons {
margin-bottom: 1em;
}
.text-form-error {
color: var(--red) !important;
}
div.dt-buttons {
margin-bottom: 1em;
}
@ -94,14 +120,14 @@
</div>
<!-- Bootstrap core JavaScript-->
<script src="/static/vendor/jquery/jquery.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript-->
<script src="/static/vendor/jquery-easing/jquery.easing.min.js"></script>
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
<!-- Custom scripts for all pages-->
<script src="/static/js/sb-admin-2.min.js"></script>
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
</body>

View file

@ -3,7 +3,7 @@
{{define "title"}}{{.Title}}{{end}}
{{define "extra_css"}}
<link href="/static/vendor/tempusdominus/css/tempusdominus-bootstrap-4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/tempusdominus/css/tempusdominus-bootstrap-4.min.css" rel="stylesheet">
{{end}}
{{define "page_body"}}
@ -423,8 +423,8 @@
{{end}}
{{define "extra_js"}}
<script src="/static/vendor/moment/js/moment.min.js"></script>
<script src="/static/vendor/tempusdominus/js/tempusdominus-bootstrap-4.min.js"></script>
<script src="{{.StaticURL}}/vendor/moment/js/moment.min.js"></script>
<script src="{{.StaticURL}}/vendor/tempusdominus/js/tempusdominus-bootstrap-4.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {

View file

@ -3,11 +3,11 @@
{{define "title"}}{{.Title}}{{end}}
{{define "extra_css"}}
<link href="/static/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<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"}}
@ -84,15 +84,15 @@
{{end}}
{{define "extra_js"}}
<script src="/static/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="/static/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="/static/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="/static/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="/static/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.select.min.js"></script>
<script src="/static/vendor/datatables/ellipsis.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
<script src="{{.StaticURL}}/vendor/datatables/ellipsis.js"></script>
<script type="text/javascript">
function deleteAction() {
@ -262,6 +262,9 @@
"scrollX": false,
"scrollY": false,
"responsive": true,
"language": {
"emptyTable": "No user defined"
},
"order": [[1, 'asc']]
});