Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
29679a8
Branding: Update with latest NLR logos
simmsa May 22, 2026
67a577a
Dev: Lint
simmsa May 22, 2026
b40e132
Dev: Add biome front end lint
simmsa May 22, 2026
c855224
Frontend: Add folder picker component
simmsa May 22, 2026
3757c4f
Frontend: Add large folder service helpers
simmsa May 22, 2026
f57b314
Frontend: Add modal for suggesting large folder usage
simmsa May 22, 2026
043cdfa
Frontend: Add user facing aws sync page with virtualization
simmsa May 22, 2026
6a45048
App: Add aws sync upload support
simmsa May 22, 2026
68c80a5
Frontend: Dev, configure biome linter/formatter
simmsa May 22, 2026
91b0ed0
Frontend: Add services for job tracking
simmsa May 22, 2026
67620b9
Frontend: lint
simmsa May 22, 2026
410e0d6
Frontend: lint
simmsa May 22, 2026
fccda46
Frontend: lint
simmsa May 22, 2026
c631d0a
Frontend: lint
simmsa May 22, 2026
12557c1
Frontend: lint
simmsa May 22, 2026
587e588
Frontend: lint
simmsa May 22, 2026
90d24eb
Frontend: lint
simmsa May 22, 2026
8ad3b98
Frontend: lint
simmsa May 22, 2026
e714a70
Frontend: lint
simmsa May 22, 2026
7bfa263
Frontend: lint
simmsa May 22, 2026
ff72460
Frontend: lint
simmsa May 22, 2026
d9898f2
Frontend: lint
simmsa May 22, 2026
0076a09
Frontend: lint
simmsa May 22, 2026
83350b7
Frontend: lint
simmsa May 22, 2026
c5ec2c9
Frontend: lint
simmsa May 22, 2026
b9e4015
Frontend: Add github links to about modal
simmsa May 22, 2026
f0a3cd7
Frontend: Update NLR logo link
simmsa May 22, 2026
33e0188
Frontend: lint
simmsa May 22, 2026
f2602c0
Frontend: lint
simmsa May 22, 2026
0403750
Frontend: lint
simmsa May 22, 2026
f5f3c1d
Frontend: Load CSV history async
simmsa May 22, 2026
e2d986e
Frontend: lint
simmsa May 22, 2026
9b7b5c0
Frontend: lint
simmsa May 22, 2026
49a6677
Frontend: lint
simmsa May 22, 2026
1676365
Frontend: lint
simmsa May 22, 2026
eda01df
Frontend: lint
simmsa May 22, 2026
be9bf95
Frontend: lint
simmsa May 22, 2026
02e092a
Frontend: lint
simmsa May 22, 2026
79cc844
Frontend: Lint
simmsa May 22, 2026
31297e7
Lint
simmsa May 22, 2026
b2f25f0
Frontend: lint tests
simmsa May 22, 2026
c82dcce
Frontend: Lint utils
simmsa May 22, 2026
477fe93
Frontend: Lint hooks
simmsa May 22, 2026
7473634
Frontend: Lint stores
simmsa May 22, 2026
002c6bb
Frontend: Lint types
simmsa May 22, 2026
a9fdbc8
Frontend: Refactor and lint pages
simmsa May 22, 2026
08c69c5
Frontend: Lint css
simmsa May 22, 2026
0e9b95e
Frontend: Add large folder upload route
simmsa May 22, 2026
cf6a265
Frontend: Lint app entry
simmsa May 22, 2026
2753a50
Frontend: Lint main
simmsa May 22, 2026
70aec82
Frontend: Add folder browser components
simmsa May 22, 2026
df825e4
Frontend: Add store tests
simmsa May 22, 2026
5f40684
Backend: add generic SSE manager
simmsa May 22, 2026
1f123e2
Backend: Add common job model
simmsa May 22, 2026
b39c6ce
Backend: Add generic service for handling file operations
simmsa May 22, 2026
3534581
Tetst: fix failing tests
simmsa May 22, 2026
9c804ea
Backend: Leverage models for upload manager
simmsa May 22, 2026
3da29b5
Backend: enable WAL for sqlite
simmsa May 22, 2026
5319a0b
Backend: Leverage DRY models in delete_manager
simmsa May 22, 2026
cff7cf5
Backend: Leverage SSE manager for uploads
simmsa May 22, 2026
dd60054
Backend: Add large folder upload route
simmsa May 22, 2026
e7eddb2
Backesd: Use os.walk for file search
simmsa May 22, 2026
e1bccb1
Backend: Leverage sse manager for deletions
simmsa May 22, 2026
52541d6
Config: Add log file support to file extensions
simmsa May 22, 2026
5495df0
App: Update init to support new features
simmsa May 22, 2026
3f513bb
Settings: Spec out user facing extensions
simmsa May 22, 2026
7c90533
Version: Bump to 1.1.0
simmsa May 23, 2026
d6424e9
Feature: Add auto update modal that leverages tags/versions for updates
simmsa May 23, 2026
6135451
Frontend: Fix failing tests
simmsa May 23, 2026
0b28f43
Dev: Add copilot instructions
simmsa May 23, 2026
55e527d
Merge pull request #13 from MODAQ2/feat_leverage_react
simmsa May 23, 2026
1e99104
Docs: Update installation instructions
simmsa May 23, 2026
e5ae829
Docs: add "page" features to documentation
simmsa May 23, 2026
b3e2169
Merge pull request #15 from MODAQ2/feat_leverage_react
simmsa May 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copilot Instructions

