622 lines
28 KiB
HTML
Executable File
622 lines
28 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="robots" content="noindex, nofollow, noarchive">
|
|
<title>Admin Users - MSPE Admin</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
|
|
<link rel="stylesheet" href="/admin/css/admin.css">
|
|
</head>
|
|
<body>
|
|
<div class="admin-wrapper">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar" id="sidebar">
|
|
<div class="sidebar-header">
|
|
<a href="/admin/dashboard.html" class="sidebar-logo">
|
|
<i class="fas fa-cube"></i>
|
|
<span>MSPE</span>
|
|
</a>
|
|
<button class="sidebar-toggle" id="sidebar-toggle">
|
|
<i class="fas fa-bars"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<nav class="sidebar-nav">
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Main</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/dashboard.html" class="nav-link">
|
|
<i class="fas fa-th-large"></i>
|
|
<span>Dashboard</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Content</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/pages.html" class="nav-link">
|
|
<i class="fas fa-file-alt"></i>
|
|
<span>Pages</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/services.html" class="nav-link">
|
|
<i class="fas fa-concierge-bell"></i>
|
|
<span>Services</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/portfolio.html" class="nav-link">
|
|
<i class="fas fa-briefcase"></i>
|
|
<span>Portfolio</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/news.html" class="nav-link">
|
|
<i class="fas fa-newspaper"></i>
|
|
<span>News & Events</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/testimonials.html" class="nav-link">
|
|
<i class="fas fa-quote-right"></i>
|
|
<span>Testimonials</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/team.html" class="nav-link">
|
|
<i class="fas fa-users"></i>
|
|
<span>Team</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Media</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/media.html" class="nav-link">
|
|
<i class="fas fa-images"></i>
|
|
<span>Media Library</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Inquiries</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/messages.html" class="nav-link">
|
|
<i class="fas fa-envelope"></i>
|
|
<span>Messages</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/subscribers.html" class="nav-link">
|
|
<i class="fas fa-bell"></i>
|
|
<span>Subscribers</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/bookings.html" class="nav-link">
|
|
<i class="fas fa-calendar-check"></i>
|
|
<span>Bookings</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a href="/admin/availability.html" class="nav-link">
|
|
<i class="fas fa-clock"></i>
|
|
<span>Availability</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<span class="nav-section-title">Settings</span>
|
|
<ul class="nav-menu">
|
|
<li class="nav-item">
|
|
<a href="/admin/settings.html" class="nav-link">
|
|
<i class="fas fa-cog"></i>
|
|
<span>Site Settings</span>
|
|
</a>
|
|
</li>
|
|
<li class="nav-item active">
|
|
<a href="/admin/users.html" class="nav-link">
|
|
<i class="fas fa-user-shield"></i>
|
|
<span>Admin Users</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="sidebar-footer">
|
|
<a href="/index.html" class="view-site-btn" target="_blank">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
<span>View Website</span>
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main-content">
|
|
<!-- Header -->
|
|
<header class="admin-header">
|
|
<div class="header-left">
|
|
<button class="mobile-toggle" id="mobile-toggle">
|
|
<i class="fas fa-bars"></i>
|
|
</button>
|
|
<h1 class="page-title">Admin Users</h1>
|
|
</div>
|
|
|
|
<div class="header-right">
|
|
<button class="btn btn-primary" id="add-user-btn">
|
|
<i class="fas fa-user-plus"></i> Add User
|
|
</button>
|
|
|
|
<div class="header-user">
|
|
<div class="user-avatar">
|
|
<img src="https://ui-avatars.com/api/?name=Admin&background=0ea5e9&color=fff" alt="Admin">
|
|
</div>
|
|
<div class="user-info">
|
|
<span class="user-name">Admin</span>
|
|
<span class="user-role">Administrator</span>
|
|
</div>
|
|
<div class="user-dropdown">
|
|
<button class="dropdown-toggle">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a href="/admin/settings.html"><i class="fas fa-cog"></i> Settings</a></li>
|
|
<li class="divider"></li>
|
|
<li><a href="#" id="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Content -->
|
|
<div class="content-wrapper">
|
|
<div class="content-header">
|
|
<p class="content-description">Manage admin user accounts and permissions</p>
|
|
</div>
|
|
|
|
<!-- Users Table -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-users-cog"></i> User Accounts</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>User</th>
|
|
<th>Email</th>
|
|
<th>Role</th>
|
|
<th>Last Login</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td colspan="6" style="text-align:center;padding:2rem;color:var(--gray-400);">
|
|
<i class="fas fa-spinner fa-spin"></i> Loading users...
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Roles & Permissions -->
|
|
<div class="card" style="margin-top: 2rem;">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-user-tag"></i> Roles & Permissions</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="roles-grid">
|
|
<div class="role-card">
|
|
<div class="role-header">
|
|
<span class="role-badge admin">Administrator</span>
|
|
<span class="role-count" id="count-admin">— users</span>
|
|
</div>
|
|
<ul class="permissions-list">
|
|
<li><i class="fas fa-check"></i> Full system access</li>
|
|
<li><i class="fas fa-check"></i> Manage all users</li>
|
|
<li><i class="fas fa-check"></i> Site settings</li>
|
|
<li><i class="fas fa-check"></i> Delete content</li>
|
|
<li><i class="fas fa-check"></i> View analytics</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="role-card">
|
|
<div class="role-header">
|
|
<span class="role-badge editor">Editor</span>
|
|
<span class="role-count" id="count-editor">— users</span>
|
|
</div>
|
|
<ul class="permissions-list">
|
|
<li><i class="fas fa-check"></i> Create/edit content</li>
|
|
<li><i class="fas fa-check"></i> Publish content</li>
|
|
<li><i class="fas fa-check"></i> Manage media</li>
|
|
<li><i class="fas fa-check"></i> View messages</li>
|
|
<li><i class="fas fa-times"></i> Site settings</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Activity Log -->
|
|
<div class="card" style="margin-top: 2rem;">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-history"></i> Recent Activity</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="activity-timeline" id="activity-timeline">
|
|
<div class="empty-state" style="padding: 2rem; text-align: center;">
|
|
<i class="fas fa-history" style="font-size: 2rem; opacity: 0.3; margin-bottom: 0.5rem; display:block;"></i>
|
|
<p>Activity is recorded in the server error log under <code>[MSPE_AUDIT]</code> entries.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Add/Edit User Modal -->
|
|
<div class="modal" id="user-modal">
|
|
<div class="modal-backdrop"></div>
|
|
<div class="modal-container">
|
|
<div class="modal-header">
|
|
<h2 id="modal-title">Add Admin User</h2>
|
|
<button class="modal-close" id="user-modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="user-form">
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Full Name *</label>
|
|
<input type="text" class="form-control" name="name" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Username *</label>
|
|
<input type="text" class="form-control" name="username" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Email Address *</label>
|
|
<input type="email" class="form-control" name="email" required>
|
|
</div>
|
|
|
|
<div class="form-row" id="password-fields">
|
|
<div class="form-group">
|
|
<label>Password *</label>
|
|
<input type="password" class="form-control" name="password" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Confirm Password *</label>
|
|
<input type="password" class="form-control" name="password_confirm" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Role *</label>
|
|
<select class="form-select" name="role" required>
|
|
<option value="editor">Editor</option>
|
|
<option value="admin">Administrator</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Status</label>
|
|
<select class="form-select" name="status">
|
|
<option value="active">Active</option>
|
|
<option value="inactive">Inactive</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="send_welcome" checked>
|
|
Send welcome email with login details
|
|
</label>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-outline" id="user-cancel-btn">Cancel</button>
|
|
<button class="btn btn-primary" id="user-save-btn">
|
|
<i class="fas fa-save"></i> Save User
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Change Password Modal -->
|
|
<div class="modal" id="password-modal">
|
|
<div class="modal-backdrop"></div>
|
|
<div class="modal-container modal-sm">
|
|
<div class="modal-header">
|
|
<h2>Change Password</h2>
|
|
<button class="modal-close" id="password-modal-close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="password-form">
|
|
<div class="form-group">
|
|
<label>New Password *</label>
|
|
<input type="password" class="form-control" name="new_password" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Confirm New Password *</label>
|
|
<input type="password" class="form-control" name="confirm_password" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="force_logout">
|
|
Force logout from all sessions
|
|
</label>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-outline" id="password-cancel-btn">Cancel</button>
|
|
<button class="btn btn-primary" id="password-save-btn">
|
|
<i class="fas fa-key"></i> Update Password
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/admin/js/admin.js"></script>
|
|
<script>
|
|
let allUsers = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setupEventListeners();
|
|
loadUsers();
|
|
});
|
|
|
|
function setupEventListeners() {
|
|
document.getElementById('add-user-btn')?.addEventListener('click', openAddUserModal);
|
|
document.getElementById('user-modal-close')?.addEventListener('click', closeModal);
|
|
document.getElementById('user-cancel-btn')?.addEventListener('click', closeModal);
|
|
document.getElementById('user-save-btn')?.addEventListener('click', saveUser);
|
|
|
|
document.getElementById('password-modal-close')?.addEventListener('click', closePasswordModal);
|
|
document.getElementById('password-cancel-btn')?.addEventListener('click', closePasswordModal);
|
|
document.getElementById('password-save-btn')?.addEventListener('click', updatePassword);
|
|
|
|
const userBackdrop = document.querySelector('#user-modal .modal-backdrop');
|
|
userBackdrop?.addEventListener('click', closeModal);
|
|
|
|
const passwordBackdrop = document.querySelector('#password-modal .modal-backdrop');
|
|
passwordBackdrop?.addEventListener('click', closePasswordModal);
|
|
|
|
const tbody = document.querySelector('tbody');
|
|
tbody?.addEventListener('click', function(event) {
|
|
const actionBtn = event.target.closest('[data-action]');
|
|
if (!actionBtn) return;
|
|
|
|
const { action, id } = actionBtn.dataset;
|
|
if (action === 'edit' && id) {
|
|
editUser(id);
|
|
} else if (action === 'password' && id) {
|
|
changePassword(id);
|
|
} else if (action === 'delete' && id) {
|
|
deleteUser(id);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function loadUsers() {
|
|
try {
|
|
const response = await MSPE.API.get('users.php');
|
|
if (response && response.success) {
|
|
allUsers = response.data;
|
|
renderUsers(allUsers);
|
|
updateRoleCounts(allUsers);
|
|
} else {
|
|
document.querySelector('tbody').innerHTML = '<tr><td colspan="6" class="text-center">Failed to load users</td></tr>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading users:', error);
|
|
document.querySelector('tbody').innerHTML = '<tr><td colspan="6" class="text-center">Failed to load users</td></tr>';
|
|
}
|
|
}
|
|
|
|
function updateRoleCounts(users) {
|
|
const counts = users.reduce((acc, u) => { acc[u.role] = (acc[u.role] || 0) + 1; return acc; }, {});
|
|
const adminCount = document.getElementById('count-admin');
|
|
const editorCount = document.getElementById('count-editor');
|
|
if (adminCount) adminCount.textContent = `${counts['admin'] || 0} user(s)`;
|
|
if (editorCount) editorCount.textContent = `${counts['editor'] || 0} user(s)`;
|
|
}
|
|
|
|
function renderUsers(users) {
|
|
const tbody = document.querySelector('tbody');
|
|
if (users.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center">No users found</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = users.map(user => {
|
|
const safeName = MSPE.escapeHtml(user.name);
|
|
const safeUsername = MSPE.escapeHtml(user.username);
|
|
const safeEmail = MSPE.escapeHtml(user.email);
|
|
const safeRole = MSPE.escapeHtml(user.role);
|
|
const safeStatus = MSPE.escapeHtml(user.status);
|
|
const safeId = MSPE.escapeHtml(user.id);
|
|
return `
|
|
<tr>
|
|
<td>
|
|
<div class="user-info">
|
|
<img src="https://ui-avatars.com/api/?name=${encodeURIComponent(user.name)}&background=random" alt="${safeName}">
|
|
<div>
|
|
<span class="name">${safeName}</span>
|
|
<span class="username">${safeUsername}</span>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>${safeEmail}</td>
|
|
<td><span class="role-badge ${safeRole}">${safeRole}</span></td>
|
|
<td>${user.last_login ? MSPE.formatDate(user.last_login) : 'Never'}</td>
|
|
<td><span class="status-badge ${safeStatus}">${safeStatus}</span></td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button class="btn btn-sm btn-outline" data-action="edit" data-id="${safeId}">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-outline" data-action="password" data-id="${safeId}">
|
|
<i class="fas fa-key"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" data-action="delete" data-id="${safeId}">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
}).join('');
|
|
}
|
|
|
|
function openAddUserModal() {
|
|
document.getElementById('modal-title').textContent = 'Add Admin User';
|
|
document.getElementById('user-form').reset();
|
|
document.getElementById('user-form').dataset.id = '';
|
|
document.getElementById('password-fields').style.display = 'flex';
|
|
document.getElementById('user-modal').classList.add('active');
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('user-modal').classList.remove('active');
|
|
}
|
|
|
|
function editUser(id) {
|
|
const user = allUsers.find(u => u.id === id);
|
|
if (!user) return;
|
|
|
|
document.getElementById('modal-title').textContent = 'Edit User';
|
|
const form = document.getElementById('user-form');
|
|
form.dataset.id = id;
|
|
|
|
form.querySelector('[name="name"]').value = user.name;
|
|
form.querySelector('[name="username"]').value = user.username;
|
|
form.querySelector('[name="email"]').value = user.email;
|
|
form.querySelector('[name="role"]').value = user.role;
|
|
form.querySelector('[name="status"]').value = user.status;
|
|
|
|
// Hide password fields for edit
|
|
document.getElementById('password-fields').style.display = 'none';
|
|
|
|
document.getElementById('user-modal').classList.add('active');
|
|
}
|
|
|
|
function deleteUser(id) {
|
|
MSPE.confirmDialog('Are you sure you want to delete this user?').then(async confirmed => {
|
|
if (confirmed) {
|
|
try {
|
|
const response = await MSPE.API.delete(`users.php?id=${id}`);
|
|
if (response.success) {
|
|
MSPE.showNotification('User deleted', 'success');
|
|
loadUsers();
|
|
} else {
|
|
MSPE.showNotification(response.message, 'error');
|
|
}
|
|
} catch (e) {
|
|
MSPE.showNotification('Error deleting user', 'error');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function changePassword(id) {
|
|
document.getElementById('password-form').dataset.id = id;
|
|
document.getElementById('password-modal').classList.add('active');
|
|
}
|
|
|
|
function closePasswordModal() {
|
|
document.getElementById('password-modal').classList.remove('active');
|
|
}
|
|
|
|
async function saveUser() {
|
|
const form = document.getElementById('user-form');
|
|
const formData = new FormData(form);
|
|
const id = form.dataset.id;
|
|
|
|
// Client-side password match check for new users
|
|
if (!id) {
|
|
const pw = form.querySelector('[name="password"]').value;
|
|
const cpw = form.querySelector('[name="password_confirm"]').value;
|
|
if (pw !== cpw) {
|
|
MSPE.showNotification('Passwords do not match', 'error');
|
|
return;
|
|
}
|
|
if (pw.length < 12) {
|
|
MSPE.showNotification('Password must be at least 12 characters', 'error');
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (id) formData.append('id', id);
|
|
|
|
try {
|
|
const result = await MSPE.API.upload('users.php', formData);
|
|
|
|
if (result && result.success) {
|
|
MSPE.showNotification(id ? 'Updated successfully' : 'Created successfully', 'success');
|
|
closeModal();
|
|
loadUsers();
|
|
} else {
|
|
MSPE.showNotification(result?.message || 'Error saving user', 'error');
|
|
}
|
|
} catch (error) {
|
|
MSPE.showNotification('Error saving user', 'error');
|
|
}
|
|
}
|
|
|
|
async function updatePassword() {
|
|
const form = document.getElementById('password-form');
|
|
const id = form.dataset.id;
|
|
const newPass = form.querySelector('[name="new_password"]').value;
|
|
const confirmPass = form.querySelector('[name="confirm_password"]').value;
|
|
|
|
if (newPass !== confirmPass) {
|
|
MSPE.showNotification('Passwords do not match', 'error');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('id', id);
|
|
formData.append('password', newPass);
|
|
|
|
try {
|
|
const result = await MSPE.API.upload('users.php', formData);
|
|
|
|
if (result && result.success) {
|
|
MSPE.showNotification('Password updated', 'success');
|
|
closePasswordModal();
|
|
} else {
|
|
MSPE.showNotification(result?.message || 'Error updating password', 'error');
|
|
}
|
|
} catch (error) {
|
|
MSPE.showNotification('Error updating password', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|