A read-only Laravel web interface for monitoring file system changes on a network drive.
Pairs with a Python file watcher that logs events to a SQLite database.
- Dashboard — Real-time metrics, sparkline chart, event type breakdown, and recent activity feed
- Analytics — Multi-range charts (7d / 30d / 90d / 1y) with stacked bar chart, top folders, file extensions, and size distribution
- Events Log — Filterable and paginated table with search, date range, event type, and extension filters
- Snapshot — Current file state with expandable directory tree, stale file detection, auto-refreshes every 15s
- File Timeline — Complete event history tracking files across renames and moves via MD5 hash linking
- Health Monitoring — Live/offline status indicator via heartbeat polling from the Python script
- Auto-refresh — Change-driven polling that reloads the page only when new events are detected
- Dark Mode — Class-based toggle with localStorage persistence
app/
├── Enums/ # EventType enum with badge colors and labels
├── Http/
│ ├── Controllers/# Dashboard, Event, File, Snapshot, Health, Analytics
│ └── Requests/ # EventFilter, SnapshotFilter, FileTimeline
├── Models/ # Event, Snapshot, Config (read-only)
├── Services/ # EventService, SnapshotService, ConfigService, Formatter
├── Providers/ # ViewServiceProvider (shared layout data)
└── View/Models/ # DashboardViewModel, EventViewModel, SnapshotViewModel
resources/views/
├── components/
│ └── layouts/
│ └── app.blade.php # Shared layout: sidebar, topbar, offline banner
├── analytics/
│ └── index.blade.php # Analytics page with Alpine.js charts
├── dashboard.blade.php
├── events/index.blade.php
├── files/timeline.blade.php
└── snapshot/
├── index.blade.php
└── _tree.blade.php # AJAX partial for directory tree
| Page | Route | Description |
|---|---|---|
| Dashboard | /filewatcher/dashboard |
Metrics, sparkline, event type bars, recent activity |
| Analytics | /filewatcher/analytics |
Multi-range charts and breakdowns |
| Events Log | /filewatcher/events |
Filterable event table with pagination |
| Snapshot | /filewatcher/snapshot |
Current file states and directory tree |
| File Timeline | /filewatcher/files?path=... |
Full event history for a single file |
The analytics page is precomputed server-side across four date ranges and rendered entirely client-side via Alpine.js with no additional requests on range switch.
| Pill | Range | Chart grouping |
|---|---|---|
| 7d | Last 7 days | Daily bars, scrollable |
| 30d | Last 30 days | Daily bars, scrollable |
| 90d | Last 90 days | Daily bars, compressed |
| 1y | Last 365 days | Weekly bars, compressed |
- Total events — count for the selected range
- Daily average — total divided by number of days
- Most active type — event type with highest count and its percentage
- Total data affected — sum of
file_sizeacross all events, formatted (B / KB / MB / GB)
Event volume by type — stacked bar chart with per-type color coding, hover tooltip showing date, per-type counts, and total. Tooltip uses a fixed-position portal to avoid overflow clipping. Y-axis uses dynamic magnitude-based tick scaling. 1y range uses PHP-aggregated weekly grouping.
Top folders by activity — horizontal bar list showing the top 10 directories by event count, with percentage labels. Folder paths are extracted from dirname(src_path) and display the last two path segments.
File extensions — horizontal bar list showing the top 8 extensions by count. Extension is extracted using the last dot in the filename to prevent compound extensions from appearing as full filenames.
File size distribution — bar chart with 6 size buckets: <10 KB, 10–50 KB, 50–200 KB, 200 KB–1 MB, 1–10 MB, >10 MB. Bars use the same hover tooltip pattern.
The UI connects to an existing SQLite database created by the Python script. All tables are read-only.
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK |
Auto-increment |
timestamp |
TEXT |
ISO 8601 datetime |
event_type |
TEXT |
CREATED / MODIFIED / DELETED / RENAMED / MOVED (+ offline variants) |
src_path |
TEXT |
Source file path (UNC or local) |
dest_path |
TEXT |
Destination path for RENAMED/MOVED |
file_size |
INTEGER |
Size in bytes |
md5_hash |
TEXT |
Hash after the event |
prev_hash |
TEXT |
Hash before the event (MODIFIED only) |
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK |
Auto-increment |
path |
TEXT UNIQUE |
Current file path |
size |
INTEGER |
File size |
mtime |
REAL |
Unix timestamp |
md5_hash |
TEXT |
Current hash |
last_seen |
TEXT |
ISO 8601 timestamp |
| Column | Type | Description |
|---|---|---|
key |
TEXT PK |
watch_directory, started_at, heartbeat, script_version |
value |
TEXT |
Corresponding value |
updated |
TEXT |
ISO 8601 timestamp |
Built with Tailwind CSS v4 and Alpine.js. Dark mode uses a class-based strategy (dark on <html>) toggled via Alpine and persisted to localStorage.
Dynamic Alpine :class bindings are not scanned by Tailwind v4's build-time scanner. Classes used in dynamic bindings are safelisted via @source inline() in app.css:
@source inline("ml-60 ml-16 w-60 w-16 translate-x-4.5 translate-x-0.5 dark:bg-gray-800 dark:bg-gray-700 dark:border-gray-700");Custom dark mode variant is defined as:
@custom-variant dark (&:is(.dark *));IDE warnings about unknown @source, @custom-variant are cosmetic — suppress via .vscode/settings.json:
{
"css.lint.unknownAtRules": "ignore"
}| Type | Color | Hex |
|---|---|---|
| CREATED | bg-green-500 |
#22c55e |
| MODIFIED | bg-blue-500 |
#3b82f6 |
| DELETED | bg-red-500 |
#ef4444 |
| RENAMED | bg-yellow-500 |
#eab308 |
| MOVED | bg-teal-400 |
#2dd4bf |
| MOVED & RENAMED | bg-indigo-400 |
#818cf8 |
| Component | Usage | Props |
|---|---|---|
<x-event-badge> |
Events table, dashboard, timeline | label, color |
<x-metric-card> |
Dashboard cards | title, value, trend, icon, sparkline |
<x-file-path> |
All file path displays | path, truncated |
<x-hash-display> |
Hash columns | hash, truncated, searchable |
<x-timeline-dot> |
File timeline | color |
<x-directory-tree> |
Snapshot sidebar | nodes, current-directory, level |
<x-filter-tabs> |
Events quick-filter tabs | tabs, active, base-url |
<x-empty-state> |
Empty table states | title, description, icon |
- PHP 8.2+
- Composer
- Node.js 18+ and npm
- SQLite (PHP extension enabled)
# 1. Clone the repository
git clone <repository-url>
cd filewatcher
# 2. Install PHP dependencies
composer install
# 3. Configure environment
cp .env.example .envEdit .env to point to your SQLite database:
DB_CONNECTION=sqlite
DB_DATABASE=C:/path/to/logs/filelog.db# 4. Generate application key
php artisan key:generate
# 5. Install and build frontend assets
npm install
npm run build
# 6. Start the development server
php artisan serveVisit http://localhost:8000 — the root URL redirects to /filewatcher/dashboard.
# Start Laravel dev server
php artisan serve
# Watch for frontend changes (Vite HMR)
npm run dev
# Build frontend for production
npm run buildThe Laravel UI pairs with a companion Python file watcher available at:
gh repo clone islacchi/Python-File-WatcherThe script:
- Monitors a network drive for file system changes using
watchdog - Logs CREATED, MODIFIED, DELETED, RENAMED, MOVED, and MOVED_AND_RENAMED events to the SQLite database
- Maintains a
snapshotstable with current file state and MD5 hashes - Writes a
heartbeattimestamp to theconfigtable every 30 seconds
The health check endpoint (/filewatcher/health) reads the heartbeat timestamp. If no heartbeat is received within 90 seconds, the UI switches to Offline status and displays a banner. Events logged while offline are stored with an (offline) suffix on the event type and are merged into the canonical type counts throughout the UI.
This project is open-sourced software licensed under the MIT license.