## Architecture & Tech Stack

This project is a hybrid Flask + React application for uploading MCAP files to AWS S3.

- **Backend (`app/`)**: Python 3.11+ Flask API.
- Serves the built React frontend from `frontend/dist`.
- Uses `boto3` for AWS S3 operations.
- Uses `modaq_toolkit` for MCAP file parsing.
- Handles file uploads, duplicate detection, and progress tracking via SSE.
- **Frontend (`frontend/`)**: React 19 SPA built with Vite.
- TypeScript, TailwindCSS, Zustand (state), TanStack Table.
- Communicates with backend via `/api/*` endpoints.
- Proxies to backend port 5000 during development.

## Development Workflow

- **Backend**: Run `python app.py` (serves on :5000).
- **Frontend**: Run `cd frontend && npm run dev` (serves on :3000, proxies `/api` to :5000).
- **Production**: Frontend is built to `frontend/dist`, which Flask serves statically.

## Build, Test, and Lint Commands

### Python (Backend)
- **Test**: `pytest tests/ -v` (Single file: `pytest tests/test_mcap_service.py -v`)
- **Lint**: `ruff check app/ tests/`
- **Format**: `ruff format app/ tests/`
- **Type Check**: `mypy app/`

### JavaScript/TypeScript (Frontend)
- **Build**: `cd frontend && npm run build` (outputs to `frontend/dist`)
- **Test**: `cd frontend && npm run test` (Vitest)
- **Lint**: `cd frontend && npm run lint` (Biome)
- **Type Check**: `cd frontend && npm run typecheck`

## Key Conventions

### Branding & UI
- **Organization Name**: National Laboratory of the Rockies (**NLR**). **NEVER** use "NREL".
- **CSS Classes**: Use `nlr-` prefix for custom classes (e.g., `nlr-blue-500` in Tailwind).
- **Icons**: Import icons ONLY from `frontend/src/utils/icons.tsx` (abstraction over lucide-react). Do not import directly from icon libraries.

### Data & S3
- **S3 Paths**: Use Hive partitioning: `year=YYYY/month=MM/day=DD/hour=HH/minute=M0/filename.mcap`.
- Minutes are bucketed to 10-minute intervals (00, 10, 20...).
- **Timestamps**: Extracted from MCAP files using `MCAPParser` from `modaq_toolkit`.

### Project Structure
- `app/routes/main.py`: Serves the React app (`frontend/dist/index.html`).
- `app/routes/upload.py`: Handles upload logic and SSE progress streams.
- `app/static`: **Legacy/Unused**. Do not use for new frontend code; work in `frontend/`.
197 changes: 169 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A Python based local web application for uploading MODAQ files to S3 with progress tracking, duplicate detection, and configuration.

![MODAQ Upload front page showing the upload page with drag-and-drop area](./docs/img/upload_screen.png)
![MODAQ Upload: Upload page showing the folder browser with per-folder upload status](./docs/images/index_upload.png)

## Features

Expand Down Expand Up @@ -39,28 +39,51 @@ python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

3. Install dependencies:
3. Install Python dependencies:

