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:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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
@@ -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 & 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 →
|
||||
</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 →
|
||||
</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 & 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 & 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 & 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 →
|
||||
</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 →
|
||||
</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 →
|
||||
</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 →
|
||||
<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 %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user