// VaultSentinel - Account Management Application // Main JavaScript functionality class AccountManager { constructor() { // Check if user is logged in const currentUser = this.loadFromStorage('currentUser'); if (!currentUser) { window.location.href = '../pages/login.html'; return; } this.currentUser = currentUser; this.accounts = []; this.applications = []; this.apiBase = '/api'; this.currentPage = 'dashboard'; this.accountSearchTerm = ''; this.applicationSearchTerm = ''; this.accountServiceFilter = ''; this.configureNotifications(); this.initPromise = this.init(); this.pendingAccountAppId = undefined; } configureNotifications() { if (window.Notiflix?.Notify) { Notiflix.Notify.init({ position: 'right-top', timeout: 2500, clickToClose: true, pauseOnHover: true, distance: '12px', fontSize: '14px' }); } } notifySuccess(message) { if (window.Notiflix?.Notify) { Notiflix.Notify.success(message); } else { alert(message); } } notifyFailure(message) { if (window.Notiflix?.Notify) { Notiflix.Notify.failure(message); } else { alert(message); } } notifyWarning(message) { if (window.Notiflix?.Notify) { Notiflix.Notify.warning(message); } else { alert(message); } } getUserId() { const u = this.currentUser; const detected = u?.UserId ?? u?.userId ?? u?.id ?? u?.ID ?? u?.userid ?? u?.user_id ?? u?.user?.UserId ?? u?.user?.userId; // Fallback: if only username/role exist (no id), use default admin id = 1 return detected ?? 1; } async init() { await this.fetchApplications(); await this.fetchAccounts(); this.setupEventListeners(); this.loadModals(); // Load modals từ file riêng // Single-page navigation based on hash this.handleRoute(location.hash || '#dashboard'); window.addEventListener('hashchange', () => this.handleRoute(location.hash)); } handleRoute(hash) { const route = (hash || '#dashboard').replace('#', '') || 'dashboard'; this.renderView(route); } renderView(page) { this.currentPage = page; const mainContent = document.getElementById('mainContent'); if (!mainContent) return; if (page === 'applications') { mainContent.innerHTML = this.getApplicationsContent(); this.setupAccountRowListeners(); this.setupAddButtonListeners(); this.setupFilters(); } else if (page === 'accounts') { mainContent.innerHTML = this.getAccountsContent(); this.setupAccountRowListeners(); this.setupAddButtonListeners(); this.setupFilters(); } else { mainContent.innerHTML = this.renderDashboard(); } this.restoreSearchFocus(); this.setActiveNav(page); } setActiveNav(page) { document.querySelectorAll('[data-nav]').forEach(link => { const isActive = link.dataset.nav === page; link.classList.toggle('border-l-4', isActive); link.classList.toggle('border-blue-600', isActive); link.classList.toggle('bg-slate-200/80', isActive); link.classList.toggle('dark:bg-slate-800', isActive); link.classList.toggle('text-slate-900', isActive); link.classList.toggle('dark:text-slate-50', isActive); link.classList.toggle('font-bold', isActive); }); } async fetchApplications() { const res = await fetch(`${this.apiBase}/applications`); const data = await res.json(); if (data.success) { this.applications = data.data; } else { console.error('Load applications failed:', data.message); } } async fetchAccounts() { const userId = this.getUserId(); if (!userId) return; const res = await fetch(`${this.apiBase}/accounts/user/${userId}`); const data = await res.json(); if (data.success) { this.accounts = data.data; } else { console.error('Load accounts failed:', data.message); } } async loadModals() { try { const existingContainer = document.getElementById('modalsContainer'); if (existingContainer && existingContainer.children.length) { return; } const response = await fetch('../modals.html'); const modalsHTML = await response.text(); const container = existingContainer || document.createElement('div'); if (!container.id) container.id = 'modalsContainer'; container.innerHTML = modalsHTML; if (!container.parentElement) { document.body.appendChild(container); } this.setupFormListeners(); this.setupAccountRowListeners(); this.setupAddButtonListeners(); this.setupFilters(); } catch (error) { console.error('Lỗi load modals:', error); } } restoreSearchFocus() { const accountSearch = document.getElementById('accountSearch'); const appSearch = document.getElementById('appSearch'); if (accountSearch && accountSearch.dataset.focused === 'true') { const pos = accountSearch.selectionStart || accountSearch.value.length; accountSearch.focus(); accountSearch.setSelectionRange(pos, pos); } if (appSearch && appSearch.dataset.focused === 'true') { const pos = appSearch.selectionStart || appSearch.value.length; appSearch.focus(); appSearch.setSelectionRange(pos, pos); } } setupEventListeners() { // Modal close buttons document.querySelectorAll('[data-close-modal]').forEach(btn => { btn.addEventListener('click', () => this.closeModals()); }); // Close with Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { this.closeModals(); } }); // Form submissions const accountForm = document.getElementById('accountForm'); if (accountForm) { accountForm.addEventListener('submit', (e) => this.handleAccountSubmit(e)); } const appForm = document.getElementById('appForm'); if (appForm) { appForm.addEventListener('submit', (e) => this.handleAppSubmit(e)); } // Logout button const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => this.handleLogout()); } // Update account display this.updateAccountDisplay(); // Account table row clicks this.setupAccountRowListeners(); this.setupFilters(); } setupFormListeners() { const accountForm = document.getElementById('accountForm'); if (accountForm) { accountForm.addEventListener('submit', (e) => this.handleAccountSubmit(e)); } const appForm = document.getElementById('appForm'); if (appForm) { appForm.addEventListener('submit', (e) => this.handleAppSubmit(e)); } // Close when clicking backdrop outside modal content document.querySelectorAll('.modal-backdrop').forEach(backdrop => { backdrop.addEventListener('click', (evt) => { if (evt.target === backdrop) { this.closeModals(); } }); }); } updateAccountDisplay() { // Use the logged-in user from constructor const usernameEl = document.getElementById('accountUsername'); const roleEl = document.getElementById('accountRole'); if (usernameEl) usernameEl.textContent = this.currentUser?.username || this.currentUser?.Username || 'User'; if (roleEl) roleEl.textContent = this.currentUser?.role || this.currentUser?.Role || 'Administrator'; } getFilteredAccounts() { const svcFilter = this.accountServiceFilter || ''; const search = (this.accountSearchTerm || '').toLowerCase(); return this.accounts.filter(acc => { const matchesService = !svcFilter || String(acc.AppId) === String(svcFilter); if (!matchesService) return false; if (!search) return true; const hay = [acc.AccountUsername, acc.Email, acc.AppName, acc.AppType].map(v => (v || '').toLowerCase()); return hay.some(val => val.includes(search)); }); } getFilteredApplications() { const search = (this.applicationSearchTerm || '').toLowerCase(); if (!search) return this.applications; return this.applications.filter(app => { const hay = [app.Name, app.Type, app.Description, app.Url, app.Icon].map(v => (v || '').toLowerCase()); return hay.some(val => val.includes(search)); }); } handleLogout() { if (confirm('Are you sure you want to logout?')) { this.saveToStorage('currentUser', null); localStorage.clear(); window.location.href = '../pages/login.html'; } } renderDashboard() { return `

