diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8346d1..e7a60f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
+## [0.6.11] — 2026-11-06 — "acs-wifi-modal"
+
+### Ditambahkan
+- Tombol **Atur WiFi (SSID / Sandi)** di daftar perangkat ACS.
+- Modal menampilkan SSID 2.4G/5G saat ini, simpan SSID dan sandi terpisah.
+- Endpoint `POST /admin/acs/api/device/:id/ssid` dan `/password`.
+
## [0.6.10] — 2026-11-06 — "acs-lookup"
### Diperbaiki
diff --git a/README.md b/README.md
index 94e0c1c..1427193 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
-
+
diff --git a/package.json b/package.json
index 355a21d..c54230e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "open-isp",
- "version": "0.6.10",
+ "version": "0.6.11",
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
"main": "app-customer.js",
"scripts": {
diff --git a/routes/acsPortal.js b/routes/acsPortal.js
index 70ca678..4a4a9e7 100644
--- a/routes/acsPortal.js
+++ b/routes/acsPortal.js
@@ -1601,6 +1601,36 @@ router.get('/api/wifi-settings/:deviceId', requireAdmin, async (req, res) => {
}
});
+router.post('/api/device/:deviceId/ssid', requireAdmin, express.json(), async (req, res) => {
+ try {
+ const deviceId = req.params.deviceId;
+ const ssid = String(req.body.ssid || '').trim();
+ if (!ssid || ssid.length < 2) {
+ return res.status(400).json({ success: false, message: 'Nama WiFi (SSID) minimal 2 karakter' });
+ }
+ const ok = await customerDevice.updateSSID(deviceId, ssid);
+ if (!ok) return res.status(500).json({ success: false, message: 'Gagal mengubah SSID. Pastikan perangkat aktif.' });
+ return res.json({ success: true, message: `SSID diubah menjadi "${ssid}"` });
+ } catch (err) {
+ return res.status(500).json({ success: false, message: err.message });
+ }
+});
+
+router.post('/api/device/:deviceId/password', requireAdmin, express.json(), async (req, res) => {
+ try {
+ const deviceId = req.params.deviceId;
+ const password = String(req.body.password || '').trim();
+ if (!password || password.length < 8) {
+ return res.status(400).json({ success: false, message: 'Sandi WiFi minimal 8 karakter' });
+ }
+ const ok = await customerDevice.updatePassword(deviceId, password);
+ if (!ok) return res.status(500).json({ success: false, message: 'Gagal mengubah sandi WiFi. Pastikan perangkat aktif.' });
+ return res.json({ success: true, message: 'Sandi WiFi berhasil diperbarui' });
+ } catch (err) {
+ return res.status(500).json({ success: false, message: err.message });
+ }
+});
+
router.get('/api/add-wan-status/:deviceId', requireAdmin, async (req, res) => {
try {
const { deviceId } = req.params;
diff --git a/version.txt b/version.txt
index b976b13..8f1a869 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.6.10
\ No newline at end of file
+0.6.11
\ No newline at end of file
diff --git a/views/admin/acs.ejs b/views/admin/acs.ejs
index 558c6bc..4121ba5 100644
--- a/views/admin/acs.ejs
+++ b/views/admin/acs.ejs
@@ -189,6 +189,7 @@
@@ -276,6 +277,37 @@
+
+
+
+ Atur WiFi ONU
+
+
+
+
+
+
+
SN: -
+
SSID saat ini: Memuat…
+
+
+
+
+
SSID 5G otomatis menjadi <nama>-5G jika didukung perangkat. SSID2/Guest tidak diubah.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1071,6 +1103,73 @@ async function bulkDelete() {
}
}
+function openWifiModal(deviceId, acsId, sn) {
+ document.getElementById('wifiDeviceId').value = deviceId;
+ document.getElementById('wifiAcsId').value = acsId || '';
+ document.getElementById('wifiSn').textContent = sn || deviceId;
+ document.getElementById('wifiSsidInput').value = '';
+ document.getElementById('wifiPassInput').value = '';
+ document.getElementById('wifiCurrentSsid').textContent = 'Memuat…';
+ document.getElementById('wifiMsg').textContent = '';
+ openModal('wifiModal');
+ fetch('/admin/acs/api/wifi-settings/' + encodeURIComponent(deviceId) + '?acsId=' + encodeURIComponent(acsId || ''))
+ .then(function (r) { return r.json(); })
+ .then(function (d) {
+ if (!d || !d.success) {
+ document.getElementById('wifiCurrentSsid').textContent = (d && (d.message || d.error)) || 'Gagal memuat WiFi';
+ return;
+ }
+ var bands = d.bands || [];
+ var b1 = bands.find(function (b) { return String(b.index) === '1'; }) || bands[0] || null;
+ var b5 = bands.find(function (b) { return String(b.index) === '5'; });
+ var parts = [];
+ if (b1) parts.push('2.4G: ' + String(b1.ssid || '-'));
+ if (b5) parts.push('5G: ' + String(b5.ssid || '-'));
+ document.getElementById('wifiCurrentSsid').textContent = parts.join(' • ') || '-';
+ if (b1) document.getElementById('wifiSsidInput').value = String(b1.ssid || '').replace(/-5G$/, '');
+ })
+ .catch(function (e) { document.getElementById('wifiCurrentSsid').textContent = 'Error: ' + e.message; });
+}
+
+async function saveWifiSsid(btn) {
+ var id = document.getElementById('wifiDeviceId').value;
+ var ssid = String(document.getElementById('wifiSsidInput').value || '').trim();
+ var msg = document.getElementById('wifiMsg');
+ if (ssid.length < 2) { msg.textContent = 'SSID minimal 2 karakter.'; return; }
+ if (!confirm('Ubah SSID perangkat ini?')) return;
+ var old = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' Menyimpan…';
+ msg.textContent = '';
+ try {
+ var r = await fetch('/admin/acs/api/device/' + encodeURIComponent(id) + '/ssid', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ssid: ssid })
+ });
+ var d = await r.json();
+ msg.style.color = d.success ? 'var(--success)' : 'var(--danger)';
+ msg.textContent = d.success ? d.message : ('Gagal: ' + (d.message || 'unknown'));
+ } catch (e) { msg.style.color = 'var(--danger)'; msg.textContent = 'Error: ' + e.message; }
+ finally { btn.disabled = false; btn.innerHTML = old; }
+}
+
+async function saveWifiPassword(btn) {
+ var id = document.getElementById('wifiDeviceId').value;
+ var pass = String(document.getElementById('wifiPassInput').value || '').trim();
+ var msg = document.getElementById('wifiMsg');
+ if (pass.length < 8) { msg.textContent = 'Sandi minimal 8 karakter.'; return; }
+ if (!confirm('Ubah sandi WiFi perangkat ini?')) return;
+ var old = btn.innerHTML; btn.disabled = true; btn.innerHTML = ' Menyimpan…';
+ msg.textContent = '';
+ try {
+ var r = await fetch('/admin/acs/api/device/' + encodeURIComponent(id) + '/password', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pass })
+ });
+ var d = await r.json();
+ msg.style.color = d.success ? 'var(--success)' : 'var(--danger)';
+ msg.textContent = d.success ? d.message : ('Gagal: ' + (d.message || 'unknown'));
+ if (d.success) document.getElementById('wifiPassInput').value = '';
+ } catch (e) { msg.style.color = 'var(--danger)'; msg.textContent = 'Error: ' + e.message; }
+ finally { btn.disabled = false; btn.innerHTML = old; }
+}
+
async function deleteDevice(deviceId, acsId) {
if (!confirm('Hapus perangkat ' + deviceId + ' dari database ACS?')) return;
try {