Add German localization and styling for administration module

- Introduced new JavaScript file `fis.admin_txt_de.js` for German translations of administration-related terms and messages.
- Created `fis.admin.css` for styling the administration interface, including layout, cards, and buttons.
- Added `fis.admin.de.js` for the main functionality of the administration module, implementing features such as system status checks and email testing.
- Minified version of the German JavaScript file created as `fis.admin.de.min.js`.
- Minified CSS file created as `fis.admin.min.css` for optimized loading.
This commit is contained in:
Stefan
2026-07-16 14:59:14 +02:00
parent f6079af0de
commit 8a0ebeeb1e
34 changed files with 2765 additions and 16 deletions
@@ -0,0 +1,105 @@
using Fuchs.Services;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OCORE.SQL;
using static OCORE.SQL.sql;
using static OCORE.web.mvc_helper_async;
namespace Fuchs.Controllers;
// Partial class: Admin / system-status module.
//
// Access is restricted to users whose "fds_sys" module authorization is greater than 4.
// The menu button is only shown, and the module script only loaded, for such users (frontend),
// but every data endpoint here ALSO enforces the level server-side (defense in depth) — the
// passive/probe data is diagnostic and must never be reachable by a lower-privileged session.
public partial class IntranetController
{
// fds_sys authorization must exceed this to use the Admin module.
private const int AdminMinAuthExclusive = 4;
private async Task<IActionResult> Do_Process_Admin(string fn, string id, string code)
{
_logger.LogDebug("Do_Process_Admin action={Action} code={Code} user={User}", id, code, UserAccountID);
// The auth probe is the one endpoint that answers for BOTH authorized and unauthorized
// users (the frontend uses manage>0 to decide whether to render the module at all).
int authLevel = await GetSystemAdminAuthAsync(fn, id, code);
bool authorized = authLevel > AdminMinAuthExclusive;
if (id.Equals("auth", StringComparison.OrdinalIgnoreCase))
return await JSONAsync(new { manage = authorized ? 1 : 0, level = authLevel });
if (!authorized)
{
_logger.LogWarning("Admin access denied for user={User} (fds_sys={Level}) action={Action}",
UserAccountID, authLevel, id);
return Unauthorized401();
}
var status = _systemStatus;
switch (id.ToLowerInvariant())
{
case "status":
{
var info = status.GetInfo();
var probes = await status.ProbeAllAsync(HttpContext.RequestAborted);
return AdminJson(new { info, probes });
}
case "info":
return AdminJson(new { info = status.GetInfo() });
case "probe":
{
// code carries the component id, e.g. /do/admin/probe/database
string component = string.IsNullOrWhiteSpace(code) ? Form("component") : code;
if (string.IsNullOrWhiteSpace(component))
return BadRequest400();
var probe = await status.ProbeAsync(component, HttpContext.RequestAborted);
return AdminJson(new { probe });
}
case "testmail":
{
if (!HasForm("to", "subject"))
return BadRequest400();
var result = await status.SendTestEmailAsync(
Form("to"), Form("subject"), Form("body"), HttpContext.RequestAborted);
_logger.LogInformation("Admin test email requested by user={User} to={To} sent={Sent}",
UserAccountID, result.RequestedRecipient, result.Sent);
return AdminJson(new { result });
}
default:
_logger.LogWarning("Admin: no handler for action={Action}, user={User}", id, UserAccountID);
return BadRequest400();
}
}
// The status DTOs (SystemInfoSnapshot / SystemProbeResult) are PascalCase; serialize them
// camelCase so the Admin frontend contract matches the lowercase convention used elsewhere.
private static readonly JsonSerializerSettings CamelCaseJson = new()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
};
private ContentResult AdminJson(object payload) =>
Content(JsonConvert.SerializeObject(payload, CamelCaseJson), "application/json");
/// <summary>
/// Resolves the calling user's <c>fds_sys</c> module authorization level via the
/// <c>fis_getModuleAuth</c> SQL function (same mechanism as <see cref="HandleAuth"/>).
/// Returns -3 when it cannot be determined (fail-closed).
/// </summary>
private async Task<int> GetSystemAdminAuthAsync(string fn, string id, string code)
{
var val = await getSQLValue_async<int>(
"SELECT [dbo].[fis_getModuleAuth](@module, @authuser);",
_intranet.Intranet__SQLConnectionString, -3,
StdParamlist(SQL_VarChar("@module", "fds_sys")),
Security: DbSec, options: SqlOpt(fn, id, code));
return val.Result;
}
}
+5 -1
View File
@@ -38,6 +38,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
private readonly IInvoiceDraftService _invoiceDrafts;
private readonly IReminderDraftService _reminderDrafts;
private readonly IDraftNotifier _draftNotifier;
private readonly ISystemStatusService _systemStatus;
private readonly List<string> _allowedNonAuth = new() { "spwc", "spw" };
private readonly List<string> _allowedGet = new()
{
@@ -68,7 +69,8 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
IEventService events,
IInvoiceDraftService invoiceDrafts,
IReminderDraftService reminderDrafts,
IDraftNotifier draftNotifier)
IDraftNotifier draftNotifier,
ISystemStatusService systemStatus)
{
_intranet = intranet;
_mfr = mfr;
@@ -85,6 +87,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
_invoiceDrafts = invoiceDrafts;
_reminderDrafts = reminderDrafts;
_draftNotifier = draftNotifier;
_systemStatus = systemStatus;
}
/// <summary>Merged query-string + form parameters (form wins) for report processing.</summary>
@@ -163,6 +166,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller
"rem" => await Do_Process_Reminder(fn, id, code),
"rep" => await Do_Process_Reports(fn, id, code),
"bam" => await Do_Process_Bankings(fn, id, code),
"admin" => await Do_Process_Admin(fn, id, code),
"auth" => await HandleAuth(fn, id, code),
"spwc" => await HandleSendPasswordCode(fn, id, code),
"spw" => await HandleSendPassword(fn, id, code),