mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Merge pull request #115 from LD-Reborn/99-feature-add-groups-view
99 feature add groups view
This commit is contained in:
@@ -18,55 +18,86 @@ public class GroupsController : Controller
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("Index")]
|
||||
public async Task<IEnumerable<GroupModel>> Index(GroupsIndexRequestModel requestModel)
|
||||
[HttpGet("Get")]
|
||||
public async Task<GroupsGetResponseModel> GetAsync(GroupsIndexRequestModel model)
|
||||
{
|
||||
string? cn = requestModel.Cn;
|
||||
List<string> attributes = [.. _ldap.GroupsAttributes];
|
||||
if (!requestModel.GidNumber) attributes.Remove("gidNumber");
|
||||
if (!requestModel.Permissions) attributes.Remove("description");
|
||||
IEnumerable<GroupModel> groups;
|
||||
if (cn is null)
|
||||
if (model is null)
|
||||
{
|
||||
groups = await _ldap.ListGroupsAsync([.. attributes]);
|
||||
return new GroupsGetResponseModel(
|
||||
successful: false,
|
||||
groupModels: null,
|
||||
exception: "Unable to create a group because the GroupsCreateRequestModel is null.");
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
try
|
||||
|
||||
string? cn = model.Cn;
|
||||
List<string> attributes = [.. _ldap.GroupsAttributes];
|
||||
if (!model.GidNumber) attributes.Remove("gidNumber");
|
||||
if (!model.Permissions) attributes.Remove("description");
|
||||
IEnumerable<GroupModel> groups;
|
||||
if (cn is null)
|
||||
{
|
||||
groups = [await _ldap.GetGroupByCnAsync(cn, [.. attributes])];
|
||||
groups = await _ldap.ListGroupsAsync([.. attributes]);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
else
|
||||
{
|
||||
groups = [];
|
||||
try
|
||||
{
|
||||
groups = [await _ldap.GetGroupByCnAsync(cn, [.. attributes])];
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
groups = [];
|
||||
}
|
||||
}
|
||||
return new(true, groups);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
if (model.Cn is not null)
|
||||
{
|
||||
_logger.LogError("Unable to get group {model.Cn}: {ex.Message} - {ex.StackTrace}", [model.Cn, ex.Message, ex.StackTrace]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Unable to get groups: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
||||
}
|
||||
return new(false, null, ex.Message);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
[HttpGet("Delete")]
|
||||
public async Task<bool> Delete(string uid)
|
||||
[HttpDelete("Delete")]
|
||||
public async Task<GroupsDeleteResponseModel> Delete(string uid)
|
||||
{
|
||||
return await Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _ldap.DeleteGroupAsync(uid);
|
||||
return true;
|
||||
return new GroupsDeleteResponseModel(true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
return new GroupsDeleteResponseModel(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("Create")]
|
||||
public async Task<bool> Create(string cn, string gidNumber, GroupPermission[] permissions, string description)
|
||||
[HttpPost("Create")]
|
||||
public async Task<GroupsCreateResponseModel> Create([FromBody]GroupsCreateRequestModel model)
|
||||
{
|
||||
if (model is null)
|
||||
{
|
||||
return new GroupsCreateResponseModel(
|
||||
successful: false,
|
||||
exception: "Unable to create a group because the GroupsCreateRequestModel is null.");
|
||||
}
|
||||
try
|
||||
{
|
||||
description ??= JsonSerializer.Serialize(new GroupPermissions() {Permissions = []});
|
||||
List<GroupPermission> permissions = model.Permissions;
|
||||
string gidNumber = "0"; // TODO implement counter
|
||||
string cn = model.Cn;
|
||||
string displayName = model.DisplayName;
|
||||
|
||||
LdapAttributeSet attributeSet =
|
||||
[
|
||||
@@ -76,29 +107,26 @@ public class GroupsController : Controller
|
||||
new LdapAttribute("gidNumber", gidNumber),
|
||||
new LdapAttribute(
|
||||
"description",
|
||||
JsonSerializer.Serialize(new GroupPermissions()
|
||||
{
|
||||
Permissions = [.. permissions]
|
||||
}))
|
||||
JsonSerializer.Serialize(new GroupDescription(){DisplayName = displayName, Permissions = permissions}))
|
||||
];
|
||||
|
||||
await _ldap.CreateGroup(cn, attributeSet);
|
||||
return true;
|
||||
return new(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Unable to create user: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
||||
return false;
|
||||
_logger.LogError("Unable to create group: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
||||
return new(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("Update")]
|
||||
public async Task<bool> Update([FromBody]GroupsModifyRequestModel requestModel)
|
||||
[HttpPatch("Update")]
|
||||
public async Task<GroupsUpdateResponseModel> Update([FromBody]GroupsModifyRequestModel requestModel)
|
||||
{
|
||||
if (requestModel is null)
|
||||
{
|
||||
_logger.LogError("Unable to update a group because the GroupsModifyRequestModel is null");
|
||||
return false;
|
||||
return new(false, "Unable to update a group because the GroupsModifyRequestModel is null");
|
||||
}
|
||||
string cn = requestModel.Cn;
|
||||
|
||||
@@ -111,10 +139,10 @@ public class GroupsController : Controller
|
||||
{
|
||||
await _ldap.UpdateGroup(cn, "gidNumber", requestModel.GidNumber);
|
||||
}
|
||||
if (requestModel.Permissions is not null)
|
||||
if (requestModel.Description is not null)
|
||||
{
|
||||
await _ldap.UpdateGroup(cn, "description", JsonSerializer.Serialize(requestModel.Permissions));
|
||||
await _ldap.UpdateGroup(cn, "description", JsonSerializer.Serialize(requestModel.Description));
|
||||
}
|
||||
return true;
|
||||
return new(true);
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,15 @@ public class HomeController : Controller
|
||||
Workplace = user.Description?.Workplace ?? ""
|
||||
});
|
||||
}
|
||||
return View(new UsersIndexViewModel() { UserTableViewModels = UserTableViewModels }); }
|
||||
return View(new UsersIndexViewModel() { UserTableViewModels = UserTableViewModels });
|
||||
}
|
||||
|
||||
[HttpGet("Groups")]
|
||||
public async Task<ActionResult> GroupsAsync()
|
||||
{
|
||||
IEnumerable<GroupModel> groups = await _ldap.ListGroupsAsync();
|
||||
return View(new GroupsIndexViewModel(groups));
|
||||
}
|
||||
|
||||
[HttpPost("Login")]
|
||||
public async Task<ActionResult> Login(string username, string password)
|
||||
|
||||
@@ -6,8 +6,13 @@ namespace Berufsschule_HAM.Models;
|
||||
|
||||
public class GroupModel
|
||||
{
|
||||
[JsonPropertyName("Cn")]
|
||||
public required string Cn { get; set; }
|
||||
[JsonPropertyName("DisplayName")]
|
||||
public string DisplayName { get; set; }
|
||||
[JsonPropertyName("GidNumber")]
|
||||
public string? GidNumber { get; set; }
|
||||
[JsonPropertyName("Permissions")]
|
||||
public List<GroupPermission> Permissions { get; set; }
|
||||
public GroupModel(Dictionary<string, string> ldapData)
|
||||
{
|
||||
@@ -20,13 +25,18 @@ public class GroupModel
|
||||
}
|
||||
else
|
||||
{
|
||||
Permissions = JsonSerializer.Deserialize<GroupPermissions>(descriptionValue)?.Permissions ?? [];
|
||||
GroupDescription? description = JsonSerializer.Deserialize<GroupDescription>(descriptionValue);
|
||||
DisplayName = description?.DisplayName ?? Cn;
|
||||
Permissions = description?.Permissions ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupPermissions
|
||||
public class GroupDescription
|
||||
{
|
||||
[JsonPropertyName("DisplayName")]
|
||||
public required string DisplayName { get; set; }
|
||||
[JsonPropertyName("Permissions")]
|
||||
public required List<GroupPermission> Permissions { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Berufsschule_HAM.Models;
|
||||
|
||||
public class GroupsIndexRequestModel
|
||||
@@ -7,10 +9,24 @@ public class GroupsIndexRequestModel
|
||||
public bool Permissions { get; set; } = true;
|
||||
}
|
||||
|
||||
public class GroupsCreateRequestModel
|
||||
{
|
||||
[JsonPropertyName("Cn")]
|
||||
public required string Cn { get; set; }
|
||||
[JsonPropertyName("DisplayName")]
|
||||
public required string DisplayName { get; set; }
|
||||
[JsonPropertyName("Permissions")]
|
||||
public required List<GroupPermission> Permissions { get; set; }
|
||||
}
|
||||
|
||||
public class GroupsModifyRequestModel
|
||||
{
|
||||
[JsonPropertyName("Cn")]
|
||||
public required string Cn { get; set; }
|
||||
[JsonPropertyName("NewCn")]
|
||||
public string? NewCn { get; set; } = null;
|
||||
[JsonPropertyName("GidNumber")]
|
||||
public string? GidNumber { get; set; } = null;
|
||||
public GroupPermissions? Permissions { get; set; } = null;
|
||||
[JsonPropertyName("Description")]
|
||||
public GroupDescription? Description { get; set; } = null;
|
||||
}
|
||||
29
src/Models/GroupsResponseModels.cs
Normal file
29
src/Models/GroupsResponseModels.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Berufsschule_HAM.Models;
|
||||
|
||||
public class GroupsGetResponseModel(bool successful, IEnumerable<GroupModel>? groupModels, string exception = "None")
|
||||
{
|
||||
public bool Success { get; set; } = successful;
|
||||
[JsonPropertyName("GroupModels")]
|
||||
public IEnumerable<GroupModel> GroupModels { get; set; } = groupModels ?? [];
|
||||
public string? Exception { get; set; } = exception;
|
||||
}
|
||||
|
||||
public class GroupsCreateResponseModel(bool successful, string exception = "None")
|
||||
{
|
||||
public bool Success { get; set; } = successful;
|
||||
public string? Exception { get; set; } = exception;
|
||||
}
|
||||
|
||||
public class GroupsUpdateResponseModel(bool successful, string exception = "None")
|
||||
{
|
||||
public bool Success { get; set; } = successful;
|
||||
public string? Exception { get; set; } = exception;
|
||||
}
|
||||
|
||||
public class GroupsDeleteResponseModel(bool successful, string exception = "None")
|
||||
{
|
||||
public bool Success { get; set; } = successful;
|
||||
public string? Exception { get; set; } = exception;
|
||||
}
|
||||
38
src/Models/GroupsViewModels.cs
Normal file
38
src/Models/GroupsViewModels.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace Berufsschule_HAM.Models;
|
||||
|
||||
public class GroupsIndexViewModel
|
||||
{
|
||||
public List<GroupsTableViewModel> GroupsTableViewModels { get; set; } = [];
|
||||
|
||||
public GroupsIndexViewModel(IEnumerable<GroupModel> groupModels)
|
||||
{
|
||||
foreach (GroupModel model in groupModels)
|
||||
{
|
||||
GroupsTableViewModels.Add(new()
|
||||
{
|
||||
Cn = model.Cn,
|
||||
Group = model.DisplayName,
|
||||
CanInventorize = model.Permissions.Any(x => x == GroupPermission.CanInventorize),
|
||||
CanManageAssets = model.Permissions.Any(x => x == GroupPermission.CanManageAssets),
|
||||
CanManageGroups = model.Permissions.Any(x => x == GroupPermission.CanManageGroups),
|
||||
CanManageLocations = model.Permissions.Any(x => x == GroupPermission.CanManageLocations),
|
||||
CanManageUsers = model.Permissions.Any(x => x == GroupPermission.CanManageUsers)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public class GroupsTableViewModel
|
||||
{
|
||||
public required string Cn { get; set; }
|
||||
public required string Group { get; set; }
|
||||
public required bool CanInventorize { get; set; }
|
||||
public required bool CanManageUsers { get; set; }
|
||||
public required bool CanManageLocations { get; set; }
|
||||
public required bool CanManageAssets { get; set; }
|
||||
public required bool CanManageGroups { get; set; }
|
||||
}
|
||||
@@ -16,8 +16,8 @@
|
||||
<data name="Jump to content" xml:space="preserve">
|
||||
<value>Zum Inhalt springen</value>
|
||||
</data>
|
||||
<data name="Home" xml:space="preserve">
|
||||
<value>Startseite</value>
|
||||
<data name="Quick-Actions" xml:space="preserve">
|
||||
<value>Schnellaktionen</value>
|
||||
</data>
|
||||
<data name="Inventory" xml:space="preserve">
|
||||
<value>Inventur</value>
|
||||
|
||||
@@ -134,6 +134,11 @@ public partial class LdapService : IDisposable
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<GroupModel>> ListGroupsAsync()
|
||||
{
|
||||
return await ListGroupsAsync(GroupsAttributes);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<GroupModel>> ListGroupsAsync(string[] attributes)
|
||||
{
|
||||
List<GroupModel> returnValue = [];
|
||||
|
||||
@@ -484,10 +484,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const input = updateForm.querySelector(`[name="${key}"]`);
|
||||
if (input) input.value = value;
|
||||
}
|
||||
console.log("responseJson:");
|
||||
console.log(responseJson);
|
||||
console.log("asset:");
|
||||
console.log(asset);
|
||||
|
||||
// Handle nested description fields
|
||||
if (asset.Description) {
|
||||
@@ -529,12 +525,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const formData = new FormData(updateForm);
|
||||
const jsonData = {};
|
||||
console.log("DEBUG@1");
|
||||
console.log(jsonData);
|
||||
for (const [key, value] of formData.entries()) {
|
||||
console.log("DEBUG@1.1");
|
||||
console.log(key);
|
||||
console.log(value);
|
||||
if (!value) continue;
|
||||
const keys = key.split('.');
|
||||
let target = jsonData;
|
||||
@@ -544,8 +535,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
target[keys[keys.length - 1]] = value;
|
||||
}
|
||||
console.log("DEBUG@2");
|
||||
console.log(jsonData);
|
||||
|
||||
const attributes = {};
|
||||
document.querySelectorAll('#updateAttributesContainer .attribute-row').forEach(row => {
|
||||
|
||||
488
src/Views/Home/Groups.cshtml
Normal file
488
src/Views/Home/Groups.cshtml
Normal file
@@ -0,0 +1,488 @@
|
||||
@using Microsoft.AspNetCore.Mvc.Localization
|
||||
@using Berufsschule_HAM.Models
|
||||
@using System.Buffers.Text
|
||||
@model GroupsIndexViewModel
|
||||
@inject IViewLocalizer T
|
||||
@{
|
||||
ViewData["Title"] = T["Groups"];
|
||||
}
|
||||
|
||||
|
||||
<div class="container py-4">
|
||||
<h2 class="mb-3">@T["Groups"]</h2>
|
||||
|
||||
|
||||
<div class="mb-4 d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createGroupModal">
|
||||
@T["Create group"]
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: center">Group</th>
|
||||
<th style="text-align: center">@T["Can"]:<br/>@T["inventorize"]</th>
|
||||
<th style="text-align: center">@T["Can"]:<br/>@T["manage users"]</th>
|
||||
<th style="text-align: center">@T["Can"]:<br/>@T["manage locations"]</th>
|
||||
<th style="text-align: center">@T["Can"]:<br/>@T["manage assets"]</th>
|
||||
<th style="text-align: center">@T["Can"]:<br/>@T["manage groups"]</th>
|
||||
<th style="text-align: center">@T["Action"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{
|
||||
foreach (GroupsTableViewModel groupTableViewModel in Model.GroupsTableViewModels)
|
||||
{
|
||||
<tr>
|
||||
<td style="text-align: center">@groupTableViewModel.Group</td>
|
||||
<td style="text-align: center">@(groupTableViewModel.CanInventorize ? "☑️" : "❌")</td>
|
||||
<td style="text-align: center">@(groupTableViewModel.CanManageUsers ? "☑️" : "❌")</td>
|
||||
<td style="text-align: center">@(groupTableViewModel.CanManageLocations ? "☑️" : "❌")</td>
|
||||
<td style="text-align: center">@(groupTableViewModel.CanManageAssets ? "☑️" : "❌")</td>
|
||||
<td style="text-align: center">@(groupTableViewModel.CanManageGroups ? "☑️" : "❌")</td>
|
||||
<td style="text-align: center">
|
||||
<div class="d-flex gap-2 justify-content-center">
|
||||
<button class="btn btn-sm btn-warning btn-update"
|
||||
data-group-id="@groupTableViewModel.Cn"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updateGroupModal">
|
||||
@T["Update"]
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger btn-delete"
|
||||
data-group-id="@groupTableViewModel.Cn"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteModal">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="deleteModalLabel">Confirm Delete</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the group <strong id="groupName"></strong> (ID: <span id="groupId"></span>)?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<form id="deleteForm" method="post" action="">
|
||||
<button type="submit" class="btn btn-danger">Yes, Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
// Simple toast helper
|
||||
function showToast(message, type) {
|
||||
const toastContainer = document.getElementById('toastContainer') || createToastContainer();
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast align-items-center text-white bg-${type} border-0`;
|
||||
toast.role = 'alert';
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
||||
</div>
|
||||
`;
|
||||
toastContainer.appendChild(toast);
|
||||
const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
|
||||
bsToast.show();
|
||||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||||
}
|
||||
|
||||
function createToastContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'toastContainer';
|
||||
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const deleteModal = document.getElementById('deleteModal');
|
||||
let currentButton = null; // The delete button that opened the modal
|
||||
|
||||
deleteModal.addEventListener('show.bs.modal', event => {
|
||||
currentButton = event.relatedTarget; // Button that triggered the modal
|
||||
const groupId = currentButton.getAttribute('data-group-id');
|
||||
const groupName = currentButton.getAttribute('data-group-name');
|
||||
|
||||
deleteModal.querySelector('#groupId').textContent = groupId;
|
||||
deleteModal.querySelector('#groupName').textContent = groupName;
|
||||
|
||||
// Store the delete URL for later use
|
||||
deleteModal.querySelector('#deleteForm').dataset.url = `/Groups/Delete?uid=${groupId}`;
|
||||
});
|
||||
|
||||
// Handle submit of deleteForm via fetch()
|
||||
const deleteForm = document.getElementById('deleteForm');
|
||||
deleteForm.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const url = deleteForm.dataset.url;
|
||||
const groupId = deleteModal.querySelector('#groupId').textContent;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}//,
|
||||
//body: JSON.stringify({ id: groupId }) // Use this for Post requests with [FromBody] parameters like in /Groups/Update
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Close the modal
|
||||
const modal = bootstrap.Modal.getInstance(deleteModal);
|
||||
modal.hide();
|
||||
|
||||
// Remove the row from the table
|
||||
const row = currentButton.closest('tr');
|
||||
row.classList.add('table-danger');
|
||||
setTimeout(() => row.remove(), 300);
|
||||
|
||||
showToast('@T["Group deleted successfully"]', 'success');
|
||||
} else {
|
||||
let exception = result.exception;
|
||||
switch (exception) {
|
||||
case "None":
|
||||
exception = "@T["Unknown error"]";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
showToast(`${@T["Error"] + ": " + exception}`, 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showToast('@T["Error contacting server"]', 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Groups Create Modal -->
|
||||
<div class="modal fade" id="createGroupModal" tabindex="-1" aria-labelledby="createGroupModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-primary text-white">
|
||||
<h5 class="modal-title" id="createGroupModalLabel">@T["Create Group"]</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form id="createGroupForm">
|
||||
<div class="modal-body">
|
||||
<div class="row g-3 justify-content-center">
|
||||
<!-- Basic Info -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">@T["Group ID"] *</label>
|
||||
<input type="text" class="form-control" name="Cn" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">@T["Display name"] *</label>
|
||||
<input type="text" class="form-control" name="DisplayName" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Permissions.CanInventorize" id="canInventorize" />
|
||||
<label class="form-check-label" for="canInventorize">@T["Can inventorize"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Permissions.CanManageAssets" id="canManageAssets" />
|
||||
<label class="form-check-label" for="canManageAssets">@T["Can manage assets"]</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Permissions.CanManageUsers" id="canManageUsers" />
|
||||
<label class="form-check-label" for="canManageUsers">@T["Can manage users"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Permissions.CanManageGroups" id="canManageGroups" />
|
||||
<label class="form-check-label" for="canManageGroups">@T["Can manage groups"]</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Permissions.CanManageLocations" id="canManageLocations" />
|
||||
<label class="form-check-label" for="canManageLocations">@T["Can manage locations"]</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@T["Cancel"]</button>
|
||||
<button type="submit" class="btn btn-primary">@T["Create"]</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const createForm = document.getElementById('createGroupForm');
|
||||
|
||||
createForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(createForm);
|
||||
const jsonData = {};
|
||||
|
||||
// Basic fields
|
||||
jsonData.Cn = formData.get('Cn');
|
||||
jsonData.DisplayName = formData.get('DisplayName');
|
||||
|
||||
// Permissions
|
||||
jsonData.Permissions = [];
|
||||
if (createForm.querySelector('[name="Permissions.CanInventorize"]').checked) jsonData.Permissions.push("CanInventorize");
|
||||
if (createForm.querySelector('[name="Permissions.CanManageAssets"]').checked) jsonData.Permissions.push("CanManageAssets");
|
||||
if (createForm.querySelector('[name="Permissions.CanManageUsers"]').checked) jsonData.Permissions.push("CanManageUsers");
|
||||
if (createForm.querySelector('[name="Permissions.CanManageGroups"]').checked) jsonData.Permissions.push("CanManageGroups");
|
||||
if (createForm.querySelector('[name="Permissions.CanManageLocations"]').checked) jsonData.Permissions.push("CanManageLocations");
|
||||
|
||||
try {
|
||||
const response = await fetch('/Groups/Create', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(jsonData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
const createModalEl = document.getElementById('createGroupModal');
|
||||
if (result.success) {
|
||||
bootstrap.Modal.getInstance(createModalEl).hide();
|
||||
createForm.reset();
|
||||
// Add the new group to the table
|
||||
const tableBody = document.querySelector('tbody');
|
||||
const newRow = document.createElement('tr');
|
||||
|
||||
newRow.innerHTML = `
|
||||
<td style="text-align: center">${jsonData.Cn}</td>
|
||||
<td style="text-align: center">${jsonData.Permissions.includes("CanInventorize") ? "☑️" : "❌"}</td>
|
||||
<td style="text-align: center">${jsonData.Permissions.includes("CanManageUsers") ? "☑️" : "❌"}</td>
|
||||
<td style="text-align: center">${jsonData.Permissions.includes("CanManageLocations") ? "☑️" : "❌"}</td>
|
||||
<td style="text-align: center">${jsonData.Permissions.includes("CanManageAssets") ? "☑️" : "❌"}</td>
|
||||
<td style="text-align: center">${jsonData.Permissions.includes("CanManageGroups") ? "☑️" : "❌"}</td>
|
||||
<td style="text-align: center">
|
||||
<div class="d-flex gap-2 justify-content-center">
|
||||
<button class="btn btn-sm btn-warning btn-update"
|
||||
data-group-id="${jsonData.Cn}"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updateGroupModal">
|
||||
@T["Update"]
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger btn-delete"
|
||||
data-group-id="${jsonData.Cn}"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteModal">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tableBody.appendChild(newRow);
|
||||
showToast('@T["Group created successfully"]', 'success');
|
||||
} else {
|
||||
let exception = result.exception;
|
||||
switch (exception) {
|
||||
case "Entry Already Exists":
|
||||
exception = "@T["Entry Already Exists"]";
|
||||
break;
|
||||
case "None":
|
||||
exception = "@T["Unknown error"]";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
showToast("@T["Error"]: " + result.exception || '@T["Error creating group"]', 'danger');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showToast('@T["Error contacting server"]', 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Groups Update Modal -->
|
||||
<div class="modal fade" id="updateGroupModal" tabindex="-1" aria-labelledby="updateGroupModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-warning text-dark">
|
||||
<h5 class="modal-title" id="updateGroupModalLabel">@T["Update Group"]</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form id="updateGroupForm">
|
||||
<div class="modal-body">
|
||||
<div class="row g-3 justify-content-center">
|
||||
<!-- Basic Info -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">@T["Group ID"] *</label>
|
||||
<input type="text" class="form-control" name="Cn" required />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">@T["Display name"] *</label>
|
||||
<input type="text" class="form-control" name="DisplayName" />
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanInventorize" id="canInventorize" />
|
||||
<label class="form-check-label" for="canInventorize">@T["Can inventorize"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageAssets" id="canManageAssets" />
|
||||
<label class="form-check-label" for="canManageAssets">@T["Can manage assets"]</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageUsers" id="canManageUsers" />
|
||||
<label class="form-check-label" for="canManageUsers">@T["Can manage users"]</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageGroups" id="canManageGroups" />
|
||||
<label class="form-check-label" for="canManageGroups">@T["Can manage groups"]</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageLocations" id="canManageLocations" />
|
||||
<label class="form-check-label" for="canManageLocations">@T["Can manage locations"]</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@T["Cancel"]</button>
|
||||
<button type="submit" class="btn btn-warning">@T["Save Changes"]</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const updateButtons = document.querySelectorAll('.btn-update');
|
||||
const updateModal = document.getElementById('updateGroupModal');
|
||||
const updateForm = document.getElementById('updateGroupForm');
|
||||
|
||||
updateModal.addEventListener('show.bs.modal', async event => {
|
||||
const button = event.relatedTarget;
|
||||
const groupId = button.getAttribute('data-group-id');
|
||||
|
||||
updateForm.reset();
|
||||
|
||||
try {
|
||||
const response = await fetch(`/Groups/Get?cn=${groupId}`);
|
||||
const responseJson = await response.json();
|
||||
const groups = responseJson.GroupModels[0];
|
||||
|
||||
for (const [key, value] of Object.entries(groups)) {
|
||||
const input = updateForm.querySelector(`[name="${key}"]`);
|
||||
if (input) input.value = value;
|
||||
}
|
||||
if (groups.Permissions) {
|
||||
for (const permission of groups.Permissions)
|
||||
{
|
||||
updateForm.querySelector(`[name="Description.Permissions.${permission}"]`).checked = true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showToast('Error loading group data', 'danger');
|
||||
}
|
||||
});
|
||||
|
||||
updateForm.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(updateForm);
|
||||
const jsonData = {};
|
||||
for (const [key, value] of formData.entries()) {
|
||||
if (!value) continue;
|
||||
const keys = key.split('.');
|
||||
let target = jsonData;
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
target[keys[i]] = target[keys[i]] || {};
|
||||
target = target[keys[i]];
|
||||
}
|
||||
target[keys[keys.length - 1]] = value;
|
||||
}
|
||||
let permissions = [];
|
||||
const elements = document.querySelectorAll('[name^="Description.Permissions"]');
|
||||
for (const element of elements)
|
||||
{
|
||||
if (element.checked)
|
||||
{
|
||||
permissions.push(element.name.substr(element.name.lastIndexOf(".") + 1));
|
||||
}
|
||||
}
|
||||
jsonData.Description.Permissions = permissions;
|
||||
jsonData.Description.DisplayName = jsonData.DisplayName;
|
||||
jsonData.DisplayName = null;
|
||||
try {
|
||||
const response = await fetch('/Groups/Update', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||
body: JSON.stringify(jsonData)
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
bootstrap.Modal.getInstance(updateModal).hide();
|
||||
showToast('Group updated successfully', 'success');
|
||||
|
||||
// Optionally refresh the row
|
||||
const row = [...document.querySelectorAll('tr')]
|
||||
.find(r => r.querySelector(`[data-group-id="${jsonData.Cn}"]`));
|
||||
if (row) {
|
||||
row.children[0].textContent = jsonData.Description.DisplayName || '';
|
||||
row.children[1].textContent = jsonData.Description.Permissions.includes("CanInventorize") ? "☑️" : "❌" || '';
|
||||
row.children[2].textContent = jsonData.Description.Permissions.includes("CanManageUsers") ? "☑️" : "❌" || '';
|
||||
row.children[3].textContent = jsonData.Description.Permissions.includes("CanManageLocations") ? "☑️" : "❌" || '';
|
||||
row.children[4].textContent = jsonData.Description.Permissions.includes("CanManageAssets") ? "☑️" : "❌" || '';
|
||||
row.children[5].textContent = jsonData.Description.Permissions.includes("CanManageGroups") ? "☑️" : "❌" || '';
|
||||
}
|
||||
} else {
|
||||
showToast(result.reason || 'Error updating group', 'danger');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showToast('Error contacting server', 'danger');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -30,7 +30,7 @@
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text" asp-area="" asp-controller="Home" asp-action="Index">@T["Home"]</a>
|
||||
<a class="nav-link text" asp-area="" asp-controller="Home" asp-action="Index">@T["Quick-Actions"]</a>
|
||||
</li>
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
@@ -46,6 +46,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text" asp-area="" asp-controller="Home" asp-action="Users">@T["Users"]</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text" asp-area="" asp-controller="Home" asp-action="Groups">@T["Groups"]</a>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item">
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
|
||||
Reference in New Issue
Block a user