System Overview

Account & Service Management

Applications
${this.applications.length} ${this.applications.filter(a => (a.Status || a.status) === 'online').length} Active
Total Accounts
${this.accounts.length} Managed
Last Updated
${new Date().toLocaleDateString()}
Status
Operational check_circle

history Recent Accounts

${this.accounts.length > 0 ? `
${this.accounts.slice(-5).reverse().map(acc => { const username = acc.AccountUsername || acc.username || '-'; const service = acc.AppName || acc.service || '-'; const owner = acc.Email || acc.owner || this.currentUser?.Username || this.currentUser?.username || '-'; return `

${username}

${service} • ${owner}

`;}).join('')}
` : `

No accounts yet. Create one

`}
`; } getAccountsContent() { const filteredAccounts = this.getFilteredAccounts(); return `

Accounts Management

Administrative Access Control

Service
Search
${filteredAccounts.length > 0 ? `
${filteredAccounts.map(acc => ` `).join('')}
Owner Username Service Actions
` : `

No accounts yet. Create one to get started.

`}
`; } getApplicationsContent() { const filteredApps = this.getFilteredApplications(); return `

Applications

Manage and monitor active infrastructure services.

lan

Active

${this.applications.filter(a => a.status === 'online').length}

bolt

Total

${this.applications.length}

