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
+2
View File
@@ -5,6 +5,7 @@ Aggregated view routers for the application.
from fastapi import APIRouter
from app.views.dropbox import router as dropbox_router
from app.views.filemanager import router as filemanager_router
# Import all the view routers
from app.views.general import router as general_router
@@ -25,3 +26,4 @@ router.include_router(dropbox_router)
router.include_router(google_drive_router)
router.include_router(license_router) # Include the license router
router.include_router(settings_router)
router.include_router(filemanager_router)
+147
View File
@@ -0,0 +1,147 @@
"""
Admin-only file manager view for browsing the workdir directory.
"""
import logging
import mimetypes
import os
from datetime import datetime
from pathlib import Path
from fastapi import Request
from fastapi.responses import FileResponse
from app.config import settings
from app.views.base import APIRouter, require_login, templates
from app.views.settings import require_admin_access
logger = logging.getLogger(__name__)
router = APIRouter()
_SIZE_UNITS = ["B", "KB", "MB", "GB", "TB"]
def _format_size(size_bytes: int) -> str:
"""Format file size in human-readable form."""
size = float(size_bytes)
for unit in _SIZE_UNITS:
if size < 1024.0:
return f"{size:.1f} {unit}"
size /= 1024.0
return f"{size:.1f} PB"
def _safe_path(workdir: str, rel_path: str) -> Path:
"""
Resolve a relative path inside workdir safely.
Raises ValueError if the resolved path escapes workdir.
"""
base = Path(workdir).resolve()
target = (base / rel_path).resolve()
if not str(target).startswith(str(base)):
raise ValueError("Path traversal detected")
return target
@router.get("/admin/files")
@require_login
@require_admin_access
async def filemanager(request: Request):
"""
Admin-only file manager for browsing the workdir directory.
"""
workdir = settings.workdir
rel_path = request.query_params.get("path", "")
# Sanitise the relative path strip leading slashes / dots
rel_path = rel_path.lstrip("/").lstrip(".")
try:
target = _safe_path(workdir, rel_path)
except ValueError:
logger.warning(f"Path traversal attempt blocked: path={rel_path!r}")
target = Path(workdir).resolve()
rel_path = ""
if not target.exists():
target = Path(workdir).resolve()
rel_path = ""
entries = []
if target.is_dir():
for item in sorted(target.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())):
try:
stat = item.stat()
item_rel = str(item.relative_to(Path(workdir).resolve()))
mime_type, _ = mimetypes.guess_type(item.name)
entries.append(
{
"name": item.name,
"rel_path": item_rel,
"is_dir": item.is_dir(),
"size": _format_size(stat.st_size) if not item.is_dir() else "",
"size_bytes": stat.st_size if not item.is_dir() else 0,
"modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M"),
"mime_type": mime_type or "",
}
)
except (PermissionError, OSError) as exc:
logger.warning(f"Could not stat {item}: {exc}")
# Build breadcrumb trail
breadcrumbs = []
if rel_path:
parts = Path(rel_path).parts
accumulated = ""
for part in parts:
accumulated = str(Path(accumulated) / part) if accumulated else part
breadcrumbs.append({"name": part, "path": accumulated})
# Parent path for the "go up" link
parent_path = str(Path(rel_path).parent) if rel_path and Path(rel_path).parent != Path(".") else ""
if parent_path == ".":
parent_path = ""
return templates.TemplateResponse(
"filemanager.html",
{
"request": request,
"entries": entries,
"current_path": rel_path,
"parent_path": parent_path,
"breadcrumbs": breadcrumbs,
"workdir": workdir,
"app_version": settings.version,
},
)
@router.get("/admin/files/download")
@require_login
@require_admin_access
async def filemanager_download(request: Request):
"""
Admin-only endpoint to download a file from workdir.
"""
workdir = settings.workdir
rel_path = request.query_params.get("path", "").lstrip("/").lstrip(".")
try:
target = _safe_path(workdir, rel_path)
except ValueError:
from fastapi import HTTPException
raise HTTPException(status_code=400, detail="Invalid path")
if not target.exists() or not target.is_file():
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="File not found")
mime_type, _ = mimetypes.guess_type(target.name)
return FileResponse(
path=str(target),
filename=target.name,
media_type=mime_type or "application/octet-stream",
)
+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 %}