fix(acs): SSID hanya SSID1/SSID5, split task 9007, RX Power/LOS
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
- port updateSSID/updatePassword dari upstream - tidak menyentuh SSID2/Guest - setParameterValues per parameter - RX power negatif/raw + LOS - versi 0.6.9
This commit is contained in:
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
Format mengikuti [Keep a Changelog](https://keepachangelog.com/) dan [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [0.6.9] — 2026-11-06 — "acs-wifi-rx"
|
||||||
|
|
||||||
|
### Diperbaiki
|
||||||
|
- **ACS/GenieACS WiFi**: SSID hanya mengubah SSID1 (2.4G) dan SSID5 (5G bila tersedia); tidak lagi menyentuh SSID2/Guest.
|
||||||
|
- `setParameterValues` dipecah per parameter agar tidak kena TR-069 fault 9007.
|
||||||
|
- Sinkronisasi nama WiFi baru ke data pelanggan setelah update.
|
||||||
|
- **RX Power**: dukung nilai negatif, konversi raw CTC EPON/GPON, dan tampilkan **LOS** saat sinyal putus.
|
||||||
|
|
||||||
## [0.6.8] — 2026-11-06 — "isolir-url-setting"
|
## [0.6.8] — 2026-11-06 — "isolir-url-setting"
|
||||||
|
|
||||||
### Ditambahkan
|
### Ditambahkan
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
|
<img src="https://img.shields.io/badge/lisensi-ISC-ADFF2F" alt="Lisensi: ISC">
|
||||||
<img src="https://img.shields.io/badge/versi-0.6.8_isolir--url-ADFF2F" alt="Versi 0.6.8 (isolir-url-setting)">
|
<img src="https://img.shields.io/badge/versi-0.6.9_acs--wifi-ADFF2F" alt="Versi 0.6.9 (acs-wifi-rx)">
|
||||||
<img src="https://img.shields.io/badge/Node.js-%E2%89%A520-339933" alt="Node.js >= 20">
|
<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">
|
<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>
|
<a href="https://demo-openisp.oxpoe.xyz"><img src="https://img.shields.io/badge/demo-live-ADFF2F?logo=googlechrome&logoColor=black" alt="Demo"></a>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "open-isp",
|
"name": "open-isp",
|
||||||
"version": "0.6.8",
|
"version": "0.6.9",
|
||||||
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
|
"description": "OPEN-ISP \u2014 sistem billing & manajemen ISP open-source (PWA pelanggan, multi-OLT, RADIUS, WhatsApp, QRIS)",
|
||||||
"main": "app-customer.js",
|
"main": "app-customer.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -696,11 +696,19 @@ function mapDeviceData(device, tag, isPppoeActive = false) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
let rxPower = getParameterWithPaths(device, parameterPaths.rxPower);
|
let rxPower = getParameterWithPaths(device, parameterPaths.rxPower);
|
||||||
if (rxPower !== 'N/A' && rxPower !== '-' && rxPower !== '') {
|
if (rxPower !== 'N/A' && rxPower !== '-' && rxPower !== '' && rxPower !== null && rxPower !== undefined) {
|
||||||
const num = parseFloat(rxPower);
|
const raw = parseFloat(rxPower);
|
||||||
if (!isNaN(num) && num > 0) {
|
if (!isNaN(raw)) {
|
||||||
const dbVal = 30 + (Math.log10(num * Math.pow(10, -7)) * 10);
|
if (raw === 0 || raw >= 65535) {
|
||||||
rxPower = (Math.ceil(dbVal * 100) / 100).toFixed(2);
|
rxPower = 'LOS';
|
||||||
|
} else if (raw < 0 && raw > -50) {
|
||||||
|
rxPower = raw.toFixed(2);
|
||||||
|
} else if (raw <= -50) {
|
||||||
|
rxPower = (raw / 100).toFixed(2);
|
||||||
|
} else if (raw > 0) {
|
||||||
|
const dbm = 30 + (Math.log10(raw * 1e-7) * 10);
|
||||||
|
rxPower = (Math.round(dbm * 100) / 100).toFixed(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const pppoeIP = extractPppoeIp(device);
|
const pppoeIP = extractPppoeIp(device);
|
||||||
@@ -851,6 +859,7 @@ async function updateSSID(tag, newSSID, actor = null) {
|
|||||||
try {
|
try {
|
||||||
const ssidClean = String(newSSID ?? '').replace(/[\r\n\t]+/g, '').trim();
|
const ssidClean = String(newSSID ?? '').replace(/[\r\n\t]+/g, '').trim();
|
||||||
if (!ssidClean) { logger.warn('[updateSSID] SSID kosong, dibatalkan.'); return false; }
|
if (!ssidClean) { logger.warn('[updateSSID] SSID kosong, dibatalkan.'); return false; }
|
||||||
|
newSSID = ssidClean;
|
||||||
const device = await resolveDeviceToken(tag);
|
const device = await resolveDeviceToken(tag);
|
||||||
if (!device) return false;
|
if (!device) return false;
|
||||||
const deviceId = encodeURIComponent(device._id);
|
const deviceId = encodeURIComponent(device._id);
|
||||||
@@ -863,62 +872,96 @@ async function updateSSID(tag, newSSID, actor = null) {
|
|||||||
const tasksUrl = `/devices/${deviceId}/tasks`;
|
const tasksUrl = `/devices/${deviceId}/tasks`;
|
||||||
|
|
||||||
const parameterValues = [];
|
const parameterValues = [];
|
||||||
|
const igd = device.InternetGatewayDevice;
|
||||||
|
const dev181 = device.Device;
|
||||||
|
|
||||||
// Check supported paths in DB
|
// Check supported paths directly from loaded device object
|
||||||
|
if (igd && igd.LANDevice && igd.LANDevice['1'] && igd.LANDevice['1'].WLANConfiguration) {
|
||||||
|
const wlan = igd.LANDevice['1'].WLANConfiguration;
|
||||||
|
// Selalu ubah SSID 1 (2.4GHz Utama)
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', newSSID, 'xsd:string']);
|
||||||
|
|
||||||
|
// HANYA jika parameter SSID 5G (WLANConfiguration.5) benar-benar ada di perangkat.
|
||||||
|
// JANGAN PERNAH mengubah WLANConfiguration.2 (SSID 2) karena itu adalah SSID2/Guest pada perangkat single-band!
|
||||||
|
const hasFiveG = Boolean(
|
||||||
|
wlan['5'] && (
|
||||||
|
wlan['5'].SSID !== undefined ||
|
||||||
|
wlan['5']._object === true ||
|
||||||
|
wlan['5'].Enable !== undefined ||
|
||||||
|
wlan['5'].KeyPassphrase !== undefined
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (hasFiveG) {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.SSID', `${newSSID}-5G`, 'xsd:string']);
|
||||||
|
}
|
||||||
|
} else if (dev181 && dev181.WiFi) {
|
||||||
|
parameterValues.push(['Device.WiFi.SSID.1.SSID', newSSID, 'xsd:string']);
|
||||||
|
} else {
|
||||||
|
// Fallback check in local DB
|
||||||
const db = require('../config/database');
|
const db = require('../config/database');
|
||||||
const row = db.prepare('SELECT params FROM acs_devices WHERE id = ?').get(device._id);
|
const row = db.prepare('SELECT params FROM acs_devices WHERE id = ?').get(device._id);
|
||||||
const flatParams = row && row.params ? JSON.parse(row.params) : null;
|
const flatParams = row && row.params ? JSON.parse(row.params) : null;
|
||||||
|
|
||||||
if (flatParams) {
|
if (flatParams) {
|
||||||
// SSID 2.4G paths
|
if (flatParams['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID'] !== undefined) {
|
||||||
const paths24G = [
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', newSSID, 'xsd:string']);
|
||||||
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID',
|
} else if (flatParams['Device.WiFi.SSID.1.SSID'] !== undefined) {
|
||||||
'Device.WiFi.SSID.1.SSID'
|
parameterValues.push(['Device.WiFi.SSID.1.SSID', newSSID, 'xsd:string']);
|
||||||
];
|
|
||||||
paths24G.forEach(p => {
|
|
||||||
if (flatParams[p] !== undefined) {
|
|
||||||
parameterValues.push([p, ssidClean, 'xsd:string']);
|
|
||||||
}
|
}
|
||||||
});
|
if (flatParams['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.SSID'] !== undefined) {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.SSID', `${newSSID}-5G`, 'xsd:string']);
|
||||||
// SSID 5G paths
|
|
||||||
const paths5G = [
|
|
||||||
'Device.WiFi.SSID.2.SSID'
|
|
||||||
];
|
|
||||||
for (const idx of [5, 6, 7, 8]) {
|
|
||||||
paths5G.push(`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.SSID`);
|
|
||||||
}
|
}
|
||||||
paths5G.forEach(p => {
|
|
||||||
if (flatParams[p] !== undefined) {
|
|
||||||
parameterValues.push([p, `${ssidClean}-5G`, 'xsd:string']);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback if no parameters match or device not bootstrapped yet
|
|
||||||
if (parameterValues.length === 0) {
|
if (parameterValues.length === 0) {
|
||||||
parameterValues.push(
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', newSSID, 'xsd:string']);
|
||||||
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.SSID', ssidClean, 'xsd:string'],
|
}
|
||||||
['Device.WiFi.SSID.1.SSID', ssidClean, 'xsd:string']
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ok = false;
|
let ok = false;
|
||||||
|
for (const pv of parameterValues) {
|
||||||
try {
|
try {
|
||||||
await instance.post(tasksUrl, {
|
await instance.post(tasksUrl, {
|
||||||
name: 'setParameterValues',
|
name: 'setParameterValues',
|
||||||
parameterValues: parameterValues
|
parameterValues: [pv]
|
||||||
}, { timeout: 20000 });
|
}, { timeout: 15000 });
|
||||||
ok = true;
|
ok = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`[updateSSID] Failed to set SSID: ${e.message}`);
|
logger.error(`[updateSSID] Failed to set parameter ${pv[0]}: ${e.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh + inform dijalankan di latar belakang agar respons cepat (tidak menahan UI)
|
// Refresh objects untuk trigger update parameter dari ONU
|
||||||
|
try {
|
||||||
|
await instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 15000 });
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
// Tunggu sesaat agar task dieksekusi oleh ONU
|
||||||
if (ok) {
|
if (ok) {
|
||||||
instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 12000 }).catch(() => {});
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
instance.post(tasksUrl, { name: 'inform' }, { timeout: 12000 }).catch(() => {});
|
|
||||||
logger.info(`[updateSSID] Set SSID dikirim untuk ${tag}; refresh & inform berjalan di latar belakang`);
|
// Sinkronkan nama WiFi (SSID) baru ke database pelanggan
|
||||||
|
try {
|
||||||
|
const db = require('../config/database');
|
||||||
|
const pppoeUser = extractPppoeUser(device);
|
||||||
|
const tagCandidate = (device._tags && device._tags[0]) || null;
|
||||||
|
const snCandidate = device._deviceId?._SerialNumber || null;
|
||||||
|
const idCandidate = device._id || null;
|
||||||
|
|
||||||
|
const cust = db.prepare(`
|
||||||
|
SELECT id FROM customers
|
||||||
|
WHERE id = ?
|
||||||
|
OR (pppoe_username IS NOT NULL AND pppoe_username != '' AND (pppoe_username = ? OR pppoe_username = ?))
|
||||||
|
OR (genieacs_tag IS NOT NULL AND genieacs_tag != '' AND (genieacs_tag = ? OR genieacs_tag = ?))
|
||||||
|
OR (mac_address IS NOT NULL AND mac_address != '' AND (mac_address = ? OR mac_address = ? OR mac_address = ?))
|
||||||
|
OR phone = ?
|
||||||
|
LIMIT 1
|
||||||
|
`).get(tag, tag, pppoeUser, tag, tagCandidate, tag, snCandidate, idCandidate, tag);
|
||||||
|
|
||||||
|
if (cust && cust.id) {
|
||||||
|
db.prepare('UPDATE customers SET wifi_ssid = ? WHERE id = ?').run(newSSID, cust.id);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.debug(`[updateSSID] Failed to sync customer wifi_ssid: ${err.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catat audit trail jika berhasil
|
// Catat audit trail jika berhasil
|
||||||
@@ -970,71 +1013,78 @@ async function updatePassword(tag, newPassword, actor = null) {
|
|||||||
logger.info(`[updatePassword] Setting password for device ${deviceId}, tag ${tag}`);
|
logger.info(`[updatePassword] Setting password for device ${deviceId}, tag ${tag}`);
|
||||||
|
|
||||||
const parameterValues = [];
|
const parameterValues = [];
|
||||||
|
const igd = device.InternetGatewayDevice;
|
||||||
|
const dev181 = device.Device;
|
||||||
|
|
||||||
// Check supported paths in DB
|
if (igd && igd.LANDevice && igd.LANDevice['1'] && igd.LANDevice['1'].WLANConfiguration) {
|
||||||
const db = require('../config/database');
|
const wlan = igd.LANDevice['1'].WLANConfiguration;
|
||||||
const row = db.prepare('SELECT params FROM acs_devices WHERE id = ?').get(device._id);
|
const wlan1 = wlan['1'] || {};
|
||||||
const flatParams = row && row.params ? JSON.parse(row.params) : null;
|
|
||||||
|
|
||||||
if (flatParams) {
|
if (wlan1.KeyPassphrase !== undefined) {
|
||||||
// 2.4G password paths
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string']);
|
||||||
const paths24G = [
|
|
||||||
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase',
|
|
||||||
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.KeyPassphrase',
|
|
||||||
'InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey',
|
|
||||||
'Device.WiFi.AccessPoint.1.Security.KeyPassphrase',
|
|
||||||
'Device.WiFi.AccessPoint.1.Security.PreSharedKey'
|
|
||||||
];
|
|
||||||
paths24G.forEach(p => {
|
|
||||||
if (flatParams[p] !== undefined) {
|
|
||||||
parameterValues.push([p, pw, 'xsd:string']);
|
|
||||||
}
|
}
|
||||||
});
|
if (wlan1.PreSharedKey && typeof wlan1.PreSharedKey === 'object') {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
|
||||||
// 5G password paths
|
|
||||||
const paths5G = [
|
|
||||||
'Device.WiFi.AccessPoint.2.Security.KeyPassphrase',
|
|
||||||
'Device.WiFi.AccessPoint.2.Security.PreSharedKey'
|
|
||||||
];
|
|
||||||
for (const idx of [5, 6, 7, 8]) {
|
|
||||||
paths5G.push(
|
|
||||||
`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.KeyPassphrase`,
|
|
||||||
`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.PreSharedKey.1.KeyPassphrase`,
|
|
||||||
`InternetGatewayDevice.LANDevice.1.WLANConfiguration.${idx}.PreSharedKey.1.PreSharedKey`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
paths5G.forEach(p => {
|
|
||||||
if (flatParams[p] !== undefined) {
|
|
||||||
parameterValues.push([p, pw, 'xsd:string']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback if no parameters match or device not bootstrapped yet
|
|
||||||
if (parameterValues.length === 0) {
|
if (parameterValues.length === 0) {
|
||||||
parameterValues.push(
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string']);
|
||||||
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string'],
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
|
||||||
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.KeyPassphrase', pw, 'xsd:string'],
|
}
|
||||||
['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string'],
|
|
||||||
['Device.WiFi.AccessPoint.1.Security.KeyPassphrase', pw, 'xsd:string']
|
// 5G if available - HANYA jika WLANConfiguration.5 ada di perangkat. JANGAN PERNAH mengubah WLANConfiguration.2!
|
||||||
|
const hasFiveG = Boolean(
|
||||||
|
wlan['5'] && (
|
||||||
|
wlan['5'].SSID !== undefined ||
|
||||||
|
wlan['5'].KeyPassphrase !== undefined ||
|
||||||
|
wlan['5'].PreSharedKey !== undefined ||
|
||||||
|
wlan['5']._object === true ||
|
||||||
|
wlan['5'].Enable !== undefined
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
if (hasFiveG) {
|
||||||
|
const wlan5 = wlan['5'] || {};
|
||||||
|
if (wlan5.KeyPassphrase !== undefined) {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.KeyPassphrase', pw, 'xsd:string']);
|
||||||
|
}
|
||||||
|
if (wlan5.PreSharedKey && typeof wlan5.PreSharedKey === 'object') {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
|
||||||
|
}
|
||||||
|
if (!wlan5.KeyPassphrase && (!wlan5.PreSharedKey || typeof wlan5.PreSharedKey !== 'object')) {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.KeyPassphrase', pw, 'xsd:string']);
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.5.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (dev181 && dev181.WiFi) {
|
||||||
|
parameterValues.push(['Device.WiFi.AccessPoint.1.Security.KeyPassphrase', pw, 'xsd:string']);
|
||||||
|
} else {
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.KeyPassphrase', pw, 'xsd:string']);
|
||||||
|
parameterValues.push(['InternetGatewayDevice.LANDevice.1.WLANConfiguration.1.PreSharedKey.1.PreSharedKey', pw, 'xsd:string']);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ok = false;
|
let ok = false;
|
||||||
|
for (const pv of parameterValues) {
|
||||||
try {
|
try {
|
||||||
await instance.post(tasksUrl, {
|
await instance.post(tasksUrl, {
|
||||||
name: 'setParameterValues',
|
name: 'setParameterValues',
|
||||||
parameterValues: parameterValues
|
parameterValues: [pv]
|
||||||
}, { timeout: 20000 });
|
}, { timeout: 15000 });
|
||||||
ok = true;
|
ok = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`[updatePassword] Failed to set password: ${e.message}`);
|
logger.debug(`[updatePassword] Param ${pv[0]} failed: ${e.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh di latar belakang agar respons cepat
|
// Refresh object - only refresh InternetGatewayDevice path yang lebih universal
|
||||||
instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 12000 }).catch(() => {});
|
try {
|
||||||
|
await instance.post(tasksUrl, { name: 'refreshObject', objectName: 'InternetGatewayDevice.LANDevice.1.WLANConfiguration' }, { timeout: 15000 });
|
||||||
|
} catch (e) {}
|
||||||
// Skip Device.WiFi.AccessPoint refresh karena tidak semua ONU support (CIOT tidak support)
|
// Skip Device.WiFi.AccessPoint refresh karena tidak semua ONU support (CIOT tidak support)
|
||||||
|
|
||||||
|
// Tunggu sesaat agar task dieksekusi oleh ONU
|
||||||
|
if (ok) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
}
|
||||||
|
|
||||||
// Catat audit trail jika berhasil
|
// Catat audit trail jika berhasil
|
||||||
if (ok && actor) {
|
if (ok && actor) {
|
||||||
auditTrail.logAuditTrail({
|
auditTrail.logAuditTrail({
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.6.8
|
0.6.9
|
||||||
Reference in New Issue
Block a user