feat(acs): modal atur SSID & sandi dari daftar perangkat
All checks were successful
CI / test (push) Successful in 1m36s

- tombol Atur WiFi di menu aksi
- tampilkan SSID 2.4G/5G saat ini
- endpoint simpan SSID & sandi terpisah
- versi 0.6.11
This commit is contained in:
oxpoe
2026-11-06 11:04:25 +07:00
parent f66c3e2b7f
commit 2658efd89d
6 changed files with 139 additions and 3 deletions

View File

@@ -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

View File

@@ -11,7 +11,7 @@
<p align="center">
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
<img src="https://img.shields.io/badge/versi-0.6.10_acs--lookup-ADFF2F" alt="Versi 0.6.10 (acs-lookup)">
<img src="https://img.shields.io/badge/versi-0.6.11_acs--modal-ADFF2F" alt="Versi 0.6.11 (acs-wifi-modal)">
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker">
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>

View File

@@ -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": {

View File

@@ -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;

View File

@@ -1 +1 @@
0.6.10
0.6.11

View File

@@ -189,6 +189,7 @@
<div class="act-menu">
<button type="button" class="act-item primary" onclick="refreshDevice('<%= d.id %>')"><i class="bi bi-arrow-repeat"></i> Refresh Device</button>
<button type="button" class="act-item" onclick="openAddWanModal('<%= d.id %>', '<%= d.acs_server_id || selectedAcsId %>', '<%= d.sn %>', '<%= d.manufacturer || "" %>', '<%= d.model || "" %>')"><i class="bi bi-diagram-3"></i> Add WAN (All-in-One)</button>
<button type="button" class="act-item" onclick="openWifiModal('<%= d.id %>', '<%= d.acs_server_id || selectedAcsId %>', '<%= d.sn %>')"><i class="bi bi-wifi"></i> Atur WiFi (SSID / Sandi)</button>
<div class="act-sep"></div>
<button type="button" class="act-item danger" onclick="deleteDevice('<%= d.id %>', '<%= d.acs_server_id || selectedAcsId %>')"><i class="bi bi-trash"></i> <%= t('admin.acs.del_device','Hapus Perangkat') %></button>
</div>
@@ -276,6 +277,37 @@
</div>
<!-- Unified Add WAN All-in-One Modal -->
<div class="mo" id="wifiModal">
<div class="mb" style="max-width:520px">
<div class="mh">
<span class="mt"><i class="bi bi-wifi"></i> Atur WiFi ONU</span>
<button class="mc" onclick="closeModal('wifiModal')">&times;</button>
</div>
<div class="mbody">
<input type="hidden" id="wifiDeviceId">
<input type="hidden" id="wifiAcsId">
<div style="background:var(--sub-bg);border:1px dashed var(--border);border-radius:10px;padding:12px;margin-bottom:14px;font-size:13px">
<div><b>SN:</b> <span id="wifiSn" class="text-primary" style="font-weight:700">-</span></div>
<div style="margin-top:4px"><b>SSID saat ini:</b> <span id="wifiCurrentSsid" style="color:var(--muted)">Memuat…</span></div>
</div>
<div class="fg">
<label class="fl">SSID Baru (2.4G)</label>
<input class="fc" id="wifiSsidInput" placeholder="Nama WiFi baru">
<div style="font-size:11px;color:var(--muted);margin-top:4px">SSID 5G otomatis menjadi <code>&lt;nama&gt;-5G</code> jika didukung perangkat. SSID2/Guest tidak diubah.</div>
</div>
<button type="button" class="btn btn-p btn-sm" onclick="saveWifiSsid(this)"><i class="bi bi-save"></i> Simpan SSID</button>
<hr style="border-color:var(--border);margin:18px 0">
<div class="fg">
<label class="fl">Sandi WiFi Baru</label>
<input class="fc" type="password" id="wifiPassInput" placeholder="Minimal 8 karakter">
</div>
<button type="button" class="btn btn-p btn-sm" onclick="saveWifiPassword(this)"><i class="bi bi-shield-lock"></i> Simpan Sandi</button>
<div id="wifiMsg" style="margin-top:10px;font-size:12px;color:var(--muted)"></div>
</div>
<div class="mf"><button type="button" class="btn btn-g" onclick="closeModal('wifiModal')">Tutup</button></div>
</div>
</div>
<div class="mo" id="addWanModal">
<div class="mb" style="max-width:650px">
<div class="mh">
@@ -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 = '<i class="bi bi-hourglass-split"></i> 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 = '<i class="bi bi-hourglass-split"></i> 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 {