0004
This commit is contained in:
parent
b5c30c0773
commit
3d274404c6
136
add-client.php
Normal file
136
add-client.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$displayName = $_SESSION['user_display_name'] ?? 'User';
|
||||
$errors = [];
|
||||
$successMessage = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$clientId = $_POST['client_id'] ?? '';
|
||||
$clientName = $_POST['name'] ?? '';
|
||||
$status = $_POST['status'] ?? 'active';
|
||||
|
||||
// Validation
|
||||
if (empty($clientId) || !preg_match('/^\d{4}$/', $clientId)) {
|
||||
$errors[] = "Client ID must be exactly 4 digits.";
|
||||
}
|
||||
|
||||
if (empty($clientName)) {
|
||||
$errors[] = "Client Name is required.";
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
$pdo = db();
|
||||
// Check for uniqueness
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM clients WHERE client_id = ?");
|
||||
$stmt->execute([$clientId]);
|
||||
if ($stmt->fetchColumn() > 0) {
|
||||
$errors[] = "Client ID already exists.";
|
||||
} else {
|
||||
// Insert into database
|
||||
$stmt = $pdo->prepare("INSERT INTO clients (client_id, name, status) VALUES (?, ?, ?)");
|
||||
if ($stmt->execute([$clientId, $clientName, $status])) {
|
||||
log_audit_event('client_create', $_SESSION['user_id'], 'client', $clientId);
|
||||
header("Location: dashboard.php?status=client_added");
|
||||
exit;
|
||||
} else {
|
||||
$errors[] = "Failed to create the client. Please try again.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add New Client - FlexPass</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="dashboard.php">
|
||||
<i class="bi bi-shield-lock"></i> FlexPass
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="dashboard.php">Clients</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-person-circle"></i> <?php echo htmlspecialchars($displayName); ?>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1 class="mb-0">Add New Client</h1>
|
||||
<a href="dashboard.php" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i> Back to Client List</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?php echo htmlspecialchars($error); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form action="add-client.php" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="client_id" class="form-label">Client ID</label>
|
||||
<input type="text" class="form-control" id="client_id" name="client_id" required maxlength="4" pattern="\d{4}" title="Client ID must be exactly 4 digits.">
|
||||
<div class="form-text">A unique 4-digit identifier for the client.</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Client Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select class="form-select" id="status" name="status">
|
||||
<option value="active" selected>Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Save Client</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
152
add-credential.php
Normal file
152
add-credential.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$displayName = $_SESSION['user_display_name'] ?? 'User';
|
||||
$pdo = db();
|
||||
|
||||
// Ensure a client_id is provided
|
||||
if (!isset($_GET['client_id']) || empty($_GET['client_id'])) {
|
||||
header('Location: dashboard.php?error=noclient');
|
||||
exit;
|
||||
}
|
||||
|
||||
$client_id = $_GET['client_id'];
|
||||
|
||||
// Fetch client details to display
|
||||
$stmt = $pdo->prepare("SELECT name FROM clients WHERE client_id = ?");
|
||||
$stmt->execute([$client_id]);
|
||||
$client = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$client) {
|
||||
header('Location: dashboard.php?error=invalidclient');
|
||||
exit;
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? ''; // Do not trim password
|
||||
$url = trim($_POST['url'] ?? '');
|
||||
$notes = trim($_POST['notes'] ?? '');
|
||||
|
||||
if (empty($name)) {
|
||||
$errors[] = 'Credential name is required.';
|
||||
}
|
||||
if (empty($password)) {
|
||||
$errors[] = 'Password is required.';
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
// For now, we will store passwords in plain text. This is NOT secure and will be updated.
|
||||
// In a real-world scenario, use a proper encryption method.
|
||||
$insertStmt = $pdo->prepare(
|
||||
"INSERT INTO credentials (client_id, name, username, password, url, notes, created_by, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
try {
|
||||
$insertStmt->execute([$client_id, $name, $username, $password, $url, $notes, $user_id, $user_id]);
|
||||
$newCredentialId = $pdo->lastInsertId();
|
||||
log_audit_event('credential_create', $user_id, 'credential', $newCredentialId);
|
||||
header("Location: dashboard.php?client_id=$client_id&status=credential_added");
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
$errors[] = "Database error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add Credential - FlexPass</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="dashboard.php">
|
||||
<i class="bi bi-shield-lock"></i> FlexPass
|
||||
</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="dashboard.php">Clients</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="h4 mb-0">Add New Credential for <?php echo htmlspecialchars($client['name']); ?></h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if (!empty($errors)): ?>
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($errors as $error): ?>
|
||||
<li><?php echo htmlspecialchars($error); ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="add-credential.php?client_id=<?php echo htmlspecialchars($client_id); ?>" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Credential Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" required>
|
||||
<div class="form-text">A descriptive name, e.g., "Primary Admin Login" or "Database Access".</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">URL</label>
|
||||
<input type="url" class="form-control" id="url" name="url" placeholder="https://example.com/login">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="notes" class="form-label">Notes</label>
|
||||
<textarea class="form-control" id="notes" name="notes" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="dashboard.php?client_id=<?php echo htmlspecialchars($client_id); ?>" class="btn btn-secondary me-2">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Add Credential</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
51
add-note.php
Normal file
51
add-note.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// If user is not logged in, return error
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'User not logged in.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$clientId = $_POST['client_id'] ?? null;
|
||||
$note = $_POST['note'] ?? null;
|
||||
$userId = $_SESSION['user_id'];
|
||||
|
||||
if ($clientId && $note) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("INSERT INTO notes (client_id, user_id, note) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$clientId, $userId, $note]);
|
||||
$newNoteId = $pdo->lastInsertId();
|
||||
log_audit_event('note_create', $userId, 'note', $newNoteId);
|
||||
|
||||
// Fetch the new note to return to the client
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT n.*, u.display_name FROM notes n " .
|
||||
"JOIN users u ON n.user_id = u.user_id " .
|
||||
"WHERE n.note_id = ?"
|
||||
);
|
||||
$stmt->execute([$newNoteId]);
|
||||
$newNote = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode(['success' => true, 'note' => $newNote]);
|
||||
exit;
|
||||
} catch (PDOException $e) {
|
||||
// Optional: Log error
|
||||
echo json_encode(['success' => false, 'message' => 'Database error.']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Client ID and note are required.']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid request method.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@ -0,0 +1,160 @@
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Password visibility toggle
|
||||
const togglePasswordButtons = document.querySelectorAll('.btn-toggle-password');
|
||||
togglePasswordButtons.forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
const targetInput = document.querySelector(this.dataset.target);
|
||||
const icon = this.querySelector('i');
|
||||
if (targetInput.type === 'password') {
|
||||
targetInput.type = 'text';
|
||||
icon.classList.remove('bi-eye-slash');
|
||||
icon.classList.add('bi-eye');
|
||||
} else {
|
||||
targetInput.type = 'password';
|
||||
icon.classList.remove('bi-eye');
|
||||
icon.classList.add('bi-eye-slash');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Copy password to clipboard
|
||||
const copyPasswordButtons = document.querySelectorAll('.btn-copy-password');
|
||||
copyPasswordButtons.forEach(button => {
|
||||
button.addEventListener('click', function () {
|
||||
const targetInput = document.querySelector(this.dataset.target);
|
||||
|
||||
navigator.clipboard.writeText(targetInput.value).then(() => {
|
||||
// Visual feedback
|
||||
const originalIcon = 'bi-clipboard';
|
||||
const successIcon = 'bi-check-lg';
|
||||
const icon = this.querySelector('i');
|
||||
|
||||
icon.classList.remove(originalIcon);
|
||||
icon.classList.add(successIcon);
|
||||
|
||||
setTimeout(() => {
|
||||
icon.classList.remove(successIcon);
|
||||
icon.classList.add(originalIcon);
|
||||
}, 1500);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy password: ', err);
|
||||
alert('Failed to copy password.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Client search filtering
|
||||
const clientSearchInput = document.getElementById('client-search');
|
||||
if (clientSearchInput) {
|
||||
clientSearchInput.addEventListener('keyup', function () {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
const clientList = document.getElementById('client-list');
|
||||
const clients = clientList.getElementsByTagName('tr');
|
||||
|
||||
for (let i = 0; i < clients.length; i++) {
|
||||
const client = clients[i];
|
||||
const clientId = client.getElementsByTagName('td')[0].textContent.toLowerCase();
|
||||
const clientName = client.getElementsByTagName('td')[1].textContent.toLowerCase();
|
||||
|
||||
if (clientId.includes(searchTerm) || clientName.includes(searchTerm)) {
|
||||
client.style.display = '';
|
||||
} else {
|
||||
client.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// AJAX for adding a new note
|
||||
const addNoteForm = document.getElementById('add-note-form');
|
||||
if (addNoteForm) {
|
||||
addNoteForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
const noteTextarea = this.querySelector('textarea[name="note"]');
|
||||
|
||||
fetch('add-note.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Create the new note element
|
||||
const newNote = data.note;
|
||||
const noteElement = document.createElement('div');
|
||||
noteElement.classList.add('card', 'mb-2');
|
||||
noteElement.innerHTML = `
|
||||
<div class="card-body p-2">
|
||||
<p class="card-text mb-1">"${newNote.note.replace(/\n/g, '<br>')}"</p>
|
||||
<small class="text-muted">
|
||||
Added by ${newNote.display_name} on ${new Date(newNote.created_at).toLocaleString()}
|
||||
</small>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-top-0 text-end p-1">
|
||||
<a href="delete-note.php?note_id=${newNote.note_id}" class="btn btn-sm btn-outline-danger delete-note-btn" data-note-id="${newNote.note_id}"><i class="bi bi-trash"></i></a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add the new note to the list
|
||||
const notesList = document.getElementById('notes-list');
|
||||
notesList.insertBefore(noteElement, notesList.firstChild);
|
||||
|
||||
// Hide the "no notes" message if it's visible
|
||||
const noNotesMessage = document.getElementById('no-notes-message');
|
||||
if (noNotesMessage) {
|
||||
noNotesMessage.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Clear the textarea
|
||||
noteTextarea.value = '';
|
||||
} else {
|
||||
alert('Error adding note: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An unexpected error occurred.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// AJAX for deleting a new note
|
||||
const notesList = document.getElementById('notes-list');
|
||||
if (notesList) {
|
||||
notesList.addEventListener('click', function (e) {
|
||||
const deleteButton = e.target.closest('.delete-note-btn');
|
||||
if (deleteButton) {
|
||||
e.preventDefault();
|
||||
|
||||
if (confirm('Are you sure you want to delete this note?')) {
|
||||
const noteId = deleteButton.dataset.noteId;
|
||||
|
||||
fetch(`delete-note.php?note_id=${noteId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Remove the note element from the DOM
|
||||
deleteButton.closest('.card').remove();
|
||||
|
||||
// Show the "no notes" message if the list is empty
|
||||
if (notesList.children.length === 0) {
|
||||
const noNotesMessage = document.getElementById('no-notes-message');
|
||||
if (noNotesMessage) {
|
||||
noNotesMessage.classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert('Error deleting note: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An unexpected error occurred.');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
114
audit-log.php
Normal file
114
audit-log.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Optional: Add role-based access control here if needed
|
||||
|
||||
$displayName = $_SESSION['user_display_name'] ?? 'User';
|
||||
$pdo = db();
|
||||
|
||||
// Fetch audit events
|
||||
$stmt = $pdo->query(
|
||||
"SELECT ae.*, u.display_name " .
|
||||
"FROM audit_events ae " .
|
||||
"LEFT JOIN users u ON ae.user_id = u.id " .
|
||||
"ORDER BY ae.created_at DESC LIMIT 200" // Limit to recent 200 events for performance
|
||||
);
|
||||
$events = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Audit Log - FlexPass</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="dashboard.php">
|
||||
<i class="bi bi-shield-lock"></i> FlexPass
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="dashboard.php">Clients</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="audit-log.php">Audit Log</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-person-circle"></i> <?php echo htmlspecialchars($displayName); ?>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-3">Audit Log</h1>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>User</th>
|
||||
<th>Action</th>
|
||||
<th>Target</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($events as $event): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars(date('Y-m-d H:i:s', strtotime($event['created_at']))); ?></td>
|
||||
<td><?php echo htmlspecialchars($event['display_name'] ?? 'System/Unknown'); ?></td>
|
||||
<td><span class="badge bg-info text-dark"><?php echo htmlspecialchars($event['action']); ?></span></td>
|
||||
<td>
|
||||
<?php if ($event['target_type'] && $event['target_id']): ?>
|
||||
<?php echo htmlspecialchars($event['target_type'] . ' #' . $event['target_id']); ?>
|
||||
<?php else: ?>
|
||||
N/A
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($events)): ?>
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">No audit events found.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
146
dashboard.php
146
dashboard.php
@ -18,7 +18,22 @@ if (isset($_GET['client_id'])) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM clients WHERE client_id = ?");
|
||||
$stmt->execute([$_GET['client_id']]);
|
||||
$viewingClient = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
// TODO: Fetch credentials and notes for the client
|
||||
|
||||
$credentials = [];
|
||||
$notes = [];
|
||||
if ($viewingClient) {
|
||||
$credStmt = $pdo->prepare("SELECT * FROM credentials WHERE client_id = ? ORDER BY name ASC");
|
||||
$credStmt->execute([$viewingClient['client_id']]);
|
||||
$credentials = $credStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$noteStmt = $pdo->prepare(
|
||||
"SELECT n.*, u.display_name FROM notes n " .
|
||||
"JOIN users u ON n.user_id = u.user_id " .
|
||||
"WHERE n.client_id = ? ORDER BY n.created_at DESC"
|
||||
);
|
||||
$noteStmt->execute([$viewingClient['client_id']]);
|
||||
$notes = $noteStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
} else {
|
||||
$stmt = $pdo->query("SELECT * FROM clients ORDER BY name ASC");
|
||||
$clients = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
@ -50,6 +65,9 @@ if (isset($_GET['client_id'])) {
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" aria-current="page" href="dashboard.php">Clients</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="audit-log.php">Audit Log</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<ul class="navbar-nav">
|
||||
@ -72,9 +90,44 @@ if (isset($_GET['client_id'])) {
|
||||
<!-- Client Detail View -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<a href="dashboard.php" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i> Back to Client List</a>
|
||||
<button class="btn btn-sm btn-primary"><i class="bi bi-plus-circle"></i> Add Credential</button>
|
||||
<a href="add-credential.php?client_id=<?php echo htmlspecialchars($viewingClient['client_id']); ?>" class="btn btn-sm btn-primary"><i class="bi bi-plus-circle"></i> Add Credential</a>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'credential_added'): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
New credential has been added successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'credential_updated'): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
Credential has been updated successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'credential_deleted'): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
Credential has been deleted successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'note_added'): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
New note has been added successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'note_deleted'): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
Note has been deleted successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="mb-0">
|
||||
@ -88,14 +141,81 @@ if (isset($_GET['client_id'])) {
|
||||
<hr>
|
||||
|
||||
<h4>Credentials</h4>
|
||||
<p class="text-muted">Credentials management coming soon.</p>
|
||||
<!-- Placeholder for credentials list -->
|
||||
<table class="table table-sm table-vcenter">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Username</th>
|
||||
<th>Password</th>
|
||||
<th>Last Modified</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($credentials as $cred): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($cred['name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($cred['username']); ?></td>
|
||||
<td>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="password" class="form-control form-control-sm" readonly value="<?php echo htmlspecialchars($cred['password']); ?>" id="cred-pass-<?php echo $cred['credential_id']; ?>">
|
||||
<button class="btn btn-outline-secondary btn-toggle-password" type="button" data-target="#cred-pass-<?php echo $cred['credential_id']; ?>"><i class="bi bi-eye-slash"></i></button>
|
||||
<button class="btn btn-outline-secondary btn-copy-password" type="button" data-target="#cred-pass-<?php echo $cred['credential_id']; ?>"><i class="bi bi-clipboard"></i></button>
|
||||
</div>
|
||||
</td>
|
||||
<td><?php echo htmlspecialchars(date('Y-m-d H:i', strtotime($cred['updated_at']))); ?></td>
|
||||
<td>
|
||||
<a href="edit-credential.php?credential_id=<?php echo $cred['credential_id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil-square"></i> Edit</a>
|
||||
<a href="delete-credential.php?credential_id=<?php echo $cred['credential_id']; ?>" class="btn btn-sm btn-outline-danger" onclick="return confirm('Are you sure you want to delete this credential?');"><i class="bi bi-trash"></i> Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($credentials)): ?>
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">No credentials found for this client.</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h4>Notes</h4>
|
||||
<p class="text-muted">Notes timeline coming soon.</p>
|
||||
<!-- Placeholder for notes timeline -->
|
||||
<div id="notes-section">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form id="add-note-form" action="add-note.php" method="POST">
|
||||
<input type="hidden" name="client_id" value="<?php echo htmlspecialchars($viewingClient['client_id']); ?>">
|
||||
<div class="mb-3">
|
||||
<label for="note" class="form-label">Add a new note</label>
|
||||
<textarea class="form-control" id="note" name="note" rows="3" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-sm btn-primary">Add Note</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="notes-list">
|
||||
<?php foreach ($notes as $note): ?>
|
||||
<div class="card mb-2">
|
||||
<div class="card-body p-2">
|
||||
<p class="card-text mb-1">"<?php echo nl2br(htmlspecialchars($note['note'])); ?>"</p>
|
||||
<small class="text-muted">
|
||||
Added by <?php echo htmlspecialchars($note['display_name']); ?> on <?php echo htmlspecialchars(date('Y-m-d H:i', strtotime($note['created_at']))); ?>
|
||||
</small>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent border-top-0 text-end p-1">
|
||||
<a href="delete-note.php?note_id=<?php echo $note['note_id']; ?>" class="btn btn-sm btn-outline-danger delete-note-btn" data-note-id="<?php echo $note['note_id']; ?>"><i class="bi bi-trash"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (empty($notes)): ?>
|
||||
<p id="no-notes-message" class="text-center text-muted">No notes found for this client.</p>
|
||||
<?php else: ?>
|
||||
<p id="no-notes-message" class="text-center text-muted d-none">No notes found for this client.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -103,9 +223,19 @@ if (isset($_GET['client_id'])) {
|
||||
<!-- Client List View -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1 class="mb-0">Clients</h1>
|
||||
<button class="btn btn-primary"><i class="bi bi-plus-circle"></i> Add New Client</button>
|
||||
<a href="add-client.php" class="btn btn-primary"><i class="bi bi-plus-circle"></i> Add New Client</a>
|
||||
</div>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'client_added'): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
New client has been added successfully.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<input type="text" id="client-search" class="form-control" placeholder="Search clients...">
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-hover">
|
||||
@ -117,7 +247,7 @@ if (isset($_GET['client_id'])) {
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="client-list">
|
||||
<?php foreach ($clients as $client): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($client['client_id']); ?></td>
|
||||
|
||||
42
delete-credential.php
Normal file
42
delete-credential.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if credential_id is provided
|
||||
if (!isset($_GET['credential_id'])) {
|
||||
header('Location: dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$credential_id = $_GET['credential_id'];
|
||||
|
||||
// Fetch the client_id for redirecting back
|
||||
$stmt = $pdo->prepare("SELECT client_id FROM credentials WHERE credential_id = ?");
|
||||
$stmt->execute([$credential_id]);
|
||||
$credential = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($credential) {
|
||||
$client_id = $credential['client_id'];
|
||||
|
||||
log_audit_event('credential_delete', $_SESSION['user_id'], 'credential', $credential_id);
|
||||
|
||||
// Delete the credential
|
||||
$deleteStmt = $pdo->prepare("DELETE FROM credentials WHERE credential_id = ?");
|
||||
$deleteStmt->execute([$credential_id]);
|
||||
|
||||
// Redirect back to the client detail page with a success message
|
||||
header("Location: dashboard.php?client_id=$client_id&status=credential_deleted");
|
||||
exit;
|
||||
} else {
|
||||
// Credential not found, just redirect
|
||||
header('Location: dashboard.php');
|
||||
exit;
|
||||
}
|
||||
50
delete-note.php
Normal file
50
delete-note.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// If user is not logged in, return error
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'User not logged in.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$noteId = $_GET['note_id'] ?? null;
|
||||
|
||||
if ($noteId) {
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// First, get the client_id for redirection
|
||||
$stmt = $pdo->prepare("SELECT client_id FROM notes WHERE note_id = ?");
|
||||
$stmt->execute([$noteId]);
|
||||
$note = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$clientId = $note['client_id'] ?? null;
|
||||
|
||||
if ($clientId) {
|
||||
log_audit_event('note_delete', $_SESSION['user_id'], 'note', $noteId);
|
||||
|
||||
// Now, delete the note
|
||||
$deleteStmt = $pdo->prepare("DELETE FROM notes WHERE note_id = ?");
|
||||
$deleteStmt->execute([$noteId]);
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
exit;
|
||||
} else {
|
||||
// Note not found or no client_id associated
|
||||
echo json_encode(['success' => false, 'message' => 'Note not found.']);
|
||||
exit;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// Optional: Log error
|
||||
echo json_encode(['success' => false, 'message' => 'Database error.']);
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
// Redirect if note_id is missing
|
||||
echo json_encode(['success' => false, 'message' => 'Note ID is required.']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
158
edit-credential.php
Normal file
158
edit-credential.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if credential_id is provided
|
||||
if (!isset($_GET['credential_id'])) {
|
||||
header('Location: dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$credential_id = $_GET['credential_id'];
|
||||
|
||||
// Fetch the credential
|
||||
$stmt = $pdo->prepare("SELECT * FROM credentials WHERE credential_id = ?");
|
||||
$stmt->execute([$credential_id]);
|
||||
$credential = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$credential) {
|
||||
// Credential not found
|
||||
header('Location: dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$client_id = $credential['client_id']; // For redirecting back
|
||||
|
||||
// Handle form submission
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$name = trim($_POST['name']);
|
||||
$username = trim($_POST['username']);
|
||||
$password = $_POST['password']; // Don't trim password
|
||||
$url = trim($_POST['url']);
|
||||
$notes = trim($_POST['notes']);
|
||||
|
||||
// Basic validation
|
||||
if (empty($name) || empty($username)) {
|
||||
$error = "Credential Name and Username are required.";
|
||||
} else {
|
||||
// If password is not changed, don't update it
|
||||
if (empty($password)) {
|
||||
$updateStmt = $pdo->prepare(
|
||||
"UPDATE credentials SET name = ?, username = ?, url = ?, notes = ?, updated_at = NOW() WHERE credential_id = ?"
|
||||
);
|
||||
$updateStmt->execute([$name, $username, $url, $notes, $credential_id]);
|
||||
} else {
|
||||
$updateStmt = $pdo->prepare(
|
||||
"UPDATE credentials SET name = ?, username = ?, password = ?, url = ?, notes = ?, updated_at = NOW() WHERE credential_id = ?"
|
||||
);
|
||||
$updateStmt->execute([$name, $username, $password, $url, $notes, $credential_id]);
|
||||
}
|
||||
|
||||
log_audit_event('credential_update', $_SESSION['user_id'], 'credential', $credential_id);
|
||||
|
||||
// Redirect back to the client detail page with a success message
|
||||
header("Location: dashboard.php?client_id=$client_id&status=credential_updated");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$displayName = $_SESSION['user_display_name'] ?? 'User';
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-bs-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Edit Credential - FlexPass</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="dashboard.php">
|
||||
<i class="bi bi-shield-lock"></i> FlexPass
|
||||
</a>
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="dashboard.php">Clients</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-person-circle"></i> <?php echo htmlspecialchars($displayName); ?>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" href="logout.php">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h1 class="h4 mb-0">Edit Credential</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<?php if (isset($error)): ?>
|
||||
<div class="alert alert-danger"><?php echo htmlspecialchars($error); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="edit-credential.php?credential_id=<?php echo htmlspecialchars($credential_id); ?>" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Credential Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="<?php echo htmlspecialchars($credential['name']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($credential['username']); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" placeholder="Leave blank to keep current password">
|
||||
<small class="form-text text-muted">Enter a new password only if you want to change it.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label">URL</label>
|
||||
<input type="url" class="form-control" id="url" name="url" value="<?php echo htmlspecialchars($credential['url']); ?>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="notes" class="form-label">Notes</label>
|
||||
<textarea class="form-control" id="notes" name="notes" rows="3"><?php echo htmlspecialchars($credential['notes']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="dashboard.php?client_id=<?php echo htmlspecialchars($client_id); ?>" class="btn btn-secondary me-2">Cancel</a>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
26
includes/audit.php
Normal file
26
includes/audit.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
/**
|
||||
* Logs an audit event.
|
||||
*
|
||||
* @param string $action The action performed (e.g., 'login_success', 'credential_create').
|
||||
* @param int|null $userId The ID of the user who performed the action. Can be null.
|
||||
* @param string|null $targetType The type of object the action was performed on (e.g., 'client', 'credential').
|
||||
* @param int|null $targetId The ID of the object.
|
||||
*/
|
||||
function log_audit_event(string $action, ?int $userId, ?string $targetType = null, ?int $targetId = null)
|
||||
{
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO audit_events (user_id, action, target_type, target_id) VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
$stmt->execute([$userId, $action, $targetType, $targetId]);
|
||||
} catch (PDOException $e) {
|
||||
// In a real application, you would log this error to a file or monitoring service.
|
||||
// For this example, we'll fail silently to not disrupt the user experience.
|
||||
error_log('Audit log failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
?>
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
// If user is already logged in, redirect to dashboard
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
@ -37,13 +38,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$updateStmt = $pdo->prepare("UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
$updateStmt->execute([$user['id']]);
|
||||
|
||||
log_audit_event('login_success', $user['id']);
|
||||
|
||||
header('Location: dashboard.php');
|
||||
exit;
|
||||
} else {
|
||||
$error_message = 'Your account is disabled. Please contact an administrator.';
|
||||
log_audit_event('login_failed_disabled', $user['id']);
|
||||
}
|
||||
} else {
|
||||
$error_message = 'Invalid email or password.';
|
||||
log_audit_event('login_failed_credentials', $user['id'] ?? null);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// In a real app, you would log this error.
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/includes/audit.php';
|
||||
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
log_audit_event('logout', $_SESSION['user_id']);
|
||||
}
|
||||
|
||||
// Unset all of the session variables
|
||||
$_SESSION = [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user