database

Health

99.9%

Search
${filteredApps.map(app => ` `).join('')}
Name Type Description URL Status Actions
${app.Icon || 'apps'}
${app.Name}
${app.Type} ${app.Description || '-'} ${(app.Url || app.url) ? `${app.Url || app.url}` : '-'}
${(app.Status || app.status) === 'online' ? 'Online' : 'Offline'}
`; } renderAccountsTableBody() { const tbody = document.querySelector('.accounts-table-body'); if (!tbody) return; const filteredAccounts = this.getFilteredAccounts(); tbody.innerHTML = filteredAccounts.map(acc => ` ${acc.Email || '-'} ${acc.AccountUsername || '-'} ${acc.AppName || '-'} `).join(''); this.setupAccountRowListeners(); } renderApplicationsTableBody() { const tbody = document.querySelector('.apps-table-body'); if (!tbody) return; const filteredApps = this.getFilteredApplications(); tbody.innerHTML = filteredApps.map(app => `
${app.Icon || 'apps'}
${app.Name}
${app.Type} ${app.Description || '-'} ${(app.Url || app.url) ? `${app.Url || app.url}` : '-'}
${(app.Status || app.status) === 'online' ? 'Online' : 'Offline'}
`).join(''); this.setupAccountRowListeners(); } setupAccountRowListeners() { // View Account listeners document.querySelectorAll('.view-account').forEach(btn => { btn.addEventListener('click', (e) => { const accountId = Number(btn.dataset.accountId); const account = this.accounts.find(a => a.AccountId === accountId); this.currentViewAccountId = accountId; this.currentViewAccount = account; document.getElementById('viewAccountService').textContent = account?.AppName || '-'; document.getElementById('viewAccountOwner').textContent = account?.Email || '-'; document.getElementById('viewAccountUsername').textContent = account?.AccountUsername || '-'; const passwordEl = document.getElementById('viewAccountPassword'); passwordEl.dataset.password = account?.AccountPassword || ''; passwordEl.textContent = '••••••••'; passwordEl.dataset.visible = 'false'; document.getElementById('toggleIcon').textContent = 'visibility'; document.getElementById('viewAccountModal').classList.add('open'); }); }); // Delete Account listeners - show confirmation modal document.querySelectorAll('.delete-account').forEach(btn => { btn.addEventListener('click', (e) => { const accountId = Number(btn.dataset.accountId); const account = this.accounts.find(a => a.AccountId === accountId); this.pendingDeleteAccountId = accountId; document.getElementById('deleteAccountUsername').textContent = account?.AccountUsername || ''; document.getElementById('deleteAccountModal').classList.add('open'); }); }); // Confirm Delete Account document.querySelectorAll('.confirm-delete-account').forEach(btn => { btn.addEventListener('click', () => { if (this.pendingDeleteAccountId !== undefined) { fetch(`${this.apiBase}/accounts/${this.pendingDeleteAccountId}`, { method: 'DELETE' }) .then(res => res.json()) .then(data => { if (data.success) { this.notifySuccess('Account deleted successfully'); this.closeModals(); this.refreshAccountsUI(); } else { this.notifyFailure(data.message || 'Delete account failed'); } }) .catch(err => { console.error(err); this.notifyFailure('Delete account failed'); }); } }); }); // Edit Account listeners document.querySelectorAll('.edit-account').forEach(btn => { btn.addEventListener('click', (e) => { const accountId = Number(btn.dataset.accountId); const account = this.accounts.find(a => a.AccountId === accountId); // Populate form with existing data const form = document.getElementById('accountForm'); if (form) { const userInput = form.querySelector('#accountUsername'); const passInput = form.querySelector('#accountPassword'); const ownerInput = form.querySelector('#accountOwner'); const serviceSelect = form.querySelector('#accountService'); if (userInput) userInput.value = account?.AccountUsername || ''; if (passInput) passInput.value = account?.AccountPassword || ''; if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || ''; if (serviceSelect) serviceSelect.value = account?.AppId || ''; } this.pendingAccountAppId = account?.AppId; this.editingAccountId = account?.AccountId; this.closeModals(); this.openAccountModal(); }); }); // Edit from View modal document.querySelectorAll('.edit-account-from-view').forEach(btn => { btn.addEventListener('click', () => { const account = this.currentViewAccount; const form = document.getElementById('accountForm'); if (form) { const userInput = form.querySelector('#accountUsername'); const passInput = form.querySelector('#accountPassword'); const ownerInput = form.querySelector('#accountOwner'); const serviceSelect = form.querySelector('#accountService'); if (userInput) userInput.value = account?.AccountUsername || ''; if (passInput) passInput.value = account?.AccountPassword || ''; if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || ''; if (serviceSelect) serviceSelect.value = account?.AppId || ''; } this.pendingAccountAppId = account?.AppId; this.editingAccountId = account?.AccountId; this.closeModals(); this.openAccountModal(); }); }); // Toggle Password Visibility document.querySelectorAll('.toggle-password').forEach(btn => { btn.addEventListener('click', () => { const passwordEl = document.getElementById('viewAccountPassword'); const toggleIcon = document.getElementById('toggleIcon'); const storedPwd = passwordEl.dataset.password || this.currentViewAccount?.AccountPassword || ''; const isVisible = passwordEl.dataset.visible === 'true'; if (isVisible) { passwordEl.textContent = '••••••••'; passwordEl.dataset.visible = 'false'; toggleIcon.textContent = 'visibility'; } else { passwordEl.textContent = storedPwd || '(no password stored)'; passwordEl.dataset.visible = 'true'; toggleIcon.textContent = 'visibility_off'; } }); }); // View App listeners document.querySelectorAll('.view-app').forEach(btn => { btn.addEventListener('click', (e) => { const appId = Number(btn.dataset.appId); const app = this.applications.find(a => a.AppId === appId); this.currentViewAppId = appId; document.getElementById('viewAppName').textContent = app?.Name || '-'; document.getElementById('viewAppType').textContent = app?.Type || '-'; const iconVal = app?.Icon || app?.icon || 'apps'; const iconSymbolEl = document.getElementById('viewAppIconSymbol'); const iconNameEl = document.getElementById('viewAppIconName'); if (iconSymbolEl) iconSymbolEl.textContent = iconVal; if (iconNameEl) iconNameEl.textContent = iconVal; document.getElementById('viewAppDescription').textContent = app?.Description || '-'; const urlEl = document.getElementById('viewAppUrl'); const urlVal = app?.Url || app?.url; if (urlEl) { if (urlVal) { urlEl.innerHTML = `${urlVal}`; } else { urlEl.textContent = '-'; } } const statusValue = app?.Status || app?.status; document.getElementById('viewAppStatus').textContent = statusValue === 'online' ? 'Online' : 'Offline'; document.getElementById('viewAppModal').classList.add('open'); }); }); // Delete App listeners - show confirmation modal document.querySelectorAll('.delete-app').forEach(btn => { btn.addEventListener('click', (e) => { const appId = Number(btn.dataset.appId); const app = this.applications.find(a => a.AppId === appId); this.pendingDeleteAppId = appId; document.getElementById('deleteAppName').textContent = app?.Name || ''; document.getElementById('deleteAppModal').classList.add('open'); }); }); // Confirm Delete App document.querySelectorAll('.confirm-delete-app').forEach(btn => { btn.addEventListener('click', () => { if (this.pendingDeleteAppId !== undefined) { fetch(`${this.apiBase}/applications/${this.pendingDeleteAppId}`, { method: 'DELETE' }) .then(res => res.json()) .then(data => { if (data.success) { this.notifySuccess('Application deleted successfully'); this.closeModals(); this.refreshApplicationsUI(); } else { this.notifyFailure(data.message || 'Delete application failed'); } }) .catch(err => { console.error(err); this.notifyFailure('Delete application failed'); }); } }); }); // Edit App listeners document.querySelectorAll('.edit-app').forEach(btn => { btn.addEventListener('click', (e) => { const appId = Number(btn.dataset.appId); const app = this.applications.find(a => a.AppId === appId); document.getElementById('appName').value = app?.Name || ''; document.getElementById('appType').value = app?.Type || ''; document.getElementById('appStatus').value = app?.Status || 'online'; document.getElementById('appDescription').value = app?.Description || ''; document.getElementById('appIcon').value = app?.Icon || app?.icon || ''; document.getElementById('appUrl').value = app?.Url || app?.url || ''; this.editingAppId = app?.AppId; this.closeModals(); this.openAppModal(); }); }); // Edit App from View modal document.querySelectorAll('.edit-app-from-view').forEach(btn => { btn.addEventListener('click', () => { const appId = this.currentViewAppId; const app = this.applications.find(a => a.AppId === appId); document.getElementById('appName').value = app?.Name || ''; document.getElementById('appType').value = app?.Type || ''; document.getElementById('appStatus').value = app?.Status || 'online'; document.getElementById('appDescription').value = app?.Description || ''; document.getElementById('appIcon').value = app?.Icon || app?.icon || ''; document.getElementById('appUrl').value = app?.Url || ''; this.editingAppId = app?.AppId; this.closeModals(); this.openAppModal(); }); }); } setupAddButtonListeners() { // Add Account button document.querySelectorAll('#addAccountBtn').forEach(btn => { btn.addEventListener('click', () => { this.editingAccountId = undefined; this.pendingAccountAppId = undefined; this.openAccountModal(); }); }); // Add Application button document.querySelectorAll('#addAppBtn').forEach(btn => { btn.addEventListener('click', () => { this.editingAppId = undefined; this.openAppModal(); }); }); } setupFilters() { const serviceFilter = document.getElementById('serviceFilter'); if (serviceFilter) { serviceFilter.value = this.accountServiceFilter || ''; serviceFilter.addEventListener('change', (e) => { this.accountServiceFilter = e.target.value; this.renderAccountsTableBody(); }); } const accountSearch = document.getElementById('accountSearch'); if (accountSearch) { accountSearch.value = this.accountSearchTerm; const handleAccountSearch = event => { this.accountSearchTerm = event.target.value.toLowerCase(); this.renderAccountsTableBody(); }; accountSearch.addEventListener('input', handleAccountSearch); // Restore focus/selection after renders to avoid typing interruptions accountSearch.addEventListener('focus', () => { accountSearch.dataset.focused = 'true'; }); accountSearch.addEventListener('blur', () => { accountSearch.dataset.focused = 'false'; }); } const appSearch = document.getElementById('appSearch'); if (appSearch) { appSearch.value = this.applicationSearchTerm; const handleApplicationSearch = event => { this.applicationSearchTerm = event.target.value.toLowerCase(); this.renderApplicationsTableBody(); }; appSearch.addEventListener('input', handleApplicationSearch); // Restore focus/selection after renders to avoid typing interruptions appSearch.addEventListener('focus', () => { appSearch.dataset.focused = 'true'; }); appSearch.addEventListener('blur', () => { appSearch.dataset.focused = 'false'; }); } } async handleAccountSubmit(e) { e.preventDefault(); const accountForm = document.getElementById('accountForm'); const userId = this.getUserId(); const appId = Number(accountForm?.querySelector('#accountService')?.value || 0); const accountUsername = (accountForm?.querySelector('#accountUsername')?.value || '').trim(); const accountPassword = (accountForm?.querySelector('#accountPassword')?.value || '').trim(); const accountEmail = ((accountForm?.querySelector('#accountOwner')?.value || '').trim()) || this.currentUser?.Username || this.currentUser?.username || ''; if (!accountForm) { this.notifyFailure('Account form not found.'); return; } if (!userId) { this.notifyFailure('User is not authenticated. Please login again.'); return; } if (!appId) { this.notifyWarning('Please select a service.'); return; } if (!accountUsername) { this.notifyWarning('Please enter a username.'); return; } if (!accountPassword) { this.notifyWarning('Please enter a password.'); return; } const payload = { userId, appId, accountUsername, accountPassword, email: accountEmail, accessLevel: 'user', notes: '' }; const isEdit = this.editingAccountId !== undefined; const url = isEdit ? `${this.apiBase}/accounts/${this.editingAccountId}` : `${this.apiBase}/accounts`; const method = isEdit ? 'PUT' : 'POST'; fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(res => res.json()).then(data => { if (data.success) { this.editingAccountId = undefined; this.pendingAccountAppId = undefined; this.notifySuccess(isEdit ? 'Account updated successfully' : 'Account created successfully'); this.closeModals(); this.refreshAccountsUI(); } else { this.notifyFailure(data.message || 'Save account failed'); } }).catch(err => { console.error(err); this.notifyFailure('Save account failed'); }); } async refreshAccountsUI() { await this.fetchAccounts(); if (this.currentPage === 'accounts') { this.renderView('accounts'); } } async handleAppSubmit(e) { e.preventDefault(); const payload = { name: document.getElementById('appName').value, type: document.getElementById('appType').value, status: document.getElementById('appStatus').value, icon: (document.getElementById('appIcon')?.value || 'apps').trim() || 'apps', description: document.getElementById('appDescription')?.value || '', url: (document.getElementById('appUrl')?.value || '').trim() }; const isEdit = this.editingAppId !== undefined; const url = isEdit ? `${this.apiBase}/applications/${this.editingAppId}` : `${this.apiBase}/applications`; const method = isEdit ? 'PUT' : 'POST'; fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(res => res.json()).then(data => { if (data.success) { this.editingAppId = undefined; this.notifySuccess(isEdit ? 'Application updated successfully' : 'Application created successfully'); this.closeModals(); this.refreshApplicationsUI(); } else { this.notifyFailure(data.message || 'Save application failed'); } }).catch(err => { console.error(err); this.notifyFailure('Save application failed'); }); } async refreshApplicationsUI() { await this.fetchApplications(); if (this.currentPage === 'applications') { this.renderView('applications'); } } openAccountModal() { // Refresh service options so newly added applications appear const serviceSelect = document.getElementById('accountService'); if (serviceSelect) { serviceSelect.innerHTML = `` + this.applications.map(app => ``).join(''); if (this.editingAccountId !== undefined && this.pendingAccountAppId) { serviceSelect.value = this.pendingAccountAppId; } } if (this.editingAccountId === undefined) { const form = document.getElementById('accountForm'); if (form) { const serviceSelect = form.querySelector('#accountService'); const ownerInput = form.querySelector('#accountOwner'); const userInput = form.querySelector('#accountUsername'); const passInput = form.querySelector('#accountPassword'); if (serviceSelect) serviceSelect.value = ''; if (ownerInput) ownerInput.value = this.currentUser?.Username || this.currentUser?.username || ''; if (userInput) userInput.value = ''; if (passInput) passInput.value = ''; } } document.getElementById('accountModal').classList.add('open'); } openAppModal() { if (this.editingAppId === undefined) { document.getElementById('appName').value = ''; document.getElementById('appType').value = ''; document.getElementById('appStatus').value = 'online'; const iconInput = document.getElementById('appIcon'); const desc = document.getElementById('appDescription'); const url = document.getElementById('appUrl'); if (iconInput) iconInput.value = ''; if (desc) desc.value = ''; if (url) url.value = ''; } document.getElementById('appModal').classList.add('open'); } closeModals() { document.querySelectorAll('.modal-backdrop').forEach(modal => { modal.classList.remove('open'); }); } loadFromStorage(key) { const data = localStorage.getItem(key); return data ? JSON.parse(data) : null; } saveToStorage(key, data) { localStorage.setItem(key, JSON.stringify(data)); } } // Global modal close functions function closeAllModals() { document.querySelectorAll('.modal-backdrop').forEach(modal => { modal.classList.remove('open'); }); } function closeAccountModal() { document.getElementById('accountModal').classList.remove('open'); } function closeViewAccountModal() { document.getElementById('viewAccountModal').classList.remove('open'); } function closeDeleteAccountModal() { document.getElementById('deleteAccountModal').classList.remove('open'); } function closeAppModal() { document.getElementById('appModal').classList.remove('open'); } function closeViewAppModal() { document.getElementById('viewAppModal').classList.remove('open'); } function closeDeleteAppModal() { document.getElementById('deleteAppModal').classList.remove('open'); } // Initialize app when DOM is ready let app; document.addEventListener('DOMContentLoaded', () => { app = new AccountManager(); });