feat(admin): add admin-only file manager, admin menu, de-emphasize status, improve dashboard

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-21 09:15:27 +00:00
parent 8813e4e8f3
commit 16e7b6478e
6 changed files with 492 additions and 119 deletions
+12
View File
@@ -16,6 +16,18 @@
// Get the display name (prefer name, fall back to preferred_username, then email)
const displayName = data.name || data.preferred_username || data.email;
// Show admin menu items if the user is an admin
if (data.is_admin) {
const adminMenuContainer = document.getElementById("adminMenuContainer");
if (adminMenuContainer) {
adminMenuContainer.classList.remove("hidden");
}
const mobileAdminSection = document.getElementById("mobileAdminSection");
if (mobileAdminSection) {
mobileAdminSection.classList.remove("hidden");
}
}
// User is logged in - use DOM API to prevent XSS
if (authSection) {
authSection.textContent = ''; // Clear existing content
+69 -5
View File
@@ -41,13 +41,55 @@
</div>
<!-- Menu Items - using x-data for mobile menu toggle -->
<div x-data="{ mobileMenuOpen: false }">
<div x-data="{ mobileMenuOpen: false, adminMenuOpen: false }">
<div class="hidden md:flex space-x-4 items-center">
<a href="/" class="text-gray-700 hover:text-gray-900">Home</a>
<a href="/upload" class="text-gray-700 hover:text-gray-900">Upload</a>
<a href="/files" class="text-gray-700 hover:text-gray-900">Files</a>
<a href="/status" class="text-gray-700 hover:text-gray-900">Status</a>
<a href="/settings" class="text-gray-700 hover:text-gray-900">Settings</a>
<!-- Admin dropdown (shown only for admin users via JS) -->
<div id="adminMenuContainer" class="relative hidden">
<button
@click="adminMenuOpen = !adminMenuOpen"
@click.outside="adminMenuOpen = false"
type="button"
class="inline-flex items-center text-gray-700 hover:text-gray-900 focus:outline-none"
aria-haspopup="true"
:aria-expanded="adminMenuOpen"
>
<i class="fas fa-shield-alt mr-1 text-red-500"></i>
Admin
<i class="fas fa-chevron-down ml-1 text-xs"></i>
</button>
<div
x-show="adminMenuOpen"
x-transition:enter="transition ease-out duration-100 transform"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75 transform"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 z-50"
>
<div class="py-1">
<a href="/settings" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-cog w-4 mr-2 text-gray-500"></i> Settings
</a>
<a href="/env" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-terminal w-4 mr-2 text-gray-500"></i> Environment
</a>
<a href="/admin/files" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-folder-open w-4 mr-2 text-gray-500"></i> File Manager
</a>
</div>
</div>
</div>
<!-- Status (de-emphasized) -->
<a href="/status" class="text-gray-400 hover:text-gray-600 text-sm" title="System Status">
<i class="fas fa-circle-dot mr-0.5"></i> Status
</a>
<a href="/about" class="text-gray-700 hover:text-gray-900">About</a>
<!-- Dynamic Auth Section -->
@@ -82,8 +124,30 @@
<a href="/" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Home</a>
<a href="/upload" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Upload</a>
<a href="/files" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Files</a>
<a href="/status" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Status</a>
<a href="/settings" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">Settings</a>
<!-- Admin section in mobile menu (shown only for admin users via JS) -->
<div id="mobileAdminSection" class="hidden">
<div class="border-t border-gray-200 mt-1 pt-1">
<p class="px-3 py-1 text-xs font-semibold text-red-600 uppercase tracking-wider flex items-center">
<i class="fas fa-shield-alt mr-1"></i> Admin
</p>
<a href="/settings" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-cog mr-2 text-gray-400"></i> Settings
</a>
<a href="/env" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-terminal mr-2 text-gray-400"></i> Environment
</a>
<a href="/admin/files" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-folder-open mr-2 text-gray-400"></i> File Manager
</a>
</div>
</div>
<!-- Status (de-emphasized) in mobile -->
<a href="/status" class="block px-3 py-2 rounded-md text-sm font-medium text-gray-400 hover:text-gray-600 hover:bg-gray-50">
<i class="fas fa-circle-dot mr-1"></i> Status
</a>
<a href="/about" class="block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">About</a>
<!-- Mobile Auth Section -->
+125
View File
@@ -0,0 +1,125 @@
{% extends "base.html" %}
{% block title %}File Manager Admin{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<!-- Header -->
<div class="mb-6">
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-2">
<i class="fas fa-folder-open text-blue-500"></i>
File Manager
<span class="text-sm font-normal text-red-600 bg-red-50 border border-red-200 rounded px-2 py-0.5 ml-2">Admin Only</span>
</h1>
<p class="text-gray-500 mt-1 text-sm">
Browsing: <code class="bg-gray-100 rounded px-1">{{ workdir }}</code>
{% if current_path %}/ <code class="bg-gray-100 rounded px-1">{{ current_path }}</code>{% endif %}
</p>
</div>
<!-- Breadcrumb -->
<nav class="flex mb-4 text-sm" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1">
<li>
<a href="/admin/files" class="text-blue-600 hover:underline flex items-center">
<i class="fas fa-home mr-1"></i> workdir
</a>
</li>
{% for crumb in breadcrumbs %}
<li class="flex items-center">
<i class="fas fa-chevron-right text-gray-400 mx-1 text-xs"></i>
{% if loop.last %}
<span class="text-gray-700 font-medium">{{ crumb.name }}</span>
{% else %}
<a href="/admin/files?path={{ crumb.path | urlencode }}" class="text-blue-600 hover:underline">{{ crumb.name }}</a>
{% endif %}
</li>
{% endfor %}
</ol>
</nav>
<!-- File listing table -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-8"></th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden sm:table-cell">Size</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider hidden md:table-cell">Modified</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<!-- Parent directory row -->
{% if current_path %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-3 text-yellow-500"><i class="fas fa-folder"></i></td>
<td class="px-6 py-3 text-sm font-medium text-gray-900" colspan="3">
<a href="/admin/files{% if parent_path %}?path={{ parent_path | urlencode }}{% endif %}" class="text-blue-600 hover:underline flex items-center gap-1">
<i class="fas fa-level-up-alt text-xs"></i> ..
</a>
</td>
<td class="px-6 py-3"></td>
</tr>
{% endif %}
{% if entries %}
{% for entry in entries %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-3">
{% if entry.is_dir %}
<i class="fas fa-folder text-yellow-400"></i>
{% elif entry.mime_type.startswith('image') %}
<i class="fas fa-file-image text-blue-400"></i>
{% elif entry.mime_type == 'application/pdf' %}
<i class="fas fa-file-pdf text-red-400"></i>
{% elif 'text' in entry.mime_type %}
<i class="fas fa-file-alt text-gray-400"></i>
{% elif 'json' in entry.mime_type %}
<i class="fas fa-file-code text-green-400"></i>
{% else %}
<i class="fas fa-file text-gray-400"></i>
{% endif %}
</td>
<td class="px-6 py-3 text-sm font-medium text-gray-900">
{% if entry.is_dir %}
<a href="/admin/files?path={{ entry.rel_path | urlencode }}" class="text-blue-600 hover:underline">
{{ entry.name }}
</a>
{% else %}
<span>{{ entry.name }}</span>
{% endif %}
</td>
<td class="px-6 py-3 text-sm text-gray-500 hidden sm:table-cell">
{{ entry.size }}
</td>
<td class="px-6 py-3 text-sm text-gray-500 hidden md:table-cell">
{{ entry.modified }}
</td>
<td class="px-6 py-3 text-sm">
{% if not entry.is_dir %}
<a href="/admin/files/download?path={{ entry.rel_path | urlencode }}"
class="inline-flex items-center px-2.5 py-1 border border-gray-300 text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50"
title="Download">
<i class="fas fa-download mr-1"></i> Download
</a>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="5" class="px-6 py-10 text-center text-sm text-gray-500">
<i class="fas fa-folder-open text-gray-300 text-3xl mb-2 block"></i>
This directory is empty.
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% endblock %}
+137 -114
View File
@@ -1,136 +1,159 @@
{% extends "base.html" %}
{% block title %}Home - DocuElevate{% endblock %}
{% block title %}Dashboard DocuElevate{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<!-- Hero Section -->
<div class="bg-gradient-to-r from-blue-500 to-indigo-600 rounded-lg shadow-lg text-white p-8 mb-8">
<h1 class="text-4xl font-bold mb-4">Welcome to DocuElevate</h1>
<p class="text-xl mb-6">
Your intelligent solution for processing, managing, and organizing documents effortlessly.
</p>
<div class="flex flex-wrap gap-4">
<a href="/upload" class="bg-white text-blue-600 hover:bg-gray-100 font-bold py-2 px-4 rounded-lg transition duration-300 flex items-center">
<i class="fas fa-upload mr-2"></i> Upload Document
</a>
<a href="/status" class="bg-transparent border border-white text-white hover:bg-white hover:text-blue-600 font-bold py-2 px-4 rounded-lg transition duration-300 flex items-center">
<i class="fas fa-cog mr-2"></i> System Status
</a>
</div>
</div>
<!-- Stats Section -->
<div class="bg-white rounded-lg shadow p-6 mb-8">
<h2 class="text-2xl font-semibold mb-4">System Overview</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="border-r border-gray-200 px-4">
<p class="text-4xl font-bold text-blue-600">{{ stats.processed_files|default('0') }}</p>
<p class="text-gray-600">Documents Processed</p>
<!-- Hero / Quick Actions -->
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-xl shadow-lg text-white p-8 mb-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 class="text-3xl font-bold mb-1">DocuElevate Dashboard</h1>
<p class="text-blue-100 text-sm">Intelligent document processing &amp; management</p>
</div>
<div class="border-r border-gray-200 px-4">
<p class="text-4xl font-bold text-green-600">{{ stats.active_integrations|default('0') }}</p>
<p class="text-gray-600">Active Integrations</p>
</div>
<div class="px-4">
<p class="text-4xl font-bold text-indigo-600">{{ stats.storage_targets|default('0') }}</p>
<p class="text-gray-600">Storage Targets</p>
<div class="flex flex-wrap gap-3">
<a href="/upload" class="bg-white text-blue-700 hover:bg-blue-50 font-semibold py-2 px-5 rounded-lg transition duration-200 flex items-center shadow-sm">
<i class="fas fa-upload mr-2"></i> Upload
</a>
<a href="/files" class="bg-transparent border border-white text-white hover:bg-white hover:text-blue-700 font-semibold py-2 px-5 rounded-lg transition duration-200 flex items-center">
<i class="fas fa-list mr-2"></i> Browse Files
</a>
</div>
</div>
</div>
<!-- Features Section -->
<h2 class="text-3xl font-semibold mb-6">Core Features</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-10">
<div class="bg-white shadow rounded-lg p-6 hover:shadow-lg transition duration-300">
<div class="text-blue-600 mb-4">
<i class="fas fa-file-alt fa-2x"></i>
<!-- Stats Row -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-xl shadow p-6 flex items-center gap-4">
<div class="h-12 w-12 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
<i class="fas fa-file-alt text-blue-600 text-xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Document Processing</h3>
<p class="text-gray-600 mb-4">
Upload and process documents with OCR, metadata extraction, and intelligent classification.
</p>
<a href="/upload" class="text-blue-600 hover:text-blue-800 font-medium">
Start processing &rarr;
</a>
<div>
<p class="text-3xl font-bold text-blue-600">{{ stats.processed_files | default(0) }}</p>
<p class="text-gray-500 text-sm">Documents Processed</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-6 flex items-center gap-4">
<div class="h-12 w-12 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0">
<i class="fas fa-plug text-green-600 text-xl"></i>
</div>
<div>
<p class="text-3xl font-bold text-green-600">{{ stats.active_integrations | default(0) }}</p>
<p class="text-gray-500 text-sm">Active Integrations</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-6 flex items-center gap-4">
<div class="h-12 w-12 rounded-full bg-indigo-100 flex items-center justify-center flex-shrink-0">
<i class="fas fa-database text-indigo-600 text-xl"></i>
</div>
<div>
<p class="text-3xl font-bold text-indigo-600">{{ stats.storage_targets | default(0) }}</p>
<p class="text-gray-500 text-sm">Storage Targets</p>
</div>
</div>
</div>
<!-- Widget Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
<!-- Quick Actions Widget -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-bolt text-yellow-500"></i> Quick Actions
</h2>
<ul class="space-y-2">
<li>
<a href="/upload" class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition group">
<span class="h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0 group-hover:bg-blue-200">
<i class="fas fa-upload text-blue-600 text-sm"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">Upload Document</p>
<p class="text-xs text-gray-400">Process a new file</p>
</div>
</a>
</li>
<li>
<a href="/files" class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition group">
<span class="h-8 w-8 rounded-full bg-indigo-100 flex items-center justify-center flex-shrink-0 group-hover:bg-indigo-200">
<i class="fas fa-list text-indigo-600 text-sm"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">View All Files</p>
<p class="text-xs text-gray-400">Browse processed documents</p>
</div>
</a>
</li>
<li>
<a href="/status" class="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition group">
<span class="h-8 w-8 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0 group-hover:bg-green-200">
<i class="fas fa-heartbeat text-green-600 text-sm"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">System Status</p>
<p class="text-xs text-gray-400">Check integration health</p>
</div>
</a>
</li>
</ul>
</div>
<div class="bg-white shadow rounded-lg p-6 hover:shadow-lg transition duration-300">
<div class="text-blue-600 mb-4">
<i class="fas fa-cloud fa-2x"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Multiple Storage Options</h3>
<p class="text-gray-600 mb-4">
Connect to Dropbox, NextCloud, OneDrive and other storage solutions seamlessly.
</p>
<a href="/status" class="text-blue-600 hover:text-blue-800 font-medium">
Configure storage &rarr;
</a>
<!-- Capabilities Widget -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-cubes text-blue-500"></i> Capabilities
</h2>
<ul class="space-y-3 text-sm text-gray-600">
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0"></i>
<span>OCR &amp; metadata extraction with AI</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0"></i>
<span>Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0"></i>
<span>Paperless-ngx integration for document management</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0"></i>
<span>Email &amp; URL-based document ingestion</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0"></i>
<span>Automated classification &amp; routing workflows</span>
</li>
</ul>
</div>
<div class="bg-white shadow rounded-lg p-6 hover:shadow-lg transition duration-300">
<div class="text-blue-600 mb-4">
<i class="fas fa-robot fa-2x"></i>
<!-- Getting Started / Help Widget -->
<div class="bg-white rounded-xl shadow p-6 flex flex-col justify-between">
<div>
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-compass text-indigo-500"></i> Getting Started
</h2>
<ol class="space-y-3 text-sm text-gray-600 list-none">
<li class="flex items-start gap-2">
<span class="h-5 w-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center flex-shrink-0 font-bold mt-0.5">1</span>
Configure integrations via <a href="/status" class="text-blue-600 hover:underline">System Status</a>
</li>
<li class="flex items-start gap-2">
<span class="h-5 w-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center flex-shrink-0 font-bold mt-0.5">2</span>
<a href="/upload" class="text-blue-600 hover:underline">Upload</a> your first document
</li>
<li class="flex items-start gap-2">
<span class="h-5 w-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center flex-shrink-0 font-bold mt-0.5">3</span>
Review results in <a href="/files" class="text-blue-600 hover:underline">Files</a>
</li>
</ol>
</div>
<h3 class="text-xl font-semibold mb-2">AI-Powered Analysis</h3>
<p class="text-gray-600 mb-4">
Leverage AI to extract metadata, classify documents, and organize your information.
</p>
<a href="/files" class="text-blue-600 hover:text-blue-800 font-medium">
View processed files &rarr;
</a>
</div>
<div class="bg-white shadow rounded-lg p-6 hover:shadow-lg transition duration-300">
<div class="text-blue-600 mb-4">
<i class="fas fa-envelope fa-2x"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Email Integration</h3>
<p class="text-gray-600 mb-4">
Auto-process documents received via email with our IMAP polling capabilities.
</p>
<a href="/status" class="text-blue-600 hover:text-blue-800 font-medium">
Set up email &rarr;
</a>
</div>
<div class="bg-white shadow rounded-lg p-6 hover:shadow-lg transition duration-300">
<div class="text-blue-600 mb-4">
<i class="fas fa-cogs fa-2x"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Workflow Automation</h3>
<p class="text-gray-600 mb-4">
Create workflows to automatically process and route documents based on content.
</p>
<a href="/about" class="text-blue-600 hover:text-blue-800 font-medium">
Learn more &rarr;
</a>
</div>
<div class="bg-white shadow rounded-lg p-6 hover:shadow-lg transition duration-300">
<div class="text-blue-600 mb-4">
<i class="fas fa-shield-alt fa-2x"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Secure & Private</h3>
<p class="text-gray-600 mb-4">
Your documents are processed securely with privacy-focused design principles.
</p>
<a href="/privacy" class="text-blue-600 hover:text-blue-800 font-medium">
Privacy policy &rarr;
<a href="/about" class="mt-6 inline-flex items-center text-sm text-indigo-600 hover:text-indigo-800 font-medium">
Learn more about DocuElevate <i class="fas fa-arrow-right ml-1 text-xs"></i>
</a>
</div>
</div>
<!-- Getting Started Section -->
<div class="bg-gray-50 rounded-lg border border-gray-200 p-8">
<div class="flex flex-col md:flex-row md:items-center justify-between">
<div class="mb-6 md:mb-0">
<h2 class="text-2xl font-bold mb-2">Ready to get started?</h2>
<p class="text-gray-600">Upload your first document and see DocuElevate in action.</p>
</div>
<a href="/upload" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300">
Try it now
</a>
</div>
</div>
</div>
{% endblock %}