mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 14:50:55 +03:00
WIP: new WebClient UI
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
@@ -1,274 +1,393 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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"}}
|
||||
{{- define "base"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
{{- template "fonts" .StaticURL }}
|
||||
<link href="{{.StaticURL}}/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{{.StaticURL}}/assets/css/style.bundle.css" rel="stylesheet" type="text/css" />
|
||||
{{- template "globalstyle" }}
|
||||
{{- block "extra_css" .}}{{- end}}
|
||||
{{- range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
{{- template "commonjs" }}
|
||||
</head>
|
||||
|
||||
<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-->
|
||||
<link href="{{.StaticURL}}{{.Branding.DefaultCSS}}" rel="stylesheet">
|
||||
<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 .LoggedUser.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">
|
||||
|
||||
<li class="nav-item {{if eq .CurrentURL .FilesURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.FilesURL}}">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<span>{{.FilesTitle}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{if .LoggedUser.CanManageShares}}
|
||||
<li class="nav-item {{if eq .CurrentURL .SharesURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.SharesURL}}">
|
||||
<i class="fas fa-share-alt"></i>
|
||||
<span>{{.SharesTitle}}</span></a>
|
||||
</li>
|
||||
{{end}}
|
||||
<li class="nav-item {{if eq .CurrentURL .ProfileURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.ProfileURL}}">
|
||||
<i class="fas fa-user"></i>
|
||||
<span>{{.ProfileTitle}}</span></a>
|
||||
</li>
|
||||
{{if .LoggedUser.CanManageMFA}}
|
||||
<li class="nav-item {{if eq .CurrentURL .MFAURL}}active{{end}}">
|
||||
<a class="nav-link" href="{{.MFAURL}}">
|
||||
<i class="fas fa-user-lock"></i>
|
||||
<span>{{.MFATitle}}</span></a>
|
||||
</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 .LoggedUser.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">{{.LoggedUser.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 .LoggedUser.CanChangePassword}}
|
||||
<a class="dropdown-item" href="{{.ChangePwdURL}}">
|
||||
<i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Change password
|
||||
</a>
|
||||
<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>
|
||||
<body data-kt-app-header-fixed="true" data-kt-app-header-fixed-mobile="true" data-kt-app-toolbar-enabled="true" data-kt-app-sidebar-enabled="true" data-kt-app-sidebar-fixed="true" data-kt-app-sidebar-push-header="true" data-kt-app-sidebar-push-toolbar="true" data-kt-app-sidebar-push-footer="true" class="app-default">
|
||||
{{- template "theme-setup"}}
|
||||
<div class="d-flex flex-column flex-root app-root" id="kt_app_root">
|
||||
<div class="app-page flex-column flex-column-fluid " id="kt_app_page">
|
||||
{{- if .LoggedUser.Username}}
|
||||
<div id="kt_app_header" class="app-header" data-kt-sticky="true" data-kt-sticky-activate="{default: true, lg: true}" data-kt-sticky-name="app-header-minimize" data-kt-sticky-offset="{default: '200px', lg: '300px'}" data-kt-sticky-animation="false">
|
||||
<div class="app-container container-fluid d-flex align-items-stretch flex-stack " id="kt_app_header_container">
|
||||
<div class="d-flex align-items-center d-block d-lg-none ms-n3" title="Show sidebar menu">
|
||||
<div class="btn btn-icon btn-color-gray-800 btn-active-color-primary w-35px h-35px me-1" id="kt_app_sidebar_mobile_toggle">
|
||||
<i class="ki-duotone ki-abstract-14 fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</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 .LoggedUser.Username}}
|
||||
<!-- Footer -->
|
||||
<footer class="sticky-footer bg-white">
|
||||
<div class="container my-auto">
|
||||
<div class="copyright text-center my-auto">
|
||||
<span>SFTPGo {{.Version}}</span>
|
||||
<span>
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-30px" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-navbar flex-lg-grow-1" id="kt_app_header_navbar">
|
||||
<div class="app-navbar-item d-flex align-items-center flex-lg-grow-1 me-2 me-lg-0">
|
||||
</div>
|
||||
<!-- <div class="d-flex align-self-center flex-center flex-shrink-0">
|
||||
</div> -->
|
||||
<div class="d-flex align-self-center flex-center flex-shrink-0">
|
||||
{{- block "additionalnavitems" .}}{{- end}}
|
||||
{{- if ne .CurrentURL .EditURL }}
|
||||
<div class="d-flex align-items-center ms-2 ms-lg-3">
|
||||
<a href="#" class="btn btn-icon btn-active-light-primary w-35px h-35px w-md-40px h-md-40px" data-kt-menu-trigger="{default:'click', lg: 'hover'}" data-kt-menu-attach="parent" data-kt-menu-placement="bottom-end">
|
||||
<i class="ki-duotone ki-night-day theme-light-show fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
<span class="path6"></span>
|
||||
<span class="path7"></span>
|
||||
<span class="path8"></span>
|
||||
<span class="path9"></span>
|
||||
<span class="path10"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-moon theme-dark-show fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</a>
|
||||
<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 fs-base w-150px" data-kt-menu="true" data-kt-element="theme-mode-menu">
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="#" class="menu-link px-3 py-2" data-kt-element="mode" data-kt-value="light">
|
||||
<span class="menu-icon" data-kt-element="icon">
|
||||
<i class="ki-duotone ki-night-day fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
<span class="path6"></span>
|
||||
<span class="path7"></span>
|
||||
<span class="path8"></span>
|
||||
<span class="path9"></span>
|
||||
<span class="path10"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">Light</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="#" class="menu-link px-3 py-2" data-kt-element="mode" data-kt-value="dark">
|
||||
<span class="menu-icon" data-kt-element="icon">
|
||||
<i class="ki-duotone ki-moon fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">Dark</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="#" class="menu-link px-3 py-2" data-kt-element="mode" data-kt-value="system">
|
||||
<span class="menu-icon" data-kt-element="icon">
|
||||
<i class="ki-duotone ki-screen fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">System</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
<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="{default:'click', lg: 'hover'}" 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>
|
||||
<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">
|
||||
<span class="text-gray-700">User: {{.LoggedUser.Username}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-2"></div>
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="{{.ProfileURL}}" class="menu-link px-3 py-2">
|
||||
<span class="menu-title">Profile</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- if .LoggedUser.CanChangePassword}}
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="{{.ChangePwdURL}}" class="menu-link px-3 py-2">
|
||||
<span class="menu-title">Change password</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
<div class="menu-item px-3 my-0">
|
||||
<a href="#" class="menu-link px-3 py-2" onclick="doLogout();">
|
||||
<span class="menu-title">Sign out</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- End of Footer -->
|
||||
{{end}}
|
||||
{{- end}}
|
||||
<div class="{{- if .LoggedUser.Username}}app-wrapper{{- end}} flex-column flex-row-fluid " id="kt_app_wrapper">
|
||||
{{- if .LoggedUser.Username}}
|
||||
<!-- <div id="kt_app_toolbar" class="app-toolbar">
|
||||
<div id="kt_app_toolbar_container" class="app-container container-fluid d-flex flex-lg-column py-3 py-lg-6 ">
|
||||
|
||||
</div>
|
||||
<!-- End of Content Wrapper -->
|
||||
|
||||
</div>
|
||||
<!-- End of Page Wrapper -->
|
||||
|
||||
<!-- Scroll to Top Button-->
|
||||
<a class="scroll-to-top rounded" href="#page-top">
|
||||
<i class="fas fa-angle-up"></i>
|
||||
</a>
|
||||
|
||||
{{if .LoggedUser.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 id="kt_app_sidebar" class="app-sidebar flex-column" data-kt-drawer="true" data-kt-drawer-name="app-sidebar" data-kt-drawer-activate="{default: true, lg: false}" data-kt-drawer-overlay="true" data-kt-drawer-width="300px" data-kt-drawer-direction="start" data-kt-drawer-toggle="#kt_app_sidebar_mobile_toggle">
|
||||
<div class="app-sidebar-header flex-column mx-10 pt-8" id="kt_app_sidebar_header">
|
||||
<div class="d-flex flex-stack d-none d-lg-flex mb-13">
|
||||
<div class="app-sidebar-logo">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-40px app-sidebar-logo-default" />
|
||||
<span class="text-sidebar fs-4 fw-semibold ps-5">{{.Branding.ShortName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-sidebar-navs flex-column-fluid pb-6" id="kt_app_sidebar_navs">
|
||||
<div id="kt_app_sidebar_navs_wrappers" class="hover-scroll-y my-2 mx-4" data-kt-scroll="true" data-kt-scroll-activate="true" data-kt-scroll-height="auto" data-kt-scroll-dependencies="#kt_app_sidebar_header" data-kt-scroll-wrappers="#kt_app_sidebar_navs" data-kt-scroll-offset="5px">
|
||||
<div id="#kt_app_sidebar_menu" data-kt-menu="true" data-kt-menu-expand="false" class="menu menu-column menu-rounded menu-sub-indention menu-active-bg mb-7">
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .FilesURL}} active{{- end}}" href="{{.FilesURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-folder fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">{{.FilesTitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- if .LoggedUser.CanManageShares}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .SharesURL}} active{{- end}}" href="{{.SharesURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-share fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
<span class="path6"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">{{.SharesTitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .LoggedUser.CanManageMFA}}
|
||||
<div class="menu-item">
|
||||
<a class="menu-link {{- if eq .CurrentURL .MFAURL}} active{{- end}}" href="{{.MFAURL}}">
|
||||
<span class="menu-icon">
|
||||
<i class="ki-duotone ki-shield fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">{{.MFATitle}}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
<div class="app-main flex-column flex-row-fluid " id="kt_app_main">
|
||||
<div class="d-flex flex-column flex-column-fluid">
|
||||
<div id="kt_app_content" class="app-content flex-column-fluid">
|
||||
<div id="kt_app_content_container" class="app-container container-fluid">
|
||||
{{template "page_body" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- if .LoggedUser.Username}}
|
||||
<div id="kt_app_footer" class="app-footer">
|
||||
<div class="app-container container-fluid d-flex flex-column flex-md-row flex-center flex-md-stack py-3 align-items-center justify-content-center">
|
||||
<div class="text-dark order-2 order-md-1">
|
||||
<span class="text-gray-700 fw-semibold me-1">SFTPGo {{.Version}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div id="kt_scrolltop" class="scrolltop" data-kt-scrolltop="true">
|
||||
<i class="ki-duotone ki-arrow-up">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</div>
|
||||
<div class="page-loader flex-column">
|
||||
<span class="spinner-border text-primary" role="status"></span>
|
||||
<span id="loading_message" class="text-muted fs-4 fw-semibold mt-5"></span>
|
||||
</div>
|
||||
|
||||
{{block "dialog" .}}{{end}}
|
||||
<div class="modal fade" tabindex="-1" id="modal_alert">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="d-flex flex-center flex-column">
|
||||
<span id="modal_alert_icon">
|
||||
</span>
|
||||
<div class="mt-10 text-center">
|
||||
<span id="modal_alert_text" class="fs-6 text-dark fw-semibold"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 justify-content-center">
|
||||
<button id="modal_alert_cancel" type="button" class="btn btn-secondary m-2"></button>
|
||||
<button id="modal_alert_ok" type="button" class="btn btn-primary m-2"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
{{- block "modals" .}}{{- end}}
|
||||
<script src="{{.StaticURL}}/assets/plugins/global/plugins.bundle.js"></script>
|
||||
<script src="{{.StaticURL}}/assets/js/scripts.bundle.js"></script>
|
||||
{{- template "basejs" }}
|
||||
<script type="text/javascript">
|
||||
var ModalAlert = function () {
|
||||
var modal;
|
||||
var promiseResolve;
|
||||
var isResolved;
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
function resolvePromise(result) {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
promiseResolve({
|
||||
isConfirmed: result
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
|
||||
return {
|
||||
fire: function (params) {
|
||||
if (!modal){
|
||||
modal = new bootstrap.Modal('#modal_alert');
|
||||
|
||||
<script type="text/javascript">
|
||||
function escapeHTML(str) {
|
||||
var div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(str));
|
||||
return div.innerHTML;
|
||||
}
|
||||
modal._element.addEventListener('hide.bs.modal',function() {
|
||||
resolvePromise(false);
|
||||
});
|
||||
|
||||
function unescapeHTML(escapedStr) {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = escapedStr;
|
||||
var child = div.childNodes[0];
|
||||
return child ? child.nodeValue : '';
|
||||
}
|
||||
$('#modal_alert_cancel').on("click", function(){
|
||||
resolvePromise(false);
|
||||
modal.hide();
|
||||
});
|
||||
|
||||
function escapeHTMLForceSafe(str) {
|
||||
return str
|
||||
.replace(/&/g, '_')
|
||||
.replace(/</g, '_')
|
||||
.replace(/>/g, '_')
|
||||
.replace(/\"/g, '_')
|
||||
.replace(/\'/g, '_');
|
||||
}
|
||||
$('#modal_alert_ok').on("click", function(){
|
||||
resolvePromise(true);
|
||||
modal.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function fixedEncodeURIComponent(str) {
|
||||
return encodeURIComponent(unescapeHTML(str)).replace(/[!'()*]/g, function (c) {
|
||||
return '%' + c.charCodeAt(0).toString(16);
|
||||
});
|
||||
}
|
||||
let okBtn = $("#modal_alert_ok");
|
||||
let cancelBtn = $("#modal_alert_cancel");
|
||||
|
||||
function replaceSlash(str){
|
||||
return str.replace(/\//g,'\u2215');
|
||||
}
|
||||
okBtn.removeClass();
|
||||
okBtn.addClass(params.customClass.confirmButton);
|
||||
okBtn.addClass("m-2");
|
||||
okBtn.text(params.confirmButtonText);
|
||||
if (params.cancelButtonText){
|
||||
cancelBtn.text(params.cancelButtonText);
|
||||
cancelBtn.removeClass();
|
||||
cancelBtn.addClass(params.customClass.cancelButton);
|
||||
cancelBtn.addClass("m-2");
|
||||
} else {
|
||||
cancelBtn.addClass("d-none");
|
||||
}
|
||||
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(encodeURIComponent(str));
|
||||
}
|
||||
$("#modal_alert_text").text(params.text);
|
||||
|
||||
function UnicodeDecodeB64(str) {
|
||||
return decodeURIComponent(atob(str));
|
||||
}
|
||||
switch (params.icon){
|
||||
case "warning":
|
||||
$("#modal_alert_icon").html(`<i class="ki-duotone ki-information fs-5x text-danger">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>`);
|
||||
break;
|
||||
case "success":
|
||||
$("#modal_alert_icon").html(`<i class="ki-duotone ki-information-2 fs-5x text-success">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>`);
|
||||
break;
|
||||
default:
|
||||
$("#modal_alert_icon").html(`<i class="ki-duotone ki-question-2 fs-5x text-primary">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>`);
|
||||
}
|
||||
|
||||
function fileSizeIEC(a,b,c,d,e){
|
||||
return (b=Math,c=b.log,d=1024,e=c(a)/c(d)|0,a/b.pow(d,e)).toFixed(1)
|
||||
+' '+(e?'KMGTPEZY'[--e]+'iB':'Bytes')
|
||||
}
|
||||
</script>
|
||||
return new Promise(function(resolve, reject) {
|
||||
promiseResolve = resolve;
|
||||
isResolved = false;
|
||||
modal.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
}();
|
||||
|
||||
<!-- Page level plugins -->
|
||||
{{block "extra_js" .}}{{end}}
|
||||
|
||||
</body>
|
||||
|
||||
function doLogout() {
|
||||
ModalAlert.fire({
|
||||
text: `Are you sure you want to sign out?`,
|
||||
icon: "question",
|
||||
confirmButtonText: "Sign out",
|
||||
cancelButtonText: 'Cancel',
|
||||
customClass: {
|
||||
confirmButton: "btn btn-primary",
|
||||
cancelButton: 'btn btn-secondary'
|
||||
}
|
||||
}).then((result) =>{
|
||||
if (result.isConfirmed){
|
||||
window.location.replace('{{.LogoutURL}}');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{{- block "extra_js" .}}{{- end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
@@ -1,91 +1,60 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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 "baselogin"}}
|
||||
{{- define "baselogin"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
{{- template "fonts" .StaticURL }}
|
||||
<link href="{{.StaticURL}}/assets/plugins/global/plugins.bundle.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{{.StaticURL}}/assets/css/style.bundle.css" rel="stylesheet" type="text/css" />
|
||||
{{- template "globalstyle" }}
|
||||
{{- range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
{{- template "commonjs" }}
|
||||
</head>
|
||||
|
||||
<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 styles for this template-->
|
||||
<link href="{{.StaticURL}}{{.Branding.DefaultCSS}}" rel="stylesheet">
|
||||
<style>
|
||||
{{template "commoncss" .}}
|
||||
</style>
|
||||
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-gradient-primary">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-xl-10 col-lg-12 col-md-9">
|
||||
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row d-lg-none login-image">
|
||||
<div class="col-lg-12 d-block d-lg-none bg-login-image">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-5 d-none d-lg-block bg-login-image">
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.ShortName}} - {{.Version}}</h1>
|
||||
</div>
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
</body>
|
||||
|
||||
<body class="app-blank">
|
||||
{{- template "theme-setup"}}
|
||||
<div class="d-flex flex-column flex-root">
|
||||
<div class="d-flex flex-column flex-column-fluid bgi-position-y-bottom position-x-center bgi-no-repeat bgi-size-contain bgi-attachment-fixed">
|
||||
<div class="d-flex flex-center flex-column flex-column-fluid p-10 pb-lg-20">
|
||||
<div class="w-lg-500px w-md-450px w-sm-400px bg-body rounded shadow-sm p-10 p-lg-15 mx-auto">
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="{{.StaticURL}}/assets/plugins/global/plugins.bundle.js"></script>
|
||||
<script src="{{.StaticURL}}/assets/js/scripts.bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
$('#sign_in_form').submit(function (event) {
|
||||
let submitButton = document.querySelector('#sign_in_submit');
|
||||
submitButton.setAttribute('data-kt-indicator', 'on');
|
||||
submitButton.disabled = true;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
@@ -1,62 +1,75 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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 "title"}}{{.Title}}{{- end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Change password</h6>
|
||||
{{- define "page_body"}}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title text-primary">Change password</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="user_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
|
||||
{{- template "errmsg" .Error}}
|
||||
<form id="change_pwd_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
|
||||
<div class="form-group row">
|
||||
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idCurrentPassword" name="current_password" autocomplete="new-password" spellcheck="false" required>
|
||||
<label class="col-md-3 col-form-label required">Current password</label>
|
||||
<div class="col-md-9">
|
||||
<input type="password" class="form-control" placeholder="" name="current_password"
|
||||
spellcheck="false" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idNewPassword1" class="col-sm-2 col-form-label">New password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idNewPassword1" name="new_password1" autocomplete="new-password" spellcheck="false" required>
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label required">New password</label>
|
||||
<div class="col-md-9">
|
||||
<input type="password" class="form-control" placeholder="" name="new_password1"
|
||||
autocomplete="new-password" spellcheck="false" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idNewPassword2" class="col-sm-2 col-form-label">Confirm password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idNewPassword2" name="new_password2" autocomplete="new-password" spellcheck="false" required>
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label required">Confirm password</label>
|
||||
<div class="col-md-9">
|
||||
<input type="password" class="form-control" placeholder="" name="new_password2"
|
||||
autocomplete="new-password" spellcheck="false" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Change my password</button>
|
||||
<div class="d-flex justify-content-end mt-12">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="form_submit" class="btn btn-primary px-10">
|
||||
<span class="indicator-label">
|
||||
Submit
|
||||
</span>
|
||||
<span class="indicator-progress">
|
||||
Please wait... <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{- define "extra_js"}}
|
||||
<script type="text/javascript">
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
$('#change_pwd_form').submit(function (event) {
|
||||
let submitButton = document.querySelector('#form_submit');
|
||||
submitButton.setAttribute('data-kt-indicator', 'on');
|
||||
submitButton.disabled = true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{- end}}
|
||||
@@ -1,209 +1,164 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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}}/vendor/codemirror/codemirror.css" rel="stylesheet">
|
||||
<link href="{{.StaticURL}}/vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
|
||||
<link href="{{.StaticURL}}/vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
|
||||
{{- define "extra_css"}}
|
||||
<style>
|
||||
.CodeMirror {
|
||||
border: 1px solid #eee;
|
||||
height: 30em;
|
||||
font-size: 13px;
|
||||
}
|
||||
.shortcut {font-family: monospace; color: #666;}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "additionalnavitems"}}
|
||||
<li class="nav-item dropdown no-arrow mx-1">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="infoDropdown" role="button"
|
||||
data-toggle="modal" data-target="#infoModal">
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<div class="topbar-divider d-none d-sm-block"></div>
|
||||
{{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();
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</script>
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="d-flex justify-content-between align-items-center">
|
||||
<span class="font-weight-bold text-primary">{{- if .ReadOnly}}View{{- else}}Edit{{- end}} file "{{.Path}}"</span>
|
||||
<span class="btn-toolbar">
|
||||
<a id="idBack" class="btn btn-secondary mx-1 my-1" href='{{.FilesURL}}?path={{.CurrentDir}}' role="button">Back</a>
|
||||
{{if not .ReadOnly}}
|
||||
<a id="idSave" class="btn btn-primary mx-1 my-1" href="#" onclick="saveFile()" role="button">Save</a>
|
||||
{{end}}
|
||||
</span>
|
||||
</h6>
|
||||
|
||||
</style>
|
||||
{{- end}}
|
||||
|
||||
{{- define "additionalnavitems"}}
|
||||
<div class="d-flex align-items-center ms-2 ms-lg-3" id="kt_header_user_menu_toggle">
|
||||
<div class="btn btn-icon btn-active-light-primary w-35px h-35px w-md-40px h-md-40px" data-bs-toggle="modal" data-bs-target="#info_modal">
|
||||
<i class="ki-duotone ki-information-2 fs-2">
|
||||
<i class="path1"></i>
|
||||
<i class="path2"></i>
|
||||
<i class="path3"></i>
|
||||
</i>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="col-sm-12">
|
||||
<textarea id="editor" name="editor"></textarea>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
{{- define "page_body"}}
|
||||
{{- template "errmsg" ""}}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
{{- if .ReadOnly}}
|
||||
<h6 class="card-title">View file "{{.Path}}"</h6>
|
||||
{{- else}}
|
||||
<h6 class="card-title">Edit file "{{.Path}}"</h6>
|
||||
{{- end}}
|
||||
<div class="card-toolbar">
|
||||
<a class="btn btn-light-primary px-10 me-5" href='{{.FilesURL}}?path={{.CurrentDir}}' role="button">Back</a>
|
||||
{{- if not .ReadOnly}}
|
||||
<a id="save_button" type="button" class="btn btn-primary px-10" href="#" role="button" onclick="saveFile();">
|
||||
<span class="indicator-label">Save</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="editor" class="col-sm-12 border border-light-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{define "dialog"}}
|
||||
<div class="modal fade" id="infoModal" tabindex="-1" role="dialog" aria-labelledby="infoModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
{{- define "modals"}}
|
||||
<div class="modal fade" id="info_modal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="infoModalLabel">
|
||||
Editor keybindings
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h3 class="modal-title">Editor keybindings</h3>
|
||||
<div class="btn btn-icon btn-sm btn-active-light-primary ms-2" data-bs-dismiss="modal" aria-label="Close">
|
||||
<i class="ki-duotone ki-cross fs-1"><span class="path1"></span><span class="path2"></span></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<span class="shortcut">Ctrl-F / Cmd-F</span> => Start searching
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Ctrl-G / Cmd-G</span> => Find next
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Shift-Ctrl-G / Shift-Cmd-G</span> => Find previous
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Shift-Ctrl-F / Cmd-Option-F</span> => Replace
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Shift-Ctrl-R / Shift-Cmd-Option-F</span> => Replace all
|
||||
<span class="shortcut">Ctrl-F / Cmd-F</span> => Open search panel
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Alt-G</span> => Jump to line
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Alt-F</span> => Persistent search: enter to find next, Shift-Enter to find previous
|
||||
<span class="shortcut">Tab</span> => Indent more
|
||||
</p>
|
||||
<p>
|
||||
<span class="shortcut">Shift-Tab</span> => Indent less
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" data-dismiss="modal">OK</button>
|
||||
<button class="btn btn-primary" type="button" data-bs-dismiss="modal">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/codemirror.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/meta.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/selection/active-line.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/dialog/dialog.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/search/search.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/addon/search/jump-to-line.js"></script>
|
||||
{{- define "extra_js"}}
|
||||
<script src="{{.StaticURL}}/vendor/codemirror/cm6.bundle.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
var cm = CodeMirror.fromTextArea(document.getElementById("editor"), {
|
||||
lineNumbers: true,
|
||||
styleActiveLine: true,
|
||||
extraKeys: {"Alt-F": "findPersistent"},
|
||||
{{if .ReadOnly}}
|
||||
readOnly: true,
|
||||
{{end}}
|
||||
autofocus: true
|
||||
});
|
||||
var filename = "{{.Path}}";
|
||||
var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
|
||||
mode = CodeMirror.findModeByExtension(extension);
|
||||
if (mode != null) {
|
||||
cm.setOption("mode", mode.mode);
|
||||
}
|
||||
cm.setValue("{{.Data}}");
|
||||
setInterval(keepAlive, 300000);
|
||||
});
|
||||
var cmView;
|
||||
|
||||
function keepAlive() {
|
||||
$.ajax({
|
||||
url: '{{.ProfileURL}}',
|
||||
axios.get('{{.ProfileURL}}',{
|
||||
timeout: 15000
|
||||
});
|
||||
}).catch(function (error){});
|
||||
}
|
||||
|
||||
{{if not .ReadOnly}}
|
||||
//{{- if not .ReadOnly}}
|
||||
function saveFile() {
|
||||
$('#idSave').addClass("disabled");
|
||||
$('#errorMsg').hide();
|
||||
$('#errorMsg').addClass("d-none");
|
||||
let saveButton = document.querySelector('#save_button');
|
||||
saveButton.setAttribute('data-kt-indicator', 'on');
|
||||
saveButton.disabled = true;
|
||||
|
||||
async function uploadFile() {
|
||||
var errorMessage = "Error saving file";
|
||||
let response;
|
||||
try {
|
||||
var uploadPath = '{{.FileURL}}?path='+encodeURIComponent('{{.CurrentDir}}/{{.Name}}');
|
||||
var cm = document.querySelector('.CodeMirror').CodeMirror;
|
||||
var blob = new Blob([cm.getValue()]);
|
||||
response = await fetch(uploadPath, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
redirect: 'error',
|
||||
body: blob
|
||||
});
|
||||
if (response.status == 201){
|
||||
window.location.href = '{{.FilesURL}}?path='+encodeURIComponent('{{.CurrentDir}}');
|
||||
} else {
|
||||
let jsonResponse;
|
||||
try {
|
||||
jsonResponse = await response.json();
|
||||
} catch(e){
|
||||
throw Error(errorMessage);
|
||||
}
|
||||
if (jsonResponse.message) {
|
||||
errorMessage = jsonResponse.message;
|
||||
}
|
||||
if (jsonResponse.error) {
|
||||
errorMessage += ": " + jsonResponse.error;
|
||||
}
|
||||
throw Error(errorMessage);
|
||||
let uploadPath = '{{.FileURL}}?path='+encodeURIComponent('{{.CurrentDir}}/{{.Name}}');
|
||||
let blob = new Blob([cmView.state.doc.toString()]);
|
||||
|
||||
axios.post(uploadPath, blob, {
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||
},
|
||||
timeout: 600000,
|
||||
validateStatus: function (status) {
|
||||
return status == 201;
|
||||
}
|
||||
}).then(function (response) {
|
||||
window.location.replace('{{.FilesURL}}?path='+encodeURIComponent('{{.CurrentDir}}'));
|
||||
}).catch(function (error) {
|
||||
saveButton.removeAttribute('data-kt-indicator');
|
||||
saveButton.disabled = false;
|
||||
let errorMessage = "Error saving file";
|
||||
if (error && error.response) {
|
||||
if (error.response.data.message) {
|
||||
errorMessage = error.response.data.message;
|
||||
}
|
||||
if (error.response.data.error) {
|
||||
errorMessage += ": " + error.response.data.error;
|
||||
}
|
||||
} catch (e){
|
||||
throw Error(errorMessage+": " +e.message);
|
||||
}
|
||||
}
|
||||
|
||||
uploadFile().catch(function(error){
|
||||
$('#idSave').removeClass("disabled");
|
||||
$('#errorTxt').text(error.message);
|
||||
$('#errorMsg').show();
|
||||
$('#errorTxt').text(errorMessage);
|
||||
$('#errorMsg').removeClass("d-none");
|
||||
});
|
||||
}
|
||||
{{end}}
|
||||
//{{- end}}
|
||||
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
let options = {
|
||||
oneDark: KTThemeMode.getMode() == "dark"
|
||||
};
|
||||
//{{- if .ReadOnly}}
|
||||
options.readOnly = true;
|
||||
//{{- end}}
|
||||
cmView = cm6.createEditorView(undefined, document.getElementById("editor"));
|
||||
cmView.setState(cm6.createEditorState("{{.Data}}", options));
|
||||
|
||||
setInterval(keepAlive, 300000);
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
File diff suppressed because it is too large
Load Diff
58
templates/webclient/forgot-password.html
Normal file
58
templates/webclient/forgot-password.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
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 "baselogin" .}}
|
||||
|
||||
{{- define "title"}}Forgot password{{- end}}
|
||||
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<a href="{{.LoginURL}}" class="text-dark mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-dark mb-3">
|
||||
Forgot Password ?
|
||||
</h2>
|
||||
<div class="text-gray-600 fw-semibold fs-4">
|
||||
Enter your account username below, you will receive a password reset code by email.
|
||||
</div>
|
||||
</div>
|
||||
{{template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="text" placeholder="Your username" name="username" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span class="indicator-label">Send Reset Code</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
||||
@@ -1,69 +1,79 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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 "baselogin" .}}
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{- define "title"}}Login{{- end}}
|
||||
|
||||
{{define "content"}}
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST"
|
||||
class="user-custom">
|
||||
{{if not .FormDisabled}}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputUsername" name="username" placeholder="Username" spellcheck="false" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control form-control-user-custom"
|
||||
id="inputPassword" name="password" placeholder="Password" autocomplete="current-password" spellcheck="false" required>
|
||||
{{if .ForgotPwdURL}}
|
||||
<div class="text-right">
|
||||
<a class="small" href="{{.ForgotPwdURL}}">Forgot password?</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Login
|
||||
</button>
|
||||
{{end}}
|
||||
{{if .OpenIDLoginURL}}
|
||||
<hr>
|
||||
<a href="{{.OpenIDLoginURL}}" class="btn btn-secondary btn-user-custom btn-block">
|
||||
Login with OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
</form>
|
||||
{{if .AltLoginURL}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="{{.AltLoginURL}}">{{.AltLoginName}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Branding.DisclaimerName .Branding.DisclaimerPath}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="{{.StaticURL}}{{.Branding.DisclaimerPath}}" target="_blank">{{.Branding.DisclaimerName}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h1 class="text-dark mb-3 ms-3">
|
||||
{{.Branding.ShortName}}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "errmsg" .Error}}
|
||||
{{- if not .FormDisabled}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="text" name="username" placeholder="Username" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="password" name="password" placeholder="Password" autocomplete="current-password" spellcheck="false" required />
|
||||
<div class="d-flex justify-content-end mt-2">
|
||||
{{- if .ForgotPwdURL}}
|
||||
<a href="{{.ForgotPwdURL}}" class="link-primary fs-6 fw-bold">Forgot Password ?</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
<div class="text-center">
|
||||
{{- if not .FormDisabled}}
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span class="indicator-label">Sign in</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
{{- end}}
|
||||
{{- if .OpenIDLoginURL}}
|
||||
{{- if not .FormDisabled}}
|
||||
<div class="text-center text-muted text-uppercase fw-bold mb-5">or</div>
|
||||
{{- end}}
|
||||
<a href="{{.OpenIDLoginURL}}" class="btn btn-flex flex-center btn-light btn-lg w-100 mb-5">
|
||||
<img alt="Logo" src="{{.StaticURL}}/img/openid-logo.png" class="h-20px me-3" />Sign in with OpenID</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</form>
|
||||
{{- if or .AltLoginURL (and .Branding.DisclaimerName .Branding.DisclaimerPath) }}
|
||||
<div class="d-flex flex-center flex-column-auto pt-10 mt-5">
|
||||
<div class="d-flex align-items-center fw-semibold fs-6">
|
||||
{{- if .AltLoginURL}}
|
||||
<a href="{{.AltLoginURL}}" class="text-muted text-hover-primary px-2">{{.AltLoginName}}</a>
|
||||
{{- end}}
|
||||
{{- if and .Branding.DisclaimerName .Branding.DisclaimerPath}}
|
||||
<a href="{{.StaticURL}}{{.Branding.DisclaimerPath}}" target="_blank" class="text-muted text-hover-primary px-2">{{.Branding.DisclaimerName}}</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
@@ -1,74 +1,82 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
{{if .LoggedUser.Username}}
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">{{.Title}}</h6>
|
||||
{{- define "title"}}{{.Title}}{{- end}}
|
||||
{{- define "page_body"}}
|
||||
<div class="d-flex flex-center flex-column flex-column-fluid p-10 pb-lg-20">
|
||||
{{- if not .LoggedUser.Username}}
|
||||
{{- if .LoginURL}}
|
||||
<div class="mb-12">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
<a href="{{.LoginURL}}" class="text-dark fs-1 fw-bold ms-3 ps-5">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="card mb-2 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Success}}
|
||||
<div class="card mb-2 border-left-success">
|
||||
<div class="card-body">{{.Success}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- else}}
|
||||
<div class="mb-12">
|
||||
<span>
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</span>
|
||||
<span class="text-dark fs-1 fw-bold ms-3 ps-5">
|
||||
{{.Branding.ShortName}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-xl-8 col-lg-9 col-md-9">
|
||||
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-9">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Title}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="card mb-4 border-left-warning">
|
||||
<div class="card-body text-form-error">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Success}}
|
||||
<div class="card mb-4 border-left-success">
|
||||
<div class="card-body">{{.Success}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
<div class="card shadow-sm w-lg-600px">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title text-primary">{{.Title}}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{- if .Error}}
|
||||
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed p-6">
|
||||
<i class="ki-duotone ki-information fs-2tx text-warning me-4">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
<div class="d-flex flex-stack flex-grow-1 ">
|
||||
<div class=" fw-semibold">
|
||||
<div class="fs-5 text-gray-800">
|
||||
{{.Error}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .Success}}
|
||||
<div class="notice d-flex bg-light-primary rounded border-primary border border-dashed p-6">
|
||||
<i class="ki-duotone ki-information-2 fs-2tx text-primary me-4">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
<div class="d-flex flex-stack flex-grow-1 ">
|
||||
<div class=" fw-semibold">
|
||||
<div class="fs-5 text-gray-800">
|
||||
{{.Success}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
{{- end}}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,146 +1,166 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">My profile - {{.LoggedUser.Username}}</h6>
|
||||
{{- define "page_body"}}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title text-primary">My profile - {{.LoggedUser.Username}}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="profile_form" action="{{.CurrentURL}}" method="POST">
|
||||
{{- template "errmsg" .Error}}
|
||||
<form id="page_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="form-group row">
|
||||
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
|
||||
<div class="col-sm-10">
|
||||
<label class="col-md-3 col-form-label">Email</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" id="idEmail" name="email" placeholder="" spellcheck="false"
|
||||
value="{{.Email}}" maxlength="255" autocomplete="nope" {{if not .LoggedUser.CanChangeInfo}}readonly{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Description</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" id="idDescription" name="description" placeholder=""
|
||||
value="{{.Description}}" maxlength="255" {{if not .LoggedUser.CanChangeInfo}}readonly{{end}}>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth" {{if not .LoggedUser.CanChangeAPIKeyAuth}}disabled="disabled"{{end}}
|
||||
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
|
||||
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
|
||||
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
|
||||
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
|
||||
</small>
|
||||
<div class="form-group row align-items-center mt-10">
|
||||
<label class="col-md-3 col-form-label">API key authentication</label>
|
||||
<div class="col-md-9">
|
||||
<div class="form-check form-switch form-check-custom form-check-solid">
|
||||
<input class="form-check-input" type="checkbox" id="idAllowAPIKeyAuth" name="allow_api_key_auth" {{if not .LoggedUser.CanChangeAPIKeyAuth}}disabled="disabled"{{end}} {{if .AllowAPIKeyAuth}}checked="checked"{{end}}/>
|
||||
<label class="form-check-label fw-semibold text-gray-800" for="idAllowAPIKeyAuth">
|
||||
Allow to impersonate yourself, in REST API, using an API key
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .LoggedUser.CanManagePublicKeys}}
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
Public keys
|
||||
{{- if .LoggedUser.CanManagePublicKeys}}
|
||||
<div class="card card-rounded mt-10">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title">Public keys</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_pk_outer">
|
||||
{{range $idx, $val := .PublicKeys}}
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey{{$idx}}" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here">{{$val}}</textarea>
|
||||
<div id="public_keys">
|
||||
<div class="form-group">
|
||||
<div data-repeater-list="public_keys">
|
||||
{{- range $idx, $val := .PublicKeys}}
|
||||
<div data-repeater-item>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9">
|
||||
<textarea class="form-control mt-3 mt-md-8" name="public_key" rows="4"
|
||||
placeholder="Paste your public key here">{{$val}}</textarea>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="javascript:;" 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>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
{{- else}}
|
||||
<div data-repeater-item>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9">
|
||||
<textarea class="form-control mt-3 mt-md-8" name="public_key" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="javascript:;" 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>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey0" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field" disabled>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_pk_field_btn">
|
||||
<i class="fas fa-plus"></i> Add new public key
|
||||
</button>
|
||||
<div class="form-group mt-5">
|
||||
<a href="javascript:;" data-repeater-create class="btn btn-light-primary">
|
||||
<i class="ki-duotone ki-plus fs-3"></i>
|
||||
Add
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .CanSubmit}}
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
<div class="d-flex justify-content-end mt-12">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="form_submit" class="btn btn-primary px-10">
|
||||
<span class="indicator-label">
|
||||
Submit
|
||||
</span>
|
||||
<span class="indicator-progress">
|
||||
Please wait... <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
{{if .LoggedUser.CanManagePublicKeys}}
|
||||
{{- define "extra_js"}}
|
||||
{{- if .LoggedUser.CanManagePublicKeys}}
|
||||
<script src="{{.StaticURL}}/assets/plugins/custom/formrepeater/formrepeater.bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
$("body").on("click", ".add_new_pk_field_btn", function () {
|
||||
let index = $(".form_field_pk_outer").find(".form_field_pk_outer_row").length;
|
||||
while (document.getElementById("idPublicKey"+index) != null){
|
||||
index++;
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
$('#public_keys').repeater({
|
||||
initEmpty: false,
|
||||
|
||||
show: function () {
|
||||
$(this).slideDown();
|
||||
},
|
||||
|
||||
hide: function (deleteElement) {
|
||||
$(this).slideUp(deleteElement);
|
||||
}
|
||||
$(".form_field_pk_outer").append(`
|
||||
<div class="row form_field_pk_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<textarea class="form-control" id="idPublicKey${index}" name="public_keys" rows="4"
|
||||
placeholder="Paste your public key here"></textarea>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
$("body").on("click", ".remove_pk_btn_frm_field", function () {
|
||||
$(this).closest(".form_field_pk_outer_row").remove();
|
||||
|
||||
$("#page_form").submit(function (event) {
|
||||
let submitButton = document.querySelector('#form_submit');
|
||||
submitButton.setAttribute('data-kt-indicator', 'on');
|
||||
submitButton.disabled = true;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
64
templates/webclient/reset-password.html
Normal file
64
templates/webclient/reset-password.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!--
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
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 "baselogin" .}}
|
||||
|
||||
{{- define "title"}}Reset password{{- end}}
|
||||
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<a href="{{.LoginURL}}" class="text-dark mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-10">
|
||||
<h2 class="text-dark mb-3">
|
||||
Reset Password
|
||||
</h2>
|
||||
<div class="text-gray-600 fw-semibold fs-4">
|
||||
Check your email for the confirmation code
|
||||
</div>
|
||||
</div>
|
||||
{{template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="text" placeholder="Confirmation code" name="code" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="password" name="password" placeholder="New Password" autocomplete="new-password" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="password" name="confirm_password" placeholder="Confirm Password" autocomplete="new-password" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span class="indicator-label">Update Password & Login</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
||||
@@ -1,230 +1,237 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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}}/vendor/tempusdominus/css/tempusdominus-bootstrap-4.min.css" rel="stylesheet">
|
||||
<link href="{{.StaticURL}}/vendor/bootstrap-select/css/bootstrap-select.min.css" rel="stylesheet">
|
||||
{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">{{if .IsAdd}}Add a new share{{else}}Edit share{{end}}</h6>
|
||||
{{- define "page_body"}}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title text-primary">{{if .IsAdd}}Add a new share{{else}}Edit share{{end}}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- template "errmsg" .Error}}
|
||||
<form id="share_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
|
||||
<div class="form-group row">
|
||||
<label for="idName" class="col-sm-2 col-form-label">Name</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="idName" name="name" placeholder=""
|
||||
value="{{.Share.Name}}" maxlength="255" autocomplete="nope" required {{if not .IsAdd}}readonly{{end}}>
|
||||
<label class="col-md-3 col-form-label">Name</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control" placeholder="" name="name" value="{{.Share.Name}}"
|
||||
maxlength="255" autocomplete="nope" required {{if not .IsAdd}}readonly{{end}} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idScope" class="col-sm-2 col-form-label">Scope</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control selectpicker" id="idScope" name="scope" aria-describedby="scopeHelpBlock">
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Scope</label>
|
||||
<div class="col-md-9">
|
||||
<select name="scope" class="form-select" data-control="select2" data-hide-search="true">
|
||||
<option value="1" {{if eq .Share.Scope 1 }}selected{{end}}>Read</option>
|
||||
<option value="2" {{if eq .Share.Scope 2 }}selected{{end}}>Write</option>
|
||||
<option value="3" {{if eq .Share.Scope 3 }}selected{{end}}>Read/Write</option>
|
||||
</select>
|
||||
<small id="scopeHelpBlock" class="form-text text-muted">
|
||||
<div class="form-text">
|
||||
For scope "Write" and "Read&Write" you have to define one path and it must be a directory
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">
|
||||
Paths
|
||||
<div class="card card-rounded mt-10">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title">Paths</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12 form_field_path_outer">
|
||||
{{range $idx, $val := .Share.Paths}}
|
||||
<div class="row form_field_path_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<input type="text" class="form-control" id="idPath{{$idx}}" name="paths"
|
||||
placeholder="file or directory path, i.e. /dir or /dir/file.txt" value="{{$val}}" maxlength="255">
|
||||
<div id="paths">
|
||||
<div class="form-group">
|
||||
<div data-repeater-list="paths">
|
||||
{{- range $idx, $val := .Share.Paths}}
|
||||
<div data-repeater-item>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control mt-3 mt-md-8"
|
||||
placeholder="file or directory path, i.e. /dir or /dir/file.txt"
|
||||
name="path" value="{{$val}}" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="javascript:;" 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>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_path_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
{{- else}}
|
||||
<div data-repeater-item>
|
||||
<div class="form-group row">
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control mt-3 mt-md-8"
|
||||
placeholder="file or directory path, i.e. /dir or /dir/file.txt"
|
||||
name="path" value="" />
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<a href="javascript:;" 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>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
<span class="path5"></span>
|
||||
</i>
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="row form_field_path_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<input type="text" class="form-control" id="idPath0" name="paths"
|
||||
placeholder="file or directory path, i.e. /dir or /dir/file.txt" value="" maxlength="512">
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_path_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="form-group mt-5">
|
||||
<a href="javascript:;" data-repeater-create class="btn btn-light-primary">
|
||||
<i class="ki-duotone ki-plus fs-3"></i>
|
||||
Add
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mx-1">
|
||||
<button type="button" class="btn btn-secondary add_new_path_field_btn">
|
||||
<i class="fas fa-plus"></i> Add a path
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idPassword" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" id="idPassword" name="password" autocomplete="new-password" placeholder="" spellcheck="false"
|
||||
value="{{.Share.Password}}" aria-describedby="passwordHelpBlock">
|
||||
<small id="passwordHelpBlock" class="form-text text-muted">
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Password</label>
|
||||
<div class="col-md-9">
|
||||
<input type="password" class="form-control" name="password" autocomplete="new-password"
|
||||
placeholder="" spellcheck="false" value="{{.Share.Password}}" />
|
||||
<div class="form-text">
|
||||
If set the share will be password-protected
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idExpiration" class="col-sm-2 col-form-label">Expiration</label>
|
||||
<div class="col-sm-10 input-group date" id="expirationDateTimePicker" data-target-input="nearest">
|
||||
<input type="text" class="form-control datetimepicker-input" id="idExpiration"
|
||||
data-target="#expirationDateTimePicker" placeholder="none">
|
||||
<div class="input-group-append" data-target="#expirationDateTimePicker" data-toggle="datetimepicker">
|
||||
<div class="input-group-text"><i class="fas fa-calendar"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idMaxTokens" class="col-sm-2 col-form-label">Max tokens</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" min="0" class="form-control" id="idMaxTokens" name="max_tokens" placeholder=""
|
||||
value="{{.Share.MaxTokens}}" aria-describedby="maxTokensHelpBlock">
|
||||
<small id="maxTokensHelpBlock" class="form-text text-muted">
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Expiration</label>
|
||||
<div class="col-md-9">
|
||||
<div class="input-group" data-td-target-input="nearest" data-td-target-toggle="nearest">
|
||||
<input id="id_expiration" type="text" class="form-control" data-td-target="#id_expiration" />
|
||||
<span class="input-group-text" data-td-target="#id_expiration" data-td-toggle="datetimepicker">
|
||||
<i class="ki-duotone ki-calendar fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Max tokens</label>
|
||||
<div class="col-md-9">
|
||||
<input type="number" min="0" class="form-control" name="max_tokens" value="{{.Share.MaxTokens}}" />
|
||||
<div class="form-text">
|
||||
Maximum number of times this share can be accessed. 0 means no limit
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idAllowedIP" class="col-sm-2 col-form-label">Allowed IP/Mask</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="idAllowedIP" name="allowed_ip" rows="3" placeholder=""
|
||||
aria-describedby="allowedIPHelpBlock">{{.Share.GetAllowedFromAsString}}</textarea>
|
||||
<small id="allowedIPHelpBlock" class="form-text text-muted">
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Allowed IP/Mask</label>
|
||||
<div class="col-md-9">
|
||||
<textarea class="form-control" name="allowed_ip" rows="3"
|
||||
placeholder="">{{.Share.GetAllowedFromAsString}}</textarea>
|
||||
<div class="form-text">
|
||||
Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" id="idDescription" name="description" rows="3">{{.Share.Description}}</textarea>
|
||||
<div class="form-group row mt-10">
|
||||
<label class="col-md-3 col-form-label">Description</label>
|
||||
<div class="col-md-9">
|
||||
<textarea class="form-control" name="description" rows="3"
|
||||
placeholder="">{{.Share.Description}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="expiration_date" id="hidden_start_datetime" value="">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
<div class="d-flex justify-content-end mt-12">
|
||||
<input type="hidden" name="expiration_date" id="hidden_start_datetime" value="">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="form_submit" class="btn btn-primary px-10">
|
||||
<span class="indicator-label">
|
||||
Submit
|
||||
</span>
|
||||
<span class="indicator-progress">
|
||||
Please wait... <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
<script src="{{.StaticURL}}/vendor/moment/js/moment.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/tempusdominus/js/tempusdominus-bootstrap-4.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/bootstrap-select/js/bootstrap-select.min.js"></script>
|
||||
{{- define "extra_js"}}
|
||||
<script src="{{.StaticURL}}/assets/plugins/custom/formrepeater/formrepeater.bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function () {
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
$('#paths').repeater({
|
||||
initEmpty: false,
|
||||
|
||||
$('#expirationDateTimePicker').datetimepicker({
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
sideBySide: true,
|
||||
minDate: moment(),
|
||||
buttons: {
|
||||
showClear: false,
|
||||
showClose: true,
|
||||
showToday: false
|
||||
}
|
||||
});
|
||||
show: function () {
|
||||
$(this).slideDown();
|
||||
},
|
||||
|
||||
{{ if gt .Share.ExpiresAt 0 }}
|
||||
var input_dt = moment({{.Share.ExpiresAt }}).format('YYYY-MM-DD HH:mm');
|
||||
$('#idExpiration').val(input_dt);
|
||||
$('#expirationDateTimePicker').datetimepicker('viewDate', input_dt);
|
||||
{{ end }}
|
||||
hide: function (deleteElement) {
|
||||
$(this).slideUp(deleteElement);
|
||||
}
|
||||
});
|
||||
|
||||
$("#share_form").submit(function (event) {
|
||||
var dt = $('#idExpiration').val();
|
||||
if (dt) {
|
||||
var d = $('#expirationDateTimePicker').datetimepicker('viewDate');
|
||||
if (d) {
|
||||
var dateString = moment.utc(d).format('YYYY-MM-DD HH:mm:ss');
|
||||
$('#hidden_start_datetime').val(dateString);
|
||||
const picker = new tempusDominus.TempusDominus(document.getElementById("id_expiration"), {
|
||||
localization: {
|
||||
format: 'yyyy-MM-dd HH:mm'
|
||||
}
|
||||
});
|
||||
//{{ if gt .Share.ExpiresAt 0 }}
|
||||
let input_dt = moment('{{.Share.ExpiresAt }}','x').toDate();
|
||||
picker.dates.setValue(tempusDominus.DateTime.convert(input_dt));
|
||||
//{{ end }}
|
||||
|
||||
$("#share_form").submit(function (event) {
|
||||
let dt = $('#id_expiration').val();
|
||||
if (dt) {
|
||||
let d = picker.viewDate;
|
||||
if (d) {
|
||||
let dateString = moment.utc(d).format('YYYY-MM-DD HH:mm:ss');
|
||||
$('#hidden_start_datetime').val(dateString);
|
||||
} else {
|
||||
$('#hidden_start_datetime').val("");
|
||||
}
|
||||
} else {
|
||||
$('#hidden_start_datetime').val("");
|
||||
}
|
||||
} else {
|
||||
$('#hidden_start_datetime').val("");
|
||||
}
|
||||
return true;
|
||||
let submitButton = document.querySelector('#form_submit');
|
||||
submitButton.setAttribute('data-kt-indicator', 'on');
|
||||
submitButton.disabled = true;
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on("click", ".add_new_path_field_btn", function () {
|
||||
let index = $(".form_field_path_outer").find(".form_field_path_outer_row").length;
|
||||
while (document.getElementById("idPath"+index) != null){
|
||||
index++;
|
||||
}
|
||||
$(".form_field_path_outer").append(`
|
||||
<div class="row form_field_path_outer_row">
|
||||
<div class="form-group col-md-11">
|
||||
<input type="text" class="form-control" id="idPath${index}" name="paths"
|
||||
placeholder="file or directory path, i.e. /dir or /dir/file.txt" value="" maxlength="512">
|
||||
</div>
|
||||
<div class="form-group col-md-1">
|
||||
<button class="btn btn-circle btn-danger remove_path_btn_frm_field">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
$("body").on("click", ".remove_path_btn_frm_field", function () {
|
||||
$(this).closest(".form_field_path_outer_row").remove();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
@@ -1,572 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 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 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.
|
||||
|
||||
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/>.
|
||||
-->
|
||||
{{template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{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/dataTables.checkboxes.css" rel="stylesheet">
|
||||
<link href="{{.StaticURL}}/vendor/filepond/filepond.min.css" rel="stylesheet" />
|
||||
<style>
|
||||
div.dataTables_wrapper span.selected-info,
|
||||
div.dataTables_wrapper span.selected-item {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
<div class="card shadow my-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold"><a href="{{.FilesURL}}?path=%2F"><i class="fas fa-home"></i> Home</a> {{range .Paths}}{{if eq .Href ""}}/{{.DirName}}{{else}}<a href="{{.Href}}">/{{.DirName}}</a>{{end}}{{end}}</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<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="tableContainer" class="table-responsive">
|
||||
<table class="table table-hover nowrap" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Type</th>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th>Last modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "dialog"}}
|
||||
<div class="modal fade" id="uploadFilesModal" tabindex="-1" role="dialog" aria-labelledby="uploadFilesModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="uploadFilesModalLabel">
|
||||
Upload one or more files
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="upload_files_form" action="{{.FilesURL}}?path={{.CurrentDir}}" method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<div id="uploadErrorMsg" class="card mb-4 border-left-warning" style="display: none;">
|
||||
<div id="uploadErrorTxt" class="card-body text-form-error"></div>
|
||||
</div>
|
||||
<input type="file" id="files_name" name="filenames" required multiple>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="spinnerModal" tabindex="-1" role="dialog" data-keyboard="false" data-backdrop="static">
|
||||
<div class="modal-dialog modal-dialog-centered justify-content-center" role="document">
|
||||
<span style="color: #333333;" class="fa fa-spinner fa-spin fa-3x"></span>
|
||||
</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/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.checkboxes.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/filepond/filepond.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var spinnerDone = false;
|
||||
|
||||
function shortenData(d, cutoff) {
|
||||
if ( typeof d !== 'string' ) {
|
||||
return d;
|
||||
}
|
||||
|
||||
if ( d.length <= cutoff ) {
|
||||
return escapeHTML(d);
|
||||
}
|
||||
|
||||
var shortened = d.substr(0, cutoff-1);
|
||||
return escapeHTML(shortened)+'…';
|
||||
}
|
||||
|
||||
function getIconForFile(filename) {
|
||||
var extension = filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2).toLowerCase();
|
||||
switch (extension) {
|
||||
case "doc":
|
||||
case "docx":
|
||||
case "odt":
|
||||
case "wps":
|
||||
return "far fa-file-word";
|
||||
case "ppt":
|
||||
case "pptx":
|
||||
return "far fa-file-powerpoint";
|
||||
case "xls":
|
||||
case "xlsx":
|
||||
case "ods":
|
||||
return "far fa-file-excel";
|
||||
case "pdf":
|
||||
return "far fa-file-pdf";
|
||||
case "webm":
|
||||
case "mkv":
|
||||
case "flv":
|
||||
case "vob":
|
||||
case "ogv":
|
||||
case "ogg":
|
||||
case "avi":
|
||||
case "ts":
|
||||
case "mov":
|
||||
case "wmv":
|
||||
case "asf":
|
||||
case "mpeg":
|
||||
case "mpv":
|
||||
case "3gp":
|
||||
case "mp4":
|
||||
return "far fa-file-video";
|
||||
case "jpeg":
|
||||
case "jpg":
|
||||
case "png":
|
||||
case "gif":
|
||||
case "webp":
|
||||
case "tiff":
|
||||
case "psd":
|
||||
case "bmp":
|
||||
case "svg":
|
||||
case "jp2":
|
||||
return "far fa-file-image";
|
||||
case "go":
|
||||
case "sh":
|
||||
case "bat":
|
||||
case "java":
|
||||
case "php":
|
||||
case "cs":
|
||||
case "asp":
|
||||
case "aspx":
|
||||
case "css":
|
||||
case "html":
|
||||
case "xhtml":
|
||||
case "htm":
|
||||
case "js":
|
||||
case "jsp":
|
||||
case "py":
|
||||
case "rb":
|
||||
case "cgi":
|
||||
case "c":
|
||||
case "cpp":
|
||||
case "h":
|
||||
case "hpp":
|
||||
case "kt":
|
||||
case "ktm":
|
||||
case "kts":
|
||||
case "swift":
|
||||
case "r":
|
||||
return "far fa-file-code";
|
||||
case "zip":
|
||||
case "zipx":
|
||||
case "7z":
|
||||
case "rar":
|
||||
case "tar":
|
||||
case "gz":
|
||||
case "bz2":
|
||||
case "zstd":
|
||||
case "zst":
|
||||
case "sz":
|
||||
case "lz":
|
||||
case "lz4":
|
||||
case "xz":
|
||||
case "jar":
|
||||
return "far fa-file-archive";
|
||||
case "txt":
|
||||
case "rtf":
|
||||
case "json":
|
||||
case "xml":
|
||||
case "yaml":
|
||||
case "toml":
|
||||
case "log":
|
||||
case "csv":
|
||||
case "ini":
|
||||
case "cfg":
|
||||
return "far fa-file-alt";
|
||||
default:
|
||||
return "far fa-file";
|
||||
}
|
||||
}
|
||||
|
||||
function getNameFromMeta(meta) {
|
||||
return meta.split('_').slice(1).join('_');
|
||||
}
|
||||
|
||||
const isDirectoryEntry = item => isEntry(item) && (getAsEntry(item) || {}).isDirectory;
|
||||
const isEntry = item => 'webkitGetAsEntry' in item;
|
||||
const getAsEntry = item => item.webkitGetAsEntry();
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#spinnerModal').on('shown.bs.modal', function () {
|
||||
if (spinnerDone){
|
||||
$('#spinnerModal').modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
{{if gt .Scope 1}}
|
||||
FilePond.create(document.getElementById("files_name"),{
|
||||
allowMultiple: true,
|
||||
name: 'filenames',
|
||||
maxFiles: 30,
|
||||
credits: false,
|
||||
required: true,
|
||||
onwarning: function(error){
|
||||
if (error.code == 0){
|
||||
$('#uploadErrorTxt').text('You can upload a maximum of 30 files');
|
||||
$('#uploadErrorMsg').show();
|
||||
setTimeout(function () {
|
||||
$('#uploadErrorMsg').hide();
|
||||
}, 10000);
|
||||
}
|
||||
},
|
||||
beforeAddFile: (fileItem) => new Promise(resolve => {
|
||||
let num = 0;
|
||||
FilePond.find(document.getElementById("files_name")).getFiles().forEach(function(val){
|
||||
if (val.filename == fileItem.filename){
|
||||
num++;
|
||||
}
|
||||
});
|
||||
resolve(num == 1);
|
||||
})
|
||||
});
|
||||
|
||||
$('#tableContainer').on("dragover", function(ev){
|
||||
ev.preventDefault();
|
||||
$('#tableContainer').css('opacity','0.5');
|
||||
});
|
||||
|
||||
$('#tableContainer').on("dragend dragleave", function(ev){
|
||||
ev.preventDefault();
|
||||
$('#tableContainer').css('opacity','1');
|
||||
});
|
||||
|
||||
$('#tableContainer').on("drop", function(ev){
|
||||
ev.preventDefault();
|
||||
$('#tableContainer').css('opacity','1');
|
||||
let filesDropped = false;
|
||||
|
||||
if (ev.originalEvent.dataTransfer.items) {
|
||||
[...ev.originalEvent.dataTransfer.items].forEach((item, i) => {
|
||||
if (item.kind === 'file') {
|
||||
// if this is a directory just open the upload dialog
|
||||
if (!isDirectoryEntry(item)){
|
||||
FilePond.find(document.getElementById("files_name")).addFile(item.getAsFile());
|
||||
filesDropped = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
[...ev.originalEvent.dataTransfer.files].forEach((file, i) => {
|
||||
FilePond.find(document.getElementById("files_name")).addFile(file);
|
||||
filesDropped = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (filesDropped && !$('#uploadFilesModal').hasClass('show')){
|
||||
$('#uploadFilesModal').modal('show');
|
||||
}
|
||||
});
|
||||
{{end}}
|
||||
|
||||
$("#upload_files_form").submit(function (event){
|
||||
event.preventDefault();
|
||||
let files = FilePond.find(document.getElementById("files_name")).getFiles();
|
||||
let has_errors = false;
|
||||
let index = 0;
|
||||
let success = 0;
|
||||
spinnerDone = false;
|
||||
|
||||
$('#uploadFilesModal').modal('hide');
|
||||
$('#spinnerModal').modal('show');
|
||||
$('#errorMsg').hide();
|
||||
|
||||
function uploadFile() {
|
||||
if (index >= files.length || has_errors){
|
||||
$('#spinnerModal').modal('hide');
|
||||
spinnerDone = true;
|
||||
if (!has_errors){
|
||||
location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
let errorMessage = "Error uploading files";
|
||||
let response;
|
||||
try {
|
||||
let f = files[index].file;
|
||||
let uploadPath = '{{.UploadBaseURL}}'+fixedEncodeURIComponent("/"+escapeHTML(f.name));
|
||||
let lastModified;
|
||||
try {
|
||||
lastModified = f.lastModified;
|
||||
} catch (e) {
|
||||
console.log("unable to get last modified time from file: "+e.message);
|
||||
lastModified = "";
|
||||
}
|
||||
response = await fetch(uploadPath, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-SFTPGO-MTIME': lastModified
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
redirect: 'error',
|
||||
body: f
|
||||
});
|
||||
} catch (e){
|
||||
throw Error(errorMessage+": " +e.message);
|
||||
}
|
||||
if (response.status == 201){
|
||||
index++;
|
||||
success++;
|
||||
uploadFile();
|
||||
} else {
|
||||
let jsonResponse;
|
||||
try {
|
||||
jsonResponse = await response.json();
|
||||
} catch(e){
|
||||
throw Error(errorMessage);
|
||||
}
|
||||
if (jsonResponse.message) {
|
||||
errorMessage = jsonResponse.message;
|
||||
}
|
||||
if (jsonResponse.error) {
|
||||
errorMessage += ": " + jsonResponse.error;
|
||||
}
|
||||
throw Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
saveFile().catch(function(error){
|
||||
index++;
|
||||
has_errors = true;
|
||||
$('#errorTxt').text(error.message);
|
||||
$('#errorMsg').show();
|
||||
uploadFile();
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile();
|
||||
});
|
||||
|
||||
$.fn.dataTable.ext.buttons.refresh = {
|
||||
text: '<i class="fas fa-sync-alt"></i>',
|
||||
name: 'refresh',
|
||||
titleAttr: "Refresh",
|
||||
action: function (e, dt, node, config) {
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.download = {
|
||||
text: '<i class="fas fa-download"></i>',
|
||||
name: 'download',
|
||||
titleAttr: "Download zip",
|
||||
action: function (e, dt, node, config) {
|
||||
var filesArray = [];
|
||||
var selected = dt.column(0).checkboxes.selected();
|
||||
for (i = 0; i < selected.length; i++) {
|
||||
filesArray.push(getNameFromMeta(selected[i]));
|
||||
}
|
||||
var files = encodeURIComponent(JSON.stringify(filesArray));
|
||||
var downloadURL = '{{.DownloadURL}}';
|
||||
var currentDir = '{{.CurrentDir}}';
|
||||
var ts = new Date().getTime().toString();
|
||||
window.open(`${downloadURL}?path=${currentDir}&files=${files}&_=${ts}`);
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.addFiles = {
|
||||
text: '<i class="fas fa-file-upload"></i>',
|
||||
name: 'addFiles',
|
||||
titleAttr: "Upload files",
|
||||
action: function (e, dt, node, config) {
|
||||
//FilePond.find(document.getElementById("files_name")).removeFiles();
|
||||
$('#uploadFilesModal').modal('show');
|
||||
},
|
||||
enabled: true
|
||||
};
|
||||
|
||||
let table = $('#dataTable').DataTable({
|
||||
"ajax": {
|
||||
"url": "{{.DirsURL}}?path={{.CurrentDir}}",
|
||||
"dataSrc": "",
|
||||
"error": function ($xhr, textStatus, errorThrown) {
|
||||
$(".dataTables_processing").hide();
|
||||
let txt = "Failed to get directory listing";
|
||||
if ($xhr) {
|
||||
let json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message){
|
||||
txt += ": " + json.message;
|
||||
} else {
|
||||
txt += ": " + json.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
}
|
||||
},
|
||||
"deferRender": true,
|
||||
"processing": true,
|
||||
"lengthMenu": [ 10, 25, 50, 100, 250, 500 ],
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
"stateSaveParams": function (settings, data) {
|
||||
data.sftpgo_dir = '{{.CurrentDir}}';
|
||||
},
|
||||
"stateLoadParams": function (settings, data) {
|
||||
if (!data.sftpgo_dir || data.sftpgo_dir != '{{.CurrentDir}}'){
|
||||
data.start = 0;
|
||||
data.search.search = "";
|
||||
}
|
||||
data.checkboxes = [];
|
||||
},
|
||||
"columns": [
|
||||
{ "data": "meta" },
|
||||
{ "data": "type" },
|
||||
{
|
||||
"data": "name",
|
||||
"render": function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
var title = "";
|
||||
var cssClass = "";
|
||||
var shortened = shortenData(data, 70);
|
||||
data = escapeHTML(data);
|
||||
if (shortened != data){
|
||||
title = escapeHTML(data);
|
||||
cssClass = "ellipsis";
|
||||
}
|
||||
|
||||
if (row["type"] == "1") {
|
||||
return `<i class="fas fa-folder"></i> <a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
|
||||
}
|
||||
if (row["size"] === "") {
|
||||
return `<i class="fas fa-external-link-alt"></i> <a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
|
||||
}
|
||||
var icon = getIconForFile(data);
|
||||
return `<i class="${icon}"></i> <a class="${cssClass}" href="${row['url']}" title="${title}">${shortened}</a>`;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
"data": "size",
|
||||
"render": function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
if (data || data === 0){
|
||||
return fileSizeIEC(data);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{ "data": "last_modified" }
|
||||
],
|
||||
"buttons": [],
|
||||
"lengthChange": true,
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0],
|
||||
"checkboxes": {
|
||||
"selectCallback": function (nodes, selected) {
|
||||
var selectedItems = table.column(0).checkboxes.selected().length;
|
||||
var selectedText = "";
|
||||
if (selectedItems == 1) {
|
||||
selectedText = "1 item selected";
|
||||
} else if (selectedItems > 1) {
|
||||
selectedText = `${selectedItems} items selected`;
|
||||
}
|
||||
table.button('download:name').enable(selectedItems > 0);
|
||||
$('#dataTable_info').find('span').remove();
|
||||
$("#dataTable_info").append('<span class="selected-info"><span class="selected-item">' + selectedText + '</span></span>');
|
||||
}
|
||||
},
|
||||
"orderable": false,
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [1],
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
},
|
||||
{
|
||||
"targets": [3, 4],
|
||||
"searchable": false
|
||||
}
|
||||
],
|
||||
"scrollX": false,
|
||||
"scrollY": false,
|
||||
"responsive": true,
|
||||
"language": {
|
||||
"loadingRecords": "",
|
||||
"emptyTable": "No files or folders"
|
||||
},
|
||||
"initComplete": function (settings, json) {
|
||||
table.button().add(0, 'refresh');
|
||||
//table.button().add(0, 'pageLength');
|
||||
table.button().add(0, 'download');
|
||||
{{if gt .Scope 1}}
|
||||
table.button().add(0, 'addFiles');
|
||||
{{end}}
|
||||
table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
|
||||
},
|
||||
"orderFixed": [1, 'asc'],
|
||||
"order": [2, 'asc']
|
||||
});
|
||||
|
||||
new $.fn.dataTable.FixedHeader(table);
|
||||
$.fn.dataTable.ext.errMode = 'none';
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -1,40 +1,48 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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 "baselogin" .}}
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{define "title"}}Share login{{end}}
|
||||
{{- define "title"}}Share login{{- end}}
|
||||
|
||||
{{define "content"}}
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control form-control-user-custom"
|
||||
id="inputSharePassword" name="share_password" placeholder="Password" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h1 class="text-dark mb-3 ms-3">
|
||||
{{.Branding.ShortName}}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="password" name="share_password" placeholder="Password" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span class="indicator-label">Sign in</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
||||
@@ -1,115 +1,87 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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}}/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 "extra_css"}}
|
||||
<link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
|
||||
{{- 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 class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">View and manage shares</h6>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title text-primary">View and manage shares</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">
|
||||
{{- template "errmsg" ""}}
|
||||
<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 class="text-gray-600">Loading...</span>
|
||||
</div>
|
||||
<div id="card_content" class="d-none">
|
||||
<div class="d-flex flex-stack 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 type="text" data-share-table-filter="search"
|
||||
class="form-control form-control-solid w-200px ps-15" placeholder="Search" />
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end" data-share-table-toolbar="base">
|
||||
<a href="{{.ShareURL}}" class="btn btn-primary">
|
||||
<i class="ki-duotone ki-plus fs-2"></i>
|
||||
Add
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<tr class="text-start text-muted fw-bold fs-6 gs-0">
|
||||
<th>Name</th>
|
||||
<th>Scope</th>
|
||||
<th>Info</th>
|
||||
<th></th>
|
||||
<th class="min-w-100px"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Shares}}
|
||||
<tr>
|
||||
<td>{{.ShareID}}</td>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{.GetScopeAsString}}</td>
|
||||
<td>{{.GetInfoString}}</td>
|
||||
<td>{{if .IsExpired}}1{{else}}0{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
<tbody class="text-gray-800 fw-semibold"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{define "dialog"}}
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel"
|
||||
aria-hidden="true">
|
||||
{{- define "modals"}}
|
||||
<div class="modal fade" id="link_modal" tabindex="-1">
|
||||
<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 share?</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>
|
||||
|
||||
<div class="modal fade" id="linkModal" tabindex="-1" role="dialog" aria-labelledby="linkModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="linkModalLabel">
|
||||
<div class="modal-header border-0">
|
||||
<h3 class="modal-title">
|
||||
Share access links
|
||||
</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</h3>
|
||||
<div class="btn btn-icon btn-sm btn-active-color-primary" data-bs-dismiss="modal" aria-label="Close">
|
||||
<i class="ki-duotone ki-cross fs-1"><span class="path1"></span><span class="path2"></span></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="modal-body fs-5">
|
||||
<div id="readShare">
|
||||
<p>You can download the shared contents, as single zip file, using this <a id="readLink" href="#" target="_blank">link</a>.</p>
|
||||
<p>If the share consists of a single directory you can browse and download files using this <a id="readBrowseLink" href="#" target="_blank">page</a>.</p>
|
||||
@@ -122,8 +94,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
This share is no longer accessible because it has expired
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" type="button" data-dismiss="modal">
|
||||
<div class="modal-footer border-0">
|
||||
<button class="btn btn-primary" type="button" data-bs-dismiss="modal">
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
@@ -133,161 +105,177 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
{{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/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}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
function deleteAction() {
|
||||
let table = $('#dataTable').DataTable();
|
||||
table.button('delete:name').enable(false);
|
||||
let shareID = table.row({ selected: true }).data()[0];
|
||||
let path = '{{.ShareURL}}' + "/" + fixedEncodeURIComponent(shareID);
|
||||
$('#deleteModal').modal('hide');
|
||||
$('#errorMsg').hide();
|
||||
function deleteAction(shareID) {
|
||||
let errDivEl = $('#errorMsg');
|
||||
let errTxtEl = $('#errorTxt');
|
||||
errDivEl.addClass("d-none");
|
||||
|
||||
$.ajax({
|
||||
url: path,
|
||||
type: 'DELETE',
|
||||
dataType: 'json',
|
||||
headers: {'X-CSRF-TOKEN' : '{{.CSRFToken}}'},
|
||||
timeout: 15000,
|
||||
success: function (result) {
|
||||
window.location.href = '{{.SharesURL}}';
|
||||
},
|
||||
error: function ($xhr, textStatus, errorThrown) {
|
||||
let txt = "Unable to delete the selected share";
|
||||
if ($xhr) {
|
||||
let json = $xhr.responseJSON;
|
||||
if (json) {
|
||||
if (json.message){
|
||||
txt += ": " + json.message;
|
||||
} else {
|
||||
txt += ": " + json.error;
|
||||
ModalAlert.fire({
|
||||
text: `Do you want to delete the selected share? This action is irreversible`,
|
||||
icon: "warning",
|
||||
confirmButtonText: "Delete",
|
||||
cancelButtonText: 'Cancel',
|
||||
customClass: {
|
||||
confirmButton: "btn btn-danger",
|
||||
cancelButton: 'btn btn-secondary'
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed){
|
||||
$('#loading_message').text("");
|
||||
KTApp.showPageLoading();
|
||||
let path = '{{.ShareURL}}' + "/" + fixedEncodeURIComponent(shareID);
|
||||
|
||||
axios.delete(path, {
|
||||
timeout: 15000,
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||
},
|
||||
validateStatus: function (status) {
|
||||
return status == 200;
|
||||
}
|
||||
}).then(function(response){
|
||||
location.reload();
|
||||
}).catch(function(error){
|
||||
KTApp.hidePageLoading();
|
||||
let errorMessage = `Unable to delete the selected share`;
|
||||
if (error && error.response) {
|
||||
if (error.response.data.message) {
|
||||
errorMessage = error.response.data.message;
|
||||
}
|
||||
if (error.response.data.error) {
|
||||
errorMessage += ": " + error.response.data.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#errorTxt').text(txt);
|
||||
$('#errorMsg').show();
|
||||
errTxtEl.text(errorMessage);
|
||||
errDivEl.removeClass("d-none");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(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 = '{{.ShareURL}}';
|
||||
}
|
||||
};
|
||||
function editAction(shareID) {
|
||||
window.location.replace('{{.ShareURL}}' + "/" + fixedEncodeURIComponent(shareID));
|
||||
}
|
||||
|
||||
$.fn.dataTable.ext.buttons.edit = {
|
||||
text: '<i class="fas fa-pen"></i>',
|
||||
name: 'edit',
|
||||
titleAttr: "Edit",
|
||||
action: function (e, dt, node, config) {
|
||||
var shareID = dt.row({ selected: true }).data()[0];
|
||||
var path = '{{.ShareURL}}' + "/" + fixedEncodeURIComponent(shareID);
|
||||
window.location.href = path;
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.delete = {
|
||||
text: '<i class="fas fa-trash"></i>',
|
||||
name: 'delete',
|
||||
titleAttr: "Delete",
|
||||
action: function (e, dt, node, config) {
|
||||
$('#deleteModal').modal('show');
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
$.fn.dataTable.ext.buttons.link = {
|
||||
text: '<i class="fas fa-link"></i>',
|
||||
name: 'link',
|
||||
titleAttr: "Link",
|
||||
action: function (e, dt, node, config) {
|
||||
var shareData = dt.row({ selected: true }).data();
|
||||
var shareID = shareData[0];
|
||||
var shareScope = shareData[2];
|
||||
var isExpired = shareData[4];
|
||||
if (isExpired == "1"){
|
||||
$('#expiredShare').show();
|
||||
function showShareLink(shareID, shareScope, isExpired) {
|
||||
if (isExpired == "1") {
|
||||
$('#expiredShare').show();
|
||||
$('#writeShare').hide();
|
||||
$('#readShare').hide();
|
||||
} else {
|
||||
let shareURL = '{{.BasePublicSharesURL}}' + "/" + fixedEncodeURIComponent(shareID);
|
||||
if (shareScope == 'Read') {
|
||||
$('#expiredShare').hide();
|
||||
$('#writeShare').hide();
|
||||
$('#readShare').hide();
|
||||
$('#readShare').show();
|
||||
$('#readLink').attr("href", shareURL);
|
||||
$('#readLink').attr("title", shareURL);
|
||||
$('#readUncompressedLink').attr("href", shareURL + "?compress=false");
|
||||
$('#readUncompressedLink').attr("title", shareURL + "?compress=false");
|
||||
$('#readBrowseLink').attr("href", shareURL + "/browse");
|
||||
$('#readBrowseLink').attr("title", shareURL + "/browse");
|
||||
} else {
|
||||
var shareURL = '{{.BasePublicSharesURL}}' + "/" + fixedEncodeURIComponent(shareID);
|
||||
if (shareScope == 'Read'){
|
||||
$('#expiredShare').hide();
|
||||
$('#writeShare').hide();
|
||||
$('#readShare').show();
|
||||
$('#readLink').attr("href", shareURL);
|
||||
$('#readLink').attr("title", shareURL);
|
||||
$('#readUncompressedLink').attr("href", shareURL+"?compress=false");
|
||||
$('#readUncompressedLink').attr("title", shareURL+"?compress=false");
|
||||
$('#readBrowseLink').attr("href", shareURL+"/browse");
|
||||
$('#readBrowseLink').attr("title", shareURL+"/browse");
|
||||
} else {
|
||||
$('#expiredShare').hide();
|
||||
$('#writeShare').show();
|
||||
$('#readShare').hide();
|
||||
$('#writePageLink').attr("href", shareURL+"/upload");
|
||||
$('#writePageLink').attr("title", shareURL+"/upload");
|
||||
$('#expiredShare').hide();
|
||||
$('#writeShare').show();
|
||||
$('#readShare').hide();
|
||||
$('#writePageLink').attr("href", shareURL + "/upload");
|
||||
$('#writePageLink').attr("title", shareURL + "/upload");
|
||||
}
|
||||
}
|
||||
$('#link_modal').modal('show');
|
||||
}
|
||||
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
const tableData = [];
|
||||
{{- range .Shares}}
|
||||
tableData.push(['{{.Name}}','{{.GetScopeAsString}}','{{.GetInfoString}}','{{.ShareID}}','{{- if .IsExpired}}1{{- else}}0{{- end}}']);
|
||||
{{- end}}
|
||||
|
||||
let dt = $('#dataTable').DataTable({
|
||||
data: tableData,
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 3,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: 'text-end',
|
||||
render: function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
return `<div class="d-flex justify-content-end">
|
||||
<div class="ms-2">
|
||||
<a href="#" onclick="showShareLink('${row[3]}','${row[1]}','${row[4]}')" class="btn btn-sm btn-icon btn-light btn-active-light-primary">
|
||||
<i class="ki-duotone ki-fasten fs-5 m-0">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="ms-2">
|
||||
<button type="button" class="btn btn-sm btn-icon btn-light btn-active-light-primary"
|
||||
data-kt-menu-trigger="click" data-kt-menu-placement="bottom-end">
|
||||
<i class="ki-duotone ki-dots-square fs-5 m-0">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
</button>
|
||||
<div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-semibold fs-7 w-150px py-4" data-kt-menu="true">
|
||||
<div class="menu-item px-3">
|
||||
<a href="#" onclick="editAction('${row[3]}');" class="menu-link px-3">Edit</a>
|
||||
</div>
|
||||
<div class="menu-item px-3">
|
||||
<a href="#" onclick="deleteAction('${row[3]}');" class="menu-link text-danger px-3">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
},
|
||||
{
|
||||
targets: [0, 2],
|
||||
render: function (data, type, row) {
|
||||
if (type === 'display') {
|
||||
return escapeHTML(data);
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
||||
$('#linkModal').modal('show');
|
||||
},
|
||||
enabled: false
|
||||
};
|
||||
|
||||
var table = $('#dataTable').DataTable({
|
||||
"select": {
|
||||
"style": "single",
|
||||
"blurable": true
|
||||
},
|
||||
"stateSave": true,
|
||||
"stateDuration": 0,
|
||||
"buttons": [],
|
||||
"columnDefs": [
|
||||
{
|
||||
"targets": [0, 4],
|
||||
"visible": false,
|
||||
"searchable": false
|
||||
}
|
||||
],
|
||||
"scrollX": false,
|
||||
"scrollY": false,
|
||||
"responsive": true,
|
||||
"language": {
|
||||
"emptyTable": "No share defined"
|
||||
deferRender: true,
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
stateLoadParams: function (settings, data) {
|
||||
if (data.search.search){
|
||||
const filterSearch = document.querySelector('[data-share-table-filter="search"]');
|
||||
filterSearch.value = data.search.search;
|
||||
}
|
||||
},
|
||||
language: {
|
||||
emptyTable: "No share defined"
|
||||
},
|
||||
"order": [[1, 'asc']]
|
||||
order: [[1, 'asc']],
|
||||
initComplete: function(settings, json) {
|
||||
$('#loader').addClass("d-none");
|
||||
$('#card_content').removeClass("d-none");
|
||||
$.fn.dataTable.Api(settings).columns.adjust().draw();
|
||||
KTMenu.createInstances();
|
||||
}
|
||||
});
|
||||
|
||||
new $.fn.dataTable.FixedHeader( table );
|
||||
|
||||
table.button().add(0,'link');
|
||||
table.button().add(0,'delete');
|
||||
table.button().add(0,'edit');
|
||||
table.button().add(0,'add');
|
||||
|
||||
table.buttons().container().appendTo('.col-md-6:eq(0)', table.table().container());
|
||||
|
||||
table.on('select deselect', function () {
|
||||
var selectedRows = table.rows({ selected: true }).count();
|
||||
table.button('edit:name').enable(selectedRows == 1);
|
||||
table.button('clone:name').enable(selectedRows == 1);
|
||||
table.button('delete:name').enable(selectedRows == 1);
|
||||
table.button('link:name').enable(selectedRows == 1);
|
||||
document.querySelector('[data-share-table-filter="search"]').addEventListener('keyup', function(e){
|
||||
dt.search(e.target.value, true, false).draw();
|
||||
});
|
||||
|
||||
dt.on('draw', function () {
|
||||
KTMenu.createInstances();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -1,160 +1,170 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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" .}}
|
||||
{{- template "base" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
{{- define "title"}}{{.Title}}{{- end}}
|
||||
|
||||
{{define "page_body"}}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-xl-5 col-lg-6 col-md-8">
|
||||
<div class="card shadow-lg my-5">
|
||||
<div class="card-header py-3">
|
||||
<h6 id="default_title" class="m-0 font-weight-bold text-primary">Upload one or more files to share "{{.Share.Name}}"</h6>
|
||||
<h6 id="success_title" class="m-0 font-weight-bold text-primary" style="display: none;">Upload completed to share "{{.Share.Name}}"</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="errorMsg" class="alert alert-warning alert-dismissible fade show" style="display: none;" role="alert">
|
||||
<span id="errorTxt"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="successMsg" class="card mb-4 border-left-success" style="display: none;">
|
||||
<div id="successTxt" class="card-body">
|
||||
<p>File/s uploaded successfully</p>
|
||||
<p>If you want to upload other files click <a href="javascript:refreshPage();">here</a></p>
|
||||
{{- define "page_body"}}
|
||||
<div class="d-flex flex-center flex-column flex-column-fluid p-10 pb-lg-20">
|
||||
<div class="mb-12">
|
||||
<span>
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px" />
|
||||
</span>
|
||||
<span class="text-gray-800 fs-2 fw-semibold ps-5">
|
||||
{{.Branding.ShortName}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card shadow-sm w-lg-600px">
|
||||
<div class="card-header bg-light">
|
||||
<h3 class="card-title text-primary">Upload one or more files to share "{{.Share.Name}}"</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{- template "errmsg" ""}}
|
||||
<form id="upload_files_form" action="{{.UploadBasePath}}" method="POST" enctype="multipart/form-data">
|
||||
<div class="fv-row">
|
||||
<div class="dropzone" id="upload_files">
|
||||
<div class="dz-message needsclick align-items-center">
|
||||
<i class="ki-duotone ki-file-up fs-3x text-primary"><span class="path1"></span><span class="path2"></span></i>
|
||||
<div class="ms-4">
|
||||
<h3 class="fs-5 fw-bold text-gray-900 mb-1">Drop files here or click to upload.</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form id="upload_files_form" action="#" method="POST" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<input type="file" class="form-control-file" id="files_name" name="filenames" required multiple>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary float-right mt-3 px-5">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-10">
|
||||
<button type="button" id="upload_files_button" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
|
||||
{{define "dialog"}}
|
||||
<div class="modal fade" id="spinnerModal" tabindex="-1" role="dialog" data-keyboard="false" data-backdrop="static">
|
||||
<div class="modal-dialog modal-dialog-centered justify-content-center" role="document">
|
||||
<span style="color: #333333;" class="fa fa-spinner fa-spin fa-3x"></span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
{{- define "extra_js"}}
|
||||
<script type="text/javascript">
|
||||
var spinnerDone = false;
|
||||
|
||||
function refreshPage() {
|
||||
location.reload();
|
||||
function uploadFiles(files) {
|
||||
let has_errors = false;
|
||||
let index = 0;
|
||||
let success = 0;
|
||||
$('#errorMsg').addClass("d-none");
|
||||
$('#loading_message').text("");
|
||||
KTApp.showPageLoading();
|
||||
|
||||
function uploadFile() {
|
||||
if (index >= files.length || has_errors) {
|
||||
KTApp.hidePageLoading();
|
||||
if (!has_errors) {
|
||||
ModalAlert.fire({
|
||||
text: `File/s uploaded successfully`,
|
||||
icon: "success",
|
||||
confirmButtonText: "OK",
|
||||
customClass: {
|
||||
confirmButton: 'btn btn-primary'
|
||||
}
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed){
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let f = files[index];
|
||||
let uploadPath = '{{.UploadBasePath}}/'+fixedEncodeURIComponent(escapeHTML(f.name));
|
||||
let lastModified;
|
||||
try {
|
||||
lastModified = f.lastModified;
|
||||
} catch (e) {
|
||||
console.log("unable to get last modified time from file: " + e.message);
|
||||
lastModified = "";
|
||||
}
|
||||
|
||||
let uploadTxt = f.name;
|
||||
if (files.length > 1){
|
||||
uploadTxt = `Upload ${index+1}/${files.length}: ${uploadTxt}`;
|
||||
}
|
||||
|
||||
$('#loading_message').text(uploadTxt);
|
||||
|
||||
axios.post(uploadPath, f, {
|
||||
headers: {
|
||||
'X-SFTPGO-MTIME': lastModified,
|
||||
'X-CSRF-TOKEN': '{{.CSRFToken}}'
|
||||
},
|
||||
onUploadProgress: function (progressEvent) {
|
||||
if (!progressEvent.total){
|
||||
return;
|
||||
}
|
||||
const percentage = Math.round((100 * progressEvent.loaded) / progressEvent.total);
|
||||
if (percentage > 0 && percentage < 100){
|
||||
$('#loading_message').text(`${uploadTxt} ${percentage}%`);
|
||||
}
|
||||
},
|
||||
validateStatus: function (status) {
|
||||
return status == 201;
|
||||
}
|
||||
}).then(function (response) {
|
||||
index++;
|
||||
success++;
|
||||
uploadFile();
|
||||
}).catch(function (error) {
|
||||
let errorMessage = "Error uploading files";
|
||||
if (error && error.response) {
|
||||
if (error.response.data.message) {
|
||||
errorMessage = error.response.data.message;
|
||||
}
|
||||
if (error.response.data.error) {
|
||||
errorMessage += ": " + error.response.data.error;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
has_errors = true;
|
||||
$('#errorTxt').text(errorMessage);
|
||||
$('#errorMsg').removeClass("d-none");
|
||||
uploadFile();
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile();
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#spinnerModal').on('shown.bs.modal', function () {
|
||||
if (spinnerDone){
|
||||
$('#spinnerModal').modal('hide');
|
||||
}
|
||||
});
|
||||
|
||||
$("#upload_files_form").submit(function (event){
|
||||
event.preventDefault();
|
||||
let files = $("#files_name")[0].files;
|
||||
let has_errors = false;
|
||||
let index = 0;
|
||||
let success = 0;
|
||||
spinnerDone = false;
|
||||
|
||||
$('#spinnerModal').modal('show');
|
||||
$('#errorMsg').hide();
|
||||
|
||||
function uploadFile() {
|
||||
if (index >= files.length || has_errors){
|
||||
$('#spinnerModal').modal('hide');
|
||||
spinnerDone = true;
|
||||
if (!has_errors){
|
||||
$('#errorMsg').hide();
|
||||
$('#upload_files_form').hide();
|
||||
$('#default_title').hide();
|
||||
$('#success_title').show();
|
||||
$('#successMsg').show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
let errorMessage = "Error uploading files";
|
||||
let response;
|
||||
try {
|
||||
let f = files[index];
|
||||
let uploadPath = '{{.UploadBasePath}}/'+fixedEncodeURIComponent(escapeHTML(f.name));
|
||||
let lastModified;
|
||||
try {
|
||||
lastModified = f.lastModified;
|
||||
} catch (e) {
|
||||
console.log("unable to get last modified time from file: "+e.message);
|
||||
lastModified = "";
|
||||
}
|
||||
response = await fetch(uploadPath, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-SFTPGO-MTIME': lastModified
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
redirect: 'error',
|
||||
body: f
|
||||
});
|
||||
} catch (e){
|
||||
throw Error(errorMessage+": " +e.message);
|
||||
}
|
||||
if (response.status == 201){
|
||||
index++;
|
||||
success++;
|
||||
uploadFile();
|
||||
} else {
|
||||
let jsonResponse;
|
||||
try {
|
||||
jsonResponse = await response.json();
|
||||
} catch(e){
|
||||
throw Error(errorMessage);
|
||||
}
|
||||
if (jsonResponse.message) {
|
||||
errorMessage = jsonResponse.message;
|
||||
}
|
||||
if (jsonResponse.error) {
|
||||
errorMessage += ": " + jsonResponse.error;
|
||||
}
|
||||
throw Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
saveFile().catch(function(error){
|
||||
index++;
|
||||
has_errors = true;
|
||||
$('#errorTxt').text(error.message);
|
||||
$('#errorMsg').show();
|
||||
uploadFile();
|
||||
KTUtil.onDOMContentLoaded(function () {
|
||||
var dropzone = new Dropzone("#upload_files", {
|
||||
url: "{{.UploadBasePath}}",
|
||||
paramName: "filenames",
|
||||
maxFiles: 200,
|
||||
maxFilesize: null,
|
||||
autoQueue: false,
|
||||
addRemoveLinks: true,
|
||||
autoProcessQueue: false,
|
||||
filesizeBase: 1000,
|
||||
init: function() {
|
||||
var dropzone = this;
|
||||
$("#upload_files_button").click(function(){
|
||||
uploadFiles(dropzone.getAcceptedFiles());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
uploadFile();
|
||||
dropzone.on("addedfile", file => {
|
||||
file.previewElement.querySelector(".dz-progress").style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
{{- end}}
|
||||
@@ -1,44 +1,61 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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 "baselogin" .}}
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{define "title"}}Two-Factor recovery{{end}}
|
||||
{{- define "title"}}Two-Factor recovery{{- end}}
|
||||
|
||||
{{define "content"}}
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputRecoveryCode" name="recovery_code" placeholder="Recovery code" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Verify
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<div>
|
||||
<p>You can enter one of your recovery codes in case you lost access to your mobile device.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h1 class="text-dark mb-3 ms-3">
|
||||
{{.Branding.ShortName}}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="text" name="recovery_code" placeholder="Recovery code" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span class="indicator-label">Verify</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="notice d-flex bg-light-primary rounded border-primary border border-dashed p-6 mb-5">
|
||||
<i class="ki-duotone ki-shield-tick fs-2tx text-primary me-4">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
<div class="d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap">
|
||||
<div class="mb-3 mb-md-0 fw-semibold">
|
||||
<div class="fs-6 text-gray-800">
|
||||
You can enter one of your recovery codes in case you lost access to your mobile device.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
@@ -1,49 +1,67 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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 "baselogin" .}}
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{define "title"}}Two-Factor authentication{{end}}
|
||||
{{- define "title"}}Two-Factor authentication{{- end}}
|
||||
|
||||
{{define "content"}}
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputPasscode" name="passcode" placeholder="Authentication code" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Verify
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<div>
|
||||
<p>Open the two-factor authentication app on your device to view your authentication code and verify your identity.</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<p><strong>Having problems?</strong></p>
|
||||
<p><a href="{{.RecoveryURL}}">Enter a two-factor recovery code</a></p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h1 class="text-dark mb-3 ms-3">
|
||||
{{.Branding.ShortName}}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="text" placeholder="Authentication code" name="passcode" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span class="indicator-label">Verify</span>
|
||||
<span class="indicator-progress">Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="notice d-flex bg-light-primary rounded border-primary border border-dashed p-6 mb-5">
|
||||
<i class="ki-duotone ki-shield-tick fs-2tx text-primary me-4">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
<div class="d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap">
|
||||
<div class="mb-3 mb-md-0 fw-semibold">
|
||||
<div class="fs-6 text-gray-700">
|
||||
Open the two-factor authentication app on your device to view your authentication code and verify your identity.
|
||||
</div>
|
||||
<div class="fs-6 text-gray-800 mt-5">
|
||||
<p class="fw-bold">Having problems?</p>
|
||||
<p><a href="{{.RecoveryURL}}">Enter a two-factor recovery code</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{- end}}
|
||||
@@ -1,17 +1,17 @@
|
||||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 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).
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
Reference in New Issue
Block a user