// 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 // Render dashboard content (only for index.html) const mainContent = document.getElementById('mainContent'); if (mainContent && window.location.pathname.endsWith('index.html')) { mainContent.innerHTML = this.renderDashboard(); } } 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 response = await fetch('../modals.html'); const modalsHTML = await response.text(); const modalsContainer = document.getElementById('modalsContainer'); if (modalsContainer) { modalsContainer.innerHTML = modalsHTML; // Re-attach event listeners after modals are loaded setTimeout(() => { this.setupAccountRowListeners(); this.setupFormListeners(); }, 50); } } catch (error) { console.error('Lỗi load modals:', error); } } 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'}
`; } 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 || '-'; document.getElementById('viewAccountPassword').textContent = '••••••••'; document.getElementById('viewAccountPassword').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 isVisible = passwordEl.dataset.visible === 'true'; if (isVisible) { passwordEl.textContent = '••••••••'; passwordEl.dataset.visible = 'false'; toggleIcon.textContent = 'visibility'; } else { passwordEl.textContent = this.currentViewAccount?.AccountPassword || ''; 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.refreshAccountsUI(); }); } const accountSearch = document.getElementById('accountSearch'); if (accountSearch) { accountSearch.value = this.accountSearchTerm; accountSearch.addEventListener('input', (e) => { this.accountSearchTerm = e.target.value.toLowerCase(); this.refreshAccountsUI(); }); } const appSearch = document.getElementById('appSearch'); if (appSearch) { appSearch.value = this.applicationSearchTerm; appSearch.addEventListener('input', (e) => { this.applicationSearchTerm = e.target.value.toLowerCase(); this.refreshApplicationsUI(); }); } } 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(); const mainContent = document.getElementById('mainContent'); if (mainContent) { mainContent.innerHTML = this.getAccountsContent(); this.setupAccountRowListeners(); this.setupAddButtonListeners(); this.setupFilters(); } } 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(); const mainContent = document.getElementById('mainContent'); if (mainContent) { mainContent.innerHTML = this.getApplicationsContent(); this.setupAccountRowListeners(); this.setupAddButtonListeners(); this.setupFilters(); } } 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(); });