OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor, admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS. Kredit & lisensi (ISC): - Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak atas base kode dan repo yang bermanfaat. - Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder. - Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
This commit is contained in:
commit
eaf05f2811
280 files changed
+123975
No files matched your search
@@ -0,0 +1,375 @@
|
||||
const crypto = require('crypto');
|
||||
const { getSetting, getSettings, saveSettings } = require('../config/settingsManager');
|
||||
const { getAppSetting, saveAppSetting } = require('../config/database');
|
||||
|
||||
const FEATURE_PASSWORD_HASH = 'af03458614be223016c5bc1c0788eaefc6a6bfd0c11cdcd4bc520f49d0cb474a'; // sha256 kode aktivasi — plaintext hanya di settings.json (activation_code), tidak di repo
|
||||
const FEATURE_CONTACT_PHONE = '081947215703';
|
||||
|
||||
function getFeaturePasswordHash() {
|
||||
return getSetting('feature_password_hash', FEATURE_PASSWORD_HASH);
|
||||
}
|
||||
|
||||
function getFeatureContactPhone() {
|
||||
return getSetting('feature_contact_phone', FEATURE_CONTACT_PHONE);
|
||||
}
|
||||
|
||||
const SETTINGS_KEY = 'sidebar_menu_states';
|
||||
const STATE_VISIBLE = 'visible';
|
||||
const STATE_HIDDEN = 'hidden';
|
||||
const STATE_LOCKED = 'locked';
|
||||
const VALID_STATES = new Set([STATE_VISIBLE, STATE_HIDDEN, STATE_LOCKED]);
|
||||
|
||||
const MENU_DEFINITIONS = [
|
||||
{ key: 'dashboard', section: 'main', href: '/admin', icon: 'bi bi-speedometer2', labelKey: 'admin.nav.dashboard', labelDefault: 'Dashboard', roles: ['admin', 'cashier'], bottomNav: true, bottomNavOrder: 1, activePages: ['dashboard'] },
|
||||
{ key: 'network', section: 'main', href: '/admin/mikrotik', icon: 'bi bi-diagram-3', labelKey: 'admin.nav.network', labelDefault: 'Jaringan', roles: ['admin', 'cashier'], activePages: ['mikrotik', 'radius_settings', 'acs_pro', 'onu_provision', 'olts', 'monitoring'], children: [
|
||||
{ key: 'mikrotik', section: 'main', href: '/admin/mikrotik', icon: 'bi bi-router', labelKey: 'admin.nav.mikrotik', labelDefault: 'MikroTik', roles: ['admin', 'cashier'], activePages: ['mikrotik'] },
|
||||
{ key: 'radius_server', section: 'main', href: '/admin/radius-settings', icon: 'bi bi-broadcast', labelKey: 'admin.nav.radius_server', labelDefault: 'RADIUS Server', roles: ['admin'], activePages: ['radius_settings'] },
|
||||
{ key: 'acs_pro', section: 'main', href: '/admin/acs', icon: 'bi bi-hdd-network', labelKey: 'admin.nav.acs_pro', labelDefault: 'GenieACS Pro', roles: ['admin'], activePages: ['acs_pro'] },
|
||||
{ key: 'onu_provision', section: 'main', href: '/admin/onu-provision', icon: 'bi bi-hdd-network-fill', labelKey: 'admin.nav.onu_provision', labelDefault: 'ONU Provision', roles: ['admin'], activePages: ['onu_provision'] },
|
||||
{ key: 'olts', section: 'main', href: '/admin/olts', icon: 'bi bi-hdd-fill', labelKey: 'admin.nav.olt_management', labelDefault: 'Manajemen OLT', roles: ['admin'], activePages: ['olts'] },
|
||||
{ key: 'monitoring', section: 'main', href: '/admin/monitoring', icon: 'bi bi-activity', labelKey: 'admin.nav.monitoring', labelDefault: 'Monitoring Sistem', roles: ['admin'], activePages: ['monitoring'] }
|
||||
] },
|
||||
{ key: 'map', section: 'main', href: '/admin/map', icon: 'bi bi-map', labelKey: 'admin.nav.network_map', labelDefault: 'Peta Jaringan', roles: ['admin', 'cashier'], bottomNav: true, bottomNavOrder: 4, activePages: ['map'] },
|
||||
{ key: 'whatsapp', section: 'main', href: '/admin/whatsapp', icon: 'bi bi-whatsapp', labelKey: 'admin.nav.whatsapp', labelDefault: 'WhatsApp', roles: ['admin', 'cashier'], activePages: ['whatsapp','broadcast','whatsapp_live_chat','whatsapp_templates'] },
|
||||
{ key: 'automation', section: 'main', href: '/admin/automation', icon: 'bi bi-robot', labelKey: 'admin.nav.automation', labelDefault: 'Automation', roles: ['admin'], activePages: ['automation'] },
|
||||
|
||||
{ key: 'grp_customers', section: 'billing', href: '/admin/customers', icon: 'bi bi-people', labelKey: 'admin.nav.customers', labelDefault: 'Pelanggan', roles: ['admin', 'cashier'], bottomNav: true, bottomNavOrder: 2, activePages: ['customers', 'onu_stickers'], children: [
|
||||
{ key: 'customers', section: 'billing', href: '/admin/customers', icon: 'bi bi-people', labelKey: 'admin.nav.customers_data', labelDefault: 'Data Pelanggan', roles: ['admin', 'cashier'], activePages: ['customers'] },
|
||||
{ key: 'onu_stickers', section: 'billing', href: '/admin/onu-stickers', icon: 'bi bi-qr-code', labelKey: 'admin.nav.onu_stickers', labelDefault: 'Stiker Modem ONU', roles: ['admin', 'cashier'], activePages: ['onu_stickers'] }
|
||||
] },
|
||||
{ key: 'grp_packages', section: 'billing', href: '/admin/packages', icon: 'bi bi-box-seam', labelKey: 'admin.nav.grp_packages', labelDefault: 'Paket & Voucher', roles: ['admin', 'cashier'], activePages: ['packages', 'voucher_packages', 'vouchers_discounts', 'discounts', 'promo_slides'], children: [
|
||||
{ key: 'packages', section: 'billing', href: '/admin/packages', icon: 'bi bi-box-seam', labelKey: 'admin.nav.internet_packages', labelDefault: 'Paket Internet', roles: ['admin', 'cashier'], activePages: ['packages'] },
|
||||
{ key: 'voucher_packages', section: 'billing', href: '/admin/vouchers/packages', icon: 'bi bi-ticket-detailed', labelKey: 'admin.nav.voucher_packages', labelDefault: 'Paket Voucher', roles: ['admin', 'cashier'], activePages: ['voucher_packages'] },
|
||||
{ key: 'vouchers_discounts', section: 'billing', href: '/admin/vouchers-discounts', icon: 'bi bi-ticket-perforated', labelKey: 'admin.nav.vouchers_discounts', labelDefault: 'Biaya Pasang', roles: ['admin'], activePages: ['vouchers_discounts'] },
|
||||
{ key: 'discounts', section: 'billing', href: '/admin/discounts', icon: 'bi bi-percent', labelKey: 'admin.nav.discounts', labelDefault: 'Diskon', roles: ['admin', 'cashier'], activePages: ['discounts'] },
|
||||
{ key: 'promo_slides', section: 'billing', href: '/admin/promo-slides', icon: 'bi bi-image', labelKey: 'admin.nav.promo_slides', labelDefault: 'Promo Slides', roles: ['admin'], activePages: ['promo_slides'] }
|
||||
] },
|
||||
{ key: 'grp_billing', section: 'billing', href: '/admin/billing', icon: 'bi bi-receipt', labelKey: 'admin.nav.invoices', labelDefault: 'Tagihan', roles: ['admin', 'cashier'], bottomNav: true, bottomNavOrder: 3, activePages: ['billing', 'collector_payments'], children: [
|
||||
{ key: 'billing', section: 'billing', href: '/admin/billing', icon: 'bi bi-receipt', labelKey: 'admin.nav.invoices_list', labelDefault: 'Data Tagihan', roles: ['admin', 'cashier'], activePages: ['billing'] },
|
||||
{ key: 'collector_payments', section: 'billing', href: '/admin/collector-payments', icon: 'bi bi-check2-square', labelKey: 'admin.nav.collector_payments', labelDefault: 'Approval Kolektor', roles: ['admin', 'cashier'], activePages: ['collector_payments'] }
|
||||
] },
|
||||
|
||||
{ key: 'grp_finance', section: 'finance', href: '/admin/reports', icon: 'bi bi-cash-stack', labelKey: 'admin.nav.grp_finance', labelDefault: 'Keuangan', roles: ['admin', 'cashier'], activePages: ['reports', 'cashiers_reports', 'payroll', 'cash_in', 'expenses', 'expense_categories'], children: [
|
||||
{ key: 'reports', section: 'finance', href: '/admin/reports', icon: 'bi bi-bar-chart-line', labelKey: 'admin.nav.finance_report', labelDefault: 'Laporan Keuangan', roles: ['admin', 'cashier'], activePages: ['reports'] },
|
||||
{ key: 'cashiers_reports', section: 'finance', href: '/admin/cashiers/reports', icon: 'bi bi-journal-text', labelKey: 'admin.nav.cashiers_reports', labelDefault: 'Laporan Kasir', roles: ['admin', 'cashier'], activePages: ['cashiers_reports'] },
|
||||
{ key: 'cash_in', section: 'finance', href: '/admin/finance/cash-in', icon: 'bi bi-cash-stack', labelKey: 'admin.nav.cash_in', labelDefault: 'Kas Masuk', roles: ['admin', 'cashier'], activePages: ['cash_in'] },
|
||||
{ key: 'expenses', section: 'finance', href: '/admin/finance/expenses', icon: 'bi bi-wallet2', labelKey: 'admin.nav.expenses', labelDefault: 'Pengeluaran', roles: ['admin', 'cashier'], activePages: ['expenses'] },
|
||||
{ key: 'expense_categories', section: 'finance', href: '/admin/finance/expense-categories', icon: 'bi bi-tags', labelKey: 'admin.nav.expense_categories', labelDefault: 'Kategori Pengeluaran', roles: ['admin'], activePages: ['expense_categories'] },
|
||||
{ key: 'payroll', section: 'finance', href: '/admin/payroll', icon: 'bi bi-wallet2', labelKey: 'admin.nav.payroll', labelDefault: 'Gaji & Payroll', roles: ['admin'], activePages: ['payroll'] }
|
||||
] },
|
||||
|
||||
{ key: 'grp_service', section: 'service', href: '/admin/tickets', icon: 'bi bi-headset', labelKey: 'admin.nav.grp_service', labelDefault: 'Layanan', roles: ['admin', 'cashier'], activePages: ['tickets', 'inventory', 'attendance'], children: [
|
||||
{ key: 'tickets', section: 'service', href: '/admin/tickets', icon: 'bi bi-headset', labelKey: 'admin.nav.customer_tickets', labelDefault: 'Keluhan Pelanggan', roles: ['admin', 'cashier'], activePages: ['tickets'] },
|
||||
{ key: 'inventory', section: 'service', href: '/admin/inventory', icon: 'bi bi-boxes', labelKey: 'admin.nav.inventory', labelDefault: 'Inventaris (Stok)', roles: ['admin', 'cashier'], activePages: ['inventory'] },
|
||||
{ key: 'attendance', section: 'service', href: '/admin/attendance', icon: 'bi bi-calendar-check', labelKey: 'admin.nav.attendance', labelDefault: 'Absensi Karyawan', roles: ['admin', 'cashier'], activePages: ['attendance'] }
|
||||
] },
|
||||
|
||||
{ key: 'cashier_attendance', section: 'cashier', href: '/admin/cashiers/attendance', icon: 'bi bi-calendar-check', labelKey: 'admin.nav.cashier_attendance', labelDefault: 'Absensi Saya', roles: ['cashier'], activePages: ['cashier_attendance'] },
|
||||
|
||||
{ key: 'grp_users', section: 'user_management', href: '/admin/technicians', icon: 'bi bi-person-gear', labelKey: 'admin.nav.grp_users', labelDefault: 'Pengguna', roles: ['admin', 'cashier'], activePages: ['technicians', 'cashiers', 'collectors', 'areas'], children: [
|
||||
{ key: 'technicians', section: 'user_management', href: '/admin/technicians', icon: 'bi bi-person-gear', labelKey: 'admin.nav.technicians', labelDefault: 'Teknisi', roles: ['admin'], activePages: ['technicians'] },
|
||||
{ key: 'cashiers', section: 'user_management', href: '/admin/cashiers', icon: 'bi bi-person-vcard', labelKey: 'admin.nav.cashiers', labelDefault: 'Kasir', roles: ['admin'], activePages: ['cashiers'] },
|
||||
{ key: 'collectors', section: 'user_management', href: '/admin/collectors', icon: 'bi bi-person-badge', labelKey: 'admin.nav.collectors', labelDefault: 'Kolektor', roles: ['admin'], activePages: ['collectors'] },
|
||||
{ key: 'areas', section: 'user_management', href: '/admin/areas', icon: 'bi bi-geo-alt', labelKey: 'admin.nav.areas', labelDefault: 'Manajemen Area', roles: ['admin', 'cashier'], activePages: ['areas'] }
|
||||
] },
|
||||
|
||||
{ key: 'grp_system', section: 'system', href: '/admin/settings', icon: 'bi bi-gear', labelKey: 'admin.nav.grp_system', labelDefault: 'Sistem', roles: ['admin'], activePages: ['update', 'service_notice', 'settings', 'ewallet_logs', 'backup', 'audit_logs'], children: [
|
||||
{ key: 'update', section: 'system', href: '/admin/update', icon: 'bi bi-cloud-arrow-down', labelKey: 'admin.nav.update', labelDefault: 'Update GitHub', roles: ['admin'], activePages: ['update'] },
|
||||
{ key: 'service_notice', section: 'system', href: '/admin/service-notice', icon: 'bi bi-megaphone-fill', labelKey: 'admin.nav.service_notice', labelDefault: 'Status Layanan', roles: ['admin'], activePages: ['service_notice'] },
|
||||
{ key: 'settings', section: 'system', href: '/admin/settings', icon: 'bi bi-gear', labelKey: 'admin.nav.settings', labelDefault: 'Pengaturan', roles: ['admin'], activePages: ['settings'] },
|
||||
{ key: 'ewallet_logs', section: 'system', href: '/admin/ewallet-logs', icon: 'bi bi-journal-text', labelKey: 'admin.nav.log_rekap', labelDefault: 'Log & Rekap', roles: ['admin'], activePages: ['ewallet_logs'] },
|
||||
{ key: 'backup', section: 'system', href: '/admin/backup', icon: 'bi bi-hdd-stack', labelKey: 'admin.nav.backup', labelDefault: 'Backup & Recovery', roles: ['admin'], activePages: ['backup'] },
|
||||
{ key: 'audit_logs', section: 'system', href: '/admin/audit-logs', icon: 'bi bi-shield-lock', labelKey: 'admin.nav.audit_logs', labelDefault: 'Log Aktivitas', roles: ['admin'], activePages: ['audit_logs'] }
|
||||
] }
|
||||
|
||||
];
|
||||
|
||||
const DEFAULT_MENU_STATES = {
|
||||
dashboard: STATE_VISIBLE,
|
||||
mikrotik: STATE_VISIBLE,
|
||||
network: STATE_VISIBLE,
|
||||
grp_customers: STATE_VISIBLE,
|
||||
grp_packages: STATE_VISIBLE,
|
||||
grp_billing: STATE_VISIBLE,
|
||||
grp_finance: STATE_VISIBLE,
|
||||
grp_service: STATE_VISIBLE,
|
||||
grp_users: STATE_VISIBLE,
|
||||
grp_system: STATE_VISIBLE,
|
||||
map: STATE_VISIBLE,
|
||||
radius_server: STATE_VISIBLE,
|
||||
map: STATE_VISIBLE,
|
||||
acs_pro: STATE_VISIBLE,
|
||||
onu_provision: STATE_VISIBLE,
|
||||
olts: STATE_HIDDEN,
|
||||
whatsapp: STATE_VISIBLE,
|
||||
automation: STATE_VISIBLE,
|
||||
broadcast: STATE_VISIBLE,
|
||||
whatsapp_live_chat: STATE_VISIBLE,
|
||||
promo_slides: STATE_VISIBLE,
|
||||
customers: STATE_VISIBLE,
|
||||
onu_stickers: STATE_VISIBLE,
|
||||
packages: STATE_VISIBLE,
|
||||
voucher_packages: STATE_VISIBLE,
|
||||
billing: STATE_VISIBLE,
|
||||
reports: STATE_VISIBLE,
|
||||
cashiers_reports: STATE_VISIBLE,
|
||||
collector_payments: STATE_VISIBLE,
|
||||
tickets: STATE_VISIBLE,
|
||||
inventory: STATE_LOCKED,
|
||||
attendance: STATE_LOCKED,
|
||||
payroll: STATE_LOCKED,
|
||||
cash_in: STATE_VISIBLE,
|
||||
expenses: STATE_VISIBLE,
|
||||
expense_categories: STATE_VISIBLE,
|
||||
cashier_attendance: STATE_VISIBLE,
|
||||
technicians: STATE_LOCKED,
|
||||
cashiers: STATE_LOCKED,
|
||||
collectors: STATE_LOCKED,
|
||||
areas: STATE_VISIBLE,
|
||||
update: STATE_VISIBLE,
|
||||
vouchers_discounts: STATE_VISIBLE,
|
||||
discounts: STATE_VISIBLE,
|
||||
service_notice: STATE_VISIBLE,
|
||||
settings: STATE_VISIBLE,
|
||||
ewallet_logs: STATE_VISIBLE,
|
||||
backup: STATE_VISIBLE,
|
||||
monitoring: STATE_VISIBLE,
|
||||
audit_logs: STATE_VISIBLE
|
||||
};
|
||||
|
||||
const SECTION_DEFINITIONS = [
|
||||
{ key: 'main', labelKey: 'admin.section.main', labelDefault: 'UTAMA' },
|
||||
{ key: 'billing', labelKey: 'admin.section.billing', labelDefault: 'BILLING' },
|
||||
{ key: 'finance', labelKey: 'admin.section.finance', labelDefault: 'KEUANGAN' },
|
||||
{ key: 'service', labelKey: 'admin.section.service', labelDefault: 'LAYANAN' },
|
||||
{ key: 'cashier', labelKey: 'admin.section.cashier', labelDefault: 'KASIR' },
|
||||
{ key: 'user_management', labelKey: 'admin.section.user_management', labelDefault: 'MANAJEMEN USER' },
|
||||
{ key: 'system', labelKey: 'admin.section.system', labelDefault: 'SISTEM' }
|
||||
];
|
||||
|
||||
function sha256(input) {
|
||||
return crypto.createHash('sha256').update(String(input || '')).digest('hex');
|
||||
}
|
||||
|
||||
function normalizeState(value) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return VALID_STATES.has(normalized) ? normalized : STATE_VISIBLE;
|
||||
}
|
||||
|
||||
function allMenuDefs() {
|
||||
const out = [];
|
||||
for (const m of MENU_DEFINITIONS) {
|
||||
out.push(m);
|
||||
if (Array.isArray(m.children)) { for (const c of m.children) out.push(c); }
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function getStoredMenuStates() {
|
||||
// Coba ambil dari Database dulu (Lebih Aman)
|
||||
let raw = getAppSetting(SETTINGS_KEY, null);
|
||||
let activationKeys = getAppSetting('sidebar_activation_keys', {});
|
||||
|
||||
// Fallback ke settings.json jika di DB masih kosong (Migration)
|
||||
if (raw === null) {
|
||||
raw = getSetting(SETTINGS_KEY, {});
|
||||
activationKeys = getSetting('sidebar_activation_keys', {});
|
||||
// Langsung migrasi ke DB agar kedepannya pakai DB
|
||||
if (Object.keys(raw).length > 0) {
|
||||
saveAppSetting(SETTINGS_KEY, raw);
|
||||
saveAppSetting('sidebar_activation_keys', activationKeys);
|
||||
}
|
||||
}
|
||||
|
||||
const stateMap = {};
|
||||
|
||||
for (const menu of allMenuDefs()) {
|
||||
const defaultState = DEFAULT_MENU_STATES[menu.key] || STATE_VISIBLE;
|
||||
let storedState = raw && raw[menu.key] ? raw[menu.key] : defaultState;
|
||||
|
||||
const normalized = normalizeState(storedState);
|
||||
|
||||
// Jika menu aslinya LOCKED tapi diubah jadi VISIBLE/HIDDEN, cek kunci aktivasinya
|
||||
if (defaultState === STATE_LOCKED && normalized !== STATE_LOCKED) {
|
||||
const expectedKey = sha256(menu.key + getFeaturePasswordHash());
|
||||
const providedKey = activationKeys[menu.key];
|
||||
|
||||
if (providedKey !== expectedKey) {
|
||||
// Kunci tidak cocok! Kembalikan ke LOCKED
|
||||
stateMap[menu.key] = STATE_LOCKED;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
stateMap[menu.key] = normalized;
|
||||
}
|
||||
return stateMap;
|
||||
}
|
||||
|
||||
function saveMenuStates(stateMap) {
|
||||
const activationKeys = getAppSetting('sidebar_activation_keys', {});
|
||||
const passwordHash = getFeaturePasswordHash();
|
||||
|
||||
for (const key in stateMap) {
|
||||
const newState = stateMap[key];
|
||||
const defaultState = DEFAULT_MENU_STATES[key] || STATE_VISIBLE;
|
||||
|
||||
// Jika menu yang aslinya LOCKED diaktifkan (jadi visible/hidden), generate kunci
|
||||
if (defaultState === STATE_LOCKED && newState !== STATE_LOCKED) {
|
||||
activationKeys[key] = sha256(key + passwordHash);
|
||||
} else if (newState === STATE_LOCKED) {
|
||||
// Jika dikunci kembali, hapus kuncinya
|
||||
delete activationKeys[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Simpan ke Database (Utama)
|
||||
saveAppSetting(SETTINGS_KEY, sanitizeMenuStates(stateMap));
|
||||
saveAppSetting('sidebar_activation_keys', activationKeys);
|
||||
|
||||
// Hanya gunakan database, tidak perlu backup ke settings.json
|
||||
return true;
|
||||
}
|
||||
|
||||
function sanitizeMenuStates(input) {
|
||||
const clean = {};
|
||||
for (const menu of allMenuDefs()) {
|
||||
const defaultState = DEFAULT_MENU_STATES[menu.key] || STATE_VISIBLE;
|
||||
let state = input && input[menu.key] ? input[menu.key] : defaultState;
|
||||
|
||||
clean[menu.key] = normalizeState(state);
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
|
||||
function isMenuAllowedForSession(menu, session) {
|
||||
const roles = Array.isArray(menu.roles) ? menu.roles : ['admin'];
|
||||
if (roles.includes('admin') && session && session.isAdmin) return true;
|
||||
if (roles.includes('cashier') && session && session.isCashier) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function enrichMenu(menu, states) {
|
||||
const state = states[menu.key] || DEFAULT_MENU_STATES[menu.key] || STATE_VISIBLE;
|
||||
const locked = state === STATE_LOCKED;
|
||||
const hidden = state === STATE_HIDDEN;
|
||||
const out = {
|
||||
...menu,
|
||||
state,
|
||||
locked,
|
||||
hidden,
|
||||
hrefResolved: menu.href,
|
||||
lockedMessage: locked ? `Menu "${menu.labelDefault}" terkunci. Hubungi ${getFeatureContactPhone()} untuk mendapatkan password aktivasi.` : ''
|
||||
};
|
||||
if (Array.isArray(menu.children)) {
|
||||
out.children = menu.children.map((ch) => enrichMenu(ch, states));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function getSidebarSections(session) {
|
||||
const states = getStoredMenuStates();
|
||||
return SECTION_DEFINITIONS.map((section) => {
|
||||
const items = MENU_DEFINITIONS
|
||||
.filter((menu) => menu.section === section.key)
|
||||
.filter((menu) => isMenuAllowedForSession(menu, session))
|
||||
.map((menu) => {
|
||||
const enriched = enrichMenu(menu, states);
|
||||
if (Array.isArray(enriched.children)) {
|
||||
enriched.children = enriched.children
|
||||
.filter((ch) => isMenuAllowedForSession(ch, session))
|
||||
.filter((ch) => !ch.hidden);
|
||||
}
|
||||
return enriched;
|
||||
})
|
||||
.filter((menu) => !menu.hidden);
|
||||
|
||||
return {
|
||||
...section,
|
||||
items
|
||||
};
|
||||
}).filter((section) => section.items.length > 0);
|
||||
}
|
||||
|
||||
function getBottomNavItems(session) {
|
||||
const states = getStoredMenuStates();
|
||||
return MENU_DEFINITIONS
|
||||
.filter((menu) => menu.bottomNav)
|
||||
.filter((menu) => isMenuAllowedForSession(menu, session))
|
||||
.map((menu) => enrichMenu(menu, states))
|
||||
.filter((menu) => !menu.hidden)
|
||||
.sort((a, b) => (a.bottomNavOrder || 99) - (b.bottomNavOrder || 99));
|
||||
}
|
||||
|
||||
function getConfigMenus() {
|
||||
const states = getStoredMenuStates();
|
||||
return MENU_DEFINITIONS.map((menu) => {
|
||||
const section = SECTION_DEFINITIONS.find((s) => s.key === menu.section);
|
||||
return {
|
||||
...menu,
|
||||
state: states[menu.key] || DEFAULT_MENU_STATES[menu.key] || STATE_VISIBLE,
|
||||
roleLabel: menu.roles.includes('admin') && menu.roles.includes('cashier')
|
||||
? 'Admin & Kasir'
|
||||
: menu.roles.includes('cashier')
|
||||
? 'Kasir'
|
||||
: 'Admin',
|
||||
sectionLabel: section?.labelDefault || menu.section,
|
||||
sectionLabelKey: section?.labelKey || ''
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getMenuDefinition(key) {
|
||||
const top = MENU_DEFINITIONS.find((menu) => menu.key === key);
|
||||
if (top) return top;
|
||||
for (const menu of MENU_DEFINITIONS) {
|
||||
if (Array.isArray(menu.children)) {
|
||||
const ch = menu.children.find((c) => c.key === key);
|
||||
if (ch) return ch;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isFeaturePasswordValid(password) {
|
||||
return sha256(password) === getFeaturePasswordHash();
|
||||
}
|
||||
|
||||
function evaluateMenuAccess(menuKey, session) {
|
||||
const menu = getMenuDefinition(menuKey);
|
||||
if (!menu) {
|
||||
return { allowed: true, state: STATE_VISIBLE, menu: null };
|
||||
}
|
||||
|
||||
if (!isMenuAllowedForSession(menu, session)) {
|
||||
return { allowed: false, state: 'forbidden', menu, reason: 'forbidden' };
|
||||
}
|
||||
|
||||
const states = getStoredMenuStates();
|
||||
const state = states[menu.key] || DEFAULT_MENU_STATES[menu.key] || STATE_VISIBLE;
|
||||
if (state === STATE_HIDDEN) {
|
||||
return { allowed: false, state, menu, reason: 'hidden' };
|
||||
}
|
||||
if (state === STATE_LOCKED) {
|
||||
return { allowed: false, state, menu, reason: 'locked' };
|
||||
}
|
||||
return { allowed: true, state, menu, reason: null };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getFeatureContactPhone,
|
||||
getFeaturePasswordHash,
|
||||
STATE_VISIBLE,
|
||||
STATE_HIDDEN,
|
||||
STATE_LOCKED,
|
||||
MENU_DEFINITIONS,
|
||||
getSidebarSections,
|
||||
getBottomNavItems,
|
||||
getConfigMenus,
|
||||
getMenuDefinition,
|
||||
getStoredMenuStates,
|
||||
sanitizeMenuStates,
|
||||
isFeaturePasswordValid,
|
||||
saveMenuStates,
|
||||
evaluateMenuAccess,
|
||||
};
|
||||
Reference in new issue
Block a user