| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- <!--
- 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 ([email protected]).
- -->
- {{- template "base" .}}
- {{- define "extra_css"}}
- <link href="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.css" rel="stylesheet" type="text/css"/>
- {{- end}}
- {{- define "page_body"}}
- {{- template "errmsg" ""}}
- <div class="card shadow-sm">
- <div class="card-header bg-light">
- <h3 data-i18n="share.view_manage" class="card-title section-title">View and manage shares</h3>
- </div>
- <div id="card_body" class="card-body">
- <div id="loader" class="align-items-center text-center my-10">
- <span class="spinner-border w-15px h-15px text-muted align-middle me-2"></span>
- <span data-i18n="general.loading" class="text-gray-700">Loading...</span>
- </div>
- <div id="card_content" class="d-none">
- <div class="d-flex flex-stack flex-wrap mb-5">
- <div class="d-flex align-items-center position-relative my-2">
- <i class="ki-solid ki-magnifier fs-1 position-absolute ms-6"></i>
- <input name="search" data-i18n="[placeholder]general.search" type="text" data-table-filter="search"
- class="form-control rounded-1 w-250px ps-15 me-5" placeholder="Search" />
- </div>
- <div class="d-flex justify-content-end my-2" data-table-toolbar="base">
- <a href="{{.ShareURL}}" class="btn btn-primary">
- <i class="ki-duotone ki-plus fs-2"></i>
- <span data-i18n="general.add">Add</span>
- </a>
- </div>
- </div>
- <table id="dataTable" class="table align-middle table-row-dashed fs-6 gy-5">
- <thead>
- <tr class="text-start text-muted fw-bold fs-6 gs-0">
- <th data-i18n="general.name">Name</th>
- <th data-i18n="share.scope">Scope</th>
- <th data-i18n="general.info">Info</th>
- <th class="min-w-100px"></th>
- </tr>
- </thead>
- <tbody id="table_body" class="text-gray-800 fw-semibold"></tbody>
- </table>
- </div>
- </div>
- </div>
- {{- end}}
- {{- 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 border-0">
- <h3 data-i18n="share.access_links_title" class="modal-title">
- Share access links
- </h3>
- <div data-i18n="[aria-label]general.close" class="btn btn-icon btn-sm btn-active-light-primary" data-bs-dismiss="modal" aria-label="Close">
- <i class="ki-solid ki-cross fs-2x text-gray-700"></i>
- </div>
- </div>
- <div class="modal-body fs-5">
- <div id="readShare" class="mb-5">
- <div class="mb-3">
- <h4 data-i18n="share.link_single_title">Single zip file</h4>
- <p data-i18n="share.link_single_desc">You can download shared content as a single zip file</p>
- <div class="d-flex">
- <button id="readLinkCopy" type="button" class="btn btn-flex btn-light-primary btn-clipboard-copy me-3">
- <i class="ki-duotone ki-fasten fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="general.copy_link">Copy link</span>
- </button>
- <a id="readLink" href="#" target="_blank" type="button" class="btn btn-flex btn-primary">
- <i class="ki-duotone ki-folder-down fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="fs.download">Download</span>
- </a>
- </div>
- </div>
- <hr>
- <div class="mb-3 mt-10">
- <h4 data-i18n="share.link_dir_title">Single directory</h4>
- <p data-i18n="share.link_dir_desc">If the share consists of a single directory you can browse and download files</p>
- <button id="readBrowseLinkCopy" data-clipboard-target="#readBrowseLink" type="button" class="btn btn-flex btn-light-primary btn-clipboard-copy me-3">
- <i class="ki-duotone ki-fasten fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="general.copy_link">Copy link</span>
- </button>
- <a id="readBrowseLink" href="#" target="_blank" type="button" class="btn btn-flex btn-primary">
- <i class="ki-duotone ki-arrow-up-right fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="share.go">Go to share</span>
- </a>
- </div>
- <hr>
- <div class="mt-10">
- <h4 data-i18n="share.link_uncompressed_title">Uncompressed file</h4>
- <p data-i18n="share.link_uncompressed_desc">If the share consists of a single file you can download it uncompressed</p>
- <button id="readUncompressedLinkCopy" data-clipboard-target="#readUncompressedLink" type="button" class="btn btn-flex btn-light-primary btn-clipboard-copy me-3">
- <i class="ki-duotone ki-fasten fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="general.copy_link">Copy link</span>
- </button>
- <a id="readUncompressedLink" href="#" target="_blank" type="button" class="btn btn-flex btn-primary">
- <i class="ki-duotone ki-folder-down fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="fs.download">Download</span>
- </a>
- </div>
- </div>
- <div id="writeShare" class="mb-5">
- <p data-i18n="share.upload_desc">You can upload one or more files to the shared directory</p>
- <button id="writePageLinkCopy" data-clipboard-target="#writePageLink" type="button" class="btn btn-flex btn-light-primary btn-clipboard-copy me-3">
- <i class="ki-duotone ki-fasten fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="general.copy_link">Copy link</span>
- </button>
- <a id="writePageLink" href="#" target="_blank" type="button" class="btn btn-flex btn-primary">
- <i class="ki-duotone ki-folder-up fs-2">
- <span class="path1"></span>
- <span class="path2"></span>
- </i>
- <span data-i18n="fs.upload.text">Upload</span>
- </a>
- </div>
- <div data-i18n="share.expired_desc" id="expiredShare" class="fw-semibold fs-4 mb-5">
- This share is no longer accessible because it has expired
- </div>
- </div>
- </div>
- </div>
- </div>
- {{end}}
- {{- define "extra_js"}}
- <script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/datatables/datatables.bundle.js"></script>
- <script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
- function deleteAction(shareID) {
- ModalAlert.fire({
- text: $.t('general.delete_confirm_generic'),
- icon: "warning",
- confirmButtonText: $.t('general.delete_confirm_btn'),
- cancelButtonText: $.t('general.cancel'),
- customClass: {
- confirmButton: "btn btn-danger",
- cancelButton: 'btn btn-secondary'
- }
- }).then((result) => {
- if (result.isConfirmed){
- $('#loading_message').text("");
- KTApp.showPageLoading();
- let path = '{{.ShareURL}}' + "/" + encodeURIComponent(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;
- if (error && error.response) {
- switch (error.response.status) {
- case 403:
- errorMessage = "general.delete_error_403";
- break;
- case 404:
- errorMessage = "general.delete_error_404";
- break;
- }
- }
- if (!errorMessage){
- errorMessage = "general.delete_error_generic";
- }
- ModalAlert.fire({
- text: $.t(errorMessage),
- icon: "warning",
- confirmButtonText: $.t('general.ok'),
- customClass: {
- confirmButton: "btn btn-primary"
- }
- });
- });
- }
- });
- }
- function editAction(shareID) {
- window.location.replace('{{.ShareURL}}' + "/" + encodeURIComponent(shareID));
- }
- function showShareLink(shareID, shareScope, expiresAt) {
- if (expiresAt < Date.now()) {
- $('#expiredShare').show();
- $('#writeShare').hide();
- $('#readShare').hide();
- } else {
- let shareURL = '{{.BasePublicSharesURL}}' + "/" + encodeURIComponent(shareID);
- if (shareScope == '1') {
- $('#expiredShare').hide();
- $('#writeShare').hide();
- $('#readShare').show();
- $('#readLink').attr("href", shareURL + "/download");
- $('#readLink').attr("title", shareURL + "/download");
- $('#readLinkCopy').attr("data-clipboard-text",getCurrentURI()+shareURL + "/download");
- $('#readUncompressedLink').attr("href", shareURL + "/download?compress=false");
- $('#readUncompressedLink').attr("title", shareURL + "/download?compress=false");
- $('#readUncompressedLinkCopy').attr("data-clipboard-text",getCurrentURI()+shareURL + "/download?compress=false");
- $('#readBrowseLink').attr("href", shareURL + "/browse");
- $('#readBrowseLink').attr("title", shareURL + "/browse");
- $('#readBrowseLinkCopy').attr("data-clipboard-text",getCurrentURI()+shareURL + "/browse");
- } else {
- $('#expiredShare').hide();
- $('#writeShare').show();
- $('#readShare').hide();
- $('#writePageLink').attr("href", shareURL + "/upload");
- $('#writePageLink').attr("title", shareURL + "/upload");
- $('#writePageLinkCopy').attr("data-clipboard-text",getCurrentURI()+shareURL + "/upload");
- }
- }
- $('#link_modal').modal('show');
- }
- var sharesDatatable = function(){
- var dt;
- var initDatatable = function () {
- dt = $('#dataTable').DataTable({
- ajax: {
- url: "{{.SharesURL}}/json",
- dataSrc: "",
- error: function ($xhr, textStatus, errorThrown) {
- $(".dataTables_processing").hide();
- $('#loader').addClass("d-none");
- let txt = "";
- if ($xhr) {
- let json = $xhr.responseJSON;
- if (json) {
- if (json.message){
- txt = json.message;
- }
- }
- }
- if (!txt){
- txt = "general.error500";
- }
- setI18NData($('#errorTxt'), txt);
- $('#errorMsg').removeClass("d-none");
- }
- },
- columns: [
- {
- data: "name",
- render: function(data, type, row) {
- if (type === 'display') {
- return escapeHTML(data);
- }
- return data;
- }
- },
- {
- data: "scope",
- render: function (data, type, row) {
- if (type === 'display') {
- switch (data){
- case 2:
- return $.t('share.scope_write');
- case 3:
- return $.t('share.scope_read_write');
- default:
- return $.t('share.scope_read');
- }
- }
- return data;
- }
- },
- {
- data: "expires_at",
- defaultContent: 0,
- searchable: false,
- orderable: false,
- render: function (data, type, row) {
- if (type === 'display') {
- let info = "";
- if (row.expires_at && row.expires_at > 0){
- info+= $.t('share.expiration_date', {
- val: row.expires_at,
- formatParams: {
- val: { year: 'numeric', month: 'numeric', day: 'numeric' },
- }
- });
- }
- if (row.last_use_at && row.last_use_at > 0){
- info+= $.t('share.last_use', {
- val: row.last_use_at,
- formatParams: {
- val: { year: 'numeric', month: 'numeric', day: 'numeric' },
- }
- });
- }
- let used_tokens = 0;
- if (row.used_tokens && row.used_tokens > 0){
- used_tokens = row.used_tokens;
- }
- if (row.max_tokens && row.max_tokens > 0){
- info+= $.t('share.usage', {used: used_tokens, total: row.max_tokens});
- } else {
- info+= $.t('share.used_tokens', {used: used_tokens});
- }
- if (row.password){
- info+= $.t('share.password_protected')
- }
- return info;
- }
- return data;
- }
- },
- {
- data: "id",
- 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="#" class="btn btn-sm btn-icon btn-light btn-active-light-primary" data-table-action="show_link">
- <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-700 menu-state-bg-light-primary fw-semibold fs-6 w-150px py-4" data-kt-menu="true">
- <div class="menu-item px-3">
- <a data-i18n="general.edit" href="#" class="menu-link px-3" data-table-action="edit_row">Edit</a>
- </div>
- <div class="menu-item px-3">
- <a data-i18n="general.delete" href="#" class="menu-link text-danger px-3" data-table-action="delete_row">Delete</a>
- </div>
- </div>
- </div>
- </div>`;
- }
- return "";
- }
- }
- ],
- deferRender: true,
- stateSave: true,
- stateDuration: 0,
- stateLoadParams: function (settings, data) {
- if (data.search.search){
- const filterSearch = document.querySelector('[data-table-filter="search"]');
- filterSearch.value = data.search.search;
- }
- },
- language: {
- info: $.t('datatable.info'),
- infoEmpty: $.t('datatable.info_empty'),
- infoFiltered: $.t('datatable.info_filtered'),
- loadingRecords: "",
- processing: $.t('datatable.processing'),
- zeroRecords: "",
- emptyTable: $.t('share.no_share')
- },
- order: [[1, 'asc']],
- initComplete: function(settings, json) {
- $('#loader').addClass("d-none");
- $('#card_content').removeClass("d-none");
- let api = $.fn.dataTable.Api(settings);
- api.columns.adjust().draw("page");
- drawAction();
- }
- });
- dt.on('draw', drawAction);
- }
- function drawAction() {
- KTMenu.createInstances();
- handleRowActions();
- $('#table_body').localize();
- }
- var handleSearchDatatable = function () {
- const filterSearch = $(document.querySelector('[data-table-filter="search"]'));
- filterSearch.off("keyup");
- filterSearch.on('keyup', function (e) {
- dt.rows().deselect();
- dt.search(e.target.value, true, false).draw();
- });
- }
- function handleRowActions() {
- const editButtons = document.querySelectorAll('[data-table-action="edit_row"]');
- editButtons.forEach(d => {
- let el = $(d);
- el.off("click");
- el.on("click", function(e){
- e.preventDefault();
- const parent = e.target.closest('tr');
- editAction(dt.row(parent).data()["id"]);
- });
- });
- const deleteButtons = document.querySelectorAll('[data-table-action="delete_row"]');
- deleteButtons.forEach(d => {
- let el = $(d);
- el.off("click");
- el.on("click", function(e){
- e.preventDefault();
- const parent = e.target.closest('tr');
- deleteAction(dt.row(parent).data()["id"]);
- });
- });
- const showLinkButtons = document.querySelectorAll('[data-table-action="show_link"]');
- showLinkButtons.forEach(d => {
- let el = $(d);
- el.off("click");
- el.on("click", function(e){
- e.preventDefault();
- let rowData = dt.row(e.target.closest('tr')).data();
- showShareLink(rowData["id"], rowData["scope"], rowData["expires_at"]);
- });
- });
- }
- return {
- init: function () {
- initDatatable();
- handleSearchDatatable();
- }
- }
- }();
- $(document).on("i18nshow", function(){
- sharesDatatable.init();
- var clipboard = new ClipboardJS('.btn-clipboard-copy',{
- container: document.getElementById('link_modal')
- });
- clipboard.on('success', function (e) {
- e.trigger.querySelectorAll('span').forEach(spanEl => {
- if (spanEl.getAttribute('data-i18n')){
- e.trigger.classList.remove("btn-light-primary");
- e.trigger.classList.add("btn-success");
- setI18NData($(spanEl),"general.copied");
- setTimeout(function(){
- e.trigger.classList.remove("btn-success");
- e.trigger.classList.add("btn-light-primary");
- setI18NData($(spanEl),"general.copy_link");
- }, 3000)
- }
- });
- e.clearSelection();
- });
- });
- </script>
- {{end}}
|