WebAdmin: completed base page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
784b7585c1
commit
ca880f6cbb
11 changed files with 395 additions and 773 deletions
2
go.mod
2
go.mod
|
@ -54,7 +54,7 @@ require (
|
|||
github.com/rs/xid v1.5.0
|
||||
github.com/rs/zerolog v1.31.0
|
||||
github.com/sftpgo/sdk v0.1.6-0.20231105181545-b44c8058fc25
|
||||
github.com/shirou/gopsutil/v3 v3.23.11
|
||||
github.com/shirou/gopsutil/v3 v3.23.12
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
|
|
4
go.sum
4
go.sum
|
@ -356,8 +356,8 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
|||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sftpgo/sdk v0.1.6-0.20231105181545-b44c8058fc25 h1:R8cTb41ZX5WSYw8q8ufTKQfOvXh7aLQWqdnteDY/96U=
|
||||
github.com/sftpgo/sdk v0.1.6-0.20231105181545-b44c8058fc25/go.mod h1:6s/PFoLUd7FXG3wGlrdVhrA0SJOwri2h9kzTph/2oiU=
|
||||
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
|
||||
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
|
|
|
@ -42,7 +42,6 @@ import (
|
|||
"github.com/drakkan/sftpgo/v2/internal/plugin"
|
||||
"github.com/drakkan/sftpgo/v2/internal/smtp"
|
||||
"github.com/drakkan/sftpgo/v2/internal/util"
|
||||
"github.com/drakkan/sftpgo/v2/internal/version"
|
||||
"github.com/drakkan/sftpgo/v2/internal/vfs"
|
||||
)
|
||||
|
||||
|
@ -160,21 +159,6 @@ type basePage struct {
|
|||
FolderQuotaScanURL string
|
||||
StatusURL string
|
||||
MaintenanceURL string
|
||||
UsersTitle string
|
||||
AdminsTitle string
|
||||
ConnectionsTitle string
|
||||
FoldersTitle string
|
||||
GroupsTitle string
|
||||
EventRulesTitle string
|
||||
EventActionsTitle string
|
||||
RolesTitle string
|
||||
StatusTitle string
|
||||
MaintenanceTitle string
|
||||
DefenderTitle string
|
||||
IPListsTitle string
|
||||
EventsTitle string
|
||||
ConfigsTitle string
|
||||
Version string
|
||||
CSRFToken string
|
||||
IsEventManagerPage bool
|
||||
IsIPManagerPage bool
|
||||
|
@ -182,7 +166,7 @@ type basePage struct {
|
|||
HasDefender bool
|
||||
HasSearcher bool
|
||||
HasExternalLogin bool
|
||||
LoggedAdmin *dataprovider.Admin
|
||||
LoggedUser *dataprovider.Admin
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
|
@ -416,7 +400,7 @@ type userTemplateFields struct {
|
|||
|
||||
func loadAdminTemplates(templatesPath string) {
|
||||
usersPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBase),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateUsers),
|
||||
}
|
||||
|
@ -730,22 +714,7 @@ func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request)
|
|||
StatusURL: webStatusPath,
|
||||
FolderQuotaScanURL: webScanVFolderPath,
|
||||
MaintenanceURL: webMaintenancePath,
|
||||
UsersTitle: pageUsersTitle,
|
||||
AdminsTitle: pageAdminsTitle,
|
||||
ConnectionsTitle: pageConnectionsTitle,
|
||||
FoldersTitle: pageFoldersTitle,
|
||||
GroupsTitle: pageGroupsTitle,
|
||||
EventRulesTitle: pageEventRulesTitle,
|
||||
EventActionsTitle: pageEventActionsTitle,
|
||||
RolesTitle: pageRolesTitle,
|
||||
StatusTitle: pageStatusTitle,
|
||||
MaintenanceTitle: pageMaintenanceTitle,
|
||||
DefenderTitle: pageDefenderTitle,
|
||||
IPListsTitle: pageIPListsTitle,
|
||||
EventsTitle: pageEventsTitle,
|
||||
ConfigsTitle: pageConfigsTitle,
|
||||
Version: version.GetAsString(),
|
||||
LoggedAdmin: getAdminFromToken(r),
|
||||
LoggedUser: getAdminFromToken(r),
|
||||
IsEventManagerPage: isEventManagerResource(currentURL),
|
||||
IsIPManagerPage: isIPListsResource(currentURL),
|
||||
IsServerManagerPage: isServerManagerResource(currentURL),
|
||||
|
@ -859,7 +828,7 @@ func (s *httpdServer) renderMFAPage(w http.ResponseWriter, r *http.Request) {
|
|||
SaveTOTPURL: webAdminTOTPSavePath,
|
||||
RecCodesURL: webAdminRecoveryCodesPath,
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
|
||||
admin, err := dataprovider.AdminExists(data.LoggedUser.Username)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
return
|
||||
|
@ -873,7 +842,7 @@ func (s *httpdServer) renderProfilePage(w http.ResponseWriter, r *http.Request,
|
|||
basePage: s.getBasePageData(pageProfileTitle, webAdminProfilePath, r),
|
||||
Error: error,
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
|
||||
admin, err := dataprovider.AdminExists(data.LoggedUser.Username)
|
||||
if err != nil {
|
||||
s.renderInternalServerErrorPage(w, r, err)
|
||||
return
|
||||
|
@ -1015,7 +984,7 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use
|
|||
}
|
||||
}
|
||||
var roles []dataprovider.Role
|
||||
if basePage.LoggedAdmin.Role == "" {
|
||||
if basePage.LoggedUser.Role == "" {
|
||||
var err error
|
||||
roles, err = s.getWebRoles(w, r, 10, true)
|
||||
if err != nil {
|
||||
|
@ -1049,7 +1018,7 @@ func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, use
|
|||
Filesystem: user.FsConfig,
|
||||
IsUserPage: true,
|
||||
IsGroupPage: false,
|
||||
IsHidden: basePage.LoggedAdmin.Filters.Preferences.HideFilesystem(),
|
||||
IsHidden: basePage.LoggedUser.Filters.Preferences.HideFilesystem(),
|
||||
HasUsersBaseDir: dataprovider.HasUsersBaseDir(),
|
||||
DirPath: user.HomeDir,
|
||||
},
|
||||
|
|
|
@ -27,7 +27,24 @@
|
|||
"error429": "Too Many Requests",
|
||||
"error500": "Internal Server Error",
|
||||
"errorPDF": "Unable to show PDF file",
|
||||
"error_editor": "Cannot open file editor"
|
||||
"error_editor": "Cannot open file editor",
|
||||
"users": "Users",
|
||||
"groups": "Groups",
|
||||
"folders": "Virtual folders",
|
||||
"connections": "Active sessions",
|
||||
"event_manager": "Event Manager",
|
||||
"event_rules": "Rules",
|
||||
"event_actions": "Actions",
|
||||
"ip_manager": "IP Manager",
|
||||
"ip_lists": "IP Lists",
|
||||
"defender": "Auto Block List",
|
||||
"admins": "Admins",
|
||||
"roles": "Roles",
|
||||
"server_manager": "Server Manager",
|
||||
"configs": "Configurations",
|
||||
"logs": "Logs",
|
||||
"maintenance": "Maintenance",
|
||||
"status": "Status"
|
||||
},
|
||||
"setup": {
|
||||
"desc": "To start using SFTPGo you need to create an administrator user",
|
||||
|
@ -370,6 +387,7 @@
|
|||
"required": "Password change is required. Set a new password to continue using your account"
|
||||
},
|
||||
"user": {
|
||||
"view_manage": "View and manage users",
|
||||
"username_reserved": "The specified username is reserved",
|
||||
"username_invalid": "The specified username is not valid, the following characters are allowed: a-zA-Z0-9-_.~",
|
||||
"home_required": "The home directory is mandatory",
|
||||
|
|
|
@ -27,7 +27,24 @@
|
|||
"error429": "Troppe richieste",
|
||||
"error500": "Errore interno del server",
|
||||
"errorPDF": "Impossibile mostrare il file PDF",
|
||||
"error_editor": "Impossibile aprire l'editor di file"
|
||||
"error_editor": "Impossibile aprire l'editor di file",
|
||||
"users": "Utenti",
|
||||
"groups": "Gruppi",
|
||||
"folders": "Cartelle virtuali",
|
||||
"connections": "Sessioni attive",
|
||||
"event_manager": "Gestione eventi",
|
||||
"event_rules": "Regole",
|
||||
"event_actions": "Azioni",
|
||||
"ip_manager": "Gestione IP",
|
||||
"ip_lists": "Liste IP",
|
||||
"defender": "Blocchi automatici",
|
||||
"admins": "Amministratori",
|
||||
"roles": "Ruoli",
|
||||
"server_manager": "Gestione server",
|
||||
"configs": "Configurazioni",
|
||||
"logs": "Registro eventi",
|
||||
"maintenance": "Manutenzione",
|
||||
"status": "Stato"
|
||||
},
|
||||
"setup": {
|
||||
"desc": "Per iniziare a utilizzare SFTPGo devi creare un utente amministratore",
|
||||
|
@ -370,6 +387,7 @@
|
|||
"required": "È richiesta la modifica della password. Imposta una nuova password per continuare a utilizzare il tuo account"
|
||||
},
|
||||
"user": {
|
||||
"view_manage": "Visualizza e gestisci utenti",
|
||||
"username_reserved": "Il nome utente specificato è riservato",
|
||||
"username_invalid": "Il nome utente specificato non è valido, sono consentiti i seguenti caratteri: a-zA-Z0-9-_.~",
|
||||
"home_required": "La directory principale è obbligatoria",
|
||||
|
|
|
@ -182,7 +182,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
expirationTime: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
defaultVersion: '{{.Version}}'
|
||||
}, {
|
||||
loadPath: '{{.StaticURL}}/locales/{{"{{lng}}"}}/{{"{{ns}}"}}.json'
|
||||
loadPath: '{{.StaticURL}}/locales/{{"{{lng}}"}}/{{"{{ns}}"}}.json?_='+new Date().getTime().toString()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,345 +1,286 @@
|
|||
<!--
|
||||
Copyright (C) 2019 Nicola Murino
|
||||
Copyright (C) 2024 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{define "base"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
|
||||
<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">
|
||||
<link href="{{.StaticURL}}/vendor/fontawesome-free/css/solid.min.css" rel="stylesheet" type="text/css">
|
||||
<link href="{{.StaticURL}}/vendor/fontawesome-free/css/regular.min.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
{{- range .Branding.DefaultCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
<style>
|
||||
{{template "commoncss" .}}
|
||||
</style>
|
||||
{{block "extra_css" .}}{{end}}
|
||||
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
||||
<body id="page-top">
|
||||
|
||||
<!-- Page Wrapper -->
|
||||
<div id="wrapper">
|
||||
|
||||
{{if .LoggedAdmin.Username}}
|
||||
<!-- Sidebar -->
|
||||
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<div class="sidebar-brand d-flex align-items-center justify-content-center">
|
||||
<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 -->
|
||||
<hr class="sidebar-divider my-0">
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "view_users"}}
|
||||
<li class="nav-item {{if eq .CurrentURL .UsersURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.UsersURL}}">
|
||||
<i class="fas fa-users"></i>
|
||||
<span>{{.UsersTitle}}</span></a>
|
||||
</li>
|
||||
{{ end }}
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "manage_groups"}}
|
||||
<li class="nav-item {{if eq .CurrentURL .GroupsURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.GroupsURL}}">
|
||||
<i class="fas fa-user-friends"></i>
|
||||
<span>{{.GroupsTitle}}</span></a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "manage_folders"}}
|
||||
<li class="nav-item {{if eq .CurrentURL .FoldersURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.FoldersURL}}">
|
||||
<i class="fas fa-folder"></i>
|
||||
<span>{{.FoldersTitle}}</span></a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "view_conns"}}
|
||||
<li class="nav-item {{if eq .CurrentURL .ConnectionsURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.ConnectionsURL}}">
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
<span>{{.ConnectionsTitle}}</span></a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "manage_event_rules"}}
|
||||
<li class="nav-item {{if .IsEventManagerPage}}active{{end}}">
|
||||
<a class="nav-link {{if not .IsEventManagerPage}}collapsed{{end}}" href="#" data-toggle="collapse" data-target="#collapseEventManager"
|
||||
aria-expanded="true" aria-controls="collapseEventManager">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
<span>Event Manager</span>
|
||||
</a>
|
||||
<div id="collapseEventManager" class="collapse {{if .IsEventManagerPage}}show{{end}}" aria-labelledby="headingEventManager" data-parent="#accordionSidebar">
|
||||
<div class="bg-white py-2 collapse-inner rounded">
|
||||
<a class="collapse-item {{if eq .CurrentURL .EventRulesURL}}active{{end}}" href="{{.EventRulesURL}}">{{.EventRulesTitle}}</a>
|
||||
<a class="collapse-item {{if eq .CurrentURL .EventActionsURL}}active{{end}}" href="{{.EventActionsURL}}">{{.EventActionsTitle}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if or (.LoggedAdmin.HasPermission "manage_ip_lists") (and .HasDefender (.LoggedAdmin.HasPermission "view_defender"))}}
|
||||
<li class="nav-item {{if .IsIPManagerPage}}active{{end}}">
|
||||
<a class="nav-link {{if not .IsIPManagerPage}}collapsed{{end}}" href="#" data-toggle="collapse" data-target="#collapseIPManager"
|
||||
aria-expanded="true" aria-controls="collapseIPManager">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<span>IP Manager</span>
|
||||
</a>
|
||||
<div id="collapseIPManager" class="collapse {{if .IsIPManagerPage}}show{{end}}" aria-labelledby="headingIPManager" data-parent="#accordionSidebar">
|
||||
<div class="bg-white py-2 collapse-inner rounded">
|
||||
{{ if .LoggedAdmin.HasPermission "manage_ip_lists"}}
|
||||
<a class="collapse-item {{if eq .CurrentURL .IPListsURL}}active{{end}}" href="{{.IPListsURL}}">{{.IPListsTitle}}</a>
|
||||
{{end}}
|
||||
{{ if and .HasDefender (.LoggedAdmin.HasPermission "view_defender")}}
|
||||
<a class="collapse-item {{if eq .CurrentURL .DefenderURL}}active{{end}}" href="{{.DefenderURL}}">{{.DefenderTitle}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "manage_admins"}}
|
||||
<li class="nav-item {{if eq .CurrentURL .AdminsURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.AdminsURL}}">
|
||||
<i class="fas fa-user-cog"></i>
|
||||
<span>{{.AdminsTitle}}</span></a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if .LoggedAdmin.HasPermission "manage_roles"}}
|
||||
<li class="nav-item {{if eq .CurrentURL .RolesURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.RolesURL}}">
|
||||
<i class="fas fa-user-lock"></i>
|
||||
<span>{{.RolesTitle}}</span></a>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
{{ if or (.LoggedAdmin.HasPermission "manage_system") (.LoggedAdmin.HasPermission "view_status") (and .HasSearcher (.LoggedAdmin.HasPermission "view_events"))}}
|
||||
<li class="nav-item {{if .IsServerManagerPage}}active{{end}}">
|
||||
<a class="nav-link {{if not .IsServerManagerPage}}collapsed{{end}}" href="#" data-toggle="collapse" data-target="#collapseServerManager"
|
||||
aria-expanded="true" aria-controls="collapseServerManager">
|
||||
<i class="fas fa-tools"></i>
|
||||
<span>Server Manager</span>
|
||||
</a>
|
||||
<div id="collapseServerManager" class="collapse {{if .IsServerManagerPage}}show{{end}}" aria-labelledby="headingServerManager" data-parent="#accordionSidebar">
|
||||
<div class="bg-white py-2 collapse-inner rounded">
|
||||
{{ if .LoggedAdmin.HasPermission "manage_system"}}
|
||||
<a class="collapse-item {{if eq .CurrentURL .ConfigsURL}}active{{end}}" href="{{.ConfigsURL}}">{{.ConfigsTitle}}</a>
|
||||
{{end}}
|
||||
{{ if and .HasSearcher (.LoggedAdmin.HasPermission "view_events")}}
|
||||
<a class="collapse-item {{if eq .CurrentURL .EventsURL}}active{{end}}" href="{{.EventsURL}}">{{.EventsTitle}}</a>
|
||||
{{end}}
|
||||
{{ if .LoggedAdmin.HasPermission "manage_system"}}
|
||||
<a class="collapse-item {{if eq .CurrentURL .MaintenanceURL}}active{{end}}" href="{{.MaintenanceURL}}">{{.MaintenanceTitle}}</a>
|
||||
{{end}}
|
||||
{{ if .LoggedAdmin.HasPermission "view_status"}}
|
||||
<a class="collapse-item {{if eq .CurrentURL .StatusURL}}active{{end}}" href="{{.StatusURL}}">{{.StatusTitle}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{{end}}
|
||||
|
||||
<!-- Divider -->
|
||||
<hr class="sidebar-divider d-none d-md-block">
|
||||
|
||||
<!-- Sidebar Toggler (Sidebar) -->
|
||||
<div class="text-center d-none d-md-inline">
|
||||
<button class="rounded-circle border-0" id="sidebarToggle"></button>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
<!-- End of Sidebar -->
|
||||
{{end}}
|
||||
|
||||
<!-- Content Wrapper -->
|
||||
<div id="content-wrapper" class="d-flex flex-column">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="content">
|
||||
|
||||
{{if .LoggedAdmin.Username}}
|
||||
<!-- Topbar -->
|
||||
<nav class="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
|
||||
|
||||
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<!-- Topbar Navbar -->
|
||||
<ul class="navbar-nav ml-auto">
|
||||
{{block "additionalnavitems" .}}{{end}}
|
||||
|
||||
<!-- Nav Item - User Information -->
|
||||
<li class="nav-item dropdown no-arrow">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<span class="mr-2 d-none d-lg-inline text-gray-600 small">{{.LoggedAdmin.Username}}</span>
|
||||
<i class="fas fa-user fa-fw"></i>
|
||||
</a>
|
||||
<!-- Dropdown - User Information -->
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
|
||||
{{if not .HasExternalLogin}}
|
||||
<a class="dropdown-item" href="{{.ProfileURL}}">
|
||||
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Profile
|
||||
</a>
|
||||
<a class="dropdown-item" href="{{.ChangePwdURL}}">
|
||||
<i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Change password
|
||||
</a>
|
||||
{{if .LoggedAdmin.CanManageMFA}}
|
||||
<a class="dropdown-item" href="{{.MFAURL}}">
|
||||
<i class="fas fa-user-lock fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Two-Factor Auth
|
||||
</a>
|
||||
{{end}}
|
||||
<div class="dropdown-divider"></div>
|
||||
{{end}}
|
||||
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
|
||||
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Logout
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
<!-- End of Topbar -->
|
||||
{{end}}
|
||||
|
||||
<!-- Begin Page Content -->
|
||||
<div class="container-fluid">
|
||||
|
||||
{{template "page_body" .}}
|
||||
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
|
||||
</div>
|
||||
<!-- End of Main Content -->
|
||||
{{if .LoggedAdmin.Username}}
|
||||
<!-- Footer -->
|
||||
<footer class="sticky-footer bg-white">
|
||||
<div class="container my-auto">
|
||||
<div class="copyright text-center my-auto">
|
||||
<span>SFTPGo {{.Version}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- End of Footer -->
|
||||
{{end}}
|
||||
|
||||
</div>
|
||||
<!-- End of Content Wrapper -->
|
||||
|
||||
{{- define "navitems"}}
|
||||
{{- block "additionalnavitems" .}}{{- end}}
|
||||
{{- template "theme-switcher"}}
|
||||
<div class="d-flex align-items-center ms-2 ms-lg-3">
|
||||
<div class="btn btn-icon btn-active-light-primary w-35px h-35px w-md-40px h-md-40px" data-kt-menu-trigger="click" data-kt-menu-attach="parent" data-kt-menu-placement="bottom-end">
|
||||
<i class="ki-duotone ki-user fs-2">
|
||||
<i class="path1"></i>
|
||||
<i class="path2"></i>
|
||||
</i>
|
||||
</div>
|
||||
<!-- End of Page Wrapper -->
|
||||
<div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-title-gray-700 menu-icon-gray-500 menu-active-bg menu-state-color fw-semibold py-4 w-250px" data-kt-menu="true">
|
||||
<div class="menu-item px-3 my-0">
|
||||
<div class="menu-content d-flex align-items-center px-3 py-2">
|
||||
<div class="me-5">
|
||||
<i class="ki-duotone ki-user fs-2">
|
||||
<i class="path1"></i>
|
||||
<i class="path2"></i>
|
||||
</i>
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div class="fw-semibold d-flex align-items-center fs-5">
|
||||
<span class="w-175px wrap-word">{{.LoggedUser.Username}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-2"></div>
|
||||
{{if not .HasExternalLogin}}
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="{{.ProfileURL}}" class="menu-link px-3 py-2">
|
||||
<span data-i18n="title.profile" class="menu-title">Profile</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="{{.ChangePwdURL}}" class="menu-link px-3 py-2">
|
||||
<span data-i18n="title.change_password" class="menu-title">Change password</span>
|
||||
</a>
|
||||
</div>
|
||||
{{if .LoggedUser.CanManageMFA}}
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="{{.MFAURL}}" class="menu-link px-3 py-2">
|
||||
<span data-i18n="title.two_factor_auth" class="menu-title">Two-factor authentication</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a id="id_logout_link" href="#" class="menu-link px-3 py-2">
|
||||
<span data-i18n="login.signout" class="menu-title">Sign out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
<!-- Scroll to Top Button-->
|
||||
<a class="scroll-to-top rounded" href="#page-top">
|
||||
<i class="fas fa-angle-up"></i>
|
||||
{{- define "sidebaritems"}}
|
||||
{{- if .LoggedUser.HasPermission "view_users"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .UsersURL}} active{{- end}}" href="{{.UsersURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-people fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.users" class="menu-title">Users</span>
|
||||
</a>
|
||||
|
||||
{{if .LoggedAdmin.Username}}
|
||||
<!-- Logout Modal-->
|
||||
<div class="modal fade" id="logoutModal" tabindex="-1" role="dialog" aria-labelledby="modalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalLabel">Ready to Leave?</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Select "Logout" below if you are ready to end your current session.</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<a class="btn btn-primary" href="{{.LogoutURL}}">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "manage_groups"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .GroupsURL}} active{{- end}}" href="{{.GroupsURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-profile-user fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.groups" class="menu-title">Groups</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "manage_folders"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .FoldersURL}} active{{- end}}" href="{{.FoldersURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-folder fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.folders" class="menu-title">Folders</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "view_conns"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .ConnectionsURL}} active{{- end}}" href="{{.ConnectionsURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-arrow-up-down fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.connections" class="menu-title">Connections</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{ if .LoggedUser.HasPermission "manage_event_rules"}}
|
||||
<div data-kt-menu-trigger="click" class="menu-item menu-accordion {{- if .IsEventManagerPage}} here show{{- end}}">
|
||||
<span class="menu-link">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-calendar fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.event_manager" class="menu-title">Event Manager</span>
|
||||
<span class="menu-arrow"></span>
|
||||
</span>
|
||||
<div class="menu-sub menu-sub-accordion">
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .EventRulesURL}} active{{- end}}" href="{{.EventRulesURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.event_rules" class="menu-title fs-5 fw-semibold">Rules</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .EventActionsURL}} active{{- end}}" href="{{.EventActionsURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.event_actions" class="menu-title fs-5 fw-semibold">Actions</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if or (.LoggedUser.HasPermission "manage_ip_lists") (and .HasDefender (.LoggedUser.HasPermission "view_defender"))}}
|
||||
<div data-kt-menu-trigger="click" class="menu-item menu-accordion {{- if .IsIPManagerPage}} here show{{- end}}">
|
||||
<span class="menu-link">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-security-check fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.ip_manager" class="menu-title">IP Manager</span>
|
||||
<span class="menu-arrow"></span>
|
||||
</span>
|
||||
<div class="menu-sub menu-sub-accordion">
|
||||
{{- if .LoggedUser.HasPermission "manage_ip_lists"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .IPListsURL}} active{{- end}}" href="{{.IPListsURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.ip_lists" class="menu-title fs-5 fw-semibold">IP lists</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if and .HasDefender (.LoggedUser.HasPermission "view_defender")}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .DefenderURL}} active{{- end}}" href="{{.DefenderURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.defender" class="menu-title fs-5 fw-semibold">Auto Blocklist</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "manage_admins"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .AdminsURL}} active{{- end}}" href="{{.AdminsURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-security-user fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.admins" class="menu-title">Admins</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "manage_roles"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .RolesURL}} active{{- end}}" href="{{.RolesURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-user-tick fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.roles" class="menu-title">Roles</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if or (.LoggedUser.HasPermission "manage_system") (.LoggedUser.HasPermission "view_status") (and .HasSearcher (.LoggedUser.HasPermission "view_events"))}}
|
||||
<div data-kt-menu-trigger="click" class="menu-item menu-accordion {{- if .IsServerManagerPage}} here show{{- end}}">
|
||||
<span class="menu-link">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-setting-3 fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span data-i18n="title.server_manager" class="menu-title">Server Manager</span>
|
||||
<span class="menu-arrow"></span>
|
||||
</span>
|
||||
<div class="menu-sub menu-sub-accordion">
|
||||
{{- if .LoggedUser.HasPermission "manage_system"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .ConfigsURL}} active{{- end}}" href="{{.ConfigsURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.configs" class="menu-title fs-5 fw-semibold">Configurations</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{ if and .HasSearcher (.LoggedUser.HasPermission "view_events")}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .EventsURL}} active{{- end}}" href="{{.EventsURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.logs" class="menu-title fs-5 fw-semibold">Logs</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "manage_system"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .MaintenanceURL}} active{{- end}}" href="{{.MaintenanceURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.maintenance" class="menu-title fs-5 fw-semibold">Maintenance</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.HasPermission "view_status"}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .StatusURL}} active{{- end}}" href="{{.StatusURL}}">
|
||||
<span class="menu-bullet">
|
||||
<span class="bullet bullet-dot"></span>
|
||||
</span>
|
||||
<span data-i18n="title.status" class="menu-title fs-5 fw-semibold">Status</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
{{block "dialog" .}}{{end}}
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<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="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function escapeHTML(str) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(str));
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function unescapeHTML(escapedStr) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = escapedStr;
|
||||
var child = div.childNodes[0];
|
||||
return child ? child.nodeValue : '';
|
||||
}
|
||||
|
||||
function fixedEncodeURIComponent(str) {
|
||||
return encodeURIComponent(unescapeHTML(str)).replace(/[!'()*]/g, function (c) {
|
||||
return '%' + c.charCodeAt(0).toString(16);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Page level plugins -->
|
||||
{{block "extra_js" .}}{{end}}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{end}}
|
||||
{{- end}}
|
|
@ -1,386 +1,65 @@
|
|||
<!--
|
||||
Copyright (C) 2019 Nicola Murino
|
||||
Copyright (C) 2024 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
{{- define "extra_css"}}
|
||||
<link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
|
||||
{{- end}}
|
||||
|
||||
{{define "extra_css"}}
|
||||
<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">
|
||||
<link href="{{.StaticURL}}/vendor/datatables/colReorder.bootstrap4.min.css" rel="stylesheet">
|
||||
{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
<div id="errorMsg" class="alert alert-warning fade show" style="display: none;" role="alert">
|
||||
<span id="errorTxt"></span>
|
||||
<button type="button" class="close" aria-label="Close" onclick="dismissErrorMsg();">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
function dismissErrorMsg(){
|
||||
$('#errorMsg').hide();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="successMsg" class="card mb-4 border-left-success" style="display: none;">
|
||||
<div id="successTxt" class="card-body"></div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">View and manage users</h6>
|
||||
{{- define "page_body"}}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 data-i18n="user.view_manage" class="card-title section-title">View and manage users</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
|
||||
<div id="card_body" class="card-body">
|
||||
<div id="loader" class="align-items-center text-center my-10">
|
||||
<span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
|
||||
<span data-i18n="general.loading" class="text-gray-700">Loading...</span>
|
||||
</div>
|
||||
<div id="card_content" class="d-none">
|
||||
<div class="d-flex flex-stack flex-wrap mb-5">
|
||||
<div class="d-flex align-items-center position-relative my-1">
|
||||
<i class="ki-duotone ki-magnifier fs-1 position-absolute ms-6">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
<input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
|
||||
class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end" data-table-toolbar="base">
|
||||
<a href="{{.UserURL}}" class="btn btn-primary">
|
||||
<i class="ki-duotone ki-plus fs-2"></i>
|
||||
<span data-i18n="general.add">Add</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Status</th>
|
||||
<th>Last login</th>
|
||||
<th>Description</th>
|
||||
<th>Email</th>
|
||||
<th>Storage</th>
|
||||
<th>Groups</th>
|
||||
<th>MFA</th>
|
||||
<th>Bandwidth</th>
|
||||
<th>Quota</th>
|
||||
<th>Role</th>
|
||||
<th>Other</th>
|
||||
<th></th>
|
||||
<tr class="text-start text-muted fw-bold fs-6 gs-0">
|
||||
<th data-i18n="general.name">Name</th>
|
||||
<th data-i18n="share.scope">Scope</th>
|
||||
<th data-i18n="general.info">Info</th>
|
||||
<th class="min-w-100px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Users}}
|
||||
<tr>
|
||||
<td>{{.ID}}</td>
|
||||
<td>{{.Username}}</td>
|
||||
<td>{{.GetStatusAsString}}</td>
|
||||
<td>{{.GetLastLoginAsString}}</td>
|
||||
<td>{{.Description}}</td>
|
||||
<td>{{.Email}}</td>
|
||||
<td>{{.GetStorageDescrition}}</td>
|
||||
<td>{{.GetGroupsAsString}}</td>
|
||||
<td>{{.GetMFAStatusAsString}}</td>
|
||||
<td>{{.GetBandwidthAsString}}</td>
|
||||
<td>{{.GetQuotaSummary}}</td>
|
||||
<td>{{.Role}}</td>
|
||||
<td>{{.GetInfoString}}</td>
|
||||
<td>{{.GetLastQuotaUpdateAsString}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{end}}
|
||||
|
||||
{{define "dialog"}}
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteModalLabel">
|
||||
Confirmation required
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">Do you want to delete the selected user?</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<a class="btn btn-warning" href="#" onclick="deleteAction()">
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
<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/buttons.colVis.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 src="{{.StaticURL}}/vendor/datatables/dataTables.colReorder.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
function deleteAction() {
|
||||
let table = $('#dataTable').DataTable();
|
||||
table.button('delete:name').enable(false);
|
||||
let username = table.row({ selected: true }).data()[1];
|
||||
let path = '{{.UserURL}}' + "/" + fixedEncodeURIComponent(username);
|
||||
$('#deleteModal').modal('hide');
|
||||
$('#errorMsg').hide();
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
window.location.href = '{{.UsersURL}}';
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
var txt = "Unable to delete the selected user";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message){
|
||||
txt += ": " + json.message;
|
||||
} else {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$.fn.dataTable.ext.buttons.add = {
|
||||
text: '<i class="fas fa-plus"></i>',
|
||||
name: 'add',
|
||||
titleAttr: "Add",
|
||||
action: function (e, dt, node, config) {
|
||||
window.location.href = '{{.UserURL}}';
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.edit = {
|
||||
text: '<i class="fas fa-pen"></i>',
|
||||
name: 'edit',
|
||||
titleAttr: "Edit",
|
||||
action: function (e, dt, node, config) {
|
||||
var username = dt.row({ selected: true }).data()[1];
|
||||
var path = '{{.UserURL}}' + "/" + fixedEncodeURIComponent(username);
|
||||
window.location.href = path;
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.template = {
|
||||
text: '<i class="fas fa-clone"></i>',
|
||||
name: 'template',
|
||||
titleAttr: "Template",
|
||||
action: function (e, dt, node, config) {
|
||||
var selectedRows = table.rows({ selected: true }).count();
|
||||
if (selectedRows == 1){
|
||||
var username = dt.row({ selected: true }).data()[1];
|
||||
var path = '{{.UserTemplateURL}}' + "?from=" + encodeURIComponent(username);
|
||||
window.location.href = path;
|
||||
} else {
|
||||
window.location.href = '{{.UserTemplateURL}}';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.delete = {
|
||||
text: '<i class="fas fa-trash"></i>',
|
||||
name: 'delete',
|
||||
titleAttr: "Delete",
|
||||
action: function (e, dt, node, config) {
|
||||
/*console.log("delete clicked, num row selected: " + dt.rows({ selected: true }).count());
|
||||
var data = dt.rows({ selected: true }).data();
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
console.log("selected row data: " + JSON.stringify(data[i]));
|
||||
}*/
|
||||
$('#deleteModal').modal('show');
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.quota_scan = {
|
||||
text: '<i class="fas fa-redo-alt"></i>',
|
||||
name: 'quota_scan',
|
||||
titleAttr: 'Quota Scan',
|
||||
action: function (e, dt, node, config) {
|
||||
dt.button('quota_scan:name').enable(false);
|
||||
var username = dt.row({ selected: true }).data()[1];
|
||||
var path = '{{.QuotaScanURL}}'+ "/" + fixedEncodeURIComponent(username);
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'POST',
|
||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
dt.button('quota_scan:name').enable(true);
|
||||
$('#successTxt').text("Quota scan started for the selected user. Please reload the user's page to check when the scan ends");
|
||||
$('#successMsg').show();
|
||||
setTimeout(function () {
|
||||
$('#successMsg').hide();
|
||||
}, 15000);
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
dt.button('quota_scan:name').enable(true);
|
||||
var txt = "Unable to update quota for the selected user";
|
||||
if ($xhr) {
|
||||
var json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message) {
|
||||
txt += ": " + json.message;
|
||||
} else if (json.error) {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
}
|
||||
});
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
let dateFn = $.fn.dataTable.render.datetime();
|
||||
let table = $('#dataTable').DataTable({
|
||||
"select": {
|
||||
"style": "single",
|
||||
"blurable": true
|
||||
},
|
||||
"colReorder": {
|
||||
"enable": true,
|
||||
"fixedColumnsLeft": 2
|
||||
},
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
"buttons": [
|
||||
{
|
||||
"text": "Column visibility",
|
||||
"extend": "colvis",
|
||||
"columns": ":not(.noVis)"
|
||||
}
|
||||
],
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0,13],
|
||||
"visible": false,
|
||||
"searchable": false,
|
||||
"className": "noVis"
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"className": "noVis"
|
||||
},
|
||||
{
|
||||
"targets": [3],
|
||||
"render": $.fn.dataTable.render.datetime()
|
||||
},
|
||||
{
|
||||
"targets": [4,5,8,11],
|
||||
"visible": false
|
||||
},
|
||||
{
|
||||
"targets": [6,7],
|
||||
"visible": false,
|
||||
"render": $.fn.dataTable.render.ellipsis(50, true)
|
||||
},
|
||||
{
|
||||
"targets": [9],
|
||||
"visible": false,
|
||||
"render": $.fn.dataTable.render.ellipsis(40, true)
|
||||
},
|
||||
{
|
||||
"targets": [10],
|
||||
"visible": false,
|
||||
"render": function ( data, type, row, meta ) {
|
||||
if (type !== 'display') {
|
||||
return data;
|
||||
}
|
||||
if (row[13] !== ""){
|
||||
var formattedDate = dateFn(row[13], type);
|
||||
data = `${data}. Updated at: ${formattedDate}`;
|
||||
}
|
||||
let ellipsisFn = $.fn.dataTable.render.ellipsis(70, true);
|
||||
return ellipsisFn(data, type);
|
||||
}
|
||||
},
|
||||
{
|
||||
"targets": [12],
|
||||
"visible": false,
|
||||
"render": $.fn.dataTable.render.ellipsis(100, true)
|
||||
}
|
||||
],
|
||||
"scrollX": false,
|
||||
"scrollY": false,
|
||||
"responsive": true,
|
||||
"language": {
|
||||
"emptyTable": "No user defined"
|
||||
},
|
||||
"order": [[1, 'asc']]
|
||||
});
|
||||
|
||||
new $.fn.dataTable.FixedHeader( table );
|
||||
|
||||
{{if .LoggedAdmin.HasPermission "quota_scans"}}
|
||||
table.button().add(0,'quota_scan');
|
||||
{{end}}
|
||||
|
||||
{{if .LoggedAdmin.HasPermission "del_users"}}
|
||||
table.button().add(0,'delete');
|
||||
{{end}}
|
||||
|
||||
{{if .LoggedAdmin.HasPermission "manage_system"}}
|
||||
table.button().add(0,'template');
|
||||
{{end}}
|
||||
|
||||
{{if .LoggedAdmin.HasPermission "edit_users"}}
|
||||
table.button().add(0,'edit');
|
||||
{{end}}
|
||||
|
||||
{{if .LoggedAdmin.HasPermission "add_users"}}
|
||||
table.button().add(0,'add');
|
||||
{{end}}
|
||||
|
||||
table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
|
||||
|
||||
table.on('select deselect', function () {
|
||||
var selectedRows = table.rows({ selected: true }).count();
|
||||
{{if .LoggedAdmin.HasPermission "edit_users"}}
|
||||
table.button('edit:name').enable(selectedRows == 1);
|
||||
{{end}}
|
||||
{{if .LoggedAdmin.HasPermission "del_users"}}
|
||||
table.button('delete:name').enable(selectedRows == 1);
|
||||
{{end}}
|
||||
{{if .LoggedAdmin.HasPermission "quota_scans"}}
|
||||
table.button('quota_scan:name').enable(selectedRows == 1);
|
||||
{{end}}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{- end}}
|
|
@ -68,7 +68,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
placeholder="Paste your public key here">{{$val}}</textarea>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a data-i18n="general.delete" href="#" data-repeater-delete
|
||||
<a href="#" data-repeater-delete
|
||||
class="btn btn-light-danger mt-3 mt-md-8">
|
||||
<i class="ki-duotone ki-trash fs-5">
|
||||
<span class="path1"></span>
|
||||
|
@ -77,7 +77,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
<span data-i18n="general.delete">Delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -90,7 +90,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a data-i18n="general.delete" href="#" data-repeater-delete
|
||||
<a href="#" data-repeater-delete
|
||||
class="btn btn-light-danger mt-3 mt-md-8">
|
||||
<i class="ki-duotone ki-trash fs-5">
|
||||
<span class="path1"></span>
|
||||
|
@ -99,7 +99,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
<span data-i18n="general.delete">Delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -109,9 +109,9 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
</div>
|
||||
|
||||
<div class="form-group mt-5">
|
||||
<a data-i18n="general.add" href="#" data-repeater-create class="btn btn-light-primary">
|
||||
<a href="#" data-repeater-create class="btn btn-light-primary">
|
||||
<i class="ki-duotone ki-plus fs-3"></i>
|
||||
Add
|
||||
<span data-i18n="general.add">Add</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
name="path" value="{{$val}}" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a data-i18n="general.delete" href="#" data-repeater-delete
|
||||
<a href="#" data-repeater-delete
|
||||
class="btn btn-light-danger mt-3 mt-md-8">
|
||||
<i class="ki-duotone ki-trash fs-5">
|
||||
<span class="path1"></span>
|
||||
|
@ -70,7 +70,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
<span data-i18n="general.delete">Delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,7 +83,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
name="path" value="" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a data-i18n="general.delete" href="#" data-repeater-delete
|
||||
<a href="#" data-repeater-delete
|
||||
class="btn btn-light-danger mt-3 mt-md-8">
|
||||
<i class="ki-duotone ki-trash fs-5">
|
||||
<span class="path1"></span>
|
||||
|
@ -92,7 +92,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
<span data-i18n="general.delete">Delete</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -102,9 +102,9 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
</div>
|
||||
|
||||
<div class="form-group mt-5">
|
||||
<a data-i18n="general.add" href="#" data-repeater-create class="btn btn-light-primary">
|
||||
<a href="#" data-repeater-create class="btn btn-light-primary">
|
||||
<i class="ki-duotone ki-plus fs-3"></i>
|
||||
Add
|
||||
<span data-i18n="general.add">Add</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,15 +13,13 @@ This WebUI is allowed for use only within the SFTPGo product and
|
|||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{template "base" .}}
|
||||
{{- template "base" .}}
|
||||
|
||||
{{- define "extra_css"}}
|
||||
<link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
|
||||
{{- end}}
|
||||
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
{{- define "page_body"}}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 data-i18n="share.view_manage" class="card-title section-title">View and manage shares</h3>
|
||||
|
@ -29,23 +27,22 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
<div id="card_body" class="card-body">
|
||||
<div id="loader" class="align-items-center text-center my-10">
|
||||
<span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
|
||||
<span data-i18n="general.loading" class="text-gray-600">Loading...</span>
|
||||
<span data-i18n="general.loading" class="text-gray-700">Loading...</span>
|
||||
</div>
|
||||
<div id="card_content" class="d-none">
|
||||
<div class="d-flex flex-stack mb-5">
|
||||
<div class="d-flex flex-stack flex-wrap mb-5">
|
||||
<div class="d-flex align-items-center position-relative my-1">
|
||||
<i class="ki-duotone ki-magnifier fs-1 position-absolute ms-6">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
<input name="search" data-i18n="[placeholder]general.search" type="text" data-share-table-filter="search"
|
||||
<input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
|
||||
class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end" data-share-table-toolbar="base">
|
||||
<a data-i18n="general.add" href="{{.ShareURL}}" class="btn btn-primary">
|
||||
<div class="d-flex justify-content-end" data-table-toolbar="base">
|
||||
<a href="{{.ShareURL}}" class="btn btn-primary">
|
||||
<i class="ki-duotone ki-plus fs-2"></i>
|
||||
Add
|
||||
<span data-i18n="general.add">Add</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -372,7 +369,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
stateDuration: 0,
|
||||
stateLoadParams: function (settings, data) {
|
||||
if (data.search.search){
|
||||
const filterSearch = document.querySelector('[data-share-table-filter="search"]');
|
||||
const filterSearch = document.querySelector('[data-table-filter="search"]');
|
||||
filterSearch.value = data.search.search;
|
||||
}
|
||||
},
|
||||
|
@ -405,7 +402,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
}
|
||||
|
||||
var handleSearchDatatable = function () {
|
||||
const filterSearch = document.querySelector('[data-share-table-filter="search"]');
|
||||
const filterSearch = document.querySelector('[data-table-filter="search"]');
|
||||
filterSearch.addEventListener('keyup', function (e) {
|
||||
dt.rows().deselect();
|
||||
dt.search(e.target.value, true, false).draw();
|
||||
|
|
Loading…
Add table
Reference in a new issue