Files
sides/README.md
2026-05-30 23:41:15 +08:00

18 KiB
Raw Blame History

SIDES — SABO Integrated Debris Flows Monitoring and Early Warning System

SIDES is a flood and debris flow monitoring system built for the Sabo Dam construction project at Sg Kupang, Baling, Kedah, Malaysia. It provides real-time monitoring of rainfall, water level, siren status, and CCTV feeds from field stations, with historical data analysis, notifications, and an admin dashboard for station and user management.

The system is part of the flood and debris flow mitigation initiative by Jabatan Pengairan dan Saliran (JPS) Kedah and is live at https://sides.tck.com.my.


Table of Contents


Features

Monitoring Dashboard

  • Interactive OpenStreetMap showing all stations with real-time sensor data
  • Station markers color-coded by type (rainfall, water level, siren)

Rainfall Monitoring

  • Current Rainfall — real-time rainfall readings per station with hourly graphs
  • Daily Rainfall — date-based rainfall data with hourly interval breakdown
  • Threshold Monitoring — cumulative rainfall totals with 4-tier alert levels:
    • Light (110 mm) — green
    • Moderate (1130 mm) — yellow
    • Heavy (3160 mm) — orange
    • Very Heavy (>60 mm) — red
  • Historical Rainfall — query by station and date range with CSV export

Water Level Monitoring

  • Current Water Level — real-time readings per station with graphs
  • Historical Water Level — query by station and date range with CSV export

Siren Monitoring

  • Siren status for all siren-equipped stations (Normal / Warning / Danger)
  • Active column shows the last data received timestamp (from siren, rainfall, or waterlevel data)
  • Siren history with PDF export

Notifications

  • Rainfall, water level, and siren notifications with history
  • PDF export for notification history

CCTV

  • Live CCTV feed links per station
  • Admin-only inline editing of CCTV URLs

Admin Panel

  • Station Management — CRUD operations, CSV import/export with sample CSV download
  • User Management — CRUD operations, password updates
  • Activity Logs — per-user activity log viewer with CSV export (tracks login, logout, CRUD operations, CSV import/export)

Other

  • Bilingual support (English / Bahasa Malaysia)
  • Responsive design with Boxicons UI
  • PDF report generation (siren history, notification history)
  • HTTPS via Caddy reverse proxy with auto Let's Encrypt
  • Android app detection — EWT link hidden when accessed from Android app

Tech Stack

Layer Technology
Backend PHP 8.2, Laravel 12
Database PostgreSQL 18
Web Server Nginx (stable-alpine)
Frontend Blade templates, Tailwind CSS, Vite
JS Libraries Boxicons, Flatpickr, Chart.js
Maps OpenStreetMap + Leaflet.js
Containerization Docker, Docker Compose
Reverse Proxy Caddy (auto HTTPS)
Asset Building Vite 7, Node.js LTS

Architecture

Internet → Caddy (HTTPS/443) → Nginx (8080) → PHP-FPM (9000) → Laravel
                                   │
                               PostgreSQL (5432)

Additional services:
  - pgAdmin (5050)     — database management UI
  - Dozzle (777)       — container log viewer
  - FileBrowser (8900) — file management UI

All services run in Docker containers on a Debian LXC (Proxmox), connected via the sides_net bridge network.


Project Structure

sides/
├── .env                          # Docker Compose environment (DB creds, TZ)
├── docker-compose.yml            # Service definitions (app, postgres, web, pgadmin, dozzle, filebrowser)
├── Dockerfile                    # PHP 8.2-FPM with pgsql, gd, composer, node
├── docker/
│   ├── nginx/default.conf        # Nginx config with CSP headers, fastcgi proxy params
│   └── postgres/                 # PostgreSQL data directory
├── backup/                       # Database backups (e.g., sides_20260528/)
├── src/                          # Laravel application root
│   ├── .env                      # Laravel config (APP_URL, DB, timezone)
│   ├── app/
│   │   ├── Http/
│   │   │   ├── Controllers/      # RainfallController, WaterLevelController, SirenController, AdminController, etc.
│   │   │   └── Middleware/       # AdminMiddleware, ForceRequestScheme, TrustProxies
│   │   ├── Models/               # Eloquent models (User, Station, ActivityLog, etc.)
│   │   ├── Exports/              # Maatwebsite Excel exports (StationExport, WaterLevelExport, etc.)
│   │   └── Imports/              # Maatwebsite Excel imports (StationImport)
│   ├── database/migrations/      # 15 migrations (users, station, rainfall, waterlevel, siren, notification, activity_log, etc.)
│   ├── resources/
│   │   ├── views/layout/         # Blade templates (dashboard, rainfall, waterlevel, siren, cctv, admin, etc.)
│   │   ├── js/                   # Frontend JavaScript (app.js)
│   │   └── css/                  # Tailwind styles
│   ├── routes/web.php            # Application routes
│   ├── lang/en/                  # English translations
│   ├── lang/bm/                  # Bahasa Malaysia translations
│   └── public/                   # Document root (index.php, build assets, js, css)
├── filebrowser-data/             # FileBrowser config
└── filebrowser-files/            # FileBrowser shared files

