- Login page with animated floating stat cards - Executive dashboard with Chart.js KPIs and activity feed - Full asset registry (list, search, filter, bulk actions, QR) - Asset detail page with 6 tabs (financial, maintenance, history…) - 3-step asset creation wizard with category-specific fields - Inventory: stock overview, GRN, location tree, physical audit - Procurement: PR → approval → PO → GRN → asset lifecycle - Maintenance: Kanban board, PM schedule, AMC contracts - Reports: depreciation schedule, utilization, compliance gauge - User management: roles, permission matrix, session control - Settings: 8 config sections (org, depreciation, security, integrations) - Premium dark UI with CSS variables, Chart.js 4, toast system
310 lines
18 KiB
HTML
310 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||
<title>Users & Roles | AMS</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="css/styles.css">
|
||
</head>
|
||
<body>
|
||
<div class="app-layout">
|
||
<aside class="sidebar">
|
||
<div class="sidebar-logo"><div class="logo-icon">📦</div><div><div class="logo-title">AMS</div><div class="logo-sub">Asset Management</div></div></div>
|
||
<nav class="sidebar-nav">
|
||
<div class="nav-section-label">Overview</div>
|
||
<a href="dashboard.html" class="nav-item"><span class="nav-icon">📊</span> Dashboard</a>
|
||
<div class="nav-section-label">Assets</div>
|
||
<a href="assets.html" class="nav-item"><span class="nav-icon">📦</span> All Assets</a>
|
||
<a href="asset-create.html" class="nav-item"><span class="nav-icon">➕</span> Add Asset</a>
|
||
<div class="nav-section-label">Supply Chain</div>
|
||
<a href="inventory.html" class="nav-item"><span class="nav-icon">🏪</span> Inventory</a>
|
||
<a href="procurement.html" class="nav-item"><span class="nav-icon">🛒</span> Procurement</a>
|
||
<div class="nav-section-label">Operations</div>
|
||
<a href="maintenance.html" class="nav-item"><span class="nav-icon">🔧</span> Maintenance</a>
|
||
<a href="reports.html" class="nav-item"><span class="nav-icon">📈</span> Reports</a>
|
||
<div class="nav-section-label">Administration</div>
|
||
<a href="users.html" class="nav-item active"><span class="nav-icon">👥</span> Users & Roles</a>
|
||
<a href="settings.html" class="nav-item"><span class="nav-icon">⚙️</span> Settings</a>
|
||
</nav>
|
||
<div class="sidebar-footer"><div class="user-card"><div class="user-av">AS</div><div style="flex:1;min-width:0"><div class="user-name">Arjun Sharma</div><div class="user-role">Asset Manager</div></div></div></div>
|
||
</aside>
|
||
|
||
<div class="main-wrapper">
|
||
<header class="topbar">
|
||
<div class="topbar-left"><div class="topbar-title">Users & Access Control</div></div>
|
||
<div class="topbar-actions">
|
||
<button class="btn btn-primary btn-sm" onclick="openModal('inviteModal')">+ Invite User</button>
|
||
<a href="index.html" class="icon-btn">🚪</a>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="content">
|
||
<!-- Stats -->
|
||
<div class="grid-4 mb-4">
|
||
<div class="stat-card" style="--sc-color:var(--primary)"><div class="stat-icon" style="background:var(--primary-glow);color:var(--primary)">👥</div><div class="stat-label">Total Users</div><div class="stat-value" style="color:var(--primary)">10</div></div>
|
||
<div class="stat-card" style="--sc-color:var(--success)"><div class="stat-icon" style="background:var(--success-bg);color:var(--success)">✅</div><div class="stat-label">Active</div><div class="stat-value" style="color:var(--success)">9</div></div>
|
||
<div class="stat-card" style="--sc-color:var(--warning)"><div class="stat-icon" style="background:var(--warning-bg);color:var(--warning)">⚠️</div><div class="stat-label">Inactive / Suspended</div><div class="stat-value" style="color:var(--warning)">1</div></div>
|
||
<div class="stat-card" style="--sc-color:var(--info)"><div class="stat-icon" style="background:var(--info-bg);color:var(--info)">🔐</div><div class="stat-label">Roles Defined</div><div class="stat-value" style="color:var(--info)">6</div></div>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div class="tab-container">
|
||
<div class="tabs" data-group="usr">
|
||
<button class="tab-btn active" data-tab="tab-users" data-group="usr">👥 User List</button>
|
||
<button class="tab-btn" data-tab="tab-roles" data-group="usr">🔐 Roles & Permissions</button>
|
||
<button class="tab-btn" data-tab="tab-sessions" data-group="usr">🖥️ Active Sessions</button>
|
||
</div>
|
||
|
||
<!-- Users List -->
|
||
<div class="tab-content active" id="tab-users" data-group="usr">
|
||
<div class="filters-row mb-3">
|
||
<div class="search-wrap" style="max-width:280px"><span style="color:var(--text-muted)">🔍</span><input type="text" id="userSearch" placeholder="Search users…"></div>
|
||
<select class="filter-sel"><option>All Roles</option><option>Super Admin</option><option>Asset Manager</option><option>IT Head</option><option>Finance Head</option><option>Employee</option></select>
|
||
<select class="filter-sel"><option>All Departments</option><option>IT</option><option>Finance</option><option>HR</option><option>Operations</option><option>Marketing</option></select>
|
||
<select class="filter-sel"><option>All Statuses</option><option>Active</option><option>Inactive</option></select>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="data-table" id="userTable">
|
||
<thead><tr><th>User</th><th>Email</th><th>Role</th><th>Department</th><th>Status</th><th>Last Login</th><th>2FA</th><th>Actions</th></tr></thead>
|
||
<tbody id="userTbody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Roles & Permissions -->
|
||
<div class="tab-content" id="tab-roles" data-group="usr">
|
||
<div class="grid-2 mb-4" id="roleCards"></div>
|
||
<div class="card">
|
||
<div class="card-header"><span class="card-title">🔐 Permission Matrix</span><span style="font-size:12px;color:var(--text-muted)">✓ = Full Access · E = Edit Only · V = View Only · — = No Access</span></div>
|
||
<div class="card-body" style="overflow-x:auto">
|
||
<table class="perm-matrix">
|
||
<thead>
|
||
<tr>
|
||
<th>Module / Permission</th>
|
||
<th>Super Admin</th>
|
||
<th>Asset Manager</th>
|
||
<th>Dept Head</th>
|
||
<th>Finance Head</th>
|
||
<th>IT Head</th>
|
||
<th>Employee</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="permMatrix"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Active Sessions -->
|
||
<div class="tab-content" id="tab-sessions" data-group="usr">
|
||
<div class="alert alert-info mb-4">
|
||
<div class="alert-icon">ℹ️</div>
|
||
<div><div class="alert-title">7 active sessions currently</div><div class="alert-text">You can revoke suspicious sessions from below. All sessions auto-expire after 7 days of inactivity.</div></div>
|
||
</div>
|
||
<div class="table-wrapper">
|
||
<table class="data-table">
|
||
<thead><tr><th>User</th><th>Device / Browser</th><th>IP Address</th><th>Location</th><th>Login Time</th><th>Last Active</th><th>Action</th></tr></thead>
|
||
<tbody id="sessionTbody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Invite Modal -->
|
||
<div class="modal-overlay" id="inviteModal">
|
||
<div class="modal">
|
||
<div class="modal-header"><span class="modal-title">Invite New User</span><button class="modal-close">✕</button></div>
|
||
<div class="modal-body">
|
||
<div class="form-group"><label class="form-label">Full Name <span class="req">*</span></label><input class="form-input" placeholder="Employee full name"></div>
|
||
<div class="form-group"><label class="form-label">Email Address <span class="req">*</span></label><input type="email" class="form-input" placeholder="employee@acmecorp.com"></div>
|
||
<div class="form-row">
|
||
<div class="form-group"><label class="form-label">Department <span class="req">*</span></label><select class="form-select"><option>IT</option><option>Finance</option><option>HR</option><option>Operations</option><option>Marketing</option><option>Admin</option></select></div>
|
||
<div class="form-group"><label class="form-label">Role <span class="req">*</span></label><select class="form-select"><option>Employee</option><option>Asset Coordinator</option><option>Department Head</option><option>Asset Manager</option><option>Finance Head</option></select></div>
|
||
</div>
|
||
<div class="form-group"><label class="form-label">Welcome Message (optional)</label><textarea class="form-textarea" rows="2" placeholder="Custom message to include in invite email…"></textarea></div>
|
||
<div class="alert alert-info" style="margin-top:8px">
|
||
<div class="alert-icon">📧</div>
|
||
<div><div class="alert-title">Invite process</div><div class="alert-text">An invite email with a one-time setup link (expires in 48 hours) will be sent to the user.</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-ghost" onclick="closeModal('inviteModal')">Cancel</button>
|
||
<button class="btn btn-primary" onclick="sendInvite()">Send Invite</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Edit User Modal -->
|
||
<div class="modal-overlay" id="editUserModal">
|
||
<div class="modal">
|
||
<div class="modal-header"><span class="modal-title">Edit User</span><button class="modal-close">✕</button></div>
|
||
<div class="modal-body" id="editUserBody"></div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-ghost" onclick="closeModal('editUserModal')">Cancel</button>
|
||
<button class="btn btn-primary" onclick="saveUser()">Save Changes</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast-container" id="toastContainer"></div>
|
||
<script src="js/data.js"></script>
|
||
<script src="js/app.js"></script>
|
||
<script>
|
||
const roles = [
|
||
{ name:'Super Admin', desc:'Full system access. Can manage all settings, users, and data.', color:'var(--danger)', perms:'All Modules', users:1 },
|
||
{ name:'Asset Manager', desc:'Manages all assets, can approve PRs, view all reports.', color:'var(--primary)', perms:'Assets, Reports, Procurement', users:2 },
|
||
{ name:'Department Head', desc:'Can view department assets, approve team PRs, raise tickets.', color:'var(--cyan)', perms:'Dept Assets, PR Approval', users:5 },
|
||
{ name:'Finance Head', desc:'Approves PRs (budget stage), views depreciation & financial reports.',color:'var(--warning)', perms:'Financial Reports, PR Approval', users:1 },
|
||
{ name:'IT Head', desc:'Manages IT assets, tickets, AMC, and user provisioning.', color:'var(--success)', perms:'IT Assets, Users (IT Dept)', users:1 },
|
||
{ name:'Employee', desc:'View own assigned assets, raise requests, acknowledge assignments.', color:'var(--text-muted)',perms:'View Own Assets, Raise Tickets', users:20 }
|
||
];
|
||
|
||
const permRows = [
|
||
{ module:'View All Assets', sa:'✓',am:'✓',dh:'V',fi:'V',it:'V',em:'—' },
|
||
{ module:'Create / Edit Assets', sa:'✓',am:'✓',dh:'—',fi:'—',it:'E',em:'—' },
|
||
{ module:'Delete / Dispose', sa:'✓',am:'✓',dh:'—',fi:'—',it:'—',em:'—' },
|
||
{ module:'Assign Assets', sa:'✓',am:'✓',dh:'E',fi:'—',it:'E',em:'—' },
|
||
{ module:'Transfer Assets', sa:'✓',am:'✓',dh:'E',fi:'—',it:'E',em:'—' },
|
||
{ module:'View Financial Reports',sa:'✓',am:'✓',dh:'—',fi:'✓',it:'—',em:'—' },
|
||
{ module:'Approve Purchase Req.', sa:'✓',am:'✓',dh:'E',fi:'E',it:'—',em:'—' },
|
||
{ module:'Raise Tickets', sa:'✓',am:'✓',dh:'✓',fi:'V',it:'✓',em:'✓' },
|
||
{ module:'Manage Users', sa:'✓',am:'—',dh:'—',fi:'—',it:'V',em:'—' },
|
||
{ module:'System Settings', sa:'✓',am:'—',dh:'—',fi:'—',it:'—',em:'—' },
|
||
{ module:'View Own Assets', sa:'✓',am:'✓',dh:'✓',fi:'✓',it:'✓',em:'✓' }
|
||
];
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderUsers();
|
||
renderRoles();
|
||
renderPermMatrix();
|
||
renderSessions();
|
||
initTabs();
|
||
document.getElementById('userSearch').addEventListener('input', filterUsers);
|
||
});
|
||
|
||
function renderUsers(data) {
|
||
const users = data || AMS.users;
|
||
document.getElementById('userTbody').innerHTML = users.map(u=>`
|
||
<tr>
|
||
<td>
|
||
<div class="flex items-center gap-3">
|
||
<div style="width:34px;height:34px;border-radius:50%;background:${u.color}22;color:${u.color};display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;flex-shrink:0">${u.av}</div>
|
||
<div><div style="font-weight:600;color:var(--text-primary)">${u.name}</div><div style="font-size:11px;color:var(--text-muted)">${u.dept}</div></div>
|
||
</div>
|
||
</td>
|
||
<td style="font-size:12.5px">${u.email}</td>
|
||
<td><span class="badge badge-primary" style="font-size:10px">${u.role}</span></td>
|
||
<td>${u.dept}</td>
|
||
<td><span class="badge ${statusBadge(u.status)}">${u.status}</span></td>
|
||
<td style="font-size:12px;color:var(--text-muted)">${u.lastLogin}</td>
|
||
<td><span style="color:${u.id!=='u009'?'var(--success)':'var(--danger)'}">${u.id!=='u009'?'✅ Enabled':'❌ Disabled'}</span></td>
|
||
<td>
|
||
<div class="flex gap-1">
|
||
<button class="btn btn-ghost btn-sm" onclick="editUser('${u.id}')">✏️</button>
|
||
<button class="btn btn-ghost btn-sm" onclick="revokeUser('${u.id}')" title="Revoke sessions">🔒</button>
|
||
<button class="btn btn-ghost btn-sm danger" onclick="toggleStatus('${u.id}')" style="color:var(--danger)">${u.status==='Active'?'🚫':'✅'}</button>
|
||
</div>
|
||
</td>
|
||
</tr>`).join('');
|
||
}
|
||
|
||
function filterUsers() {
|
||
const q = document.getElementById('userSearch').value.toLowerCase();
|
||
const filtered = AMS.users.filter(u => !q || u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q) || u.role.toLowerCase().includes(q));
|
||
renderUsers(filtered);
|
||
}
|
||
|
||
function renderRoles() {
|
||
document.getElementById('roleCards').innerHTML = roles.map(r=>`
|
||
<div class="card">
|
||
<div class="card-body">
|
||
<div class="flex items-center gap-3 mb-3">
|
||
<div style="width:36px;height:36px;border-radius:var(--radius-md);background:${r.color}18;color:${r.color};display:flex;align-items:center;justify-content:center;font-size:16px">🔐</div>
|
||
<div><div style="font-weight:700;font-size:13.5px">${r.name}</div><div style="font-size:11px;color:var(--text-muted)">${r.users} user${r.users!==1?'s':''}</div></div>
|
||
</div>
|
||
<div style="font-size:12.5px;color:var(--text-secondary);margin-bottom:10px">${r.desc}</div>
|
||
<div style="font-size:11px;color:var(--text-muted)">Access: <span style="color:${r.color};font-weight:600">${r.perms}</span></div>
|
||
</div>
|
||
</div>`).join('');
|
||
}
|
||
|
||
function renderPermMatrix() {
|
||
const cols = ['sa','am','dh','fi','it','em'];
|
||
const colored = (v) => {
|
||
if(v==='✓') return `<span style="color:var(--success);font-size:15px">✓</span>`;
|
||
if(v==='E') return `<span class="badge badge-info" style="font-size:9px">Edit</span>`;
|
||
if(v==='V') return `<span class="badge badge-neutral" style="font-size:9px">View</span>`;
|
||
return `<span style="color:var(--text-muted)">—</span>`;
|
||
};
|
||
document.getElementById('permMatrix').innerHTML = permRows.map(r=>`
|
||
<tr>
|
||
<td>${r.module}</td>
|
||
${cols.map(c=>`<td>${colored(r[c])}</td>`).join('')}
|
||
</tr>`).join('');
|
||
}
|
||
|
||
function renderSessions() {
|
||
const sessions = [
|
||
{ user:'Arjun Sharma', device:'Chrome 124 / macOS', ip:'192.168.1.42', loc:'Bengaluru, IN', login:'Today 09:12', last:'2 min ago', current:true },
|
||
{ user:'Priya Kumar', device:'Chrome 124 / Windows',ip:'192.168.1.55', loc:'Bengaluru, IN', login:'Today 08:45', last:'15 min ago', current:false },
|
||
{ user:'Anita Singh', device:'Safari / iPhone 15', ip:'10.0.0.23', loc:'Bengaluru, IN', login:'Today 10:02', last:'5 min ago', current:false },
|
||
{ user:'Kavya Nair', device:'Firefox 125 / Ubuntu',ip:'192.168.1.78', loc:'Bengaluru, IN', login:'Today 09:55', last:'1 min ago', current:false },
|
||
{ user:'Deepak Joshi', device:'Edge 124 / Windows', ip:'192.168.1.90', loc:'Bengaluru, IN', login:'Yesterday', last:'18 hrs ago', current:false }
|
||
];
|
||
document.getElementById('sessionTbody').innerHTML = sessions.map(s=>`
|
||
<tr>
|
||
<td style="font-weight:600">${s.user} ${s.current?'<span class="badge badge-success" style="font-size:9px">You</span>':''}</td>
|
||
<td>${s.device}</td>
|
||
<td><code style="font-size:11px">${s.ip}</code></td>
|
||
<td>${s.loc}</td>
|
||
<td style="font-size:12px;color:var(--text-muted)">${s.login}</td>
|
||
<td style="font-size:12px;color:var(--text-muted)">${s.last}</td>
|
||
<td><button class="btn btn-${s.current?'ghost':'danger'} btn-sm" ${s.current?'disabled':''} onclick="revokeSession('${s.user}')">${s.current?'Current':'🔒 Revoke'}</button></td>
|
||
</tr>`).join('');
|
||
}
|
||
|
||
function editUser(id) {
|
||
const u = AMS.users.find(x=>x.id===id);
|
||
if(!u) return;
|
||
document.getElementById('editUserBody').innerHTML = `
|
||
<div class="form-row"><div class="form-group"><label class="form-label">Full Name</label><input class="form-input" value="${u.name}"></div>
|
||
<div class="form-group"><label class="form-label">Email</label><input class="form-input" type="email" value="${u.email}" readonly></div></div>
|
||
<div class="form-row"><div class="form-group"><label class="form-label">Role</label><select class="form-select"><option ${u.role==='Asset Manager'?'selected':''}>Asset Manager</option><option ${u.role==='IT Head'?'selected':''}>IT Head</option><option ${u.role==='Finance Head'?'selected':''}>Finance Head</option><option>Employee</option><option>Super Admin</option></select></div>
|
||
<div class="form-group"><label class="form-label">Department</label><select class="form-select"><option>IT</option><option>Finance</option><option>HR</option><option>Operations</option><option>Marketing</option><option>Admin</option></select></div></div>
|
||
<div class="form-group"><label class="form-label">Status</label><select class="form-select"><option ${u.status==='Active'?'selected':''}>Active</option><option ${u.status==='Inactive'?'selected':''}>Inactive</option></select></div>`;
|
||
openModal('editUserModal');
|
||
}
|
||
|
||
function saveUser() { closeModal('editUserModal'); showToast('User Updated','Changes saved','success'); }
|
||
|
||
function revokeUser(id) {
|
||
confirmAction('Revoke Sessions','This will log out all active sessions for this user. They will need to login again.',()=>{
|
||
showToast('Sessions Revoked','All sessions for user revoked','warning');
|
||
});
|
||
}
|
||
|
||
function toggleStatus(id) {
|
||
const u = AMS.users.find(x=>x.id===id);
|
||
if(!u) return;
|
||
u.status = u.status==='Active' ? 'Inactive' : 'Active';
|
||
renderUsers();
|
||
showToast('Status Updated',`${u.name} is now ${u.status}`, u.status==='Active'?'success':'warning');
|
||
}
|
||
|
||
function revokeSession(user) { showToast('Session Revoked',`${user}'s session has been terminated`,'warning'); }
|
||
|
||
function sendInvite() {
|
||
closeModal('inviteModal');
|
||
showToast('Invite Sent','Invitation email sent with 48-hour setup link','success');
|
||
}
|
||
|
||
function confirmAction(title, msg, cb) {
|
||
if(confirm(msg) && cb) cb();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|