421 lines
18 KiB
Markdown
421 lines
18 KiB
Markdown
# 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](https://sides.tck.com.my).
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
- [Features](#features)
|
||
- [Tech Stack](#tech-stack)
|
||
- [Architecture](#architecture)
|
||
- [Project Structure](#project-structure)
|
||
- [Database Schema](#database-schema)
|
||
- [Requirements](#requirements)
|
||
- [Deployment (Docker)](#deployment-docker)
|
||
- [Environment Variables](#environment-variables)
|
||
- [User Accounts](#user-accounts)
|
||
- [Third-Party Packages](#third-party-packages)
|
||
- [Frontend Libraries (CDN)](#frontend-libraries-cdn)
|
||
- [Localization](#localization)
|
||
- [Activity Logging](#activity-logging)
|
||
- [Android App Integration](#android-app-integration)
|
||
- [Backup & Restore](#backup--restore)
|
||
|
||
---
|
||
|
||
## 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 (1–10 mm) — green
|
||
- Moderate (11–30 mm) — yellow
|
||
- Heavy (31–60 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
|
||
|
||
```bash
|
||
git clone <repo-url> /root/sides
|
||
cd /root/sides
|
||
```
|
||
|
||
### 2. Configure environment
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
docker compose build app
|
||
docker compose up -d
|
||
```
|
||
|
||
### 4. Install Laravel dependencies
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
docker compose exec app chown -R 1000:1000 /var/www/html/storage /var/www/html/bootstrap/cache
|
||
```
|
||
|
||
### 6. (Optional) Restore from backup
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
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:
|
||
|
||
```php
|
||
$isAndroid = preg_match('/Android/i', request()->userAgent());
|
||
```
|
||
|
||
---
|
||
|
||
## Backup & Restore
|
||
|
||
### Create a backup
|
||
|
||
```bash
|
||
docker compose exec postgres pg_dump -U sides_user sides > backup/sides_$(date +%Y%m%d).sql
|
||
```
|
||
|
||
### Restore from backup
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
# 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
|
||
```
|