```bash
pip install -r requirements.txt
```

4. (Optional) Install development dependencies:
4. Install frontend dependencies and build the UI:

```bash
pip install -r requirements-dev.txt
cd frontend && npm install && npm run build && cd ..
```

## Usage

### Running the Application (Development)
### Running the Application

```bash
python app.py
```

The application will be available at `http://localhost:5000`.

### Development (Live Reload)

Run the Flask backend and Vite frontend separately for hot-reload during development:

Terminal 1: Flask API (port 5000):

```bash
python app.py
```

Terminal 2: Vite dev server (port 3000):

```bash
cd frontend && npm run dev
```

Open `http://localhost:3000`. The Vite server proxies `/api` requests to Flask.
After making frontend changes you're happy with, rebuild for production:

```bash
cd frontend && npm run build
```

### Production Deployment (Linux)

For production use on a Linux machine, use the automated installation script which sets up a systemd service with Gunicorn.
Expand Down Expand Up @@ -175,28 +198,140 @@ Settings are saved to `settings.json` (gitignored).

### Uploading Files

1. Go to the Upload page
2. Drag and drop MCAP files or click to select
3. Review the analysis results showing timestamps and S3 paths
4. Check "Skip duplicates" to avoid re-uploading existing files
5. Click Upload Files to start
6. Monitor progress in real-time
See [Application Guide: Upload](#upload) below for a walkthrough.

### Browsing Files

1. Go to the Browse Files page
2. Navigate through the Hive-partitioned folder structure
3. Use the search box to find specific files
See [Application Guide: Browse Uploaded Files](#browse-uploaded-files) below.

### Updating the Application

1. Go to Settings > Application Updates
2. Click Check for Updates to see if updates are available
3. Click Update Application to:
- Pull latest changes from git
- Reinstall Python dependencies
- Update modaq_toolkit to the latest version
4. Restart the application after updating
See [Application Guide: Updating the Software](#updating-the-software) below.

## Application Guide

### Upload

![Upload page showing the folder browser with per-folder upload status and step indicator](./docs/images/index_upload.png)

The Upload page walks you through uploading files in four steps: Select → Review → Upload → Complete.

1. Navigate to the folder containing your MCAP files using the file browser. Quick Links on the left give fast access to common locations.
2. The browser shows each subfolder's upload status: data file count, log file count, and how many have already been uploaded to S3.
3. Check or uncheck folders to include or exclude them. Use Select All / None or search to filter.
4. Click Upload N files to proceed to the Review step, where you can inspect the per-file S3 destination paths before committing.
5. Already-uploaded files are skipped; no duplicates are created.

For more than 500 files, use [Large Folder Upload](#large-folder-upload) instead.


### Large Folder Upload

![Large Folder Upload page showing the folder sync interface](./docs/images/large_folder_upload_index.png)

The Large Folder Upload page syncs an entire folder tree to S3 without per-file analysis. Use this when you have 500+ files and don't need to inspect each file's timestamp individually.

1. Navigate to the root folder you want to sync.
2. Click Select This Folder to confirm.
3. The folder structure is copied to S3 as-is. Already-uploaded files are skipped.


### Browse Uploaded Files

![Browse Uploaded Files page showing the S3 bucket folder list](./docs/images/browse_index.png)

The Browse Uploaded Files page lets you navigate the contents of your S3 bucket directly from the app.

1. Click any folder to drill down into it.
2. Use the breadcrumb trail at the top to navigate back up.
3. This is useful for verifying that uploads landed in the correct location.


### History

![History page showing upload sessions with file counts, data sizes, and transfer speeds](./docs/images/logs_index.png)

The History page keeps a record of every upload session run from this machine.

- Upload History tab: each session's date, file count, data transferred, transfer speed, and outcome (completed / skipped / failed). Click any row to expand the per-file breakdown. Use CSV to export a session log.
- Event Log tab: application events for troubleshooting.

The running totals at the top (files uploaded, total data, failed, sessions) summarise all sessions.


### Clear Hard Drive

![Clear Hard Drive page showing folder selection with uploaded/deletable file counts](./docs/images/delete_index.png)

The Clear Hard Drive page removes local files that have already been uploaded to S3. Files are verified against S3 before any deletion.

1. Navigate to the folder you want to clean up.
2. The browser shows how many files are uploaded (deletable) vs not yet uploaded.
3. Only uploaded files are deleted; files not yet in S3 are not touched.
4. Click Clear N files and confirm to proceed through the workflow (Select → Review → Confirm → Clear → Complete).


### Settings

![Settings page showing AWS configuration fields](./docs/images/settings_index.png)

The Settings page controls the AWS connection used for all uploads and browsing.

- AWS Profile: profile from `~/.aws/credentials` to use.
- AWS Region: region of your S3 bucket.
- S3 Bucket: bucket files are uploaded to.
- Default Upload Folder: pre-populates the file browser on the Upload page.
- Display Name: title shown in the application header.
- Log Directory: where upload history logs are stored.

Fields marked *Locked: set by environment variable* are controlled by your `.env` file and cannot be changed from the UI (see [Configuration](#configuration)).


### Updating the Software

#### v1.1 and later: in-app update

From v1.1 onwards, updates can be applied from within the app.

Option A: via Settings:

1. Click Settings in the navigation bar.
2. Scroll down to the Software Update section.
3. Click Check for updates.

![Software Update section in Settings showing current version, commit, and Check for updates button](./docs/images/settings_index_software_update.png)

4. If updates are available, click Update Application. This pulls the latest code, reinstalls Python dependencies, and updates `modaq_toolkit`.
5. Restart the application after the update completes (`Ctrl+C` then `python app.py`, or restart the systemd service).

Option B: via the About modal:

1. Click the version badge (e.g. v1.1.0) in the navigation bar to open the About modal.
2. Expand the Software Update section and follow the same steps.

#### Before v1.1: manual update

If you are running a version prior to v1.1, the in-app updater is not available. Update manually from the terminal:

```bash
cd modaq_upload
git pull
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
cd frontend && npm install && npm run build && cd ..
```

Then restart the application:

```bash
python app.py
```

Or, if running as a systemd service:

```bash
sudo systemctl restart modaq-upload
```

## S3 Path Format

Expand Down Expand Up @@ -238,31 +373,37 @@ ruff format app/ tests/
mypy app/
```

### Install Development Dependencies

```bash
pip install -r requirements-dev.txt
```

### JavaScript Linting (Biome)

```bash
npm run lint # Check
npm run lint:fix # Auto-fix
cd frontend && npm run lint # Check
cd frontend && npm run lint:fix # Auto-fix
```

### JavaScript Type Checking

```bash
npm run typecheck # tsc --checkJs via jsconfig.json
cd frontend && npm run typecheck
```

### JavaScript Testing (Vitest)

```bash
npm run test # Run all JS tests
npm run test:watch # Watch mode
npm run test:coverage # With coverage report
cd frontend && npm run test # Run all JS tests
cd frontend && npm run test:watch # Watch mode
cd frontend && npm run test:coverage # With coverage report
```

### All JS Checks

```bash
npm run check # Biome + tsc + Vitest
cd frontend && npm run check # Biome + tsc + Vitest
```

## Architecture
Expand Down
6 changes: 4 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@

def _sse_cleanup_worker() -> None:
"""Background worker that periodically cleans up stale SSE queues."""
from app.routes.upload import _cleanup_old_sse_queues
from app.services.sse_manager import get_sse_manager

while not _cleanup_stop_event.wait(timeout=300): # Check every 5 minutes
try:
removed = _cleanup_old_sse_queues()
removed = get_sse_manager().cleanup_old_queues()
if removed > 0:
from app.services.log_service import get_log_service

Expand Down Expand Up @@ -72,6 +72,7 @@ def inject_display_name() -> dict[str, str]:
# Register blueprints
from app.routes.delete import delete_bp
from app.routes.files import files_bp
from app.routes.large_folder_upload import large_folder_upload_bp
from app.routes.logs import logs_bp
from app.routes.main import main_bp
from app.routes.settings import settings_bp
Expand All @@ -83,6 +84,7 @@ def inject_display_name() -> dict[str, str]:
app.register_blueprint(settings_bp, url_prefix="/api/settings")
app.register_blueprint(logs_bp, url_prefix="/api/logs")
app.register_blueprint(delete_bp, url_prefix="/api/delete")
app.register_blueprint(large_folder_upload_bp, url_prefix="/api/large-folder-upload")

# Start background SSE cleanup thread
_start_sse_cleanup()
Expand Down
Loading
Loading