From 7cc372497b9a3264209c9b1d6491e3247695e5cc Mon Sep 17 00:00:00 2001 From: BIG2EYEZ Date: Mon, 1 Jan 2024 17:08:02 +0800 Subject: [PATCH] DISPLAY AND DOWNLOAD LOGS --- Sean/models/User.js | 35 ++++ Sean/modules/mysql.js | 2 +- Sean/server.js | 21 ++ Sean/views/inusers.ejs | 363 ++-------------------------------- Sean/views/inusers.js | 433 +++++++++++++++++++++++++++++++++++++++++ Sean/views/login.ejs | 6 + Sean/views/style.css | 16 +- 7 files changed, 525 insertions(+), 351 deletions(-) create mode 100644 Sean/views/inusers.js diff --git a/Sean/models/User.js b/Sean/models/User.js index e69de29..ab12ee9 100644 --- a/Sean/models/User.js +++ b/Sean/models/User.js @@ -0,0 +1,35 @@ +// models/User.js + +const { Sequelize, DataTypes } = require('sequelize'); +const sequelize = new Sequelize(process.env.database, process.env.user, process.env.password, { + host: process.env.host, + dialect: 'mysql', + timezone: 'Z', // Set the timezone to UTC +}); + +const User = sequelize.define('User', { + name: { + type: DataTypes.STRING, + allowNull: false, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + }, + jobTitle: { + type: DataTypes.STRING, + allowNull: false, + }, +}); + +module.exports = User; diff --git a/Sean/modules/mysql.js b/Sean/modules/mysql.js index f67a1bc..7dbb840 100644 --- a/Sean/modules/mysql.js +++ b/Sean/modules/mysql.js @@ -37,10 +37,10 @@ const connection = mysql.createConnection({ user: process.env.DB_USER, password: process.env.DB_PASS, database: "adminusers", + timezone: "Z", // Set the timezone to UTC }); module.exports = { connection }; -module.exports = { connection }; \ No newline at end of file diff --git a/Sean/server.js b/Sean/server.js index 16c93b3..6d0eaf4 100644 --- a/Sean/server.js +++ b/Sean/server.js @@ -722,7 +722,28 @@ app.get('/api/users', (req, res) => { }); } + app.get('/api/getLogs', (req, res) => { + // Query the database to fetch logs + const query = 'SELECT id, username, activity, timestamp FROM user_logs'; + connection.query(query, (err, results) => { + if (err) { + console.error('Error fetching logs from MySQL:', err); + res.status(500).json({ error: 'Error fetching logs from MySQL' }); + return; + } + // Format timestamps to a more readable format + const formattedLogs = results.map((log) => ({ + id: log.id, + username: log.username, + activity: log.activity, + timestamp: new Date(log.timestamp).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }), + })); + + // Send the formatted logs as a JSON response + res.json(formattedLogs); + }); + }); app.use(express.static("views")); diff --git a/Sean/views/inusers.ejs b/Sean/views/inusers.ejs index facad33..cec5785 100644 --- a/Sean/views/inusers.ejs +++ b/Sean/views/inusers.ejs @@ -23,6 +23,7 @@ Add User Delete User Reset Password + Logs Home @@ -47,11 +48,11 @@ <% if (allUsers && allUsers.length > 0) { %> <% allUsers.forEach(user => { %> - <%= user.name %> - <%= user.username %> - <%= user.email %> - <%= new Date(user.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) %> - <%= user.jobTitle %> + <%- user.name %> + <%- user.username %> + <%- user.email %> + <%- new Date(user.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) %> + <%- user.jobTitle %> <% }); %> <% } else { %> @@ -139,356 +140,20 @@ - - + + + - + diff --git a/Sean/views/inusers.js b/Sean/views/inusers.js new file mode 100644 index 0000000..32caea6 --- /dev/null +++ b/Sean/views/inusers.js @@ -0,0 +1,433 @@ + + + +$(document).ready(function () { + $('#resetPasswordLink').on('click', function () { + $('#resetPasswordFormContainer').show(); + $('#createUserForm').hide(); + $('#userDataContainer').hide(); + $('#downloadButtonContainer').hide(); + $('#deleteUserContainer').hide(); + $('#logsContainer').hide(); + }); + + $('#addUserLink').on('click', function () { + $('#resetPasswordFormContainer').hide(); + $('#createUserForm').show(); + $('#userDataContainer').hide(); + $('#downloadButtonContainer').hide(); + $('#deleteUserContainer').hide(); + $('#logsContainer').hide(); + }); + + $('#userDataLink').on('click', function () { + $('#resetPasswordFormContainer').hide(); + $('#createUserForm').hide(); + $('#userDataContainer').show(); + $('#downloadButtonContainer').show(); + $('#deleteUserContainer').hide(); + $('#logsContainer').hide(); + }); + + $('#searchUserButton').on('click', function () { + console.log('Search button clicked'); + const searchUsername = $('#searchUserInput').val(); + // Call the function to search for the user + searchUser(searchUsername); +}); + +$('#deleteUserLink').on('click', function () { +$('#deleteUserContainer').show(); + $('#resetPasswordFormContainer').hide(); + $('#createUserForm').hide(); + $('#userDataContainer').hide(); + $('#downloadButtonContainer').hide(); + $('#logsContainer').hide(); +}); + +$('#downloadButton').on('click', function () { + // Call the downloadExcel function with the allUsers data + downloadExcel(allUsers); +}); +$('#logsLink').on('click', function () { + // Hide other containers + $('#resetPasswordFormContainer').hide(); + $('#createUserForm').hide(); + $('#userDataContainer').hide(); + $('#downloadButtonContainer').hide(); + $('#deleteUserContainer').hide(); + + // Show logs container + $('#logsContainer').show(); + + fetchLogs(); +}); +function fetchLogs() { + // Make a fetch request to your server endpoint for logs + fetch('/api/getLogs') + .then(response => response.json()) + .then(logs => { + // Process and display logs in the logs container + displayLogs(logs); + }) + .catch(error => { + console.error('Error fetching logs:', error); + // Handle errors, e.g., display an alert + }); +} + +// Update the displayLogs function to generate a table +function displayLogs(logs) { + const logsContainer = $('#logsContainer'); + + // Clear previous logs + logsContainer.empty(); + + if (logs && logs.length > 0) { + // Create the table and header row + const table = $('').addClass('logs-table'); + const headerRow = ''; + table.append(headerRow); + + // Add each log as a row in the table + logs.forEach(log => { + const row = ``; + table.append(row); + }); + + // Append the table to the logsContainer + logsContainer.append(table); + + // Add a download button at the top with the current date and time in the file name + const currentDate = new Date(); + const formattedDate = currentDate.toISOString().split('T')[0]; + const formattedTime = currentDate.toTimeString().split(' ')[0].replace(/:/g, '-'); + const downloadButton = $('`; + searchResultsList.append(listItem); + }); + + // Show the search results container + $('#searchResultsContainer').show(); + } else { + // Hide the search results container if no results + $('#searchResultsContainer').hide(); + } +} +// Event listener for delete user button in search results +$('#searchResultsList').on('click', '.deleteUserButton', function () { + const usernameToDelete = $(this).data('username'); + console.log('Before fetch for user deletion'); + // Make a fetch request to delete the user + fetch(`/api/deleteUser/${usernameToDelete}`, { + method: 'DELETE', + }) + .then(response => { + console.log('Inside fetch response handler'); + if (response.ok) { + // Assuming your server sends a JSON response + return response.json(); + } else { + throw new Error(`HTTP error! Status: ${response.status}`); + } + }) + .then(data => { + console.log('User deletion success:', data); + alert('User deleted successfully'); + $('#searchResultsContainer').hide(); + }) + .catch(error => { + console.error('User deletion error:', error); + alert('Failed to delete user. Please try again.'); + // Handle errors, e.g., display an alert + }); +}); + + +function downloadExcel(allUsers) { + if (allUsers && allUsers.length > 0) { + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet('All Users'); + const headers = ['Name', 'Username', 'Email', 'Last Login', 'Job Title']; + worksheet.addRow(headers); + allUsers.forEach(user => { + const rowData = [ + user.name || '', + user.username || '', + user.email || '', + user.lastLogin ? new Date(user.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) : '', + user.jobTitle || '' + ]; + worksheet.addRow(rowData); + }); + workbook.xlsx.writeBuffer().then(buffer => { + const currentDate = new Date(); + const formattedDate = currentDate.toISOString().split('T')[0]; + const formattedTime = currentDate.toTimeString().split(' ')[0].replace(/:/g, '-'); + const fileName = `user_data_${formattedDate}_${formattedTime}.xlsx`; + const blob = new Blob([buffer], { + type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }); + saveAs(blob, fileName); + }); + } else { + console.error('No data available for download.'); + } +} + +function isStrongPassword(password) { + // Password must be at least 10 characters long + if (password.length < 10) { + return false; + } + + // Password must contain at least one uppercase letter + if (!/[A-Z]/.test(password)) { + return false; + } + + // Password must contain at least one lowercase letter + if (!/[a-z]/.test(password)) { + return false; + } + + // Password must contain at least one digit + if (!/\d/.test(password)) { + return false; + } + + // Password must contain at least one symbol + if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { + return false; + } + + return true; +} + + + +function resetFormFields() { + $('#name').val(''); + $('#username').val(''); + $('#email').val(''); + $('#password').val(''); + $('#confirmPassword').val(''); + $('#jobTitle').val(''); + } + + + $('#userForm').on('submit', function (e) { + e.preventDefault(); + + const name = $('#name').val(); + const username = $('#username').val(); + const email = $('#email').val(); + const password = $('#password').val(); + const confirmPassword = $('#confirmPassword').val(); + const jobTitle = $('#jobTitle').val(); + + if (password !== confirmPassword) { + alert('Passwords do not match. Please enter the same password in both fields.'); + return; + } + + if (!isStrongPassword(password)) { + alert('Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.'); + return; + } + + fetch('/createUser', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: name, + username: username, + email: email, + password: password, + jobTitle: jobTitle, + }), + }) + .then(response => { + if (response.status === 201) { + // Status 201 indicates successful creation + return response.json(); + } else { + return response.json().then(data => { + throw new Error(data.error || `HTTP error! Status: ${response.status}`); + }); + } + }) + .then(data => { + console.log('User registration success:', data); + alert('User registered successfully!'); + resetFormFields(); + }) + .catch(error => { + console.error('User registration error:', error); + handleRegistrationError(error); + }); + }); + +function handleRegistrationError(error) { + console.error('Registration error:', error); // Log the full error object for debugging + + let errorMessage; + + if (typeof error === 'string') { + errorMessage = error; + } else if (error instanceof Error) { + errorMessage = error.message; + } else if (error.response && error.response.data && error.response.data.error) { + errorMessage = error.response.data.error; + } else { + errorMessage = 'Unknown error'; + } + + if (errorMessage.includes('Username is already taken')) { + alert('Username is already taken. Please choose a different username.'); + } else if (errorMessage.includes('Email is already in use')) { + alert('Email is already in use. Please choose a different email.'); + } else if (errorMessage.includes('Password does not meet complexity requirements')) { + alert('Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.'); + } else if (errorMessage.includes('User registration failed')) { + alert('User registration failed. Please try again.'); + } else { + alert(`Unknown error: ${errorMessage}`); + } +} + +$('#resetPasswordForm').on('submit', function (e) { + e.preventDefault(); + + // Get values from the form + const username = $('#resetUsername').val(); + const password = $('#resetPassword').val(); + const confirmPassword = $('#resetConfirmPassword').val(); + + console.log('Username:', username); + console.log('New Password:', password); + + // Validate passwords + if (password !== confirmPassword) { + alert('Error: Passwords do not match. Please enter the same password in both fields.'); + return; + } + + // Check if the new password meets complexity requirements + if (!isStrongPassword(password)) { + alert('Error: Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.'); + return; + } + + // Make a fetch request + fetch('/reset-password', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: username, + password: password, + confirmPassword: confirmPassword, + }), + }) + .then(response => { + if (!response.ok) { + return response.text().then(errorText => { + throw new Error(`HTTP error! Status: ${response.status}, Error: ${errorText}`); + }); + } + return response.json(); + }) + .then(data => { + console.log('Success:', data); + + // Show an alert with the received data + alert(data.success || data.error || 'Password change status unknown'); + + // Optionally, you can clear the form or take other actions after the password change + $('#resetUsername').val(''); + $('#resetPassword').val(''); + $('#resetConfirmPassword').val(''); + + // You might want to hide the reset password form after submission + $('#resetPasswordFormContainer').hide(); + }) + .catch(error => { + // Handle 404 error separately to show an alert + if (error.message.includes('HTTP error! Status: 404')) { + alert('Error: User not found. Please enter a valid username.'); + } else { + console.error('Fetch Error:', error); + } + }); +}); + + + diff --git a/Sean/views/login.ejs b/Sean/views/login.ejs index 0d90b6a..f901937 100644 --- a/Sean/views/login.ejs +++ b/Sean/views/login.ejs @@ -94,5 +94,11 @@ button:hover {

If you have forgotten your password, please reset here.

+ diff --git a/Sean/views/style.css b/Sean/views/style.css index 870a9e9..1d9d4d9 100644 --- a/Sean/views/style.css +++ b/Sean/views/style.css @@ -221,4 +221,18 @@ body { background-color: #0056b3; border-color: #0056b3; } - \ No newline at end of file + .logs-table { + width: 100%; + border-collapse: collapse; + margin-top: 10px; + } + + .logs-table th, .logs-table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; + } + + .logs-table th { + background-color: #f2f2f2; + } \ No newline at end of file
IDUsernameActivityTimestamp
${log.id}${log.username}${log.activity}${log.timestamp}