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
14 changed files with 241 additions and 143 deletions

View File

@@ -218,8 +218,11 @@ func Init() {
TemplatesPath: "templates", TemplatesPath: "templates",
StaticFilesPath: "static", StaticFilesPath: "static",
BackupsPath: "backups", BackupsPath: "backups",
WebAdminRoot: "",
CertificateFile: "", CertificateFile: "",
CertificateKeyFile: "", CertificateKeyFile: "",
CACertificates: nil,
CARevocationLists: nil,
}, },
HTTPConfig: httpclient.Config{ HTTPConfig: httpclient.Config{
Timeout: 20, Timeout: 20,
@@ -857,6 +860,7 @@ func setViperDefaults() {
viper.SetDefault("httpd.templates_path", globalConf.HTTPDConfig.TemplatesPath) viper.SetDefault("httpd.templates_path", globalConf.HTTPDConfig.TemplatesPath)
viper.SetDefault("httpd.static_files_path", globalConf.HTTPDConfig.StaticFilesPath) viper.SetDefault("httpd.static_files_path", globalConf.HTTPDConfig.StaticFilesPath)
viper.SetDefault("httpd.backups_path", globalConf.HTTPDConfig.BackupsPath) 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_file", globalConf.HTTPDConfig.CertificateFile)
viper.SetDefault("httpd.certificate_key_file", globalConf.HTTPDConfig.CertificateKeyFile) viper.SetDefault("httpd.certificate_key_file", globalConf.HTTPDConfig.CertificateKeyFile)
viper.SetDefault("httpd.ca_certificates", globalConf.HTTPDConfig.CACertificates) 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 - `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 - `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 - `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_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. - `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. - `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" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
@@ -27,46 +28,47 @@ import (
) )
const ( const (
logSender = "httpd" logSender = "httpd"
tokenPath = "/api/v2/token" tokenPath = "/api/v2/token"
logoutPath = "/api/v2/logout" logoutPath = "/api/v2/logout"
activeConnectionsPath = "/api/v2/connections" activeConnectionsPath = "/api/v2/connections"
quotaScanPath = "/api/v2/quota-scans" quotaScanPath = "/api/v2/quota-scans"
quotaScanVFolderPath = "/api/v2/folder-quota-scans" quotaScanVFolderPath = "/api/v2/folder-quota-scans"
userPath = "/api/v2/users" userPath = "/api/v2/users"
versionPath = "/api/v2/version" versionPath = "/api/v2/version"
folderPath = "/api/v2/folders" folderPath = "/api/v2/folders"
serverStatusPath = "/api/v2/status" serverStatusPath = "/api/v2/status"
dumpDataPath = "/api/v2/dumpdata" dumpDataPath = "/api/v2/dumpdata"
loadDataPath = "/api/v2/loaddata" loadDataPath = "/api/v2/loaddata"
updateUsedQuotaPath = "/api/v2/quota-update" updateUsedQuotaPath = "/api/v2/quota-update"
updateFolderUsedQuotaPath = "/api/v2/folder-quota-update" updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
defenderBanTime = "/api/v2/defender/bantime" defenderBanTime = "/api/v2/defender/bantime"
defenderUnban = "/api/v2/defender/unban" defenderUnban = "/api/v2/defender/unban"
defenderScore = "/api/v2/defender/score" defenderScore = "/api/v2/defender/score"
adminPath = "/api/v2/admins" adminPath = "/api/v2/admins"
adminPwdPath = "/api/v2/changepwd/admin" adminPwdPath = "/api/v2/changepwd/admin"
healthzPath = "/healthz" healthzPath = "/healthz"
webBasePath = "/web" webRootPathDefault = "/"
webLoginPath = "/web/login" webBasePathDefault = "/web"
webLogoutPath = "/web/logout" webLoginPathDefault = "/web/login"
webUsersPath = "/web/users" webLogoutPathDefault = "/web/logout"
webUserPath = "/web/user" webUsersPathDefault = "/web/users"
webConnectionsPath = "/web/connections" webUserPathDefault = "/web/user"
webFoldersPath = "/web/folders" webConnectionsPathDefault = "/web/connections"
webFolderPath = "/web/folder" webFoldersPathDefault = "/web/folders"
webStatusPath = "/web/status" webFolderPathDefault = "/web/folder"
webAdminsPath = "/web/admins" webStatusPathDefault = "/web/status"
webAdminPath = "/web/admin" webAdminsPathDefault = "/web/admins"
webMaintenancePath = "/web/maintenance" webAdminPathDefault = "/web/admin"
webBackupPath = "/web/backup" webMaintenancePathDefault = "/web/maintenance"
webRestorePath = "/web/restore" webBackupPathDefault = "/web/backup"
webScanVFolderPath = "/web/folder-quota-scans" webRestorePathDefault = "/web/restore"
webQuotaScanPath = "/web/quota-scans" webScanVFolderPathDefault = "/web/folder-quota-scans"
webChangeAdminPwdPath = "/web/changepwd/admin" webQuotaScanPathDefault = "/web/quota-scans"
webTemplateUser = "/web/template/user" webChangeAdminPwdPathDefault = "/web/changepwd/admin"
webTemplateFolder = "/web/template/folder" webTemplateUserDefault = "/web/template/user"
webStaticFilesPath = "/static" webTemplateFolderDefault = "/web/template/folder"
webStaticFilesPathDefault = "/static"
// MaxRestoreSize defines the max size for the loaddata input file // MaxRestoreSize defines the max size for the loaddata input file
MaxRestoreSize = 10485760 // 10 MB MaxRestoreSize = 10485760 // 10 MB
maxRequestSize = 1048576 // 1MB maxRequestSize = 1048576 // 1MB
@@ -80,8 +82,33 @@ var (
jwtTokensCleanupDone chan bool jwtTokensCleanupDone chan bool
invalidatedJWTTokens sync.Map invalidatedJWTTokens sync.Map
csrfTokenAuth *jwtauth.JWTAuth 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 // Binding defines the configuration for a network listener
type Binding struct { type Binding struct {
// The address to listen on. A blank value means listen on all available network interfaces. // 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"` 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 // 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"` 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 // If files containing a certificate and matching private key for the server are provided the server will expect
// HTTPS connections. // HTTPS connections.
// Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a // 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) certificateFile := getConfigPath(c.CertificateFile, configDir)
certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir) certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
if enableWebAdmin { if enableWebAdmin {
updateWebAdminURLs(c.WebAdminRoot)
loadTemplates(templatesPath) loadTemplates(templatesPath)
} else { } else {
logger.Info(logSender, "", "built-in web interface disabled, please set templates_path and static_files_path to enable it") 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 // GetHTTPRouter returns an HTTP handler suitable to use for test cases
func GetHTTPRouter() http.Handler { func GetHTTPRouter() http.Handler {
b := Binding{ b := Binding{

View File

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

View File

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

View File

@@ -165,6 +165,7 @@
"templates_path": "templates", "templates_path": "templates",
"static_files_path": "static", "static_files_path": "static",
"backups_path": "backups", "backups_path": "backups",
"web_admin_root": "",
"certificate_file": "", "certificate_file": "",
"certificate_key_file": "", "certificate_key_file": "",
"ca_certificates": [], "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 "title"}}{{.Title}}{{end}}
{{define "extra_css"}} {{define "extra_css"}}
<link href="/static/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet"> <link href="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet"> <link href="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet"> <link href="{{.StaticURL}}/vendor/datatables/fixedHeader.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet"> <link href="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.css" rel="stylesheet">
<link href="/static/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet"> <link href="{{.StaticURL}}/vendor/datatables/select.bootstrap4.min.css" rel="stylesheet">
{{end}} {{end}}
{{define "page_body"}} {{define "page_body"}}
@@ -83,15 +83,15 @@
{{end}} {{end}}
{{define "extra_js"}} {{define "extra_js"}}
<script src="/static/vendor/datatables/jquery.dataTables.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/jquery.dataTables.min.js"></script>
<script src="/static/vendor/datatables/dataTables.bootstrap4.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/dataTables.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.buttons.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/dataTables.buttons.min.js"></script>
<script src="/static/vendor/datatables/buttons.bootstrap4.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/buttons.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.fixedHeader.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/dataTables.fixedHeader.min.js"></script>
<script src="/static/vendor/datatables/dataTables.responsive.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/dataTables.responsive.min.js"></script>
<script src="/static/vendor/datatables/responsive.bootstrap4.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/responsive.bootstrap4.min.js"></script>
<script src="/static/vendor/datatables/dataTables.select.min.js"></script> <script src="{{.StaticURL}}/vendor/datatables/dataTables.select.min.js"></script>
<script src="/static/vendor/datatables/ellipsis.js"></script> <script src="{{.StaticURL}}/vendor/datatables/ellipsis.js"></script>
<script type="text/javascript"> <script type="text/javascript">
function deleteAction() { function deleteAction() {

View File

@@ -12,16 +12,36 @@
<title>SFTPGo - {{template "title" .}}</title> <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--> <!-- Custom fonts for this template-->
<link href="/static/vendor/fontawesome-free/css/fontawesome.min.css" rel="stylesheet" type="text/css"> <link href="{{.StaticURL}}/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="{{.StaticURL}}/vendor/fontawesome-free/css/solid.min.css" rel="stylesheet" type="text/css">
<link href="/static/css/fonts.css" rel="stylesheet">
<!-- Custom styles for this template--> <!-- 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> <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 { div.dt-buttons {
margin-bottom: 1em; margin-bottom: 1em;
} }
@@ -212,14 +232,14 @@
{{block "dialog" .}}{{end}} {{block "dialog" .}}{{end}}
<!-- Bootstrap core JavaScript--> <!-- Bootstrap core JavaScript-->
<script src="/static/vendor/jquery/jquery.min.js"></script> <script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript--> <!-- 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--> <!-- 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"> <script type="text/javascript">
function fixedEncodeURIComponent(str) { function fixedEncodeURIComponent(str) {

View File

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

View File

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

View File

@@ -11,14 +11,40 @@
<title>SFTPGo - Login</title> <title>SFTPGo - Login</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/css/fonts.css" rel="stylesheet">
<!-- Custom styles for this template--> <!-- 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> <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 { div.dt-buttons {
margin-bottom: 1em; margin-bottom: 1em;
} }
@@ -94,14 +120,14 @@
</div> </div>
<!-- Bootstrap core JavaScript--> <!-- Bootstrap core JavaScript-->
<script src="/static/vendor/jquery/jquery.min.js"></script> <script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
<script src="/static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Core plugin JavaScript--> <!-- 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--> <!-- 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> </body>

View File

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

View File

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