Database Schema

Table Description Key Columns
users System users id, name, email (nullable), password, access_level, is_blocked, login_attempts
station Monitoring stations stationid (PK, varchar), name, district, lat, lng, rainfall, waterlevel, siren, cctv_link
rainfall Rainfall readings id (PK, int), stationid (FK), timestamp, interval, rainfall
waterlevel Water level readings id (PK, int), stationid (FK), datetime, waterlevel
siren Siren events id (PK), stationid (FK), active_time, level (N/H/L)
notification System notifications id, stationid, timestamp, title, description
activity_log User activity tracking id, user_id, user_name, action, subject_type, subject_id, properties (JSON), ip_address, created_at
sessions User sessions (database driver)
cache Application cache (database driver)

Notes:

  • station.stationid is a varchar primary key (e.g., KBLG0031), not an auto-increment integer
  • rainfall.id and waterlevel.id use explicit values set by the external IoT data insertion system
  • PostgreSQL RULEs are configured for ON CONFLICT DO NOTHING on sensor tables to handle duplicate inserts
  • DB sequences are reset to 300,000+ to avoid conflicts with existing data
  • activity_log.user_id has no foreign key constraint — logs are preserved when users are deleted

Requirements

  • OS: Debian/Ubuntu Linux (deployed on Proxmox LXC)
  • Docker: 24+ with Docker Compose v2
  • Ports: 8080 (HTTP), 5432 (PostgreSQL), 5050 (pgAdmin), 777 (Dozzle), 8900 (FileBrowser)
  • External: Caddy reverse proxy handling TLS termination on port 443
  • Disk: ~2 GB for application + database (238K rainfall records, 138K water level records)

Deployment (Docker)

1. Clone the repository

git clone <repo-url> /root/sides
cd /root/sides

2. Configure environment

# Edit the root .env for Docker services
cp .env.example .env
# Set POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD, TZ

# Edit src/.env for Laravel
# Set APP_URL, DB_HOST=postgres, DB_DATABASE, DB_USERNAME, DB_PASSWORD

3. Build and start containers

docker compose build app
docker compose up -d

4. Install Laravel dependencies

docker compose exec app composer install -u root
docker compose exec app php artisan key:generate
docker compose exec app php artisan migrate
docker compose exec app php artisan db:seed

5. Set file permissions

docker compose exec app chown -R 1000:1000 /var/www/html/storage /var/www/html/bootstrap/cache

6. (Optional) Restore from backup

# Copy backup SQL to postgres container
docker compose cp backup/sides_20260528/sides_db.sql postgres:/tmp/
docker compose exec postgres psql -U sides_user -d sides -f /tmp/sides_db.sql

7. Verify

curl -I http://localhost:8080

Environment Variables

Root .env (Docker Compose)

Variable Description Default
TZ Container timezone Asia/Kuala_Lumpur
POSTGRES_DB Database name sides
POSTGRES_USER Database user sides_user
POSTGRES_PASSWORD Database password (generated)
PGADMIN_EMAIL pgAdmin login email admin@sides.didkedah.gov.my
PGADMIN_PASSWORD pgAdmin login password sides2026admin

src/.env (Laravel)

Variable Description Default
APP_NAME Application name SIDES
APP_ENV Environment local
APP_URL Public URL https://sides.tck.com.my
APP_TIMEZONE Application timezone Asia/Kuala_Lumpur
DB_CONNECTION Database driver pgsql
DB_HOST Database host (container name) postgres
DB_PORT Database port 5432
DB_DATABASE Database name sides
SESSION_DRIVER Session driver database

User Accounts

All passwords use Laravel's hashed cast. The following accounts exist:

