Skip to content

Repository files navigation

Laravel Logo

PHPLaravelTailwind CSSAlpine.jsSQLiteVite

StatusLicense

File Watcher Laravel UI

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.


Features

  • 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 by filename, path, or MD5 hash; 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
  • Hash Search — Clicking any MD5 hash in the Events Log filters all events sharing that file version, enabling full file lineage tracing
  • 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

Architecture

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

Pages

PageRouteDescription
Dashboard/filewatcher/dashboardMetrics, sparkline, event type bars, recent activity
Analytics/filewatcher/analyticsMulti-range charts and breakdowns
Events Log/filewatcher/eventsFilterable event table with pagination
Snapshot/filewatcher/snapshotCurrent file states and directory tree
File Timeline/filewatcher/files?path=...Full event history for a single file

Analytics

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.

Date Ranges

PillRangeChart grouping
7dLast 7 daysDaily bars, scrollable
30dLast 30 daysDaily bars, scrollable
90dLast 90 daysDaily bars, compressed
1yLast 365 daysWeekly bars, compressed

Summary Cards

  • 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_size across all events, formatted (B / KB / MB / GB)

Charts

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.


Database

The UI connects to an existing SQLite database created by the Python script. All tables are read-only.

events — Permanent log of every file change

ColumnTypeDescription
idINTEGER PKAuto-increment
timestampTEXTISO 8601 datetime
event_typeTEXTCREATED / MODIFIED / DELETED / RENAMED / MOVED / MOVED_AND_RENAMED (+ offline variants)
src_pathTEXTSource file path (UNC or local)
dest_pathTEXTDestination path for RENAMED / MOVED / MOVED_AND_RENAMED
file_sizeINTEGERSize in bytes
md5_hashTEXTHash after the event
prev_hashTEXTHash before the event (MODIFIED only)

snapshots — Last known state of every watched file

ColumnTypeDescription
idINTEGER PKAuto-increment
pathTEXT UNIQUECurrent file path
sizeINTEGERFile size
mtimeREALUnix timestamp
md5_hashTEXTCurrent hash
last_seenTEXTISO 8601 timestamp

config — Script metadata (written by Python)

ColumnTypeDescription
keyTEXT PKwatch_directory, started_at, heartbeat, status, script_version
valueTEXTCorresponding value
updatedTEXTISO 8601 timestamp

Design System

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.

Tailwind v4 Notes

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"
}

Event Type Colors

TypeColorHex
CREATEDbg-green-500#22c55e
MODIFIEDbg-blue-500#3b82f6
DELETEDbg-red-500#ef4444
RENAMEDbg-yellow-500#eab308
MOVEDbg-teal-400#2dd4bf
MOVED & RENAMEDbg-indigo-400#818cf8

Reusable Components

ComponentUsageProps
<x-event-badge>Events table, dashboard, timelinelabel, color
<x-metric-card>Dashboard cardstitle, value, trend, icon, sparkline
<x-file-path>All file path displayspath, truncated
<x-hash-display>Hash columns — click to filter by file versionhash, truncated, searchable
<x-timeline-dot>File timelinecolor
<x-directory-tree>Snapshot sidebarnodes, current-directory, level
<x-filter-tabs>Events quick-filter tabstabs, active, base-url
<x-empty-state>Empty table statestitle, description, icon

Prerequisites

  • PHP 8.2+
  • Composer
  • Node.js 18+ and npm
  • SQLite (PHP extension enabled)

Setup

# 1. Clone the repository
git clone https://github.com/islacchi/File-Watcher.git
cd File-Watcher
# 2. Install PHP dependencies
composer install
# 3. Configure environment
cp .env.example .env

Edit .env to point to your SQLite database:

DB_CONNECTION=sqliteDB_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 serve

Visit http://localhost:8000 — the root URL redirects to /filewatcher/dashboard.


Development

# Start Laravel dev server
php artisan serve
# Watch for frontend changes (Vite HMR)
npm run dev
# Build frontend for production
npm run build

Script Integration

The Laravel UI pairs with a companion Python file watcher available at:

gh repo clone islacchi/Python-File-Watcher

The script:

  1. Monitors a network drive for file system changes using watchdog
  2. Logs CREATED, MODIFIED, DELETED, RENAMED, MOVED, and MOVED_AND_RENAMED events to the SQLite database
  3. Maintains a snapshots table with current file state and MD5 hashes
  4. Writes a heartbeat timestamp to the config table every 5 seconds and updates the status key to live on startup and offline on clean shutdown

Health Check Logic

The health endpoint (/filewatcher/health) determines online/offline status in priority order:

  1. Primary — reads the status key from the config table. The Python script writes live on startup and offline on shutdown. This is the most reliable indicator.
  2. Fallback 1 — if no status key exists, checks the heartbeat timestamp. The script writes every 5 seconds; a heartbeat older than 12 seconds (1 missed cycle + buffer) is treated as offline.
  3. Fallback 2 — if no heartbeat exists (older script versions), checks the last event timestamp. Offline if no event within 60 seconds.

Move Detection on UNC Network Shares

On UNC paths (\\server\share), Windows SMB can fire CREATE before DELETE for the same move operation — sometimes seconds apart. The Python script handles this with a move_window (default 10 seconds): it holds unmatched creates and deletes in memory and resolves pairs as MOVED or MOVED_AND_RENAMED if a hash match is found within the window. If no match is found before the window expires, the events are logged as genuine CREATED and DELETED.


License

This project is open-sourced software licensed under the MIT license.

About

A read-only Laravel web UI for monitoring file system events on a drive. Pairs with a Python file watcher script that logs changes to SQLite.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages