mirror of
https://github.com/RoastSlav/quickdrop.git
synced 2025-12-30 11:09:59 -06:00
Finished transition from bootstrap to tailwind and redame of of the page layouts (#54)
* Update Jenkinsfile to specify JDK and Maven tools * Updated to Java25 and updated to Spring Boot 3.5.6 Also made use of the improvement to GC and ObjectHeader sizes in Java25 * Updating Jenkinspipeline to use version and fix for new setup in jenkins * back to java 21 * copilot * Add logging to FileViewController and rename PasswordController to PasswordViewController * Refactor fileView.html and fileView.js for improved readability and structure * Reformat code * Refactor dashboard.html and tailwind.css for improved layout and styling consistency * Refactor file-history.html for improved layout and styling using Tailwind CSS * Remove Bootstrap script references from HTML files for consistency * Refactor settings.html for improved layout and styling using Tailwind CSS * Update tailwind.css dark theme fix and removal of bootstrap --------- Co-authored-by: Rostislav Raykov <rostislav.raykov@infinno.eu>
This commit is contained in:
16
.github/copilot-instructions.md
vendored
Normal file
16
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# QuickDrop AI Working Guide
|
||||
|
||||
- **Build & run**: Java 21; use `./mvnw clean package` then `java -jar target/quickdrop.jar`. SQLite DB lives at `db/quickdrop.db`; Flyway runs on startup with baseline-on-migrate. Docker image `roastslav/quickdrop:latest` exposes 8080.
|
||||
- **App shape**: Spring Boot MVC + Thymeleaf views; controllers in [src/main/java/org/rostislav/quickdrop/controller](src/main/java/org/rostislav/quickdrop/controller), services in [service](src/main/java/org/rostislav/quickdrop/service), interceptors in [interceptor](src/main/java/org/rostislav/quickdrop/interceptor), entities/repos under [entity](src/main/java/org/rostislav/quickdrop/entity) and [repository](src/main/java/org/rostislav/quickdrop/repository). Static assets and templates live under `src/main/resources/static` and `.../templates`.
|
||||
- **Settings source of truth**: Single-row settings (id=1) managed by [ApplicationSettingsService](src/main/java/org/rostislav/quickdrop/service/ApplicationSettingsService.java) and stored via JPA. Startup seeds defaults (max size 1GB, life 30d, paths `files`/`logs`, cron `0 0 2 * * *`, app password off, admin password empty, file list & admin button enabled, encryption on, default home upload) and schedules cleanup.
|
||||
- **Security model**: If the app password is enabled, [SecurityConfig](src/main/java/org/rostislav/quickdrop/config/SecurityConfig.java) locks everything behind `/password/login` using BCrypt auth; CSRF cookie enabled; permissive CORS. Session timeout set from settings in [WebConfig](src/main/java/org/rostislav/quickdrop/config/WebConfig.java).
|
||||
- **Interceptors**: [AdminPasswordSetupInterceptor](src/main/java/org/rostislav/quickdrop/interceptor/AdminPasswordSetupInterceptor.java) forces `/admin/setup` until admin password exists. [AdminPasswordInterceptor](src/main/java/org/rostislav/quickdrop/interceptor/AdminPasswordInterceptor.java) requires an admin session token for `/admin/**` and file history. [FilePasswordInterceptor](src/main/java/org/rostislav/quickdrop/interceptor/FilePasswordInterceptor.java) redirects to `/file/password/{uuid}` when a file has a password and session token is missing.
|
||||
- **Sessions/tokens**: [SessionService](src/main/java/org/rostislav/quickdrop/service/SessionService.java) keeps admin and file session tokens in-memory; admin tokens are issued after password check in [AdminViewController](src/main/java/org/rostislav/quickdrop/controller/AdminViewController.java), file tokens after passing a file password in [FileViewController](src/main/java/org/rostislav/quickdrop/controller/FileViewController.java).
|
||||
- **Upload pipeline**: `/api/file/upload-chunk` accepts chunked uploads with metadata `description`, `keepIndefinitely`, `password`, `hidden`, `fileSize`. [AsyncFileMergeService](src/main/java/org/rostislav/quickdrop/service/AsyncFileMergeService.java) buffers chunks in temp, merges via an executor, and streams to disk; if a password is provided and encryption is enabled, it encrypts using [FileEncryptionService](src/main/java/org/rostislav/quickdrop/service/FileEncryptionService.java). Final merge saves via [FileService](src/main/java/org/rostislav/quickdrop/service/FileService.java).
|
||||
- **File model & storage**: Files are stored on disk under `fileStoragePath` (from settings) using UUID filenames; metadata lives in `file_entity` (includes description, keepIndefinitely, hidden, optional password hash, encrypted flag). Startup ensures `./db` and storage path exist in [QuickdropApplication](src/main/java/org/rostislav/quickdrop/QuickdropApplication.java).
|
||||
- **Downloads**: Standard download `/file/download/{uuid}` decrypts on the fly when needed; logs IP/user-agent into `download_log`. Share links: `/api/file/share/{uuid}` creates token rows with expiry/download count; `/api/file/download/{uuid}/{token}` streams and decrements/removes tokens.
|
||||
- **Admin surface**: Dashboard shows analytics and file list; settings page edits the single settings row then reschedules cron and refreshes context. Admin actions include toggle hidden, delete, extend life, and keep indefinitely switches, all via [AdminViewController](src/main/java/org/rostislav/quickdrop/controller/AdminViewController.java) and [FileService](src/main/java/org/rostislav/quickdrop/service/FileService.java).
|
||||
- **Cleanup tasks**: [ScheduleService](src/main/java/org/rostislav/quickdrop/service/ScheduleService.java) schedules file deletion based on settings cron and max life, nightly DB cleanup for missing files, and share token cleanup.
|
||||
- **Thymeleaf flags**: [GlobalControllerAdvice](src/main/java/org/rostislav/quickdrop/config/GlobalControllerAdvice.java) injects template flags (file list enabled, app password set, admin dashboard button enabled, encryption enabled) used across views and nav.
|
||||
- **Conventions**: Always mutate file data via `FileService` to ensure logging and encryption decisions. When adding new routes, ensure interceptors are updated appropriately. Respect the single settings row (id=1); creating new rows will break lookups. Chunk uploads return null on intermediate chunks—frontends must handle that.
|
||||
- **Local dev reminders**: SQLite dialect configured; no migrations beyond Flyway under `src/main/resources/db/migration`. Tests use Spring Boot starter test/JUnit Jupiter but none present; rely on manual runs. Logging writes to `log/quickdrop.log` per properties.
|
||||
@@ -53,7 +53,6 @@ This project is made with the self-hosting community in mind as a self-hosted fi
|
||||
- **Spring Web**
|
||||
- **Spring Boot**
|
||||
- **Thymeleaf**
|
||||
- **Bootstrap**
|
||||
- **Maven**
|
||||
|
||||
## Getting Started
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.3.10</version>
|
||||
<version>3.5.6</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>org.rostislav</groupId>
|
||||
@@ -90,13 +90,11 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.11.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.11.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.rostislav.quickdrop.service.AnalyticsService;
|
||||
import org.rostislav.quickdrop.service.ApplicationSettingsService;
|
||||
import org.rostislav.quickdrop.service.FileService;
|
||||
import org.rostislav.quickdrop.service.SessionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@@ -32,6 +34,7 @@ public class FileViewController {
|
||||
private final ApplicationSettingsService applicationSettingsService;
|
||||
private final AnalyticsService analyticsService;
|
||||
private final SessionService sessionService;
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileViewController.class);
|
||||
|
||||
public FileViewController(FileService fileService, ApplicationSettingsService applicationSettingsService, AnalyticsService analyticsService, SessionService sessionService) {
|
||||
this.fileService = fileService;
|
||||
@@ -96,8 +99,10 @@ public class FileViewController {
|
||||
String fileSessionToken = sessionService.addFileSessionToken(UUID.randomUUID().toString(), password, uuid);
|
||||
HttpSession session = request.getSession();
|
||||
session.setAttribute("file-session-token", fileSessionToken);
|
||||
logger.info("Token has been added to the session for file UUID: {}", uuid);
|
||||
return "redirect:/file/" + uuid;
|
||||
} else {
|
||||
logger.info("Incorrect password attempt for file UUID: {}", uuid);
|
||||
model.addAttribute("uuid", uuid);
|
||||
FileEntity fileEntity = fileService.getFile(uuid);
|
||||
if (fileEntity != null) {
|
||||
@@ -152,6 +157,7 @@ public class FileViewController {
|
||||
public String updateKeepIndefinitely(@PathVariable String uuid, @RequestParam(required = false, defaultValue = "false") boolean keepIndefinitely, HttpServletRequest request) {
|
||||
FileEntity fileEntity = fileService.updateKeepIndefinitely(uuid, keepIndefinitely, request);
|
||||
if (fileEntity != null) {
|
||||
logger.info("Updated keep indefinitely for file UUID: {} to {}", uuid, keepIndefinitely);
|
||||
return "redirect:/file/" + fileEntity.uuid;
|
||||
}
|
||||
return "redirect:/file/list";
|
||||
@@ -162,6 +168,7 @@ public class FileViewController {
|
||||
public String toggleHidden(@PathVariable String uuid) {
|
||||
FileEntity fileEntity = fileService.toggleHidden(uuid);
|
||||
if (fileEntity != null) {
|
||||
logger.info("Updated hidden for file UUID: {} to {}", uuid, fileEntity.hidden);
|
||||
return "redirect:/file/" + fileEntity.uuid;
|
||||
}
|
||||
return "redirect:/file/list";
|
||||
@@ -183,6 +190,7 @@ public class FileViewController {
|
||||
model.addAttribute("file", new FileEntityView(file, analyticsService.getTotalDownloadsByFile(file.uuid)));
|
||||
model.addAttribute("downloadLink", "/api/file/download/" + file.uuid + "/" + token);
|
||||
|
||||
logger.info("Accessed shared file view for file UUID: {}", file.uuid);
|
||||
return "file-share-view";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/password")
|
||||
public class PasswordController {
|
||||
public class PasswordViewController {
|
||||
@GetMapping("/login")
|
||||
public String passwordPage() {
|
||||
return "app-password";
|
||||
@@ -19,3 +19,4 @@ spring.flyway.baseline-on-migrate=true
|
||||
spring.flyway.baseline-version=1
|
||||
spring.flyway.locations=classpath:db/migration
|
||||
app.version=1.4.6
|
||||
spring.cloud.compatibility-verifier.enabled=false
|
||||
|
||||
@@ -7,3 +7,46 @@
|
||||
.dark .bg-gray-700{background-color:#374151;}
|
||||
.dark .hover\:bg-gray-600:hover{background-color:#4b5563;}
|
||||
.focus\:ring-red-500:focus{--tw-ring-color:#ef4444;}
|
||||
|
||||
/* Light-mode overrides to ensure readability when the OS prefers dark. */
|
||||
:root{color-scheme:light;}
|
||||
html.dark{color-scheme:dark;}
|
||||
html:not(.dark) nav{background-color:#ffffff!important;color:#0f172a!important;}
|
||||
html:not(.dark) nav a,html:not(.dark) nav button{color:#0f172a!important;}
|
||||
|
||||
/* Reset dark backgrounds to light in light theme (cards, inputs, sections). */
|
||||
html:not(.dark) .dark\:bg-slate-900,
|
||||
html:not(.dark) .dark\:bg-slate-900\/50,
|
||||
html:not(.dark) .dark\:bg-slate-800,
|
||||
html:not(.dark) .dark\:bg-slate-700,
|
||||
html:not(.dark) .dark\:bg-gray-900,
|
||||
html:not(.dark) .dark\:bg-gray-800,
|
||||
html:not(.dark) .dark\:bg-gray-700,
|
||||
html:not(.dark) .dark\:bg-sky-900{
|
||||
background-color:#ffffff!important;
|
||||
color:#0f172a!important;
|
||||
}
|
||||
|
||||
/* Reset dark text utilities to normal dark text for light theme. */
|
||||
html:not(.dark) .dark\:text-gray-100,
|
||||
html:not(.dark) .dark\:text-gray-300,
|
||||
html:not(.dark) .dark\:text-gray-400,
|
||||
html:not(.dark) .dark\:text-sky-100,
|
||||
html:not(.dark) .dark\:text-sky-300,
|
||||
html:not(.dark) .dark\:text-slate-300,
|
||||
html:not(.dark) .dark\:text-slate-400{
|
||||
color:#0f172a!important;
|
||||
}
|
||||
|
||||
/* Reset borders/placeholders that are dark-only. */
|
||||
html:not(.dark) .dark\:border-gray-600,
|
||||
html:not(.dark) .dark\:border-slate-600,
|
||||
html:not(.dark) .dark\:border-slate-700{
|
||||
border-color:#e5e7eb!important;
|
||||
}
|
||||
html:not(.dark) .dark\:placeholder-gray-400::placeholder{color:#6b7280!important;}
|
||||
|
||||
/* Zebra striping helpers for light mode tables. */
|
||||
html:not(.dark) .light-table-zebra thead{background-color:#e5e7eb!important;}
|
||||
html:not(.dark) .light-table-zebra tbody tr:nth-child(odd){background-color:#f9fafb!important;}
|
||||
html:not(.dark) .light-table-zebra tbody tr:nth-child(even){background-color:#ffffff!important;}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
File diff suppressed because one or more lines are too long
@@ -4,12 +4,12 @@ function confirmDelete() {
|
||||
|
||||
function updateCheckboxState(event, checkbox) {
|
||||
event.preventDefault();
|
||||
const hiddenField = checkbox.form.querySelector('input[name="keepIndefinitely"][type="hidden"]');
|
||||
|
||||
const hiddenField = checkbox.form.querySelector(`input[name="${checkbox.name}"][type="hidden"]`);
|
||||
if (hiddenField) {
|
||||
hiddenField.value = checkbox.checked;
|
||||
}
|
||||
|
||||
console.log('Submitting form...');
|
||||
checkbox.form.submit();
|
||||
}
|
||||
|
||||
@@ -93,11 +93,14 @@ function createShareLink() {
|
||||
|
||||
function updateShareLink(link) {
|
||||
const shareLinkInput = document.getElementById('shareLink');
|
||||
const qrCodeContainer = document.getElementById('shareQRCode');
|
||||
const canvas = document.getElementById('shareQRCode');
|
||||
|
||||
shareLinkInput.value = link;
|
||||
qrCodeContainer.innerHTML = '';
|
||||
QRCode.toCanvas(qrCodeContainer, link, {width: 150, height: 150});
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
QRCode.toCanvas(canvas, link, {width: 150, height: 150});
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +153,4 @@ function positionShareModal() {
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeModal();
|
||||
openShareModal();
|
||||
window.addEventListener('resize', positionShareModal);
|
||||
});
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
// tailwindTheme.js
|
||||
// Minimal theme toggler for Tailwind
|
||||
// Unified theme toggler for Tailwind
|
||||
(function () {
|
||||
const html = document.documentElement;
|
||||
|
||||
function applyTheme(theme) {
|
||||
const applyTheme = (theme) => {
|
||||
html.classList.toggle('dark', theme === 'dark');
|
||||
}
|
||||
localStorage.setItem('theme', theme);
|
||||
};
|
||||
|
||||
const updateToggleButtons = (theme) => {
|
||||
document.querySelectorAll('#themeToggle').forEach((btn) => {
|
||||
btn.textContent = theme === 'dark' ? '☀️' : '🌙';
|
||||
btn.setAttribute('aria-label', theme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme');
|
||||
});
|
||||
};
|
||||
|
||||
const stored = localStorage.getItem('theme');
|
||||
const initial = stored || 'light';
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const initial = stored || (prefersDark ? 'dark' : 'light');
|
||||
|
||||
applyTheme(initial);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('themeToggle');
|
||||
if (!btn) return;
|
||||
btn.textContent = html.classList.contains('dark') ? '☀️' : '🌙';
|
||||
btn.addEventListener('click', () => {
|
||||
const newTheme = html.classList.contains('dark') ? 'light' : 'dark';
|
||||
applyTheme(newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
btn.textContent = newTheme === 'dark' ? '☀️' : '🌙';
|
||||
updateToggleButtons(initial);
|
||||
|
||||
document.querySelectorAll('#themeToggle').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const nextTheme = html.classList.contains('dark') ? 'light' : 'dark';
|
||||
applyTheme(nextTheme);
|
||||
updateToggleButtons(nextTheme);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-disable-theme-toggle>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Enter Admin Password</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<button aria-label="Toggle theme"
|
||||
class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -28,16 +32,24 @@
|
||||
</header>
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 md:col-span-8 md:col-start-3 lg:col-span-6 lg:col-start-4">
|
||||
<form id="adminPasswordForm" method="POST" th:action="@{/admin/password}" class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6">
|
||||
<form class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6"
|
||||
id="adminPasswordForm" method="POST"
|
||||
th:action="@{/admin/password}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<label class="block w-full">
|
||||
<span class="sr-only">Admin Password</span>
|
||||
<input id="adminPassword" name="password" type="password" required placeholder="Admin Password" class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none" style="line-height:2.7rem" />
|
||||
<input class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none"
|
||||
id="adminPassword" name="password" placeholder="Admin Password" required
|
||||
style="line-height:2.7rem"
|
||||
type="password"/>
|
||||
</label>
|
||||
<div class="pt-2 text-center">
|
||||
<button type="submit" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Submit</button>
|
||||
<button class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
<p th:if="${error}" th:text="${error}" class="text-red-700 dark:text-red-300"></p>
|
||||
<p class="text-red-700 dark:text-red-300" th:if="${error}" th:text="${error}"></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,65 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-disable-theme-toggle>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/js/darkMode.js"></script>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
Password
|
||||
Required</title>
|
||||
<meta content="width=device-width, initial-scale=1"
|
||||
name="viewport">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet">
|
||||
<link href="/images/favicon.png"
|
||||
rel="icon"
|
||||
type="image/png">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Main Content -->
|
||||
<div class="container">
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<h2 class="text-center mb-4">
|
||||
Please
|
||||
Enter
|
||||
the
|
||||
Password
|
||||
to
|
||||
Continue</h2>
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<form id="passwordForm"
|
||||
method="POST">
|
||||
<input th:name="${_csrf.parameterName}"
|
||||
th:value="${_csrf.token}"
|
||||
type="hidden"/>
|
||||
<div class="mb-3">
|
||||
<label class="form-label"
|
||||
for="password">Password:</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
type="password"
|
||||
>
|
||||
</div>
|
||||
<button class="btn btn-primary w-100"
|
||||
type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
<div th:if="${error}">
|
||||
<p class="text-danger mt-3"
|
||||
th:text="${error}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<div class="max-w-4xl mx-auto px-4 py-10">
|
||||
<div class="flex justify-end mb-6">
|
||||
<button aria-label="Toggle theme"
|
||||
class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">🌙</button>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-lg p-6 md:p-8 max-w-xl mx-auto">
|
||||
<div class="text-center space-y-2 mb-6">
|
||||
<p class="text-sm uppercase tracking-wide text-slate-500 dark:text-slate-400">Protected</p>
|
||||
<h1 class="text-3xl font-semibold tracking-tight">Password Required</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300">Enter the app password to continue.</p>
|
||||
</div>
|
||||
<form id="passwordForm" method="POST" class="space-y-4">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<label class="block" for="password">
|
||||
<span class="sr-only">Password</span>
|
||||
<input class="mt-1 w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 text-lg focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
type="password"
|
||||
placeholder="Password" />
|
||||
</label>
|
||||
<button class="w-full rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-semibold py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
<div th:if="${error}">
|
||||
<p class="text-red-700 dark:text-red-300 mt-3" th:text="${error}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/js/fileView.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,25 +3,28 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>QuickDrop Admin</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop Admin</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/admin/settings" class="hover:text-white">Settings</a>
|
||||
<button id="themeToggle" type="button"
|
||||
class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<a class="hover:text-white" href="/file/list">View Files</a>
|
||||
<a class="hover:text-white" href="/admin/settings">Settings</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -53,7 +56,7 @@
|
||||
<div class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg">
|
||||
<h2 class="text-xl font-semibold tracking-tight mb-4">Files</h2>
|
||||
<div class="overflow-x-auto w-full">
|
||||
<table class="min-w-full w-full divide-y divide-slate-200 dark:divide-slate-700">
|
||||
<table class="min-w-full w-full divide-y divide-slate-200 dark:divide-slate-700 light-table-zebra">
|
||||
<thead class="bg-gray-100 dark:bg-gray-800 divide-x divide-slate-200 dark:divide-slate-600">
|
||||
<tr>
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold">Name</th>
|
||||
@@ -63,48 +66,62 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<th:block th:each="file : ${files}">
|
||||
<tr class="bg-gray-50 dark:bg-slate-800 divide-x divide-slate-200 dark:divide-slate-600">
|
||||
<th:block th:each="file,iter : ${files}">
|
||||
<tr class="dark:bg-slate-800 divide-x divide-slate-200 dark:divide-slate-600" th:classappend="${iter.index % 2 == 0} ? ' bg-gray-50' : ' bg-white'">
|
||||
<td class="px-6 py-4" th:text="${file.name}">File Name</td>
|
||||
<td class="px-6 py-4" th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}">Date</td>
|
||||
<td class="px-6 py-4" th:text="${file.size}">--</td>
|
||||
<td class="px-6 py-4" th:text="${file.totalDownloads}">--</td>
|
||||
</tr>
|
||||
<tr class="bg-white dark:bg-slate-900 divide-x divide-slate-200 dark:divide-slate-600 border-b border-gray-400 dark:border-gray-600">
|
||||
<td class="px-6 pt-4 pb-6" colspan="2">
|
||||
<div class="flex flex-wrap gap-4 pb-2">
|
||||
<a th:href="@{/file/{uuid}(uuid=${file.uuid})}"
|
||||
class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">View</a>
|
||||
<a th:href="@{/file/history/{uuid}(uuid=${file.uuid})}"
|
||||
class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">History</a>
|
||||
<a th:href="@{/file/download/{uuid}(uuid=${file.uuid})}"
|
||||
class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Download</a>
|
||||
<form class="inline" method="post" onsubmit="return confirmDelete();" th:action="@{/admin/delete/{uuid}(uuid=${file.uuid})}">
|
||||
<tr class="dark:bg-slate-900 divide-x divide-slate-200 dark:divide-slate-600 border-b border-gray-400 dark:border-gray-600" th:classappend="${iter.index % 2 == 0} ? ' bg-gray-100' : ' bg-gray-50'">
|
||||
<td class="px-6 py-3" colspan="2">
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<a class="inline-flex rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-1.5 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
th:href="@{/file/{uuid}(uuid=${file.uuid})}">View</a>
|
||||
|
||||
<a class="inline-flex rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-1.5 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
th:href="@{/file/history/{uuid}(uuid=${file.uuid})}">History</a>
|
||||
|
||||
<a class="inline-flex rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-1.5 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
th:href="@{/file/download/{uuid}(uuid=${file.uuid})}">Download</a>
|
||||
|
||||
<form class="m-0" method="post"
|
||||
onsubmit="return confirmDelete();"
|
||||
th:action="@{/admin/delete/{uuid}(uuid=${file.uuid})}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<button type="submit" class="rounded-lg bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 text-white font-medium px-4 py-2 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-red-500">Delete</button>
|
||||
<button class="inline-flex rounded-lg bg-red-600 hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 text-white font-medium px-4 py-1.5 text-sm transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
type="submit">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<form class="inline" method="post" th:action="@{/admin/keep-indefinitely/{uuid}(uuid=${file.uuid})}">
|
||||
<td class="px-6 py-3 text-center align-middle">
|
||||
<form class="inline" method="post"
|
||||
th:action="@{/admin/keep-indefinitely/{uuid}(uuid=${file.uuid})}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<input name="keepIndefinitely" type="hidden" value="false"/>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="keepIndefinitely" value="true" th:checked="${file.keepIndefinitely}"
|
||||
onchange="updateCheckboxState(event, this)"
|
||||
class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"/>
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"
|
||||
name="keepIndefinitely" onchange="updateCheckboxState(event, this)"
|
||||
th:checked="${file.keepIndefinitely}"
|
||||
type="checkbox"
|
||||
value="true"/>
|
||||
<span class="text-sm">Keep indefinitely</span>
|
||||
</label>
|
||||
</form>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<form class="inline" method="post" th:action="@{/admin/toggle-hidden/{uuid}(uuid=${file.uuid})}">
|
||||
<form class="inline" method="post"
|
||||
th:action="@{/admin/toggle-hidden/{uuid}(uuid=${file.uuid})}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<input name="hidden" type="hidden" value="false"/>
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input type="checkbox" name="hidden" value="true" th:checked="${file.hidden}"
|
||||
onchange="updateHiddenState(event, this)"
|
||||
class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"/>
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"
|
||||
name="hidden" onchange="updateHiddenState(event, this)"
|
||||
th:checked="${file.hidden}"
|
||||
type="checkbox"
|
||||
value="true"/>
|
||||
<span class="text-sm">Hidden</span>
|
||||
</label>
|
||||
</form>
|
||||
|
||||
@@ -3,24 +3,29 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Error</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a th:if="${isAdminDashboardButtonEnabled}" href="/admin/dashboard" onclick="requestAdminPassword()" class="hover:text-white">Admin Dashboard</a>
|
||||
<button id="themeToggle" type="button" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<a class="hover:text-white" href="/admin/dashboard" onclick="requestAdminPassword()"
|
||||
th:if="${isAdminDashboardButtonEnabled}">Admin Dashboard</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -30,7 +35,9 @@
|
||||
<div class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg text-center space-y-4">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">Oops!</h1>
|
||||
<p>Something went wrong. Please try again later.</p>
|
||||
<a href="/" class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Go Back to Main Page</a>
|
||||
<a class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
href="/">Go
|
||||
Back to Main Page</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,120 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/js/darkMode.js"></script>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Download History</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.file-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.file-info-item {
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.file-description {
|
||||
font-size: 1.1rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img alt="QuickDrop Logo" class="me-2" height="40" src="/images/favicon.png">
|
||||
QuickDrop Admin
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="QuickDrop Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop Admin</span>
|
||||
</a>
|
||||
<button aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
|
||||
data-bs-target="#navbarNav" data-bs-toggle="collapse" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button id="themeToggle" type="button" class="btn btn-sm ms-2">🌙</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a class="hover:text-white" href="/admin/dashboard">Dashboard</a>
|
||||
<button aria-label="Toggle theme" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500" id="themeToggle" type="button">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">History</h1>
|
||||
<div class="container mx-auto mt-5 px-4">
|
||||
<h1 class="text-center text-3xl font-semibold mb-4">History</h1>
|
||||
|
||||
<!-- File Name and Description -->
|
||||
<div class="text-center">
|
||||
<p class="file-name" th:text="${file.name}">File Name</p>
|
||||
<p class="file-description" th:text="${file.description ?: 'No description provided'}">File Description</p>
|
||||
<div class="text-center mb-8">
|
||||
<p class="text-xl font-semibold text-blue-600" th:text="${file.name}">File Name</p>
|
||||
<p class="text-lg text-gray-600 dark:text-gray-300" th:text="${file.description ?: 'No description provided'}">
|
||||
File Description</p>
|
||||
</div>
|
||||
|
||||
<!-- File Details Grid -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-info text-white text-center">
|
||||
File Details
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="file-info">
|
||||
<div class="file-info-item">
|
||||
<strong th:text="${file.size}"></strong>
|
||||
<span>Size</span>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<strong th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></strong>
|
||||
<span>Upload Date</span>
|
||||
</div>
|
||||
<div class="file-info-item">
|
||||
<strong th:text="${file.totalDownloads}"></strong>
|
||||
<span>Total Downloads</span>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-lg mb-8 p-6">
|
||||
<div class="text-center text-xl font-semibold mb-4">File Details</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||
<div class="bg-sky-500 text-white p-4 rounded-lg">
|
||||
<p class="text-2xl font-semibold" th:text="${file.size}">--</p>
|
||||
<p class="text-sm">Size</p>
|
||||
</div>
|
||||
<div class="bg-sky-500 text-white p-4 rounded-lg">
|
||||
<p class="text-2xl font-semibold" th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}">--</p>
|
||||
<p class="text-sm">Upload Date</p>
|
||||
</div>
|
||||
<div class="bg-sky-500 text-white p-4 rounded-lg">
|
||||
<p class="text-2xl font-semibold" th:text="${file.totalDownloads}">--</p>
|
||||
<p class="text-sm">Total Downloads</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History Table -->
|
||||
<table class="table table-striped text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Action</th>
|
||||
<th>IP Address</th>
|
||||
<th>User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="log : ${actionLogs}">
|
||||
<td th:text="${#temporals.format(log.actionDate, 'dd.MM.yyyy HH:mm:ss')}">01.12.2024 20:12:22</td>
|
||||
<td th:text="${log.actionType}">Action</td>
|
||||
<td th:text="${log.ipAddress}">127.0.0.1</td>
|
||||
<td th:text="${log.userAgent}">Mozilla/5.0 (Windows NT 10.0; Win64; x64)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-lg p-6">
|
||||
<h2 class="text-xl font-semibold tracking-tight mb-4">Download History</h2>
|
||||
|
||||
<!-- scroll container -->
|
||||
<div class="-mx-6 overflow-x-auto">
|
||||
<div class="inline-block min-w-full align-middle px-6">
|
||||
<table class="min-w-max w-full divide-y divide-slate-200 dark:divide-slate-700 light-table-zebra">
|
||||
<thead class="bg-gray-100 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold whitespace-nowrap">Date</th>
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold whitespace-nowrap">Action</th>
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold whitespace-nowrap">IP Address</th>
|
||||
<th class="px-6 py-4 text-left text-sm font-semibold whitespace-nowrap">User Agent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-slate-200 dark:divide-slate-700">
|
||||
<tr class="dark:bg-slate-800" th:each="log,iter : ${actionLogs}" th:classappend="${iter.index % 2 == 0} ? ' bg-gray-50' : ' bg-white'">
|
||||
<td class="px-6 py-4 whitespace-nowrap"
|
||||
th:text="${#temporals.format(log.actionDate, 'dd.MM.yyyy HH:mm:ss')}">01.12.2024 20:12:22
|
||||
</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap" th:text="${log.actionType}">Download</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap" th:text="${log.ipAddress}">127.0.0.1</td>
|
||||
|
||||
<!-- key: allow breaking long UA strings -->
|
||||
<td class="px-6 py-4 max-w-[28rem] break-words"
|
||||
th:text="${log.userAgent}">Mozilla/5.0 ...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap Bundle -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-disable-theme-toggle>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Enter Password</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<button aria-label="Toggle theme"
|
||||
class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -28,48 +32,71 @@
|
||||
</header>
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 md:col-span-8 md:col-start-3 lg:col-span-6 lg:col-start-4">
|
||||
<form id="passwordForm" method="post" th:action="@{/file/password}" class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6">
|
||||
<form class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6"
|
||||
id="passwordForm" method="post"
|
||||
th:action="@{/file/password}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<input name="uuid" th:value="${uuid}" type="hidden"/>
|
||||
<h2 th:if="${fileName}" th:text="${fileName}" class="text-center text-lg font-semibold text-gray-900 dark:text-gray-100"></h2>
|
||||
<label for="password-input" class="sr-only">Password</label>
|
||||
<h2 class="text-center text-lg font-semibold text-gray-900 dark:text-gray-100" th:if="${fileName}"
|
||||
th:text="${fileName}"></h2>
|
||||
<label class="sr-only" for="password-input">Password</label>
|
||||
<div class="relative w-full">
|
||||
<input id="password-input" name="password" type="password" autocomplete="current-password" required placeholder="Enter the file password…" class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 pr-12 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none" style="line-height:2.7rem" />
|
||||
<button type="button" id="showPassword" aria-label="Show password" class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 focus-visible:ring-2 focus-visible:ring-sky-500 p-2">
|
||||
<svg id="eyeIcon" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<input autocomplete="current-password"
|
||||
class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 pr-12 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none"
|
||||
id="password-input" name="password" placeholder="Enter the file password…"
|
||||
required
|
||||
style="line-height:2.7rem"
|
||||
type="password"/>
|
||||
<button aria-label="Show password"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 focus-visible:ring-2 focus-visible:ring-sky-500 p-2"
|
||||
id="showPassword"
|
||||
type="button">
|
||||
<svg class="h-5 w-5" fill="none" id="eyeIcon" stroke="currentColor"
|
||||
stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="eye-open">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.03555 12.3224C1.96647 12.1151 1.9664 11.8907 2.03536 11.6834C3.42372 7.50972 7.36079 4.5 12.0008 4.5C16.6387 4.5 20.5742 7.50692 21.9643 11.6776C22.0334 11.8849 22.0335 12.1093 21.9645 12.3166C20.5761 16.4903 16.6391 19.5 11.9991 19.5C7.36119 19.5 3.42564 16.4931 2.03555 12.3224Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" />
|
||||
<path d="M2.03555 12.3224C1.96647 12.1151 1.9664 11.8907 2.03536 11.6834C3.42372 7.50972 7.36079 4.5 12.0008 4.5C16.6387 4.5 20.5742 7.50692 21.9643 11.6776C22.0334 11.8849 22.0335 12.1093 21.9645 12.3166C20.5761 16.4903 16.6391 19.5 11.9991 19.5C7.36119 19.5 3.42564 16.4931 2.03555 12.3224Z"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
</g>
|
||||
<g id="eye-closed" class="hidden">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.97993 8.22257C3.05683 9.31382 2.35242 10.596 1.93436 12.0015C3.22565 16.338 7.24311 19.5 11.9991 19.5C12.9917 19.5 13.9521 19.3623 14.8623 19.1049M6.22763 6.22763C7.88389 5.13558 9.86771 4.5 12 4.5C16.756 4.5 20.7734 7.66205 22.0647 11.9985C21.3528 14.3919 19.8106 16.4277 17.772 17.772" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.22763 6.22763L3 3M6.22763 6.22763L9.87868 9.87868M17.772 17.772L21 21M17.772 17.772L14.1213 14.1213M14.1213 14.1213C14.6642 13.5784 15 12.8284 15 12C15 10.3431 13.6569 9 12 9C11.1716 9 10.4216 9.33579 9.87868 9.87868M14.1213 14.1213L9.87868 9.87868" />
|
||||
<g class="hidden" id="eye-closed">
|
||||
<path d="M3.97993 8.22257C3.05683 9.31382 2.35242 10.596 1.93436 12.0015C3.22565 16.338 7.24311 19.5 11.9991 19.5C12.9917 19.5 13.9521 19.3623 14.8623 19.1049M6.22763 6.22763C7.88389 5.13558 9.86771 4.5 12 4.5C16.756 4.5 20.7734 7.66205 22.0647 11.9985C21.3528 14.3919 19.8106 16.4277 17.772 17.772"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
<path d="M6.22763 6.22763L3 3M6.22763 6.22763L9.87868 9.87868M17.772 17.772L21 21M17.772 17.772L14.1213 14.1213M14.1213 14.1213C14.6642 13.5784 15 12.8284 15 12C15 10.3431 13.6569 9 12 9C11.1716 9 10.4216 9.33579 9.87868 9.87868M14.1213 14.1213L9.87868 9.87868"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pt-2 text-center">
|
||||
<button type="submit" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Submit</button>
|
||||
<button class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
type="submit">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('showPassword');
|
||||
const input = document.getElementById('password-input');
|
||||
const eyeOpen = document.querySelector('#eye-open');
|
||||
const eyeClosed = document.querySelector('#eye-closed');
|
||||
if (btn && input && eyeOpen && eyeClosed) {
|
||||
btn.addEventListener('click', () => {
|
||||
const show = input.getAttribute('type') === 'password';
|
||||
input.setAttribute('type', show ? 'text' : 'password');
|
||||
eyeOpen.classList.toggle('hidden', show);
|
||||
eyeClosed.classList.toggle('hidden', !show);
|
||||
});
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const btn = document.getElementById('showPassword');
|
||||
const input = document.getElementById('password-input');
|
||||
const eyeOpen = document.querySelector('#eye-open');
|
||||
const eyeClosed = document.querySelector('#eye-closed');
|
||||
if (btn && input && eyeOpen && eyeClosed) {
|
||||
btn.addEventListener('click', () => {
|
||||
const show = input.getAttribute('type') === 'password';
|
||||
input.setAttribute('type', show ? 'text' : 'password');
|
||||
eyeOpen.classList.toggle('hidden', show);
|
||||
eyeClosed.classList.toggle('hidden', !show);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,45 +1,52 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/js/darkMode.js"></script>
|
||||
<meta charset="UTF-8">
|
||||
<title>Shared File View</title>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center mb-4">Shared File</h1>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-8 col-lg-6">
|
||||
<div class="card shadow">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-center" th:text="${file.name}">File Name</h5>
|
||||
|
||||
<p class="card-text text-center" th:text="${file.description}">Description</p>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center border-top pt-3">
|
||||
<h5 class="card-title mb-0">Uploaded At:</h5>
|
||||
<p class="card-text mb-0" th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center pt-3">
|
||||
<h5 class="card-title">File Size:</h5>
|
||||
<p class="card-text" th:text="${file.size}"></p>
|
||||
</div>
|
||||
|
||||
<h5 class="card-title border-top pt-3"></h5>
|
||||
<a
|
||||
class="btn btn-success w-100"
|
||||
id="downloadButton"
|
||||
th:href="${downloadLink}">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-5xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="QuickDrop Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button aria-label="Toggle theme" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500" id="themeToggle" type="button">🌙</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</nav>
|
||||
|
||||
<main class="max-w-5xl mx-auto px-4 py-10">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-lg p-6 md:p-8 space-y-6">
|
||||
<header class="text-center space-y-2">
|
||||
<p class="text-sm uppercase tracking-wide text-slate-500 dark:text-slate-400">Shared File</p>
|
||||
<h1 class="text-3xl font-semibold tracking-tight" th:text="${file.name}">File Name</h1>
|
||||
<p class="text-gray-700 dark:text-gray-300" th:if="${file.description}" th:text="${file.description}">Description</p>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div class="flex items-center justify-between rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 px-4 py-3">
|
||||
<span class="text-sm font-semibold text-slate-600 dark:text-slate-300">Uploaded</span>
|
||||
<span class="text-base font-medium" th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-xl border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-900/50 px-4 py-3">
|
||||
<span class="text-sm font-semibold text-slate-600 dark:text-slate-300">File Size</span>
|
||||
<span class="text-base font-medium" th:text="${file.size}"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-2">
|
||||
<a class="inline-flex w-full justify-center items-center rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-semibold px-4 py-3 transition-colors active:scale-95" id="downloadButton" th:href="${downloadLink}">
|
||||
Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -2,27 +2,32 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="_csrf" th:content="${_csrf.token}" />
|
||||
<meta name="_csrf" th:content="${_csrf.token}"/>
|
||||
<title>File View</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.4.4/build/qrcode.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a th:if="${isAdminDashboardButtonEnabled}" href="/admin/dashboard" onclick="requestAdminPassword()" class="hover:text-white">Admin Dashboard</a>
|
||||
<button id="themeToggle" type="button" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<a class="hover:text-white" href="/admin/dashboard" onclick="requestAdminPassword()"
|
||||
th:if="${isAdminDashboardButtonEnabled}">Admin Dashboard</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -30,25 +35,33 @@
|
||||
<header class="text-center mb-8 space-y-1">
|
||||
<h1 class="text-3xl md:text-4xl font-semibold tracking-tight">File View</h1>
|
||||
</header>
|
||||
<span id="fileUuid" th:text="${file.uuid}" hidden></span>
|
||||
<span id="downloadLink" th:text="${downloadLink}" hidden></span>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="md:col-span-1">
|
||||
<div id="fileInfoCard" class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-4 w-full max-w-xs">
|
||||
<span hidden id="fileUuid" th:text="${file.uuid}"></span>
|
||||
<span hidden id="downloadLink" th:text="${downloadLink}"></span>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6 items-start">
|
||||
<section class="sm:col-span-2">
|
||||
<div class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-4 w-full"
|
||||
id="fileInfoCard">
|
||||
<h2 class="text-center text-xl font-semibold tracking-tight" th:text="${file.name}">File Name</h2>
|
||||
<p th:if="${!#strings.isEmpty(file.description)}" th:text="${file.description}" class="text-center text-gray-700 dark:text-gray-300"></p>
|
||||
<p class="text-center text-gray-700 dark:text-gray-300" th:if="${!#strings.isEmpty(file.description)}"
|
||||
th:text="${file.description}"></p>
|
||||
<div class="flex justify-between border-t pt-4">
|
||||
<span class="font-semibold" th:text="${file.keepIndefinitely} ? 'Uploaded at:' : 'Uploaded/Renewed at:'"></span>
|
||||
<span class="font-semibold"
|
||||
th:text="${file.keepIndefinitely} ? 'Uploaded at:' : 'Uploaded/Renewed at:'"></span>
|
||||
<span th:text="${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></span>
|
||||
</div>
|
||||
<small th:if="${file.keepIndefinitely == false}" class="text-gray-600 dark:text-gray-400">Files are kept only for <span th:text="${maxFileLifeTime}">30</span> days after this date.</small>
|
||||
<small class="text-gray-600 dark:text-gray-400" th:if="${file.keepIndefinitely == false}">Files are kept
|
||||
only for <span th:text="${maxFileLifeTime}">30</span> days after this date.</small>
|
||||
<div class="flex justify-between items-center pt-4">
|
||||
<span class="font-semibold">Keep indefinitely:</span>
|
||||
<form method="post" th:action="@{/file/keep-indefinitely/{uuid}(uuid=${file.uuid})}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
|
||||
<input name="keepIndefinitely" type="hidden" value="false">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input id="keepIndefinitely" name="keepIndefinitely" type="checkbox" th:checked="${file.keepIndefinitely}" th:disabled="${file.passwordHash == null}" value="true" onchange="updateCheckboxState(event,this)" class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500">
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"
|
||||
id="keepIndefinitely" name="keepIndefinitely"
|
||||
onchange="updateCheckboxState(event,this)" th:checked="${file.keepIndefinitely}"
|
||||
th:disabled="${file.passwordHash == null}" type="checkbox"
|
||||
value="true">
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
@@ -58,7 +71,11 @@
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
|
||||
<input name="hidden" type="hidden" value="false">
|
||||
<label class="inline-flex items-center gap-2">
|
||||
<input id="hidden" name="hidden" type="checkbox" th:checked="${file.hidden}" value="true" onchange="updateCheckboxState(event,this)" class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500">
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"
|
||||
id="hidden" name="hidden" onchange="updateCheckboxState(event,this)"
|
||||
th:checked="${file.hidden}"
|
||||
type="checkbox"
|
||||
value="true">
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
@@ -66,62 +83,104 @@
|
||||
<span class="font-semibold">File Size:</span>
|
||||
<span th:text="${fileSize}"></span>
|
||||
</div>
|
||||
<div id="preparingMessage" class="hidden bg-sky-50 dark:bg-sky-900 text-sky-800 dark:text-sky-300 rounded-lg p-2">Your file is being prepared for download. Please wait...</div>
|
||||
<div class="hidden bg-sky-50 dark:bg-sky-900 text-sky-800 dark:text-sky-300 rounded-lg p-2"
|
||||
id="preparingMessage">Your file is
|
||||
being prepared for download. Please wait...
|
||||
</div>
|
||||
<div class="mt-4 flex flex-wrap justify-center gap-4">
|
||||
<a id="downloadButton" th:href="@{/file/download/{uuid}(uuid=${file.uuid})}" th:onclick="${file.passwordHash != null} ? 'showPreparingMessage()' : ''" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95">Download</a>
|
||||
<form method="post" th:action="@{/file/delete/{uuid}(uuid=${file.uuid})}" onsubmit="return confirmDelete();" th:if="${file.passwordHash != null}">
|
||||
<a class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95"
|
||||
id="downloadButton"
|
||||
th:href="@{/file/download/{uuid}(uuid=${file.uuid})}"
|
||||
th:onclick="${file.passwordHash != null} ? 'showPreparingMessage()' : ''">Download</a>
|
||||
<form method="post" onsubmit="return confirmDelete();"
|
||||
th:action="@{/file/delete/{uuid}(uuid=${file.uuid})}" th:if="${file.passwordHash != null}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
|
||||
<button type="submit" class="rounded-lg bg-red-600 hover:bg-red-700 text-white font-medium px-4 py-2 transition-colors active:scale-95">Delete File</button>
|
||||
<button class="rounded-lg bg-red-600 hover:bg-red-700 text-white font-medium px-4 py-2 transition-colors active:scale-95"
|
||||
type="submit">
|
||||
Delete File
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" th:action="@{/file/extend/{uuid}(uuid=${file.uuid})}" th:if="${file.keepIndefinitely == false}">
|
||||
<form method="post" th:action="@{/file/extend/{uuid}(uuid=${file.uuid})}"
|
||||
th:if="${file.keepIndefinitely == false}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden">
|
||||
<button type="submit" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95">Renew File Lifetime</button>
|
||||
<button class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95"
|
||||
type="submit">
|
||||
Renew File Lifetime
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<aside class="sm:col-span-1">
|
||||
<div class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg space-y-4 w-full"
|
||||
id="sharePanel">
|
||||
<h2 class="text-xl font-semibold tracking-tight">Share</h2>
|
||||
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400"
|
||||
th:if="${file.passwordHash != null || isAppPasswordSet}">
|
||||
<p>
|
||||
By default, this link requires a password to access the file if the file is password-protected
|
||||
or if the app password is enabled.
|
||||
<br>You can generate an unrestricted link valid for a specific number of days and downloads.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<input class="w-full min-w-0 flex-1 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2"
|
||||
id="shareLink" readonly
|
||||
type="text">
|
||||
<button class="w-full sm:w-auto shrink-0 rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2"
|
||||
onclick="copyShareLink()"
|
||||
type="button">
|
||||
Copy
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<canvas class="mx-auto" height="150" id="shareQRCode" width="150"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-2"
|
||||
th:if="${file.passwordHash != null || isAppPasswordSet == true}">
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500"
|
||||
id="unrestrictedLink" onchange="toggleLinkType()"
|
||||
type="checkbox">
|
||||
<label class="text-sm" for="unrestrictedLink">Generate an unrestricted link</label>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 hidden" id="linkOptions">
|
||||
<div>
|
||||
<label class="block text-sm" for="daysValid">Days valid:</label>
|
||||
<input class="mt-1 w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2"
|
||||
id="daysValid" min="1" type="number"
|
||||
value="30">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm" for="allowedNumberOfDownloadsCount">Allowed downloads:</label>
|
||||
<input class="mt-1 w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2"
|
||||
id="allowedNumberOfDownloadsCount" min="1" type="number"
|
||||
value="1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end items-center gap-2"
|
||||
th:if="${file.passwordHash != null || isAppPasswordSet == true}">
|
||||
<div class="hidden animate-spin rounded-full h-5 w-5 border-2 border-sky-500 border-t-transparent"
|
||||
id="spinner"></div>
|
||||
<button class="hidden rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2"
|
||||
disabled id="generateLinkButton"
|
||||
onclick="createShareLink()"
|
||||
type="button">
|
||||
Generate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Share Modal -->
|
||||
<div id="shareModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden">
|
||||
<div id="shareModalContent" class="absolute bg-white dark:bg-slate-800 rounded-2xl shadow-lg p-6 w-full max-w-md space-y-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<h2 class="text-xl font-semibold tracking-tight" id="shareModalLabel">Share File</h2>
|
||||
</div>
|
||||
<div th:if="${file.passwordHash != null || isAppPasswordSet}" class="text-sm text-gray-600 dark:text-gray-400">
|
||||
<p>By default, this link requires a password to access the file if the file is password-protected or if the app password is enabled.<br>You can generate an unrestricted link valid for a specific number of days and downloads.</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<input id="shareLink" type="text" placeholder="Generated link will appear here" readonly value="" class="flex-grow rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2">
|
||||
<button type="button" onclick="copyShareLink()" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium whitespace-nowrap px-10 py-2">Copy Link</button>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<canvas id="shareQRCode" class="mx-auto" style="width:150px;height:150px;"></canvas>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div th:if="${file.passwordHash != null || isAppPasswordSet == true}" class="flex items-center gap-2">
|
||||
<input id="unrestrictedLink" type="checkbox" onchange="toggleLinkType()" class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500">
|
||||
<label for="unrestrictedLink" class="text-sm">Generate an unrestricted link</label>
|
||||
</div>
|
||||
<div id="linkOptions" class="grid grid-cols-2 gap-4 hidden">
|
||||
<div class="col-span-1">
|
||||
<label for="daysValid" class="block text-sm">Days the link will be valid:</label>
|
||||
<input id="daysValid" type="number" min="1" value="30" class="mt-1 w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2">
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<label for="allowedNumberOfDownloadsCount" class="block text-sm">Number of allowed downloads:</label>
|
||||
<input id="allowedNumberOfDownloadsCount" type="number" min="1" value="1" class="mt-1 w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div th:if="${file.passwordHash != null || isAppPasswordSet == true}" class="flex justify-end items-center gap-2">
|
||||
<div id="spinner" class="hidden animate-spin rounded-full h-5 w-5 border-2 border-sky-500 border-t-transparent"></div>
|
||||
<button id="generateLinkButton" onclick="createShareLink()" type="button" class="hidden rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2" disabled>Generate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/fileView.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,24 +3,29 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Share Link Invalid</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a th:if="${isAdminDashboardButtonEnabled}" href="/admin/dashboard" onclick="requestAdminPassword()" class="hover:text-white">Admin Dashboard</a>
|
||||
<button id="themeToggle" type="button" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<a class="hover:text-white" href="/admin/dashboard" onclick="requestAdminPassword()"
|
||||
th:if="${isAdminDashboardButtonEnabled}">Admin Dashboard</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -29,8 +34,11 @@
|
||||
<div class="col-span-12 md:col-span-8 md:col-start-3 lg:col-span-6 lg:col-start-4">
|
||||
<div class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg text-center space-y-4">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">Link Expired</h1>
|
||||
<p class="text-gray-700 dark:text-gray-300">This share link is no longer valid. The file you are trying to access has expired or the link has been used.</p>
|
||||
<a href="/" class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Return to Homepage</a>
|
||||
<p class="text-gray-700 dark:text-gray-300">This share link is no longer valid. The file you are trying
|
||||
to access has expired or the link has been used.</p>
|
||||
<a class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
href="/">Return
|
||||
to Homepage</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,23 +3,28 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>All files</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<a th:if="${isAdminDashboardButtonEnabled}" href="/admin/dashboard" onclick="requestAdminPassword()" class="hover:text-white">Admin Dashboard</a>
|
||||
<button id="themeToggle" type="button" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<a class="hover:text-white" href="/admin/dashboard" onclick="requestAdminPassword()"
|
||||
th:if="${isAdminDashboardButtonEnabled}">Admin Dashboard</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -30,34 +35,51 @@
|
||||
<h1 class="text-3xl md:text-4xl font-semibold tracking-tight">All files</h1>
|
||||
</header>
|
||||
<div class="mx-auto w-full max-w-xl mb-8">
|
||||
<form action="/file/search" method="GET" class="w-full">
|
||||
<label for="searchInput" class="sr-only">Search files</label>
|
||||
<form action="/file/search" class="w-full" method="GET">
|
||||
<label class="sr-only" for="searchInput">Search files</label>
|
||||
<div class="h-14 flex items-center space-x-2 border border-slate-200 dark:border-slate-700 bg-white dark:bg-gray-800 rounded-full px-4 shadow-sm focus-within:border-sky-500 focus-within:ring-2 focus-within:ring-sky-500">
|
||||
<svg class="w-5 h-5 text-gray-500 dark:text-gray-400" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.9 14.32a8 8 0 111.414-1.414l4.387 4.387a1 1 0 01-1.414 1.414l-4.387-4.387zM14 8a6 6 0 11-12 0 6 6 0 0112 0z" clip-rule="evenodd"/></svg>
|
||||
<input id="searchInput" name="query" type="text" placeholder="Search files by name or date…" class="flex-grow bg-transparent py-2 focus:outline-none focus-visible:ring-0" />
|
||||
<button id="search-button" type="submit" class="rounded-full bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 h-10 transition-colors active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500">Search</button>
|
||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor"
|
||||
viewBox="0 0 20 20">
|
||||
<path clip-rule="evenodd"
|
||||
d="M12.9 14.32a8 8 0 111.414-1.414l4.387 4.387a1 1 0 01-1.414 1.414l-4.387-4.387zM14 8a6 6 0 11-12 0 6 6 0 0112 0z"
|
||||
fill-rule="evenodd"/>
|
||||
</svg>
|
||||
<input class="flex-grow bg-transparent py-2 focus:outline-none focus-visible:ring-0" id="searchInput"
|
||||
name="query" placeholder="Search files by name or date…"
|
||||
type="text"/>
|
||||
<button class="rounded-full bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 h-10 transition-colors active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500"
|
||||
id="search-button"
|
||||
type="submit">
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div th:if="${#lists.isEmpty(files)}" class="text-center text-gray-600 dark:text-gray-300 my-5">
|
||||
<div class="text-center text-gray-600 dark:text-gray-300 my-5" th:if="${#lists.isEmpty(files)}">
|
||||
<h3 class="mb-2">No files have been uploaded yet.</h3>
|
||||
<p>Start by <a href="/file/upload" class="text-sky-600 dark:text-sky-400 hover:underline">uploading a file</a>.</p>
|
||||
<p>Start by <a class="text-sky-600 dark:text-sky-400 hover:underline" href="/file/upload">uploading a file</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 p-6">
|
||||
<div th:each="file : ${files}" class="h-full">
|
||||
<div class="h-full" th:each="file : ${files}">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-lg flex flex-col h-full p-6 md:p-8">
|
||||
<div class="flex-grow space-y-3 overflow-hidden">
|
||||
<h2 class="text-xl font-semibold tracking-tight truncate" th:text="${file.name}">File Name</h2>
|
||||
<hr class="border-t border-slate-200 dark:border-slate-700">
|
||||
<p th:if="${!#strings.isEmpty(file.description)}" class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2 overflow-hidden" th:text="${file.description}"></p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2 overflow-hidden"
|
||||
th:if="${!#strings.isEmpty(file.description)}"
|
||||
th:text="${file.description}"></p>
|
||||
<div class="flex justify-between text-sm text-gray-600 dark:text-gray-400">
|
||||
<span th:text="'Keep indefinitely: ' + (${file.keepIndefinitely} ? 'Yes' : 'No')"></span>
|
||||
<span th:text="'Password protected: ' + (${file.passwordHash != null} ? 'Yes' : 'No')"></span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400" th:text="${file.keepIndefinitely} ? 'Uploaded: ' + ${#temporals.format(file.uploadDate, 'dd.MM.yyyy')} : 'Uploaded/Renewed: ' + ${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400"
|
||||
th:text="${file.keepIndefinitely} ? 'Uploaded: ' + ${#temporals.format(file.uploadDate, 'dd.MM.yyyy')} : 'Uploaded/Renewed: ' + ${#temporals.format(file.uploadDate, 'dd.MM.yyyy')}"></p>
|
||||
</div>
|
||||
<div class="mt-4 text-center">
|
||||
<a th:href="@{/file/{UUID}(UUID=${file.uuid})}" class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500">Go to file page</a>
|
||||
<a class="inline-block rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-500"
|
||||
th:href="@{/file/{UUID}(UUID=${file.uuid})}">Go
|
||||
to file page</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,181 +1,224 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<script src="/js/darkMode.js"></script>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<title>Admin Settings</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">
|
||||
<img alt="QuickDrop Logo" class="me-2" height="40" src="/images/favicon.png">
|
||||
QuickDrop Admin
|
||||
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="QuickDrop Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop Admin</span>
|
||||
</a>
|
||||
<button aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
|
||||
data-bs-target="#navbarNav" data-bs-toggle="collapse" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/dashboard">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button id="themeToggle" type="button" class="btn btn-sm ms-2">🌙</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<a class="hover:text-white" href="/admin/dashboard">Dashboard</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500" id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h1 class="mb-4 text-center">Admin Settings</h1>
|
||||
<main class="max-w-7xl mx-auto px-4 py-6 md:py-8">
|
||||
<header class="text-center mb-8 space-y-1">
|
||||
<h1 class="text-3xl md:text-4xl font-semibold tracking-tight">Admin Settings</h1>
|
||||
</header>
|
||||
|
||||
<form method="post" th:action="@{/admin/save}" th:object="${settings}">
|
||||
<form class="space-y-10" method="post" th:action="@{/admin/save}" th:object="${settings}">
|
||||
|
||||
<!-- File Settings -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5>File Settings</h5>
|
||||
<section class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold tracking-tight">File Settings</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Max File Size -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Max File Size (MB)</label>
|
||||
<input class="form-control" required th:field="*{maxFileSize}" type="text">
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Max File Size (MB)</label>
|
||||
<input class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" required th:field="*{maxFileSize}"
|
||||
type="text">
|
||||
</div>
|
||||
|
||||
<!-- Max File Lifetime -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Max File Lifetime (days)</label>
|
||||
<input class="form-control" required th:field="*{maxFileLifeTime}" type="text">
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Max File Lifetime (days)</label>
|
||||
<input class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" required th:field="*{maxFileLifeTime}"
|
||||
type="text">
|
||||
</div>
|
||||
|
||||
<!-- File Storage Path -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">File Storage Path</label>
|
||||
<input class="form-control" required th:field="*{fileStoragePath}" type="text">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium mb-1">File Storage Path</label>
|
||||
<input class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" required th:field="*{fileStoragePath}"
|
||||
type="text">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- System Settings -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5>System Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- File Deletion Cron -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">File Deletion Cron Expression</label>
|
||||
<input class="form-control" required th:field="*{fileDeletionCron}" type="text">
|
||||
<section class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg">
|
||||
<h2 class="text-xl font-semibold tracking-tight mb-4">System Settings</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
<!-- TOP: Cron (full width) -->
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-medium mb-1">File Deletion Cron Expression</label>
|
||||
<input class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" required th:field="*{fileDeletionCron}"
|
||||
type="text">
|
||||
</div>
|
||||
|
||||
<!-- Admin Session Token Lifetime -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Session Lifetime (minutes)</label>
|
||||
<small>this will impact how long file and admin sessions are kept for</small>
|
||||
<input class="form-control" min="0" required th:field="*{sessionLifeTime}" type="number">
|
||||
<!-- MIDDLE: Lifetime (left) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-1">Session Lifetime (minutes)</label>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
This impacts how long file and admin sessions are kept.
|
||||
</p>
|
||||
<input class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" min="0" required th:field="*{sessionLifeTime}"
|
||||
type="number">
|
||||
</div>
|
||||
|
||||
<!-- Enable File List Page -->
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" id="fileListPageEnabled" th:field="*{fileListPageEnabled}"
|
||||
type="checkbox">
|
||||
<label class="form-check-label" for="fileListPageEnabled">Enable File List Page</label>
|
||||
</div>
|
||||
|
||||
<!-- Default Home Page -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="defaultHomePage">Default Home Page</label>
|
||||
<select class="form-select" id="defaultHomePage" th:field="*{defaultHomePage}">
|
||||
<!-- MIDDLE: Default Home Page (right) -->
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-4 h-full">
|
||||
<label class="block text-sm font-medium mb-2" for="defaultHomePage">Default Home Page</label>
|
||||
<select class="w-full rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" id="defaultHomePage"
|
||||
th:field="*{defaultHomePage}">
|
||||
<option value="upload">Upload Page</option>
|
||||
<option value="list">File List Page</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Enable Admin Dashboard Button -->
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" id="adminDashboardButtonEnabled"
|
||||
th:field="*{adminDashboardButtonEnabled}" type="checkbox">
|
||||
<label class="form-check-label" for="adminDashboardButtonEnabled">Show Admin Dashboard
|
||||
Button</label>
|
||||
<br>
|
||||
<small class="text-muted">The Admin dashboard will still be available at /admin/dashboard</small>
|
||||
<!-- BOTTOM: File List Enabled (left) -->
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-4 h-full">
|
||||
<div class="text-sm font-medium mb-2">File List</div>
|
||||
<label class="flex items-center gap-3 pl-2">
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500" id="fileListPageEnabled" th:field="*{fileListPageEnabled}"
|
||||
type="checkbox">
|
||||
<span class="text-sm">Enable File List Page</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- BOTTOM: Admin Dashboard Button Enabled (right) -->
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-4 h-full">
|
||||
<div class="text-sm font-medium mb-2">Admin Dashboard</div>
|
||||
<label class="flex items-start gap-3 pl-2">
|
||||
<input class="mt-1 h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500" id="adminDashboardButtonEnabled"
|
||||
th:field="*{adminDashboardButtonEnabled}"
|
||||
type="checkbox">
|
||||
<span>
|
||||
<span class="text-sm">Show Admin Dashboard Button</span><br>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Still available at /admin/dashboard
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Security Settings -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5>Security Settings</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- App Password Enabled -->
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" id="appPasswordEnabled" onclick="togglePasswordField()"
|
||||
th:field="*{appPasswordEnabled}" type="checkbox">
|
||||
<label class="form-check-label">Enable Password Protection</label>
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
Protect the whole app with a password
|
||||
</small>
|
||||
<section class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg">
|
||||
<h2 class="text-xl font-semibold tracking-tight mb-4">Security Settings</h2>
|
||||
|
||||
<div class="space-y-5">
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-4">
|
||||
<label class="flex items-start gap-3 pl-2">
|
||||
<input class="mt-1 h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500" id="appPasswordEnabled" onclick="togglePasswordField()"
|
||||
th:field="*{appPasswordEnabled}"
|
||||
type="checkbox">
|
||||
<span>
|
||||
<span class="text-sm font-medium">Enable Password Protection</span><br>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">Protect the whole app with a password</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<!-- App Password -->
|
||||
<div class="mt-4 hidden" id="passwordInputGroup">
|
||||
<label class="block text-sm font-medium mb-1">App Password</label>
|
||||
<input class="w-full max-w-md rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500" th:field="*{appPassword}"
|
||||
type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App Password -->
|
||||
<div class="mb-3" id="passwordInputGroup" style="display: none;">
|
||||
<label class="form-label">App Password</label>
|
||||
<input class="form-control" th:field="*{appPassword}" type="password">
|
||||
</div>
|
||||
|
||||
<!-- Disable Encryption -->
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" id="encryptionDisabled" th:field="*{encryptionDisabled}"
|
||||
type="checkbox">
|
||||
<label class="form-check-label" for="encryptionDisabled">Disable File Encryption</label>
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
If checked, files will not be encrypted even if the file is password protected is enabled.
|
||||
</small>
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-4">
|
||||
<label class="flex items-start gap-3">
|
||||
<input class="mt-1 h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:ring-sky-500" id="encryptionDisabled" th:field="*{encryptionDisabled}"
|
||||
type="checkbox">
|
||||
<span>
|
||||
<span class="text-sm font-medium">Disable File Encryption</span><br>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
If checked, files will not be encrypted even if file password protection is enabled.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="d-flex justify-content-center">
|
||||
<button class="btn btn-primary" type="submit">Save Settings</button>
|
||||
<div class="flex justify-center">
|
||||
<button class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-6 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
type="submit">
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="card mb-4 mt-5">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5>About QuickDrop</h5>
|
||||
<!-- About -->
|
||||
<section class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg mt-12">
|
||||
<h2 class="text-2xl font-semibold tracking-tight mb-6">About QuickDrop</h2>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-5">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Version</div>
|
||||
<div class="text-lg font-semibold mt-1" th:text="${aboutInfo.appVersion}">1.0.0</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-5">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Database</div>
|
||||
<div class="text-lg font-semibold mt-1">
|
||||
SQLite <span class="font-normal" th:text="${aboutInfo.sqliteVersion}">Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-5">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">Java Version</div>
|
||||
<div class="text-lg font-semibold mt-1" th:text="${aboutInfo.javaVersion}">N/A</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-slate-200 dark:border-slate-700 p-5">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">OS Info</div>
|
||||
<div class="text-lg font-semibold mt-1" th:text="${aboutInfo.osInfo}">N/A</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong>Version:</strong>
|
||||
<span th:text="${aboutInfo.appVersion}">1.0.0</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Database:</strong>
|
||||
SQLite
|
||||
<span th:text="${aboutInfo.sqliteVersion}">Unknown</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Java Version:</strong>
|
||||
<span th:text="${aboutInfo.javaVersion}">N/A</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>OS Info:</strong>
|
||||
<span th:text="${aboutInfo.osInfo}">N/A</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/js/settings.js"></script>
|
||||
|
||||
<!-- Keep existing JS hook; this makes the password field show/hide without changing your settings.js -->
|
||||
<script>
|
||||
function togglePasswordField() {
|
||||
const group = document.getElementById('passwordInputGroup');
|
||||
const enabled = document.getElementById('appPasswordEnabled')?.checked;
|
||||
if (!group) return;
|
||||
group.classList.toggle('hidden', !enabled);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Ensure correct initial state on page load
|
||||
togglePasswordField();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,23 +3,29 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Upload File</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a th:if="${isAdminDashboardButtonEnabled}" href="/admin/dashboard" class="hover:text-white">Admin Dashboard</a>
|
||||
<button id="themeToggle" type="button" aria-label="Toggle theme" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/admin/dashboard" th:if="${isAdminDashboardButtonEnabled}">Admin
|
||||
Dashboard</a>
|
||||
<button aria-label="Toggle theme"
|
||||
class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -30,52 +36,76 @@
|
||||
Accepted file types: any • Max size <span class="maxFileSize" th:text="${maxFileSize}">1GB</span>
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-300">
|
||||
Files are deleted after <span class="maxFileLifeTime" th:text="${maxFileLifeTime}">30</span> days unless “Keep indefinitely” is selected.
|
||||
Files are deleted after <span class="maxFileLifeTime" th:text="${maxFileLifeTime}">30</span> days unless
|
||||
“Keep indefinitely” is selected.
|
||||
</p>
|
||||
</header>
|
||||
<div class="grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 md:col-span-8 md:col-start-3 lg:col-span-6 lg:col-start-4">
|
||||
<form id="uploadForm" class="bg-white dark:bg-gray-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6" method="post" enctype="multipart/form-data" th:action="@{/file/upload}">
|
||||
<form class="bg-white dark:bg-gray-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6"
|
||||
enctype="multipart/form-data"
|
||||
id="uploadForm" method="post" th:action="@{/file/upload}">
|
||||
<div id="messageContainer"></div>
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<input name="uuid" th:value="${uuid}" type="hidden"/>
|
||||
<label id="dropZone" for="file" class="relative border-2 border-dashed border-gray-400 dark:border-gray-600 rounded-lg p-6 text-center cursor-pointer focus:outline-none focus:ring-2 focus:ring-sky-500">
|
||||
<span id="dropZoneInstructions" data-default-text="Drop file here or click to browse" class="block text-gray-600 dark:text-gray-300">Drop file here or click to browse</span>
|
||||
<input id="file" name="file" type="file" class="absolute inset-0 opacity-0 cursor-pointer" required/>
|
||||
<p id="selectedFile" class="hidden mt-2 text-sm text-gray-700 dark:text-gray-300"></p>
|
||||
<label class="relative border-2 border-dashed border-gray-400 dark:border-gray-600 rounded-lg p-6 text-center cursor-pointer focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
for="file"
|
||||
id="dropZone">
|
||||
<span class="block text-gray-600 dark:text-gray-300"
|
||||
data-default-text="Drop file here or click to browse"
|
||||
id="dropZoneInstructions">Drop file here or click to browse</span>
|
||||
<input class="absolute inset-0 opacity-0 cursor-pointer" id="file" name="file" required
|
||||
type="file"/>
|
||||
<p class="hidden mt-2 text-sm text-gray-700 dark:text-gray-300" id="selectedFile"></p>
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="sr-only">Description</span>
|
||||
<input id="description" name="description" type="text" placeholder="Description" class="mt-1 w-full rounded-lg border border-gray-400 dark:border-gray-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500"/>
|
||||
<input class="mt-1 w-full rounded-lg border border-gray-400 dark:border-gray-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="description" name="description" placeholder="Description"
|
||||
type="text"/>
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-6">
|
||||
<div>
|
||||
<label class="inline-flex items-start gap-2">
|
||||
<input id="keepIndefinitely" name="keepIndefinitely" type="checkbox" class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:outline-none focus:ring-2 focus:ring-sky-500">
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="keepIndefinitely" name="keepIndefinitely"
|
||||
type="checkbox">
|
||||
<span class="text-sm">Keep indefinitely</span>
|
||||
</label>
|
||||
<small class="block ml-6 mt-1 text-sm text-gray-600 dark:text-gray-300">If checked, this file will not be auto-deleted after <span class="maxFileLifeTime" th:text="${maxFileLifeTime}">30</span> days.</small>
|
||||
<small class="block ml-6 mt-1 text-sm text-gray-600 dark:text-gray-300">If checked, this file
|
||||
will not be auto-deleted after <span class="maxFileLifeTime"
|
||||
th:text="${maxFileLifeTime}">30</span> days.</small>
|
||||
</div>
|
||||
<div th:if="${isFileListPageEnabled}">
|
||||
<label class="inline-flex items-start gap-2">
|
||||
<input id="hidden" name="hidden" type="checkbox" class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:outline-none focus:ring-2 focus:ring-sky-500">
|
||||
<input class="h-4 w-4 rounded border-gray-400 dark:border-gray-600 accent-sky-500 dark:accent-sky-400 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="hidden" name="hidden"
|
||||
type="checkbox">
|
||||
<span class="text-sm">Hide from file list</span>
|
||||
</label>
|
||||
<small class="block ml-6 mt-1 text-sm text-gray-600 dark:text-gray-300">If checked, this file won’t appear on the “View Files” page.</small>
|
||||
<small class="block ml-6 mt-1 text-sm text-gray-600 dark:text-gray-300">If checked, this file
|
||||
won’t appear on the “View Files” page.</small>
|
||||
</div>
|
||||
</div>
|
||||
<label class="block">
|
||||
<span class="sr-only">Password (Optional)</span>
|
||||
<input id="password" name="password" type="password" placeholder="Password (Optional)" class="mt-1 w-full rounded-lg border border-gray-400 dark:border-gray-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500"/>
|
||||
<input class="mt-1 w-full rounded-lg border border-gray-400 dark:border-gray-600 bg-white dark:bg-slate-700 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="password" name="password" placeholder="Password (Optional)"
|
||||
type="password"/>
|
||||
</label>
|
||||
<button type="submit" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Upload</button>
|
||||
<button class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
type="submit">
|
||||
Upload
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="uploadIndicator" class="mt-6 text-center hidden">
|
||||
<p id="uploadStatus" class="text-gray-600 dark:text-gray-300 mb-2" aria-live="polite">Upload started...</p>
|
||||
<div class="mt-6 text-center hidden" id="uploadIndicator">
|
||||
<p aria-live="polite" class="text-gray-600 dark:text-gray-300 mb-2" id="uploadStatus">Upload started...</p>
|
||||
<div class="w-full max-w-md mx-auto bg-gray-200 dark:bg-gray-700 rounded-full h-2 overflow-hidden">
|
||||
<div id="uploadProgress" class="h-2 bg-sky-500 dark:bg-sky-400 transition-[width] duration-300" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:0%"></div>
|
||||
<div aria-valuemax="100" aria-valuemin="0"
|
||||
aria-valuenow="0" class="h-2 bg-sky-500 dark:bg-sky-400 transition-[width] duration-300"
|
||||
id="uploadProgress" role="progressbar" style="width:0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 text-center text-gray-600 dark:text-gray-300" th:if="${isEncryptionEnabled}">
|
||||
|
||||
@@ -3,46 +3,66 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Welcome to QuickDrop</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/css/tailwind.css">
|
||||
<link rel="stylesheet" href="/css/custom.css">
|
||||
<link rel="icon" type="image/png" href="/images/favicon.png">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
<link href="/css/tailwind.css" rel="stylesheet">
|
||||
<link href="/css/custom.css" rel="stylesheet">
|
||||
<link href="/images/favicon.png" rel="icon" type="image/png">
|
||||
<script src="/js/tailwindTheme.js"></script>
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-800 dark:bg-slate-900 dark:text-gray-100">
|
||||
<nav class="bg-gray-800 dark:bg-gray-900 text-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<a href="/" class="flex items-center space-x-2">
|
||||
<img src="/images/favicon.png" alt="Website Logo" class="h-10">
|
||||
<a class="flex items-center space-x-2" href="/">
|
||||
<img alt="Website Logo" class="h-10" src="/images/favicon.png">
|
||||
<span class="font-semibold tracking-tight">QuickDrop</span>
|
||||
</a>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a th:if="${isFileListPageEnabled}" href="/file/list" class="hover:text-white">View Files</a>
|
||||
<a href="/file/upload" class="hover:text-white">Upload File</a>
|
||||
<button id="themeToggle" type="button" class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">🌙</button>
|
||||
<a class="hover:text-white" href="/file/list" th:if="${isFileListPageEnabled}">View Files</a>
|
||||
<a class="hover:text-white" href="/file/upload">Upload File</a>
|
||||
<button class="rounded-lg p-2 transition-colors hover:bg-sky-600 dark:hover:bg-sky-500 active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
id="themeToggle"
|
||||
type="button">
|
||||
🌙
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="max-w-7xl mx-auto px-4 py-6 md:py-8">
|
||||
<header class="text-center mb-8 space-y-1">
|
||||
<h1 class="text-3xl md:text-4xl font-semibold tracking-tight">Welcome to QuickDrop</h1>
|
||||
<p class="text-gray-600 dark:text-gray-300">Thank you for setting up QuickDrop! Please set an admin password for the dashboard.</p>
|
||||
<p class="text-gray-600 dark:text-gray-300">Thank you for setting up QuickDrop! Please set an admin password for
|
||||
the dashboard.</p>
|
||||
</header>
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<div class="col-span-12 md:col-span-8 md:col-start-3 lg:col-span-6 lg:col-start-4">
|
||||
<form id="setupForm" method="post" th:action="@{/admin/setup}" class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6">
|
||||
<form class="bg-white dark:bg-slate-800 p-6 md:p-8 rounded-2xl shadow-lg flex flex-col gap-6" id="setupForm"
|
||||
method="post"
|
||||
th:action="@{/admin/setup}">
|
||||
<input th:name="${_csrf.parameterName}" th:value="${_csrf.token}" type="hidden"/>
|
||||
<label class="block w-full">
|
||||
<span class="sr-only">Admin Password</span>
|
||||
<input id="adminPassword" name="adminPassword" type="password" minlength="8" required placeholder="Admin Password" class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none" style="line-height:2.7rem" />
|
||||
<input class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none"
|
||||
id="adminPassword" minlength="8" name="adminPassword" placeholder="Admin Password"
|
||||
required
|
||||
style="line-height:2.7rem"
|
||||
type="password"/>
|
||||
</label>
|
||||
<label class="block w-full">
|
||||
<span class="sr-only">Confirm Password</span>
|
||||
<input id="confirmPassword" name="confirmPassword" type="password" minlength="8" required placeholder="Confirm Password" class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none" style="line-height:2.7rem" />
|
||||
<input class="block w-full rounded-full border border-slate-300 dark:border-slate-600 bg-white dark:bg-gray-700 px-4 py-3 text-lg text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:border-sky-500 focus:ring-2 focus:ring-sky-500 focus:outline-none"
|
||||
id="confirmPassword" minlength="8" name="confirmPassword" placeholder="Confirm Password"
|
||||
required
|
||||
style="line-height:2.7rem"
|
||||
type="password"/>
|
||||
</label>
|
||||
<div id="error-message" class="text-red-700 dark:text-red-300" style="display:none;">Passwords do not match.</div>
|
||||
<div class="text-red-700 dark:text-red-300" id="error-message" style="display:none;">Passwords do not
|
||||
match.
|
||||
</div>
|
||||
<div class="pt-2 text-center">
|
||||
<button type="submit" class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500">Set Admin Password</button>
|
||||
<button class="rounded-lg bg-sky-500 hover:bg-sky-600 dark:bg-sky-400 dark:hover:bg-sky-500 text-white font-medium px-4 py-2 transition-colors active:scale-95 focus:outline-none focus:ring-2 focus:ring-sky-500"
|
||||
type="submit">
|
||||
Set Admin Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
logging.file.path=quickdrop.log
|
||||
spring.datasource.url=jdbc:sqlite:quickdrop.db
|
||||
file.save.path=test-path
|
||||
spring.mvc.async.request-timeout=3600000
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
spring.profiles.active=test
|
||||
spring.application.name=quickdrop
|
||||
spring.datasource.driver-class-name=org.sqlite.JDBC
|
||||
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
||||
spring.datasource.url=jdbc:sqlite:db/quickdrop.db
|
||||
spring.jpa.show-sql=true
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
spring.thymeleaf.prefix=classpath:/templates/
|
||||
spring.thymeleaf.suffix=.html
|
||||
spring.thymeleaf.cache=false
|
||||
server.tomcat.connection-timeout=60000
|
||||
spring.mvc.async.request-timeout=3600000
|
||||
logging.file.name=log/quickdrop.log
|
||||
#logging.level.org.springframework=DEBUG
|
||||
#logging.level.org.hibernate=DEBUG
|
||||
@@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
content: ['./src/main/resources/templates/**/*.html'],
|
||||
darkMode: 'class',
|
||||
theme: { extend: {} },
|
||||
plugins: [],
|
||||
content: ['./src/main/resources/templates/**/*.html',
|
||||
'./src/main/resources/static/js/**/*.js',],
|
||||
darkMode: 'class',
|
||||
theme: {extend: {}},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user