Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1726f0160 | ||
|
|
bd1841b788 | ||
|
|
bde35d1d31 |
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,5 +1,58 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Changes 5/4/2025 v1.3.1
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
|
||||||
|
- **Added** a shared `.editor-close-btn` component for all modals:
|
||||||
|
- File Tags
|
||||||
|
- User Panel
|
||||||
|
- TOTP Login & Setup
|
||||||
|
- Change Password
|
||||||
|
- **Truncated** long filenames in the File Tags modal header using CSS `text-overflow: ellipsis`.
|
||||||
|
- **Resized** File Tags modal from 400px to 450px wide (with `max-width: 90vw` fallback).
|
||||||
|
- **Capped** User Panel height at 381px and hidden scrollbars to eliminate layout jumps on hover.
|
||||||
|
|
||||||
|
### HTML
|
||||||
|
|
||||||
|
- **Moved** `<div id="loginForm">…</div>` out of `.main-wrapper` so the login form can show independently of the app shell.
|
||||||
|
- **Added** `<div id="loadingOverlay"></div>` immediately inside `<body>` to cover the UI during auth checks.
|
||||||
|
- **Inserted** inline `<style>` in `<head>` to:
|
||||||
|
- Hide `.main-wrapper` by default.
|
||||||
|
- Style `#loadingOverlay` as a full-viewport white overlay.
|
||||||
|
|
||||||
|
- **Added** `addUserModal`, `removeUserModal` & `renameFileModal` modals to `style="display:none;"`
|
||||||
|
|
||||||
|
### `main.js`
|
||||||
|
|
||||||
|
- **Extracted** `initializeApp()` helper to centralize post-auth startup (tag search, file list, drag-and-drop, folder tree, upload, trash/restore, admin config).
|
||||||
|
- **Updated** DOMContentLoaded `checkAuthentication()` flow to call `initializeApp()` when already authenticated.
|
||||||
|
- **Extended** `updateAuthenticatedUI()` to call `initializeApp()` after a fresh login so all UI modules re-hydrate.
|
||||||
|
- **Enhanced** setup-mode in `checkAuthentication()`:
|
||||||
|
- Show `#addUserModal` as a flex overlay (`style.display = 'flex'`).
|
||||||
|
- Keep `.main-wrapper` hidden until setup completes.
|
||||||
|
- **Added** post-setup handler in the Add-User modal’s save button:
|
||||||
|
- Hide setup modal.
|
||||||
|
- Show login form.
|
||||||
|
- Keep app shell hidden.
|
||||||
|
- Pre-fill and focus the new username in the login inputs.
|
||||||
|
|
||||||
|
### `auth.js` / Auth Logic
|
||||||
|
|
||||||
|
- **Refactored** `checkAuthentication()` to handle three states:
|
||||||
|
1. **`data.setup`** remove overlay, hide main UI, show setup modal.
|
||||||
|
2. **`data.authenticated`** remove overlay, call `updateAuthenticatedUI()`.
|
||||||
|
3. **not authenticated** remove overlay, show login form, keep main UI hidden.
|
||||||
|
- **Refined** `updateAuthenticatedUI()` to:
|
||||||
|
- Remove loading overlay.
|
||||||
|
- Show `.main-wrapper` and main operations.
|
||||||
|
- Hide `#loginForm`.
|
||||||
|
- Reveal header buttons.
|
||||||
|
- Initialize dynamic header buttons (restore, admin, user-panel).
|
||||||
|
- Call `initializeApp()` to load all modules after login.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Changes 5/3/2025 v1.3.0
|
## Changes 5/3/2025 v1.3.0
|
||||||
|
|
||||||
**Admin Panel Refactor & Enhancements**
|
**Admin Panel Refactor & Enhancements**
|
||||||
@@ -48,6 +101,10 @@
|
|||||||
- Adjusted endpoint paths to match controller filenames
|
- Adjusted endpoint paths to match controller filenames
|
||||||
- Fix FolderController readOnly create folder permission
|
- Fix FolderController readOnly create folder permission
|
||||||
|
|
||||||
|
### Additional changes
|
||||||
|
|
||||||
|
- Extend clean up expired shared entries
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changes 4/30/2025 v1.2.8
|
## Changes 4/30/2025 v1.2.8
|
||||||
|
|||||||
@@ -3,42 +3,61 @@
|
|||||||
|
|
||||||
require_once __DIR__ . '/../../../config/config.php';
|
require_once __DIR__ . '/../../../config/config.php';
|
||||||
|
|
||||||
// Simple auth‐check: only admins may read these
|
// Only admins may read these
|
||||||
if (empty($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
|
if (empty($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
echo json_encode(['error'=>'Forbidden']);
|
echo json_encode(['error' => 'Forbidden']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expect a ?file=share_links.json or share_folder_links.json
|
// Must supply ?file=share_links.json or share_folder_links.json
|
||||||
if (empty($_GET['file'])) {
|
if (empty($_GET['file'])) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(['error'=>'Missing `file` parameter']);
|
echo json_encode(['error' => 'Missing `file` parameter']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = basename($_GET['file']);
|
$file = basename($_GET['file']);
|
||||||
$allowed = ['share_links.json','share_folder_links.json'];
|
$allowed = ['share_links.json', 'share_folder_links.json'];
|
||||||
if (!in_array($file, $allowed, true)) {
|
if (!in_array($file, $allowed, true)) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
echo json_encode(['error'=>'Invalid file requested']);
|
echo json_encode(['error' => 'Invalid file requested']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = META_DIR . $file;
|
$path = META_DIR . $file;
|
||||||
if (!file_exists($path)) {
|
if (!file_exists($path)) {
|
||||||
http_response_code(404);
|
// Return empty object so JS sees `{}` not an error
|
||||||
echo json_encode((object)[]); // return empty object
|
http_response_code(200);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode((object)[]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = file_get_contents($path);
|
$jsonData = file_get_contents($path);
|
||||||
$json = json_decode($data, true);
|
$data = json_decode($jsonData, true);
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
if (json_last_error() !== JSON_ERROR_NONE || !is_array($data)) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
echo json_encode(['error'=>'Corrupted JSON']);
|
echo json_encode(['error' => 'Corrupted JSON']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ——— Clean up expired entries ———
|
||||||
|
$now = time();
|
||||||
|
$changed = false;
|
||||||
|
foreach ($data as $token => $entry) {
|
||||||
|
if (!empty($entry['expires']) && $entry['expires'] < $now) {
|
||||||
|
unset($data[$token]);
|
||||||
|
$changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($changed) {
|
||||||
|
// overwrite file with cleaned data
|
||||||
|
file_put_contents($path, json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ——— Send cleaned data back ———
|
||||||
|
http_response_code(200);
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
echo json_encode($json);
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
cd /var/www/public
|
|
||||||
ln -s ../uploads uploads
|
|
||||||
@@ -9,6 +9,21 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
<link rel="icon" type="image/svg+xml" href="/assets/logo.svg">
|
||||||
<meta name="csrf-token" content="">
|
<meta name="csrf-token" content="">
|
||||||
<meta name="share-url" content="">
|
<meta name="share-url" content="">
|
||||||
|
<style>
|
||||||
|
/* hide the app shell until JS says otherwise */
|
||||||
|
.main-wrapper { display: none; }
|
||||||
|
|
||||||
|
/* full-screen white overlay while we check auth */
|
||||||
|
#loadingOverlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: var(--bg-color,#fff);
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<!-- Google Fonts and Material Icons -->
|
<!-- Google Fonts and Material Icons -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet" />
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||||
@@ -165,10 +180,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div id="loadingOverlay"></div>
|
||||||
|
|
||||||
<!-- Custom Toast Container -->
|
<!-- Custom Toast Container -->
|
||||||
<div id="customToast"></div>
|
<div id="customToast"></div>
|
||||||
<div id="hiddenCardsContainer" style="display:none;"></div>
|
<div id="hiddenCardsContainer" style="display:none;"></div>
|
||||||
|
|
||||||
|
<div class="row mt-4" id="loginForm">
|
||||||
|
<div class="col-12">
|
||||||
|
<form id="authForm" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginUsername" data-i18n-key="user">User:</label>
|
||||||
|
<input type="text" class="form-control" id="loginUsername" name="username" required autofocus />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="loginPassword" data-i18n-key="password">Password:</label>
|
||||||
|
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-login" data-i18n-key="login">Login</button>
|
||||||
|
<div class="form-group remember-me-container">
|
||||||
|
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
||||||
|
<label for="rememberMeCheckbox" data-i18n-key="remember_me">Remember me</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- OIDC Login Option -->
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<button id="oidcLoginBtn" class="btn btn-secondary" data-i18n-key="login_oidc">Login with OIDC</button>
|
||||||
|
</div>
|
||||||
|
<!-- Basic HTTP Login Option -->
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<a href="/api/auth/login_basic.php" class="btn btn-secondary" data-i18n-key="basic_http_login">Use Basic
|
||||||
|
HTTP
|
||||||
|
Login</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Main Wrapper: Hidden by default; remove "display: none;" after login -->
|
<!-- Main Wrapper: Hidden by default; remove "display: none;" after login -->
|
||||||
<div class="main-wrapper">
|
<div class="main-wrapper">
|
||||||
<!-- Sidebar Drop Zone: Hidden until you drag a card (display controlled by JS) -->
|
<!-- Sidebar Drop Zone: Hidden until you drag a card (display controlled by JS) -->
|
||||||
@@ -176,37 +223,6 @@
|
|||||||
<!-- Main Column -->
|
<!-- Main Column -->
|
||||||
<div id="mainColumn" class="main-column">
|
<div id="mainColumn" class="main-column">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<!-- Login Form (unchanged) -->
|
|
||||||
<div class="row" id="loginForm">
|
|
||||||
<div class="col-12">
|
|
||||||
<form id="authForm" method="post">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="loginUsername" data-i18n-key="user">User:</label>
|
|
||||||
<input type="text" class="form-control" id="loginUsername" name="username" required autofocus />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="loginPassword" data-i18n-key="password">Password:</label>
|
|
||||||
<input type="password" class="form-control" id="loginPassword" name="password" required />
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary btn-block btn-login" data-i18n-key="login">Login</button>
|
|
||||||
<div class="form-group remember-me-container">
|
|
||||||
<input type="checkbox" id="rememberMeCheckbox" name="remember_me" />
|
|
||||||
<label for="rememberMeCheckbox" data-i18n-key="remember_me">Remember me</label>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<!-- OIDC Login Option -->
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<button id="oidcLoginBtn" class="btn btn-secondary" data-i18n-key="login_oidc">Login with OIDC</button>
|
|
||||||
</div>
|
|
||||||
<!-- Basic HTTP Login Option -->
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<a href="/api/auth/login_basic.php" class="btn btn-secondary" data-i18n-key="basic_http_login">Use Basic
|
|
||||||
HTTP
|
|
||||||
Login</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Operations: Upload and Folder Management -->
|
<!-- Main Operations: Upload and Folder Management -->
|
||||||
<div id="mainOperations">
|
<div id="mainOperations">
|
||||||
<div class="container" style="max-width: 1400px; margin: 0 auto;">
|
<div class="container" style="max-width: 1400px; margin: 0 auto;">
|
||||||
@@ -428,7 +444,7 @@
|
|||||||
<div id="changePasswordModal" class="modal" style="display:none;">
|
<div id="changePasswordModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content" style="max-width:400px; margin:auto;">
|
<div class="modal-content" style="max-width:400px; margin:auto;">
|
||||||
<span id="closeChangePasswordModal"
|
<span id="closeChangePasswordModal"
|
||||||
style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">×</span>
|
class="editor-close-btn">×</span>
|
||||||
<h3 data-i18n-key="change_password_title">Change Password</h3>
|
<h3 data-i18n-key="change_password_title">Change Password</h3>
|
||||||
<input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password"
|
<input type="password" id="oldPassword" class="form-control" data-i18n-placeholder="old_password"
|
||||||
placeholder="Old Password" style="width:100%; margin: 5px 0;" />
|
placeholder="Old Password" style="width:100%; margin: 5px 0;" />
|
||||||
@@ -439,7 +455,7 @@
|
|||||||
<button id="saveNewPasswordBtn" class="btn btn-primary" data-i18n-key="save" style="width:100%;">Save</button>
|
<button id="saveNewPasswordBtn" class="btn btn-primary" data-i18n-key="save" style="width:100%;">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="addUserModal" class="modal">
|
<div id="addUserModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h3 data-i18n-key="create_new_user_title">Create New User</h3>
|
<h3 data-i18n-key="create_new_user_title">Create New User</h3>
|
||||||
<!-- 1) Add a form around these fields -->
|
<!-- 1) Add a form around these fields -->
|
||||||
@@ -468,7 +484,7 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="removeUserModal" class="modal">
|
<div id="removeUserModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h3 data-i18n-key="remove_user_title">Remove User</h3>
|
<h3 data-i18n-key="remove_user_title">Remove User</h3>
|
||||||
<label for="removeUsernameSelect" data-i18n-key="select_user_remove">Select a user to remove:</label>
|
<label for="removeUsernameSelect" data-i18n-key="select_user_remove">Select a user to remove:</label>
|
||||||
@@ -479,7 +495,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="renameFileModal" class="modal">
|
<div id="renameFileModal" class="modal" style="display:none;">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<h4 data-i18n-key="rename_file_title">Rename File</h4>
|
<h4 data-i18n-key="rename_file_title">Rename File</h4>
|
||||||
<input type="text" id="newFileName" class="form-control" data-i18n-placeholder="rename_file_placeholder"
|
<input type="text" id="newFileName" class="form-control" data-i18n-placeholder="rename_file_placeholder"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { loadAdminConfigFunc } from './auth.js';
|
|||||||
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
|
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
|
||||||
import { sendRequest } from './networkUtils.js';
|
import { sendRequest } from './networkUtils.js';
|
||||||
|
|
||||||
const version = "v1.3.0";
|
const version = "v1.3.1";
|
||||||
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
|
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
|
||||||
|
|
||||||
// ————— Inject updated styles —————
|
// ————— Inject updated styles —————
|
||||||
@@ -184,49 +184,63 @@ function loadShareLinksSection() {
|
|||||||
const container = document.getElementById("shareLinksContent");
|
const container = document.getElementById("shareLinksContent");
|
||||||
container.textContent = t("loading") + "...";
|
container.textContent = t("loading") + "...";
|
||||||
|
|
||||||
// Helper to fetch a metadata file or return {} on any error
|
// helper: fetch one metadata file, but never throw —
|
||||||
const fetchMeta = file =>
|
// on non-2xx (including 404) or network error, resolve to {}
|
||||||
fetch(`/api/admin/readMetadata.php?file=${file}`, { credentials: "include" })
|
function fetchMeta(fileName) {
|
||||||
.then(r => r.ok ? r.json() : {}) // non-2xx → treat as empty
|
return fetch(`/api/admin/readMetadata.php?file=${encodeURIComponent(fileName)}`, {
|
||||||
.catch(() => ({}));
|
credentials: "include"
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
if (!resp.ok) {
|
||||||
|
// 404 or any other non-OK → treat as empty
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return resp.json();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// network failure, parse error, etc → also empty
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
fetchMeta("share_folder_links.json"),
|
fetchMeta("share_folder_links.json"),
|
||||||
fetchMeta("share_links.json")
|
fetchMeta("share_links.json")
|
||||||
])
|
])
|
||||||
.then(([folders, files]) => {
|
.then(([folders, files]) => {
|
||||||
// If nothing at all, show a friendly message
|
// if *both* are empty, show "no shared links"
|
||||||
if (Object.keys(folders).length === 0 && Object.keys(files).length === 0) {
|
const hasAny = Object.keys(folders).length || Object.keys(files).length;
|
||||||
container.textContent = t("no_shared_links_available");
|
if (!hasAny) {
|
||||||
|
container.innerHTML = `<p>${t("no_shared_links_available")}</p>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let html = `<h5>${t("folder_shares")}</h5><ul>`;
|
let html = `<h5>${t("folder_shares")}</h5><ul>`;
|
||||||
Object.entries(folders).forEach(([token, o]) => {
|
Object.entries(folders).forEach(([token, o]) => {
|
||||||
const lock = o.password ? `🔒 ` : "";
|
const lock = o.password ? "🔒 " : "";
|
||||||
html += `
|
html += `
|
||||||
<li>
|
<li>
|
||||||
${lock}<strong>${o.folder}</strong>
|
${lock}<strong>${o.folder}</strong>
|
||||||
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-key="${token}"
|
data-key="${token}"
|
||||||
data-type="folder"
|
data-type="folder"
|
||||||
class="btn btn-sm btn-link delete-share">🗑️</button>
|
class="btn btn-sm btn-link delete-share">🗑️</button>
|
||||||
</li>`;
|
</li>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += `</ul><h5 style="margin-top:1em;">${t("file_shares")}</h5><ul>`;
|
html += `</ul><h5 style="margin-top:1em;">${t("file_shares")}</h5><ul>`;
|
||||||
Object.entries(files).forEach(([token, o]) => {
|
Object.entries(files).forEach(([token, o]) => {
|
||||||
const lock = o.password ? `🔒 ` : "";
|
const lock = o.password ? "🔒 " : "";
|
||||||
html += `
|
html += `
|
||||||
<li>
|
<li>
|
||||||
${lock}<strong>${o.folder}/${o.file}</strong>
|
${lock}<strong>${o.folder}/${o.file}</strong>
|
||||||
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
<small>(${new Date(o.expires * 1000).toLocaleString()})</small>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
data-key="${token}"
|
data-key="${token}"
|
||||||
data-type="file"
|
data-type="file"
|
||||||
class="btn btn-sm btn-link delete-share">🗑️</button>
|
class="btn btn-sm btn-link delete-share">🗑️</button>
|
||||||
</li>`;
|
</li>`;
|
||||||
});
|
});
|
||||||
html += `</ul>`;
|
html += `</ul>`;
|
||||||
|
|
||||||
@@ -243,11 +257,11 @@ function loadShareLinksSection() {
|
|||||||
: "/api/file/deleteShareLink.php";
|
: "/api/file/deleteShareLink.php";
|
||||||
|
|
||||||
fetch(endpoint, {
|
fetch(endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||||
body: new URLSearchParams({ token })
|
body: new URLSearchParams({ token })
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
@@ -375,7 +389,7 @@ export function openAdminPanel() {
|
|||||||
// — Header Settings —
|
// — Header Settings —
|
||||||
document.getElementById("headerSettingsContent").innerHTML = `
|
document.getElementById("headerSettingsContent").innerHTML = `
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="headerTitle">${t("header_title")}:</label>
|
<label for="headerTitle">${t("header_title_text")}:</label>
|
||||||
<input type="text" id="headerTitle" class="form-control" value="${window.headerTitle}" />
|
<input type="text" id="headerTitle" class="form-control" value="${window.headerTitle}" />
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -534,7 +548,7 @@ 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" class="editor-close-btn">×</span>
|
||||||
<h3>${t("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 -->
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
setLastLoginData
|
setLastLoginData
|
||||||
} from './authModals.js';
|
} from './authModals.js';
|
||||||
import { openAdminPanel } from './adminPanel.js';
|
import { openAdminPanel } from './adminPanel.js';
|
||||||
|
import { initializeApp } from './main.js';
|
||||||
|
|
||||||
// Production OIDC configuration (override via API as needed)
|
// Production OIDC configuration (override via API as needed)
|
||||||
const currentOIDCConfig = {
|
const currentOIDCConfig = {
|
||||||
@@ -127,13 +128,13 @@ function updateItemsPerPageSelect() {
|
|||||||
|
|
||||||
function updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }) {
|
function updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }) {
|
||||||
const authForm = document.getElementById("authForm");
|
const authForm = document.getElementById("authForm");
|
||||||
if
|
if
|
||||||
(authForm) {
|
(authForm) {
|
||||||
authForm.style.display = disableFormLogin ? "none" : "block";
|
authForm.style.display = disableFormLogin ? "none" : "block";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const loginInput = document.getElementById('loginUsername');
|
const loginInput = document.getElementById('loginUsername');
|
||||||
if (loginInput) loginInput.focus();
|
if (loginInput) loginInput.focus();
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
const basicAuthLink = document.querySelector("a[href='/api/auth/login_basic.php']");
|
const basicAuthLink = document.querySelector("a[href='/api/auth/login_basic.php']");
|
||||||
if (basicAuthLink) basicAuthLink.style.display = disableBasicAuth ? "none" : "inline-block";
|
if (basicAuthLink) basicAuthLink.style.display = disableBasicAuth ? "none" : "inline-block";
|
||||||
@@ -189,6 +190,11 @@ function insertAfter(newNode, referenceNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateAuthenticatedUI(data) {
|
function updateAuthenticatedUI(data) {
|
||||||
|
document.getElementById('loadingOverlay').remove();
|
||||||
|
|
||||||
|
// show the wrapper (so the login form can be visible)
|
||||||
|
document.querySelector('.main-wrapper').style.display = '';
|
||||||
|
document.getElementById('loginForm').style.display = 'none';
|
||||||
toggleVisibility("loginForm", false);
|
toggleVisibility("loginForm", false);
|
||||||
toggleVisibility("mainOperations", true);
|
toggleVisibility("mainOperations", true);
|
||||||
toggleVisibility("uploadFileForm", true);
|
toggleVisibility("uploadFileForm", true);
|
||||||
@@ -263,6 +269,7 @@ function updateAuthenticatedUI(data) {
|
|||||||
userPanelBtn.style.display = "block";
|
userPanelBtn.style.display = "block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
initializeApp();
|
||||||
applyTranslations();
|
applyTranslations();
|
||||||
updateItemsPerPageSelect();
|
updateItemsPerPageSelect();
|
||||||
updateLoginOptionsUIFromStorage();
|
updateLoginOptionsUIFromStorage();
|
||||||
@@ -272,6 +279,11 @@ function checkAuthentication(showLoginToast = true) {
|
|||||||
return sendRequest("/api/auth/checkAuth.php")
|
return sendRequest("/api/auth/checkAuth.php")
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.setup) {
|
if (data.setup) {
|
||||||
|
document.getElementById('loadingOverlay').remove();
|
||||||
|
|
||||||
|
// show the wrapper (so the login form can be visible)
|
||||||
|
document.querySelector('.main-wrapper').style.display = '';
|
||||||
|
document.getElementById('loginForm').style.display = 'none';
|
||||||
window.setupMode = true;
|
window.setupMode = true;
|
||||||
if (showLoginToast) showToast("Setup mode: No users found. Please add an admin user.");
|
if (showLoginToast) showToast("Setup mode: No users found. Please add an admin user.");
|
||||||
toggleVisibility("loginForm", false);
|
toggleVisibility("loginForm", false);
|
||||||
@@ -283,6 +295,7 @@ function checkAuthentication(showLoginToast = true) {
|
|||||||
}
|
}
|
||||||
window.setupMode = false;
|
window.setupMode = false;
|
||||||
if (data.authenticated) {
|
if (data.authenticated) {
|
||||||
|
|
||||||
localStorage.setItem('isAdmin', data.isAdmin ? 'true' : 'false');
|
localStorage.setItem('isAdmin', data.isAdmin ? 'true' : 'false');
|
||||||
localStorage.setItem("folderOnly", data.folderOnly);
|
localStorage.setItem("folderOnly", data.folderOnly);
|
||||||
localStorage.setItem("readOnly", data.readOnly);
|
localStorage.setItem("readOnly", data.readOnly);
|
||||||
@@ -298,6 +311,11 @@ function checkAuthentication(showLoginToast = true) {
|
|||||||
updateAuthenticatedUI(data);
|
updateAuthenticatedUI(data);
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
|
document.getElementById('loadingOverlay').remove();
|
||||||
|
|
||||||
|
// show the wrapper (so the login form can be visible)
|
||||||
|
document.querySelector('.main-wrapper').style.display = '';
|
||||||
|
document.getElementById('loginForm').style.display = '';
|
||||||
if (showLoginToast) showToast("Please log in to continue.");
|
if (showLoginToast) showToast("Please log in to continue.");
|
||||||
toggleVisibility("loginForm", true);
|
toggleVisibility("loginForm", true);
|
||||||
toggleVisibility("mainOperations", false);
|
toggleVisibility("mainOperations", false);
|
||||||
@@ -443,52 +461,55 @@ function initAuth() {
|
|||||||
submitLogin(formData);
|
submitLogin(formData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("addUserBtn").addEventListener("click", function () {
|
document.getElementById("addUserBtn").addEventListener("click", function () {
|
||||||
resetUserForm();
|
resetUserForm();
|
||||||
toggleVisibility("addUserModal", true);
|
toggleVisibility("addUserModal", true);
|
||||||
document.getElementById("newUsername").focus();
|
document.getElementById("newUsername").focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove your old saveUserBtn click-handler…
|
|
||||||
|
|
||||||
// instead:
|
// remove your old saveUserBtn click-handler…
|
||||||
const addUserForm = document.getElementById("addUserForm");
|
|
||||||
addUserForm.addEventListener("submit", function (e) {
|
|
||||||
e.preventDefault(); // stop the browser from reloading the page
|
|
||||||
|
|
||||||
const newUsername = document.getElementById("newUsername").value.trim();
|
// instead:
|
||||||
const newPassword = document.getElementById("addUserPassword").value.trim();
|
const addUserForm = document.getElementById("addUserForm");
|
||||||
const isAdmin = document.getElementById("isAdmin").checked;
|
addUserForm.addEventListener("submit", function (e) {
|
||||||
|
e.preventDefault(); // stop the browser from reloading the page
|
||||||
|
|
||||||
if (!newUsername || !newPassword) {
|
const newUsername = document.getElementById("newUsername").value.trim();
|
||||||
showToast("Username and password are required!");
|
const newPassword = document.getElementById("addUserPassword").value.trim();
|
||||||
return;
|
const isAdmin = document.getElementById("isAdmin").checked;
|
||||||
}
|
|
||||||
|
|
||||||
let url = "/api/addUser.php";
|
if (!newUsername || !newPassword) {
|
||||||
if (window.setupMode) url += "?setup=1";
|
showToast("Username and password are required!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fetchWithCsrf(url, {
|
let url = "/api/addUser.php";
|
||||||
method: "POST",
|
if (window.setupMode) url += "?setup=1";
|
||||||
credentials: "include",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
fetchWithCsrf(url, {
|
||||||
body: JSON.stringify({ username: newUsername, password: newPassword, isAdmin })
|
method: "POST",
|
||||||
})
|
credentials: "include",
|
||||||
.then(r => r.json())
|
headers: { "Content-Type": "application/json" },
|
||||||
.then(data => {
|
body: JSON.stringify({ username: newUsername, password: newPassword, isAdmin })
|
||||||
if (data.success) {
|
|
||||||
showToast("User added successfully!");
|
|
||||||
closeAddUserModal();
|
|
||||||
checkAuthentication(false);
|
|
||||||
} else {
|
|
||||||
showToast("Error: " + (data.error || "Could not add user"));
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.then(r => r.json())
|
||||||
showToast("Error: Could not add user");
|
.then(data => {
|
||||||
});
|
if (data.success) {
|
||||||
});
|
showToast("User added successfully!");
|
||||||
|
closeAddUserModal();
|
||||||
|
checkAuthentication(false);
|
||||||
|
if (window.setupMode) {
|
||||||
|
toggleVisibility("loginForm", true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast("Error: " + (data.error || "Could not add user"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
showToast("Error: Could not add user");
|
||||||
|
});
|
||||||
|
});
|
||||||
document.getElementById("cancelUserBtn").addEventListener("click", closeAddUserModal);
|
document.getElementById("cancelUserBtn").addEventListener("click", closeAddUserModal);
|
||||||
|
|
||||||
document.getElementById("removeUserBtn").addEventListener("click", function () {
|
document.getElementById("removeUserBtn").addEventListener("click", function () {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function openTOTPLoginModal() {
|
|||||||
`;
|
`;
|
||||||
totpLoginModal.innerHTML = `
|
totpLoginModal.innerHTML = `
|
||||||
<div style="background: ${modalBg}; padding:20px; border-radius:8px; text-align:center; position:relative; color:${textColor};">
|
<div style="background: ${modalBg}; padding:20px; border-radius:8px; text-align:center; position:relative; color:${textColor};">
|
||||||
<span id="closeTOTPLoginModal" style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">×</span>
|
<span id="closeTOTPLoginModal" class="editor-close-btn">×</span>
|
||||||
<div id="totpSection">
|
<div id="totpSection">
|
||||||
<h3>${t("enter_totp_code")}</h3>
|
<h3>${t("enter_totp_code")}</h3>
|
||||||
<input type="text" id="totpLoginInput" maxlength="6"
|
<input type="text" id="totpLoginInput" maxlength="6"
|
||||||
@@ -172,11 +172,13 @@ export function openUserPanel() {
|
|||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
position: fixed;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 400px !important;
|
overflow-x: hidden;
|
||||||
|
max-height: 383px !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
scrollbar-gutter: stable both-edges;
|
||||||
border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"};
|
border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"};
|
||||||
transform: none;
|
box-sizing: border-box;
|
||||||
transition: none;
|
transition: none;
|
||||||
`;
|
`;
|
||||||
const savedLanguage = localStorage.getItem("language") || "en";
|
const savedLanguage = localStorage.getItem("language") || "en";
|
||||||
@@ -186,19 +188,17 @@ export function openUserPanel() {
|
|||||||
userPanelModal.id = "userPanelModal";
|
userPanelModal.id = "userPanelModal";
|
||||||
userPanelModal.style.cssText = `
|
userPanelModal.style.cssText = `
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0; right: 0; bottom: 0; left: 0;
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background-color: ${overlayBackground};
|
background-color: ${overlayBackground};
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 3000;
|
z-index: 1000;
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
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" class="editor-close-btn">×</span>
|
||||||
<h3>${t("user_panel")} (${username})</h3>
|
<h3>${t("user_panel")} (${username})</h3>
|
||||||
|
|
||||||
<button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">
|
<button type="button" id="openChangePasswordModalBtn" class="btn btn-primary" style="margin-bottom: 15px;">
|
||||||
@@ -369,7 +369,7 @@ 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" class="editor-close-btn">×</span>
|
||||||
<h3>${t("totp_setup")}</h3>
|
<h3>${t("totp_setup")}</h3>
|
||||||
<p>${t("scan_qr_code")}</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 -->
|
||||||
|
|||||||
@@ -13,10 +13,19 @@ export function openTagModal(file) {
|
|||||||
modal.id = 'tagModal';
|
modal.id = 'tagModal';
|
||||||
modal.className = 'modal';
|
modal.className = 'modal';
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="modal-content" style="width: 400px; max-width:90vw;">
|
<div class="modal-content" style="width: 450px; max-width:90vw;">
|
||||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
|
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||||||
<h3 style="margin:0;">${t("tag_file")}: ${file.name}</h3>
|
<h3 style="
|
||||||
<span id="closeTagModal" style="cursor:pointer; font-size:24px;">×</span>
|
margin:0;
|
||||||
|
display:inline-block;
|
||||||
|
max-width: calc(100% - 40px);
|
||||||
|
overflow:hidden;
|
||||||
|
text-overflow:ellipsis;
|
||||||
|
white-space:nowrap;
|
||||||
|
">
|
||||||
|
${t("tag_file")}: ${escapeHTML(file.name)}
|
||||||
|
</h3>
|
||||||
|
<span id="closeTagModal" class="editor-close-btn">×</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" style="margin-top:10px;">
|
<div class="modal-body" style="margin-top:10px;">
|
||||||
<label for="tagNameInput">${t("tag_name")}</label>
|
<label for="tagNameInput">${t("tag_name")}</label>
|
||||||
@@ -83,10 +92,10 @@ export function openMultiTagModal(files) {
|
|||||||
modal.id = 'multiTagModal';
|
modal.id = 'multiTagModal';
|
||||||
modal.className = 'modal';
|
modal.className = 'modal';
|
||||||
modal.innerHTML = `
|
modal.innerHTML = `
|
||||||
<div class="modal-content" style="width: 400px; max-width:90vw;">
|
<div class="modal-content" style="width: 450px; max-width:90vw;">
|
||||||
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
|
<div class="modal-header" style="display:flex; justify-content:space-between; align-items:center;">
|
||||||
<h3 style="margin:0;">Tag Selected Files (${files.length})</h3>
|
<h3 style="margin:0;">Tag Selected Files (${files.length})</h3>
|
||||||
<span id="closeMultiTagModal" style="cursor:pointer; font-size:24px;">×</span>
|
<span id="closeMultiTagModal" class="editor-close-btn">×</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" style="margin-top:10px;">
|
<div class="modal-body" style="margin-top:10px;">
|
||||||
<label for="multiTagNameInput">Tag Name:</label>
|
<label for="multiTagNameInput">Tag Name:</label>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ const translations = {
|
|||||||
// Additional keys for HTML translations:
|
// Additional keys for HTML translations:
|
||||||
"title": "FileRise",
|
"title": "FileRise",
|
||||||
"header_title": "FileRise",
|
"header_title": "FileRise",
|
||||||
|
"header_title_text": "Header Title",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"change_password": "Change Password",
|
"change_password": "Change Password",
|
||||||
"restore_text": "Restore or",
|
"restore_text": "Restore or",
|
||||||
@@ -316,6 +317,7 @@ const translations = {
|
|||||||
// Additional keys for HTML translations:
|
// Additional keys for HTML translations:
|
||||||
"title": "FileRise",
|
"title": "FileRise",
|
||||||
"header_title": "FileRise",
|
"header_title": "FileRise",
|
||||||
|
"header_title_text": "Header Title",
|
||||||
"logout": "Cerrar sesión",
|
"logout": "Cerrar sesión",
|
||||||
"change_password": "Cambiar contraseña",
|
"change_password": "Cambiar contraseña",
|
||||||
"restore_text": "Restaurar o",
|
"restore_text": "Restaurar o",
|
||||||
|
|||||||
@@ -14,6 +14,28 @@ import { initFileActions, renameFile, openDownloadModal, confirmSingleDownload }
|
|||||||
import { editFile, saveFile } from './fileEditor.js';
|
import { editFile, saveFile } from './fileEditor.js';
|
||||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||||
|
|
||||||
|
export function initializeApp() {
|
||||||
|
window.currentFolder = "root";
|
||||||
|
initTagSearch();
|
||||||
|
loadFileList(window.currentFolder);
|
||||||
|
initDragAndDrop();
|
||||||
|
loadSidebarOrder();
|
||||||
|
loadHeaderOrder();
|
||||||
|
initFileActions();
|
||||||
|
initUpload();
|
||||||
|
loadFolderTree();
|
||||||
|
setupTrashRestoreDelete();
|
||||||
|
loadAdminConfigFunc();
|
||||||
|
|
||||||
|
const helpBtn = document.getElementById("folderHelpBtn");
|
||||||
|
const helpTooltip = document.getElementById("folderHelpTooltip");
|
||||||
|
if (helpBtn && helpTooltip) {
|
||||||
|
helpBtn.addEventListener("click", () => {
|
||||||
|
helpTooltip.style.display =
|
||||||
|
helpTooltip.style.display === "block" ? "none" : "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loadCsrfToken() {
|
export function loadCsrfToken() {
|
||||||
return fetchWithCsrf('/api/auth/token.php', {
|
return fetchWithCsrf('/api/auth/token.php', {
|
||||||
@@ -100,31 +122,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
// Continue with initializations that rely on a valid CSRF token:
|
// Continue with initializations that rely on a valid CSRF token:
|
||||||
checkAuthentication().then(authenticated => {
|
checkAuthentication().then(authenticated => {
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
window.currentFolder = "root";
|
document.getElementById('loadingOverlay').remove();
|
||||||
initTagSearch();
|
initializeApp();
|
||||||
loadFileList(window.currentFolder);
|
}
|
||||||
initDragAndDrop();
|
|
||||||
loadSidebarOrder();
|
|
||||||
loadHeaderOrder();
|
|
||||||
initFileActions();
|
|
||||||
initUpload();
|
|
||||||
loadFolderTree();
|
|
||||||
setupTrashRestoreDelete();
|
|
||||||
loadAdminConfigFunc();
|
|
||||||
|
|
||||||
const helpBtn = document.getElementById("folderHelpBtn");
|
|
||||||
const helpTooltip = document.getElementById("folderHelpTooltip");
|
|
||||||
helpBtn.addEventListener("click", function () {
|
|
||||||
// Toggle display of the tooltip.
|
|
||||||
if (helpTooltip.style.display === "none" || helpTooltip.style.display === "") {
|
|
||||||
helpTooltip.style.display = "block";
|
|
||||||
} else {
|
|
||||||
helpTooltip.style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn("User not authenticated. Data loading deferred.");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Other DOM initialization that can happen after CSRF is ready.
|
// Other DOM initialization that can happen after CSRF is ready.
|
||||||
|
|||||||
@@ -1582,6 +1582,31 @@ class FileController
|
|||||||
echo json_encode($shareFile, JSON_PRETTY_PRINT);
|
echo json_encode($shareFile, JSON_PRETTY_PRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAllShareLinks(): void
|
||||||
|
{
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
$shareFile = META_DIR . 'share_links.json';
|
||||||
|
$links = file_exists($shareFile)
|
||||||
|
? json_decode(file_get_contents($shareFile), true) ?? []
|
||||||
|
: [];
|
||||||
|
$now = time();
|
||||||
|
$cleaned = [];
|
||||||
|
|
||||||
|
// remove expired
|
||||||
|
foreach ($links as $token => $record) {
|
||||||
|
if (!empty($record['expires']) && $record['expires'] < $now) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cleaned[$token] = $record;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($cleaned) !== count($links)) {
|
||||||
|
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($cleaned);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/file/deleteShareLink.php
|
* POST /api/file/deleteShareLink.php
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1082,11 +1082,30 @@ class FolderController
|
|||||||
/**
|
/**
|
||||||
* GET /api/folder/getShareFolderLinks.php
|
* GET /api/folder/getShareFolderLinks.php
|
||||||
*/
|
*/
|
||||||
public function getShareFolderLinks()
|
public function getAllShareFolderLinks(): void
|
||||||
{
|
{
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
$links = FolderModel::getAllShareFolderLinks();
|
$shareFile = META_DIR . 'share_folder_links.json';
|
||||||
echo json_encode($links, JSON_PRETTY_PRINT);
|
$links = file_exists($shareFile)
|
||||||
|
? json_decode(file_get_contents($shareFile), true) ?? []
|
||||||
|
: [];
|
||||||
|
$now = time();
|
||||||
|
$cleaned = [];
|
||||||
|
|
||||||
|
// 1) Remove expired
|
||||||
|
foreach ($links as $token => $record) {
|
||||||
|
if (!empty($record['expires']) && $record['expires'] < $now) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cleaned[$token] = $record;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Persist back if anything was pruned
|
||||||
|
if (count($cleaned) !== count($links)) {
|
||||||
|
file_put_contents($shareFile, json_encode($cleaned, JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($cleaned);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user