diff --git a/public/js/app.js b/public/js/app.js
index 10d138b..ad72027 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -72,11 +72,50 @@ class AccountManager {
await this.fetchAccounts();
this.setupEventListeners();
this.loadModals(); // Load modals từ file riêng
- // Render dashboard content (only for index.html)
+ // 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 && window.location.pathname.endsWith('index.html')) {
+ 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() {
@@ -103,22 +142,47 @@ class AccountManager {
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 modalsContainer = document.getElementById('modalsContainer');
- if (modalsContainer) {
- modalsContainer.innerHTML = modalsHTML;
- // Re-attach event listeners after modals are loaded
- setTimeout(() => {
- this.setupAccountRowListeners();
- this.setupFormListeners();
- }, 50);
+
+ 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 => {
@@ -341,7 +405,7 @@ class AccountManager {
${filteredApps.map(app => `
@@ -493,6 +557,76 @@ class AccountManager {
`;
}
+ 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 || '-'}
+
+
+
+ info
+
+
+ edit
+
+
+ delete
+
+
+
+ `).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'}
+
+
+
+
+
+ info
+
+
+ edit
+
+
+ delete
+
+
+
+
+ `).join('');
+ this.setupAccountRowListeners();
+ }
+
setupAccountRowListeners() {
// View Account listeners
document.querySelectorAll('.view-account').forEach(btn => {
@@ -504,8 +638,10 @@ class AccountManager {
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';
+ 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');
});
@@ -596,14 +732,15 @@ class AccountManager {
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 = this.currentViewAccount?.AccountPassword || '';
+ passwordEl.textContent = storedPwd || '(no password stored)';
passwordEl.dataset.visible = 'true';
toggleIcon.textContent = 'visibility_off';
}
@@ -733,26 +870,46 @@ class AccountManager {
serviceFilter.value = this.accountServiceFilter || '';
serviceFilter.addEventListener('change', (e) => {
this.accountServiceFilter = e.target.value;
- this.refreshAccountsUI();
+ this.renderAccountsTableBody();
});
}
const accountSearch = document.getElementById('accountSearch');
if (accountSearch) {
accountSearch.value = this.accountSearchTerm;
- accountSearch.addEventListener('input', (e) => {
- this.accountSearchTerm = e.target.value.toLowerCase();
- this.refreshAccountsUI();
- });
+ 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;
- appSearch.addEventListener('input', (e) => {
- this.applicationSearchTerm = e.target.value.toLowerCase();
- this.refreshApplicationsUI();
- });
+ 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';
+ });
}
}
@@ -820,12 +977,8 @@ class AccountManager {
async refreshAccountsUI() {
await this.fetchAccounts();
- const mainContent = document.getElementById('mainContent');
- if (mainContent) {
- mainContent.innerHTML = this.getAccountsContent();
- this.setupAccountRowListeners();
- this.setupAddButtonListeners();
- this.setupFilters();
+ if (this.currentPage === 'accounts') {
+ this.renderView('accounts');
}
}
@@ -865,12 +1018,8 @@ class AccountManager {
async refreshApplicationsUI() {
await this.fetchApplications();
- const mainContent = document.getElementById('mainContent');
- if (mainContent) {
- mainContent.innerHTML = this.getApplicationsContent();
- this.setupAccountRowListeners();
- this.setupAddButtonListeners();
- this.setupFilters();
+ if (this.currentPage === 'applications') {
+ this.renderView('applications');
}
}
diff --git a/public/pages/accounts.html b/public/pages/accounts.html
index bf67bc5..621c16b 100644
--- a/public/pages/accounts.html
+++ b/public/pages/accounts.html
@@ -90,163 +90,9 @@
.modal-backdrop.open .modal-content { transform: scale(1); }
-
-
-
-
-
-
-
-
account_circle
-
- User Account
- Administrator
-
-
-
- logout
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
-
-
-
Password
-
-
••••••••
-
- visibility
-
-
-
-
- Close
- Edit
-
-
-
-
-
-
-
-
-
- warning
-
-
-
-
Are you sure you want to delete the account for - ? This action cannot be undone.
-
- Cancel
- Delete
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/public/pages/applications.html b/public/pages/applications.html
index 74ff2bf..75de4aa 100644
--- a/public/pages/applications.html
+++ b/public/pages/applications.html
@@ -90,150 +90,9 @@
.modal-backdrop.open .modal-content { transform: scale(1); }
-
-
-
-
-
-
-
-
account_circle
-
- User Account
- Administrator
-
-
-
- logout
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- close
-
-
-
-
-
-
-
- Close
- Edit
-
-
-
-
-
-
-
-
-
- warning
-
-
-
-
Are you sure you want to delete - ? This action cannot be undone.
-
- Cancel
- Delete
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/public/pages/index.html b/public/pages/index.html
index 12d4c6e..a6ab9a6 100644
--- a/public/pages/index.html
+++ b/public/pages/index.html
@@ -124,15 +124,15 @@
-
+
dashboard
Dashboard
-
+
apps
Applications
-
+
manage_accounts
Accounts
@@ -149,10 +149,7 @@