Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9c4200827 | ||
|
|
97559873dc | ||
|
|
0683b27534 | ||
|
|
49c42e8096 | ||
|
|
ed39e112a9 | ||
|
|
25edab923a | ||
|
|
b8ae3c4402 | ||
|
|
fb537b1d61 | ||
|
|
90439022e3 | ||
|
|
b4c8738b8a | ||
|
|
e193bf9b13 |
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,13 +1,45 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Changes 4/13/2025 v1.1.3
|
||||||
|
|
||||||
|
- Decreased header height some more and clickable logo.
|
||||||
|
- authModals.js fully updated with i18n.js keys.
|
||||||
|
- main.js added Dark & Light mode i18n.js keys.
|
||||||
|
- New Admin section Header Settings to change Header Title.
|
||||||
|
- Admin Panel confirm unsaved changes.
|
||||||
|
- Added translations and data attributes for almost all user-facing text
|
||||||
|
- Extend i18n support: Add new translation keys for Download and Share modals
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Changes 4/12/2025
|
## Changes 4/12/2025
|
||||||
|
|
||||||
- **Fuse.js Integration for Indexed Real-Time Searching**
|
- Moved Gallery view toggle button into header.
|
||||||
- **Added Fuse.js Library:** Included Fuse.js via a CDN `<script>` tag to leverage its client‑side fuzzy search capabilities.
|
- Removed css entries that are not needed anymore for Gallery View Toggle.
|
||||||
- **Created searchFiles Helper Function:** Introduced a new function that uses Fuse.js to build an index and perform fuzzy searches over file properties (file name, uploader, and nested tag names).
|
- Change search box text when enabling advanced search.
|
||||||
- **Transformed JSON Object to Array:** Updated the loadFileList() function to convert the returned file data into an array (if it isn’t already) and assign file names from JSON keys.
|
- Advanced/Basic search button as material icon on same row as search bar.
|
||||||
- **Updated Rendering Functions:** Modified both renderFileTable() and renderGalleryView() to use the searchFiles() helper instead of a simple in‑array .filter(). This ensures that every search—real‑time by user input—is powered by Fuse.js’s indexed search.
|
|
||||||
- **Enhanced Search Configuration:** Configured Fuse.js to search across multiple keys (file name, uploader, and tags) so that users can find files based on any of these properties.
|
### Advanced Search Implementation
|
||||||
|
|
||||||
|
- **Advanced Search Toggle:**
|
||||||
|
- Added a global toggle (`window.advancedSearchEnabled`) and a UI button to switch between basic and advanced search modes.
|
||||||
|
- The toggle button label changes between "Advanced Search" and "Basic Search" to reflect the active mode.
|
||||||
|
|
||||||
|
- **Fuse.js Integration Updates:**
|
||||||
|
- Modified the `searchFiles()` function to conditionally include the `"content"` key in the Fuse.js keys only when advanced search mode is enabled.
|
||||||
|
- Adjusted Fuse.js options by adding `ignoreLocation: true`, adjusting the `threshold`, and optionally assigning weights (e.g., a lower weight for `name` and a higher weight for `content`) to prioritize matches in file content.
|
||||||
|
|
||||||
|
- **Backend (PHP) Enhancements:**
|
||||||
|
- Updated **getFileList.php** to read the content of text-based files (e.g., `.txt`, `.html`, `.md`, etc.) using `file_get_contents()`.
|
||||||
|
- Added a `"content"` property to the JSON response for eligible files to allow for full-text search in advanced mode.
|
||||||
|
|
||||||
|
### Fuse.js Integration for Indexed Real-Time Searching**
|
||||||
|
|
||||||
|
- **Added Fuse.js Library:** Included Fuse.js via a CDN `<script>` tag to leverage its client‑side fuzzy search capabilities.
|
||||||
|
- **Created searchFiles Helper Function:** Introduced a new function that uses Fuse.js to build an index and perform fuzzy searches over file properties (file name, uploader, and nested tag names).
|
||||||
|
- **Transformed JSON Object to Array:** Updated the loadFileList() function to convert the returned file data into an array (if it isn’t already) and assign file names from JSON keys.
|
||||||
|
- **Updated Rendering Functions:** Modified both renderFileTable() and renderGalleryView() to use the searchFiles() helper instead of a simple in‑array .filter(). This ensures that every search—real‑time by user input—is powered by Fuse.js’s indexed search.
|
||||||
|
- **Enhanced Search Configuration:** Configured Fuse.js to search across multiple keys (file name, uploader, and tags) so that users can find files based on any of these properties.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Upload, organize, and share files through a sleek web interface. **FileRise** is
|
|||||||
|
|
||||||
- 📝 **Built-in Editor & Preview:** View images, videos, audio, and PDFs inline with a preview modal – no need to download just to see them. Edit text/code files right in your browser with a CodeMirror-based editor featuring syntax highlighting and line numbers. Great for config files or notes – tweak and save changes without leaving FileRise.
|
- 📝 **Built-in Editor & Preview:** View images, videos, audio, and PDFs inline with a preview modal – no need to download just to see them. Edit text/code files right in your browser with a CodeMirror-based editor featuring syntax highlighting and line numbers. Great for config files or notes – tweak and save changes without leaving FileRise.
|
||||||
|
|
||||||
- 🏷️ **Tags & Search:** Categorize your files with color-coded tags and quickly locate them using our advanced, indexed real-time search. The built-in search now leverages Fuse.js to provide fuzzy matching across file names, tags, and uploader fields—helping you find that “important” document even if you make a typo.
|
- 🏷️ **Tags & Search:** Categorize your files with color-coded tags and locate them instantly using our indexed real-time search. Easily switch to Advanced Search mode to enable fuzzy matching not only across file names, tags, and uploader fields but also within the content of text files—helping you find that “important” document even if you make a typo or need to search deep within the file.
|
||||||
|
|
||||||
- 🔒 **User Authentication & User Permissions:** Secure your portal with username/password login. Supports multiple users – create user accounts (admin UI provided) for family or team members. User permissions such as User “Folder Only” feature assigns each user a dedicated folder within the root directory, named after their username, restricting them from viewing or modifying other directories. User Read Only and Disable Upload are additional permissions. FileRise also integrates with Single Sign-On (OIDC) providers (e.g., OAuth2/OIDC for Google/Authentik/Keycloak) and offers optional TOTP two-factor auth for extra security.
|
- 🔒 **User Authentication & User Permissions:** Secure your portal with username/password login. Supports multiple users – create user accounts (admin UI provided) for family or team members. User permissions such as User “Folder Only” feature assigns each user a dedicated folder within the root directory, named after their username, restricting them from viewing or modifying other directories. User Read Only and Disable Upload are additional permissions. FileRise also integrates with Single Sign-On (OIDC) providers (e.g., OAuth2/OIDC for Google/Authentik/Keycloak) and offers optional TOTP two-factor auth for extra security.
|
||||||
|
|
||||||
|
|||||||
124
css/styles.css
124
css/styles.css
@@ -32,8 +32,8 @@ body {
|
|||||||
|
|
||||||
@media (min-width: 1300px) {
|
@media (min-width: 1300px) {
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
padding-left: 40px !important;
|
padding-left: 30px !important;
|
||||||
padding-right: 40px !important;
|
padding-right: 30px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 65px;
|
height: 55px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
background-color: #2196F3;
|
background-color: #2196F3;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
@@ -82,28 +82,16 @@ body.dark-mode .header-container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-logo {
|
.header-logo {
|
||||||
max-height: 60px;
|
max-height: 50px;
|
||||||
width: auto;
|
width: auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-logo svg {
|
.header-logo svg {
|
||||||
height: 60px;
|
height: 50px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
height: 80px;
|
|
||||||
padding: 0 20px;
|
|
||||||
background-color: #2196F3;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode header {
|
body.dark-mode header {
|
||||||
background-color: #1f1f1f;
|
background-color: #1f1f1f;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.7);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.7);
|
||||||
@@ -1584,39 +1572,6 @@ body.dark-mode .btn-secondary {
|
|||||||
border-color: #6c757d;
|
border-color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toggleViewBtn {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
margin-left: 14px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
||||||
transition: background 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
#toggleViewBtn {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
#toggleViewBtn {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#toggleViewBtn:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode .btn-danger {
|
body.dark-mode .btn-danger {
|
||||||
background-color: #dc3545;
|
background-color: #dc3545;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -1729,21 +1684,6 @@ body.dark-mode .folder-help-icon {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark-mode #searchIcon {
|
|
||||||
background-color: #444;
|
|
||||||
border: 1px solid #555;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #fff;
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.dark-mode #searchInput {
|
|
||||||
background-color: #333;
|
|
||||||
color: #e0e0e0;
|
|
||||||
border: 1px solid #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
body.dark-mode .CodeMirror {
|
body.dark-mode .CodeMirror {
|
||||||
background: #1e1e1e !important;
|
background: #1e1e1e !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
@@ -2161,3 +2101,57 @@ body.dark-mode .header-drop-zone.drag-active {
|
|||||||
body.dark-mode #fileSummary {
|
body.dark-mode #fileSummary {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#searchIcon {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode #searchIcon {
|
||||||
|
background-color: #444;
|
||||||
|
border: 1px solid #555;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode #searchInput {
|
||||||
|
background-color: #333;
|
||||||
|
color: #e0e0e0;
|
||||||
|
border: 1px solid #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon .material-icons,
|
||||||
|
#searchIcon .material-icons {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon:hover,
|
||||||
|
.btn-icon:focus {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn-icon .material-icons,
|
||||||
|
body.dark-mode #searchIcon .material-icons {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark-mode .btn-icon:hover,
|
||||||
|
body.dark-mode .btn-icon:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
@@ -11,14 +11,24 @@ if (file_exists($configFile)) {
|
|||||||
echo json_encode(['error' => 'Failed to decrypt configuration.']);
|
echo json_encode(['error' => 'Failed to decrypt configuration.']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
// Decode the configuration and ensure globalOtpauthUrl is set
|
// Decode the configuration and ensure required fields are set
|
||||||
$config = json_decode($decryptedContent, true);
|
$config = json_decode($decryptedContent, true);
|
||||||
|
|
||||||
|
// Ensure globalOtpauthUrl is set
|
||||||
if (!isset($config['globalOtpauthUrl'])) {
|
if (!isset($config['globalOtpauthUrl'])) {
|
||||||
$config['globalOtpauthUrl'] = "";
|
$config['globalOtpauthUrl'] = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NEW: Ensure header_title is set.
|
||||||
|
if (!isset($config['header_title']) || empty($config['header_title'])) {
|
||||||
|
$config['header_title'] = "FileRise"; // default value
|
||||||
|
}
|
||||||
|
|
||||||
echo json_encode($config);
|
echo json_encode($config);
|
||||||
} else {
|
} else {
|
||||||
|
// If no config file exists, provide defaults
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
|
'header_title' => "FileRise",
|
||||||
'oidc' => [
|
'oidc' => [
|
||||||
'providerUrl' => 'https://your-oidc-provider.com',
|
'providerUrl' => 'https://your-oidc-provider.com',
|
||||||
'clientId' => 'YOUR_CLIENT_ID',
|
'clientId' => 'YOUR_CLIENT_ID',
|
||||||
|
|||||||
@@ -25,11 +25,6 @@ if ($folder !== 'root') {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper: Generate the metadata file path for a given folder.
|
* Helper: Generate the metadata file path for a given folder.
|
||||||
* For "root", returns "root_metadata.json". Otherwise, replaces slashes,
|
|
||||||
* backslashes, and spaces with dashes and appends "_metadata.json".
|
|
||||||
*
|
|
||||||
* @param string $folder The folder's relative path.
|
|
||||||
* @return string The full path to the folder's metadata file.
|
|
||||||
*/
|
*/
|
||||||
function getMetadataFilePath($folder) {
|
function getMetadataFilePath($folder) {
|
||||||
if (strtolower($folder) === 'root' || $folder === '') {
|
if (strtolower($folder) === 'root' || $folder === '') {
|
||||||
@@ -69,7 +64,6 @@ foreach ($files as $file) {
|
|||||||
|
|
||||||
// Since metadata is stored per folder, the key is simply the file name.
|
// Since metadata is stored per folder, the key is simply the file name.
|
||||||
$metaKey = $file;
|
$metaKey = $file;
|
||||||
|
|
||||||
$fileDateModified = filemtime($filePath) ? date(DATE_TIME_FORMAT, filemtime($filePath)) : "Unknown";
|
$fileDateModified = filemtime($filePath) ? date(DATE_TIME_FORMAT, filemtime($filePath)) : "Unknown";
|
||||||
$fileUploadedDate = isset($metadata[$metaKey]["uploaded"]) ? $metadata[$metaKey]["uploaded"] : "Unknown";
|
$fileUploadedDate = isset($metadata[$metaKey]["uploaded"]) ? $metadata[$metaKey]["uploaded"] : "Unknown";
|
||||||
$fileUploader = isset($metadata[$metaKey]["uploader"]) ? $metadata[$metaKey]["uploader"] : "Unknown";
|
$fileUploader = isset($metadata[$metaKey]["uploader"]) ? $metadata[$metaKey]["uploader"] : "Unknown";
|
||||||
@@ -85,7 +79,8 @@ foreach ($files as $file) {
|
|||||||
$fileSizeFormatted = sprintf("%s bytes", number_format($fileSizeBytes));
|
$fileSizeFormatted = sprintf("%s bytes", number_format($fileSizeBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
$fileList[] = [
|
// Build the basic file entry.
|
||||||
|
$fileEntry = [
|
||||||
'name' => $file,
|
'name' => $file,
|
||||||
'modified' => $fileDateModified,
|
'modified' => $fileDateModified,
|
||||||
'uploaded' => $fileUploadedDate,
|
'uploaded' => $fileUploadedDate,
|
||||||
@@ -93,6 +88,14 @@ foreach ($files as $file) {
|
|||||||
'uploader' => $fileUploader,
|
'uploader' => $fileUploader,
|
||||||
'tags' => isset($metadata[$metaKey]['tags']) ? $metadata[$metaKey]['tags'] : []
|
'tags' => isset($metadata[$metaKey]['tags']) ? $metadata[$metaKey]['tags'] : []
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add file content for text-based files.
|
||||||
|
if (preg_match('/\.(txt|html|htm|md|js|css|json|xml|php|py|ini|conf|log)$/i', $file)) {
|
||||||
|
$content = file_get_contents($filePath);
|
||||||
|
$fileEntry['content'] = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileList[] = $fileEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load global tags from createdTags.json.
|
// Load global tags from createdTags.json.
|
||||||
|
|||||||
28
index.html
28
index.html
@@ -50,6 +50,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<header class="header-container">
|
<header class="header-container">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
|
<a href="index.html">
|
||||||
<div class="header-logo">
|
<div class="header-logo">
|
||||||
<svg version="1.1" id="filingCabinetLogo" xmlns="http://www.w3.org/2000/svg"
|
<svg version="1.1" id="filingCabinetLogo" xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" xml:space="preserve">
|
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 64 64" xml:space="preserve">
|
||||||
@@ -113,6 +114,7 @@
|
|||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-title">
|
<div class="header-title">
|
||||||
<h1 data-i18n-key="header_title">FileRise</h1>
|
<h1 data-i18n-key="header_title">FileRise</h1>
|
||||||
@@ -389,29 +391,31 @@
|
|||||||
</div> <!-- end mainColumn -->
|
</div> <!-- end mainColumn -->
|
||||||
</div> <!-- end main-wrapper -->
|
</div> <!-- end main-wrapper -->
|
||||||
|
|
||||||
<!-- Download Progress Modal -->
|
<!-- Download Progress Modal -->
|
||||||
<div id="downloadProgressModal" class="modal" style="display: none;">
|
<div id="downloadProgressModal" class="modal" style="display: none;">
|
||||||
<div class="modal-content" style="text-align: center; padding: 20px;">
|
<div class="modal-content" style="text-align: center; padding: 20px;">
|
||||||
<!-- Material icon spinner with a dedicated class -->
|
<!-- Material icon spinner with a dedicated class -->
|
||||||
<span class="material-icons download-spinner">autorenew</span>
|
<span class="material-icons download-spinner">autorenew</span>
|
||||||
<p>Preparing your download...</p>
|
<p data-i18n-key="preparing_download">Preparing your download...</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Single File Download Modal -->
|
<!-- Single File Download Modal -->
|
||||||
<div id="downloadFileModal" class="modal" style="display: none;">
|
<div id="downloadFileModal" class="modal" style="display: none;">
|
||||||
<div class="modal-content" style="text-align: center; padding: 20px;">
|
<div class="modal-content" style="text-align: center; padding: 20px;">
|
||||||
<h4>Download File</h4>
|
<h4 data-i18n-key="download_file">Download File</h4>
|
||||||
<p>Confirm or change the download file name:</p>
|
<p data-i18n-key="confirm_or_change_filename">Confirm or change the download file name:</p>
|
||||||
<input type="text" id="downloadFileNameInput" class="form-control" placeholder="Filename" />
|
<input type="text" id="downloadFileNameInput" class="form-control" data-i18n-placeholder="filename" placeholder="Filename" />
|
||||||
<div style="margin-top: 15px; text-align: right;">
|
<div style="margin-top: 15px; text-align: right;">
|
||||||
<button id="cancelDownloadFile" class="btn btn-secondary"
|
<button id="cancelDownloadFile" class="btn btn-secondary"
|
||||||
onclick="document.getElementById('downloadFileModal').style.display = 'none';">Cancel</button>
|
onclick="document.getElementById('downloadFileModal').style.display = 'none';"
|
||||||
|
data-i18n-key="cancel">Cancel</button>
|
||||||
<button id="confirmSingleDownloadButton" class="btn btn-primary"
|
<button id="confirmSingleDownloadButton" class="btn btn-primary"
|
||||||
onclick="confirmSingleDownload()">Download</button>
|
onclick="confirmSingleDownload()"
|
||||||
</div>
|
data-i18n-key="download">Download</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Change Password, Add User, Remove User, Rename File, and Custom Confirm Modals (unchanged) -->
|
<!-- Change Password, Add User, Remove User, Rename File, and Custom Confirm Modals (unchanged) -->
|
||||||
<div id="changePasswordModal" class="modal" style="display:none;">
|
<div id="changePasswordModal" class="modal" style="display:none;">
|
||||||
|
|||||||
32
js/auth.js
32
js/auth.js
@@ -1,5 +1,5 @@
|
|||||||
import { sendRequest } from './networkUtils.js';
|
import { sendRequest } from './networkUtils.js';
|
||||||
import { t } from './i18n.js';
|
import { t, applyTranslations } from './i18n.js';
|
||||||
import {
|
import {
|
||||||
toggleVisibility,
|
toggleVisibility,
|
||||||
showToast as originalShowToast,
|
showToast as originalShowToast,
|
||||||
@@ -97,18 +97,36 @@ function loadAdminConfigFunc() {
|
|||||||
return fetch("getConfig.php", { credentials: "include" })
|
return fetch("getConfig.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(config => {
|
.then(config => {
|
||||||
|
// Save header_title into localStorage (if needed)
|
||||||
|
localStorage.setItem("headerTitle", config.header_title || "FileRise");
|
||||||
|
|
||||||
|
// Update login options and global OTPAuth URL as before
|
||||||
localStorage.setItem("disableFormLogin", config.loginOptions.disableFormLogin);
|
localStorage.setItem("disableFormLogin", config.loginOptions.disableFormLogin);
|
||||||
localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth);
|
localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth);
|
||||||
localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin);
|
localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin);
|
||||||
localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
|
localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
|
||||||
|
|
||||||
|
// Update the UI for login options
|
||||||
updateLoginOptionsUIFromStorage();
|
updateLoginOptionsUIFromStorage();
|
||||||
|
|
||||||
|
const headerTitleElem = document.querySelector(".header-title h1");
|
||||||
|
if (headerTitleElem) {
|
||||||
|
headerTitleElem.textContent = config.header_title || "FileRise";
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
// Fallback defaults in case of error
|
||||||
|
localStorage.setItem("headerTitle", "FileRise");
|
||||||
localStorage.setItem("disableFormLogin", "false");
|
localStorage.setItem("disableFormLogin", "false");
|
||||||
localStorage.setItem("disableBasicAuth", "false");
|
localStorage.setItem("disableBasicAuth", "false");
|
||||||
localStorage.setItem("disableOIDCLogin", "false");
|
localStorage.setItem("disableOIDCLogin", "false");
|
||||||
localStorage.setItem("globalOtpauthUrl", "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
|
localStorage.setItem("globalOtpauthUrl", "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
|
||||||
updateLoginOptionsUIFromStorage();
|
updateLoginOptionsUIFromStorage();
|
||||||
|
|
||||||
|
const headerTitleElem = document.querySelector(".header-title h1");
|
||||||
|
if (headerTitleElem) {
|
||||||
|
headerTitleElem.textContent = "FileRise";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +164,8 @@ function updateAuthenticatedUI(data) {
|
|||||||
restoreBtn = document.createElement("button");
|
restoreBtn = document.createElement("button");
|
||||||
restoreBtn.id = "restoreFilesBtn";
|
restoreBtn.id = "restoreFilesBtn";
|
||||||
restoreBtn.classList.add("btn", "btn-warning");
|
restoreBtn.classList.add("btn", "btn-warning");
|
||||||
restoreBtn.innerHTML = '<i class="material-icons" title="Restore/Delete Trash">restore_from_trash</i>';
|
restoreBtn.setAttribute("data-i18n-title", "trash_restore_delete");
|
||||||
|
restoreBtn.innerHTML = '<i class="material-icons">restore_from_trash</i>';
|
||||||
if (firstButton) insertAfter(restoreBtn, firstButton);
|
if (firstButton) insertAfter(restoreBtn, firstButton);
|
||||||
else headerButtons.appendChild(restoreBtn);
|
else headerButtons.appendChild(restoreBtn);
|
||||||
}
|
}
|
||||||
@@ -157,7 +176,8 @@ function updateAuthenticatedUI(data) {
|
|||||||
adminPanelBtn = document.createElement("button");
|
adminPanelBtn = document.createElement("button");
|
||||||
adminPanelBtn.id = "adminPanelBtn";
|
adminPanelBtn.id = "adminPanelBtn";
|
||||||
adminPanelBtn.classList.add("btn", "btn-info");
|
adminPanelBtn.classList.add("btn", "btn-info");
|
||||||
adminPanelBtn.innerHTML = '<i class="material-icons" title="Admin Panel">admin_panel_settings</i>';
|
adminPanelBtn.setAttribute("data-i18n-title", "admin_panel");
|
||||||
|
adminPanelBtn.innerHTML = '<i class="material-icons">admin_panel_settings</i>';
|
||||||
insertAfter(adminPanelBtn, restoreBtn);
|
insertAfter(adminPanelBtn, restoreBtn);
|
||||||
adminPanelBtn.addEventListener("click", openAdminPanel);
|
adminPanelBtn.addEventListener("click", openAdminPanel);
|
||||||
} else {
|
} else {
|
||||||
@@ -176,7 +196,9 @@ function updateAuthenticatedUI(data) {
|
|||||||
userPanelBtn = document.createElement("button");
|
userPanelBtn = document.createElement("button");
|
||||||
userPanelBtn.id = "userPanelBtn";
|
userPanelBtn.id = "userPanelBtn";
|
||||||
userPanelBtn.classList.add("btn", "btn-user");
|
userPanelBtn.classList.add("btn", "btn-user");
|
||||||
userPanelBtn.innerHTML = '<i class="material-icons" title="User Panel">account_circle</i>';
|
userPanelBtn.setAttribute("data-i18n-title", "user_panel");
|
||||||
|
userPanelBtn.innerHTML = '<i class="material-icons">account_circle</i>';
|
||||||
|
|
||||||
const adminBtn = document.getElementById("adminPanelBtn");
|
const adminBtn = document.getElementById("adminPanelBtn");
|
||||||
if (adminBtn) insertAfter(userPanelBtn, adminBtn);
|
if (adminBtn) insertAfter(userPanelBtn, adminBtn);
|
||||||
else if (firstButton) insertAfter(userPanelBtn, firstButton);
|
else if (firstButton) insertAfter(userPanelBtn, firstButton);
|
||||||
@@ -186,7 +208,7 @@ function updateAuthenticatedUI(data) {
|
|||||||
userPanelBtn.style.display = "block";
|
userPanelBtn.style.display = "block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applyTranslations();
|
||||||
updateItemsPerPageSelect();
|
updateItemsPerPageSelect();
|
||||||
updateLoginOptionsUIFromStorage();
|
updateLoginOptionsUIFromStorage();
|
||||||
}
|
}
|
||||||
|
|||||||
270
js/authModals.js
270
js/authModals.js
@@ -2,8 +2,9 @@ import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.
|
|||||||
import { sendRequest } from './networkUtils.js';
|
import { sendRequest } from './networkUtils.js';
|
||||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||||
|
|
||||||
const version = "v1.1.2";
|
const version = "v1.1.3";
|
||||||
const adminTitle = `Admin Panel <small style="font-size: 12px; color: gray;">${version}</small>`;
|
// Use t() for the admin panel title. (Make sure t("admin_panel") returns "Admin Panel" in English.)
|
||||||
|
const adminTitle = `${t("admin_panel")} <small style="font-size: 12px; color: gray;">${version}</small>`;
|
||||||
|
|
||||||
let lastLoginData = null;
|
let lastLoginData = null;
|
||||||
export function setLastLoginData(data) {
|
export function setLastLoginData(data) {
|
||||||
@@ -44,7 +45,7 @@ export function openTOTPLoginModal() {
|
|||||||
<input type="text" id="recoveryInput"
|
<input type="text" id="recoveryInput"
|
||||||
style="font-size:24px; text-align:center; width:100%; padding:10px;"
|
style="font-size:24px; text-align:center; width:100%; padding:10px;"
|
||||||
placeholder="Recovery code" />
|
placeholder="Recovery code" />
|
||||||
<button type="button" id="submitRecovery" class="btn btn-secondary" style="margin-top:10px;">Submit Recovery Code</button>
|
<button type="button" id="submitRecovery" class="btn btn-secondary" style="margin-top:10px;">${t("submit_recovery_code")}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -66,12 +67,12 @@ export function openTOTPLoginModal() {
|
|||||||
// Switch to recovery
|
// Switch to recovery
|
||||||
totpSection.style.display = "none";
|
totpSection.style.display = "none";
|
||||||
recoverySection.style.display = "block";
|
recoverySection.style.display = "block";
|
||||||
toggleLink.textContent = "Use TOTP Code instead";
|
toggleLink.textContent = t("use_totp_code_instead");
|
||||||
} else {
|
} else {
|
||||||
// Switch back to TOTP
|
// Switch back to TOTP
|
||||||
recoverySection.style.display = "none";
|
recoverySection.style.display = "none";
|
||||||
totpSection.style.display = "block";
|
totpSection.style.display = "block";
|
||||||
toggleLink.textContent = "Use Recovery Code instead";
|
toggleLink.textContent = t("use_recovery_code_instead");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ export function openTOTPLoginModal() {
|
|||||||
document.getElementById("submitRecovery").addEventListener("click", () => {
|
document.getElementById("submitRecovery").addEventListener("click", () => {
|
||||||
const recoveryCode = document.getElementById("recoveryInput").value.trim();
|
const recoveryCode = document.getElementById("recoveryInput").value.trim();
|
||||||
if (!recoveryCode) {
|
if (!recoveryCode) {
|
||||||
showToast("Please enter your recovery code.");
|
showToast(t("please_enter_recovery_code"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("totp_recover.php", {
|
fetch("totp_recover.php", {
|
||||||
@@ -97,11 +98,11 @@ export function openTOTPLoginModal() {
|
|||||||
// recovery succeeded → finalize login
|
// recovery succeeded → finalize login
|
||||||
window.location.href = "index.html";
|
window.location.href = "index.html";
|
||||||
} else {
|
} else {
|
||||||
showToast(json.message || "Recovery code verification failed");
|
showToast(json.message || t("recovery_code_verification_failed"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
showToast("Error verifying recovery code.");
|
showToast(t("error_verifying_recovery_code"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,14 +126,14 @@ export function openTOTPLoginModal() {
|
|||||||
if (json.status === "ok") {
|
if (json.status === "ok") {
|
||||||
window.location.href = "index.html";
|
window.location.href = "index.html";
|
||||||
} else {
|
} else {
|
||||||
showToast(json.message || "TOTP verification failed");
|
showToast(json.message || t("totp_verification_failed"));
|
||||||
this.value = "";
|
this.value = "";
|
||||||
totpLoginModal.style.display = "flex";
|
totpLoginModal.style.display = "flex";
|
||||||
totpInput.focus();
|
totpInput.focus();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
showToast("TOTP verification failed");
|
showToast(t("totp_verification_failed"));
|
||||||
this.value = "";
|
this.value = "";
|
||||||
totpLoginModal.style.display = "flex";
|
totpLoginModal.style.display = "flex";
|
||||||
totpInput.focus();
|
totpInput.focus();
|
||||||
@@ -189,24 +190,24 @@ export function openUserPanel() {
|
|||||||
userPanelModal.innerHTML = `
|
userPanelModal.innerHTML = `
|
||||||
<div class="modal-content user-panel-content" style="${modalContentStyles}">
|
<div class="modal-content user-panel-content" style="${modalContentStyles}">
|
||||||
<span id="closeUserPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
<span id="closeUserPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||||
<h3>User Panel (${username})</h3>
|
<h3>${t("user_panel")} (${username})</h3>
|
||||||
<button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">Change Password</button>
|
<button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">${t("change_password")}</button>
|
||||||
<fieldset style="margin-bottom: 15px;">
|
<fieldset style="margin-bottom: 15px;">
|
||||||
<legend>TOTP Settings</legend>
|
<legend>${t("totp_settings")}</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="userTOTPEnabled">Enable TOTP:</label>
|
<label for="userTOTPEnabled">${t("enable_totp")}:</label>
|
||||||
<input type="checkbox" id="userTOTPEnabled" style="vertical-align: middle;" />
|
<input type="checkbox" id="userTOTPEnabled" style="vertical-align: middle;" />
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset style="margin-bottom: 15px;">
|
<fieldset style="margin-bottom: 15px;">
|
||||||
<legend>Language</legend>
|
<legend>${t("language")}</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="languageSelector">Select Language:</label>
|
<label for="languageSelector">${t("select_language")}:</label>
|
||||||
<select id="languageSelector">
|
<select id="languageSelector">
|
||||||
<option value="en">English</option>
|
<option value="en">${t("english")}</option>
|
||||||
<option value="es">Español</option>
|
<option value="es">${t("spanish")}</option>
|
||||||
<option value="fr">Français</option>
|
<option value="fr">${t("french")}</option>
|
||||||
<option value="de">Deutsch</option>
|
<option value="de">${t("german")}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -239,12 +240,12 @@ export function openUserPanel() {
|
|||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
showToast("Error updating TOTP setting: " + result.error);
|
showToast(t("error_updating_totp_setting") + ": " + result.error);
|
||||||
} else if (enabled) {
|
} else if (enabled) {
|
||||||
openTOTPModal();
|
openTOTPModal();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { showToast("Error updating TOTP setting."); });
|
.catch(() => { showToast(t("error_updating_totp_setting")); });
|
||||||
});
|
});
|
||||||
// Language dropdown initialization
|
// Language dropdown initialization
|
||||||
const languageSelector = document.getElementById("languageSelector");
|
const languageSelector = document.getElementById("languageSelector");
|
||||||
@@ -283,10 +284,10 @@ function showRecoveryCodeModal(recoveryCode) {
|
|||||||
`;
|
`;
|
||||||
recoveryModal.innerHTML = `
|
recoveryModal.innerHTML = `
|
||||||
<div style="background: #fff; color: #000; padding: 20px; max-width: 400px; width: 90%; border-radius: 8px; text-align: center;">
|
<div style="background: #fff; color: #000; padding: 20px; max-width: 400px; width: 90%; border-radius: 8px; text-align: center;">
|
||||||
<h3>Your Recovery Code</h3>
|
<h3>${t("your_recovery_code")}</h3>
|
||||||
<p>Please save this code securely. It will not be shown again and can only be used once.</p>
|
<p>${t("please_save_recovery_code")}</p>
|
||||||
<code style="display: block; margin: 10px 0; font-size: 20px;">${recoveryCode}</code>
|
<code style="display: block; margin: 10px 0; font-size: 20px;">${recoveryCode}</code>
|
||||||
<button type="button" id="closeRecoveryModal" class="btn btn-primary">OK</button>
|
<button type="button" id="closeRecoveryModal" class="btn btn-primary">${t("ok")}</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(recoveryModal);
|
document.body.appendChild(recoveryModal);
|
||||||
@@ -327,15 +328,15 @@ export function openTOTPModal() {
|
|||||||
totpModal.innerHTML = `
|
totpModal.innerHTML = `
|
||||||
<div class="modal-content" style="${modalContentStyles}">
|
<div class="modal-content" style="${modalContentStyles}">
|
||||||
<span id="closeTOTPModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
<span id="closeTOTPModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||||
<h3>TOTP Setup</h3>
|
<h3>${t("totp_setup")}</h3>
|
||||||
<p>Scan this QR code with your authenticator app:</p>
|
<p>${t("scan_qr_code")}</p>
|
||||||
<!-- Create an image placeholder without the CSRF token in the src -->
|
<!-- Create an image placeholder without the CSRF token in the src -->
|
||||||
<img id="totpQRCodeImage" src="" alt="TOTP QR Code" style="max-width: 100%; height: auto; display: block; margin: 0 auto;">
|
<img id="totpQRCodeImage" src="" alt="TOTP QR Code" style="max-width: 100%; height: auto; display: block; margin: 0 auto;">
|
||||||
<br/>
|
<br/>
|
||||||
<p>Enter the 6-digit code from your app to confirm setup:</p>
|
<p>${t("enter_totp_confirmation")}</p>
|
||||||
<input type="text" id="totpConfirmInput" maxlength="6" style="font-size:24px; text-align:center; width:100%; padding:10px;" placeholder="6-digit code" />
|
<input type="text" id="totpConfirmInput" maxlength="6" style="font-size:24px; text-align:center; width:100%; padding:10px;" placeholder="6-digit code" />
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<button type="button" id="confirmTOTPBtn" class="btn btn-primary">Confirm</button>
|
<button type="button" id="confirmTOTPBtn" class="btn btn-primary">${t("confirm")}</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(totpModal);
|
document.body.appendChild(totpModal);
|
||||||
@@ -348,7 +349,7 @@ export function openTOTPModal() {
|
|||||||
document.getElementById("confirmTOTPBtn").addEventListener("click", function () {
|
document.getElementById("confirmTOTPBtn").addEventListener("click", function () {
|
||||||
const code = document.getElementById("totpConfirmInput").value.trim();
|
const code = document.getElementById("totpConfirmInput").value.trim();
|
||||||
if (code.length !== 6) {
|
if (code.length !== 6) {
|
||||||
showToast("Please enter a valid 6-digit code.");
|
showToast(t("please_enter_valid_code"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("totp_verify.php", {
|
fetch("totp_verify.php", {
|
||||||
@@ -363,7 +364,7 @@ export function openTOTPModal() {
|
|||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result.status === 'ok') {
|
if (result.status === 'ok') {
|
||||||
showToast("TOTP successfully enabled.");
|
showToast(t("totp_enabled_successfully"));
|
||||||
// After successful TOTP verification, fetch the recovery code
|
// After successful TOTP verification, fetch the recovery code
|
||||||
fetch("totp_saveCode.php", {
|
fetch("totp_saveCode.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -379,16 +380,16 @@ export function openTOTPModal() {
|
|||||||
// Show the recovery code in a secure modal
|
// Show the recovery code in a secure modal
|
||||||
showRecoveryCodeModal(data.recoveryCode);
|
showRecoveryCodeModal(data.recoveryCode);
|
||||||
} else {
|
} else {
|
||||||
showToast("Error generating recovery code: " + (data.message || "Unknown error."));
|
showToast(t("error_generating_recovery_code") + ": " + (data.message || t("unknown_error")));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { showToast("Error generating recovery code."); });
|
.catch(() => { showToast(t("error_generating_recovery_code")); });
|
||||||
closeTOTPModal(false);
|
closeTOTPModal(false);
|
||||||
} else {
|
} else {
|
||||||
showToast("TOTP verification failed: " + (result.message || "Invalid code."));
|
showToast(t("totp_verification_failed") + ": " + (result.message || t("invalid_code")));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { showToast("Error verifying TOTP code."); });
|
.catch(() => { showToast(t("error_verifying_totp_code")); });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus the input and attach enter key listener
|
// Focus the input and attach enter key listener
|
||||||
@@ -451,7 +452,7 @@ function loadTOTPQRCode() {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error("Error loading TOTP QR code:", error);
|
console.error("Error loading TOTP QR code:", error);
|
||||||
showToast("Error loading QR code.");
|
showToast(t("error_loading_qr_code"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,17 +480,88 @@ export function closeTOTPModal(disable = true) {
|
|||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
showToast("Error disabling TOTP setting: " + result.error);
|
showToast(t("error_disabling_totp_setting") + ": " + result.error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { showToast("Error disabling TOTP setting."); });
|
.catch(() => { showToast(t("error_disabling_totp_setting")); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global variable to hold the initial state of the admin form.
|
||||||
|
let originalAdminConfig = {};
|
||||||
|
|
||||||
|
// Capture the initial state of the admin form fields.
|
||||||
|
function captureInitialAdminConfig() {
|
||||||
|
originalAdminConfig = {
|
||||||
|
headerTitle: document.getElementById("headerTitle").value.trim(),
|
||||||
|
oidcProviderUrl: document.getElementById("oidcProviderUrl").value.trim(),
|
||||||
|
oidcClientId: document.getElementById("oidcClientId").value.trim(),
|
||||||
|
oidcClientSecret: document.getElementById("oidcClientSecret").value.trim(),
|
||||||
|
oidcRedirectUri: document.getElementById("oidcRedirectUri").value.trim(),
|
||||||
|
disableFormLogin: document.getElementById("disableFormLogin").checked,
|
||||||
|
disableBasicAuth: document.getElementById("disableBasicAuth").checked,
|
||||||
|
disableOIDCLogin: document.getElementById("disableOIDCLogin").checked,
|
||||||
|
globalOtpauthUrl: document.getElementById("globalOtpauthUrl").value.trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare current values to the captured initial state.
|
||||||
|
function hasUnsavedChanges() {
|
||||||
|
return (
|
||||||
|
document.getElementById("headerTitle").value.trim() !== originalAdminConfig.headerTitle ||
|
||||||
|
document.getElementById("oidcProviderUrl").value.trim() !== originalAdminConfig.oidcProviderUrl ||
|
||||||
|
document.getElementById("oidcClientId").value.trim() !== originalAdminConfig.oidcClientId ||
|
||||||
|
document.getElementById("oidcClientSecret").value.trim() !== originalAdminConfig.oidcClientSecret ||
|
||||||
|
document.getElementById("oidcRedirectUri").value.trim() !== originalAdminConfig.oidcRedirectUri ||
|
||||||
|
document.getElementById("disableFormLogin").checked !== originalAdminConfig.disableFormLogin ||
|
||||||
|
document.getElementById("disableBasicAuth").checked !== originalAdminConfig.disableBasicAuth ||
|
||||||
|
document.getElementById("disableOIDCLogin").checked !== originalAdminConfig.disableOIDCLogin ||
|
||||||
|
document.getElementById("globalOtpauthUrl").value.trim() !== originalAdminConfig.globalOtpauthUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use your custom confirmation modal.
|
||||||
|
function showCustomConfirmModal(message) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Get modal elements from DOM.
|
||||||
|
const modal = document.getElementById("customConfirmModal");
|
||||||
|
const messageElem = document.getElementById("confirmMessage");
|
||||||
|
const yesBtn = document.getElementById("confirmYesBtn");
|
||||||
|
const noBtn = document.getElementById("confirmNoBtn");
|
||||||
|
|
||||||
|
// Set the message in the modal.
|
||||||
|
messageElem.textContent = message;
|
||||||
|
modal.style.display = "block";
|
||||||
|
|
||||||
|
// Define event handlers.
|
||||||
|
function onYes() {
|
||||||
|
cleanup();
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
function onNo() {
|
||||||
|
cleanup();
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
// Remove event listeners and hide modal after choice.
|
||||||
|
function cleanup() {
|
||||||
|
yesBtn.removeEventListener("click", onYes);
|
||||||
|
noBtn.removeEventListener("click", onNo);
|
||||||
|
modal.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
yesBtn.addEventListener("click", onYes);
|
||||||
|
noBtn.addEventListener("click", onNo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function openAdminPanel() {
|
export function openAdminPanel() {
|
||||||
fetch("getConfig.php", { credentials: "include" })
|
fetch("getConfig.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(config => {
|
.then(config => {
|
||||||
|
if (config.header_title) {
|
||||||
|
document.querySelector(".header-title h1").textContent = config.header_title;
|
||||||
|
window.headerTitle = config.header_title || "FileRise";
|
||||||
|
}
|
||||||
if (config.oidc) Object.assign(window.currentOIDCConfig, config.oidc);
|
if (config.oidc) Object.assign(window.currentOIDCConfig, config.oidc);
|
||||||
if (config.globalOtpauthUrl) window.currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl;
|
if (config.globalOtpauthUrl) window.currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl;
|
||||||
const isDarkMode = document.body.classList.contains("dark-mode");
|
const isDarkMode = document.body.classList.contains("dark-mode");
|
||||||
@@ -523,77 +595,84 @@ export function openAdminPanel() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 3000;
|
z-index: 3000;
|
||||||
`;
|
`;
|
||||||
// Added a version number next to "Admin Panel"
|
|
||||||
adminModal.innerHTML = `
|
adminModal.innerHTML = `
|
||||||
<div class="modal-content" style="${modalContentStyles}">
|
<div class="modal-content" style="${modalContentStyles}">
|
||||||
<span id="closeAdminPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
<span id="closeAdminPanel" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||||
<h3>
|
|
||||||
<h3>${adminTitle}</h3>
|
<h3>${adminTitle}</h3>
|
||||||
</h3>
|
|
||||||
<form id="adminPanelForm">
|
<form id="adminPanelForm">
|
||||||
<fieldset style="margin-bottom: 15px;">
|
<fieldset style="margin-bottom: 15px;">
|
||||||
<legend>User Management</legend>
|
<legend>${t("user_management")}</legend>
|
||||||
<div style="display: flex; gap: 10px;">
|
<div style="display: flex; gap: 10px;">
|
||||||
<button type="button" id="adminOpenAddUser" class="btn btn-success">Add User</button>
|
<button type="button" id="adminOpenAddUser" class="btn btn-success">${t("add_user")}</button>
|
||||||
<button type="button" id="adminOpenRemoveUser" class="btn btn-danger">Remove User</button>
|
<button type="button" id="adminOpenRemoveUser" class="btn btn-danger">${t("remove_user")}</button>
|
||||||
<button type="button" id="adminOpenUserPermissions" class="btn btn-secondary">User Permissions</button>
|
<button type="button" id="adminOpenUserPermissions" class="btn btn-secondary">${t("user_permissions")}</button>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset style="margin-bottom: 15px;">
|
<fieldset style="margin-bottom: 15px;">
|
||||||
<legend>OIDC Configuration</legend>
|
<legend>Header Settings</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oidcProviderUrl">OIDC Provider URL:</label>
|
<label for="headerTitle">Header Title:</label>
|
||||||
|
<input type="text" id="headerTitle" class="form-control" value="${window.headerTitle}" />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset style="margin-bottom: 15px;">
|
||||||
|
<legend>${t("login_options")}</legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="disableFormLogin" />
|
||||||
|
<label for="disableFormLogin">${t("disable_login_form")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="disableBasicAuth" />
|
||||||
|
<label for="disableBasicAuth">${t("disable_basic_http_auth")}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="checkbox" id="disableOIDCLogin" />
|
||||||
|
<label for="disableOIDCLogin">${t("disable_oidc_login")}</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset style="margin-bottom: 15px;">
|
||||||
|
<legend>${t("oidc_configuration")}</legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="oidcProviderUrl">${t("oidc_provider_url")}:</label>
|
||||||
<input type="text" id="oidcProviderUrl" class="form-control" value="${window.currentOIDCConfig.providerUrl}" />
|
<input type="text" id="oidcProviderUrl" class="form-control" value="${window.currentOIDCConfig.providerUrl}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oidcClientId">OIDC Client ID:</label>
|
<label for="oidcClientId">${t("oidc_client_id")}:</label>
|
||||||
<input type="text" id="oidcClientId" class="form-control" value="${window.currentOIDCConfig.clientId}" />
|
<input type="text" id="oidcClientId" class="form-control" value="${window.currentOIDCConfig.clientId}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oidcClientSecret">OIDC Client Secret:</label>
|
<label for="oidcClientSecret">${t("oidc_client_secret")}:</label>
|
||||||
<input type="text" id="oidcClientSecret" class="form-control" value="${window.currentOIDCConfig.clientSecret}" />
|
<input type="text" id="oidcClientSecret" class="form-control" value="${window.currentOIDCConfig.clientSecret}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="oidcRedirectUri">OIDC Redirect URI:</label>
|
<label for="oidcRedirectUri">${t("oidc_redirect_uri")}:</label>
|
||||||
<input type="text" id="oidcRedirectUri" class="form-control" value="${window.currentOIDCConfig.redirectUri}" />
|
<input type="text" id="oidcRedirectUri" class="form-control" value="${window.currentOIDCConfig.redirectUri}" />
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset style="margin-bottom: 15px;">
|
<fieldset style="margin-bottom: 15px;">
|
||||||
<legend>Global TOTP Settings</legend>
|
<legend>${t("global_totp_settings")}</legend>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="globalOtpauthUrl">Global OTPAuth URL:</label>
|
<label for="globalOtpauthUrl">${t("global_otpauth_url")}:</label>
|
||||||
<input type="text" id="globalOtpauthUrl" class="form-control" value="${window.currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/{label}?secret={secret}&issuer=FileRise'}" />
|
<input type="text" id="globalOtpauthUrl" class="form-control" value="${window.currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/{label}?secret={secret}&issuer=FileRise'}" />
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset style="margin-bottom: 15px;">
|
|
||||||
<legend>Login Options</legend>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="checkbox" id="disableFormLogin" />
|
|
||||||
<label for="disableFormLogin">Disable Login Form</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="checkbox" id="disableBasicAuth" />
|
|
||||||
<label for="disableBasicAuth">Disable Basic HTTP Auth</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<input type="checkbox" id="disableOIDCLogin" />
|
|
||||||
<label for="disableOIDCLogin">Disable OIDC Login</label>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button type="button" id="cancelAdminSettings" class="btn btn-secondary">Cancel</button>
|
<button type="button" id="cancelAdminSettings" class="btn btn-secondary">${t("cancel")}</button>
|
||||||
<button type="button" id="saveAdminSettings" class="btn btn-primary">Save Settings</button>
|
<button type="button" id="saveAdminSettings" class="btn btn-primary">${t("save_settings")}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(adminModal);
|
document.body.appendChild(adminModal);
|
||||||
|
|
||||||
|
// Bind closing events that will use our enhanced close function.
|
||||||
document.getElementById("closeAdminPanel").addEventListener("click", closeAdminPanel);
|
document.getElementById("closeAdminPanel").addEventListener("click", closeAdminPanel);
|
||||||
adminModal.addEventListener("click", (e) => {
|
adminModal.addEventListener("click", (e) => {
|
||||||
if (e.target === adminModal) closeAdminPanel();
|
if (e.target === adminModal) closeAdminPanel();
|
||||||
});
|
});
|
||||||
document.getElementById("cancelAdminSettings").addEventListener("click", closeAdminPanel);
|
document.getElementById("cancelAdminSettings").addEventListener("click", closeAdminPanel);
|
||||||
|
|
||||||
|
// Bind other buttons.
|
||||||
document.getElementById("adminOpenAddUser").addEventListener("click", () => {
|
document.getElementById("adminOpenAddUser").addEventListener("click", () => {
|
||||||
toggleVisibility("addUserModal", true);
|
toggleVisibility("addUserModal", true);
|
||||||
document.getElementById("newUsername").focus();
|
document.getElementById("newUsername").focus();
|
||||||
@@ -604,7 +683,6 @@ export function openAdminPanel() {
|
|||||||
}
|
}
|
||||||
toggleVisibility("removeUserModal", true);
|
toggleVisibility("removeUserModal", true);
|
||||||
});
|
});
|
||||||
// New event binding for the User Permissions button:
|
|
||||||
document.getElementById("adminOpenUserPermissions").addEventListener("click", () => {
|
document.getElementById("adminOpenUserPermissions").addEventListener("click", () => {
|
||||||
openUserPermissionsModal();
|
openUserPermissionsModal();
|
||||||
});
|
});
|
||||||
@@ -614,7 +692,7 @@ export function openAdminPanel() {
|
|||||||
const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin");
|
const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin");
|
||||||
const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length;
|
const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length;
|
||||||
if (totalDisabled === 3) {
|
if (totalDisabled === 3) {
|
||||||
showToast("At least one login method must remain enabled.");
|
showToast(t("at_least_one_login_method"));
|
||||||
disableOIDCLoginCheckbox.checked = false;
|
disableOIDCLoginCheckbox.checked = false;
|
||||||
localStorage.setItem("disableOIDCLogin", "false");
|
localStorage.setItem("disableOIDCLogin", "false");
|
||||||
if (typeof window.updateLoginOptionsUI === "function") {
|
if (typeof window.updateLoginOptionsUI === "function") {
|
||||||
@@ -626,6 +704,7 @@ export function openAdminPanel() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const newHeaderTitle = document.getElementById("headerTitle").value.trim();
|
||||||
const newOIDCConfig = {
|
const newOIDCConfig = {
|
||||||
providerUrl: document.getElementById("oidcProviderUrl").value.trim(),
|
providerUrl: document.getElementById("oidcProviderUrl").value.trim(),
|
||||||
clientId: document.getElementById("oidcClientId").value.trim(),
|
clientId: document.getElementById("oidcClientId").value.trim(),
|
||||||
@@ -637,6 +716,7 @@ export function openAdminPanel() {
|
|||||||
const disableOIDCLogin = disableOIDCLoginCheckbox.checked;
|
const disableOIDCLogin = disableOIDCLoginCheckbox.checked;
|
||||||
const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim();
|
const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim();
|
||||||
sendRequest("updateConfig.php", "POST", {
|
sendRequest("updateConfig.php", "POST", {
|
||||||
|
header_title: newHeaderTitle,
|
||||||
oidc: newOIDCConfig,
|
oidc: newOIDCConfig,
|
||||||
disableFormLogin,
|
disableFormLogin,
|
||||||
disableBasicAuth,
|
disableBasicAuth,
|
||||||
@@ -645,27 +725,31 @@ export function openAdminPanel() {
|
|||||||
}, { "X-CSRF-Token": window.csrfToken })
|
}, { "X-CSRF-Token": window.csrfToken })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
showToast("Settings updated successfully.");
|
showToast(t("settings_updated_successfully"));
|
||||||
localStorage.setItem("disableFormLogin", disableFormLogin);
|
localStorage.setItem("disableFormLogin", disableFormLogin);
|
||||||
localStorage.setItem("disableBasicAuth", disableBasicAuth);
|
localStorage.setItem("disableBasicAuth", disableBasicAuth);
|
||||||
localStorage.setItem("disableOIDCLogin", disableOIDCLogin);
|
localStorage.setItem("disableOIDCLogin", disableOIDCLogin);
|
||||||
if (typeof window.updateLoginOptionsUI === "function") {
|
if (typeof window.updateLoginOptionsUI === "function") {
|
||||||
window.updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin });
|
window.updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin });
|
||||||
}
|
}
|
||||||
|
// Update the captured initial state since the changes have now been saved.
|
||||||
|
captureInitialAdminConfig();
|
||||||
closeAdminPanel();
|
closeAdminPanel();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
showToast("Error updating settings: " + (response.error || "Unknown error"));
|
showToast(t("error_updating_settings") + ": " + (response.error || t("unknown_error")));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { });
|
.catch(() => { });
|
||||||
});
|
});
|
||||||
|
// Enforce login option constraints.
|
||||||
const disableFormLoginCheckbox = document.getElementById("disableFormLogin");
|
const disableFormLoginCheckbox = document.getElementById("disableFormLogin");
|
||||||
const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth");
|
const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth");
|
||||||
const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin");
|
const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin");
|
||||||
function enforceLoginOptionConstraint(changedCheckbox) {
|
function enforceLoginOptionConstraint(changedCheckbox) {
|
||||||
const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length;
|
const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length;
|
||||||
if (changedCheckbox.checked && totalDisabled === 3) {
|
if (changedCheckbox.checked && totalDisabled === 3) {
|
||||||
showToast("At least one login method must remain enabled.");
|
showToast(t("at_least_one_login_method"));
|
||||||
changedCheckbox.checked = false;
|
changedCheckbox.checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -676,6 +760,9 @@ export function openAdminPanel() {
|
|||||||
document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true;
|
document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true;
|
||||||
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true;
|
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true;
|
||||||
document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true;
|
document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true;
|
||||||
|
|
||||||
|
// Capture initial state after the modal loads.
|
||||||
|
captureInitialAdminConfig();
|
||||||
} else {
|
} else {
|
||||||
adminModal.style.backgroundColor = overlayBackground;
|
adminModal.style.backgroundColor = overlayBackground;
|
||||||
const modalContent = adminModal.querySelector(".modal-content");
|
const modalContent = adminModal.querySelector(".modal-content");
|
||||||
@@ -693,6 +780,7 @@ export function openAdminPanel() {
|
|||||||
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true;
|
document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true;
|
||||||
document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true;
|
document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true;
|
||||||
adminModal.style.display = "flex";
|
adminModal.style.display = "flex";
|
||||||
|
captureInitialAdminConfig();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -714,19 +802,25 @@ export function openAdminPanel() {
|
|||||||
document.getElementById("disableBasicAuth").checked = localStorage.getItem("disableBasicAuth") === "true";
|
document.getElementById("disableBasicAuth").checked = localStorage.getItem("disableBasicAuth") === "true";
|
||||||
document.getElementById("disableOIDCLogin").checked = localStorage.getItem("disableOIDCLogin") === "true";
|
document.getElementById("disableOIDCLogin").checked = localStorage.getItem("disableOIDCLogin") === "true";
|
||||||
adminModal.style.display = "flex";
|
adminModal.style.display = "flex";
|
||||||
|
captureInitialAdminConfig();
|
||||||
} else {
|
} else {
|
||||||
openAdminPanel();
|
openAdminPanel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeAdminPanel() {
|
export async function closeAdminPanel() {
|
||||||
|
if (hasUnsavedChanges()) {
|
||||||
|
const userConfirmed = await showCustomConfirmModal(t("unsaved_changes_confirm"));
|
||||||
|
if (!userConfirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const adminModal = document.getElementById("adminPanelModal");
|
const adminModal = document.getElementById("adminPanelModal");
|
||||||
if (adminModal) adminModal.style.display = "none";
|
if (adminModal) adminModal.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- New: User Permissions Modal ---
|
// --- New: User Permissions Modal ---
|
||||||
|
|
||||||
export function openUserPermissionsModal() {
|
export function openUserPermissionsModal() {
|
||||||
let userPermissionsModal = document.getElementById("userPermissionsModal");
|
let userPermissionsModal = document.getElementById("userPermissionsModal");
|
||||||
const isDarkMode = document.body.classList.contains("dark-mode");
|
const isDarkMode = document.body.classList.contains("dark-mode");
|
||||||
@@ -759,13 +853,13 @@ export function openUserPermissionsModal() {
|
|||||||
userPermissionsModal.innerHTML = `
|
userPermissionsModal.innerHTML = `
|
||||||
<div class="modal-content" style="${modalContentStyles}">
|
<div class="modal-content" style="${modalContentStyles}">
|
||||||
<span id="closeUserPermissionsModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
<span id="closeUserPermissionsModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||||
<h3>User Permissions</h3>
|
<h3>${t("user_permissions")}</h3>
|
||||||
<div id="userPermissionsList" style="max-height: 300px; overflow-y: auto; margin-bottom: 15px;">
|
<div id="userPermissionsList" style="max-height: 300px; overflow-y: auto; margin-bottom: 15px;">
|
||||||
<!-- User rows will be loaded here -->
|
<!-- User rows will be loaded here -->
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; justify-content: flex-end; gap: 10px;">
|
<div style="display: flex; justify-content: flex-end; gap: 10px;">
|
||||||
<button type="button" id="cancelUserPermissionsBtn" class="btn btn-secondary">Cancel</button>
|
<button type="button" id="cancelUserPermissionsBtn" class="btn btn-secondary">${t("cancel")}</button>
|
||||||
<button type="button" id="saveUserPermissionsBtn" class="btn btn-primary">Save Permissions</button>
|
<button type="button" id="saveUserPermissionsBtn" class="btn btn-primary">${t("save_permissions")}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -796,14 +890,14 @@ export function openUserPermissionsModal() {
|
|||||||
sendRequest("updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken })
|
sendRequest("updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
showToast("User permissions updated successfully.");
|
showToast(t("user_permissions_updated_successfully"));
|
||||||
userPermissionsModal.style.display = "none";
|
userPermissionsModal.style.display = "none";
|
||||||
} else {
|
} else {
|
||||||
showToast("Error updating permissions: " + (response.error || "Unknown error"));
|
showToast(t("error_updating_permissions") + ": " + (response.error || t("unknown_error")));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
showToast("Error updating permissions.");
|
showToast(t("error_updating_permissions"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -828,14 +922,14 @@ function loadUserPermissionsList() {
|
|||||||
.then(usersData => {
|
.then(usersData => {
|
||||||
const users = Array.isArray(usersData) ? usersData : (usersData.users || []);
|
const users = Array.isArray(usersData) ? usersData : (usersData.users || []);
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
listContainer.innerHTML = "<p>No users found.</p>";
|
listContainer.innerHTML = "<p>" + t("no_users_found") + "</p>";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
users.forEach(user => {
|
users.forEach(user => {
|
||||||
// Skip admin users.
|
// Skip admin users.
|
||||||
if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return;
|
if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return;
|
||||||
|
|
||||||
// Use stored permissions if available; otherwise fall back to localStorage defaults.
|
// Use stored permissions if available; otherwise fall back to defaults.
|
||||||
const defaultPerm = {
|
const defaultPerm = {
|
||||||
folderOnly: false,
|
folderOnly: false,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
@@ -859,15 +953,15 @@ function loadUserPermissionsList() {
|
|||||||
<div style="display: flex; flex-direction: column; gap: 5px;">
|
<div style="display: flex; flex-direction: column; gap: 5px;">
|
||||||
<label style="display: flex; align-items: center; gap: 5px;">
|
<label style="display: flex; align-items: center; gap: 5px;">
|
||||||
<input type="checkbox" data-permission="folderOnly" ${userPerm.folderOnly ? "checked" : ""} />
|
<input type="checkbox" data-permission="folderOnly" ${userPerm.folderOnly ? "checked" : ""} />
|
||||||
User Folder Only
|
${t("user_folder_only")}
|
||||||
</label>
|
</label>
|
||||||
<label style="display: flex; align-items: center; gap: 5px;">
|
<label style="display: flex; align-items: center; gap: 5px;">
|
||||||
<input type="checkbox" data-permission="readOnly" ${userPerm.readOnly ? "checked" : ""} />
|
<input type="checkbox" data-permission="readOnly" ${userPerm.readOnly ? "checked" : ""} />
|
||||||
Read Only
|
${t("read_only")}
|
||||||
</label>
|
</label>
|
||||||
<label style="display: flex; align-items: center; gap: 5px;">
|
<label style="display: flex; align-items: center; gap: 5px;">
|
||||||
<input type="checkbox" data-permission="disableUpload" ${userPerm.disableUpload ? "checked" : ""} />
|
<input type="checkbox" data-permission="disableUpload" ${userPerm.disableUpload ? "checked" : ""} />
|
||||||
Disable Upload
|
${t("disable_upload")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<hr style="margin-top: 10px; border: 0; border-bottom: 1px solid #ccc;">
|
<hr style="margin-top: 10px; border: 0; border-bottom: 1px solid #ccc;">
|
||||||
@@ -877,6 +971,6 @@ function loadUserPermissionsList() {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
listContainer.innerHTML = "<p>Error loading users.</p>";
|
listContainer.innerHTML = "<p>" + t("error_loading_users") + "</p>";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,23 +90,36 @@ export function showToast(message, duration = 3000) {
|
|||||||
|
|
||||||
export function buildSearchAndPaginationControls({ currentPage, totalPages, searchTerm }) {
|
export function buildSearchAndPaginationControls({ currentPage, totalPages, searchTerm }) {
|
||||||
const safeSearchTerm = escapeHTML(searchTerm);
|
const safeSearchTerm = escapeHTML(searchTerm);
|
||||||
|
// Choose the placeholder text based on advanced search mode
|
||||||
|
const placeholderText = window.advancedSearchEnabled
|
||||||
|
? t("search_placeholder_advanced")
|
||||||
|
: t("search_placeholder");
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
<div class="col-12 col-md-8 mb-2 mb-md-0">
|
<div class="col-12 col-md-8 mb-2 mb-md-0">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
<!-- Advanced Search Toggle Button -->
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<button id="advancedSearchToggle" class="btn btn-outline-secondary btn-icon" onclick="toggleAdvancedSearch()" title="${window.advancedSearchEnabled ? t("basic_search_tooltip") : t("advanced_search_tooltip")}">
|
||||||
|
<i class="material-icons">${window.advancedSearchEnabled ? "filter_alt_off" : "filter_alt"}</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Search Icon -->
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text" id="searchIcon">
|
<span class="input-group-text" id="searchIcon">
|
||||||
<i class="material-icons">search</i>
|
<i class="material-icons">search</i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="searchInput" class="form-control" placeholder="${t("search_placeholder")}" value="${safeSearchTerm}" aria-describedby="searchIcon">
|
<!-- Search Input -->
|
||||||
|
<input type="text" id="searchInput" class="form-control" placeholder="${placeholderText}" value="${safeSearchTerm}" aria-describedby="searchIcon">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4 text-left">
|
<div class="col-12 col-md-4 text-left">
|
||||||
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
|
<div class="d-flex justify-content-center justify-content-md-start align-items-center">
|
||||||
<button class="custom-prev-next-btn" ${currentPage === 1 ? "disabled" : ""} onclick="changePage(${currentPage - 1})">Prev</button>
|
<button class="custom-prev-next-btn" ${currentPage === 1 ? "disabled" : ""} onclick="changePage(${currentPage - 1})">${t("prev")}</button>
|
||||||
<span class="page-indicator">Page ${currentPage} of ${totalPages || 1}</span>
|
<span class="page-indicator">${t("page")} ${currentPage} ${t("of")} ${totalPages || 1}</span>
|
||||||
<button class="custom-prev-next-btn" ${currentPage === totalPages ? "disabled" : ""} onclick="changePage(${currentPage + 1})">Next</button>
|
<button class="custom-prev-next-btn" ${currentPage === totalPages ? "disabled" : ""} onclick="changePage(${currentPage + 1})">${t("next")}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +137,7 @@ export function buildFileTableHeader(sortOrder) {
|
|||||||
<th data-column="uploaded" class="hide-small hide-medium sortable-col">${t("upload_date")} ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="uploaded" class="hide-small hide-medium sortable-col">${t("upload_date")} ${sortOrder.column === "uploaded" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th data-column="size" class="hide-small sortable-col">${t("file_size")} ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="size" class="hide-small sortable-col">${t("file_size")} ${sortOrder.column === "size" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th data-column="uploader" class="hide-small hide-medium sortable-col">${t("uploader")} ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
<th data-column="uploader" class="hide-small hide-medium sortable-col">${t("uploader")} ${sortOrder.column === "uploader" ? (sortOrder.ascending ? "▲" : "▼") : ""}</th>
|
||||||
<th>Actions</th>
|
<th>${t("actions")}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
`;
|
`;
|
||||||
@@ -168,36 +181,38 @@ export function buildFileTableRow(file, folderPath) {
|
|||||||
<div class="button-wrap" style="display: flex; justify-content: left; gap: 5px;">
|
<div class="button-wrap" style="display: flex; justify-content: left; gap: 5px;">
|
||||||
<button type="button" class="btn btn-sm btn-success download-btn"
|
<button type="button" class="btn btn-sm btn-success download-btn"
|
||||||
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
||||||
title="Download">
|
title="${t('download')}">
|
||||||
<i class="material-icons">file_download</i>
|
<i class="material-icons">file_download</i>
|
||||||
</button>
|
</button>
|
||||||
${file.editable ? `
|
${file.editable ? `
|
||||||
<button class="btn btn-sm edit-btn"
|
<button class="btn btn-sm edit-btn"
|
||||||
onclick='editFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})'
|
onclick='editFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})'
|
||||||
title="Edit">
|
title="${t('edit')}">
|
||||||
<i class="material-icons">edit</i>
|
<i class="material-icons">edit</i>
|
||||||
</button>
|
</button>
|
||||||
` : ""}
|
` : ""}
|
||||||
${previewButton}
|
${previewButton}
|
||||||
<button class="btn btn-sm btn-warning rename-btn"
|
<button class="btn btn-sm btn-warning rename-btn"
|
||||||
onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})'
|
onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})'
|
||||||
title="Rename">
|
title="${t('rename')}">
|
||||||
<i class="material-icons">drive_file_rename_outline</i>
|
<i class="material-icons">drive_file_rename_outline</i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildBottomControls(itemsPerPageSetting) {
|
export function buildBottomControls(itemsPerPageSetting) {
|
||||||
return `
|
return `
|
||||||
<div class="d-flex align-items-center mt-3 bottom-controls">
|
<div class="d-flex align-items-center mt-3 bottom-controls">
|
||||||
<label class="label-inline mr-2 mb-0">Show</label>
|
<label class="label-inline mr-2 mb-0">${t("show")}</label>
|
||||||
<select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)">
|
<select class="form-control bottom-select" onchange="changeItemsPerPage(this.value)">
|
||||||
${[10, 20, 50, 100].map(num => `<option value="${num}" ${num === itemsPerPageSetting ? "selected" : ""}>${num}</option>`).join("")}
|
${[10, 20, 50, 100]
|
||||||
|
.map(num => `<option value="${num}" ${num === itemsPerPageSetting ? "selected" : ""}>${num}</option>`)
|
||||||
|
.join("")}
|
||||||
</select>
|
</select>
|
||||||
<span class="items-per-page-text ml-2 mb-0">items per page</span>
|
<span class="items-per-page-text ml-2 mb-0">${t("items_per_page")}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ window.itemsPerPage = window.itemsPerPage || 10;
|
|||||||
window.currentPage = window.currentPage || 1;
|
window.currentPage = window.currentPage || 1;
|
||||||
window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "gallery"
|
window.viewMode = localStorage.getItem("viewMode") || "table"; // "table" or "gallery"
|
||||||
|
|
||||||
|
// Global flag for advanced search mode.
|
||||||
|
window.advancedSearchEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --- Helper Functions ---
|
* --- Helper Functions ---
|
||||||
*/
|
*/
|
||||||
@@ -70,25 +73,53 @@ function buildFolderSummary(filteredFiles) {
|
|||||||
return sum + parseSizeToBytes(file.size);
|
return sum + parseSizeToBytes(file.size);
|
||||||
}, 0);
|
}, 0);
|
||||||
const sizeStr = formatSize(totalBytes);
|
const sizeStr = formatSize(totalBytes);
|
||||||
return `<strong>Total Files:</strong> ${totalFiles} | <strong>Total Size:</strong> ${sizeStr}`;
|
return `<strong>${t('total_files')}:</strong> ${totalFiles} | <strong>${t('total_size')}:</strong> ${sizeStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* --- Advanced Search Toggle ---
|
||||||
|
* Toggles advanced search mode. When enabled, the search will include additional keys (e.g. "content").
|
||||||
|
*/
|
||||||
|
function toggleAdvancedSearch() {
|
||||||
|
window.advancedSearchEnabled = !window.advancedSearchEnabled;
|
||||||
|
const advancedBtn = document.getElementById("advancedSearchToggle");
|
||||||
|
if (advancedBtn) {
|
||||||
|
advancedBtn.textContent = window.advancedSearchEnabled ? "Basic Search" : "Advanced Search";
|
||||||
|
}
|
||||||
|
// Re-run the file table rendering with updated search settings.
|
||||||
|
renderFileTable(window.currentFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --- Fuse.js Search Helper ---
|
* --- Fuse.js Search Helper ---
|
||||||
* Uses Fuse.js to perform a fuzzy search on fileData.
|
* Uses Fuse.js to perform a fuzzy search on fileData.
|
||||||
* Searches over file name, uploader, and tag names.
|
* By default, searches over file name, uploader, and tag names.
|
||||||
|
* When advanced search is enabled, it also includes the 'content' property.
|
||||||
*/
|
*/
|
||||||
function searchFiles(searchTerm) {
|
function searchFiles(searchTerm) {
|
||||||
if (!searchTerm) return fileData;
|
if (!searchTerm) return fileData;
|
||||||
// Define search options – adjust threshold as needed.
|
|
||||||
|
// Define search keys.
|
||||||
|
let keys = [
|
||||||
|
{ name: 'name', weight: 0.1 },
|
||||||
|
{ name: 'uploader', weight: 0.1 },
|
||||||
|
{ name: 'tags.name', weight: 0.1 }
|
||||||
|
];
|
||||||
|
if (window.advancedSearchEnabled) {
|
||||||
|
keys.push({ name: 'content', weight: 0.7 });
|
||||||
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
keys: ['name', 'uploader', 'tags.name'],
|
keys: keys,
|
||||||
threshold: 0.3
|
threshold: 0.4,
|
||||||
|
minMatchCharLength: 2,
|
||||||
|
ignoreLocation: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const fuse = new Fuse(fileData, options);
|
const fuse = new Fuse(fileData, options);
|
||||||
// Fuse returns an array of results where each result has an "item" property.
|
let results = fuse.search(searchTerm);
|
||||||
return fuse.search(searchTerm).map(result => result.item);
|
return results.map(result => result.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* --- VIEW MODE TOGGLE BUTTON & Helpers ---
|
* --- VIEW MODE TOGGLE BUTTON & Helpers ---
|
||||||
@@ -98,21 +129,41 @@ export function createViewToggleButton() {
|
|||||||
if (!toggleBtn) {
|
if (!toggleBtn) {
|
||||||
toggleBtn = document.createElement("button");
|
toggleBtn = document.createElement("button");
|
||||||
toggleBtn.id = "toggleViewBtn";
|
toggleBtn.id = "toggleViewBtn";
|
||||||
toggleBtn.classList.add("btn", "btn-secondary");
|
toggleBtn.classList.add("btn", "btn-toggleview");
|
||||||
const titleElem = document.getElementById("fileListTitle");
|
|
||||||
if (titleElem) {
|
// Set initial icon and tooltip based on current view mode.
|
||||||
titleElem.parentNode.insertBefore(toggleBtn, titleElem.nextSibling);
|
if (window.viewMode === "gallery") {
|
||||||
|
toggleBtn.innerHTML = '<i class="material-icons">view_list</i>';
|
||||||
|
toggleBtn.title = t("switch_to_table_view");
|
||||||
|
} else {
|
||||||
|
toggleBtn.innerHTML = '<i class="material-icons">view_module</i>';
|
||||||
|
toggleBtn.title = t("switch_to_gallery_view");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert the button before the last button in the header.
|
||||||
|
const headerButtons = document.querySelector(".header-buttons");
|
||||||
|
if (headerButtons && headerButtons.lastElementChild) {
|
||||||
|
headerButtons.insertBefore(toggleBtn, headerButtons.lastElementChild);
|
||||||
|
} else if (headerButtons) {
|
||||||
|
headerButtons.appendChild(toggleBtn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggleBtn.textContent = window.viewMode === "gallery" ? t("switch_to_table_view") : t("switch_to_gallery_view");
|
|
||||||
toggleBtn.onclick = () => {
|
toggleBtn.onclick = () => {
|
||||||
window.viewMode = window.viewMode === "gallery" ? "table" : "gallery";
|
window.viewMode = window.viewMode === "gallery" ? "table" : "gallery";
|
||||||
localStorage.setItem("viewMode", window.viewMode);
|
localStorage.setItem("viewMode", window.viewMode);
|
||||||
loadFileList(window.currentFolder);
|
loadFileList(window.currentFolder);
|
||||||
toggleBtn.textContent = window.viewMode === "gallery" ? t("switch_to_table_view") : t("switch_to_gallery_view");
|
if (window.viewMode === "gallery") {
|
||||||
|
toggleBtn.innerHTML = '<i class="material-icons">view_list</i>';
|
||||||
|
toggleBtn.title = t("switch_to_table_view");
|
||||||
|
} else {
|
||||||
|
toggleBtn.innerHTML = '<i class="material-icons">view_module</i>';
|
||||||
|
toggleBtn.title = t("switch_to_gallery_view");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return toggleBtn;
|
return toggleBtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatFolderName(folder) {
|
export function formatFolderName(folder) {
|
||||||
if (folder === "root") return "(Root)";
|
if (folder === "root") return "(Root)";
|
||||||
@@ -147,7 +198,7 @@ export function loadFileList(folderParam) {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
fileListContainer.innerHTML = ""; // Clear loading message.
|
fileListContainer.innerHTML = ""; // Clear loading message.
|
||||||
if (data.files && Object.keys(data.files).length > 0) {
|
if (data.files && Object.keys(data.files).length > 0) {
|
||||||
// In case the returned "files" is an object instead of an array, transform it.
|
// If the returned "files" is an object instead of an array, transform it.
|
||||||
if (!Array.isArray(data.files)) {
|
if (!Array.isArray(data.files)) {
|
||||||
data.files = Object.entries(data.files).map(([name, meta]) => {
|
data.files = Object.entries(data.files).map(([name, meta]) => {
|
||||||
meta.name = name;
|
meta.name = name;
|
||||||
@@ -162,6 +213,8 @@ export function loadFileList(folderParam) {
|
|||||||
if (!file.type && /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
|
if (!file.type && /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(file.name)) {
|
||||||
file.type = "image";
|
file.type = "image";
|
||||||
}
|
}
|
||||||
|
// OPTIONAL: For text documents, preload content (if available from backend)
|
||||||
|
// Example: if (/\.txt|html|md|js|css|json|xml$/i.test(file.name)) { file.content = file.content || ""; }
|
||||||
return file;
|
return file;
|
||||||
});
|
});
|
||||||
fileData = data.files;
|
fileData = data.files;
|
||||||
@@ -234,11 +287,15 @@ export function renderFileTable(folder, container) {
|
|||||||
? "uploads/"
|
? "uploads/"
|
||||||
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
: "uploads/" + folder.split("/").map(encodeURIComponent).join("/") + "/";
|
||||||
|
|
||||||
|
// Build the top controls and append the advanced search toggle button.
|
||||||
const topControlsHTML = buildSearchAndPaginationControls({
|
const topControlsHTML = buildSearchAndPaginationControls({
|
||||||
currentPage,
|
currentPage,
|
||||||
totalPages,
|
totalPages,
|
||||||
searchTerm: window.currentSearchTerm || ""
|
searchTerm: window.currentSearchTerm || ""
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const combinedTopHTML = topControlsHTML;
|
||||||
|
|
||||||
let headerHTML = buildFileTableHeader(sortOrder);
|
let headerHTML = buildFileTableHeader(sortOrder);
|
||||||
const startIndex = (currentPage - 1) * itemsPerPageSetting;
|
const startIndex = (currentPage - 1) * itemsPerPageSetting;
|
||||||
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
|
const endIndex = Math.min(startIndex + itemsPerPageSetting, totalFiles);
|
||||||
@@ -259,7 +316,7 @@ export function renderFileTable(folder, container) {
|
|||||||
rowHTML = rowHTML.replace(/(<td class="file-name-cell">)(.*?)(<\/td>)/, (match, p1, p2, p3) => {
|
rowHTML = rowHTML.replace(/(<td class="file-name-cell">)(.*?)(<\/td>)/, (match, p1, p2, p3) => {
|
||||||
return p1 + p2 + tagBadgesHTML + p3;
|
return p1 + p2 + tagBadgesHTML + p3;
|
||||||
});
|
});
|
||||||
rowHTML = rowHTML.replace(/(<\/div>\s*<\/td>\s*<\/tr>)/, `<button class="share-btn btn btn-sm btn-secondary" data-file="${escapeHTML(file.name)}" title="Share">
|
rowHTML = rowHTML.replace(/(<\/div>\s*<\/td>\s*<\/tr>)/, `<button class="share-btn btn btn-sm btn-secondary" data-file="${escapeHTML(file.name)}" title="${t('share')}">
|
||||||
<i class="material-icons">share</i>
|
<i class="material-icons">share</i>
|
||||||
</button>$1`);
|
</button>$1`);
|
||||||
rowsHTML += rowHTML;
|
rowsHTML += rowHTML;
|
||||||
@@ -270,7 +327,7 @@ export function renderFileTable(folder, container) {
|
|||||||
rowsHTML += "</tbody></table>";
|
rowsHTML += "</tbody></table>";
|
||||||
const bottomControlsHTML = buildBottomControls(itemsPerPageSetting);
|
const bottomControlsHTML = buildBottomControls(itemsPerPageSetting);
|
||||||
|
|
||||||
fileListContent.innerHTML = topControlsHTML + headerHTML + rowsHTML + bottomControlsHTML;
|
fileListContent.innerHTML = combinedTopHTML + headerHTML + rowsHTML + bottomControlsHTML;
|
||||||
|
|
||||||
createViewToggleButton();
|
createViewToggleButton();
|
||||||
|
|
||||||
@@ -368,18 +425,18 @@ export function renderGalleryView(folder, container) {
|
|||||||
<div class="button-wrap" style="display: flex; justify-content: center; gap: 5px;">
|
<div class="button-wrap" style="display: flex; justify-content: center; gap: 5px;">
|
||||||
<button type="button" class="btn btn-sm btn-success download-btn"
|
<button type="button" class="btn btn-sm btn-success download-btn"
|
||||||
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
onclick="openDownloadModal('${file.name}', '${file.folder || 'root'}')"
|
||||||
title="Download">
|
title="${t('download')}">
|
||||||
<i class="material-icons">file_download</i>
|
<i class="material-icons">file_download</i>
|
||||||
</button>
|
</button>
|
||||||
${file.editable ? `
|
${file.editable ? `
|
||||||
<button class="btn btn-sm edit-btn" onclick='editFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="Edit">
|
<button class="btn btn-sm edit-btn" onclick='editFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="${t('Edit')}">
|
||||||
<i class="material-icons">edit</i>
|
<i class="material-icons">edit</i>
|
||||||
</button>
|
</button>
|
||||||
` : ""}
|
` : ""}
|
||||||
<button class="btn btn-sm btn-warning rename-btn" onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="Rename">
|
<button class="btn btn-sm btn-warning rename-btn" onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="${t('rename')}">
|
||||||
<i class="material-icons">drive_file_rename_outline</i>
|
<i class="material-icons">drive_file_rename_outline</i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-secondary share-btn" data-file="${escapeHTML(file.name)}" title="Share">
|
<button class="btn btn-sm btn-secondary share-btn" data-file="${escapeHTML(file.name)}" title="${t('share')}">
|
||||||
<i class="material-icons">share</i>
|
<i class="material-icons">share</i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -493,3 +550,4 @@ window.loadFileList = loadFileList;
|
|||||||
window.renderFileTable = renderFileTable;
|
window.renderFileTable = renderFileTable;
|
||||||
window.renderGalleryView = renderGalleryView;
|
window.renderGalleryView = renderGalleryView;
|
||||||
window.sortFiles = sortFiles;
|
window.sortFiles = sortFiles;
|
||||||
|
window.toggleAdvancedSearch = toggleAdvancedSearch;
|
||||||
@@ -26,7 +26,7 @@ export function openShareModal(file, folder) {
|
|||||||
<option value="240">240 minutes</option>
|
<option value="240">240 minutes</option>
|
||||||
<option value="1440">1 Day</option>
|
<option value="1440">1 Day</option>
|
||||||
</select>
|
</select>
|
||||||
<p>Password (optional):</p>
|
<p>${t("password_optional")}</p>
|
||||||
<input type="text" id="sharePassword" placeholder=${t("password_optional")} style="width: 100%;"/>
|
<input type="text" id="sharePassword" placeholder=${t("password_optional")} style="width: 100%;"/>
|
||||||
<br>
|
<br>
|
||||||
<button id="generateShareLinkBtn" class="btn btn-primary" style="margin-top:10px;">${t("generate_share_link")}</button>
|
<button id="generateShareLinkBtn" class="btn btn-primary" style="margin-top:10px;">${t("generate_share_link")}</button>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function openFolderShareModal(folder) {
|
|||||||
<option value="1440">1 ${t("day")}</option>
|
<option value="1440">1 ${t("day")}</option>
|
||||||
</select>
|
</select>
|
||||||
<p>${t("password_optional")}</p>
|
<p>${t("password_optional")}</p>
|
||||||
<input type="text" id="folderSharePassword" placeholder="${t("password")}" style="width: 100%;"/>
|
<input type="text" id="folderSharePassword" placeholder="${t("enter_password")}" style="width: 100%;"/>
|
||||||
<br>
|
<br>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" id="folderShareAllowUpload"> ${t("allow_uploads")}
|
<input type="checkbox" id="folderShareAllowUpload"> ${t("allow_uploads")}
|
||||||
|
|||||||
491
js/i18n.js
491
js/i18n.js
@@ -1,11 +1,14 @@
|
|||||||
/* i18n.js */
|
/* i18n.js */
|
||||||
const translations = {
|
const translations = {
|
||||||
en: { /* English translations */
|
en: {
|
||||||
"please_log_in_to_continue": "Please log in to continue.",
|
"please_log_in_to_continue": "Please log in to continue.",
|
||||||
"no_files_selected": "No files selected.",
|
"no_files_selected": "No files selected.",
|
||||||
"confirm_delete_files": "Are you sure you want to delete {count} selected file(s)?",
|
"confirm_delete_files": "Are you sure you want to delete {count} selected file(s)?",
|
||||||
"element_not_found": "Element with id \"{id}\" not found.",
|
"element_not_found": "Element with id \"{id}\" not found.",
|
||||||
"search_placeholder": "Search files, tags, or uploader...",
|
"search_placeholder": "Search files, tags, & uploader...",
|
||||||
|
"search_placeholder_advanced": "Advanced Search: files, tags, uploader & content...",
|
||||||
|
"basic_search_tooltip": "Basic Search: Search by file name, tags, and uploader.",
|
||||||
|
"advanced_search_tooltip": "Advanced Search: Includes file content, in addition to file name, tags, and uploader.",
|
||||||
"file_name": "File Name",
|
"file_name": "File Name",
|
||||||
"date_modified": "Date Modified",
|
"date_modified": "Date Modified",
|
||||||
"upload_date": "Upload Date",
|
"upload_date": "Upload Date",
|
||||||
@@ -32,7 +35,6 @@ const translations = {
|
|||||||
"tag_name": "Tag Name:",
|
"tag_name": "Tag Name:",
|
||||||
"tag_color": "Tag Color:",
|
"tag_color": "Tag Color:",
|
||||||
"save_tag": "Save Tag",
|
"save_tag": "Save Tag",
|
||||||
"files_in": "Files in",
|
|
||||||
"light_mode": "Light Mode",
|
"light_mode": "Light Mode",
|
||||||
"dark_mode": "Dark Mode",
|
"dark_mode": "Dark Mode",
|
||||||
"upload_instruction": "Drop files/folders here or click 'Choose files'",
|
"upload_instruction": "Drop files/folders here or click 'Choose files'",
|
||||||
@@ -83,6 +85,7 @@ const translations = {
|
|||||||
"folder_help_item_4": "To rename or delete a folder, select it and then click the appropriate button.",
|
"folder_help_item_4": "To rename or delete a folder, select it and then click the appropriate button.",
|
||||||
|
|
||||||
// File List keys:
|
// File List keys:
|
||||||
|
"actions": "Actions",
|
||||||
"file_list_title": "Files in (Root)",
|
"file_list_title": "Files in (Root)",
|
||||||
"files_in": "Files in",
|
"files_in": "Files in",
|
||||||
"delete_files": "Delete Files",
|
"delete_files": "Delete Files",
|
||||||
@@ -99,6 +102,13 @@ const translations = {
|
|||||||
"download_zip_title": "Download Selected Files as Zip",
|
"download_zip_title": "Download Selected Files as Zip",
|
||||||
"download_zip_prompt": "Enter a name for the zip file:",
|
"download_zip_prompt": "Enter a name for the zip file:",
|
||||||
"zip_placeholder": "files.zip",
|
"zip_placeholder": "files.zip",
|
||||||
|
"share": "Share",
|
||||||
|
"total_files": "Total Files",
|
||||||
|
"total_size": "Total Size",
|
||||||
|
"prev": "Prev",
|
||||||
|
"next": "Next",
|
||||||
|
"page": "Page",
|
||||||
|
"of": "of",
|
||||||
|
|
||||||
// Login Form keys:
|
// Login Form keys:
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
@@ -116,6 +126,13 @@ const translations = {
|
|||||||
"create_new_user_title": "Create New User",
|
"create_new_user_title": "Create New User",
|
||||||
"username": "Username:",
|
"username": "Username:",
|
||||||
"password": "Password:",
|
"password": "Password:",
|
||||||
|
"enter_password": "Password",
|
||||||
|
"preparing_download": "Preparing your download...",
|
||||||
|
"download_file": "Download File",
|
||||||
|
"confirm_or_change_filename": "Confirm or change the download file name:",
|
||||||
|
"filename": "Filename",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"download": "Download",
|
||||||
"grant_admin": "Grant Admin Access",
|
"grant_admin": "Grant Admin Access",
|
||||||
"save_user": "Save User",
|
"save_user": "Save User",
|
||||||
|
|
||||||
@@ -135,14 +152,12 @@ const translations = {
|
|||||||
"error_generating_share_link": "Error Generating Share Link",
|
"error_generating_share_link": "Error Generating Share Link",
|
||||||
|
|
||||||
// Folder
|
// Folder
|
||||||
"create_folder": "Create Folder",
|
|
||||||
"rename_folder": "Rename Folder",
|
|
||||||
"folder_share": "Share Folder",
|
"folder_share": "Share Folder",
|
||||||
"delete_folder": "Delete Folder",
|
|
||||||
|
|
||||||
// Custom Confirm Modal keys:
|
// Custom Confirm Modal keys:
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
"unsaved_changes_confirm": "You have unsaved changes. Are you sure you want to close without saving?",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
@@ -160,14 +175,78 @@ const translations = {
|
|||||||
|
|
||||||
// Dark Mode Toggle
|
// Dark Mode Toggle
|
||||||
"dark_mode_toggle": "Dark Mode",
|
"dark_mode_toggle": "Dark Mode",
|
||||||
"light_mode_toggle": "Light Mode"
|
"light_mode_toggle": "Light Mode",
|
||||||
|
|
||||||
|
// NEW KEYS ADDED FOR ADMIN, USER PANELS, AND TOTP MODALS:
|
||||||
|
"admin_panel": "Admin Panel",
|
||||||
|
"user_panel": "User Panel",
|
||||||
|
"trash_restore_delete": "Trash Restore/Delete",
|
||||||
|
"totp_settings": "TOTP Settings",
|
||||||
|
"enable_totp": "Enable TOTP",
|
||||||
|
"language": "Language",
|
||||||
|
"select_language": "Select Language",
|
||||||
|
"english": "English",
|
||||||
|
"spanish": "Spanish",
|
||||||
|
"french": "French",
|
||||||
|
"german": "German",
|
||||||
|
"use_totp_code_instead": "Use TOTP Code instead",
|
||||||
|
"submit_recovery_code": "Submit Recovery Code",
|
||||||
|
"please_enter_recovery_code": "Please enter your recovery code.",
|
||||||
|
"recovery_code_verification_failed": "Recovery code verification failed",
|
||||||
|
"error_verifying_recovery_code": "Error verifying recovery code",
|
||||||
|
"totp_verification_failed": "TOTP verification failed",
|
||||||
|
"error_verifying_totp_code": "Error verifying TOTP code",
|
||||||
|
"totp_setup": "TOTP Setup",
|
||||||
|
"scan_qr_code": "Scan this QR code with your authenticator app.",
|
||||||
|
"enter_totp_confirmation": "Enter the 6-digit code from your app to confirm setup:",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"please_enter_valid_code": "Please enter a valid 6-digit code.",
|
||||||
|
"totp_enabled_successfully": "TOTP successfully enabled.",
|
||||||
|
"error_generating_recovery_code": "Error generating recovery code",
|
||||||
|
"error_loading_qr_code": "Error loading QR code.",
|
||||||
|
"error_disabling_totp_setting": "Error disabling TOTP setting",
|
||||||
|
"user_management": "User Management",
|
||||||
|
"add_user": "Add User",
|
||||||
|
"remove_user": "Remove User",
|
||||||
|
"user_permissions": "User Permissions",
|
||||||
|
"oidc_configuration": "OIDC Configuration",
|
||||||
|
"oidc_provider_url": "OIDC Provider URL",
|
||||||
|
"oidc_client_id": "OIDC Client ID",
|
||||||
|
"oidc_client_secret": "OIDC Client Secret",
|
||||||
|
"oidc_redirect_uri": "OIDC Redirect URI",
|
||||||
|
"global_totp_settings": "Global TOTP Settings",
|
||||||
|
"global_otpauth_url": "Global OTPAuth URL",
|
||||||
|
"login_options": "Login Options",
|
||||||
|
"disable_login_form": "Disable Login Form",
|
||||||
|
"disable_basic_http_auth": "Disable Basic HTTP Auth",
|
||||||
|
"disable_oidc_login": "Disable OIDC Login",
|
||||||
|
"save_settings": "Save Settings",
|
||||||
|
"at_least_one_login_method": "At least one login method must remain enabled.",
|
||||||
|
"settings_updated_successfully": "Settings updated successfully.",
|
||||||
|
"error_updating_settings": "Error updating settings",
|
||||||
|
"user_permissions_updated_successfully": "User permissions updated successfully.",
|
||||||
|
"error_updating_permissions": "Error updating permissions",
|
||||||
|
"no_users_found": "No users found.",
|
||||||
|
"user_folder_only": "User Folder Only",
|
||||||
|
"read_only": "Read Only",
|
||||||
|
"disable_upload": "Disable Upload",
|
||||||
|
"error_loading_users": "Error loading users",
|
||||||
|
"save_permissions": "Save Permissions",
|
||||||
|
"your_recovery_code": "Your Recovery Code",
|
||||||
|
"please_save_recovery_code": "Please save this code securely. It will not be shown again and can only be used once.",
|
||||||
|
"ok": "OK",
|
||||||
|
"show": "Show",
|
||||||
|
"items_per_page": "items per page"
|
||||||
},
|
},
|
||||||
es: { /* Spanish translations */
|
es: {
|
||||||
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
"please_log_in_to_continue": "Por favor, inicie sesión para continuar.",
|
||||||
"no_files_selected": "No se seleccionaron archivos.",
|
"no_files_selected": "No se han seleccionado archivos.",
|
||||||
"confirm_delete_files": "¿Está seguro de que desea eliminar {count} archivo(s) seleccionado(s)?",
|
"confirm_delete_files": "¿Está seguro de que desea eliminar {count} archivo(s) seleccionado(s)?",
|
||||||
"element_not_found": "Elemento con id \"{id}\" no encontrado.",
|
"element_not_found": "Elemento con id \"{id}\" no encontrado.",
|
||||||
"search_placeholder": "Buscar archivos o etiqueta...",
|
"search_placeholder": "Buscar archivos, etiquetas y cargador...",
|
||||||
|
"search_placeholder_advanced": "Búsqueda avanzada: archivos, etiquetas, cargador y contenido...",
|
||||||
|
"basic_search_tooltip": "Búsqueda básica: Buscar por nombre de archivo, etiquetas y cargador.",
|
||||||
|
"advanced_search_tooltip": "Búsqueda avanzada: Incluye el contenido del archivo, además del nombre, etiquetas y cargador.",
|
||||||
"file_name": "Nombre del archivo",
|
"file_name": "Nombre del archivo",
|
||||||
"date_modified": "Fecha de modificación",
|
"date_modified": "Fecha de modificación",
|
||||||
"upload_date": "Fecha de carga",
|
"upload_date": "Fecha de carga",
|
||||||
@@ -194,11 +273,10 @@ const translations = {
|
|||||||
"tag_name": "Nombre de la etiqueta:",
|
"tag_name": "Nombre de la etiqueta:",
|
||||||
"tag_color": "Color de la etiqueta:",
|
"tag_color": "Color de la etiqueta:",
|
||||||
"save_tag": "Guardar etiqueta",
|
"save_tag": "Guardar etiqueta",
|
||||||
"files_in": "Archivos en",
|
|
||||||
"light_mode": "Modo claro",
|
"light_mode": "Modo claro",
|
||||||
"dark_mode": "Modo oscuro",
|
"dark_mode": "Modo oscuro",
|
||||||
"upload_instruction": "Suelte archivos/carpetas o haga clic en 'Elegir archivos'",
|
"upload_instruction": "Suelte archivos/carpetas aquí o haga clic en 'Elegir archivos'",
|
||||||
"no_files_selected_default": "No se seleccionaron archivos",
|
"no_files_selected_default": "No se han seleccionado archivos",
|
||||||
"choose_files": "Elegir archivos",
|
"choose_files": "Elegir archivos",
|
||||||
"delete_selected": "Eliminar seleccionados",
|
"delete_selected": "Eliminar seleccionados",
|
||||||
"copy_selected": "Copiar seleccionados",
|
"copy_selected": "Copiar seleccionados",
|
||||||
@@ -210,7 +288,7 @@ const translations = {
|
|||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"rename": "Renombrar",
|
"rename": "Renombrar",
|
||||||
"trash_empty": "La papelera está vacía.",
|
"trash_empty": "La papelera está vacía.",
|
||||||
"no_trash_selected": "No se seleccionaron elementos de la papelera para restaurar.",
|
"no_trash_selected": "No se han seleccionado elementos de la papelera para restaurar.",
|
||||||
|
|
||||||
// Additional keys for HTML translations:
|
// Additional keys for HTML translations:
|
||||||
"title": "FileRise",
|
"title": "FileRise",
|
||||||
@@ -240,12 +318,13 @@ const translations = {
|
|||||||
"delete_folder_message": "¿Está seguro de que desea eliminar esta carpeta?",
|
"delete_folder_message": "¿Está seguro de que desea eliminar esta carpeta?",
|
||||||
"folder_help": "Ayuda de carpetas",
|
"folder_help": "Ayuda de carpetas",
|
||||||
"folder_help_item_1": "Haga clic en una carpeta en el árbol para ver sus archivos.",
|
"folder_help_item_1": "Haga clic en una carpeta en el árbol para ver sus archivos.",
|
||||||
"folder_help_item_2": "Utilice [-] para colapsar y [+] para expandir carpetas.",
|
"folder_help_item_2": "Utilice [-] para contraer y [+] para expandir las carpetas.",
|
||||||
"folder_help_item_3": "Seleccione una carpeta y haga clic en \"Crear carpeta\" para agregar una subcarpeta.",
|
"folder_help_item_3": "Seleccione una carpeta y haga clic en \"Crear carpeta\" para agregar una subcarpeta.",
|
||||||
"folder_help_item_4": "Para renombrar o eliminar una carpeta, selecciónela y luego haga clic en el botón correspondiente.",
|
"folder_help_item_4": "Para renombrar o eliminar una carpeta, selecciónela y luego haga clic en el botón correspondiente.",
|
||||||
|
|
||||||
// File List keys:
|
// File List keys:
|
||||||
"file_list_title": "Archivos en (Raíz)",
|
"file_list_title": "Archivos en (Raíz)",
|
||||||
|
"files_in": "Archivos en",
|
||||||
"delete_files": "Eliminar archivos",
|
"delete_files": "Eliminar archivos",
|
||||||
"delete_selected_files_title": "Eliminar archivos seleccionados",
|
"delete_selected_files_title": "Eliminar archivos seleccionados",
|
||||||
"delete_files_message": "¿Está seguro de que desea eliminar los archivos seleccionados?",
|
"delete_files_message": "¿Está seguro de que desea eliminar los archivos seleccionados?",
|
||||||
@@ -257,9 +336,9 @@ const translations = {
|
|||||||
"move_files_message": "Seleccione una carpeta destino para mover los archivos seleccionados:",
|
"move_files_message": "Seleccione una carpeta destino para mover los archivos seleccionados:",
|
||||||
"move": "Mover",
|
"move": "Mover",
|
||||||
"extract_zip_button": "Extraer Zip",
|
"extract_zip_button": "Extraer Zip",
|
||||||
"download_zip_title": "Descargar archivos seleccionados en Zip",
|
"download_zip_title": "Descargar archivos seleccionados en un Zip",
|
||||||
"download_zip_prompt": "Ingrese un nombre para el archivo Zip:",
|
"download_zip_prompt": "Ingrese un nombre para el archivo Zip:",
|
||||||
"zip_placeholder": "archivos.zip",
|
"zip_placeholder": "files.zip",
|
||||||
|
|
||||||
// Login Form keys:
|
// Login Form keys:
|
||||||
"login": "Iniciar sesión",
|
"login": "Iniciar sesión",
|
||||||
@@ -277,6 +356,13 @@ const translations = {
|
|||||||
"create_new_user_title": "Crear nuevo usuario",
|
"create_new_user_title": "Crear nuevo usuario",
|
||||||
"username": "Usuario:",
|
"username": "Usuario:",
|
||||||
"password": "Contraseña:",
|
"password": "Contraseña:",
|
||||||
|
"enter_password": "Contraseña",
|
||||||
|
"preparing_download": "Preparando su descarga...",
|
||||||
|
"download_file": "Descargar Archivo",
|
||||||
|
"confirm_or_change_filename": "Confirme o cambie el nombre del archivo a descargar:",
|
||||||
|
"filename": "Nombre de archivo",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"download": "Descargar",
|
||||||
"grant_admin": "Otorgar acceso de administrador",
|
"grant_admin": "Otorgar acceso de administrador",
|
||||||
"save_user": "Guardar usuario",
|
"save_user": "Guardar usuario",
|
||||||
|
|
||||||
@@ -289,29 +375,110 @@ const translations = {
|
|||||||
"rename_file_title": "Renombrar archivo",
|
"rename_file_title": "Renombrar archivo",
|
||||||
"rename_file_placeholder": "Ingrese el nuevo nombre del archivo",
|
"rename_file_placeholder": "Ingrese el nuevo nombre del archivo",
|
||||||
|
|
||||||
|
// Folder Share
|
||||||
|
"share_folder": "Compartir carpeta",
|
||||||
|
"allow_uploads": "Permitir cargas",
|
||||||
|
"share_link_generated": "Enlace para compartir generado",
|
||||||
|
"error_generating_share_link": "Error al generar el enlace para compartir",
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
"folder_share": "Compartir carpeta",
|
||||||
|
|
||||||
// Custom Confirm Modal keys:
|
// Custom Confirm Modal keys:
|
||||||
"yes": "Sí",
|
"yes": "Sí",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
"unsaved_changes_confirm": "Tiene cambios sin guardar. ¿Está seguro de que desea cerrar sin guardar?",
|
||||||
"delete": "Eliminar",
|
"delete": "Eliminar",
|
||||||
"download": "Descargar",
|
"download": "Descargar",
|
||||||
"upload": "Cargar",
|
"upload": "Cargar",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"extract": "Extraer",
|
"extract": "Extraer",
|
||||||
|
"user": "Usuario:",
|
||||||
|
"unknown_error": "Error desconocido",
|
||||||
|
"link_copied": "Enlace copiado al portapapeles",
|
||||||
|
"minutes": "minutos",
|
||||||
|
"hours": "horas",
|
||||||
|
"days": "días",
|
||||||
|
"weeks": "semanas",
|
||||||
|
"months": "meses",
|
||||||
|
"seconds": "segundos",
|
||||||
|
|
||||||
// Dark Mode Toggle
|
// Dark Mode Toggle
|
||||||
"dark_mode_toggle": "Modo oscuro"
|
"dark_mode_toggle": "Modo oscuro",
|
||||||
|
"light_mode_toggle": "Modo claro",
|
||||||
|
|
||||||
|
// NEW KEYS ADDED FOR ADMIN, USER PANELS, AND TOTP MODALS:
|
||||||
|
"admin_panel": "Panel de Administración",
|
||||||
|
"user_panel": "Panel de Usuario",
|
||||||
|
"totp_settings": "Configuración TOTP",
|
||||||
|
"enable_totp": "Activar TOTP",
|
||||||
|
"language": "Idioma",
|
||||||
|
"select_language": "Seleccionar idioma",
|
||||||
|
"english": "Inglés",
|
||||||
|
"spanish": "Español",
|
||||||
|
"french": "Francés",
|
||||||
|
"german": "Alemán",
|
||||||
|
"use_totp_code_instead": "Usar código TOTP en su lugar",
|
||||||
|
"submit_recovery_code": "Enviar código de recuperación",
|
||||||
|
"please_enter_recovery_code": "Por favor, ingrese su código de recuperación.",
|
||||||
|
"recovery_code_verification_failed": "La verificación del código de recuperación falló",
|
||||||
|
"error_verifying_recovery_code": "Error al verificar el código de recuperación",
|
||||||
|
"totp_verification_failed": "La verificación TOTP falló",
|
||||||
|
"error_verifying_totp_code": "Error al verificar el código TOTP",
|
||||||
|
"totp_setup": "Configuración TOTP",
|
||||||
|
"scan_qr_code": "Escanee este código QR con su aplicación de autenticación.",
|
||||||
|
"enter_totp_confirmation": "Ingrese el código de 6 dígitos de su aplicación para confirmar la configuración:",
|
||||||
|
"confirm": "Confirmar",
|
||||||
|
"please_enter_valid_code": "Por favor, ingrese un código válido de 6 dígitos.",
|
||||||
|
"totp_enabled_successfully": "TOTP activado con éxito.",
|
||||||
|
"error_generating_recovery_code": "Error al generar el código de recuperación",
|
||||||
|
"error_loading_qr_code": "Error al cargar el código QR.",
|
||||||
|
"error_disabling_totp_setting": "Error al desactivar la configuración TOTP",
|
||||||
|
"user_management": "Gestión de Usuarios",
|
||||||
|
"add_user": "Agregar usuario",
|
||||||
|
"remove_user": "Eliminar usuario",
|
||||||
|
"user_permissions": "Permisos de Usuario",
|
||||||
|
"oidc_configuration": "Configuración OIDC",
|
||||||
|
"oidc_provider_url": "URL del Proveedor OIDC",
|
||||||
|
"oidc_client_id": "ID del Cliente OIDC",
|
||||||
|
"oidc_client_secret": "Secreto del Cliente OIDC",
|
||||||
|
"oidc_redirect_uri": "URI de Redirección OIDC",
|
||||||
|
"global_totp_settings": "Configuración Global TOTP",
|
||||||
|
"global_otpauth_url": "URL Global OTPAuth",
|
||||||
|
"login_options": "Opciones de inicio de sesión",
|
||||||
|
"disable_login_form": "Desactivar formulario de inicio de sesión",
|
||||||
|
"disable_basic_http_auth": "Desactivar autenticación HTTP básica",
|
||||||
|
"disable_oidc_login": "Desactivar inicio de sesión OIDC",
|
||||||
|
"save_settings": "Guardar configuración",
|
||||||
|
"at_least_one_login_method": "Al menos un método de inicio de sesión debe permanecer habilitado.",
|
||||||
|
"settings_updated_successfully": "Configuración actualizada con éxito.",
|
||||||
|
"error_updating_settings": "Error al actualizar la configuración",
|
||||||
|
"user_permissions_updated_successfully": "Permisos de usuario actualizados con éxito.",
|
||||||
|
"error_updating_permissions": "Error al actualizar los permisos",
|
||||||
|
"no_users_found": "No se encontraron usuarios.",
|
||||||
|
"user_folder_only": "Solo carpeta de usuario",
|
||||||
|
"read_only": "Solo lectura",
|
||||||
|
"disable_upload": "Desactivar carga",
|
||||||
|
"error_loading_users": "Error al cargar usuarios",
|
||||||
|
"save_permissions": "Guardar permisos",
|
||||||
|
"your_recovery_code": "Su código de recuperación",
|
||||||
|
"please_save_recovery_code": "Por favor, guarde este código de forma segura. No se mostrará de nuevo y solo podrá usarse una vez.",
|
||||||
|
"ok": "OK"
|
||||||
},
|
},
|
||||||
fr: { /* French translations */
|
fr: {
|
||||||
"please_log_in_to_continue": "Veuillez vous connecter pour continuer.",
|
"please_log_in_to_continue": "Veuillez vous connecter pour continuer.",
|
||||||
"no_files_selected": "Aucun fichier sélectionné.",
|
"no_files_selected": "Aucun fichier sélectionné.",
|
||||||
"confirm_delete_files": "Êtes-vous sûr de vouloir supprimer {count} fichier(s) sélectionné(s) ?",
|
"confirm_delete_files": "Êtes-vous sûr de vouloir supprimer {count} fichier(s) sélectionné(s) ?",
|
||||||
"element_not_found": "Élément avec l'id \"{id}\" non trouvé.",
|
"element_not_found": "Élément avec l'id \"{id}\" non trouvé.",
|
||||||
"search_placeholder": "Rechercher des fichiers ou un tag...",
|
"search_placeholder": "Rechercher des fichiers, des balises et l'uploader...",
|
||||||
|
"search_placeholder_advanced": "Recherche avancée : fichiers, balises, uploader et contenu...",
|
||||||
|
"basic_search_tooltip": "Recherche basique : rechercher par nom de fichier, balises et uploader.",
|
||||||
|
"advanced_search_tooltip": "Recherche avancée : inclut le contenu du fichier, en plus du nom, des balises et de l'uploader.",
|
||||||
"file_name": "Nom du fichier",
|
"file_name": "Nom du fichier",
|
||||||
"date_modified": "Date de modification",
|
"date_modified": "Date de modification",
|
||||||
"upload_date": "Date de téléchargement",
|
"upload_date": "Date de téléchargement",
|
||||||
"file_size": "Taille du fichier",
|
"file_size": "Taille du fichier",
|
||||||
"uploader": "Téléversé par",
|
"uploader": "Uploader",
|
||||||
"enter_totp_code": "Entrez le code TOTP",
|
"enter_totp_code": "Entrez le code TOTP",
|
||||||
"use_recovery_code_instead": "Utilisez le code de récupération à la place",
|
"use_recovery_code_instead": "Utilisez le code de récupération à la place",
|
||||||
"enter_recovery_code": "Entrez le code de récupération",
|
"enter_recovery_code": "Entrez le code de récupération",
|
||||||
@@ -321,29 +488,28 @@ const translations = {
|
|||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"close": "Fermer",
|
"close": "Fermer",
|
||||||
"no_files_found": "Aucun fichier trouvé.",
|
"no_files_found": "Aucun fichier trouvé.",
|
||||||
"switch_to_table_view": "Passer à la vue tableau",
|
"switch_to_table_view": "Passer en vue tableau",
|
||||||
"switch_to_gallery_view": "Passer à la vue galerie",
|
"switch_to_gallery_view": "Passer en vue galerie",
|
||||||
"share_file": "Partager le fichier",
|
"share_file": "Partager le fichier",
|
||||||
"set_expiration": "Définir l'expiration :",
|
"set_expiration": "Définir l'expiration :",
|
||||||
"password_optional": "Mot de passe (facultatif) :",
|
"password_optional": "Mot de passe (facultatif) :",
|
||||||
"generate_share_link": "Générer un lien de partage",
|
"generate_share_link": "Générer le lien de partage",
|
||||||
"shareable_link": "Lien partageable :",
|
"shareable_link": "Lien partageable :",
|
||||||
"copy_link": "Copier le lien",
|
"copy_link": "Copier le lien",
|
||||||
"tag_file": "Marquer le fichier",
|
"tag_file": "Étiqueter le fichier",
|
||||||
"tag_name": "Nom du tag :",
|
"tag_name": "Nom de l'étiquette :",
|
||||||
"tag_color": "Couleur du tag :",
|
"tag_color": "Couleur de l'étiquette :",
|
||||||
"save_tag": "Enregistrer le tag",
|
"save_tag": "Enregistrer l'étiquette",
|
||||||
"files_in": "Fichiers dans",
|
|
||||||
"light_mode": "Mode clair",
|
"light_mode": "Mode clair",
|
||||||
"dark_mode": "Mode sombre",
|
"dark_mode": "Mode sombre",
|
||||||
"upload_instruction": "Déposez vos fichiers/dossiers ici ou cliquez sur 'Choisir des fichiers'",
|
"upload_instruction": "Déposez des fichiers/dossiers ici ou cliquez sur 'Choisir des fichiers'",
|
||||||
"no_files_selected_default": "Aucun fichier sélectionné",
|
"no_files_selected_default": "Aucun fichier sélectionné",
|
||||||
"choose_files": "Choisir des fichiers",
|
"choose_files": "Choisir des fichiers",
|
||||||
"delete_selected": "Supprimer la sélection",
|
"delete_selected": "Supprimer la sélection",
|
||||||
"copy_selected": "Copier la sélection",
|
"copy_selected": "Copier la sélection",
|
||||||
"move_selected": "Déplacer la sélection",
|
"move_selected": "Déplacer la sélection",
|
||||||
"tag_selected": "Marquer la sélection",
|
"tag_selected": "Étiqueter la sélection",
|
||||||
"download_zip": "Télécharger en Zip",
|
"download_zip": "Télécharger le Zip",
|
||||||
"extract_zip": "Extraire le Zip",
|
"extract_zip": "Extraire le Zip",
|
||||||
"preview": "Aperçu",
|
"preview": "Aperçu",
|
||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
@@ -354,7 +520,7 @@ const translations = {
|
|||||||
// Additional keys for HTML translations:
|
// Additional keys for HTML translations:
|
||||||
"title": "FileRise",
|
"title": "FileRise",
|
||||||
"header_title": "FileRise",
|
"header_title": "FileRise",
|
||||||
"logout": "Se déconnecter",
|
"logout": "Déconnexion",
|
||||||
"change_password": "Changer le mot de passe",
|
"change_password": "Changer le mot de passe",
|
||||||
"restore_text": "Restaurer ou",
|
"restore_text": "Restaurer ou",
|
||||||
"delete_text": "Supprimer les éléments de la corbeille",
|
"delete_text": "Supprimer les éléments de la corbeille",
|
||||||
@@ -376,7 +542,7 @@ const translations = {
|
|||||||
"rename_folder_placeholder": "Entrez le nouveau nom du dossier",
|
"rename_folder_placeholder": "Entrez le nouveau nom du dossier",
|
||||||
"delete_folder": "Supprimer le dossier",
|
"delete_folder": "Supprimer le dossier",
|
||||||
"delete_folder_title": "Supprimer le dossier",
|
"delete_folder_title": "Supprimer le dossier",
|
||||||
"delete_folder_message": "Êtes-vous sûr de vouloir supprimer ce dossier ?",
|
"delete_folder_message": "Êtes-vous sûr de vouloir supprimer ce dossier ?",
|
||||||
"folder_help": "Aide des dossiers",
|
"folder_help": "Aide des dossiers",
|
||||||
"folder_help_item_1": "Cliquez sur un dossier dans l'arborescence pour voir ses fichiers.",
|
"folder_help_item_1": "Cliquez sur un dossier dans l'arborescence pour voir ses fichiers.",
|
||||||
"folder_help_item_2": "Utilisez [-] pour réduire et [+] pour développer les dossiers.",
|
"folder_help_item_2": "Utilisez [-] pour réduire et [+] pour développer les dossiers.",
|
||||||
@@ -385,25 +551,26 @@ const translations = {
|
|||||||
|
|
||||||
// File List keys:
|
// File List keys:
|
||||||
"file_list_title": "Fichiers dans (Racine)",
|
"file_list_title": "Fichiers dans (Racine)",
|
||||||
|
"files_in": "Fichiers dans",
|
||||||
"delete_files": "Supprimer les fichiers",
|
"delete_files": "Supprimer les fichiers",
|
||||||
"delete_selected_files_title": "Supprimer les fichiers sélectionnés",
|
"delete_selected_files_title": "Supprimer les fichiers sélectionnés",
|
||||||
"delete_files_message": "Êtes-vous sûr de vouloir supprimer les fichiers sélectionnés ?",
|
"delete_files_message": "Êtes-vous sûr de vouloir supprimer les fichiers sélectionnés ?",
|
||||||
"copy_files": "Copier les fichiers",
|
"copy_files": "Copier les fichiers",
|
||||||
"copy_files_title": "Copier les fichiers sélectionnés",
|
"copy_files_title": "Copier les fichiers sélectionnés",
|
||||||
"copy_files_message": "Sélectionnez un dossier de destination pour copier les fichiers sélectionnés :",
|
"copy_files_message": "Sélectionnez un dossier de destination pour copier les fichiers sélectionnés :",
|
||||||
"move_files": "Déplacer les fichiers",
|
"move_files": "Déplacer les fichiers",
|
||||||
"move_files_title": "Déplacer les fichiers sélectionnés",
|
"move_files_title": "Déplacer les fichiers sélectionnés",
|
||||||
"move_files_message": "Sélectionnez un dossier de destination pour déplacer les fichiers sélectionnés :",
|
"move_files_message": "Sélectionnez un dossier de destination pour déplacer les fichiers sélectionnés :",
|
||||||
"move": "Déplacer",
|
"move": "Déplacer",
|
||||||
"extract_zip_button": "Extraire le Zip",
|
"extract_zip_button": "Extraire le Zip",
|
||||||
"download_zip_title": "Télécharger les fichiers sélectionnés en Zip",
|
"download_zip_title": "Télécharger les fichiers sélectionnés en Zip",
|
||||||
"download_zip_prompt": "Entrez un nom pour le fichier Zip :",
|
"download_zip_prompt": "Entrez un nom pour le fichier Zip :",
|
||||||
"zip_placeholder": "fichiers.zip",
|
"zip_placeholder": "files.zip",
|
||||||
|
|
||||||
// Login Form keys:
|
// Login Form keys:
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"remember_me": "Se souvenir de moi",
|
"remember_me": "Se souvenir de moi",
|
||||||
"login_oidc": "Connexion avec OIDC",
|
"login_oidc": "Se connecter avec OIDC",
|
||||||
"basic_http_login": "Utiliser l'authentification HTTP basique",
|
"basic_http_login": "Utiliser l'authentification HTTP basique",
|
||||||
|
|
||||||
// Change Password keys:
|
// Change Password keys:
|
||||||
@@ -414,43 +581,131 @@ const translations = {
|
|||||||
|
|
||||||
// Add User keys:
|
// Add User keys:
|
||||||
"create_new_user_title": "Créer un nouvel utilisateur",
|
"create_new_user_title": "Créer un nouvel utilisateur",
|
||||||
"username": "Nom d'utilisateur :",
|
"username": "Nom d'utilisateur :",
|
||||||
"password": "Mot de passe :",
|
"password": "Mot de passe :",
|
||||||
"grant_admin": "Accorder les droits d'administrateur",
|
"enter_password": "Mot de passe",
|
||||||
|
"preparing_download": "Préparation de votre téléchargement...",
|
||||||
|
"download_file": "Télécharger le fichier",
|
||||||
|
"confirm_or_change_filename": "Confirmez ou modifiez le nom du fichier à télécharger :",
|
||||||
|
"filename": "Nom du fichier",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"download": "Télécharger",
|
||||||
|
"grant_admin": "Accorder l'accès administrateur",
|
||||||
"save_user": "Enregistrer l'utilisateur",
|
"save_user": "Enregistrer l'utilisateur",
|
||||||
|
|
||||||
// Remove User keys:
|
// Remove User keys:
|
||||||
"remove_user_title": "Supprimer l'utilisateur",
|
"remove_user_title": "Supprimer un utilisateur",
|
||||||
"select_user_remove": "Sélectionnez un utilisateur à supprimer :",
|
"select_user_remove": "Sélectionnez un utilisateur à supprimer :",
|
||||||
"delete_user": "Supprimer l'utilisateur",
|
"delete_user": "Supprimer l'utilisateur",
|
||||||
|
|
||||||
// Rename File keys:
|
// Rename File keys:
|
||||||
"rename_file_title": "Renommer le fichier",
|
"rename_file_title": "Renommer le fichier",
|
||||||
"rename_file_placeholder": "Entrez le nouveau nom du fichier",
|
"rename_file_placeholder": "Entrez le nouveau nom du fichier",
|
||||||
|
|
||||||
|
// Folder Share
|
||||||
|
"share_folder": "Partager le dossier",
|
||||||
|
"allow_uploads": "Autoriser les téléchargements",
|
||||||
|
"share_link_generated": "Lien de partage généré",
|
||||||
|
"error_generating_share_link": "Erreur lors de la génération du lien de partage",
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
"folder_share": "Partager le dossier",
|
||||||
|
|
||||||
// Custom Confirm Modal keys:
|
// Custom Confirm Modal keys:
|
||||||
"yes": "Oui",
|
"yes": "Oui",
|
||||||
"no": "Non",
|
"no": "Non",
|
||||||
|
"unsaved_changes_confirm": "Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir fermer sans enregistrer ?",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"download": "Télécharger",
|
"download": "Télécharger",
|
||||||
"upload": "Téléverser",
|
"upload": "Téléverser",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"extract": "Extraire",
|
"extract": "Extraire",
|
||||||
|
"user": "Utilisateur :",
|
||||||
|
"unknown_error": "Erreur inconnue",
|
||||||
|
"link_copied": "Lien copié dans le presse-papiers",
|
||||||
|
"minutes": "minutes",
|
||||||
|
"hours": "heures",
|
||||||
|
"days": "jours",
|
||||||
|
"weeks": "semaines",
|
||||||
|
"months": "mois",
|
||||||
|
"seconds": "secondes",
|
||||||
|
|
||||||
// Dark Mode Toggle
|
// Dark Mode Toggle
|
||||||
"dark_mode_toggle": "Mode sombre"
|
"dark_mode_toggle": "Mode sombre",
|
||||||
|
"light_mode_toggle": "Mode clair",
|
||||||
|
|
||||||
|
// NEW KEYS ADDED FOR ADMIN, USER PANELS, AND TOTP MODALS:
|
||||||
|
"admin_panel": "Panneau d'administration",
|
||||||
|
"user_panel": "Panneau utilisateur",
|
||||||
|
"totp_settings": "Paramètres TOTP",
|
||||||
|
"enable_totp": "Activer TOTP",
|
||||||
|
"language": "Langue",
|
||||||
|
"select_language": "Sélectionnez la langue",
|
||||||
|
"english": "Anglais",
|
||||||
|
"spanish": "Espagnol",
|
||||||
|
"french": "Français",
|
||||||
|
"german": "Allemand",
|
||||||
|
"use_totp_code_instead": "Utiliser le code TOTP à la place",
|
||||||
|
"submit_recovery_code": "Soumettre le code de récupération",
|
||||||
|
"please_enter_recovery_code": "Veuillez entrer votre code de récupération.",
|
||||||
|
"recovery_code_verification_failed": "La vérification du code de récupération a échoué",
|
||||||
|
"error_verifying_recovery_code": "Erreur lors de la vérification du code de récupération",
|
||||||
|
"totp_verification_failed": "La vérification TOTP a échoué",
|
||||||
|
"error_verifying_totp_code": "Erreur lors de la vérification du code TOTP",
|
||||||
|
"totp_setup": "Configuration TOTP",
|
||||||
|
"scan_qr_code": "Scannez ce QR code avec votre application d'authentification.",
|
||||||
|
"enter_totp_confirmation": "Entrez le code à 6 chiffres de votre application pour confirmer la configuration :",
|
||||||
|
"confirm": "Confirmer",
|
||||||
|
"please_enter_valid_code": "Veuillez entrer un code valide à 6 chiffres.",
|
||||||
|
"totp_enabled_successfully": "TOTP activé avec succès.",
|
||||||
|
"error_generating_recovery_code": "Erreur lors de la génération du code de récupération",
|
||||||
|
"error_loading_qr_code": "Erreur lors du chargement du QR code.",
|
||||||
|
"error_disabling_totp_setting": "Erreur lors de la désactivation des paramètres TOTP",
|
||||||
|
"user_management": "Gestion des utilisateurs",
|
||||||
|
"add_user": "Ajouter un utilisateur",
|
||||||
|
"remove_user": "Supprimer un utilisateur",
|
||||||
|
"user_permissions": "Permissions des utilisateurs",
|
||||||
|
"oidc_configuration": "Configuration OIDC",
|
||||||
|
"oidc_provider_url": "URL du fournisseur OIDC",
|
||||||
|
"oidc_client_id": "ID du client OIDC",
|
||||||
|
"oidc_client_secret": "Secret du client OIDC",
|
||||||
|
"oidc_redirect_uri": "URI de redirection OIDC",
|
||||||
|
"global_totp_settings": "Paramètres globaux TOTP",
|
||||||
|
"global_otpauth_url": "URL globale OTPAuth",
|
||||||
|
"login_options": "Options de connexion",
|
||||||
|
"disable_login_form": "Désactiver le formulaire de connexion",
|
||||||
|
"disable_basic_http_auth": "Désactiver l'authentification HTTP basique",
|
||||||
|
"disable_oidc_login": "Désactiver la connexion OIDC",
|
||||||
|
"save_settings": "Enregistrer les paramètres",
|
||||||
|
"at_least_one_login_method": "Au moins une méthode de connexion doit rester activée.",
|
||||||
|
"settings_updated_successfully": "Paramètres mis à jour avec succès.",
|
||||||
|
"error_updating_settings": "Erreur lors de la mise à jour des paramètres",
|
||||||
|
"user_permissions_updated_successfully": "Permissions des utilisateurs mises à jour avec succès.",
|
||||||
|
"error_updating_permissions": "Erreur lors de la mise à jour des permissions",
|
||||||
|
"no_users_found": "Aucun utilisateur trouvé.",
|
||||||
|
"user_folder_only": "Uniquement le dossier utilisateur",
|
||||||
|
"read_only": "Lecture seule",
|
||||||
|
"disable_upload": "Désactiver le téléchargement",
|
||||||
|
"error_loading_users": "Erreur lors du chargement des utilisateurs",
|
||||||
|
"save_permissions": "Enregistrer les permissions",
|
||||||
|
"your_recovery_code": "Votre code de récupération",
|
||||||
|
"please_save_recovery_code": "Veuillez sauvegarder ce code en toute sécurité. Il ne sera plus affiché et ne pourra être utilisé qu'une seule fois.",
|
||||||
|
"ok": "OK"
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
"please_log_in_to_continue": "Bitte melden Sie sich an, um fortzufahren.",
|
"please_log_in_to_continue": "Bitte melden Sie sich an, um fortzufahren.",
|
||||||
"no_files_selected": "Keine Dateien ausgewählt.",
|
"no_files_selected": "Keine Dateien ausgewählt.",
|
||||||
"confirm_delete_files": "Sind Sie sicher, dass Sie {count} ausgewählte Datei(en) löschen möchten?",
|
"confirm_delete_files": "Sind Sie sicher, dass Sie {count} ausgewählte Datei(en) löschen möchten?",
|
||||||
"element_not_found": "Element mit der ID \"{id}\" wurde nicht gefunden.",
|
"element_not_found": "Element mit der ID \"{id}\" wurde nicht gefunden.",
|
||||||
"search_placeholder": "Suche nach Dateien oder Tags...",
|
"search_placeholder": "Dateien, Tags und Uploader suchen...",
|
||||||
|
"search_placeholder_advanced": "Erweiterte Suche: Dateien, Tags, Uploader & Inhalt...",
|
||||||
|
"basic_search_tooltip": "Einfache Suche: Nach Dateiname, Tags und Uploader suchen.",
|
||||||
|
"advanced_search_tooltip": "Erweiterte Suche: Beinhaltet Dateiinhalte zusätzlich zum Dateinamen, Tags und Uploader.",
|
||||||
"file_name": "Dateiname",
|
"file_name": "Dateiname",
|
||||||
"date_modified": "Änderungsdatum",
|
"date_modified": "Änderungsdatum",
|
||||||
"upload_date": "Hochladedatum",
|
"upload_date": "Hochladedatum",
|
||||||
"file_size": "Dateigröße",
|
"file_size": "Dateigröße",
|
||||||
"uploader": "Hochgeladen von",
|
"uploader": "Uploader",
|
||||||
"enter_totp_code": "Geben Sie den TOTP-Code ein",
|
"enter_totp_code": "Geben Sie den TOTP-Code ein",
|
||||||
"use_recovery_code_instead": "Verwenden Sie stattdessen den Wiederherstellungscode",
|
"use_recovery_code_instead": "Verwenden Sie stattdessen den Wiederherstellungscode",
|
||||||
"enter_recovery_code": "Geben Sie den Wiederherstellungscode ein",
|
"enter_recovery_code": "Geben Sie den Wiederherstellungscode ein",
|
||||||
@@ -472,7 +727,6 @@ const translations = {
|
|||||||
"tag_name": "Tagname:",
|
"tag_name": "Tagname:",
|
||||||
"tag_color": "Tagfarbe:",
|
"tag_color": "Tagfarbe:",
|
||||||
"save_tag": "Tag speichern",
|
"save_tag": "Tag speichern",
|
||||||
"files_in": "Dateien in",
|
|
||||||
"light_mode": "Heller Modus",
|
"light_mode": "Heller Modus",
|
||||||
"dark_mode": "Dunkler Modus",
|
"dark_mode": "Dunkler Modus",
|
||||||
"upload_instruction": "Ziehen Sie Dateien/Ordner hierher oder klicken Sie auf 'Dateien auswählen'",
|
"upload_instruction": "Ziehen Sie Dateien/Ordner hierher oder klicken Sie auf 'Dateien auswählen'",
|
||||||
@@ -488,7 +742,7 @@ const translations = {
|
|||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"rename": "Umbenennen",
|
"rename": "Umbenennen",
|
||||||
"trash_empty": "Papierkorb ist leer.",
|
"trash_empty": "Papierkorb ist leer.",
|
||||||
"no_trash_selected": "Keine Elemente im Papierkorb für die Wiederherstellung ausgewählt.",
|
"no_trash_selected": "Keine Papierkorbeinträge zur Wiederherstellung ausgewählt.",
|
||||||
|
|
||||||
// Additional keys for HTML translations:
|
// Additional keys for HTML translations:
|
||||||
"title": "FileRise",
|
"title": "FileRise",
|
||||||
@@ -517,13 +771,15 @@ const translations = {
|
|||||||
"delete_folder_title": "Ordner löschen",
|
"delete_folder_title": "Ordner löschen",
|
||||||
"delete_folder_message": "Sind Sie sicher, dass Sie diesen Ordner löschen möchten?",
|
"delete_folder_message": "Sind Sie sicher, dass Sie diesen Ordner löschen möchten?",
|
||||||
"folder_help": "Ordnerhilfe",
|
"folder_help": "Ordnerhilfe",
|
||||||
"folder_help_item_1": "Klicken Sie auf einen Ordner, um dessen Dateien anzuzeigen.",
|
"folder_help_item_1": "Klicken Sie auf einen Ordner im Baum, um dessen Dateien anzuzeigen.",
|
||||||
"folder_help_item_2": "Verwenden Sie [-] um zu minimieren und [+] um zu erweitern.",
|
"folder_help_item_2": "Verwenden Sie [-] zum Einklappen und [+] zum Ausklappen der Ordner.",
|
||||||
"folder_help_item_3": "Klicken Sie auf \"Ordner erstellen\", um einen Unterordner hinzuzufügen.",
|
"folder_help_item_3": "Wählen Sie einen Ordner aus und klicken Sie auf \"Ordner erstellen\", um einen Unterordner hinzuzufügen.",
|
||||||
"folder_help_item_4": "Um einen Ordner umzubenennen oder zu löschen, wählen Sie ihn und klicken Sie auf die entsprechende Schaltfläche.",
|
"folder_help_item_4": "Um einen Ordner umzubenennen oder zu löschen, wählen Sie ihn aus und klicken Sie auf den entsprechenden Button.",
|
||||||
|
|
||||||
// File List keys:
|
// File List keys:
|
||||||
|
"actions": "Aktionen",
|
||||||
"file_list_title": "Dateien in (Root)",
|
"file_list_title": "Dateien in (Root)",
|
||||||
|
"files_in": "Dateien in",
|
||||||
"delete_files": "Dateien löschen",
|
"delete_files": "Dateien löschen",
|
||||||
"delete_selected_files_title": "Ausgewählte Dateien löschen",
|
"delete_selected_files_title": "Ausgewählte Dateien löschen",
|
||||||
"delete_files_message": "Sind Sie sicher, dass Sie die ausgewählten Dateien löschen möchten?",
|
"delete_files_message": "Sind Sie sicher, dass Sie die ausgewählten Dateien löschen möchten?",
|
||||||
@@ -537,7 +793,14 @@ const translations = {
|
|||||||
"extract_zip_button": "Zip entpacken",
|
"extract_zip_button": "Zip entpacken",
|
||||||
"download_zip_title": "Ausgewählte Dateien als Zip herunterladen",
|
"download_zip_title": "Ausgewählte Dateien als Zip herunterladen",
|
||||||
"download_zip_prompt": "Geben Sie einen Namen für die Zip-Datei ein:",
|
"download_zip_prompt": "Geben Sie einen Namen für die Zip-Datei ein:",
|
||||||
"zip_placeholder": "dateien.zip",
|
"zip_placeholder": "files.zip",
|
||||||
|
"share": "Teilen",
|
||||||
|
"total_files": "Gesamtanzahl",
|
||||||
|
"total_size": "Gesamtgröße",
|
||||||
|
"prev": "Zurück",
|
||||||
|
"next": "Weiter",
|
||||||
|
"page": "Seite",
|
||||||
|
"of": "von",
|
||||||
|
|
||||||
// Login Form keys:
|
// Login Form keys:
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
@@ -555,39 +818,127 @@ const translations = {
|
|||||||
"create_new_user_title": "Neuen Benutzer erstellen",
|
"create_new_user_title": "Neuen Benutzer erstellen",
|
||||||
"username": "Benutzername:",
|
"username": "Benutzername:",
|
||||||
"password": "Passwort:",
|
"password": "Passwort:",
|
||||||
|
"enter_password": "Passwort",
|
||||||
|
"preparing_download": "Bereite Ihren Download vor...",
|
||||||
|
"download_file": "Datei herunterladen",
|
||||||
|
"confirm_or_change_filename": "Bestätigen oder ändern Sie den Dateinamen zum Download:",
|
||||||
|
"filename": "Dateiname",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"download": "Herunterladen",
|
||||||
"grant_admin": "Admin-Rechte vergeben",
|
"grant_admin": "Admin-Rechte vergeben",
|
||||||
"save_user": "Benutzer speichern",
|
"save_user": "Benutzer speichern",
|
||||||
|
|
||||||
// Remove User keys:
|
// Remove User keys:
|
||||||
"remove_user_title": "Benutzer entfernen",
|
"remove_user_title": "Benutzer entfernen",
|
||||||
"select_user_remove": "Wählen Sie einen Benutzer zum Entfernen:",
|
"select_user_remove": "Wählen Sie einen Benutzer zum Entfernen aus:",
|
||||||
"delete_user": "Benutzer löschen",
|
"delete_user": "Benutzer löschen",
|
||||||
|
|
||||||
// Rename File keys:
|
// Rename File keys:
|
||||||
"rename_file_title": "Datei umbenennen",
|
"rename_file_title": "Datei umbenennen",
|
||||||
"rename_file_placeholder": "Neuen Dateinamen eingeben",
|
"rename_file_placeholder": "Geben Sie den neuen Dateinamen ein",
|
||||||
|
|
||||||
|
// Folder Share
|
||||||
|
"share_folder": "Ordner teilen",
|
||||||
|
"allow_uploads": "Uploads erlauben",
|
||||||
|
"share_link_generated": "Freigabelink generiert",
|
||||||
|
"error_generating_share_link": "Fehler beim Generieren des Freigabelinks",
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
"folder_share": "Ordner teilen",
|
||||||
|
|
||||||
// Custom Confirm Modal keys:
|
// Custom Confirm Modal keys:
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nein",
|
"no": "Nein",
|
||||||
|
"unsaved_changes_confirm": "Sie haben ungespeicherte Änderungen. Sind Sie sicher, dass Sie schließen möchten, ohne zu speichern?",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"download": "Herunterladen",
|
"download": "Herunterladen",
|
||||||
"upload": "Hochladen",
|
"upload": "Hochladen",
|
||||||
"copy": "Kopieren",
|
"copy": "Kopieren",
|
||||||
"extract": "Entpacken",
|
"extract": "Entpacken",
|
||||||
|
"user": "Benutzer:",
|
||||||
|
"unknown_error": "Unbekannter Fehler",
|
||||||
|
"link_copied": "Link in die Zwischenablage kopiert",
|
||||||
|
"minutes": "Minuten",
|
||||||
|
"hours": "Stunden",
|
||||||
|
"days": "Tage",
|
||||||
|
"weeks": "Wochen",
|
||||||
|
"months": "Monate",
|
||||||
|
"seconds": "Sekunden",
|
||||||
|
|
||||||
// Dark Mode Toggle
|
// Dark Mode Toggle
|
||||||
"dark_mode_toggle": "Dunkler Modus"
|
"dark_mode_toggle": "Dunkler Modus",
|
||||||
|
"light_mode_toggle": "Heller Modus",
|
||||||
|
|
||||||
|
// NEW KEYS ADDED FOR ADMIN, USER PANELS, AND TOTP MODALS:
|
||||||
|
"admin_panel": "Administrationsbereich",
|
||||||
|
"user_panel": "Benutzerbereich",
|
||||||
|
"trash_restore_delete": "Papierkorb wiederherstellen/löschen",
|
||||||
|
"totp_settings": "TOTP-Einstellungen",
|
||||||
|
"enable_totp": "TOTP aktivieren",
|
||||||
|
"language": "Sprache",
|
||||||
|
"select_language": "Sprache auswählen",
|
||||||
|
"english": "Englisch",
|
||||||
|
"spanish": "Spanisch",
|
||||||
|
"french": "Französisch",
|
||||||
|
"german": "Deutsch",
|
||||||
|
"use_totp_code_instead": "Stattdessen TOTP-Code verwenden",
|
||||||
|
"submit_recovery_code": "Wiederherstellungscode absenden",
|
||||||
|
"please_enter_recovery_code": "Bitte geben Sie Ihren Wiederherstellungscode ein.",
|
||||||
|
"recovery_code_verification_failed": "Überprüfung des Wiederherstellungscodes fehlgeschlagen",
|
||||||
|
"error_verifying_recovery_code": "Fehler bei der Überprüfung des Wiederherstellungscodes",
|
||||||
|
"totp_verification_failed": "TOTP-Überprüfung fehlgeschlagen",
|
||||||
|
"error_verifying_totp_code": "Fehler bei der Überprüfung des TOTP-Codes",
|
||||||
|
"totp_setup": "TOTP-Einrichtung",
|
||||||
|
"scan_qr_code": "Scannen Sie diesen QR-Code mit Ihrer Authenticator-App.",
|
||||||
|
"enter_totp_confirmation": "Geben Sie den 6-stelligen Code aus Ihrer App zur Bestätigung ein:",
|
||||||
|
"confirm": "Bestätigen",
|
||||||
|
"please_enter_valid_code": "Bitte geben Sie einen gültigen 6-stelligen Code ein.",
|
||||||
|
"totp_enabled_successfully": "TOTP wurde erfolgreich aktiviert.",
|
||||||
|
"error_generating_recovery_code": "Fehler beim Generieren des Wiederherstellungscodes",
|
||||||
|
"error_loading_qr_code": "Fehler beim Laden des QR-Codes.",
|
||||||
|
"error_disabling_totp_setting": "Fehler beim Deaktivieren der TOTP-Einstellungen",
|
||||||
|
"user_management": "Benutzerverwaltung",
|
||||||
|
"add_user": "Benutzer hinzufügen",
|
||||||
|
"remove_user": "Benutzer entfernen",
|
||||||
|
"user_permissions": "Benutzerberechtigungen",
|
||||||
|
"oidc_configuration": "OIDC-Konfiguration",
|
||||||
|
"oidc_provider_url": "OIDC-Anbieter-URL",
|
||||||
|
"oidc_client_id": "OIDC-Client-ID",
|
||||||
|
"oidc_client_secret": "OIDC-Client-Geheimnis",
|
||||||
|
"oidc_redirect_uri": "OIDC-Umleitungs-URI",
|
||||||
|
"global_totp_settings": "Globale TOTP-Einstellungen",
|
||||||
|
"global_otpauth_url": "Globale OTPAuth-URL",
|
||||||
|
"login_options": "Anmeldeoptionen",
|
||||||
|
"disable_login_form": "Anmeldeformular deaktivieren",
|
||||||
|
"disable_basic_http_auth": "HTTP-Basisauthentifizierung deaktivieren",
|
||||||
|
"disable_oidc_login": "OIDC-Anmeldung deaktivieren",
|
||||||
|
"save_settings": "Einstellungen speichern",
|
||||||
|
"at_least_one_login_method": "Mindestens eine Anmeldemethode muss aktiviert bleiben.",
|
||||||
|
"settings_updated_successfully": "Einstellungen wurden erfolgreich aktualisiert.",
|
||||||
|
"error_updating_settings": "Fehler beim Aktualisieren der Einstellungen",
|
||||||
|
"user_permissions_updated_successfully": "Benutzerberechtigungen wurden erfolgreich aktualisiert.",
|
||||||
|
"error_updating_permissions": "Fehler beim Aktualisieren der Berechtigungen",
|
||||||
|
"no_users_found": "Keine Benutzer gefunden.",
|
||||||
|
"user_folder_only": "Nur Benutzerordner",
|
||||||
|
"read_only": "Nur Lesen",
|
||||||
|
"disable_upload": "Upload deaktivieren",
|
||||||
|
"error_loading_users": "Fehler beim Laden der Benutzer",
|
||||||
|
"save_permissions": "Berechtigungen speichern",
|
||||||
|
"your_recovery_code": "Ihr Wiederherstellungscode",
|
||||||
|
"please_save_recovery_code": "Bitte speichern Sie diesen Code sicher. Er wird nicht erneut angezeigt und kann nur einmal verwendet werden.",
|
||||||
|
"ok": "OK",
|
||||||
|
"show": "Zeige",
|
||||||
|
"items_per_page": "elemente pro seite"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let currentLocale = 'en';
|
let currentLocale = 'en';
|
||||||
|
|
||||||
export function setLocale(locale) {
|
export function setLocale(locale) {
|
||||||
currentLocale = locale;
|
currentLocale = locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function t(key, placeholders) {
|
export function t(key, placeholders) {
|
||||||
const localeTranslations = translations[currentLocale] || {};
|
const localeTranslations = translations[currentLocale] || {};
|
||||||
let translation = localeTranslations[key] || key;
|
let translation = localeTranslations[key] || key;
|
||||||
if (placeholders) {
|
if (placeholders) {
|
||||||
@@ -596,9 +947,9 @@ const translations = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return translation;
|
return translation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyTranslations() {
|
export function applyTranslations() {
|
||||||
document.querySelectorAll('[data-i18n-key]').forEach(el => {
|
document.querySelectorAll('[data-i18n-key]').forEach(el => {
|
||||||
el.innerText = t(el.getAttribute('data-i18n-key'));
|
el.innerText = t(el.getAttribute('data-i18n-key'));
|
||||||
});
|
});
|
||||||
@@ -608,4 +959,4 @@ const translations = {
|
|||||||
document.querySelectorAll('[data-i18n-title]').forEach(el => {
|
document.querySelectorAll('[data-i18n-title]').forEach(el => {
|
||||||
el.setAttribute('title', t(el.getAttribute('data-i18n-title')));
|
el.setAttribute('title', t(el.getAttribute('data-i18n-title')));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,18 +128,18 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
if (darkModeToggle) {
|
if (darkModeToggle) {
|
||||||
darkModeToggle.textContent = document.body.classList.contains("dark-mode")
|
darkModeToggle.textContent = document.body.classList.contains("dark-mode")
|
||||||
? "Light Mode"
|
? t("light_mode")
|
||||||
: "Dark Mode";
|
: t("dark_mode");
|
||||||
|
|
||||||
darkModeToggle.addEventListener("click", function () {
|
darkModeToggle.addEventListener("click", function () {
|
||||||
if (document.body.classList.contains("dark-mode")) {
|
if (document.body.classList.contains("dark-mode")) {
|
||||||
document.body.classList.remove("dark-mode");
|
document.body.classList.remove("dark-mode");
|
||||||
localStorage.setItem("darkMode", "false");
|
localStorage.setItem("darkMode", "false");
|
||||||
darkModeToggle.textContent = "Dark Mode";
|
darkModeToggle.textContent = t("dark_mode");
|
||||||
} else {
|
} else {
|
||||||
document.body.classList.add("dark-mode");
|
document.body.classList.add("dark-mode");
|
||||||
localStorage.setItem("darkMode", "true");
|
localStorage.setItem("darkMode", "true");
|
||||||
darkModeToggle.textContent = "Light Mode";
|
darkModeToggle.textContent = t("light_mode");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ if (!is_array($data)) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve new header title, sanitize if necessary.
|
||||||
|
$headerTitle = isset($data['header_title']) ? trim($data['header_title']) : "";
|
||||||
|
|
||||||
// Validate and sanitize OIDC configuration.
|
// Validate and sanitize OIDC configuration.
|
||||||
$oidc = isset($data['oidc']) ? $data['oidc'] : [];
|
$oidc = isset($data['oidc']) ? $data['oidc'] : [];
|
||||||
$oidcProviderUrl = isset($oidc['providerUrl']) ? filter_var($oidc['providerUrl'], FILTER_SANITIZE_URL) : '';
|
$oidcProviderUrl = isset($oidc['providerUrl']) ? filter_var($oidc['providerUrl'], FILTER_SANITIZE_URL) : '';
|
||||||
@@ -54,8 +57,9 @@ $disableOIDCLogin = isset($data['disableOIDCLogin']) ? filter_var($data['disable
|
|||||||
// Retrieve the global OTPAuth URL (new field). If not provided, default to an empty string.
|
// Retrieve the global OTPAuth URL (new field). If not provided, default to an empty string.
|
||||||
$globalOtpauthUrl = isset($data['globalOtpauthUrl']) ? trim($data['globalOtpauthUrl']) : "";
|
$globalOtpauthUrl = isset($data['globalOtpauthUrl']) ? trim($data['globalOtpauthUrl']) : "";
|
||||||
|
|
||||||
// Prepare configuration array.
|
// Prepare configuration array including the header title.
|
||||||
$configUpdate = [
|
$configUpdate = [
|
||||||
|
'header_title' => $headerTitle, // New field for the header title
|
||||||
'oidc' => [
|
'oidc' => [
|
||||||
'providerUrl' => $oidcProviderUrl,
|
'providerUrl' => $oidcProviderUrl,
|
||||||
'clientId' => $oidcClientId,
|
'clientId' => $oidcClientId,
|
||||||
@@ -79,15 +83,10 @@ $encryptedContent = encryptData($plainTextConfig, $encryptionKey);
|
|||||||
|
|
||||||
// Attempt to write the new configuration.
|
// Attempt to write the new configuration.
|
||||||
if (file_put_contents($configFile, $encryptedContent, LOCK_EX) === false) {
|
if (file_put_contents($configFile, $encryptedContent, LOCK_EX) === false) {
|
||||||
// Log the error.
|
|
||||||
error_log("updateConfig.php: Initial write failed, attempting to delete the old configuration file.");
|
error_log("updateConfig.php: Initial write failed, attempting to delete the old configuration file.");
|
||||||
|
|
||||||
// Delete the old file.
|
|
||||||
if (file_exists($configFile)) {
|
if (file_exists($configFile)) {
|
||||||
unlink($configFile);
|
unlink($configFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try writing again.
|
|
||||||
if (file_put_contents($configFile, $encryptedContent, LOCK_EX) === false) {
|
if (file_put_contents($configFile, $encryptedContent, LOCK_EX) === false) {
|
||||||
error_log("updateConfig.php: Failed to write configuration even after deletion.");
|
error_log("updateConfig.php: Failed to write configuration even after deletion.");
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|||||||
Reference in New Issue
Block a user