From 3d274404c6e7dfdb8a557fd2b2b98119aa615c5c Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Thu, 25 Sep 2025 21:18:43 +0000 Subject: [PATCH] 0004 --- add-client.php | 136 +++++++++++++++++++++++++++++++++++ add-credential.php | 152 +++++++++++++++++++++++++++++++++++++++ add-note.php | 51 ++++++++++++++ assets/js/main.js | 160 ++++++++++++++++++++++++++++++++++++++++++ audit-log.php | 114 ++++++++++++++++++++++++++++++ dashboard.php | 146 +++++++++++++++++++++++++++++++++++--- delete-credential.php | 42 +++++++++++ delete-note.php | 50 +++++++++++++ edit-credential.php | 158 +++++++++++++++++++++++++++++++++++++++++ includes/audit.php | 26 +++++++ index.php | 5 ++ logout.php | 5 ++ 12 files changed, 1037 insertions(+), 8 deletions(-) create mode 100644 add-client.php create mode 100644 add-credential.php create mode 100644 add-note.php create mode 100644 audit-log.php create mode 100644 delete-credential.php create mode 100644 delete-note.php create mode 100644 edit-credential.php create mode 100644 includes/audit.php diff --git a/add-client.php b/add-client.php new file mode 100644 index 0000000..3f5c949 --- /dev/null +++ b/add-client.php @@ -0,0 +1,136 @@ +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."; + } + } + } +} +?> + + + + + + Add New Client - FlexPass + + + + + + + + +
+
+

Add New Client

+ Back to Client List +
+ +
+
+ +
+
    + +
  • + +
+
+ +
+
+ + +
A unique 4-digit identifier for the client.
+
+
+ + +
+
+ + +
+ +
+
+
+
+ + + + + diff --git a/add-credential.php b/add-credential.php new file mode 100644 index 0000000..1feea2b --- /dev/null +++ b/add-credential.php @@ -0,0 +1,152 @@ +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(); + } + } +} + +?> + + + + + + Add Credential - FlexPass + + + + + + + + +
+
+
+
+
+

Add New Credential for

+
+
+ +
+
    + +
  • + +
+
+ + +
+
+ + +
A descriptive name, e.g., "Primary Admin Login" or "Database Access".
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ Cancel + +
+
+
+
+
+
+
+ + + + + diff --git a/add-note.php b/add-note.php new file mode 100644 index 0000000..79fcf70 --- /dev/null +++ b/add-note.php @@ -0,0 +1,51 @@ + 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; +} +?> \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index e69de29..d9df405 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -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 = ` +
+

"${newNote.note.replace(/\n/g, '
')}"

+ + Added by ${newNote.display_name} on ${new Date(newNote.created_at).toLocaleString()} + +
+ + `; + + // 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.'); + }); + } + } + }); + } +}); diff --git a/audit-log.php b/audit-log.php new file mode 100644 index 0000000..d7dd1cd --- /dev/null +++ b/audit-log.php @@ -0,0 +1,114 @@ +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); + +?> + + + + + + Audit Log - FlexPass + + + + + + + + +
+

Audit Log

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
TimestampUserActionTarget
+ + + + N/A + +
No audit events found.
+
+
+
+ + + + + diff --git a/dashboard.php b/dashboard.php index 3829e04..78f8e65 100644 --- a/dashboard.php +++ b/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'])) { +