diff --git a/CHANGELOG.md b/CHANGELOG.md index 64aaf16..dee1405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## Changes 5/3/2025 v1.3.0 + +**Admin Panel Refactor & Enhancements** + +### Moved from `authModals.js` to `adminPanel.js` + +- Extracted all admin-related UI and logic out of `authModals.js` +- Created a standalone `adminPanel.js` module +- Initialized `openAdminPanel()` and `closeAdminPanel()` exports + +### Responsive, Collapsible Sections + +- Injected new CSS via JS (`adminPanelStyles`) + - Default modal width: 50% + - Small-screen override (`@media (max-width: 600px)`) to 90% width +- Introduced `.section-header` / `.section-content` pattern + - Click header to expand/collapse its content + - Animated arrow via Material Icons + - Indented and padded expanded content + +### β€œManage Shared Links” Feature + +- Added new **Manage Shared Links** section to Admin Panel +- Endpoint **GET** `/api/admin/readMetadata.php?file=…` + - Reads `share_folder_links.json` & `share_links.json` under `META_DIR` +- Endpoint **POST** + - `/api/folder/deleteShareFolderLink.php` + - `/api/file/deleteShareLink.php` +- `loadShareLinksSection()` AJAX loader + - Displays folder & file shares, expiry dates, upload-allowed, and πŸ”’ if password-protected + - β€œπŸ—‘οΈβ€ delete buttons refresh the list on success + +### Dark-Mode & Theming Fixes + +- Dark-mode CSS overrides for: + - Modal border + - `.btn-primary`, `.btn-secondary` + - `.form-control` backgrounds & placeholders + - Section headers & icons +- Close button restyled to use shared **.editor-close-btn** look + +### API and Controller changes + +- Updated all endpoints to use correct controller casing +- Renamed controller files to PascalCase (e.g. `adminController.php` to `AdminController.php`, `fileController.php` to `FileController.php`, `folderController.php` to `FolderController.php`) +- Adjusted endpoint paths to match controller filenames + +--- + ## Changes 4/30/2025 v1.2.8 - **Added** PDF preview in `filePreview.js` (the `extension === "pdf"` block): replaced in-modal `` with `window.open(urlWithTs, "_blank")` and closed the modal to avoid CSP `frame-ancestors 'none'` restrictions. diff --git a/public/js/adminPanel.js b/public/js/adminPanel.js index 41e2d5e..1296f7c 100644 --- a/public/js/adminPanel.js +++ b/public/js/adminPanel.js @@ -7,11 +7,11 @@ const version = "v1.3.0"; const adminTitle = `${t("admin_panel")} ${version}`; // β€”β€”β€”β€”β€” Inject updated styles β€”β€”β€”β€”β€” -(function(){ - if (document.getElementById('adminPanelStyles')) return; - const style = document.createElement('style'); - style.id = 'adminPanelStyles'; - style.textContent = ` +(function () { + if (document.getElementById('adminPanelStyles')) return; + const style = document.createElement('style'); + style.id = 'adminPanelStyles'; + style.textContent = ` /* Modal sizing */ #adminPanelModal .modal-content { max-width: 1100px; @@ -112,24 +112,24 @@ const adminTitle = `${t("admin_panel")} { const modal = document.getElementById("customConfirmModal"); - const msg = document.getElementById("confirmMessage"); - const yes = document.getElementById("confirmYesBtn"); - const no = document.getElementById("confirmNoBtn"); + const msg = document.getElementById("confirmMessage"); + const yes = document.getElementById("confirmYesBtn"); + const no = document.getElementById("confirmNoBtn"); msg.textContent = message; modal.style.display = "block"; function clean() { @@ -163,7 +163,7 @@ function showCustomConfirmModal(message) { no.removeEventListener("click", onNo); } function onYes() { clean(); resolve(true); } - function onNo() { clean(); resolve(false); } + function onNo() { clean(); resolve(false); } yes.addEventListener("click", onYes); no.addEventListener("click", onNo); }); @@ -181,51 +181,57 @@ function toggleSection(id) { } function loadShareLinksSection() { - const container = document.getElementById("shareLinksContent"); - container.textContent = t("loading") + "..."; - - Promise.all([ - fetch("/api/admin/readMetadata.php?file=share_folder_links.json", { credentials: "include" }) - .then(r => r.ok ? r.json() : Promise.reject(`Folder fetch ${r.status}`)), - fetch("/api/admin/readMetadata.php?file=share_links.json", { credentials: "include" }) - .then(r => r.ok ? r.json() : Promise.reject(`File fetch ${r.status}`)) - ]) + const container = document.getElementById("shareLinksContent"); + container.textContent = t("loading") + "..."; + + // Helper to fetch a metadata file or return {} on any error + const fetchMeta = file => + fetch(`/api/admin/readMetadata.php?file=${file}`, { credentials: "include" }) + .then(r => r.ok ? r.json() : {}) // non-2xx β†’ treat as empty + .catch(() => ({})); + + Promise.all([ + fetchMeta("share_folder_links.json"), + fetchMeta("share_links.json") + ]) .then(([folders, files]) => { + // If nothing at all, show a friendly message + if (Object.keys(folders).length === 0 && Object.keys(files).length === 0) { + container.textContent = t("no_shared_links_available"); + return; + } + let html = `
${t("folder_shares")}
${t("file_shares")}
`; + container.innerHTML = html; - + // wire up delete buttons container.querySelectorAll(".delete-share").forEach(btn => { btn.addEventListener("click", evt => { @@ -235,29 +241,29 @@ function loadShareLinksSection() { const endpoint = isFolder ? "/api/folder/deleteShareFolderLink.php" : "/api/file/deleteShareLink.php"; - + fetch(endpoint, { method: "POST", credentials: "include", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ token }) }) - .then(res => { - if (!res.ok) throw new Error(`HTTP ${res.status}`); - return res.json(); - }) - .then(json => { - if (json.success) { - showToast(t("share_deleted_successfully")); - loadShareLinksSection(); - } else { - showToast(t("error_deleting_share") + ": " + (json.error||""), "error"); - } - }) - .catch(err => { - console.error("Delete error:", err); - showToast(t("error_deleting_share"), "error"); - }); + .then(res => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); + }) + .then(json => { + if (json.success) { + showToast(t("share_deleted_successfully")); + loadShareLinksSection(); + } else { + showToast(t("error_deleting_share") + ": " + (json.error || ""), "error"); + } + }) + .catch(err => { + console.error("Delete error:", err); + showToast(t("error_deleting_share"), "error"); + }); }); }); }) @@ -265,36 +271,36 @@ function loadShareLinksSection() { console.error("loadShareLinksSection error:", err); container.textContent = t("error_loading_share_links"); }); - } +} export function openAdminPanel() { - fetch("/api/admin/getConfig.php",{credentials:"include"}) - .then(r=>r.json()) - .then(config=>{ + fetch("/api/admin/getConfig.php", { credentials: "include" }) + .then(r => r.json()) + .then(config => { // apply header title + globals - if(config.header_title){ + if (config.header_title) { document.querySelector(".header-title h1").textContent = config.header_title; window.headerTitle = config.header_title; } - if(config.oidc) Object.assign(window.currentOIDCConfig,config.oidc); - if(config.globalOtpauthUrl) window.currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl; + if (config.oidc) Object.assign(window.currentOIDCConfig, config.oidc); + if (config.globalOtpauthUrl) window.currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl; const dark = document.body.classList.contains("dark-mode"); - const bg = dark?"rgba(0,0,0,0.7)":"rgba(0,0,0,0.3)"; - const inner= ` - background:${dark?"#2c2c2c":"#fff"}; - color:${dark?"#e0e0e0":"#000"}; + const bg = dark ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; + const inner = ` + background:${dark ? "#2c2c2c" : "#fff"}; + color:${dark ? "#e0e0e0" : "#000"}; padding:20px; max-width:1100px; width:50%; border-radius:8px; position:relative; max-height:90vh; overflow:auto; - border:1px solid ${dark?"#555":"#ccc"}; + border:1px solid ${dark ? "#555" : "#ccc"}; `; let mdl = document.getElementById("adminPanelModal"); - if(!mdl){ + if (!mdl) { mdl = document.createElement("div"); - mdl.id="adminPanelModal"; + mdl.id = "adminPanelModal"; mdl.style.cssText = ` position:fixed; top:0; left:0; width:100vw; height:100vh; @@ -310,14 +316,14 @@ export function openAdminPanel() { ${[ - { id:"userManagement", label:t("user_management") }, - { id:"headerSettings", label:t("header_settings") }, - { id:"loginOptions", label:t("login_options") }, - { id:"webdav", label:"WebDAV Access" }, - { id:"upload", label:t("shared_max_upload_size_bytes") }, - { id:"oidc", label:t("oidc_configuration") + " & TOTP" }, - { id:"shareLinks", label:t("manage_shared_links") } - ].map(sec=>` + { id: "userManagement", label: t("user_management") }, + { id: "headerSettings", label: t("header_settings") }, + { id: "loginOptions", label: t("login_options") }, + { id: "webdav", label: "WebDAV Access" }, + { id: "upload", label: t("shared_max_upload_size_bytes_title") }, + { id: "oidc", label: t("oidc_configuration") + " & TOTP" }, + { id: "shareLinks", label: t("manage_shared_links") } + ].map(sec => ` @@ -340,10 +346,10 @@ export function openAdminPanel() { .addEventListener("click", closeAdminPanel); // Section toggles - ["userManagement","headerSettings","loginOptions","webdav","upload","oidc","shareLinks"] - .forEach(id=>{ - document.getElementById(id+"Header") - .addEventListener("click", ()=>toggleSection(id)); + ["userManagement", "headerSettings", "loginOptions", "webdav", "upload", "oidc", "shareLinks"] + .forEach(id => { + document.getElementById(id + "Header") + .addEventListener("click", () => toggleSection(id)); }); // Populate each section’s CONTENT: @@ -354,14 +360,14 @@ export function openAdminPanel() { `; document.getElementById("adminOpenAddUser") - .addEventListener("click",()=>{ - toggleVisibility("addUserModal",true); + .addEventListener("click", () => { + toggleVisibility("addUserModal", true); document.getElementById("newUsername").focus(); }); document.getElementById("adminOpenRemoveUser") - .addEventListener("click",()=>{ - if(typeof window.loadUserList==="function") window.loadUserList(); - toggleVisibility("removeUserModal",true); + .addEventListener("click", () => { + if (typeof window.loadUserList === "function") window.loadUserList(); + toggleVisibility("removeUserModal", true); }); document.getElementById("adminOpenUserPermissions") .addEventListener("click", openUserPermissionsModal); @@ -401,21 +407,21 @@ export function openAdminPanel() {
-
+
`; // β€” Share Links β€” - document.getElementById("shareLinksContent").textContent = t("loading")+"…"; + document.getElementById("shareLinksContent").textContent = t("loading") + "…"; // β€” Save handler & constraints β€” document.getElementById("saveAdminSettings") .addEventListener("click", handleSave); - ["disableFormLogin","disableBasicAuth","disableOIDCLogin"].forEach(id=>{ + ["disableFormLogin", "disableBasicAuth", "disableOIDCLogin"].forEach(id => { document.getElementById(id) - .addEventListener("change", e=>{ - const chk = ["disableFormLogin","disableBasicAuth","disableOIDCLogin"] - .filter(i=>document.getElementById(i).checked).length; - if(chk===3){ + .addEventListener("change", e => { + const chk = ["disableFormLogin", "disableBasicAuth", "disableOIDCLogin"] + .filter(i => document.getElementById(i).checked).length; + if (chk === 3) { showToast(t("at_least_one_login_method")); e.target.checked = false; } @@ -423,85 +429,85 @@ export function openAdminPanel() { }); // Initialize inputs from config + capture - document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin===true; - document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth===true; - document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin===true; - document.getElementById("enableWebDAV").checked = config.enableWebDAV===true; - document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize||""; + document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; + document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; + document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; + document.getElementById("enableWebDAV").checked = config.enableWebDAV === true; + document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || ""; captureInitialAdminConfig(); } else { // modal already exists β†’ just refresh values & re-show mdl.style.display = "flex"; // update dark/light as above... - document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin===true; - document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth===true; - document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin===true; - document.getElementById("enableWebDAV").checked = config.enableWebDAV===true; - document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize||""; - document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl; - document.getElementById("oidcClientId").value = window.currentOIDCConfig.clientId; - document.getElementById("oidcClientSecret").value = window.currentOIDCConfig.clientSecret; - document.getElementById("oidcRedirectUri").value = window.currentOIDCConfig.redirectUri; - document.getElementById("globalOtpauthUrl").value = window.currentOIDCConfig.globalOtpauthUrl||''; + document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; + document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; + document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; + document.getElementById("enableWebDAV").checked = config.enableWebDAV === true; + document.getElementById("sharedMaxUploadSize").value = config.sharedMaxUploadSize || ""; + document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl; + document.getElementById("oidcClientId").value = window.currentOIDCConfig.clientId; + document.getElementById("oidcClientSecret").value = window.currentOIDCConfig.clientSecret; + document.getElementById("oidcRedirectUri").value = window.currentOIDCConfig.redirectUri; + document.getElementById("globalOtpauthUrl").value = window.currentOIDCConfig.globalOtpauthUrl || ''; captureInitialAdminConfig(); } }) - .catch(()=>{/* if even fetching fails, open empty panel */}); + .catch(() => {/* if even fetching fails, open empty panel */ }); } -function handleSave(){ +function handleSave() { const dFL = document.getElementById("disableFormLogin").checked; const dBA = document.getElementById("disableBasicAuth").checked; const dOIDC = document.getElementById("disableOIDCLogin").checked; - const eWD = document.getElementById("enableWebDAV").checked; - const sMax = parseInt(document.getElementById("sharedMaxUploadSize").value,10)||0; - const nHT = document.getElementById("headerTitle").value.trim(); + const eWD = document.getElementById("enableWebDAV").checked; + const sMax = parseInt(document.getElementById("sharedMaxUploadSize").value, 10) || 0; + const nHT = document.getElementById("headerTitle").value.trim(); const nOIDC = { - providerUrl : document.getElementById("oidcProviderUrl").value.trim(), - clientId : document.getElementById("oidcClientId").value.trim(), + providerUrl: document.getElementById("oidcProviderUrl").value.trim(), + clientId: document.getElementById("oidcClientId").value.trim(), clientSecret: document.getElementById("oidcClientSecret").value.trim(), - redirectUri : document.getElementById("oidcRedirectUri").value.trim() + redirectUri: document.getElementById("oidcRedirectUri").value.trim() }; const gURL = document.getElementById("globalOtpauthUrl").value.trim(); - if([dFL,dBA,dOIDC].filter(x=>x).length===3){ + if ([dFL, dBA, dOIDC].filter(x => x).length === 3) { showToast(t("at_least_one_login_method")); return; } - sendRequest("/api/admin/updateConfig.php","POST",{ - header_title:nHT, oidc:nOIDC, - disableFormLogin:dFL, disableBasicAuth:dBA, disableOIDCLogin:dOIDC, - enableWebDAV:eWD, sharedMaxUploadSize:sMax, globalOtpauthUrl:gURL - },{ - "X-CSRF-Token":window.csrfToken - }).then(res=>{ - if(res.success){ - showToast(t("settings_updated_successfully"),"success"); + sendRequest("/api/admin/updateConfig.php", "POST", { + header_title: nHT, oidc: nOIDC, + disableFormLogin: dFL, disableBasicAuth: dBA, disableOIDCLogin: dOIDC, + enableWebDAV: eWD, sharedMaxUploadSize: sMax, globalOtpauthUrl: gURL + }, { + "X-CSRF-Token": window.csrfToken + }).then(res => { + if (res.success) { + showToast(t("settings_updated_successfully"), "success"); captureInitialAdminConfig(); closeAdminPanel(); loadAdminConfigFunc(); } else { - showToast(t("error_updating_settings")+": "+(res.error||t("unknown_error")),"error"); + showToast(t("error_updating_settings") + ": " + (res.error || t("unknown_error")), "error"); } - }).catch(()=>{/*noop*/}); + }).catch(() => {/*noop*/ }); } export async function closeAdminPanel() { - if(hasUnsavedChanges()){ + if (hasUnsavedChanges()) { const ok = await showCustomConfirmModal(t("unsaved_changes_confirm")); - if(!ok) return; + if (!ok) return; } - document.getElementById("adminPanelModal").style.display="none"; + document.getElementById("adminPanelModal").style.display = "none"; } // --- New: User Permissions Modal --- export function openUserPermissionsModal() { - let userPermissionsModal = document.getElementById("userPermissionsModal"); - const isDarkMode = document.body.classList.contains("dark-mode"); - const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; - const modalContentStyles = ` + let userPermissionsModal = document.getElementById("userPermissionsModal"); + const isDarkMode = document.body.classList.contains("dark-mode"); + const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; + const modalContentStyles = ` background: ${isDarkMode ? "#2c2c2c" : "#fff"}; color: ${isDarkMode ? "#e0e0e0" : "#000"}; padding: 20px; @@ -510,11 +516,11 @@ export function openUserPermissionsModal() { border-radius: 8px; position: relative; `; - - if (!userPermissionsModal) { - userPermissionsModal = document.createElement("div"); - userPermissionsModal.id = "userPermissionsModal"; - userPermissionsModal.style.cssText = ` + + if (!userPermissionsModal) { + userPermissionsModal = document.createElement("div"); + userPermissionsModal.id = "userPermissionsModal"; + userPermissionsModal.style.cssText = ` position: fixed; top: 0; left: 0; @@ -526,7 +532,7 @@ export function openUserPermissionsModal() { align-items: center; z-index: 3500; `; - userPermissionsModal.innerHTML = ` + userPermissionsModal.innerHTML = ` `; - document.body.appendChild(userPermissionsModal); - document.getElementById("closeUserPermissionsModal").addEventListener("click", () => { - userPermissionsModal.style.display = "none"; - }); - document.getElementById("cancelUserPermissionsBtn").addEventListener("click", () => { - userPermissionsModal.style.display = "none"; - }); - document.getElementById("saveUserPermissionsBtn").addEventListener("click", () => { - // Collect permissions data from each user row. - const rows = userPermissionsModal.querySelectorAll(".user-permission-row"); - const permissionsData = []; - rows.forEach(row => { - const username = row.getAttribute("data-username"); - const folderOnlyCheckbox = row.querySelector("input[data-permission='folderOnly']"); - const readOnlyCheckbox = row.querySelector("input[data-permission='readOnly']"); - const disableUploadCheckbox = row.querySelector("input[data-permission='disableUpload']"); - permissionsData.push({ - username, - folderOnly: folderOnlyCheckbox.checked, - readOnly: readOnlyCheckbox.checked, - disableUpload: disableUploadCheckbox.checked - }); + document.body.appendChild(userPermissionsModal); + document.getElementById("closeUserPermissionsModal").addEventListener("click", () => { + userPermissionsModal.style.display = "none"; + }); + document.getElementById("cancelUserPermissionsBtn").addEventListener("click", () => { + userPermissionsModal.style.display = "none"; + }); + document.getElementById("saveUserPermissionsBtn").addEventListener("click", () => { + // Collect permissions data from each user row. + const rows = userPermissionsModal.querySelectorAll(".user-permission-row"); + const permissionsData = []; + rows.forEach(row => { + const username = row.getAttribute("data-username"); + const folderOnlyCheckbox = row.querySelector("input[data-permission='folderOnly']"); + const readOnlyCheckbox = row.querySelector("input[data-permission='readOnly']"); + const disableUploadCheckbox = row.querySelector("input[data-permission='disableUpload']"); + permissionsData.push({ + username, + folderOnly: folderOnlyCheckbox.checked, + readOnly: readOnlyCheckbox.checked, + disableUpload: disableUploadCheckbox.checked }); - // Send the permissionsData to the server. - sendRequest("/api/updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken }) - .then(response => { - if (response.success) { - showToast(t("user_permissions_updated_successfully")); - userPermissionsModal.style.display = "none"; - } else { - showToast(t("error_updating_permissions") + ": " + (response.error || t("unknown_error"))); - } - }) - .catch(() => { - showToast(t("error_updating_permissions")); - }); }); - } else { - userPermissionsModal.style.display = "flex"; - } - // Load the list of users into the modal. - loadUserPermissionsList(); + // Send the permissionsData to the server. + sendRequest("/api/updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken }) + .then(response => { + if (response.success) { + showToast(t("user_permissions_updated_successfully")); + userPermissionsModal.style.display = "none"; + } else { + showToast(t("error_updating_permissions") + ": " + (response.error || t("unknown_error"))); + } + }) + .catch(() => { + showToast(t("error_updating_permissions")); + }); + }); + } else { + userPermissionsModal.style.display = "flex"; } - - function loadUserPermissionsList() { - const listContainer = document.getElementById("userPermissionsList"); - if (!listContainer) return; - listContainer.innerHTML = ""; - - // First, fetch the current permissions from the server. - fetch("/api/getUserPermissions.php", { credentials: "include" }) - .then(response => response.json()) - .then(permissionsData => { - // Then, fetch the list of users. - return fetch("/api/getUsers.php", { credentials: "include" }) - .then(response => response.json()) - .then(usersData => { - const users = Array.isArray(usersData) ? usersData : (usersData.users || []); - if (users.length === 0) { - listContainer.innerHTML = "

" + t("no_users_found") + "

"; - return; - } - users.forEach(user => { - // Skip admin users. - if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return; - - // Use stored permissions if available; otherwise fall back to defaults. - const defaultPerm = { - folderOnly: false, - readOnly: false, - disableUpload: false, - }; - - // Normalize the username key to match server storage (e.g., lowercase) - const usernameKey = user.username.toLowerCase(); - - const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData)) - ? permissionsData[usernameKey] - : defaultPerm; - - // Create a row for the user. - const row = document.createElement("div"); - row.classList.add("user-permission-row"); - row.setAttribute("data-username", user.username); - row.style.padding = "10px 0"; - row.innerHTML = ` + // Load the list of users into the modal. + loadUserPermissionsList(); +} + +function loadUserPermissionsList() { + const listContainer = document.getElementById("userPermissionsList"); + if (!listContainer) return; + listContainer.innerHTML = ""; + + // First, fetch the current permissions from the server. + fetch("/api/getUserPermissions.php", { credentials: "include" }) + .then(response => response.json()) + .then(permissionsData => { + // Then, fetch the list of users. + return fetch("/api/getUsers.php", { credentials: "include" }) + .then(response => response.json()) + .then(usersData => { + const users = Array.isArray(usersData) ? usersData : (usersData.users || []); + if (users.length === 0) { + listContainer.innerHTML = "

" + t("no_users_found") + "

"; + return; + } + users.forEach(user => { + // Skip admin users. + if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return; + + // Use stored permissions if available; otherwise fall back to defaults. + const defaultPerm = { + folderOnly: false, + readOnly: false, + disableUpload: false, + }; + + // Normalize the username key to match server storage (e.g., lowercase) + const usernameKey = user.username.toLowerCase(); + + const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData)) + ? permissionsData[usernameKey] + : defaultPerm; + + // Create a row for the user. + const row = document.createElement("div"); + row.classList.add("user-permission-row"); + row.setAttribute("data-username", user.username); + row.style.padding = "10px 0"; + row.innerHTML = `
${user.username}

`; - listContainer.appendChild(row); - }); + listContainer.appendChild(row); }); - }) - .catch(() => { - listContainer.innerHTML = "

" + t("error_loading_users") + "

"; - }); - } \ No newline at end of file + }); + }) + .catch(() => { + listContainer.innerHTML = "

" + t("error_loading_users") + "

"; + }); +} \ No newline at end of file diff --git a/public/js/i18n.js b/public/js/i18n.js index 112a72e..7d436d1 100644 --- a/public/js/i18n.js +++ b/public/js/i18n.js @@ -184,6 +184,7 @@ const translations = { // Admin Panel "header_settings": "Header Settings", + "shared_max_upload_size_bytes_title": "Shared Max Upload Size", "shared_max_upload_size_bytes": "Shared Max Upload Size (bytes)", "max_bytes_shared_uploads_note": "Enter maximum bytes allowed for shared-folder uploads", "manage_shared_links": "Manage Shared Links", @@ -194,6 +195,7 @@ const translations = { "share_deleted_successfully": "Share deleted successfully", "error_deleting_share": "Error deleting share", "password_protected": "Password protected", + "no_shared_links_available": "No shared links available", // NEW KEYS ADDED FOR ADMIN, USER PANELS, AND TOTP MODALS: