done manager account

This commit is contained in:
2026-03-31 14:14:01 +07:00
parent 900a569c51
commit 58dbefa155
4 changed files with 194 additions and 343 deletions

View File

@@ -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 {
<th class="px-4 py-2.5 text-[10px] font-bold uppercase tracking-wider text-slate-500 text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
<tbody class="divide-y divide-slate-100 accounts-table-body">
${filteredAccounts.map(acc => `
<tr class="hover:bg-slate-50/80 transition-colors group account-row" data-account-id="${acc.AccountId}">
<td class="px-4 py-3 text-sm font-medium text-slate-900">${acc.Email || '-'}</td>
@@ -447,7 +511,7 @@ class AccountManager {
<th class="px-6 py-2.5 text-[10px] font-bold text-on-surface-variant uppercase tracking-widest text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-outline-variant/5">
<tbody class="divide-y divide-outline-variant/5 apps-table-body">
${filteredApps.map(app => `
<tr class="hover:bg-surface-container-low/30 transition-colors group">
<td class="px-6 py-3">
@@ -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 => `
<tr class="hover:bg-slate-50/80 transition-colors group account-row" data-account-id="${acc.AccountId}">
<td class="px-4 py-3 text-sm font-medium text-slate-900">${acc.Email || '-'}</td>
<td class="px-4 py-3 text-sm text-slate-600">${acc.AccountUsername || '-'}</td>
<td class="px-4 py-3 text-sm">
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs font-semibold">${acc.AppName || '-'}</span>
</td>
<td class="px-4 py-3 text-right">
<button class="p-1.5 text-slate-400 hover:text-slate-600 transition-colors view-account" data-account-id="${acc.AccountId}" title="View Details">
<span class="material-symbols-outlined text-lg">info</span>
</button>
<button class="p-1.5 text-slate-400 hover:text-primary transition-colors edit-account" data-account-id="${acc.AccountId}">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
<button class="p-1.5 text-slate-400 hover:text-error transition-colors delete-account" data-account-id="${acc.AccountId}">
<span class="material-symbols-outlined text-lg">delete</span>
</button>
</td>
</tr>
`).join('');
this.setupAccountRowListeners();
}
renderApplicationsTableBody() {
const tbody = document.querySelector('.apps-table-body');
if (!tbody) return;
const filteredApps = this.getFilteredApplications();
tbody.innerHTML = filteredApps.map(app => `
<tr class="hover:bg-surface-container-low/30 transition-colors group">
<td class="px-6 py-3">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded bg-surface-container-highest flex items-center justify-center text-primary">
<span class="material-symbols-outlined text-sm">${app.Icon || 'apps'}</span>
</div>
<span class="font-bold text-sm text-on-surface">${app.Name}</span>
</div>
</td>
<td class="px-6 py-3">
<span class="px-2 py-0.5 rounded-full text-[9px] font-black uppercase bg-surface-container-highest text-on-surface-variant">${app.Type}</span>
</td>
<td class="px-6 py-3 text-sm text-on-surface-variant max-w-xs truncate" title="${app.Description || ''}">${app.Description || '-'}</td>
<td class="px-6 py-3 text-sm text-primary max-w-xs truncate">${(app.Url || app.url) ? `<a href="${app.Url || app.url}" target="_blank" class="underline">${app.Url || app.url}</a>` : '-'}</td>
<td class="px-6 py-3">
<div class="flex items-center gap-1.5">
<div class="w-1.5 h-1.5 rounded-full ${(app.Status || app.status) === 'online' ? 'bg-primary' : 'bg-error'} ring-2 ${(app.Status || app.status) === 'online' ? 'ring-primary/20' : 'ring-error/20'}"></div>
<span class="text-xs font-medium ${(app.Status || app.status) === 'online' ? 'text-on-primary-fixed-variant' : 'text-error'}">${(app.Status || app.status) === 'online' ? 'Online' : 'Offline'}</span>
</div>
</td>
<td class="px-6 py-3 text-right">
<div class="flex items-center justify-end gap-1">
<button class="p-1.5 text-on-surface-variant hover:text-on-surface transition-colors view-app" data-app-id="${app.AppId}" title="View Details">
<span class="material-symbols-outlined text-lg">info</span>
</button>
<button class="p-1.5 text-on-surface-variant hover:text-primary transition-colors edit-app" data-app-id="${app.AppId}">
<span class="material-symbols-outlined text-lg">edit</span>
</button>
<button class="p-1.5 text-on-surface-variant hover:text-error transition-colors delete-app" data-app-id="${app.AppId}">
<span class="material-symbols-outlined text-lg">delete</span>
</button>
</div>
</td>
</tr>
`).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,6 +732,7 @@ 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) {
@@ -603,7 +740,7 @@ class AccountManager {
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,25 +870,45 @@ 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');
}
}

View File

@@ -90,163 +90,9 @@
.modal-backdrop.open .modal-content { transform: scale(1); }
</style>
</head>
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
<div class="flex flex-col h-full py-6">
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">Robot Account</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<nav class="flex-1 px-3 space-y-1">
<a href="./index.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="./applications.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="./accounts.html" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold group transition-all cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<div class="px-6 pt-4 border-t border-outline-variant/10">
<div class="text-[10px] font-bold text-on-surface-variant/40 uppercase tracking-widest">v1.0.0</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col h-screen min-w-0">
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
<div class="flex flex-col">
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
</div>
</div>
<button id="logoutBtn" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 transition-colors" title="Logout">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<div id="mainContent" class="flex-1 overflow-hidden"></div>
<div id="modalsContainer">
<!-- Add Account Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="accountModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Add New Account</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeAccountModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<form id="accountForm" class="p-6 space-y-4">
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Service</label>
<select id="accountService" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required>
<option value="">Select a service</option>
</select>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Owner Name</label>
<input type="text" id="accountOwner" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="John Doe">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Username</label>
<input type="text" id="accountUsername" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="username">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Password</label>
<input type="password" id="accountPassword" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="••••••••">
</div>
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeAccountModal()">Cancel</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Save Account</button>
</div>
</form>
</div>
</div>
<!-- View Account Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="viewAccountModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Account Details</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeViewAccountModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<div class="p-6 space-y-4">
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Service</label>
<div id="viewAccountService" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Owner Name</label>
<div id="viewAccountOwner" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Username</label>
<div id="viewAccountUsername" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Password</label>
<div class="flex items-center gap-2">
<div id="viewAccountPassword" class="flex-1 border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">••••••••</div>
<button type="button" class="p-2 rounded-lg hover:bg-slate-100 text-slate-400 transition-colors toggle-password">
<span class="material-symbols-outlined text-lg" id="toggleIcon">visibility</span>
</button>
</div>
</div>
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeViewAccountModal()">Close</button>
<button type="button" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold edit-account-from-view">Edit</button>
</div>
</div>
</div>
</div>
<!-- Delete Account Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="deleteAccountModal">
<div class="modal-content w-full max-w-md bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 bg-red-50 flex items-center gap-3">
<span class="material-symbols-outlined text-red-600 text-2xl">warning</span>
<h3 class="text-base font-extrabold text-red-700">Delete Account</h3>
</div>
<div class="p-6">
<p class="text-sm text-slate-600 mb-6">Are you sure you want to delete the account for <strong id="deleteAccountUsername">-</strong>? This action cannot be undone.</p>
<div class="flex gap-3">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeDeleteAccountModal()">Cancel</button>
<button type="button" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-xs font-bold confirm-delete-account">Delete</button>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="../js/app.js"></script>
<body>
<script>
document.addEventListener('DOMContentLoaded', async () => {
if (app) {
const mainContent = document.getElementById('mainContent');
const modalsContainer = document.getElementById('modalsContainer');
await app.initPromise;
if (mainContent) {
mainContent.innerHTML = app.getAccountsContent();
app.setupAccountRowListeners();
app.setupAddButtonListeners();
app.setupFilters();
}
}
});
window.location.href = './index.html#accounts';
</script>
</body>
</html>

View File

@@ -90,150 +90,9 @@
.modal-backdrop.open .modal-content { transform: scale(1); }
</style>
</head>
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
<div class="flex flex-col h-full py-6">
<div class="px-6 mb-8">
<div class="text-lg font-black text-slate-900 dark:text-slate-50 tracking-tight leading-none">Robot Account</div>
<div class="text-[10px] uppercase tracking-widest text-on-surface-variant mt-1.5 font-bold">Admin Console</div>
</div>
<nav class="flex-1 px-3 space-y-1">
<a href="./index.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="./applications.html" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold group transition-all cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="./accounts.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
</nav>
<div class="px-6 pt-4 border-t border-outline-variant/10">
<div class="text-[10px] font-bold text-on-surface-variant/40 uppercase tracking-widest">v1.0.0</div>
</div>
</div>
</aside>
<main class="flex-1 flex flex-col h-screen min-w-0">
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
<div class="flex flex-col">
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
</div>
</div>
<button id="logoutBtn" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-red-100 dark:hover:bg-red-950 hover:text-red-700 dark:hover:text-red-300 transition-colors" title="Logout">
<span class="material-symbols-outlined">logout</span>
</button>
</div>
</header>
<div id="mainContent" class="flex-1 overflow-hidden"></div>
<div id="modalsContainer">
<!-- Add App Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="appModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Add New Application</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeAppModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<form id="appForm" class="p-6 space-y-4">
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Application Name</label>
<input type="text" id="appName" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="AWS Services">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Type</label>
<input type="text" id="appType" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3" required placeholder="Cloud">
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Status</label>
<select id="appStatus" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3">
<option value="online">Online</option>
<option value="offline">Offline</option>
</select>
</div>
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeAppModal()">Cancel</button>
<button type="submit" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold">Save Application</button>
</div>
</form>
</div>
</div>
<!-- View App Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="viewAppModal">
<div class="modal-content w-full max-w-lg bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 flex items-center justify-between bg-slate-50">
<h3 class="text-base font-extrabold text-slate-900">Application Details</h3>
<button class="p-1.5 rounded-lg hover:bg-slate-200 text-slate-400 transition-colors" onclick="closeViewAppModal()">
<span class="material-symbols-outlined">close</span>
</button>
</div>
<div class="p-6 space-y-4">
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Application Name</label>
<div id="viewAppName" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Type</label>
<div id="viewAppType" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div>
<label class="text-[10px] font-bold uppercase text-slate-500 tracking-widest block mb-1">Status</label>
<div id="viewAppStatus" class="w-full border border-slate-200 rounded-lg text-sm py-2.5 px-3 bg-slate-50 text-slate-600">-</div>
</div>
<div class="flex gap-3 pt-4">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeViewAppModal()">Close</button>
<button type="button" class="flex-1 px-4 py-2 bg-primary hover:bg-primary-dim text-on-primary rounded-lg text-xs font-bold edit-app-from-view">Edit</button>
</div>
</div>
</div>
</div>
<!-- Delete App Modal -->
<div class="modal-backdrop fixed inset-0 z-[100] flex items-center justify-center bg-black/40 backdrop-blur-sm" id="deleteAppModal">
<div class="modal-content w-full max-w-md bg-white rounded-xl shadow-2xl border border-slate-200 overflow-hidden m-4">
<div class="px-6 py-4 border-b border-slate-100 bg-red-50 flex items-center gap-3">
<span class="material-symbols-outlined text-red-600 text-2xl">warning</span>
<h3 class="text-base font-extrabold text-red-700">Delete Application</h3>
</div>
<div class="p-6">
<p class="text-sm text-slate-600 mb-6">Are you sure you want to delete <strong id="deleteAppName">-</strong>? This action cannot be undone.</p>
<div class="flex gap-3">
<button type="button" class="flex-1 px-4 py-2 text-xs font-bold text-slate-600 border border-slate-200 rounded-lg" onclick="closeDeleteAppModal()">Cancel</button>
<button type="button" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-xs font-bold confirm-delete-app">Delete</button>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="../js/app.js"></script>
<body>
<script>
document.addEventListener('DOMContentLoaded', async () => {
if (app) {
const mainContent = document.getElementById('mainContent');
await app.initPromise;
if (mainContent) {
mainContent.innerHTML = app.getApplicationsContent();
app.setupAccountRowListeners();
app.setupAddButtonListeners();
app.setupFilters();
}
}
});
window.location.href = './index.html#applications';
</script>
</body>
</html>

View File

@@ -124,15 +124,15 @@
</div>
<!-- Primary Nav -->
<nav class="flex-1 px-3 space-y-1">
<a href="#" onclick="location.reload(); return false;" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold group transition-all cursor-pointer">
<a href="#dashboard" data-nav="dashboard" class="flex items-center gap-3 px-3 py-2 border-l-4 border-blue-600 bg-slate-200/80 dark:bg-slate-800 text-slate-900 dark:text-slate-50 font-bold group transition-all cursor-pointer">
<span class="material-symbols-outlined">dashboard</span>
<span>Dashboard</span>
</a>
<a href="./applications.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<a href="#applications" data-nav="applications" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">apps</span>
<span>Applications</span>
</a>
<a href="./accounts.html" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<a href="#accounts" data-nav="accounts" class="flex items-center gap-3 px-3 py-2 text-slate-600 dark:text-slate-400 hover:text-slate-900 hover:bg-slate-200/50 transition-all group cursor-pointer">
<span class="material-symbols-outlined">manage_accounts</span>
<span>Accounts</span>
</a>
@@ -149,10 +149,7 @@
<!-- TopAppBar -->
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
<div class="flex items-center gap-4 flex-1">
<div class="flex items-center bg-surface-container-high px-3 py-1.5 rounded-full w-64 group focus-within:ring-2 ring-primary/20 transition-all">
<span class="material-symbols-outlined text-on-surface-variant text-base">search</span>
<input id="searchInput" class="bg-transparent border-none focus:ring-0 text-xs w-full placeholder:text-on-surface-variant/60 py-0" placeholder="Search resources..." type="text"/>
</div>
</div>
<div class="flex items-center gap-4">
<div class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800">