Merge pull request #119 from LD-Reborn/76-feature-implement-a-counter-for-the-assetid

76 feature implement a counter for the assetid
This commit is contained in:
LD50
2025-10-12 18:49:34 +02:00
committed by GitHub
8 changed files with 116 additions and 48 deletions

View File

@@ -28,6 +28,10 @@ public class AssetsController : Controller
{ {
var assetList = await _ldap.ListDeviceAsync(Cn); var assetList = await _ldap.ListDeviceAsync(Cn);
result = new AssetsGetResponseModel(successful: true, assetModel: assetList); result = new AssetsGetResponseModel(successful: true, assetModel: assetList);
if (result.AssetsModel is not null)
{
result.AssetsModel.Owner = result.AssetsModel?.Owner?.Replace("uid=", "");
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -45,6 +49,11 @@ public class AssetsController : Controller
{ {
var assetList = await _ldap.ListDeviceAsync(); var assetList = await _ldap.ListDeviceAsync();
result = new AssetsGetAllResponseModel(successful: true, assetsModel: assetList); result = new AssetsGetAllResponseModel(successful: true, assetsModel: assetList);
result.AssetsModel = result.AssetsModel?.Select(asset =>
{
asset.Owner = asset.Owner?.Replace("uid=", "");
return asset;
});
} }
catch (Exception e) catch (Exception e)
{ {
@@ -71,13 +80,10 @@ public class AssetsController : Controller
{ {
LdapAttributeSet attributeSet = LdapAttributeSet attributeSet =
[ [
new LdapAttribute("objectClass", new[] {"top", "device", "extensibleObject"}), new LdapAttribute("objectClass", ["top", "device", "extensibleObject"]),
]; ];
if (assetModel.Cn != null) attributeSet.Add(new LdapAttribute("cn", await _ldap.GetAssetCounterAndIncrementAsync()));
{
attributeSet.Add(new LdapAttribute("cn", assetModel.Cn));
}
if (assetModel.SerialNumber != null) if (assetModel.SerialNumber != null)
{ {
attributeSet.Add(new LdapAttribute("serialNumber", assetModel.SerialNumber)); attributeSet.Add(new LdapAttribute("serialNumber", assetModel.SerialNumber));
@@ -168,7 +174,7 @@ public class AssetsController : Controller
} }
if (requestModel.Owner is not null) if (requestModel.Owner is not null)
{ {
await _ldap.UpdateAsset(cn, "owner", requestModel.Owner); await _ldap.UpdateAsset(cn, "owner", $"uid={requestModel.Owner}");
} }
if (requestModel.SerialNumber is not null) if (requestModel.SerialNumber is not null)
{ {

View File

@@ -1,11 +0,0 @@
namespace Berufsschule_HAM.Models;
public class AssetsCreateRequestModel
{
public required string Cn { get; set; }
public AssetDescription? Description { get; set; } = null;
public string? Location { get; set; } = null;
public string? Name { get; set; } = null;
public string? Owner { get; set; } = null;
public string? SerialNumber { get; set; } = null;
}

View File

@@ -71,3 +71,8 @@ public class AssetsTableViewModel
public string? AssetName { get; set; } public string? AssetName { get; set; }
public string? LocationName { get; set; } public string? LocationName { get; set; }
} }
public class AssetsMetadataModel
{
public int CnCounter { get; set; }
}

View File

@@ -1,15 +1,31 @@
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Berufsschule_HAM.Models; namespace Berufsschule_HAM.Models;
public class AssetsModifyRequestModel public class AssetsCreateRequestModel
{ {
public required string Cn { get; set; }
public string? NewCn { get; set; } = null;
[FromBody]
public AssetDescription? Description { get; set; } = null; public AssetDescription? Description { get; set; } = null;
public string? Location { get; set; } = null; public string? Location { get; set; } = null;
public string? Name { get; set; } = null; public string? Name { get; set; } = null;
public string? Owner { get; set; } = null; public string? Owner { get; set; } = null;
public string? SerialNumber { get; set; } = null; public string? SerialNumber { get; set; } = null;
} }
public class AssetsModifyRequestModel
{
[JsonPropertyName("Cn")]
public required string Cn { get; set; }
[JsonPropertyName("NewCn")]
public string? NewCn { get; set; } = null;
[JsonPropertyName("Description")]
public AssetDescription? Description { get; set; } = null;
[JsonPropertyName("Location")]
public string? Location { get; set; } = null;
[JsonPropertyName("Name")]
public string? Name { get; set; } = null;
[JsonPropertyName("Owner")]
public string? Owner { get; set; } = null;
[JsonPropertyName("SerialNumber")]
public string? SerialNumber { get; set; } = null;
}

View File

@@ -32,7 +32,7 @@ builder.Services.AddElmah<XmlFileErrorLog>(Options =>
}); });
builder.Services.AddSingleton<LdapService>(); builder.Services.AddSingleton<LdapService>();
builder.Services.AddSingleton<MigrationService>(); builder.Services.AddHostedService<MigrationService>();
builder.Services.AddHealthChecks() builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck"); .AddCheck<DatabaseHealthCheck>("DatabaseHealthCheck");
@@ -123,8 +123,5 @@ app.MapControllerRoute(
app.MapHealthChecks("/healthz") app.MapHealthChecks("/healthz")
.RequireAuthorization(); .RequireAuthorization();
// Run migrations
using var scope = app.Services.CreateScope();
var migrationService = scope.ServiceProvider.GetRequiredService<MigrationService>();
app.Run(); app.Run();

View File

@@ -118,6 +118,51 @@ public partial class LdapService : IDisposable
return true; return true;
} }
public async Task<string> GetAssetCounterAndIncrementAsync()
{
AssetsMetadataModel assetModel = await GetAssetCounterAsync();
string returnValue = assetModel.CnCounter.ToString();
assetModel.CnCounter++;
await UpdateAssetCounterAsync(assetModel);
return returnValue;
}
public async Task<AssetsMetadataModel> GetAssetCounterAsync()
{
Dictionary<string, string> entry = (await ListObjectBy(_opts.BaseDn, _opts.AssetsOu, ["description"])).First();
try
{
string description = entry["description"];
return JsonSerializer.Deserialize<AssetsMetadataModel>(description) ?? throw new Exception($"Unable to deserialize description: {description}");
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to retrieve asset counter due to exception: {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
throw;
}
}
public async Task<bool> UpdateAssetCounterAsync(AssetsMetadataModel assetMetadataModel)
{
await ConnectAndBind();
try
{
string dn = AssetsBaseDn; //PrependRDN($"", MigrationsBaseDn);
string targetText = JsonSerializer.Serialize(assetMetadataModel);
_logger.LogInformation("Setting the LDAP asset counter to {targetText} for {dn}", [targetText, dn]);
var modification = new LdapModification(
LdapModification.Replace,
new LdapAttribute("description", targetText)
);
await _conn.ModifyAsync(dn, modification);
}
catch (Exception)
{
return false;
}
return true;
}
public async Task<IEnumerable<UserModel>> ListUsersAsync(string[] attributes) public async Task<IEnumerable<UserModel>> ListUsersAsync(string[] attributes)
{ {
List<UserModel> returnValue = []; List<UserModel> returnValue = [];
@@ -396,6 +441,11 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
await _conn.AddAsync(ldapEntry); await _conn.AddAsync(ldapEntry);
} }
public async Task ModifyAsync(string dn, LdapModification ldapModification)
{
await _conn.ModifyAsync(dn, ldapModification);
}
public void Dispose() public void Dispose()
{ {
if (_conn != null && _conn.Connected) if (_conn != null && _conn.Connected)

View File

@@ -2,9 +2,10 @@ using Berufsschule_HAM.Models;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Novell.Directory.Ldap; using Novell.Directory.Ldap;
using System.Reflection; using System.Reflection;
using System.Text.Json;
namespace Berufsschule_HAM.Services; namespace Berufsschule_HAM.Services;
public class MigrationService public class MigrationService : IHostedService
{ {
private readonly LdapService _ldapService; private readonly LdapService _ldapService;
private readonly ILogger<MigrationService> _logger; private readonly ILogger<MigrationService> _logger;
@@ -15,8 +16,15 @@ public class MigrationService
_ldapService = ldapService; _ldapService = ldapService;
_logger = logger; _logger = logger;
_ldapConfig = ldapConfig.Value; _ldapConfig = ldapConfig.Value;
MigrateAsync().ConfigureAwait(false);
} }
public async Task StartAsync(CancellationToken cancellationToken)
{
await MigrateAsync();
}
public async Task StopAsync(CancellationToken cancellationToken) { }
public async Task<MigrationModel> MigrateAsync() public async Task<MigrationModel> MigrateAsync()
{ {
MigrationModel migrationModel = await _ldapService.GetMigrationVersionAsync(); MigrationModel migrationModel = await _ldapService.GetMigrationVersionAsync();
@@ -37,17 +45,12 @@ public class MigrationService
{ {
_logger.LogInformation("Migrating LDAP database from version {version} to {methodVersion}", [version, methodVersion + 1]); _logger.LogInformation("Migrating LDAP database from version {version} to {methodVersion}", [version, methodVersion + 1]);
if (method is null) { continue; } if (method is null) { continue; }
#pragma warning disable CS8605 // Unboxing a possibly null value. Task<int>? invocation = (Task<int>?)method.Invoke(null, [_ldapService]) ?? throw new Exception("Invocation is null");
version = (int)method.Invoke(null, [_ldapService]); version = await invocation;
#pragma warning restore CS8605 // Unboxing a possibly null value.
}
}
if (version != migrationModel.Version)
{
migrationModel.Version = version; migrationModel.Version = version;
await _ldapService.UpdateMigrationVersionAsync(migrationModel); await _ldapService.UpdateMigrationVersionAsync(migrationModel);
} }
}
return migrationModel; return migrationModel;
} }
@@ -92,6 +95,15 @@ public class MigrationService
await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.MigrationsBaseDn, migrationsAttributes); await TryCreateObjectIgnoreAlreadyExists(ldapService, ldapService.MigrationsBaseDn, migrationsAttributes);
return 1; return 1;
} }
public static async Task<int> UpdateFrom1Async(LdapService ldapService)
{
// Add the description attribute to ou=assets
AssetsMetadataModel assetsMetadataModel = new() { CnCounter = 1 };
LdapAttribute ldapAttribute = new("description", JsonSerializer.Serialize(assetsMetadataModel));
LdapModification ldapModification = new(LdapModification.Add, ldapAttribute);
await ldapService.ModifyAsync(ldapService.AssetsBaseDn, ldapModification);
return 2;
}
private static async Task TryCreateObjectIgnoreAlreadyExists(LdapService ldapService, string dn, LdapAttributeSet ldapAttributes) private static async Task TryCreateObjectIgnoreAlreadyExists(LdapService ldapService, string dn, LdapAttributeSet ldapAttributes)
{ {

View File

@@ -182,10 +182,6 @@
<div class="modal-body"> <div class="modal-body">
<div class="row g-3"> <div class="row g-3">
<!-- Basic Info --> <!-- Basic Info -->
<div class="col-md-6">
<label class="form-label">@T["Asset ID (Cn)"] *</label>
<input type="text" class="form-control" name="Cn" required />
</div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">@T["Name"]</label> <label class="form-label">@T["Name"]</label>
<input type="text" class="form-control" name="Name" /> <input type="text" class="form-control" name="Name" />
@@ -232,7 +228,7 @@
<!-- Dynamic attribute rows will appear here --> <!-- Dynamic attribute rows will appear here -->
</div> </div>
<button type="button" class="btn btn-sm btn-success mt-3" id="addAttributeBtn"> <button type="button" class="btn btn-sm btn-success mt-3" id="addAttributeBtn">
@T["Add Attribute"] @T["Add Attribute"]
</button> </button>
</div> </div>
@@ -363,10 +359,6 @@
<div class="modal-body"> <div class="modal-body">
<div class="row g-3"> <div class="row g-3">
<!-- Same fields as in Create --> <!-- Same fields as in Create -->
<div class="col-md-6">
<label class="form-label">@T["Asset ID (Cn)"] *</label>
<input type="text" class="form-control" name="Cn" readonly />
</div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">@T["Name"]</label> <label class="form-label">@T["Name"]</label>
<input type="text" class="form-control" name="Name" /> <input type="text" class="form-control" name="Name" />
@@ -408,7 +400,7 @@
</div> </div>
<div id="updateAttributesContainer" class="d-flex flex-column gap-2"></div> <div id="updateAttributesContainer" class="d-flex flex-column gap-2"></div>
<button type="button" class="btn btn-sm btn-success mt-3" id="updateAddAttributeBtn"> <button type="button" class="btn btn-sm btn-success mt-3" id="updateAddAttributeBtn">
@T["Add Attribute"] @T["Add Attribute"]
</button> </button>
</div> </div>
@@ -450,6 +442,7 @@ document.addEventListener('DOMContentLoaded', () => {
const updateForm = document.getElementById('updateAssetForm'); const updateForm = document.getElementById('updateAssetForm');
const updateAttributesContainer = document.getElementById('updateAttributesContainer'); const updateAttributesContainer = document.getElementById('updateAttributesContainer');
const addAttrBtn = document.getElementById('updateAddAttributeBtn'); const addAttrBtn = document.getElementById('updateAddAttributeBtn');
let assetId = null;
addAttrBtn.addEventListener('click', () => { addAttrBtn.addEventListener('click', () => {
const row = document.createElement('div'); const row = document.createElement('div');
@@ -470,7 +463,7 @@ document.addEventListener('DOMContentLoaded', () => {
updateModal.addEventListener('show.bs.modal', async event => { updateModal.addEventListener('show.bs.modal', async event => {
const button = event.relatedTarget; const button = event.relatedTarget;
const assetId = button.getAttribute('data-asset-id'); assetId = button.getAttribute('data-asset-id');
updateAttributesContainer.innerHTML = ''; updateAttributesContainer.innerHTML = '';
updateForm.reset(); updateForm.reset();
@@ -524,7 +517,7 @@ document.addEventListener('DOMContentLoaded', () => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(updateForm); const formData = new FormData(updateForm);
const jsonData = {}; const jsonData = {"Cn": assetId};
for (const [key, value] of formData.entries()) { for (const [key, value] of formData.entries()) {
if (!value) continue; if (!value) continue;
const keys = key.split('.'); const keys = key.split('.');