Username Access Level Description Password
admin 1 (Admin) System administrator sides2026
jpskedah 1 (Admin) JPS Kedah officer sides2026
ijantck 1 (Admin) TCK staff sides2026
imam14 1 (Admin) TCK staff sides2026

Security notes:

  • Accounts are blocked after 3 failed login attempts (is_blocked flag, login_attempts counter)
  • Blocked accounts must be manually unblocked in the database: UPDATE users SET is_blocked = false, login_attempts = 0 WHERE name = 'username';
  • Password resets are done through the admin User Management panel

Third-Party Packages

Composer (PHP)

Package Purpose
laravel/framework ^12.0 Core Laravel framework
laravel/breeze ^2.3 Authentication scaffolding
barryvdh/laravel-dompdf ^3.1 PDF generation (siren history, notifications)
maatwebsite/excel ^3.1 CSV/Excel import and export
google/auth ^1.49 Google Auth library (Firebase push notifications)
laravel/tinker ^2.10 Interactive REPL

NPM (Build)

Package Purpose
vite ^7.0.7 Asset bundler
tailwindcss ^3.1.0 CSS framework
alpinejs ^3.4.2 Lightweight JS reactivity
axios ^1.11.0 HTTP client
laravel-vite-plugin ^2.0 Vite integration for Laravel

Frontend Libraries (CDN)

Loaded via CDN in Blade templates (see CSP headers in docker/nginx/default.conf):

Library Source Purpose
Boxicons cdn.jsdelivr.net Icon library
Flatpickr cdn.jsdelivr.net Date/time picker
Chart.js cdn.jsdelivr.net Rainfall/water level graphs
jQuery code.jquery.com DOM manipulation
Bootstrap 5 cdn.jsdelivr.net CSS framework (admin panels)
Leaflet.js unpkg.com OpenStreetMap integration
Data Tables cdn.datatables.net Sortable/searchable tables

Localization

SIDES supports two languages:

  • English (en) — src/lang/en/messages.php, src/lang/en/toast.php
  • Bahasa Malaysia (bm) — src/lang/bm/messages.php, src/lang/bm/toast.php

Language switcher available in the navbar via the /locale/{locale} route.


Activity Logging

User actions are tracked in the activity_log table via the ActivityLog::record() helper. Logged actions include:

Category Actions
Auth login, logout
Stations create_station, update_station, delete_station
Users create_user, update_user, update_password, delete_user
CSV import_stations_csv, export_stations_csv
CCTV update_cctv_link
Logs export_user_logs

Activity logs are accessible from User Management → View Logs (per-user), with CSV export.


Android App Integration

The system detects Android user agents and hides the Early Warning Threshold (EWT) nav link when accessed from the Android companion app. Detection is server-side:

$isAndroid = preg_match('/Android/i', request()->userAgent());

Backup & Restore

Create a backup

docker compose exec postgres pg_dump -U sides_user sides > backup/sides_$(date +%Y%m%d).sql

Restore from backup

docker compose cp backup/sides_YYYYMMDD/sides_db.sql postgres:/tmp/
docker compose exec postgres psql -U sides_user -d sides -f /tmp/sides_db.sql

Reset sequences after restore

docker compose exec postgres psql -U sides_user -d sides -c "
  SELECT setval('rainfall_id_seq', 300000);
  SELECT setval('waterlevel_id_seq', 300000);
  SELECT setval('users_id_seq', (SELECT MAX(id) + 1 FROM users));
  SELECT setval('notification_id_seq', (SELECT MAX(id) + 1 FROM notification));
"

Service Ports

Service Container Port URL
Nginx (HTTP) sides-web 8080 http://localhost:8080
PostgreSQL sides-db 5432 localhost:5432
pgAdmin sides-pgAdmin 5050 http://localhost:5050
Dozzle (Logs) 777 http://localhost:777
FileBrowser quantum-prod 8900 http://localhost:8900
SIDES (public) 443 https://sides.tck.com.my (via Caddy)

Useful Commands

# Rebuild and restart
docker compose up -d --build

# View logs
docker compose logs -f app
docker compose logs -f web

# Artisan commands (run as root due to vendor permissions)
docker compose exec -u root app php artisan migrate
docker compose exec -u root app php artisan route:clear
docker compose exec -u root app php artisan view:clear
docker compose exec -u root app php artisan config:clear
docker compose exec -u root app composer dump-autoload

# Copy file to container
docker compose cp src/path/to/file app:/var/www/html/path/to/file