mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Implemented user creation in frontend
This commit is contained in:
@@ -60,15 +60,19 @@ public class UsersController : Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("Create")]
|
[HttpPost("Create")]
|
||||||
public async Task<bool> Create(string cn, string sn, string? title, string? uid, string userPassword, string? description, string jpegPhoto)
|
public async Task<UsersCreateResponseModel> Create([FromBody] UsersCreateRequestModel requestModel)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
string? jpegPhoto = requestModel.JpegPhoto;
|
||||||
|
string? title = requestModel.Title;
|
||||||
|
string userPassword = requestModel.UserPassword ?? "";
|
||||||
|
UserDescription? description = requestModel.Description;
|
||||||
jpegPhoto ??= Convert.ToBase64String(System.IO.File.ReadAllBytes("wwwroot/user_default.jpeg")); // TODO: cleanup - make this a config setting
|
jpegPhoto ??= Convert.ToBase64String(System.IO.File.ReadAllBytes("wwwroot/user_default.jpeg")); // TODO: cleanup - make this a config setting
|
||||||
uid ??= UsersHelper.CreateUsername(cn, sn);
|
string uid = UsersHelper.CreateUsername(requestModel.Cn ?? "", requestModel.Sn ?? "");
|
||||||
title ??= "";
|
title ??= "";
|
||||||
description ??= "{}";
|
description ??= new() {Address = new(), BirthDate = "", Workplace = "", Groups = []};
|
||||||
if (!userPassword.StartsWith('{'))
|
if (!userPassword.StartsWith('{'))
|
||||||
{
|
{
|
||||||
byte[] passwordBytes = Encoding.UTF8.GetBytes(userPassword);
|
byte[] passwordBytes = Encoding.UTF8.GetBytes(userPassword);
|
||||||
@@ -79,26 +83,26 @@ public class UsersController : Controller
|
|||||||
LdapAttributeSet attributeSet =
|
LdapAttributeSet attributeSet =
|
||||||
[
|
[
|
||||||
new LdapAttribute("objectClass", "inetOrgPerson"),
|
new LdapAttribute("objectClass", "inetOrgPerson"),
|
||||||
new LdapAttribute("cn", cn),
|
new LdapAttribute("cn", requestModel.Cn),
|
||||||
new LdapAttribute("sn", sn),
|
new LdapAttribute("sn", requestModel.Sn),
|
||||||
new LdapAttribute("title", title),
|
new LdapAttribute("title", title),
|
||||||
new LdapAttribute("uid", uid),
|
new LdapAttribute("uid", uid),
|
||||||
new LdapAttribute("jpegPhoto", jpegPhoto),
|
new LdapAttribute("jpegPhoto", jpegPhoto),
|
||||||
new LdapAttribute("description", description),
|
new LdapAttribute("description", JsonSerializer.Serialize(description)),
|
||||||
new LdapAttribute("userPassword", userPassword),
|
new LdapAttribute("userPassword", userPassword),
|
||||||
];
|
];
|
||||||
await _ldap.CreateUser(uid, attributeSet);
|
await _ldap.CreateUser(uid, attributeSet);
|
||||||
return true;
|
return new(){Success = true, Uid = uid};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError("Unable to create user: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
_logger.LogError("Unable to create user: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
||||||
return false;
|
return new() {Success = false, Exception = ex.Message};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("Update")]
|
[HttpPost("Update")]
|
||||||
public async Task<UsersUpdateRequestModel> Update([FromBody] UsersModifyRequestModel requestModel)
|
public async Task<UsersUpdateResponseModel> Update([FromBody] UsersModifyRequestModel requestModel)
|
||||||
{
|
{
|
||||||
if (requestModel is null)
|
if (requestModel is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,12 +13,16 @@ public class UsersIndexRequestModel
|
|||||||
public bool UserPassword { get; set; } = true;
|
public bool UserPassword { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class UsersModifyRequestModel
|
public class UsersModifyRequestModel : UsersCreateRequestModel
|
||||||
{
|
{
|
||||||
[JsonPropertyName("Uid")]
|
[JsonPropertyName("Uid")]
|
||||||
public required string Uid { get; set; }
|
public required string Uid { get; set; }
|
||||||
[JsonPropertyName("NewUid")]
|
[JsonPropertyName("NewUid")]
|
||||||
public string? NewUid { get; set; } = null;
|
public string? NewUid { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UsersCreateRequestModel
|
||||||
|
{
|
||||||
[JsonPropertyName("Cn")]
|
[JsonPropertyName("Cn")]
|
||||||
public string? Cn { get; set; } = null;
|
public string? Cn { get; set; } = null;
|
||||||
[JsonPropertyName("Sn")]
|
[JsonPropertyName("Sn")]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using System.Text.Json.Serialization;
|
|||||||
|
|
||||||
namespace Berufsschule_HAM.Models;
|
namespace Berufsschule_HAM.Models;
|
||||||
|
|
||||||
public class UsersUpdateRequestModel
|
public class UsersUpdateResponseModel
|
||||||
{
|
{
|
||||||
[JsonPropertyName("Success")]
|
[JsonPropertyName("Success")]
|
||||||
public required bool Success { get; set; }
|
public required bool Success { get; set; }
|
||||||
@@ -10,4 +10,15 @@ public class UsersUpdateRequestModel
|
|||||||
public string? Exception { get; set; }
|
public string? Exception { get; set; }
|
||||||
[JsonPropertyName("NewUid")]
|
[JsonPropertyName("NewUid")]
|
||||||
public string? NewUid { get; set; }
|
public string? NewUid { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class UsersCreateResponseModel
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
[JsonPropertyName("Exception")]
|
||||||
|
public string? Exception { get; set; }
|
||||||
|
[JsonPropertyName("Uid")]
|
||||||
|
public string? Uid { get; set; }
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="mb-4 d-flex flex-wrap gap-2">
|
<div class="mb-4 d-flex flex-wrap gap-2">
|
||||||
<button class="btn btn-primary">@T["Create user"]</button>
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
|
||||||
|
@T["Create user"]
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -461,3 +463,188 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- User Create Modal -->
|
||||||
|
<div class="modal fade" id="createModal" tabindex="-1" aria-labelledby="createModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title" id="createModalLabel">Create User</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="createForm">
|
||||||
|
<div class="row g-3">
|
||||||
|
<h6 class="fw-bold">Personal information</h6>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Title</label>
|
||||||
|
<input type="text" name="Title" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Name</label>
|
||||||
|
<input type="text" name="Cn" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Surname</label>
|
||||||
|
<input type="text" name="Sn" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Birth date</label>
|
||||||
|
<input type="text" name="Description.BirthDate" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">City</label>
|
||||||
|
<input type="text" name="Description.Address.City" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Street</label>
|
||||||
|
<input type="text" name="Description.Address.Street" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Street Nr.</label>
|
||||||
|
<input type="text" name="Description.Address.StreetNr" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<hr class="my-3">
|
||||||
|
<h6 class="fw-bold">Workplace & account</h6>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Workplace</label>
|
||||||
|
<input type="text" name="Description.Workplace" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Groups</label>
|
||||||
|
<select id="createGroups" name="Description.Groups" class="form-select" multiple></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Password</label>
|
||||||
|
<input type="password" name="UserPassword" class="form-control" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Photo</label>
|
||||||
|
<input type="file" id="createPhotoFile" accept="image/*" class="form-control" />
|
||||||
|
<input type="hidden" id="createJpegPhoto" name="JpegPhoto" />
|
||||||
|
<div class="mt-2 text-center">
|
||||||
|
<img id="createPhotoPreview" src="" alt="Preview" class="img-thumbnail" style="max-height: 150px; display: none;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" form="createForm" class="btn btn-primary">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const createModal = document.getElementById('createModal');
|
||||||
|
const createForm = document.getElementById('createForm');
|
||||||
|
const createGroupsSelect = document.getElementById('createGroups');
|
||||||
|
const photoInput = document.getElementById('createPhotoFile');
|
||||||
|
const photoPreview = document.getElementById('createPhotoPreview');
|
||||||
|
const photoHidden = document.getElementById('createJpegPhoto');
|
||||||
|
|
||||||
|
// Load available groups when modal is shown
|
||||||
|
createModal.addEventListener('show.bs.modal', async () => {
|
||||||
|
await populateGroupsDropdown(createGroupsSelect);
|
||||||
|
createForm.reset();
|
||||||
|
photoPreview.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle photo upload preview
|
||||||
|
photoInput.addEventListener('change', () => {
|
||||||
|
const file = photoInput.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
const base64 = e.target.result.split(',')[1];
|
||||||
|
photoHidden.value = base64;
|
||||||
|
photoPreview.src = e.target.result;
|
||||||
|
photoPreview.style.display = 'block';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit create form
|
||||||
|
createForm.addEventListener('submit', async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const dataFromEntries = Object.fromEntries(new FormData(createForm).entries());
|
||||||
|
const data = unflatten(dataFromEntries);
|
||||||
|
data.Description.Groups = Array.from(createGroupsSelect.selectedOptions).map(o => o.value);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/Users/Create', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.Success) {
|
||||||
|
bootstrap.Modal.getInstance(createModal).hide();
|
||||||
|
showToast('User created successfully', 'success');
|
||||||
|
|
||||||
|
// Add new row to table dynamically
|
||||||
|
const tbody = document.querySelector('table tbody');
|
||||||
|
const newRow = document.createElement('tr');
|
||||||
|
newRow.innerHTML = `
|
||||||
|
<td><img class="rounded-circle user-icon" src="data:image/jpeg;base64,${data.JpegPhoto || ''}" alt="Photo" style="max-width:300px;" /></td>
|
||||||
|
<td>${result.NewUid || ''}</td>
|
||||||
|
<td>${data.Title || ''}</td>
|
||||||
|
<td>${data.Cn || ''}</td>
|
||||||
|
<td>${data.Sn || ''}</td>
|
||||||
|
<td>${data.Description?.Workplace || ''}</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-sm btn-warning"
|
||||||
|
data-user-id="${result.Uid || ''}"
|
||||||
|
data-user-title="${data.Title || ''}"
|
||||||
|
data-user-name="${data.Cn || ''}"
|
||||||
|
data-user-surname="${data.Sn || ''}"
|
||||||
|
data-user-workplace="${data.Description?.Workplace || ''}"
|
||||||
|
data-user-groups='${JSON.stringify(data.Description?.Groups || [])}'
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#updateModal">
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-danger btn-delete"
|
||||||
|
data-user-id="${result.NewUid || ''}"
|
||||||
|
data-user-name="${data.Cn || ''}"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#deleteModal">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(newRow);
|
||||||
|
} else {
|
||||||
|
showToast(`${result.Exception || 'Create failed'}`, 'danger');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
showToast('Error creating user', 'danger');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function populateGroupsDropdown(selectElement) {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/Groups/Get');
|
||||||
|
const json = await res.json();
|
||||||
|
if (!json.success) return;
|
||||||
|
selectElement.innerHTML = '';
|
||||||
|
json.GroupModels.forEach(group => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = group.Cn;
|
||||||
|
opt.textContent = group.DisplayName || group.Cn;
|
||||||
|
selectElement.appendChild(opt);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching groups', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user