Merge branch 'main' into copilot/implement-database-backed-settings-page

This commit is contained in:
Christian Krakau-Louis
2026-02-08 08:00:33 +01:00
committed by GitHub
9 changed files with 605 additions and 497 deletions
+50 -123
View File
@@ -5,12 +5,7 @@ All notable changes to DocuElevate will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# Changelog
All notable changes to DocuElevate will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.5.0] - 2026-02-08
@@ -21,14 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Settings organized by category: Core, Authentication, AI Services, Storage Providers, Email, IMAP, Monitoring, Processing, Notifications, Feature Flags
- Form pre-filled with current values, all fields optional for flexible editing
- Bulk update support for changing multiple settings at once
- **Encryption for Sensitive Settings**: Fernet symmetric encryption for database storage
- Automatic encryption/decryption for passwords, API keys, tokens, and secrets
- Encryption key derived from `SESSION_SECRET` via SHA256
- Values prefixed with `enc:` in database to identify encrypted data
- Graceful fallback if cryptography library unavailable (logs warning)
- Lock icon (🔒) in UI indicates encrypted fields
- **Setup Wizard**: First-time configuration wizard for fresh installations
- 3-step wizard: Infrastructure → Security → AI Services
- Auto-detects missing critical settings and redirects from homepage
@@ -36,13 +29,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Auto-generate option for session secrets
- Skippable for advanced users
- Settings saved encrypted to database
- **Settings Precedence System**: Clear resolution order with visual indicators
- Precedence: Database > Environment Variables > Defaults
- Color-coded badges in UI: 🟢 DB (green), 🔵 ENV (blue), ⚪ DEFAULT (gray)
- Source detection for each setting shows where value originates
- Info section explaining precedence order
- **OAuth Admin Support**: Enhanced authentication for settings access
- Admin flag set from OAuth group membership (`admin` or `administrators`)
- Proper decorator pattern for admin access control
@@ -75,7 +66,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `app/views/wizard.py` - Wizard routes (GET/POST /setup)
- `frontend/templates/setup_wizard.html` - Wizard UI
- `frontend/templates/settings.html` - Enhanced settings page
- Modified files:
- `app/utils/settings_service.py` - Encryption integration, 102 setting metadata
- `app/views/settings.py` - Fixed decorator, source detection
@@ -93,80 +83,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.3.3] - 2026-02-08
### Added
- **Settings Management System**: Database-backed configuration management with web UI
- Admin-only settings page at `/settings` with 102 settings across 10 categories
- REST API endpoints: `GET/POST /api/settings/{key}`, `POST /api/settings/bulk-update`, `DELETE /api/settings/{key}`
- Settings organized by category: Core, Authentication, AI Services, Storage Providers, Email, IMAP, Monitoring, Processing, Notifications, Feature Flags
- Form pre-filled with current values, all fields optional for flexible editing
- Bulk update support for changing multiple settings at once
- **Encryption for Sensitive Settings**: Fernet symmetric encryption for database storage
- Automatic encryption/decryption for passwords, API keys, tokens, and secrets
- Encryption key derived from `SESSION_SECRET` via SHA256
- Values prefixed with `enc:` in database to identify encrypted data
- Graceful fallback if cryptography library unavailable (logs warning)
- Lock icon (🔒) in UI indicates encrypted fields
- **Setup Wizard**: First-time configuration wizard for fresh installations
- 3-step wizard: Infrastructure → Security → AI Services
- Auto-detects missing critical settings and redirects from homepage
- Beautiful UI with progress indicators and step navigation
- Auto-generate option for session secrets
- Skippable for advanced users
- Settings saved encrypted to database
- **Settings Precedence System**: Clear resolution order with visual indicators
- Precedence: Database > Environment Variables > Defaults
- Color-coded badges in UI: 🟢 DB (green), 🔵 ENV (blue), ⚪ DEFAULT (gray)
- Source detection for each setting shows where value originates
- Info section explaining precedence order
- **OAuth Admin Support**: Enhanced authentication for settings access
- Admin flag set from OAuth group membership (`admin` or `administrators`)
- Proper decorator pattern for admin access control
- Session-based authorization with redirect on unauthorized access
- **Drag-and-drop file upload on Files page**: You can now drag and drop files anywhere on the Files view (`/files`) to upload them, making it more convenient to add documents without navigating to the dedicated Upload page
- Visual drop overlay that appears when dragging files over the Files page
- Upload progress modal in bottom-right corner showing real-time upload status
- Reusable `upload.js` module extracted from upload page for better code maintainability
- Tests for drag-and-drop functionality presence in Files view
- Enhanced visual feedback with animations and improved styling
### Changed
- Updated `requirements.txt` to include `cryptography>=41.0.0` for encryption
- Enhanced settings service to auto-encrypt/decrypt sensitive values transparently
- Improved `/settings` route with proper admin decorator (fixes redirect loop)
- Updated settings template with enhanced UI: source badges, encryption indicators, show/hide toggles
- Modified `app/views/general.py` to redirect to wizard when setup required
### Fixed
- Fixed `/settings` endpoint returning 301 redirect to `/` (converted to proper decorator)
- Resolved redirect loop for logged-in non-admin users
- Fixed OAuth users not receiving admin privileges from group membership
### Documentation
- Added [docs/SettingsManagement.md](docs/SettingsManagement.md) - Comprehensive user guide
- Added [SETTINGS_IMPLEMENTATION.md](SETTINGS_IMPLEMENTATION.md) - Technical documentation
- Added [FRAMEWORK_ANALYSIS.md](FRAMEWORK_ANALYSIS.md) - Research on existing frameworks
- Added [IMPLEMENTATION_CHECKLIST.md](IMPLEMENTATION_CHECKLIST.md) - Feature tracking
- Updated TODO.md with completed features
- Updated MILESTONES.md with v0.3.3 release details
### Technical Details
- New files:
- `app/utils/encryption.py` - Fernet encryption utilities
- `app/utils/setup_wizard.py` - Wizard detection and logic
- `app/views/wizard.py` - Wizard routes (GET/POST /setup)
- `frontend/templates/setup_wizard.html` - Wizard UI
- `frontend/templates/settings.html` - Enhanced settings page
- Modified files:
- `app/utils/settings_service.py` - Encryption integration, 102 setting metadata
- `app/views/settings.py` - Fixed decorator, source detection
- `app/auth.py` - OAuth admin support
- `app/api/settings.py` - Enhanced admin checks
- `tests/test_settings.py` - Comprehensive test coverage
- Refactored upload functionality into a shared JavaScript module (`/static/js/upload.js`)
- Updated Upload page to use the new shared upload module
- Improved drop zone visual styling with better colors and animations
### Security
- Sensitive settings encrypted at rest in database using Fernet (AES-128-CBC + HMAC)
- Encryption key derived from `SESSION_SECRET` (minimum 32 characters required)
- Admin-only access enforced on all settings operations
- Visual masking of sensitive values in UI by default
- CodeQL security scan: 0 alerts
- Continued security improvements from v0.3.2 (authlib, starlette updates)
## [0.3.2] - 2026-02-06
@@ -175,6 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Security scanning workflows (CodeQL, Bandit)
- SECURITY_AUDIT.md documentation
- ROADMAP.md and MILESTONES.md planning documents
- API integration tests and configuration validation tests
- Pre-commit hooks configuration
### Changed
@@ -182,6 +113,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated starlette to 0.49.1+ (DoS vulnerability fix)
- Improved SESSION_SECRET validation and handling
- Enhanced .gitignore for security
- Updated README with improved documentation structure
### Fixed
- Critical security vulnerabilities in dependencies
@@ -190,61 +122,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.3.1] - 2026-01-15
### Added
- OAuth2 authentication with Authentik support
- Basic admin authentication
- Session management
- Files view with sorting and filtering capabilities
- Bulk operations (delete, reprocess) for multiple files
- File detail view with processing history
- Processing flow visualization
### Changed
- Improved authentication flow
- Enhanced error handling
- Improved UI responsiveness
- Enhanced error handling and user feedback
## [0.3.0] - 2026-01-01
### Fixed
- Various bug fixes in file processing pipeline
## [0.3.0] - 2025-12-20
### Added
- Multi-provider storage support (Dropbox, Google Drive, OneDrive, S3, FTP, SFTP, WebDAV)
- Document processing pipeline with OCR
- Metadata extraction with OpenAI
- Basic web UI with file listing
- REST API for document operations
- Celery task queue for async processing
- OAuth2 authentication support with Authentik
- Multi-provider storage support (Dropbox, Google Drive, OneDrive, S3, Nextcloud)
- Azure Document Intelligence integration for OCR
- OpenAI metadata extraction
- Gotenberg PDF conversion service integration
- IMAP integration for email attachment processing
- REST API with FastAPI
- Web UI for document management
- Celery task queue for asynchronous processing
### Changed
- Migrated from Flask to FastAPI
- Updated database schema
- Improved error handling
- Major architectural improvements
- Database schema optimizations
## [0.2.0] - 2025-12-01
## [0.2.0] - 2025-11-01
### Added
- Initial document processing capabilities
- Basic storage integration
- Simple web interface
- Basic document upload functionality
- Simple storage integration
- Basic metadata extraction
## [0.1.0] - 2025-11-01
## [0.1.0] - 2025-10-01
### Added
- Initial project setup
- Basic FastAPI application structure
- Database models
- Docker configuration
- Initial release
- Core document processing framework
- Basic file handling
---
## Version History Summary
- **v0.5.0** (2026-02-08): Settings management, encryption, setup wizard
- **v0.3.3** (2026-02-08): Drag-and-drop upload
- **v0.3.2** (2026-02-06): Security hardening, testing infrastructure
- **v0.3.1** (2026-01-15): OAuth2 authentication
- **v0.3.0** (2026-01-01): Multi-provider storage, OCR, metadata extraction
- **v0.2.0** (2025-12-01): Document processing
- **v0.1.0** (2025-11-01): Initial release
---
## Links
- [GitHub Repository](https://github.com/christianlouis/DocuElevate)
- [Documentation](https://docuelevate.readthedocs.io)
- [Issue Tracker](https://github.com/christianlouis/DocuElevate/issues)
- [Release Notes](https://github.com/christianlouis/DocuElevate/releases)
[Unreleased]: https://github.com/christianlouis/DocuElevate/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/christianlouis/DocuElevate/compare/v0.3.3...v0.5.0
[0.3.3]: https://github.com/christianlouis/DocuElevate/compare/v0.3.2...v0.3.3
[0.3.2]: https://github.com/christianlouis/DocuElevate/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/christianlouis/DocuElevate/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/christianlouis/DocuElevate/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/christianlouis/DocuElevate/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/christianlouis/DocuElevate/releases/tag/v0.1.0
+22 -181
View File
@@ -39,17 +39,15 @@ DocuElevate follows [Semantic Versioning 2.0.0](https://semver.org/):
- Enhanced upload UI and functionality
### v0.3.2 (February 2026)
- Production-ready document processing
- Multi-provider storage support
- Basic web UI and REST API
- OAuth2 authentication
- Security hardening (Authlib/Starlette updates)
- Testing infrastructure implementation
- CI/CD improvements
---
## Upcoming Milestones
## Completed Milestones
### v0.5.0 - Settings Management & Configuration (February 2026)
**Target Date:** February 15, 2026
**Release Date:** February 8, 2026
**Status:** ✅ Released
**Theme:** Configuration Management, Security, User Experience
@@ -80,23 +78,6 @@ DocuElevate follows [Semantic Versioning 2.0.0](https://semver.org/):
- **Source Indicators**: Visual badges showing where each setting value originates (🟢 DB, 🔵 ENV, ⚪ DEFAULT)
- **Admin Access Control**: OAuth admin group support and proper decorator pattern for authorization
#### Technical Improvements
- Fixed /settings redirect loop issue
- Added cryptography>=41.0.0 dependency
- Created encryption utilities (app/utils/encryption.py)
- Implemented settings service with auto-encrypt/decrypt
- Built responsive wizard UI with progress indicators
- Comprehensive test coverage for settings functionality
#### Breaking Changes
- None
#### Migration Notes
- Setup wizard automatically appears for fresh installations
- Existing installations can skip wizard
- All settings remain backward compatible with environment variables
- Database settings override environment variables when present
---
### v0.3.3 - Drag-and-Drop Upload (February 2026)
@@ -104,75 +85,42 @@ DocuElevate follows [Semantic Versioning 2.0.0](https://semver.org/):
**Status:** ✅ Released
**Theme:** User Experience Enhancement
#### Features
- Drag-and-drop file upload on Files page
- Visual drop overlay with animations
- Upload progress modal
- Shared upload JavaScript module
#### Goals
- [x] Add drag-and-drop file upload to Files view
- [x] Refactor upload logic for maintainability
- [x] Improve visual feedback during file interactions
#### Deliverables
- [x] Drag-and-drop upload functionality in Files view
- [x] Reusable `upload.js` module for code DRYness
- [x] Visual drop overlay and progress modal
- [x] Enhanced upload error handling
---
### v0.3.2 - Security & Testing Hardening (February 2026)
**Target Date:** February 15, 2026
**Release Date:** February 8, 2026
**Release Date:** February 6, 2026
**Status:** ✅ Released
**Theme:** Security, Quality, Testing, Configuration Management
**Theme:** Security, Quality, Testing
#### Goals
- [x] Fix critical security vulnerabilities (authlib, starlette)
- [x] Implement comprehensive test suite
- [x] Add security scanning (CodeQL, Bandit)
- [x] Improve CI/CD pipeline
- [x] **Implement database-backed settings management**
- [x] **Add encryption for sensitive configuration**
- [x] **Create setup wizard for first-time installation**
- [ ] Achieve 60% test coverage (ongoing)
- [ ] Add pre-commit hooks (ongoing)
- [ ] Update all dependencies to latest secure versions (ongoing)
#### Deliverables
- [x] SECURITY_AUDIT.md documentation
- [x] pytest configuration and fixtures
- [x] API integration tests
- [x] Configuration validation tests
- [x] **Settings management UI at /settings**
- [x] **Setup wizard at /setup**
- [x] **Fernet encryption for sensitive settings**
- [x] **Source indicators (DB/ENV/DEFAULT)**
- [x] **Complete settings documentation**
- [x] **Framework analysis (FRAMEWORK_ANALYSIS.md)**
- [ ] Task processing tests (ongoing)
- [ ] Storage provider integration tests (ongoing)
- [x] Updated CI/CD workflows
- [ ] Security best practices guide (ongoing)
#### New Features
- **Settings Management System**: Web-based admin UI for viewing and editing 102 application settings across 10 categories
- **Encryption**: Fernet symmetric encryption for sensitive values (passwords, API keys, tokens) with key derived from SESSION_SECRET
- **Setup Wizard**: 3-step wizard for first-time configuration (Infrastructure → Security → AI Services)
- **Precedence System**: Settings resolved in order: Database > Environment Variables > Defaults
- **Source Indicators**: Visual badges showing where each setting value originates (🟢 DB, 🔵 ENV, ⚪ DEFAULT)
- **Admin Access Control**: OAuth admin group support and proper decorator pattern for authorization
#### Technical Improvements
- Fixed /settings redirect loop issue
- Added cryptography>=41.0.0 dependency
- Created encryption utilities (app/utils/encryption.py)
- Implemented settings service with auto-encrypt/decrypt
- Built responsive wizard UI with progress indicators
- Comprehensive test coverage for settings functionality
#### Breaking Changes
- None
#### Migration Notes
- Setup wizard automatically appears for fresh installations
- Existing installations can skip wizard
- All settings remain backward compatible with environment variables
- Database settings override environment variables when present
- [x] Pre-commit hooks configuration
---
## Upcoming Milestones
### v0.6.0 - Enhanced Search & UI Improvements (April 2026)
**Target Date:** April 1, 2026
**Status:** 📋 Planned
@@ -225,9 +173,6 @@ DocuElevate follows [Semantic Versioning 2.0.0](https://semver.org/):
- Integration examples and templates
- Webhook payload documentation
#### Breaking Changes
- None
---
### v0.7.0 - Advanced AI & Multi-language (August 2026)
@@ -251,9 +196,6 @@ DocuElevate follows [Semantic Versioning 2.0.0](https://semver.org/):
- Translation framework (10+ languages)
- Localized documentation
#### Breaking Changes
- Configuration file format changes (auto-migration script provided)
---
### v1.0.0 - Enterprise Edition (November 2026)
@@ -291,88 +233,6 @@ This is our first major release, marking production-ready enterprise capabilitie
- Database replication support
- Message queue clustering
- **Observability**
- Comprehensive audit logs
- Prometheus metrics export
- Grafana dashboards
- APM integration (New Relic, DataDog)
- SLA monitoring
- **Documentation**
- Enterprise deployment guide
- High availability setup
- Disaster recovery procedures
- Security compliance guide
- Professional services offerings
#### Breaking Changes
- Database schema migration (automatic with Alembic)
- Configuration file restructure (migration tool provided)
- API v1 deprecated (v2 required for new features)
#### Migration Path
- Detailed migration guide provided
- Automated migration scripts
- Rollback procedures documented
- Migration support via GitHub Discussions
---
### v1.1.0 - Collaboration & Analytics (January 2027)
**Target Date:** January 15, 2027
**Status:** 📋 Planned
**Theme:** Collaboration, Reporting, Analytics
#### Goals
- Document sharing with expiring links
- Comments and annotations
- Version history
- Analytics dashboard
- Cost analysis
- Export reports
#### Deliverables
- Sharing interface with permissions
- Comment system with threading
- Version control and diff viewer
- Analytics dashboard with charts
- Cost breakdown by provider
- Report generation (PDF, CSV, Excel)
- User activity tracking
#### Breaking Changes
- None
---
### v2.0.0 - On-Premise AI & Platform Expansion (Q3 2027)
**Target Date:** Q3 2027
**Status:** 🔮 Future
**Theme:** Self-hosting, Privacy, Platform Diversity
#### Goals
- Self-hosted AI models (no cloud dependencies)
- Local LLM integration
- Desktop and mobile applications
- Offline-first capabilities
- Enhanced privacy features
- Plugin marketplace
#### Deliverables
- Tesseract/EasyOCR integration
- Ollama/LLaMA support
- Desktop app (Windows, Mac, Linux)
- Mobile apps (iOS, Android)
- Browser extensions (Chrome, Firefox)
- Plugin SDK and marketplace
- Offline mode
#### Breaking Changes
- Major API restructure (v3)
- New authentication system
- Configuration format change
- Minimum Python version: 3.12
---
## Release Process
@@ -395,14 +255,6 @@ This is our first major release, marking production-ready enterprise capabilitie
- Helm charts (future)
- Documentation site update
### Post-release
- [ ] GitHub release created
- [ ] Blog post published
- [ ] Social media announcement
- [ ] Community notification
- [ ] Support documentation updated
- [ ] Monitor for critical issues
---
## Version History
@@ -412,9 +264,10 @@ This is our first major release, marking production-ready enterprise capabilitie
| v0.1.0 | 2024-Q1 | Initial Release | Released |
| v0.2.0 | 2024-Q3 | Multi-provider Support | Released |
| v0.3.0 | 2025-Q4 | UI & Authentication | Released |
| v0.3.1 | 2026-01-15 | OAuth2 Integration | Released |
| v0.3.2 | 2026-02-06 | Security Updates | Released |
| v0.3.3 | 2026-02-08 | Drag-and-Drop Upload | Released |
| v0.5.0 | 2026-02-08 | **Current Stable** - Settings Management | **Released** |
| v0.5.0 | 2026-02-08 | **Settings & Encryption** | **Released** |
| v0.6.0 | 2026-04 | Search & UX | Planned |
| v0.7.0 | 2026-08 | Advanced AI | Planned |
| v1.0.0 | 2026-11 | Enterprise | Planned |
@@ -440,16 +293,4 @@ This is our first major release, marking production-ready enterprise capabilitie
---
## Contributing to Milestones
Want to contribute to a specific milestone?
1. Check the [GitHub Projects](https://github.com/christianlouis/DocuElevate/projects) board
2. Look for issues tagged with milestone labels
3. Read [CONTRIBUTING.md](CONTRIBUTING.md)
4. Comment on the issue you'd like to work on
5. Submit a PR linked to the issue
---
*This milestone document is updated regularly. For real-time status, check our [GitHub Projects](https://github.com/christianlouis/DocuElevate/projects) board.*
*This milestone document is updated regularly. For real-time status, check our [GitHub Projects](https://github.com/christianlouis/DocuElevate/projects) board.*
+11 -7
View File
@@ -79,20 +79,24 @@ Users can choose to send documents to any combination of these destinations thro
## Features
- **Intuitive File Upload**:
- Drag-and-drop file upload on both Upload and Files pages—upload anywhere on the Files page
- Real-time upload progress with validation
- Support for PDF, Office documents, images, and more (up to 500MB per file)
- **Document Upload & Storage**:
- Manual uploads (via API or UI) to Dropbox, Nextcloud, Google Drive, or Paperless.
- Manual uploads (via API or UI) to Dropbox, Nextcloud, Google Drive, or Paperless
- **OCR Processing (Azure)**:
- Extract text from scanned PDFs using Azure Document Intelligence.
- Extract text from scanned PDFs using Azure Document Intelligence
- **Metadata Extraction (OpenAI)**:
- Use GPT to classify, label, or otherwise enrich the text with structured metadata.
- Use GPT to classify, label, or otherwise enrich the text with structured metadata
- **PDF Conversion (Gotenberg)**:
- Convert non-PDF attachments (e.g., Word docs, images) into PDFs.
- Convert non-PDF attachments (e.g., Word docs, images) into PDFs
- **Document Management (Paperless NGX)**:
- Store processed documents and metadata in a Paperless NGX instance.
- Store processed documents and metadata in a Paperless NGX instance
- **IMAP Integration**:
- Fetch documents from multiple mailboxes (including Gmail) and automatically enqueue them for processing.
- Fetch documents from multiple mailboxes (including Gmail) and automatically enqueue them for processing
- **Authentication**:
- Secure access to the system using **Authentik** for OAuth2-based login.
- Secure access to the system using **Authentik** for OAuth2-based login
## Frameworks Used
+4 -1
View File
@@ -114,7 +114,7 @@ This document tracks actionable tasks for the current development cycle. For lon
- [ ] Add dark mode
- [ ] Add loading spinners for async operations
- [ ] Improve error messages for users
- [ ] Add drag-and-drop file upload
- [x] Add drag-and-drop file upload (completed 2026-02-08)
- [ ] Add file type icons
- [ ] Implement toast notifications
- [ ] Add keyboard shortcuts
@@ -224,6 +224,9 @@ This document tracks actionable tasks for the current development cycle. For lon
- [x] Created comprehensive settings documentation
- [x] Added cryptography dependency for encryption
- [x] Analyzed existing frameworks (justified custom implementation)
- [x] Added drag-and-drop file upload to Files view
- [x] Extracted reusable upload.js module for code reuse
- [x] Enhanced UX with visual drop overlay and upload progress modal
### 2026-02-06
- [x] Created comprehensive test infrastructure
+27 -5
View File
@@ -43,13 +43,35 @@ DocuElevate features a simple navigation system with the following main sections
## Uploading Documents
### Web Upload
DocuElevate provides multiple convenient ways to upload documents to the system.
### Web Upload (Upload Page)
1. Navigate to the **Upload** page
2. Drag and drop files onto the upload area, or click to browse your files
3. Select files to upload (supported formats include PDF, Word documents, images, etc.)
4. Click "Upload" to begin the process
5. Your documents will be processed automatically according to the system configuration
2. **Drag and drop** files onto the upload area, or **click** the upload area to browse your files
3. Select files to upload (supported formats include PDF, Word documents, Excel spreadsheets, PowerPoint presentations, images, and more)
4. The system will automatically validate and upload your files
5. You'll see real-time progress for each file being uploaded
6. Your documents will be processed automatically according to the system configuration
#### Supported File Types
- **Documents**: PDF, Word (.doc, .docx), Excel (.xls, .xlsx), PowerPoint (.ppt, .pptx)
- **Images**: JPEG, PNG, GIF, BMP, TIFF, WebP, SVG
- **Text**: Plain text (.txt), CSV, RTF, HTML, XML, Markdown
- **Maximum file size**: 500MB per file
### Drag-and-Drop on Files Page
For even more convenience, you can upload files directly from the **Files** page:
1. Navigate to the **Files** page where you view your processed documents
2. **Drag files from your computer** and drop them **anywhere** on the page
3. A visual overlay will appear to confirm you're in drag mode
4. Release the files to begin uploading
5. An upload progress modal will appear in the bottom-right corner
6. The page will automatically refresh to show your newly uploaded files once complete
This feature allows you to quickly add new files without navigating away from your document management view.
### Email Attachments
+264
View File
@@ -0,0 +1,264 @@
// frontend/static/js/upload.js
// Reusable drag-and-drop upload functionality for DocuElevate
// Configuration
const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB
// Allowed file types
const ACCEPTED_TYPES = {
// PDF files
'application/pdf': true,
// Image formats
'image/jpeg': true, 'image/jpg': true, 'image/png': true,
'image/gif': true, 'image/bmp': true, 'image/tiff': true,
'image/webp': true, 'image/svg+xml': true,
// Office document formats - Word
'application/msword': true,
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': true,
'application/vnd.openxmlformats-officedocument.wordprocessingml.template': true,
'application/vnd.ms-word.document.macroEnabled.12': true,
// Excel
'application/vnd.ms-excel': true,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': true,
'application/vnd.openxmlformats-officedocument.spreadsheetml.template': true,
'application/vnd.ms-excel.sheet.macroEnabled.12': true,
// PowerPoint
'application/vnd.ms-powerpoint': true,
'application/vnd.openxmlformats-officedocument.presentationml.presentation': true,
'application/vnd.openxmlformats-officedocument.presentationml.template': true,
'application/vnd.openxmlformats-officedocument.presentationml.slideshow': true,
// Other common formats
'text/plain': true,
'text/csv': true,
'application/rtf': true,
'text/rtf': true,
'text/html': true,
'application/xml': true,
'text/xml': true
};
// File extensions that are always allowed (even if mime type is not recognized)
const ACCEPTED_EXTENSIONS = [
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.odt', '.ods', '.odp', '.rtf', '.txt', '.csv',
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg', '.md'
];
/**
* Process a list of files for upload
* @param {FileList} files - Files to process
* @param {HTMLElement} progressContainer - Container element for progress display
* @param {HTMLElement} statusMessage - Element for status message display
*/
function processFiles(files, progressContainer, statusMessage) {
if (files.length === 0) return;
if (statusMessage) {
statusMessage.textContent = `Processing ${files.length} file(s)...`;
}
// Clear previous upload progress
if (progressContainer) {
progressContainer.innerHTML = "";
}
// Process each file
for (let i = 0; i < files.length; i++) {
const file = files[i];
validateAndUpload(file, progressContainer, statusMessage);
}
}
/**
* Validate and upload a single file
* @param {File} file - File to validate and upload
* @param {HTMLElement} progressContainer - Container element for progress display
* @param {HTMLElement} statusMessage - Element for status message display
*/
function validateAndUpload(file, progressContainer, statusMessage) {
// Create progress element for this file
const fileProgress = document.createElement("div");
fileProgress.className = "flex flex-col mb-2";
fileProgress.innerHTML = `
<div class="flex justify-between">
<span class="text-sm truncate" title="${file.name}">${file.name}</span>
<span class="text-xs text-gray-500">${formatFileSize(file.size)}</span>
</div>
<div class="w-full bg-gray-200 h-2 rounded-full mt-1">
<div class="file-progress-bar bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
</div>
<div class="file-status text-xs text-gray-600 mt-1">Validating...</div>
`;
if (progressContainer) {
progressContainer.appendChild(fileProgress);
}
const progressBar = fileProgress.querySelector(".file-progress-bar");
const statusEl = fileProgress.querySelector(".file-status");
// Validate file type by checking both MIME type and extension
const isValidMimeType = ACCEPTED_TYPES[file.type] || false;
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
const isValidExtension = ACCEPTED_EXTENSIONS.includes(fileExtension);
if (!isValidMimeType && !isValidExtension) {
statusEl.textContent = `Error: ${file.name} - Unsupported file type`;
statusEl.className = "text-xs text-red-500 mt-1";
return;
}
// Validate file size
if (file.size > MAX_FILE_SIZE) {
statusEl.textContent = `Error: ${file.name} - File size exceeds 500MB limit`;
statusEl.className = "text-xs text-red-500 mt-1";
return;
}
// Upload the file
uploadFile(file, progressBar, statusEl, statusMessage);
}
/**
* Upload a file to the server
* @param {File} file - File to upload
* @param {HTMLElement} progressBar - Progress bar element
* @param {HTMLElement} statusEl - Status element
* @param {HTMLElement} statusMessage - Overall status message element
*/
async function uploadFile(file, progressBar, statusEl, statusMessage) {
statusEl.textContent = `Uploading...`;
try {
let formData = new FormData();
formData.append("file", file);
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/ui-upload", true);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + "%";
statusEl.textContent = `Uploading: ${Math.round(percentComplete)}%`;
}
};
xhr.onload = function() {
if (xhr.status === 200) {
const result = JSON.parse(xhr.responseText);
progressBar.style.width = "100%";
progressBar.className = "file-progress-bar bg-green-500 h-2 rounded-full";
statusEl.textContent = `Success: Task ID: ${result.task_id}`;
statusEl.className = "text-xs text-green-600 mt-1";
updateOverallStatus(statusMessage);
} else {
throw new Error(`Upload failed with status ${xhr.status}`);
}
};
xhr.onerror = function() {
throw new Error("Network error occurred");
};
xhr.send(formData);
} catch (err) {
statusEl.textContent = `Error: ${err.message}`;
statusEl.className = "text-xs text-red-500 mt-1";
progressBar.className = "file-progress-bar bg-red-500 h-2 rounded-full";
updateOverallStatus(statusMessage);
}
}
/**
* Update the overall status message based on file statuses
* @param {HTMLElement} statusMessage - Status message element
*/
function updateOverallStatus(statusMessage) {
if (!statusMessage) return;
// Count success/failure
const fileStatuses = document.querySelectorAll('.file-status');
let completed = 0;
let total = fileStatuses.length;
fileStatuses.forEach(status => {
if (status.textContent.includes('Success') || status.textContent.includes('Error')) {
completed++;
}
});
if (completed === total) {
statusMessage.textContent = `All uploads completed (${completed}/${total})`;
} else {
statusMessage.textContent = `Uploading files (${completed}/${total})`;
}
}
/**
* Format file size for display
* @param {number} bytes - File size in bytes
* @returns {string} Formatted file size
*/
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
/**
* Initialize drag-and-drop on an element
* @param {HTMLElement} element - Element to enable drag-and-drop on
* @param {HTMLElement} progressContainer - Container for progress display
* @param {HTMLElement} statusMessage - Element for status messages
* @param {Object} options - Additional options
*/
function initDragAndDrop(element, progressContainer, statusMessage, options = {}) {
if (!element) {
console.error("Element not found for drag-and-drop initialization");
return;
}
// Add event listeners for drag-and-drop
element.addEventListener("dragover", (e) => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = "copy";
// Add visual feedback
if (options.dragOverClass) {
element.classList.add(options.dragOverClass);
}
});
element.addEventListener("dragleave", (e) => {
e.preventDefault();
e.stopPropagation();
// Remove visual feedback
if (options.dragOverClass) {
element.classList.remove(options.dragOverClass);
}
});
element.addEventListener("drop", (e) => {
e.preventDefault();
e.stopPropagation();
// Remove visual feedback
if (options.dragOverClass) {
element.classList.remove(options.dragOverClass);
}
if (e.dataTransfer.files.length) {
processFiles(e.dataTransfer.files, progressContainer, statusMessage);
}
});
}
+186
View File
@@ -3,7 +3,100 @@
{% block head_extra %}
<script src="/static/js/common.js"></script>
<script src="/static/js/upload.js"></script>
<style>
/* Drop overlay styles - covers entire page for drag-and-drop anywhere */
.drop-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(59, 130, 246, 0.15);
border: 6px dashed #3b82f6;
z-index: 999;
pointer-events: none;
justify-content: center;
align-items: center;
}
.drop-overlay.active {
display: flex;
animation: fadeIn 0.2s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.drop-message {
background-color: white;
padding: 2rem 3rem;
border-radius: 0.5rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
text-align: center;
pointer-events: none;
}
.drop-message i {
font-size: 4rem;
color: #3b82f6;
margin-bottom: 1rem;
display: block;
}
.drop-message p {
font-size: 1.5rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.5rem;
}
.drop-message .drop-hint {
font-size: 1rem;
color: #6b7280;
font-weight: 400;
margin-top: 0.5rem;
}
/* Upload progress modal */
.upload-modal {
display: none;
position: fixed;
bottom: 1rem;
right: 1rem;
width: 400px;
max-width: 90vw;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
z-index: 1000;
overflow: hidden;
}
.upload-modal.active {
display: block;
}
.upload-modal-header {
background-color: #3b82f6;
color: white;
padding: 0.75rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.upload-modal-body {
padding: 1rem;
max-height: 400px;
overflow-y: auto;
}
.close-modal-btn {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 1.25rem;
}
.close-modal-btn:hover {
opacity: 0.8;
}
<style>
.file-table {
width: 100%;
border-collapse: collapse;
@@ -241,6 +334,29 @@
{% endblock %}
{% block content %}
<!-- Drop overlay -->
<div id="dropOverlay" class="drop-overlay">
<div class="drop-message">
<i class="fas fa-cloud-upload-alt"></i>
<p>Drop files anywhere to upload</p>
<div class="drop-hint">Supports PDF, Office docs, images, and more</div>
</div>
</div>
<!-- Upload progress modal -->
<div id="uploadModal" class="upload-modal">
<div class="upload-modal-header">
<span><i class="fas fa-upload"></i> Uploading Files</span>
<button class="close-modal-btn" onclick="closeUploadModal()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="upload-modal-body">
<div id="uploadStatusMessage" class="text-sm text-gray-700 mb-2"></div>
<div id="uploadProgressContainer" class="space-y-2"></div>
</div>
</div>
<div class="container mx-auto px-4 py-8">
<h2 class="text-3xl font-bold mb-6">File Records</h2>
@@ -673,6 +789,76 @@
alert(`Error reprocessing files: ${error.message}`);
});
}
// ===== Drag-and-Drop Upload Functionality =====
const dropOverlay = document.getElementById('dropOverlay');
const uploadModal = document.getElementById('uploadModal');
const uploadStatusMessage = document.getElementById('uploadStatusMessage');
const uploadProgressContainer = document.getElementById('uploadProgressContainer');
let dragCounter = 0; // Track nested drag events
// Show overlay when dragging files over the window
window.addEventListener('dragenter', (e) => {
e.preventDefault();
dragCounter++;
// Only show overlay if dragging files
if (e.dataTransfer.types.includes('Files')) {
dropOverlay.classList.add('active');
}
});
window.addEventListener('dragleave', (e) => {
e.preventDefault();
dragCounter--;
if (dragCounter === 0) {
dropOverlay.classList.remove('active');
}
});
window.addEventListener('dragover', (e) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'copy';
});
window.addEventListener('drop', (e) => {
e.preventDefault();
dragCounter = 0;
dropOverlay.classList.remove('active');
if (e.dataTransfer.files.length > 0) {
// Show upload modal
uploadModal.classList.add('active');
uploadProgressContainer.innerHTML = '';
// Process the dropped files
processFiles(e.dataTransfer.files, uploadProgressContainer, uploadStatusMessage);
// Optionally reload page after uploads complete (with a delay)
setTimeout(() => {
const fileStatuses = document.querySelectorAll('.file-status');
let allCompleted = true;
fileStatuses.forEach(status => {
if (!status.textContent.includes('Success') && !status.textContent.includes('Error')) {
allCompleted = false;
}
});
if (allCompleted && fileStatuses.length > 0) {
// Refresh the page after a short delay to show the new files
setTimeout(() => {
window.location.reload();
}, 2000);
}
}, 1000);
}
});
function closeUploadModal() {
uploadModal.classList.remove('active');
}
</script>
</div>
{% endblock %}
+19 -180
View File
@@ -37,55 +37,8 @@
{% endblock %}
{% block scripts %}
<script src="/static/js/upload.js"></script>
<script>
// Configuration
const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB
// Allowed file types
const ACCEPTED_TYPES = {
// PDF files
'application/pdf': true,
// Image formats
'image/jpeg': true, 'image/jpg': true, 'image/png': true,
'image/gif': true, 'image/bmp': true, 'image/tiff': true,
'image/webp': true, 'image/svg+xml': true,
// Office document formats - Word
'application/msword': true,
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': true,
'application/vnd.openxmlformats-officedocument.wordprocessingml.template': true,
'application/vnd.ms-word.document.macroEnabled.12': true,
// Excel
'application/vnd.ms-excel': true,
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': true,
'application/vnd.openxmlformats-officedocument.spreadsheetml.template': true,
'application/vnd.ms-excel.sheet.macroEnabled.12': true,
// PowerPoint
'application/vnd.ms-powerpoint': true,
'application/vnd.openxmlformats-officedocument.presentationml.presentation': true,
'application/vnd.openxmlformats-officedocument.presentationml.template': true,
'application/vnd.openxmlformats-officedocument.presentationml.slideshow': true,
// Other common formats
'text/plain': true,
'text/csv': true,
'application/rtf': true,
'text/rtf': true,
'text/html': true,
'application/xml': true,
'text/xml': true
};
// File extensions that are always allowed (even if mime type is not recognized)
const ACCEPTED_EXTENSIONS = [
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
'.odt', '.ods', '.odp', '.rtf', '.txt', '.csv',
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.svg', '.md'
];
// Drag-and-drop / file input logic
const dropZone = document.getElementById("dropZone");
const fileInput = document.getElementById("fileInput");
@@ -109,146 +62,32 @@
e.preventDefault();
dropZone.classList.remove("bg-gray-100");
if (e.dataTransfer.files.length) {
processFiles(e.dataTransfer.files);
// Clear previous upload progress
uploadProgress.innerHTML = "";
// Create progress container
const progressContainer = document.createElement("div");
progressContainer.className = "space-y-2";
uploadProgress.appendChild(progressContainer);
// Use the shared processFiles function
processFiles(e.dataTransfer.files, progressContainer, statusMessage);
}
}
function handleFileSelect(e) {
if (e.target.files.length) {
processFiles(e.target.files);
}
}
function processFiles(files) {
if (files.length === 0) return;
statusMessage.textContent = `Processing ${files.length} file(s)...`;
// Clear previous upload progress
uploadProgress.innerHTML = "";
// Create progress container
const progressContainer = document.createElement("div");
progressContainer.className = "space-y-2";
uploadProgress.appendChild(progressContainer);
// Process each file
for (let i = 0; i < files.length; i++) {
const file = files[i];
validateAndUpload(file, progressContainer);
}
}
function validateAndUpload(file, progressContainer) {
// Create progress element for this file
const fileProgress = document.createElement("div");
fileProgress.className = "flex flex-col mb-2";
fileProgress.innerHTML = `
<div class="flex justify-between">
<span class="text-sm truncate" title="${file.name}">${file.name}</span>
<span class="text-xs text-gray-500">${formatFileSize(file.size)}</span>
</div>
<div class="w-full bg-gray-200 h-2 rounded-full mt-1">
<div class="file-progress-bar bg-blue-500 h-2 rounded-full" style="width: 0%"></div>
</div>
<div class="file-status text-xs text-gray-600 mt-1">Validating...</div>
`;
progressContainer.appendChild(fileProgress);
const progressBar = fileProgress.querySelector(".file-progress-bar");
const statusEl = fileProgress.querySelector(".file-status");
// Validate file type by checking both MIME type and extension
const isValidMimeType = ACCEPTED_TYPES[file.type] || false;
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
const isValidExtension = ACCEPTED_EXTENSIONS.includes(fileExtension);
if (!isValidMimeType && !isValidExtension) {
statusEl.textContent = `Error: ${file.name} - Unsupported file type`;
statusEl.className = "text-xs text-red-500 mt-1";
return;
}
// Validate file size
if (file.size > MAX_FILE_SIZE) {
statusEl.textContent = `Error: ${file.name} - File size exceeds 500MB limit`;
statusEl.className = "text-xs text-red-500 mt-1";
return;
}
// Upload the file
uploadFile(file, progressBar, statusEl);
}
async function uploadFile(file, progressBar, statusEl) {
statusEl.textContent = `Uploading...`;
try {
let formData = new FormData();
formData.append("file", file);
// Clear previous upload progress
uploadProgress.innerHTML = "";
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/ui-upload", true);
// Create progress container
const progressContainer = document.createElement("div");
progressContainer.className = "space-y-2";
uploadProgress.appendChild(progressContainer);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.style.width = percentComplete + "%";
statusEl.textContent = `Uploading: ${Math.round(percentComplete)}%`;
}
};
xhr.onload = function() {
if (xhr.status === 200) {
const result = JSON.parse(xhr.responseText);
progressBar.style.width = "100%";
progressBar.className = "file-progress-bar bg-green-500 h-2 rounded-full";
statusEl.textContent = `Success: Task ID: ${result.task_id}`;
statusEl.className = "text-xs text-green-600 mt-1";
updateOverallStatus();
} else {
throw new Error(`Upload failed with status ${xhr.status}`);
}
};
xhr.onerror = function() {
throw new Error("Network error occurred");
};
xhr.send(formData);
} catch (err) {
statusEl.textContent = `Error: ${err.message}`;
statusEl.className = "text-xs text-red-500 mt-1";
progressBar.className = "file-progress-bar bg-red-500 h-2 rounded-full";
updateOverallStatus();
// Use the shared processFiles function
processFiles(e.target.files, progressContainer, statusMessage);
}
}
function updateOverallStatus() {
// Count success/failure
const fileStatuses = document.querySelectorAll('.file-status');
let completed = 0;
let total = fileStatuses.length;
fileStatuses.forEach(status => {
if (status.textContent.includes('Success') || status.textContent.includes('Error')) {
completed++;
}
});
if (completed === total) {
statusMessage.textContent = `All uploads completed (${completed}/${total})`;
} else {
statusMessage.textContent = `Uploading files (${completed}/${total})`;
}
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
</script>
{% endblock %}
+22
View File
@@ -68,3 +68,25 @@ class TestFilesView:
# The key test: ensure the min/max functions work in the template
# (they're used for pagination on lines 397 and 406 of files.html)
_assert_no_template_errors(content)
def test_files_view_includes_drag_drop_elements(self, client: TestClient, db_session):
"""Test that the /files view includes drag-and-drop upload elements."""
# Access the /files view
response = client.get("/files")
assert response.status_code == 200
content = response.text
# Check that drag-and-drop elements are present
assert "dropOverlay" in content, "Drop overlay element should be present"
assert "uploadModal" in content, "Upload modal element should be present"
assert "drop-overlay" in content, "Drop overlay CSS class should be present"
assert "upload-modal" in content, "Upload modal CSS class should be present"
# Check that the upload.js script is included
assert "/static/js/upload.js" in content, "upload.js script should be included"
# Check for drag-and-drop event handlers
assert "dragenter" in content or "drag" in content, "Drag event handlers should be present"
assert "Drop files anywhere to upload" in content, "Drop message should be present"