UploadPath change to new path C:\\inetpub\\cprnims-web\\wwwroot\\Content\\Images
All checks were successful
Build and Deploy CPRNIMS / build-and-deploy (push) Successful in 3m35s

This commit is contained in:
rowell_m_soriano 2026-07-01 07:07:26 +08:00
parent 421b2959ec
commit 7cf4852256
12 changed files with 1044 additions and 1176 deletions

View File

@ -63,7 +63,7 @@ jobs:
$json = $config | ConvertTo-Json -Depth 5 $json = $config | ConvertTo-Json -Depth 5
$json | Out-File -FilePath "C:\ci-output\webapi\appsettings.Production.json" -Encoding utf8 $json | Out-File -FilePath "C:\ci-output\webapi\appsettings.Production.json" -Encoding utf8
Write-Host "Wrote appsettings.Production.json to webapi output (values masked in this log automatically)" Write-Host "Wrote appsettings.Production.json to webapi output (secret values not echoed)"
exit 0 exit 0
# ---- Generate production config for WebApps (uses Variables, not Secrets, since BaseUrl is not sensitive) ---- # ---- Generate production config for WebApps (uses Variables, not Secrets, since BaseUrl is not sensitive) ----
@ -100,10 +100,10 @@ jobs:
if (Test-Path "C:\inetpub\cprnims-api") { if (Test-Path "C:\inetpub\cprnims-api") {
robocopy "C:\inetpub\cprnims-api" "C:\backups\$stamp\webapi" /MIR /R:2 /W:3 | Out-Null robocopy "C:\inetpub\cprnims-api" "C:\backups\$stamp\webapi" /MIR /R:2 /W:3 | Out-Null
} }
# Exclude dynamic user images from backup - not part of deployable artifact # Exclude dynamic user images by folder NAME so the exclusion always matches
# regardless of source/destination path direction.
if (Test-Path "C:\inetpub\cprnims-web") { if (Test-Path "C:\inetpub\cprnims-web") {
robocopy "C:\inetpub\cprnims-web" "C:\backups\$stamp\webapps" /MIR /R:2 /W:3 ` robocopy "C:\inetpub\cprnims-web" "C:\backups\$stamp\webapps" /MIR /R:2 /W:3 /XD Images | Out-Null
/XD "C:\inetpub\cprnims-web\wwwroot\Content\Images" | Out-Null
} }
$stamp | Out-File -FilePath "C:\backups\latest.txt" -Encoding ascii -NoNewline $stamp | Out-File -FilePath "C:\backups\latest.txt" -Encoding ascii -NoNewline
@ -142,33 +142,18 @@ jobs:
shell: pwsh shell: pwsh
run: | run: |
# Deploy everything EXCEPT the dynamic images folder. # Deploy everything EXCEPT the dynamic images folder.
# /XD excludes the Images directory itself from mirroring (prevents deletion of subfolders). # /XD Images excludes the directory by NAME, which robocopy matches reliably
# We use the destination full path for /XD so only this specific Images folder is protected, # against the source tree it walks. This prevents /MIR from purging the live
# not any other folder named Images elsewhere in the tree. # C:\inetpub\cprnims-web\wwwroot\Content\Images folder where end users upload images.
# Note: files sitting directly inside Images\ are protected because /XD stops robocopy # NOTE: the build output contains no images, so there is nothing to re-seed -
# from treating that directory as part of the mirror scope entirely. # the live Images folder must simply be left untouched.
robocopy "C:\ci-output\webapps" "C:\inetpub\cprnims-web" /MIR /R:3 /W:5 ` robocopy "C:\ci-output\webapps" "C:\inetpub\cprnims-web" /MIR /R:3 /W:5 /XD Images
/XD "C:\inetpub\cprnims-web\wwwroot\Content\Images"
$rc = $LASTEXITCODE $rc = $LASTEXITCODE
Write-Host "ROBOCOPY EXIT CODE: $rc" Write-Host "ROBOCOPY EXIT CODE: $rc"
if ($rc -ge 8) { if ($rc -ge 8) {
throw "robocopy failed for WebApps with exit code $rc" throw "robocopy failed for WebApps with exit code $rc"
} }
# Separately sync only the static seed images that ARE part of the repo
# (1.jpg, 2.jpg, 3.jpg, 404userImage.jpg) without /PURGE or /MIR,
# so new files from the build are added but existing dynamic uploads are never deleted.
if (Test-Path "C:\ci-output\webapps\wwwroot\Content\Images") {
robocopy "C:\ci-output\webapps\wwwroot\Content\Images" `
"C:\inetpub\cprnims-web\wwwroot\Content\Images" `
/E /R:3 /W:5
$rc2 = $LASTEXITCODE
Write-Host "ROBOCOPY IMAGES EXIT CODE: $rc2"
if ($rc2 -ge 8) {
throw "robocopy failed for Images folder with exit code $rc2"
}
}
exit 0 exit 0
- name: Start app pools - name: Start app pools
@ -196,7 +181,7 @@ jobs:
if: failure() if: failure()
shell: pwsh shell: pwsh
run: | run: |
$stamp = Get-Content "C:\backups\latest.txt" -Raw $stamp = (Get-Content "C:\backups\latest.txt" -Raw).Trim()
$backupPath = "C:\backups\$stamp" $backupPath = "C:\backups\$stamp"
Write-Host "Deployment failed - rolling back to backup: $backupPath" Write-Host "Deployment failed - rolling back to backup: $backupPath"
@ -208,10 +193,10 @@ jobs:
if (Test-Path "$backupPath\webapi") { if (Test-Path "$backupPath\webapi") {
robocopy "$backupPath\webapi" "C:\inetpub\cprnims-api" /MIR /R:3 /W:5 | Out-Null robocopy "$backupPath\webapi" "C:\inetpub\cprnims-api" /MIR /R:3 /W:5 | Out-Null
} }
# Exclude dynamic images from rollback restore - preserve images uploaded after the backup was taken # Exclude Images by name on rollback too - the backup never contains Images,
# so without this /MIR would DELETE the live user-uploaded images.
if (Test-Path "$backupPath\webapps") { if (Test-Path "$backupPath\webapps") {
robocopy "$backupPath\webapps" "C:\inetpub\cprnims-web" /MIR /R:3 /W:5 ` robocopy "$backupPath\webapps" "C:\inetpub\cprnims-web" /MIR /R:3 /W:5 /XD Images | Out-Null
/XD "C:\inetpub\cprnims-web\wwwroot\Content\Images" | Out-Null
} }
Start-WebAppPool -Name "CPRNIMS-Api" Start-WebAppPool -Name "CPRNIMS-Api"

View File

@ -118,8 +118,6 @@ namespace CPRNIMS.Domain.Services.Account
} }
public async Task<UserRights> PutPostUserAccess(AccountDto itemDto) public async Task<UserRights> PutPostUserAccess(AccountDto itemDto)
{
try
{ {
await _accountDbContext.Database await _accountDbContext.Database
.ExecuteSqlRawAsync("EXEC PutPostUserAccess @ContAccId,@AdminUserId,@UserId,@AccessTypeId,@UserAccessId,@IsActive", .ExecuteSqlRawAsync("EXEC PutPostUserAccess @ContAccId,@AdminUserId,@UserId,@AccessTypeId,@UserAccessId,@IsActive",
@ -132,11 +130,5 @@ namespace CPRNIMS.Domain.Services.Account
return new UserRights(); return new UserRights();
} }
catch (SqlException ex)
{
ex.ToString();
throw;
}
}
} }
} }

View File

@ -28,7 +28,7 @@ namespace CPRNIMS.Infrastructure.ViewModel.Account
public AttachmentVM? Attachment { get; set; } public AttachmentVM? Attachment { get; set; }
public string? ProfilePictureStr { get; set; } public string? ProfilePictureStr { get; set; }
public string? NewPassword { get; set; } public string? NewPassword { get; set; }
public string UserId { get; set; } public string UserId { get; set; }= string.Empty;
public string PasswordHash { get; set; } public string? PasswordHash { get; set; }
} }
} }

View File

@ -112,7 +112,7 @@ namespace CPRNIMS.WebApi.Controllers.Account
if (user.AccessFailedCount > 3 || signInResult.IsLockedOut) if (user.AccessFailedCount > 3 || signInResult.IsLockedOut)
{ {
await _userManager.SetLockoutEnabledAsync(user, true); await _userManager.SetLockoutEnabledAsync(user, true);
await _userManager.SetLockoutEndDateAsync(user, DateTime.Now.AddMinutes(30)); await _userManager.SetLockoutEndDateAsync(user, DateTime.Now.AddHours(2));
return BadRequest(new ResponseObject return BadRequest(new ResponseObject
{ {

View File

@ -215,27 +215,10 @@ namespace CPRNIMS.WebApps.Controllers.Account
} }
public async Task<IActionResult> GetRoles() public async Task<IActionResult> GetRoles()
{ {
try var viewModels = new UserRightsVM();
{
var response = await _account.GetRoles(GetUser()); var response = await _account.GetRoles(GetUser());
if (response != null) return GetResponse(response);
{
// return Json(new { data = response });
return new JsonResult(new { data = response });
}
else
{
return RedirectToAction("Logout", "Home");
}
}
catch (Exception ex)
{
var message = ex.InnerException?.ToString() ?? ex.Message.ToString();
return Json(new { data = "No Data" });
}
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> GetUserRights(UserRightsVM viewModel) public async Task<IActionResult> GetUserRights(UserRightsVM viewModel)

View File

@ -1,6 +1,5 @@
using CPRNIMS.Domain.UIServices.Updater; using CPRNIMS.Domain.UIServices.Updater;
using CPRNIMS.WebApps.Common; using CPRNIMS.WebApps.Common;
using Microsoft.AspNetCore.StaticFiles;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);

File diff suppressed because it is too large Load Diff

View File

@ -213,7 +213,7 @@
"GetInventoryReport": "api/InventoryReports/GetInventoryReport/" "GetInventoryReport": "api/InventoryReports/GetInventoryReport/"
}, },
"ImageUploadSettings": { "ImageUploadSettings": {
"UploadPath": "C:\\WebApps\\wwwroot\\Content\\Images" "UploadPath": "C:\\inetpub\\cprnims-web\\wwwroot\\Content\\Images"
} }
} }
}, },

View File

@ -1,4 +1,5 @@
function postPutAccessRights(IsNotExist) { let isNotExist = false;
function postPutAccessRights(IsNotExist) {
loader = $('#overlay, #loader').css('z-index', 1070); loader = $('#overlay, #loader').css('z-index', 1070);
const selectedItems = Object.values(selectedProductsMap); const selectedItems = Object.values(selectedProductsMap);
@ -9,7 +10,7 @@
const userRightsList = selectedItems.map(item => { const userRightsList = selectedItems.map(item => {
return { return {
UserAccessId: item.userAccessId, UserAccessId: isNotExist,
ContAccId: item.contAccId, ContAccId: item.contAccId,
AccessTypeId: item.accessTypeId, AccessTypeId: item.accessTypeId,
IsActive: true, IsActive: true,

View File

@ -1,32 +1,3 @@
//navigate the picture
//document.addEventListener("DOMContentLoaded", function () {
// var profilePictureImage = document.getElementById("profilePictureImage");
// var profilePictureInput = document.getElementById("profilePictureInput");
// // Set a default image source for the profile picture
// profilePictureImage.src = "wwwroot/Content/Images/404userImage.jpg";
// // Add a click event listener to the profile picture to trigger the file input
// profilePictureImage.addEventListener("click", function () {
// profilePictureInput.click();
// });
// // Add a change event listener to the file input
// profilePictureInput.addEventListener("change", function () {
// var ProfilePicture = profilePictureInput.files[0];
// if (ProfilePicture) {
// // Display the selected image
// var imageURL = URL.createObjectURL(ProfilePicture);
// profilePictureImage.src = imageURL;
// } else {
// // No file selected, revert to the default image
// console.log("No file selected, using default image");
// profilePictureImage.src = "wwwroot/Content/Images/404userImage.jpg";
// }
// });
//});
let jsonObj = {}; let jsonObj = {};
function renderAccessDetailbtn(data, row) { function renderAccessDetailbtn(data, row) {
@ -39,144 +10,13 @@ function renderAccessDetailbtn(data, row) {
return buttonsHtml; return buttonsHtml;
} }
////function viewUserAccessNotExist() {
//// loader = $('#overlay, #loader');
//// $('#viewUserAccessNotExist').modal('show');
//// $('#viewUserAccessNotExist').css('z-index', 1060);
//// tableElement = $('#NotAccessDataTable');
//// tableDestroy(tableElement);
//// var submitButton = $('#btnNotAccess');
//// var totalSelectedLabel = $('#totalSelNotAccess');
//// let IsNotExist = true;
//// notAccessDataTable = tableElement.DataTable({
//// ajax: $.extend({
//// url: '/Account/GetUserRights',
//// type: 'POST',
//// data: { UserId, IsNotExist },
//// }, beforeComplete(loader)),
//// language: {
//// emptyTable: "No record available"
//// },
//// initComplete: function () {
//// var api = this.api();
//// var data = api.ajax.json();
//// if (!data || !data.data || data.data === "No Data") {
//// $('.dataTables_empty').html("No record available");
//// }
//// updateSubmitBtnVisib();
//// updateSelectedCount();
//// },
//// columns: colAccountDetailNot,
//// responsive: true,
//// error: errorHandler
//// });
//// function updateSubmitBtnVisib() {
//// var isEmpty = notAccessDataTable.data().length === 0;
//// submitButton.toggle(!isEmpty);
//// }
//// $('#NotAccessDataTable').on('change', '.select-NotAccess-checkbox', function () {
//// var row = $(this).closest('tr');
//// if ($(this).prop('checked')) {
//// row.addClass('selected-row');
//// } else {
//// row.removeClass('selected-row');
//// }
//// updateSelectedCount();
//// });
//// $('#selectAllCheckboxNotAccess').on('change', function () {
//// var isChecked = $(this).prop('checked');
//// $('.select-NotAccess-checkbox').prop('checked', isChecked);
//// if (isChecked) {
//// $('#NotAccessDataTable tbody tr').addClass('selected-row');
//// } else {
//// $('#NotAccessDataTable tbody tr').removeClass('selected-row');
//// }
//// updateSelectedCount();
//// });
//// function updateSelectedCount() {
//// var totalSelected = $('.select-NotAccess-checkbox:checked').length;
//// totalSelectedLabel.text(totalSelected);
//// }
////}
////function viewUserAccess(data) {
//// loader = $('#overlay, #loader');
//// $('#viewUserAccess').modal('show');
//// $('#viewUserAccess').css('z-index', 1050);
//// tableElement = $('#AccessDataTable');
//// tableDestroy(tableElement);
//// var submitButton = $('#btnAccess');
//// var totalSelectedLabel = $('#totalSelAccess');
//// UserId = data.id;
//// //CanvassId = data.canvassId;
//// $('#ua-EmailAddress').val(data.email);
//// $('#ua-FullName').val(data.fullName);
//// $('#uan-EmailAddress').val(data.email);
//// $('#uan-FullName').val(data.fullName);
//// accessDataTable = tableElement.DataTable({
//// ajax: $.extend({
//// url: '/Account/GetUserRights',
//// type: 'POST',
//// data: { UserId },
//// }, beforeComplete(loader)),
//// language: {
//// emptyTable: "No record available"
//// },
//// initComplete: function () {
//// var api = this.api();
//// var data = api.ajax.json();
//// if (!data || !data.data || data.data === "No Data") {
//// $('.dataTables_empty').html("No record available");
//// }
//// updateSubmitBtnVisib();
//// updateSelectedCount();
//// },
//// columns: colAccountDetail,
//// responsive: true,
//// error: errorHandler
//// });
//// function updateSubmitBtnVisib() {
//// var isEmpty = accessDataTable.data().length === 0;
//// submitButton.toggle(!isEmpty);
//// }
//// $('#AccessDataTable').on('change', '.select-Access-checkbox', function () {
//// var row = $(this).closest('tr');
//// if ($(this).prop('checked')) {
//// row.addClass('selected-row');
//// } else {
//// row.removeClass('selected-row');
//// }
//// updateSelectedCount();
//// });
//// $('#selectAllCheckboxAccess').on('change', function () {
//// var isChecked = $(this).prop('checked');
//// $('.select-Access-checkbox').prop('checked', isChecked);
//// if (isChecked) {
//// $('#AccessDataTable tbody tr').addClass('selected-row');
//// } else {
//// $('#AccessDataTable tbody tr').removeClass('selected-row');
//// }
//// updateSelectedCount();
//// });
//// function updateSelectedCount() {
//// var totalSelected = $('.select-Access-checkbox:checked').length;
//// totalSelectedLabel.text(totalSelected);
//// }
////}
////function ShowNewAccess() {
//// viewUserAccessNotExist();
////}
function showUpdateUserProfile(jsonData) { function showUpdateUserProfile(jsonData) {
jsonObj = jsonData; jsonObj = jsonData;
// jsonData.lockoutEnabled is a boolean value (true or false)
var lockoutEnabled = jsonData.lockoutEnabled; var lockoutEnabled = jsonData.lockoutEnabled;
// Get the checkbox element by its ID
var lockoutEnabledCheckbox = document.getElementById("LockoutEnabled"); var lockoutEnabledCheckbox = document.getElementById("LockoutEnabled");
// Set the checkbox state based on the boolean value
lockoutEnabledCheckbox.checked = lockoutEnabled; lockoutEnabledCheckbox.checked = lockoutEnabled;
$("#Id").val(jsonData.Id); $("#Id").val(jsonData.Id);
@ -199,29 +39,21 @@ function showUpdateUserProfile(jsonData) {
$("#UpdatedDate").val(jsonData.updatedDate); $("#UpdatedDate").val(jsonData.updatedDate);
$("#CreatedDate").val(jsonData.createdDate); $("#CreatedDate").val(jsonData.createdDate);
// Assuming jsonData.profilePicture contains the base64-encoded image
var urlContent = jsonData.url; var urlContent = jsonData.url;
// Get the img element by its ID
var imgElement = document.getElementById("profilePictureImage"); var imgElement = document.getElementById("profilePictureImage");
// Check if urlContent is not null, undefined, or an empty string
if (urlContent && urlContent.trim() !== "") { if (urlContent && urlContent.trim() !== "") {
// If not null or empty, set the src attribute of the img element to display the image
imgElement.src = urlContent; imgElement.src = urlContent;
} else { } else {
// If null or empty, set the src to the default image
imgElement.src = "/Content/Images/404userImage.jpg"; imgElement.src = "/Content/Images/404userImage.jpg";
} }
// Get the input element and label element
var inputFieldUserRole = document.getElementById("PUserRole"); var inputFieldUserRole = document.getElementById("PUserRole");
var labelUserRole = document.getElementById("PUserRoleLabel"); var labelUserRole = document.getElementById("PUserRoleLabel");
// Initialize the label text with the initial input value
labelUserRole.textContent = inputFieldUserRole.value; labelUserRole.textContent = inputFieldUserRole.value;
// Listen for changes in the input field
inputFieldUserRole.addEventListener("input", function () { inputFieldUserRole.addEventListener("input", function () {
labelUserRole.textContent = inputFieldUserRole.value; labelUserRole.textContent = inputFieldUserRole.value;
@ -230,10 +62,8 @@ function showUpdateUserProfile(jsonData) {
var inputField = document.getElementById("PFullName"); var inputField = document.getElementById("PFullName");
var label = document.getElementById("PFullNameLabel"); var label = document.getElementById("PFullNameLabel");
// Initialize the label text with the initial input value
label.textContent = inputField.value; label.textContent = inputField.value;
// Listen for changes in the input field
inputField.addEventListener("input", function () { inputField.addEventListener("input", function () {
label.textContent = inputField.value; label.textContent = inputField.value;
@ -247,7 +77,6 @@ function updateUserProfile() {
var loader = $('#overlay, #loader'); var loader = $('#overlay, #loader');
var userId = jsonObj.id; var userId = jsonObj.id;
// Get the file input element for the profile picture
var profilePictureInput = document.getElementById("profilePictureInput"); var profilePictureInput = document.getElementById("profilePictureInput");
if (profilePictureInput && profilePictureInput.files.length > 0) { if (profilePictureInput && profilePictureInput.files.length > 0) {
@ -256,21 +85,16 @@ function updateUserProfile() {
reader.onload = function (event) { reader.onload = function (event) {
var base64Image = event.target.result; var base64Image = event.target.result;
// Include the base64Image in your JSON object
var data = { var data = {
// Other properties... // Other properties...
ProfilePictureStr: base64Image ProfilePictureStr: base64Image
}; };
// Send the AJAX request
sendUpdateRequest(data, loader, userId); sendUpdateRequest(data, loader, userId);
}; };
// Read the file as data URL
reader.readAsDataURL(ProfilePicture); reader.readAsDataURL(ProfilePicture);
} else { } else {
// No file selected, send the request with an empty ProfilePictureStr
var data = { var data = {
ProfilePictureStr: '' ProfilePictureStr: ''
// Other properties...
}; };
sendUpdateRequest(data, loader, userId); sendUpdateRequest(data, loader, userId);
} }
@ -298,30 +122,27 @@ function sendUpdateRequest(data, loader, userId) {
$.ajax({ $.ajax({
url: '/Account/UpdateUserProfile', url: '/Account/UpdateUserProfile',
type: 'POST', type: 'POST',
data: { ProfilePictureStr, userId, UserName, FullName, Company, Role, Email, PhoneNumber, NewPassword, LockoutEnabled, Address }, // Send form data including the profile picture data: { ProfilePictureStr, userId, UserName, FullName, Company, Role, Email, PhoneNumber, NewPassword, LockoutEnabled, Address },
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
$('#updateUserProfile').modal('hide'); $('#updateUserProfile').modal('hide');
refreshTable(); refreshTable();
// Close the modal after 5 seconds
$('#UpdateMessage').modal('show'); $('#UpdateMessage').modal('show');
setTimeout(function () { setTimeout(function () {
$('#UpdateMessage').modal('hide'); $('#UpdateMessage').modal('hide');
}, 5000); // 5000 milliseconds (5 seconds) }, 5000);
refreshTable(); refreshTable();
clearTextModal(); clearTextModal();
} else { } else {
// User update was not successful, display the error message
alert('User update failed: ' + response.response); alert('User update failed: ' + response.response);
} }
}, },
beforeSend: function () { beforeSend: function () {
// Show the loader before making the AJAX request
loader.show(); loader.show();
}, },
complete: function () { complete: function () {
// Hide the loader after the AJAX request is complete (success or error)
loader.hide(); loader.hide();
} }
}); });
@ -363,7 +184,6 @@ function ShowModal() {
$('#createNewUser').modal('show'); $('#createNewUser').modal('show');
clearTextModal(); clearTextModal();
} }
// Function to refresh the DataTable
function refreshTable() { function refreshTable() {
userListTable.ajax.reload(); userListTable.ajax.reload();
} }
@ -373,18 +193,33 @@ function clearTextModal() {
document.getElementById("userName").value = ""; document.getElementById("userName").value = "";
document.getElementById("email").value = ""; document.getElementById("email").value = "";
document.getElementById("password").value = ""; document.getElementById("password").value = "";
// Reset the select fields to their default options
document.getElementById("company").selectedIndex = 0; document.getElementById("company").selectedIndex = 0;
document.getElementById("role").selectedIndex = 0; document.getElementById("role").selectedIndex = 0;
} }
// Function to create a new user
function addNewUser() { function addNewUser() {
var loader = $('#overlay, #loader'); var loader = $('#overlay, #loader');
var formData = $("#userRegistrationForm").serialize(); var formData = {
FullName: $('#fullName').val(),
UserName: $('#userName').val(),
Company: $('#company').val(),
DepartmentId: parseInt($('#department').val(), 10),
Role: $('#role').val(),
Email: $('#email').val(),
Password: $('#password').val()
};
showConfirmation({
title: 'User Registration',
message: 'Are you sure you want to create? This action cannot be undone.',
type: 'warning',
confirmText: 'Yes',
cancelText: 'No'
}).then((confirmed) => {
if (confirmed) {
$.ajax({ $.ajax({
url: '/Account/CreateAccount', url: '/Account/CreateAccount',
type: 'POST', type: 'POST',
data: formData, // Send the form data data: formData,
success: function (response) { success: function (response) {
if (response.success) { if (response.success) {
$('#createNewUser').modal('hide'); $('#createNewUser').modal('hide');
@ -393,10 +228,10 @@ function addNewUser() {
setTimeout(function () { setTimeout(function () {
$('#Message').modal('hide'); $('#Message').modal('hide');
}, 2000); }, 2000);
closeModal('modalCreate');
refreshTable(); refreshTable();
clearTextModal(); clearTextModal();
} else { } else {
// User creation was not successful, display the error message
console.log('User creation failed:', response.response); console.log('User creation failed:', response.response);
$('#createNewUser').modal('hide'); $('#createNewUser').modal('hide');
alert('User creation failed: ' + response.response); alert('User creation failed: ' + response.response);
@ -404,32 +239,54 @@ function addNewUser() {
}, },
beforeSend: function () { beforeSend: function () {
// Show the loader before making the AJAX request
loader.show(); loader.show();
}, },
complete: function () { complete: function () {
// Hide the loader after the AJAX request is complete (success or error)
loader.hide(); loader.hide();
} }
}); });
}
});
} }
// Use this function to populate roles //Population
function populateDepartment() {
$.ajax({
url: "/Account/GetDepartment",
success: function (response) {
if (response && response.data && Array.isArray(response.data)) {
var $department = $('#department');
$department.empty();
response.data.forEach(function (item) {
$department.append(
$('<option>')
.val(item.departmentId)
.text(item.department)
);
});
}
else {
console.error("Invalid response:", response);
}
},
error: function (xhr, status, error) {
console.error("AJAX ERROR");
console.log(xhr.status);
console.log(xhr.responseText);
console.log(error);
}
});
}
function populateRoles() { function populateRoles() {
$.ajax({ $.ajax({
url: "/Account/GetRoles", // Update this with your actual controller and action url: "/Account/GetRoles",
success: function (data) { success: function (data) {
//console.log('Received data:', data);
// Assuming data is an object with a property 'data' that contains the array of roles
var roles = data.data; var roles = data.data;
// Ensure roles is an array before processing
if (Array.isArray(roles)) { if (Array.isArray(roles)) {
// Clear existing options in the select element
$('#role').empty(); $('#role').empty();
// Populate the select element with options
roles.forEach(function (role) { roles.forEach(function (role) {
$('#role').append($('<option>', { $('#role').append($('<option>', {
value: role, value: role,
@ -443,25 +300,4 @@ function populateRoles() {
} }
}); });
} }
//$(document).ready(function () {
// loader = $('#overlay, #loader');
// //UserRights = document.getElementById("roleRights").value;
// populateRoles();
// userListTable = $('#UserListTable').DataTable({
// ajax: $.extend({
// url: '/Account/GetAllUsers',
// type: 'GET',
// }, beforeComplete(loader)),
// initComplete: initCompleteCallback,
// columns: colOnUserList,
// columnDefs: colOnColDef,
// language: {
// emptyTable: "No record available"
// },
// rowCallback: rowAccountCallback,
// responsive: true,
// error: errorHandler
// });
//})

View File

@ -229,11 +229,11 @@ function handleLogout() {
* Initializes the session timeout manager * Initializes the session timeout manager
*/ */
function initialize() { function initialize() {
console.log('Session timeout initialized:', { //console.log('Session timeout initialized:', {
timeout: CONFIG.timeoutMinutes + ' minutes', // timeout: CONFIG.timeoutMinutes + ' minutes',
warning: CONFIG.warningMinutes + ' minutes before timeout', // warning: CONFIG.warningMinutes + ' minutes before timeout',
checkInterval: CONFIG.checkIntervalSeconds + ' seconds' // checkInterval: CONFIG.checkIntervalSeconds + ' seconds'
}); //});
// Validate configuration // Validate configuration
if (CONFIG.warningMinutes >= CONFIG.timeoutMinutes) { if (CONFIG.warningMinutes >= CONFIG.timeoutMinutes) {
@ -286,7 +286,7 @@ function handleLogout() {
clearInterval(checkInterval); clearInterval(checkInterval);
checkInterval = null; checkInterval = null;
} }
console.log('Session timeout manager cleaned up'); /* console.log('Session timeout manager cleaned up');*/
} }
// Initialize when DOM is ready // Initialize when DOM is ready

View File

@ -0,0 +1,817 @@
:root {
--teal-900: #04342C;
--teal-800: #085041;
--teal-700: #0F6E56;
--teal-600: #0F7A60;
--teal-500: #1D9E75;
--teal-400: #3dbf96;
--teal-100: #9FE1CB;
--teal-50: #E1F5EE;
--surface: #f6f8f7;
--card-bg: #ffffff;
--text-primary: #0d1f1a;
--text-secondary: #4a6b61;
--text-muted: #8ba89f;
--border: #ddeee8;
--shadow-sm: 0 1px 3px rgba(15,110,86,.07), 0 1px 2px rgba(15,110,86,.04);
--shadow-md: 0 4px 16px rgba(15,110,86,.10), 0 1px 4px rgba(15,110,86,.06);
--shadow-lg: 0 8px 32px rgba(15,110,86,.13), 0 2px 8px rgba(15,110,86,.07);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 24px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'DM Sans', sans-serif;
background: var(--surface);
color: var(--text-primary);
min-height: 100vh;
}
/* ── Page header ── */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 32px 0;
margin-bottom: 24px;
}
.page-title-group h1 {
font-family: 'Space Grotesk', sans-serif;
font-size: 22px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -.3px;
}
.page-title-group p {
font-size: 13.5px;
color: var(--text-muted);
margin-top: 2px;
}
.btn-create {
display: inline-flex;
align-items: center;
gap: 8px;
background: var(--teal-600);
color: #fff;
border: none;
border-radius: var(--radius-md);
padding: 10px 20px;
font-size: 14px;
font-weight: 500;
font-family: 'DM Sans', sans-serif;
cursor: pointer;
transition: background .18s, transform .12s;
box-shadow: 0 2px 8px rgba(29,158,117,.25);
}
.btn-create:hover {
background: var(--teal-700);
transform: translateY(-1px);
}
.btn-create:active {
transform: scale(.98);
}
/* ── Stat cards ── */
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 0 32px;
margin-bottom: 24px;
}
.stat-card {
background: var(--card-bg);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
padding: 18px 20px;
box-shadow: var(--shadow-sm);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--teal-500);
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.stat-card.danger::before {
background: #E24B4A;
}
.stat-card.amber::before {
background: #EF9F27;
}
.stat-card.blue::before {
background: #378ADD;
}
.stat-label {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: .6px;
color: var(--text-muted);
margin-bottom: 6px;
}
.stat-value {
font-family: 'Space Grotesk', sans-serif;
font-size: 26px;
font-weight: 600;
color: var(--text-primary);
line-height: 1;
}
.stat-icon {
position: absolute;
right: 16px;
top: 16px;
width: 36px;
height: 36px;
border-radius: var(--radius-sm);
background: var(--teal-50);
display: flex;
align-items: center;
justify-content: center;
color: var(--teal-700);
font-size: 17px;
}
.stat-card.danger .stat-icon {
background: #FCEBEB;
color: #A32D2D;
}
.stat-card.amber .stat-icon {
background: #FAEEDA;
color: #854F0B;
}
.stat-card.blue .stat-icon {
background: #E6F1FB;
color: #185FA5;
}
/* ── Main card / table ── */
.table-card {
margin: 0 32px 32px;
background: var(--card-bg);
border-radius: var(--radius-xl);
border: 1px solid var(--border);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.table-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid var(--border);
background: linear-gradient(135deg, var(--teal-900) 0%, var(--teal-700) 100%);
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
.table-card-header h2 {
font-family: 'Space Grotesk', sans-serif;
font-size: 16px;
font-weight: 600;
color: #fff;
letter-spacing: -.2px;
}
.search-box {
display: flex;
align-items: center;
gap: 8px;
background: rgba(255,255,255,.12);
border: 1px solid rgba(255,255,255,.2);
border-radius: var(--radius-md);
padding: 7px 14px;
color: #fff;
font-size: 13px;
}
.search-box input {
background: transparent;
border: none;
outline: none;
color: #fff;
font-size: 13px;
font-family: 'DM Sans', sans-serif;
width: 180px;
}
.search-box input::placeholder {
color: rgba(255,255,255,.55);
}
table.um-table {
width: 100%;
border-collapse: collapse;
}
table.um-table thead th {
font-size: 11.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .5px;
color: var(--text-muted);
padding: 13px 16px;
border-bottom: 1px solid var(--border);
background: #fafcfb;
white-space: nowrap;
}
table.um-table tbody tr {
border-bottom: 1px solid var(--border);
transition: background .14s;
}
table.um-table tbody tr:last-child {
border-bottom: none;
}
table.um-table tbody tr:hover {
background: var(--teal-50);
}
table.um-table td {
padding: 14px 16px;
font-size: 13.5px;
color: var(--text-primary);
vertical-align: middle;
}
/* user cell with avatar */
.user-cell {
display: flex;
align-items: center;
gap: 10px;
}
.avatar {
width: 34px;
height: 34px;
border-radius: 50%;
background: var(--teal-100);
color: var(--teal-800);
font-size: 12px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-family: 'Space Grotesk', sans-serif;
}
.avatar.blue {
background: #B5D4F4;
color: #0C447C;
}
.avatar.amber {
background: #FAC775;
color: #633806;
}
.avatar.purple {
background: #CECBF6;
color: #3C3489;
}
.avatar.coral {
background: #F5C4B3;
color: #712B13;
}
.user-name {
font-weight: 500;
font-size: 13.5px;
}
.user-sub {
font-size: 11.5px;
color: var(--text-muted);
}
/* badges */
.badge-status, .badge-role, .badge-company {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
border-radius: 20px;
font-size: 11.5px;
font-weight: 500;
}
.badge-status.active {
background: #EAF3DE;
color: #3B6D11;
}
.badge-status.inactive {
background: #FCEBEB;
color: #A32D2D;
}
.badge-role {
background: var(--teal-50);
color: var(--teal-800);
}
.badge-company {
background: #E6F1FB;
color: #185FA5;
}
/* action buttons */
.action-wrap {
display: flex;
gap: 6px;
}
.action-btn {
width: 30px;
height: 30px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--card-bg);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
font-size: 14px;
transition: all .15s;
}
.action-btn:hover.edit {
background: var(--teal-50);
border-color: var(--teal-400);
color: var(--teal-700);
}
.action-btn:hover.access {
background: #E6F1FB;
border-color: #85B7EB;
color: #185FA5;
}
.action-btn:hover.delete {
background: #FCEBEB;
border-color: #F09595;
color: #A32D2D;
}
/* pagination */
.table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 24px;
border-top: 1px solid var(--border);
background: #fafcfb;
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
}
.table-footer span {
font-size: 13px;
color: var(--text-muted);
}
.pager {
display: flex;
gap: 4px;
}
.pager-btn {
width: 30px;
height: 30px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: var(--card-bg);
font-size: 12.5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
transition: all .15s;
}
.pager-btn.active {
background: var(--teal-600);
border-color: var(--teal-600);
color: #fff;
}
.pager-btn:hover:not(.active) {
background: var(--teal-50);
border-color: var(--teal-400);
color: var(--teal-700);
}
/* ── MODAL styles ── */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(4,52,44,.45);
backdrop-filter: blur(3px);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal-overlay.show {
display: flex;
}
.modal-box {
background: var(--card-bg);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
width: 90%;
max-width: 520px;
overflow: hidden;
animation: slideUp .22s cubic-bezier(.34,1.3,.64,1);
}
.modal-box.lg {
max-width: 760px;
}
.modal-box.xl {
max-width: 980px;
}
.modal-head {
padding: 22px 24px 18px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, var(--teal-900), var(--teal-700));
}
.modal-head h3 {
font-family: 'Space Grotesk', sans-serif;
font-size: 15px;
font-weight: 600;
color: #fff;
display: flex;
align-items: center;
gap: 8px;
}
.modal-head .icon-badge {
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
background: rgba(255,255,255,.15);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
}
.modal-close {
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
border: 1px solid rgba(255,255,255,.25);
background: rgba(255,255,255,.1);
color: rgba(255,255,255,.8);
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
transition: background .15s;
}
.modal-close:hover {
background: rgba(255,255,255,.2);
color: #fff;
}
.modal-body {
padding: 24px;
max-height: 70vh;
overflow-y: auto;
}
.form-label {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .5px;
color: var(--text-muted);
display: block;
margin-bottom: 6px;
}
.form-input {
width: 100%;
padding: 9px 13px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: 13.5px;
font-family: 'DM Sans', sans-serif;
color: var(--text-primary);
background: var(--card-bg);
transition: border .15s, box-shadow .15s;
outline: none;
}
.form-input:focus {
border-color: var(--teal-500);
box-shadow: 0 0 0 3px rgba(29,158,117,.12);
}
.form-input[readonly] {
background: var(--surface);
color: var(--text-muted);
}
select.form-input {
cursor: pointer;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
.form-group {
margin-bottom: 16px;
}
.modal-foot {
padding: 16px 24px;
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
background: #fafcfb;
}
.btn-secondary {
padding: 9px 18px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--card-bg);
font-size: 13.5px;
font-family: 'DM Sans', sans-serif;
font-weight: 500;
color: var(--text-secondary);
cursor: pointer;
transition: all .15s;
}
.btn-secondary:hover {
background: var(--surface);
border-color: var(--teal-400);
color: var(--teal-700);
}
.btn-primary {
padding: 9px 22px;
border: none;
border-radius: var(--radius-md);
background: var(--teal-600);
font-size: 13.5px;
font-family: 'DM Sans', sans-serif;
font-weight: 500;
color: #fff;
cursor: pointer;
transition: all .15s;
box-shadow: 0 2px 8px rgba(29,158,117,.22);
}
.btn-primary:hover {
background: var(--teal-700);
transform: translateY(-1px);
}
/* Profile modal sidebar */
.profile-layout {
display: grid;
grid-template-columns: 220px 1fr;
gap: 0;
}
.profile-sidebar {
background: var(--teal-900);
padding: 24px 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.profile-avatar-lg {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--teal-500);
color: #fff;
font-size: 28px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Space Grotesk', sans-serif;
border: 3px solid rgba(255,255,255,.2);
}
.profile-sidebar h4 {
color: #fff;
font-size: 14px;
font-weight: 600;
text-align: center;
}
.profile-sidebar span {
color: var(--teal-100);
font-size: 12px;
}
.profile-nav {
width: 100%;
margin-top: 8px;
}
.profile-nav a {
display: flex;
align-items: center;
gap: 8px;
padding: 9px 12px;
border-radius: var(--radius-sm);
color: rgba(255,255,255,.65);
font-size: 13px;
text-decoration: none;
transition: all .15s;
margin-bottom: 2px;
}
.profile-nav a:hover, .profile-nav a.active {
background: rgba(255,255,255,.1);
color: #fff;
}
.profile-content {
padding: 24px;
overflow-y: auto;
max-height: 60vh;
}
/* Nav tabs inside modal */
.um-tabs {
display: flex;
gap: 0;
border-bottom: 1px solid var(--border);
margin-bottom: 20px;
}
.um-tab {
padding: 10px 18px;
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all .15s;
}
.um-tab.active {
color: var(--teal-600);
border-bottom-color: var(--teal-600);
}
.um-tab-pane {
display: none;
}
.um-tab-pane.active {
display: block;
}
/* Access rights table inside modal */
table.access-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
table.access-table th {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .4px;
color: var(--text-muted);
padding: 9px 12px;
background: #fafcfb;
border-bottom: 1px solid var(--border);
}
table.access-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--border);
vertical-align: middle;
}
table.access-table tr:last-child td {
border-bottom: none;
}
table.access-table tr:hover td {
background: var(--teal-50);
}
.counter-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 12px;
background: var(--teal-50);
border-radius: 20px;
font-size: 13px;
color: var(--teal-800);
font-weight: 500;
margin-bottom: 14px;
}
.modal-box.shake {
animation: modalShake .35s cubic-bezier(.36,.07,.19,.97);
}
/* Checkbox custom */
input[type="checkbox"] {
accent-color: var(--teal-600);
width: 15px;
height: 15px;
cursor: pointer;
}
/* scrollbar */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--teal-100);
border-radius: 4px;
}
@keyframes modalShake {
0%, 100% {
transform: translateX(0);
}
20% {
transform: translateX(-8px);
}
40% {
transform: translateX(8px);
}
60% {
transform: translateX(-6px);
}
80% {
transform: translateX(6px);
}
}