update sistem proyek menjadi lebih struktural. dimulai dengan memisahkan dashboard publik dan administrator pada setiap proyek, membuat dockerfile untuk setup environment docker, dan merapikan database serta membuat file migration.

This commit is contained in:
2026-06-10 17:44:46 +07:00
parent c6087ed7dd
commit e98a8ecec9
34 changed files with 963 additions and 211 deletions
+3
View File
@@ -0,0 +1,3 @@
.git
.env
*.log
+12
View File
@@ -0,0 +1,12 @@
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=webgis_spbu
DB_USERNAME=root
DB_PASSWORD=
# Alternatif nama env yang juga didukung:
# MYSQL_HOST=localhost
# MYSQL_PORT=3306
# MYSQL_DATABASE=webgis_spbu
# MYSQL_USER=root
# MYSQL_PASSWORD=
+9
View File
@@ -0,0 +1,9 @@
FROM php:8.2-apache
RUN docker-php-ext-install mysqli
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80
+129 -73
View File
@@ -18,11 +18,17 @@ Aplikasi dibuat menggunakan PHP native, MySQL, JavaScript, Leaflet.js, Leaflet D
### 1. WebGIS Geometri
Lokasi: `geometri/index.php`
Lokasi publik: `geometri/index.php`
Lokasi admin: `geometri/admin.php`
Modul ini digunakan untuk latihan dan pengelolaan data geometri dasar.
Fitur:
Fitur publik:
- Menampilkan data point, jalan/polyline, dan polygon/parsil.
- Melihat detail data melalui popup peta.
Fitur admin:
- Menampilkan data point, jalan/polyline, dan polygon/parsil.
- Menambah point dengan nama dan kode point.
@@ -33,12 +39,20 @@ Fitur:
### 2. WebGIS SPBU
Lokasi: `spbu/index.php`
Lokasi publik: `spbu/index.php`
Lokasi admin: `spbu/admin.php`
Modul ini digunakan untuk memetakan lokasi SPBU di Pontianak.
Fitur:
Fitur publik:
- Menampilkan titik SPBU.
- Membedakan SPBU 24 jam dan tidak 24 jam.
- Toggle layer untuk menampilkan/menyembunyikan SPBU berdasarkan status.
Fitur admin:
- Login admin.
- Menampilkan titik SPBU.
- Membedakan SPBU 24 jam dan tidak 24 jam.
- Menambah titik SPBU dari peta.
@@ -148,77 +162,110 @@ Pastikan perangkat sudah memiliki:
3. Jalankan Apache dan MySQL dari XAMPP Control Panel.
4. Buat database MySQL, contoh:
4. Siapkan konfigurasi database melalui environment variable. Contoh variabel tersedia di `.env.example`.
```text
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=webgis_spbu
DB_USERNAME=root
DB_PASSWORD=
```
Pada Coolify, isi variabel tersebut dari menu Environment Variables project. Jangan menulis password database langsung di `php/db.php`.
5. Jalankan migration database yang sudah disediakan.
```sql
CREATE DATABASE webgis_spbu;
SOURCE migrations/001_init_database.sql;
```
5. Sesuaikan konfigurasi koneksi database pada file `php/db.php`.
File migration tersebut akan membuat database `webgis_spbu`, seluruh tabel aplikasi, dan akun default.
```php
$conn = new mysqli("localhost", "root", "password_database", "webgis_spbu");
```
6. Buat tabel yang dibutuhkan. Jika belum memiliki file `.sql`, gunakan struktur dasar berikut sebagai acuan.
```sql
CREATE TABLE spbu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
no_spbu VARCHAR(100) NOT NULL,
status VARCHAR(50) NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL
);
CREATE TABLE jalan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
status VARCHAR(50) NOT NULL,
panjang DOUBLE DEFAULT 0,
geom LONGTEXT NOT NULL
);
CREATE TABLE parsil (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
status VARCHAR(50) NOT NULL,
luas DOUBLE DEFAULT 0,
geom LONGTEXT NOT NULL
);
CREATE TABLE masjid (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
pic VARCHAR(150) NOT NULL,
radius DOUBLE DEFAULT 500,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
radius_layanan DOUBLE DEFAULT 500
);
CREATE TABLE penduduk_miskin (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_kk VARCHAR(150) NOT NULL,
jumlah_anggota INT DEFAULT 1,
alamat TEXT NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
penghasilan VARCHAR(50),
kondisi_rumah VARCHAR(50),
sumber_air VARCHAR(50),
skor_kerentanan DOUBLE DEFAULT 0,
jenis_bantuan VARCHAR(100)
);
```
7. Buka aplikasi melalui browser:
6. Buka aplikasi melalui browser:
```text
http://localhost/WebGIS2026/
```
## Catatan Deploy Coolify
Untuk deploy di Coolify, project ini dapat dijalankan sebagai aplikasi PHP native. Pastikan project memiliki service MySQL/MariaDB, lalu set environment variable berikut pada aplikasi:
```text
DB_HOST=<host service database>
DB_PORT=3306
DB_DATABASE=webgis_spbu
DB_USERNAME=<username database>
DB_PASSWORD=<password database>
```
Jika service database di Coolify memakai nama variabel `MYSQL_*`, aplikasi juga mendukung:
```text
MYSQL_HOST=<host service database>
MYSQL_PORT=3306
MYSQL_DATABASE=webgis_spbu
MYSQL_USER=<username database>
MYSQL_PASSWORD=<password database>
```
Docker tidak wajib jika Coolify/Nixpacks berhasil mendeteksi aplikasi PHP dan menyediakan web server PHP. Namun repository ini sudah menyediakan `Dockerfile`, sehingga mode deployment paling stabil di Coolify adalah memilih build dari Dockerfile. Dockerfile tersebut memakai `php:8.2-apache` dan memasang ekstensi `mysqli`.
## Menjalankan Dengan Docker Compose
Repository ini juga menyediakan `docker-compose.yml` untuk menjalankan seluruh service secara lokal:
- `app`: aplikasi PHP Apache
- `db`: MySQL 8.4
- `phpmyadmin`: panel database
Jalankan:
```bash
docker compose up -d --build
```
URL akses:
```text
Aplikasi: http://localhost:8080
phpMyAdmin: http://localhost:8081
```
Kredensial database default Docker Compose:
```text
Host: db
Port internal: 3306
Port host/lokal: 3307
Database: webgis_spbu
Username: webgis_user
Password: webgis_password
Root password: root_password
```
File migration di folder `migrations/` akan otomatis dijalankan oleh MySQL saat volume database pertama kali dibuat. File `001_init_database.sql` membuat struktur tabel dan akun login, sedangkan `002_seed_sample_data.sql` mengisi data contoh untuk Docker Compose.
Data contoh yang disediakan:
- 3 point geometri
- 3 polyline jalan
- 3 polygon/parsil
- 7 SPBU, terdiri dari 5 SPBU 24 jam dan 2 tidak 24 jam
- 3 masjid dengan radius layanan
- 14 data penduduk miskin, termasuk minimal 1 blind spot
- akun `admin / password` dan `publik / password`
Jika migration atau seed berubah dan ingin mengulang dari awal, hentikan container lalu hapus volume database:
```bash
docker compose down -v
docker compose up -d --build
```
Catatan: perintah `down -v` akan menghapus data database container.
## Cara Penggunaan
### Membuka Dashboard Utama
@@ -254,20 +301,28 @@ Pastikan perangkat sudah memiliki:
6. Gunakan tombol layer `24 Jam` dan `Tidak 24 Jam` untuk memfilter tampilan.
7. Klik marker SPBU untuk melihat detail atau menghapus data.
### Login Admin Modul Sosial
### Login Admin
1. Buka `http://localhost/WebGIS2026/sosial/`.
1. Buka salah satu dashboard publik:
- `http://localhost/WebGIS2026/geometri/`
- `http://localhost/WebGIS2026/spbu/`
- `http://localhost/WebGIS2026/sosial/`
2. Klik tombol `Admin Login`.
3. Masukkan akun default:
3. Masukkan salah satu akun default:
```text
Role Admin
Username: admin
Password: admin123
Password: password
Role Publik
Username: publik
Password: password
```
4. Setelah berhasil login, sistem akan membuka panel admin di `sosial/admin.php`.
4. Akun `admin` akan diarahkan ke panel admin sesuai project yang sedang dibuka. Akun `publik` diarahkan ke dashboard publik. Dashboard publik juga dapat diakses tanpa login.
Catatan: ubah kredensial default di `sosial/login.php` sebelum proyek digunakan di lingkungan publik.
Catatan: akun default berasal dari tabel `users`. Ubah atau tambah akun melalui database, bukan dari `sosial/login.php`.
### Menambah Data Masjid
@@ -332,7 +387,8 @@ Beberapa endpoint utama yang digunakan aplikasi:
| `php/show_geometri.php` | Mengambil data point, jalan, dan parsil |
| `php/show_spbu.php` | Mengambil data SPBU |
| `php/show_sosial.php` | Mengambil data masjid dan penduduk miskin |
| `php/insert.php` | Menambah data SPBU/point |
| `php/insert_point.php` | Menambah data point geometri |
| `php/insert.php` | Menambah data SPBU |
| `php/insert_line.php` | Menambah data jalan |
| `php/insert_polygon.php` | Menambah data parsil |
| `php/insert_masjid.php` | Menambah data masjid |
@@ -355,7 +411,7 @@ Beberapa endpoint utama yang digunakan aplikasi:
- Import/export data GeoJSON.
- Filter data berdasarkan kecamatan atau kelurahan.
- Role user untuk admin, petugas, dan viewer.
- Pemisahan akses admin dan publik untuk seluruh modul WebGIS.
- Upload dokumentasi/foto penduduk atau masjid.
- Dashboard laporan bantuan sosial.
- Integrasi data batas administrasi.
+54
View File
@@ -0,0 +1,54 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: webgis_app
ports:
- "8080:80"
environment:
DB_HOST: db
DB_PORT: 3306
DB_DATABASE: webgis_spbu
DB_USERNAME: webgis_user
DB_PASSWORD: webgis_password
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: mysql:8.4
container_name: webgis_db
environment:
MYSQL_DATABASE: webgis_spbu
MYSQL_USER: webgis_user
MYSQL_PASSWORD: webgis_password
MYSQL_ROOT_PASSWORD: root_password
ports:
- "3307:3306"
volumes:
- webgis_db_data:/var/lib/mysql
- ./migrations:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot_password"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
phpmyadmin:
image: phpmyadmin:5.2
container_name: webgis_phpmyadmin
ports:
- "8081:80"
environment:
PMA_HOST: db
PMA_PORT: 3306
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
webgis_db_data:
+51
View File
@@ -0,0 +1,51 @@
<?php
require_once '../php/auth.php';
require_roles_page(['admin']);
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Panel Admin - WebGIS Geometri</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css"/>
<link rel="stylesheet" href="../css/style.css"/>
</head>
<body>
<header>
<a href="index.php" class="back-btn" title="Lihat Dashboard Publik">←</a>
<div class="logo">
<div class="logo-icon" style="background:#22c55e;">G</div>
<div class="logo-text">ADMIN<span style="color:#22c55e;"> GEOMETRI</span></div>
</div>
<div class="header-divider"></div>
<span style="font-size:12px;color:var(--muted);">Kelola Point, Polyline, Polygon</span>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:#3b82f6"></div> Point</div>
<div class="legend-item"><div class="legend-line" style="background:#a855f7"></div> Polyline/Jalan</div>
<div class="legend-item"><div class="legend-poly" style="background:#10b981"></div> Polygon/Parsil</div>
</div>
<div class="layer-sep"></div>
<div class="layer-control">
<a href="logout.php" class="layer-btn" style="text-decoration:none; color:#ef4444; border-color:#ef444433;">Keluar</a>
</div>
</header>
<div id="map"></div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<script src="https://unpkg.com/leaflet-geometryutil/src/leaflet.geometryutil.js"></script>
<script>window.IS_ADMIN = true;</script>
<script src="../js/draw_geometri.js?v=<?= time() ?>"></script>
</body>
</html>
+9 -5
View File
@@ -9,7 +9,6 @@
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css"/>
<link rel="stylesheet" href="../css/style.css"/>
</head>
<body>
@@ -23,7 +22,13 @@
<div class="header-divider"></div>
<span style="font-size:12px;color:var(--muted);">Point, Polyline, Polygon</span>
<div class="legend">
<div class="layer-control" style="margin-left:auto;">
<a href="login.php" class="layer-btn" style="text-decoration:none; color:#22c55e; border-color:#22c55e33;">Admin Login</a>
</div>
<div class="layer-sep"></div>
<div class="legend" style="margin-left:0;">
<div class="legend-item">
<div class="legend-dot" style="background:#3b82f6"></div> Point
</div>
@@ -39,9 +44,8 @@
<div id="map"></div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<script src="https://unpkg.com/leaflet-geometryutil/src/leaflet.geometryutil.js"></script>
<script src="../js/draw_geometri.js"></script>
<script>window.IS_ADMIN = false;</script>
<script src="../js/draw_geometri.js?v=<?= time() ?>"></script>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
<?php
session_start();
include '../php/db.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = trim($_POST['username'] ?? '');
$pass = $_POST['password'] ?? '';
$stmt = $conn->prepare("SELECT id, username, password_hash, role, full_name, is_active FROM users WHERE username = ? LIMIT 1");
if (!$stmt) {
$error = 'Konfigurasi login belum siap. Jalankan migration database terlebih dahulu.';
} else {
$stmt->bind_param("s", $user);
$stmt->execute();
$result = $stmt->get_result();
$account = $result ? $result->fetch_assoc() : null;
if ($account && intval($account['is_active']) === 1 && password_verify($pass, $account['password_hash'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = intval($account['id']);
$_SESSION['username'] = $account['username'];
$_SESSION['role'] = $account['role'];
$_SESSION['full_name'] = $account['full_name'];
$_SESSION['is_admin'] = $account['role'] === 'admin';
header('Location: ' . ($account['role'] === 'admin' ? 'admin.php' : 'index.php'));
exit;
}
$error = 'Username atau password salah.';
$stmt->close();
}
}
if (isset($conn)) {
$conn->close();
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Admin - WebGIS Geometri</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family:'DM Sans',sans-serif; background:#0a0d14; color:#e2e8f0; min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; }
body::before { content:''; position:fixed; inset:0; background-image:linear-gradient(rgba(34,197,94,.04) 1px, transparent 1px),linear-gradient(90deg, rgba(34,197,94,.04) 1px, transparent 1px); background-size:40px 40px; }
.login-card { position:relative; z-index:1; width:100%; max-width:380px; background:#1a1d27; border:1px solid #2a2d3a; border-radius:16px; padding:36px 32px; box-shadow:0 25px 60px rgba(0,0,0,.5); }
.login-logo { display:flex; align-items:center; gap:12px; margin-bottom:28px; }
.login-logo-icon { width:44px; height:44px; background:linear-gradient(135deg,#166534,#22c55e); border-radius:12px; display:flex; align-items:center; justify-content:center; font-weight:700; }
.login-logo-text { font-family:'Space Mono',monospace; font-size:14px; font-weight:700; letter-spacing:.05em; }
.login-logo-text span { color:#22c55e; }
.login-logo-sub { font-size:11px; color:#6b7280; margin-top:1px; }
h1 { font-size:20px; margin-bottom:6px; }
.login-desc { font-size:13px; color:#6b7280; margin-bottom:24px; line-height:1.5; }
label { display:block; font-size:11px; color:#9ca3af; font-weight:600; letter-spacing:.05em; text-transform:uppercase; margin-bottom:6px; margin-top:16px; }
input { width:100%; background:#0f1117; border:1px solid #2a2d3a; color:#e2e8f0; border-radius:8px; padding:10px 14px; font-size:14px; font-family:'DM Sans',sans-serif; outline:none; }
input:focus { border-color:#22c55e; }
.error-msg { background:rgba(239,68,68,.1); border:1px solid rgba(239,68,68,.3); color:#f87171; border-radius:8px; padding:10px 14px; font-size:13px; margin-top:16px; }
.btn-login { width:100%; margin-top:24px; padding:12px; background:linear-gradient(135deg,#166534,#22c55e); color:#fff; border:0; border-radius:8px; font-size:14px; font-weight:700; font-family:'DM Sans',sans-serif; cursor:pointer; }
.back-link { display:block; text-align:center; margin-top:16px; font-size:12px; color:#6b7280; text-decoration:none; }
.back-link:hover { color:#e2e8f0; }
</style>
</head>
<body>
<div class="login-card">
<div class="login-logo">
<div class="login-logo-icon">G</div>
<div>
<div class="login-logo-text">WEB<span>GIS</span> GEOMETRI</div>
<div class="login-logo-sub">Portal Administrasi</div>
</div>
</div>
<h1>Masuk Admin</h1>
<p class="login-desc">Halaman ini khusus untuk pengelola data point, polyline, dan polygon.</p>
<?php if ($error): ?><div class="error-msg"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<form method="POST">
<label for="username">Username</label>
<input type="text" id="username" name="username" placeholder="admin" autocomplete="off" required>
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="password" required>
<button type="submit" class="btn-login">Masuk</button>
</form>
<a href="index.php" class="back-link">Kembali ke Dashboard Publik</a>
</div>
</body>
</html>
+6
View File
@@ -0,0 +1,6 @@
<?php
session_start();
session_destroy();
header('Location: index.php');
exit;
?>
+71 -62
View File
@@ -13,20 +13,23 @@ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
var drawnItems = new L.FeatureGroup().addTo(map);
var layerGroupPoint = L.featureGroup().addTo(map);
var currentLayer = null;
var isAdmin = window.IS_ADMIN === true;
var drawControl = new L.Control.Draw({
position: 'topleft',
edit: { featureGroup: drawnItems },
draw: {
marker: true,
polyline: { shapeOptions: { color: '#a855f7', weight: 3 } },
polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } },
rectangle: false,
circle: false,
circlemarker: false
}
});
map.addControl(drawControl);
if (isAdmin) {
var drawControl = new L.Control.Draw({
position: 'topleft',
edit: { featureGroup: drawnItems },
draw: {
marker: true,
polyline: { shapeOptions: { color: '#a855f7', weight: 3 } },
polygon: { shapeOptions: { color: '#10b981', weight: 2, fillOpacity: 0.35 } },
rectangle: false,
circle: false,
circlemarker: false
}
});
map.addControl(drawControl);
}
function getLineColor(status) {
return status === 'Nasional' ? '#ef4444' : status === 'Provinsi' ? '#38bdf8' : '#a855f7';
@@ -88,9 +91,11 @@ function buildInfoPopup(d, type) {
<div class="ip-row"><span class="ip-label">Luas</span><span class="ip-val">${fmtNum(d.luas)} m²</span></div>`;
}
var deleteButton = isAdmin ? `<button class="ip-del" onclick="hapusData(${d.id},'${type}')">Hapus</button>` : '';
return `<div class="ip">
<div class="ip-head" style="background:${headerColors[type]}">${icons[type]} ${titles[type]}</div>
<div class="ip-body">${rows}<button class="ip-del" onclick="hapusData(${d.id},'${type}')">Hapus</button></div>
<div class="ip-body">${rows}${deleteButton}</div>
</div>`;
}
@@ -142,22 +147,24 @@ function hapusData(id, type) {
});
}
map.on(L.Draw.Event.CREATED, function(e) {
var layer = e.layer;
if (isAdmin) {
map.on(L.Draw.Event.CREATED, function(e) {
var layer = e.layer;
if (e.layerType === 'marker') {
drawnItems.addLayer(layer);
handlePoint(layer);
}
if (e.layerType === 'polyline') {
drawnItems.addLayer(layer);
handleLine(layer);
}
if (e.layerType === 'polygon') {
drawnItems.addLayer(layer);
handlePolygon(layer);
}
});
if (e.layerType === 'marker') {
drawnItems.addLayer(layer);
handlePoint(layer);
}
if (e.layerType === 'polyline') {
drawnItems.addLayer(layer);
handleLine(layer);
}
if (e.layerType === 'polygon') {
drawnItems.addLayer(layer);
handlePolygon(layer);
}
});
}
function handlePoint(layer) {
var ll = layer.getLatLng();
@@ -180,7 +187,7 @@ function savePoint(lat, lng) {
alert('Nama dan kode point wajib diisi!');
return;
}
fetch('../php/insert.php', {
fetch('../php/insert_point.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nama, no, status, lat, lng })
@@ -294,38 +301,40 @@ function savePolygon() {
});
}
map.on('draw:edited', function(e) {
e.layers.eachLayer(layer => {
if (!layer._db_id) return;
if (layer._type === 'line') {
fetch('../php/update_line.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: layer._db_id,
geom: JSON.stringify(layer.getLatLngs()),
panjang: getLength(layer.getLatLngs())
})
});
}
if (layer._type === 'polygon') {
fetch('../php/update_polygon.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: layer._db_id,
geom: JSON.stringify(layer.getLatLngs()),
luas: L.GeometryUtil.geodesicArea(layer.getLatLngs()[0])
})
});
}
if (isAdmin) {
map.on('draw:edited', function(e) {
e.layers.eachLayer(layer => {
if (!layer._db_id) return;
if (layer._type === 'line') {
fetch('../php/update_line.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: layer._db_id,
geom: JSON.stringify(layer.getLatLngs()),
panjang: getLength(layer.getLatLngs())
})
});
}
if (layer._type === 'polygon') {
fetch('../php/update_polygon.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: layer._db_id,
geom: JSON.stringify(layer.getLatLngs()),
luas: L.GeometryUtil.geodesicArea(layer.getLatLngs()[0])
})
});
}
});
});
});
map.on('draw:deleted', function(e) {
e.layers.eachLayer(layer => {
if (layer._db_id && layer._type) {
fetch(`../php/delete.php?id=${layer._db_id}&type=${layer._type}`);
}
map.on('draw:deleted', function(e) {
e.layers.eachLayer(layer => {
if (layer._db_id && layer._type) {
fetch(`../php/delete.php?id=${layer._db_id}&type=${layer._type}`);
}
});
});
});
}
+27 -21
View File
@@ -15,6 +15,7 @@ var layerGroup24 = L.featureGroup().addTo(map);
var layerGroupTidak = L.featureGroup().addTo(map);
var show24 = true;
var showTidak = true;
var isAdmin = window.IS_ADMIN === true;
function toggleLayer(type) {
if (type === '24jam') {
@@ -28,19 +29,21 @@ function toggleLayer(type) {
}
}
var drawControl = new L.Control.Draw({
position: 'topleft',
edit: { featureGroup: drawnItems },
draw: {
marker: true,
polyline: false,
polygon: false,
rectangle: false,
circle: false,
circlemarker: false
}
});
map.addControl(drawControl);
if (isAdmin) {
var drawControl = new L.Control.Draw({
position: 'topleft',
edit: { featureGroup: drawnItems },
draw: {
marker: true,
polyline: false,
polygon: false,
rectangle: false,
circle: false,
circlemarker: false
}
});
map.addControl(drawControl);
}
function makeIcon(status) {
var c = status === '24 Jam' ? '#4ade80' : '#f87171';
@@ -60,13 +63,14 @@ function statusBadge(status) {
}
function buildInfoPopup(d) {
var deleteButton = isAdmin ? `<button class="ip-del" onclick="hapusData(${d.id},'spbu')">Hapus</button>` : '';
return `<div class="ip">
<div class="ip-head" style="background:linear-gradient(135deg,#1d4ed8,#3b82f6)">⛽ SPBU - ${d.nama}</div>
<div class="ip-body">
<div class="ip-row"><span class="ip-label">No. SPBU</span><span class="ip-val">${d.no_spbu}</span></div>
<div class="ip-row"><span class="ip-label">Status</span><span class="ip-val">${statusBadge(d.status)}</span></div>
<div class="ip-row"><span class="ip-label">Koordinat</span><span class="ip-val" style="font-size:10px">${parseFloat(d.latitude).toFixed(5)}, ${parseFloat(d.longitude).toFixed(5)}</span></div>
<button class="ip-del" onclick="hapusData(${d.id},'point')">Hapus</button>
${deleteButton}
</div>
</div>`;
}
@@ -76,7 +80,7 @@ fetch('../php/show_spbu.php')
.then(data => {
data.forEach(d => {
var layer = L.marker([d.latitude, d.longitude], { icon: makeIcon(d.status) });
layer._type = 'point';
layer._type = 'spbu';
layer._db_id = d.id;
layer.bindPopup(buildInfoPopup(d), { maxWidth: 260 });
d.status === '24 Jam' ? layerGroup24.addLayer(layer) : layerGroupTidak.addLayer(layer);
@@ -94,12 +98,14 @@ function hapusData(id, type) {
});
}
map.on(L.Draw.Event.CREATED, function(e) {
if (e.layerType !== 'marker') return;
var layer = e.layer;
drawnItems.addLayer(layer);
handlePoint(layer);
});
if (isAdmin) {
map.on(L.Draw.Event.CREATED, function(e) {
if (e.layerType !== 'marker') return;
var layer = e.layer;
drawnItems.addLayer(layer);
handlePoint(layer);
});
}
function handlePoint(layer) {
var ll = layer.getLatLng();
+88
View File
@@ -0,0 +1,88 @@
CREATE DATABASE IF NOT EXISTS webgis_spbu
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE webgis_spbu;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role ENUM('admin', 'publik') NOT NULL DEFAULT 'publik',
full_name VARCHAR(150) NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS spbu (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
no_spbu VARCHAR(100) NOT NULL,
status VARCHAR(50) NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS points (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
kode_point VARCHAR(100) NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS jalan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
status VARCHAR(50) NOT NULL,
panjang DOUBLE DEFAULT 0,
geom LONGTEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS parsil (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
status VARCHAR(50) NOT NULL,
luas DOUBLE DEFAULT 0,
geom LONGTEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS masjid (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(150) NOT NULL,
pic VARCHAR(150) NOT NULL,
radius DOUBLE DEFAULT 500,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
radius_layanan DOUBLE DEFAULT 500,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS penduduk_miskin (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_kk VARCHAR(150) NOT NULL,
jumlah_anggota INT DEFAULT 1,
alamat TEXT NOT NULL,
latitude DOUBLE NOT NULL,
longitude DOUBLE NOT NULL,
penghasilan VARCHAR(50),
kondisi_rumah VARCHAR(50),
sumber_air VARCHAR(50),
skor_kerentanan DOUBLE DEFAULT 0,
jenis_bantuan VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO users (username, password_hash, role, full_name, is_active) VALUES
('admin', '$2y$10$X/2pN5ag6A/SmFlm/bZJq.V1Ul41mm3lX1PNDY0ZBncinKhEnQvpO', 'admin', 'Administrator WebGIS', 1),
('publik', '$2y$10$X/2pN5ag6A/SmFlm/bZJq.V1Ul41mm3lX1PNDY0ZBncinKhEnQvpO', 'publik', 'Pengguna Publik', 1)
ON DUPLICATE KEY UPDATE
password_hash = VALUES(password_hash),
role = VALUES(role),
full_name = VALUES(full_name),
is_active = VALUES(is_active);
+73
View File
@@ -0,0 +1,73 @@
USE webgis_spbu;
INSERT INTO points (nama, kode_point, latitude, longitude)
SELECT *
FROM (
SELECT 'Titik Survei Alun Kapuas' AS nama, 'P-001' AS kode_point, -0.023700 AS latitude, 109.342900 AS longitude
UNION ALL SELECT 'Titik Survei Akcaya', 'P-002', -0.051200, 109.339000
UNION ALL SELECT 'Titik Survei Siantan', 'P-003', -0.009500, 109.339500
) AS seed
WHERE NOT EXISTS (SELECT 1 FROM points LIMIT 1);
INSERT INTO spbu (nama, no_spbu, status, latitude, longitude)
SELECT *
FROM (
SELECT 'SPBU Ahmad Yani Pontianak' AS nama, '64.781.01' AS no_spbu, '24 Jam' AS status, -0.045050 AS latitude, 109.345900 AS longitude
UNION ALL SELECT 'SPBU Imam Bonjol', '64.781.02', '24 Jam', -0.036500, 109.333100
UNION ALL SELECT 'SPBU Tanjungpura', '64.781.03', '24 Jam', -0.028950, 109.337800
UNION ALL SELECT 'SPBU Sungai Jawi', '64.781.04', '24 Jam', -0.040100, 109.305400
UNION ALL SELECT 'SPBU Siantan', '64.781.05', '24 Jam', -0.006700, 109.340800
UNION ALL SELECT 'SPBU Paris II', '64.781.06', 'Tidak', -0.061800, 109.351500
UNION ALL SELECT 'SPBU Tanjung Hulu', '64.781.07', 'Tidak', -0.023800, 109.364400
) AS seed
WHERE NOT EXISTS (SELECT 1 FROM spbu LIMIT 1);
INSERT INTO jalan (nama, status, panjang, geom)
SELECT *
FROM (
SELECT 'Koridor Jalan Ahmad Yani' AS nama, 'Nasional' AS status, 1430.50 AS panjang, '[{"lat":-0.0565,"lng":109.3418},{"lat":-0.0493,"lng":109.3443},{"lat":-0.0402,"lng":109.3475}]' AS geom
UNION ALL SELECT 'Koridor Jalan Tanjungpura', 'Provinsi', 1210.25, '[{"lat":-0.0339,"lng":109.3323},{"lat":-0.0304,"lng":109.3372},{"lat":-0.0275,"lng":109.3420}]'
UNION ALL SELECT 'Koridor Jalan Kom Yos Sudarso', 'Kabupaten', 1655.75, '[{"lat":-0.0128,"lng":109.3142},{"lat":-0.0187,"lng":109.3237},{"lat":-0.0239,"lng":109.3338}]'
) AS seed
WHERE NOT EXISTS (SELECT 1 FROM jalan LIMIT 1);
INSERT INTO parsil (nama, status, luas, geom)
SELECT *
FROM (
SELECT 'Parsil Kawasan GOR Pangsuma' AS nama, 'SHM' AS status, 24500.75 AS luas, '[[{"lat":-0.0493,"lng":109.3419},{"lat":-0.0470,"lng":109.3470},{"lat":-0.0514,"lng":109.3490},{"lat":-0.0535,"lng":109.3440}]]' AS geom
UNION ALL SELECT 'Parsil Area Sungai Bangkong', 'HGB', 18250.40, '[[{"lat":-0.0340,"lng":109.3090},{"lat":-0.0308,"lng":109.3134},{"lat":-0.0346,"lng":109.3168},{"lat":-0.0381,"lng":109.3120}]]'
UNION ALL SELECT 'Parsil Area Tanjung Hulu', 'HP', 21780.10, '[[{"lat":-0.0211,"lng":109.3634},{"lat":-0.0176,"lng":109.3673},{"lat":-0.0219,"lng":109.3712},{"lat":-0.0251,"lng":109.3669}]]'
) AS seed
WHERE NOT EXISTS (SELECT 1 FROM parsil LIMIT 1);
INSERT INTO masjid (nama, pic, radius, latitude, longitude, radius_layanan)
SELECT *
FROM (
SELECT 'Masjid Raya Mujahidin' AS nama, 'H. Sulaiman Arif' AS pic, 900 AS radius, -0.032500 AS latitude, 109.338200 AS longitude, 900 AS radius_layanan
UNION ALL SELECT 'Masjid Jami Sultan Syarif Abdurrahman', 'Ust. Rahman Hakim', 700, -0.020600, 109.338900, 700
UNION ALL SELECT 'Masjid Al-Falah Sungai Jawi', 'H. Darmawan Yusuf', 820, -0.041900, 109.310500, 820
) AS seed
WHERE NOT EXISTS (SELECT 1 FROM masjid LIMIT 1);
INSERT INTO penduduk_miskin (
nama_kk, jumlah_anggota, alamat, latitude, longitude,
penghasilan, kondisi_rumah, sumber_air, skor_kerentanan, jenis_bantuan
)
SELECT *
FROM (
SELECT 'Rahmat Hidayat' AS nama_kk, 5 AS jumlah_anggota, 'Gang Karya Baru, Sungai Bangkong' AS alamat, -0.037800 AS latitude, 109.314100 AS longitude, '< 500rb' AS penghasilan, 'Kayu' AS kondisi_rumah, 'Sumur' AS sumber_air, 75.00 AS skor_kerentanan, 'BLT / Modal Usaha' AS jenis_bantuan
UNION ALL SELECT 'Siti Aminah', 4, 'Jl. Tanjungpura Dalam', -0.030500, 109.337600, '500rb - 1jt', 'Kayu', 'PDAM', 42.00, 'BLT / Modal Usaha'
UNION ALL SELECT 'Mulyadi Saputra', 6, 'Gang Sepakat, Benua Melayu Darat', -0.034300, 109.343600, '< 500rb', 'Bambu/Tepas', 'Sumur', 90.00, 'Bedah Rumah'
UNION ALL SELECT 'Nuraini Hasanah', 3, 'Jl. Gajah Mada Gang Kecil', -0.026900, 109.335900, '500rb - 1jt', 'Tembok', 'PDAM', 30.00, 'Sembako'
UNION ALL SELECT 'Agus Salim', 5, 'Kelurahan Tambelan Sampit', -0.019900, 109.340100, '< 500rb', 'Kayu', 'Sungai', 85.00, 'Air Bersih / PDAM'
UNION ALL SELECT 'Yuliana Putri', 4, 'Sungai Jawi Dalam', -0.041200, 109.309900, '500rb - 1jt', 'Kayu', 'Sumur', 50.00, 'Sembako'
UNION ALL SELECT 'Bambang Irawan', 7, 'Gang Bersama, Mariana', -0.028000, 109.326800, '< 500rb', 'Bambu/Tepas', 'Sungai', 100.00, 'Bedah Rumah'
UNION ALL SELECT 'Kartini Rahma', 2, 'Jl. Dr. Sutomo Dalam', -0.033800, 109.331800, '> 1jt', 'Kayu', 'PDAM', 28.00, 'Beasiswa Pendidikan'
UNION ALL SELECT 'Firdaus Ahmad', 5, 'Siantan Hilir Gang Masjid', -0.018400, 109.337600, '500rb - 1jt', 'Kayu', 'Sungai', 60.00, 'Air Bersih / PDAM'
UNION ALL SELECT 'Mariam Abdullah', 4, 'Tanjung Hulu Gang Keluarga', -0.022300, 109.362800, '< 500rb', 'Kayu', 'Sumur', 75.00, 'BLT / Modal Usaha'
UNION ALL SELECT 'Rudi Hartono', 6, 'Parit Mayor Dalam', -0.043800, 109.372900, '< 500rb', 'Bambu/Tepas', 'Sungai', 100.00, 'Bedah Rumah'
UNION ALL SELECT 'Desi Wulandari', 3, 'Akcaya Gang Damai', -0.052200, 109.339600, '500rb - 1jt', 'Tembok', 'PDAM', 30.00, 'Beasiswa Pendidikan'
UNION ALL SELECT 'Hendra Gunawan', 5, 'Sungai Raya Dalam, ujung permukiman', -0.079500, 109.395200, '< 500rb', 'Bambu/Tepas', 'Sungai', 100.00, 'Air Bersih / PDAM'
UNION ALL SELECT 'Rosnawati Idris', 4, 'Pal Lima Gang Mandiri', -0.048800, 109.300500, '500rb - 1jt', 'Kayu', 'Sumur', 50.00, 'Sembako'
) AS seed
WHERE NOT EXISTS (SELECT 1 FROM penduduk_miskin LIMIT 1);
+45
View File
@@ -0,0 +1,45 @@
<?php
function start_app_session()
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
}
function current_user_role()
{
start_app_session();
return $_SESSION['role'] ?? null;
}
function user_has_role(array $allowed_roles)
{
$role = current_user_role();
return $role !== null && in_array($role, $allowed_roles, true);
}
function require_roles_json(array $allowed_roles)
{
if (!user_has_role($allowed_roles)) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode([
"message" => "Akses ditolak. Silakan login dengan akun yang berwenang."
]);
exit;
}
}
function require_write_access_json()
{
require_roles_json(['admin']);
}
function require_roles_page(array $allowed_roles, $login_path = 'login.php')
{
if (!user_has_role($allowed_roles)) {
header('Location: ' . $login_path);
exit;
}
}
?>
+20 -2
View File
@@ -1,7 +1,25 @@
<?php
$conn = new mysqli("localhost", "root", "bobbyandreanjapri", "webgis_spbu");
function env_value($keys, $default = '')
{
foreach ($keys as $key) {
$value = getenv($key);
if ($value !== false && $value !== '') {
return $value;
}
}
return $default;
}
$db_host = env_value(['DB_HOST', 'MYSQL_HOST', 'MYSQL_SERVER'], 'localhost');
$db_port = intval(env_value(['DB_PORT', 'MYSQL_PORT'], 3306));
$db_name = env_value(['DB_DATABASE', 'MYSQL_DATABASE', 'MYSQL_DB'], 'webgis_spbu');
$db_user = env_value(['DB_USERNAME', 'MYSQL_USER'], 'root');
$db_pass = env_value(['DB_PASSWORD', 'MYSQL_PASSWORD', 'MYSQL_ROOT_PASSWORD'], '');
$conn = new mysqli($db_host, $db_user, $db_pass, $db_name, $db_port);
if ($conn->connect_error) {
die(json_encode(["error" => "Koneksi gagal: " . $conn->connect_error]));
}
$conn->set_charset("utf8");
$conn->set_charset("utf8mb4");
?>
+5 -2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
@@ -11,7 +13,8 @@ if ($id == 0 || $type == '') {
}
$table = '';
if ($type == 'point') $table = 'spbu';
if ($type == 'point') $table = 'points';
if ($type == 'spbu') $table = 'spbu';
if ($type == 'line') $table = 'jalan';
if ($type == 'polygon') $table = 'parsil';
if ($type == 'masjid') $table = 'masjid';
@@ -30,4 +33,4 @@ if ($stmt->execute()) {
} else {
echo json_encode(["message" => "Gagal hapus", "error" => $stmt->error]);
}
?>
?>
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+3 -1
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
@@ -22,4 +24,4 @@ if ($stmt->execute()) {
echo json_encode(["message"=>"Gagal", "error"=>$stmt->error]);
}
$stmt->close(); $conn->close();
?>
?>
+3 -1
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
@@ -48,4 +50,4 @@ if ($stmt->execute()) {
echo json_encode(["message"=>"Gagal", "error"=>$stmt->error]);
}
$stmt->close(); $conn->close();
?>
?>
+35
View File
@@ -0,0 +1,35 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
$data = json_decode(file_get_contents("php://input"), true);
if (!$data) {
echo json_encode(["message" => "Data tidak diterima"]);
exit;
}
$nama = $data['nama'] ?? '';
$kode = $data['no'] ?? '';
$lat = $data['lat'] ?? '';
$lng = $data['lng'] ?? '';
if ($nama === '' || $kode === '' || $lat === '' || $lng === '') {
echo json_encode(["message" => "Field tidak lengkap"]);
exit;
}
$stmt = $conn->prepare("INSERT INTO points (nama, kode_point, latitude, longitude) VALUES (?, ?, ?, ?)");
$stmt->bind_param("ssdd", $nama, $kode, $lat, $lng);
if ($stmt->execute()) {
echo json_encode(["message" => "Data berhasil disimpan", "id" => $stmt->insert_id]);
} else {
echo json_encode(["message" => "Gagal simpan", "error" => $stmt->error]);
}
$stmt->close();
$conn->close();
?>
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+1 -1
View File
@@ -5,7 +5,7 @@ include 'db.php';
$data = [];
/* POINT */
$res = $conn->query("SELECT * FROM spbu");
$res = $conn->query("SELECT id, nama, kode_point AS no_spbu, latitude, longitude FROM points");
if ($res) {
while ($r = $res->fetch_assoc()) {
$r['type'] = 'point';
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+2
View File
@@ -1,4 +1,6 @@
<?php
require_once 'auth.php';
require_write_access_json();
header('Content-Type: application/json');
include 'db.php';
+2 -5
View File
@@ -1,9 +1,6 @@
<?php
session_start();
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
header('Location: login.php');
exit;
}
require_once '../php/auth.php';
require_roles_page(['admin']);
?>
<!DOCTYPE html>
<html lang="id">
-8
View File
@@ -87,13 +87,5 @@
</div>
</div>
<!-- App script -->
<script src="../js/draw_sosial.js?v=<?= time() ?>"></script>
<script>
window.setMode = function(mode) {
console.log('Mode changed to: ' + mode);
};
</script>
</body>
</html>
+36 -12
View File
@@ -1,23 +1,47 @@
<?php
session_start();
include '../php/db.php';
// Kredensial Admin — bisa diganti sesuai kebutuhan
$ADMIN_USER = 'admin';
$ADMIN_PASS = 'admin123';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $_POST['username'] ?? '';
$user = trim($_POST['username'] ?? '');
$pass = $_POST['password'] ?? '';
if ($user === $ADMIN_USER && $pass === $ADMIN_PASS) {
$_SESSION['is_admin'] = true;
header('Location: admin.php');
exit;
$stmt = $conn->prepare("SELECT id, username, password_hash, role, full_name, is_active FROM users WHERE username = ? LIMIT 1");
if (!$stmt) {
$error = 'Konfigurasi login belum siap. Jalankan migration database terlebih dahulu.';
} else {
$stmt->bind_param("s", $user);
$stmt->execute();
$result = $stmt->get_result();
$account = $result ? $result->fetch_assoc() : null;
if ($account && intval($account['is_active']) === 1 && password_verify($pass, $account['password_hash'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = intval($account['id']);
$_SESSION['username'] = $account['username'];
$_SESSION['role'] = $account['role'];
$_SESSION['full_name'] = $account['full_name'];
$_SESSION['is_admin'] = $account['role'] === 'admin';
if ($account['role'] === 'admin') {
header('Location: admin.php');
} else {
header('Location: index.php');
}
exit;
}
$error = 'Username atau password salah.';
$stmt->close();
}
}
if (isset($conn)) {
$conn->close();
}
?>
<!DOCTYPE html>
<html lang="id">
@@ -182,12 +206,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<div class="login-logo-icon">🕌</div>
<div>
<div class="login-logo-text">WEB<span>GIS</span> SOSIAL</div>
<div class="login-logo-sub">Portal Administrasi</div>
<div class="login-logo-sub">Portal Pengguna</div>
</div>
</div>
<h1>🔐 Masuk sebagai Admin</h1>
<p class="login-desc">Halaman ini khusus untuk petugas yang berwenang mengelola data penduduk dan masjid.</p>
<h1>🔐 Masuk Pengguna</h1>
<p class="login-desc">Gunakan akun sesuai role untuk mengakses dashboard publik atau panel pengelolaan data.</p>
<?php if ($error): ?>
<div class="error-msg">⚠️ <?= htmlspecialchars($error) ?></div>
@@ -200,7 +224,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="••••••••" required>
<button type="submit" class="btn-login">Masuk ke Panel Admin →</button>
<button type="submit" class="btn-login">Masuk →</button>
</form>
<a href="index.php" class="back-link">← Kembali ke Dashboard Publik</a>
+59
View File
@@ -0,0 +1,59 @@
<?php
require_once '../php/auth.php';
require_roles_page(['admin']);
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Panel Admin - WebGIS SPBU</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css"/>
<link rel="stylesheet" href="../css/style.css"/>
</head>
<body>
<header>
<a href="index.php" class="back-btn" title="Lihat Dashboard Publik">←</a>
<div class="logo">
<div class="logo-icon" style="background:#3b82f6;">S</div>
<div class="logo-text">ADMIN<span style="color:#60a5fa;"> SPBU</span></div>
</div>
<div class="header-divider"></div>
<span style="font-size:12px;color:var(--muted);">Kelola Titik SPBU Pontianak</span>
<div class="legend">
<div class="legend-item"><div class="legend-dot" style="background:#4ade80"></div> SPBU 24 Jam</div>
<div class="legend-item"><div class="legend-dot" style="background:#f87171"></div> Tidak 24 Jam</div>
<div class="legend-item"><div class="legend-dot" style="background:#3b82f6"></div> Titik SPBU</div>
</div>
<div class="layer-sep"></div>
<div class="layer-control">
<button class="layer-btn btn-24 active" id="btn24" onclick="toggleLayer('24jam')">
<span class="dot" style="background:#4ade80"></span>
24 Jam
</button>
<button class="layer-btn btn-tidak active" id="btnTidak" onclick="toggleLayer('tidak')">
<span class="dot" style="background:#f87171"></span>
Tidak 24 Jam
</button>
<div class="layer-sep"></div>
<a href="logout.php" class="layer-btn" style="text-decoration:none; color:#ef4444; border-color:#ef444433;">Keluar</a>
</div>
</header>
<div id="map"></div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<script>window.IS_ADMIN = true;</script>
<script src="../js/draw_spbu_point.js?v=<?= time() ?>"></script>
</body>
</html>
+18 -18
View File
@@ -11,7 +11,6 @@
<!-- Leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
<link rel="stylesheet" href="https://unpkg.com/leaflet-draw/dist/leaflet.draw.css"/>
<!-- App styles -->
<link rel="stylesheet" href="../css/style.css"/>
@@ -27,7 +26,22 @@
<div class="header-divider"></div>
<span style="font-size:12px;color:var(--muted);">Pontianak, Kalimantan Barat</span>
<div class="legend">
<div class="layer-control" style="margin-left:auto;">
<button class="layer-btn btn-24 active" id="btn24" onclick="toggleLayer('24jam')">
<span class="dot" style="background:#4ade80"></span>
24 Jam
</button>
<button class="layer-btn btn-tidak active" id="btnTidak" onclick="toggleLayer('tidak')">
<span class="dot" style="background:#f87171"></span>
Tidak 24 Jam
</button>
<div class="layer-sep"></div>
<a href="login.php" class="layer-btn" style="text-decoration:none; color:#60a5fa; border-color:#60a5fa33;">Admin Login</a>
</div>
<div class="layer-sep"></div>
<div class="legend" style="margin-left:0;">
<div class="legend-item">
<div class="legend-dot" style="background:#4ade80"></div> SPBU 24 Jam
</div>
@@ -38,30 +52,16 @@
<div class="legend-dot" style="background:#3b82f6"></div> Titik SPBU
</div>
</div>
<div class="layer-sep"></div>
<!-- Toggle SPBU -->
<div class="layer-control">
<button class="layer-btn btn-24 active" id="btn24" onclick="toggleLayer('24jam')">
<span class="dot" style="background:#4ade80"></span>
24 Jam
</button>
<button class="layer-btn btn-tidak active" id="btnTidak" onclick="toggleLayer('tidak')">
<span class="dot" style="background:#f87171"></span>
Tidak 24 Jam
</button>
</div>
</header>
<div id="map"></div>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet-draw/dist/leaflet.draw.js"></script>
<!-- App script -->
<script src="../js/draw_spbu_point.js"></script>
<script>window.IS_ADMIN = false;</script>
<script src="../js/draw_spbu_point.js?v=<?= time() ?>"></script>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
<?php
session_start();
include '../php/db.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = trim($_POST['username'] ?? '');
$pass = $_POST['password'] ?? '';
$stmt = $conn->prepare("SELECT id, username, password_hash, role, full_name, is_active FROM users WHERE username = ? LIMIT 1");
if (!$stmt) {
$error = 'Konfigurasi login belum siap. Jalankan migration database terlebih dahulu.';
} else {
$stmt->bind_param("s", $user);
$stmt->execute();
$result = $stmt->get_result();
$account = $result ? $result->fetch_assoc() : null;
if ($account && intval($account['is_active']) === 1 && password_verify($pass, $account['password_hash'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = intval($account['id']);
$_SESSION['username'] = $account['username'];
$_SESSION['role'] = $account['role'];
$_SESSION['full_name'] = $account['full_name'];
$_SESSION['is_admin'] = $account['role'] === 'admin';
header('Location: ' . ($account['role'] === 'admin' ? 'admin.php' : 'index.php'));
exit;
}
$error = 'Username atau password salah.';
$stmt->close();
}
}
if (isset($conn)) {
$conn->close();
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login Admin - WebGIS SPBU</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family:'DM Sans',sans-serif; background:#0a0d14; color:#e2e8f0; min-height:100vh; display:flex; align-items:center; justify-content:center; padding:20px; }
body::before { content:''; position:fixed; inset:0; background-image:linear-gradient(rgba(59,130,246,.04) 1px, transparent 1px),linear-gradient(90deg, rgba(59,130,246,.04) 1px, transparent 1px); background-size:40px 40px; }
.login-card { position:relative; z-index:1; width:100%; max-width:380px; background:#1a1d27; border:1px solid #2a2d3a; border-radius:16px; padding:36px 32px; box-shadow:0 25px 60px rgba(0,0,0,.5); }
.login-logo { display:flex; align-items:center; gap:12px; margin-bottom:28px; }
.login-logo-icon { width:44px; height:44px; background:linear-gradient(135deg,#1d4ed8,#3b82f6); border-radius:12px; display:flex; align-items:center; justify-content:center; font-weight:700; }
.login-logo-text { font-family:'Space Mono',monospace; font-size:14px; font-weight:700; letter-spacing:.05em; }
.login-logo-text span { color:#60a5fa; }
.login-logo-sub { font-size:11px; color:#6b7280; margin-top:1px; }
h1 { font-size:20px; margin-bottom:6px; }
.login-desc { font-size:13px; color:#6b7280; margin-bottom:24px; line-height:1.5; }
label { display:block; font-size:11px; color:#9ca3af; font-weight:600; letter-spacing:.05em; text-transform:uppercase; margin-bottom:6px; margin-top:16px; }
input { width:100%; background:#0f1117; border:1px solid #2a2d3a; color:#e2e8f0; border-radius:8px; padding:10px 14px; font-size:14px; font-family:'DM Sans',sans-serif; outline:none; }
input:focus { border-color:#60a5fa; }
.error-msg { background:rgba(239,68,68,.1); border:1px solid rgba(239,68,68,.3); color:#f87171; border-radius:8px; padding:10px 14px; font-size:13px; margin-top:16px; }
.btn-login { width:100%; margin-top:24px; padding:12px; background:linear-gradient(135deg,#1d4ed8,#3b82f6); color:#fff; border:0; border-radius:8px; font-size:14px; font-weight:700; font-family:'DM Sans',sans-serif; cursor:pointer; }
.back-link { display:block; text-align:center; margin-top:16px; font-size:12px; color:#6b7280; text-decoration:none; }
.back-link:hover { color:#e2e8f0; }
</style>
</head>
<body>
<div class="login-card">
<div class="login-logo">
<div class="login-logo-icon">S</div>
<div>
<div class="login-logo-text">WEB<span>GIS</span> SPBU</div>
<div class="login-logo-sub">Portal Administrasi</div>
</div>
</div>
<h1>Masuk Admin</h1>
<p class="login-desc">Halaman ini khusus untuk pengelola data titik SPBU.</p>
<?php if ($error): ?><div class="error-msg"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<form method="POST">
<label for="username">Username</label>
<input type="text" id="username" name="username" placeholder="admin" autocomplete="off" required>
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="password" required>
<button type="submit" class="btn-login">Masuk</button>
</form>
<a href="index.php" class="back-link">Kembali ke Dashboard Publik</a>
</div>
</body>
</html>
+6
View File
@@ -0,0 +1,6 @@
<?php
session_start();
session_destroy();
header('Location: index.php');
exit;
?>