fix: update final code deployment

This commit is contained in:
muthianura
2026-06-15 21:53:17 +07:00
parent 62a5a26fe0
commit 9cc6fcf52d
42 changed files with 177 additions and 23 deletions
+4
View File
@@ -2,6 +2,10 @@ FROM php:8.2-apache
RUN a2enmod rewrite
# The sub-projects connect via mysqli, which isn't bundled in the base image
RUN docker-php-ext-install mysqli pdo_mysql \
&& echo "date.timezone = Asia/Jakarta" > /usr/local/etc/php/conf.d/timezone.ini
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html \
+9
View File
@@ -0,0 +1,9 @@
FROM mysql:8.0
# Bake all three sub-project schemas + seed data into one database.
# No table-name collisions across them, and config.php reads the DB name
# from the DB_NAME env var, so a single shared database is fine.
# Files in initdb.d run alphabetically against $MYSQL_DATABASE on first boot.
COPY spbu/webgis.sql /docker-entrypoint-initdb.d/01_spbu.sql
COPY parsiljalan/webgis2.sql /docker-entrypoint-initdb.d/02_jalantanah.sql
COPY povertymap/gis_penduduk_miskin.sql /docker-entrypoint-initdb.d/03_miskin.sql
+60
View File
@@ -0,0 +1,60 @@
name: muthia-webgis
services:
app:
build:
context: .
dockerfile: Dockerfile
image: muthia-webgis-app
container_name: muthia-webgis-app
ports:
- "127.0.0.1:8089:80"
environment:
DB_HOST: db
DB_USER: webgis_user
DB_PASS: webgis_password
DB_NAME: webgis_muthia
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
build:
context: .
dockerfile: Dockerfile.db
image: muthia-webgis-db
container_name: muthia-webgis-db
environment:
MYSQL_DATABASE: webgis_muthia
MYSQL_USER: webgis_user
MYSQL_PASSWORD: webgis_password
MYSQL_ROOT_PASSWORD: root_password
expose:
- "3306"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
phpmyadmin:
image: phpmyadmin:latest
container_name: muthia-webgis-phpmyadmin
ports:
- "127.0.0.1:8090:80"
environment:
PMA_HOST: db
PMA_PORT: 3306
PMA_ABSOLUTE_URI: https://muthia.webgis-untan.xyz/db/
UPLOAD_LIMIT: 64M
depends_on:
db:
condition: service_healthy
restart: unless-stopped
volumes:
db_data:
+4 -4
View File
@@ -1,9 +1,9 @@
<?php
// Semua project di-deploy dalam satu folder — gunakan relative path
$url_spbu = 'SIG_spbu/';
$url_miskin = 'SIG_miskin/gis/';
$url_jalantanah = 'SIG2_jalantanah/';
$url_choropleth = 'Choropleth/ptk.html';
$url_spbu = 'spbu/';
$url_miskin = 'povertymap/';
$url_jalantanah = 'parsiljalan/';
$url_choropleth = 'choropleth/';
?>
<!DOCTYPE html>
<html lang="id">
@@ -15,12 +15,58 @@ function toGeoJsonPoint($lat, $lng) {
return json_encode(['type' => 'Point', 'coordinates' => [(float)$lng, (float)$lat]]);
}
// Deteksi POST yang melebihi post_max_size: PHP mengosongkan $_POST/$_FILES
// padahal CONTENT_LENGTH > 0. Tanpa ini respons jadi kosong -> "Koneksi gagal!".
if ($_SERVER['REQUEST_METHOD'] === 'POST'
&& empty($_POST) && empty($_FILES)
&& (int)($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
header('Content-Type: application/json; charset=utf-8');
http_response_code(413);
echo json_encode([
'ok' => false,
'msg' => 'Data terlalu besar (melebihi post_max_size=' . ini_get('post_max_size')
. '). Kecilkan ukuran foto atau naikkan post_max_size.'
]);
exit;
}
$action = $_GET['action'] ?? $_POST['action'] ?? null;
if (!$action) return;
header('Content-Type: application/json; charset=utf-8');
$a = $action;
if ($a === 'reverse') {
$lat = (float)($_GET['lat'] ?? 0);
$lon = (float)($_GET['lon'] ?? $_GET['lng'] ?? 0);
if ($lat == 0 && $lon == 0) { echo json_encode(['error' => 'koordinat kosong']); exit; }
$url = 'https://nominatim.openstreetmap.org/reverse?format=jsonv2&zoom=17&accept-language=id'
. '&lat=' . urlencode($lat) . '&lon=' . urlencode($lon);
// Nominatim mewajibkan User-Agent yang valid; browser tak bisa set ini, jadi diproxy dari server
$ua = 'SIG2-JalanTanah/1.0 (kontak: webmaster@webgis-untan.xyz)';
$body = false;
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => $ua,
CURLOPT_TIMEOUT => 8,
CURLOPT_FOLLOWLOCATION => true,
]);
$body = curl_exec($ch);
curl_close($ch);
}
if ($body === false) {
$ctx = stream_context_create(['http' => [
'header' => "User-Agent: $ua\r\n",
'timeout' => 8,
]]);
$body = @file_get_contents($url, false, $ctx);
}
if ($body === false) { echo json_encode(['error' => 'gagal hubungi nominatim']); exit; }
echo $body; exit;
}
if ($a === 'search') {
$q = '%' . $conn->real_escape_string($_GET['q'] ?? '') . '%';
$tipe = $_GET['tipe'] ?? 'semua';
@@ -250,11 +250,12 @@ function openRusakModal(latlng) {
document.getElementById('rusak-ket').value = '';
clearFoto();
fetch('https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=' + latlng.lat + '&lon=' + latlng.lng + '&zoom=17&accept-language=id')
fetch('api.php?action=reverse&lat=' + latlng.lat + '&lon=' + latlng.lng)
.then(function (r) { return r.json(); })
.then(function (geo) {
var road = geo.address && (geo.address.road || geo.address.residential || geo.address.path || geo.address.pedestrian || geo.address.footway || '');
document.getElementById('rusak-nama-jalan').value = road || '(Jalan tidak teridentifikasi)';
var a = geo.address || {};
var road = a.road || a.residential || a.path || a.pedestrian || a.footway || a.track || a.cycleway || a.neighbourhood || a.hamlet || a.village || a.suburb || '';
document.getElementById('rusak-nama-jalan').value = road || geo.display_name || '(Jalan tidak teridentifikasi)';
})
.catch(function () {
document.getElementById('rusak-nama-jalan').value = '(Gagal deteksi nama jalan)';
@@ -327,10 +328,11 @@ function handleFoto(input) {
badge.textContent = 'GPS dari Foto';
}
fetch('https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=' + latDec + '&lon=' + lonDec + '&zoom=17&accept-language=id')
fetch('api.php?action=reverse&lat=' + latDec + '&lon=' + lonDec)
.then(function (r) { return r.json(); })
.then(function (geo) {
var road = geo.address && (geo.address.road || geo.address.residential || geo.address.path || geo.address.pedestrian || geo.address.footway || '');
var a = geo.address || {};
var road = a.road || a.residential || a.path || a.pedestrian || a.footway || a.track || a.cycleway || a.neighbourhood || a.hamlet || a.village || a.suburb || '';
document.getElementById('rusak-nama-jalan').value = road || geo.display_name || '(Jalan tidak teridentifikasi)';
});
}
@@ -339,12 +341,32 @@ function handleFoto(input) {
var reader = new FileReader();
reader.onload = function (e) {
// Kompres & resize foto agar payload kecil (hindari post_max_size & batas kolom DB)
var img = new Image();
img.onload = function () {
var maxDim = 1280;
var w = img.width, h = img.height;
if (w > h && w > maxDim) { h = Math.round(h * maxDim / w); w = maxDim; }
else if (h >= w && h > maxDim) { w = Math.round(w * maxDim / h); h = maxDim; }
var canvas = document.createElement('canvas');
canvas.width = w; canvas.height = h;
canvas.getContext('2d').drawImage(img, 0, 0, w, h);
fotoBase64 = canvas.toDataURL('image/jpeg', 0.7);
var preview = document.getElementById('photo-preview');
preview.src = fotoBase64;
preview.style.display = 'block';
document.getElementById('foto-clear-btn').style.display = 'flex';
};
img.onerror = function () {
// Fallback bila gambar tak bisa di-decode (mis. format tak didukung canvas)
fotoBase64 = e.target.result;
var preview = document.getElementById('photo-preview');
preview.src = fotoBase64;
preview.style.display = 'block';
document.getElementById('foto-clear-btn').style.display = 'flex';
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
@@ -381,27 +403,40 @@ function saveRusak() {
formData.append('foto', fotoBase64);
fetch('api.php', { method: 'POST', body: formData })
.then(function (r) { return r.json(); })
.then(function (r) {
return r.text().then(function (txt) {
var res;
try { res = JSON.parse(txt); }
catch (e) {
// Respons bukan JSON: tampilkan status & potongan isi supaya jelas penyebabnya
var info = txt.trim() ? txt.trim().slice(0, 200) : '(respons kosong)';
throw new Error('HTTP ' + r.status + ' — ' + info);
}
return res;
});
})
.then(function (res) {
btn.textContent = 'Simpan Titik';
btn.disabled = false;
if (res.ok) {
showToast('Titik jalan rusak disimpan!', 'success');
var savedFoto = fotoBase64; // simpan dulu: closeRusakModal() me-reset fotoBase64
closeRusakModal();
var id = res.id;
var nama = res.nama || pelapor;
var layer = makeRusakLayer(id, nama, tanggal, lat, lng, ket, fotoBase64, 'recent', false);
var layer = makeRusakLayer(id, nama, tanggal, lat, lng, ket, savedFoto, 'recent', false);
rusakLayers[id] = layer;
rusakData[id] = { id: id, nama: nama, tgl: tanggal, lat: lat, lng: lng, ket: ket, foto: fotoBase64, age: 'recent', recurring: false };
rusakData[id] = { id: id, nama: nama, tgl: tanggal, lat: lat, lng: lng, ket: ket, foto: savedFoto, age: 'recent', recurring: false };
if (res.tr !== undefined) updateStats(res);
cancelDraw();
} else {
showToast('Gagal: ' + (res.msg || 'unknown'), 'error');
}
})
.catch(function () {
.catch(function (err) {
btn.textContent = 'Simpan Titik';
btn.disabled = false;
showToast('Koneksi gagal!', 'error');
showToast('Gagal simpan: ' + (err && err.message ? err.message : 'koneksi gagal'), 'error');
console.error('saveRusak error:', err);
});
}
@@ -1080,7 +1080,7 @@ function exitFloatingGeoEdit(save) {
}
}
function spHandleFoto(input){var f=input.files[0];if(!f)return;if(f.size>5*1024*1024){showToast('Foto maks 5MB!','error');return;}var r=new FileReader();r.onload=function(e){spFotoBase64=e.target.result;el('sp-foto-preview').src=spFotoBase64;el('sp-foto-preview').style.display='block';el('sp-foto-clear').style.display='flex';};r.readAsDataURL(f);}
function spHandleFoto(input){var f=input.files[0];if(!f)return;if(f.size>5*1024*1024){showToast('Foto maks 5MB!','error');return;}var r=new FileReader();r.onload=function(e){var img=new Image();img.onload=function(){var maxDim=1280,w=img.width,h=img.height;if(w>h&&w>maxDim){h=Math.round(h*maxDim/w);w=maxDim;}else if(h>=w&&h>maxDim){w=Math.round(w*maxDim/h);h=maxDim;}var c=document.createElement('canvas');c.width=w;c.height=h;c.getContext('2d').drawImage(img,0,0,w,h);spFotoBase64=c.toDataURL('image/jpeg',0.7);el('sp-foto-preview').src=spFotoBase64;el('sp-foto-preview').style.display='block';el('sp-foto-clear').style.display='flex';};img.onerror=function(){spFotoBase64=e.target.result;el('sp-foto-preview').src=spFotoBase64;el('sp-foto-preview').style.display='block';el('sp-foto-clear').style.display='flex';};img.src=e.target.result;};r.readAsDataURL(f);}
function spClearFoto(){spFotoBase64='';el('sp-foto-preview').style.display='none';el('sp-foto-file').value='';el('sp-foto-clear').style.display='none';}
/* ══ GEOMETRY EDIT ═══════════════════════════════════ */
@@ -1410,7 +1410,7 @@ if (document.getElementById('lnav-list')) {
}
</script>
<?php if ($mode === 'input'): ?>
<script src="assets/jalan-input.js"></script>
<script src="assets/jalan-input.js?v=<?= @filemtime(__DIR__ . '/assets/jalan-input.js') ?: time() ?>"></script>
<?php endif; ?>
</body>
</html>
@@ -56,7 +56,7 @@ CREATE TABLE `jalan_rusak` (
`tanggal` date NOT NULL,
`latitude` double NOT NULL DEFAULT 0,
`longitude` double NOT NULL DEFAULT 0,
`foto` text DEFAULT NULL COMMENT 'base64 atau path file foto',
`foto` mediumtext DEFAULT NULL COMMENT 'base64 atau path file foto',
`keterangan` text DEFAULT NULL,
`koordinat` text NOT NULL COMMENT 'GeoJSON Point string',
`created_at` timestamp NOT NULL DEFAULT current_timestamp()
@@ -138,7 +138,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<body>
<header class="login-header">
<a href="../../index.php">
<a href="../index.php">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
Portal WebGIS
</a>

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File
View File