9cff87fe85
Remove project bookkeeping citations from shipped code comments across the solution: hyphenated task IDs (WP-14, StoreAndForward-025), milestone/task/ issue refs (M3, Task 4, Audit Log #23, #21), Bundle X task-bundle labels, and C/D/K/S/T phase labels. Comment text only — no code logic, string/log literals, or XML-doc structure changed. Genuine descriptions are preserved (only the citation is stripped), and technical lookalikes are retained (UTF-8, SHA-256, T00:00:00, M365, UTC-5, pre-C4/pre-C5 schema versions). Flagged by the new CommentChecker TaskReferenceInComment / TrackingReferenceInComment checks plus targeted grep passes; full solution builds clean, append-only guard tests pass.
245 lines
8.2 KiB
Plaintext
245 lines
8.2 KiB
Plaintext
@page "/design/schema-library"
|
|
@using ZB.MOM.WW.ScadaBridge.Security
|
|
@using ZB.MOM.WW.ScadaBridge.CentralUI.Services
|
|
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Schemas
|
|
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDesign)]
|
|
@inject ISchemaLibraryService SchemaLibraryService
|
|
@inject IDialogService Dialog
|
|
|
|
@* Schema Library: list + create/edit (via SchemaBuilder) + delete the
|
|
reusable named JSON-Schema library entries that the {"$ref":"lib:Name"} resolver
|
|
resolves against. Every mutation is dispatched through ISchemaLibraryService
|
|
— the guard-running ManagementActor path — never a direct repo write. *@
|
|
|
|
<div class="container-fluid mt-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h4 class="mb-0">Schema Library</h4>
|
|
@if (!_editing)
|
|
{
|
|
<button class="btn btn-primary btn-sm" @onclick="BeginCreate">New Schema</button>
|
|
}
|
|
</div>
|
|
|
|
<ToastNotification @ref="_toast" />
|
|
|
|
@if (_editing)
|
|
{
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title mb-3">@(_editId.HasValue ? $"Edit Schema: {_editOriginalName}" : "New Schema")</h5>
|
|
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Name</label>
|
|
<input type="text" class="form-control form-control-sm"
|
|
placeholder="e.g. Address" @bind="_formName" />
|
|
<div class="form-text">Referenced as <code>lib:@(string.IsNullOrWhiteSpace(_formName) ? "Name" : _formName.Trim())</code>.</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small">Scope <span class="text-muted">(optional)</span></label>
|
|
<input type="text" class="form-control form-control-sm"
|
|
placeholder="(global)" @bind="_formScope" />
|
|
</div>
|
|
</div>
|
|
|
|
<label class="form-label small">Schema</label>
|
|
<SchemaBuilder Mode="value"
|
|
Value="@_formSchemaJson"
|
|
ValueChanged="@(v => _formSchemaJson = v)" />
|
|
|
|
@if (_formError != null)
|
|
{
|
|
<div class="text-danger small mt-2">@_formError</div>
|
|
}
|
|
|
|
<div class="mt-3">
|
|
<button class="btn btn-success btn-sm me-1" @onclick="SaveSchema" disabled="@_busy">Save</button>
|
|
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelEdit" disabled="@_busy">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@if (_loading)
|
|
{
|
|
<LoadingSpinner IsLoading="true" />
|
|
}
|
|
else if (_schemas.Count == 0)
|
|
{
|
|
<div class="text-center py-5 text-muted">
|
|
<p class="mb-3">No library schemas defined.</p>
|
|
@if (!_editing)
|
|
{
|
|
<button class="btn btn-primary btn-sm" @onclick="BeginCreate">Create your first schema</button>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<table class="table table-sm align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Scope</th>
|
|
<th>Reference</th>
|
|
<th class="text-end">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var s in _schemas)
|
|
{
|
|
<tr @key="s.Id">
|
|
<td class="fw-semibold">@s.Name</td>
|
|
<td>@(string.IsNullOrWhiteSpace(s.Scope) ? "—" : s.Scope)</td>
|
|
<td><code>lib:@s.Name</code></td>
|
|
<td class="text-end">
|
|
@* Row actions are disabled while the editor is open so the
|
|
row under edit (and its siblings) can't be deleted out from
|
|
under the form, and while a delete is in flight (_busy). *@
|
|
<button class="btn btn-outline-primary btn-sm me-1"
|
|
@onclick="() => BeginEdit(s)" disabled="@(_editing || _busy)">Edit</button>
|
|
<button class="btn btn-outline-danger btn-sm"
|
|
@onclick="() => DeleteSchema(s)" disabled="@(_editing || _busy)">Delete</button>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
private List<SharedSchema> _schemas = new();
|
|
private bool _loading = true;
|
|
private bool _busy;
|
|
|
|
private bool _editing;
|
|
private int? _editId;
|
|
private string _editOriginalName = string.Empty;
|
|
private string _formName = string.Empty;
|
|
private string? _formScope;
|
|
private string? _formSchemaJson;
|
|
private string? _formError;
|
|
|
|
private ToastNotification _toast = default!;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadAsync();
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
_loading = true;
|
|
try
|
|
{
|
|
_schemas = (await SchemaLibraryService.ListAsync()).ToList();
|
|
}
|
|
finally
|
|
{
|
|
_loading = false;
|
|
}
|
|
}
|
|
|
|
private void BeginCreate()
|
|
{
|
|
_editing = true;
|
|
_editId = null;
|
|
_editOriginalName = string.Empty;
|
|
_formName = string.Empty;
|
|
_formScope = null;
|
|
// Seed an object-shaped schema so the builder opens with a useful default.
|
|
_formSchemaJson = """{"type":"object","properties":{}}""";
|
|
_formError = null;
|
|
}
|
|
|
|
private void BeginEdit(SharedSchema schema)
|
|
{
|
|
_editing = true;
|
|
_editId = schema.Id;
|
|
_editOriginalName = schema.Name;
|
|
_formName = schema.Name;
|
|
_formScope = schema.Scope;
|
|
_formSchemaJson = schema.SchemaJson;
|
|
_formError = null;
|
|
}
|
|
|
|
private void CancelEdit()
|
|
{
|
|
_editing = false;
|
|
_formError = null;
|
|
}
|
|
|
|
private async Task SaveSchema()
|
|
{
|
|
_formError = null;
|
|
var name = _formName?.Trim() ?? string.Empty;
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
_formError = "Schema name is required.";
|
|
return;
|
|
}
|
|
|
|
var scope = string.IsNullOrWhiteSpace(_formScope) ? null : _formScope.Trim();
|
|
var schemaJson = _formSchemaJson ?? string.Empty;
|
|
|
|
_busy = true;
|
|
try
|
|
{
|
|
var result = _editId.HasValue
|
|
? await SchemaLibraryService.UpdateAsync(_editId.Value, name, scope, schemaJson)
|
|
: await SchemaLibraryService.CreateAsync(name, scope, schemaJson);
|
|
|
|
if (result.Success)
|
|
{
|
|
_toast.ShowSuccess(_editId.HasValue
|
|
? $"Schema '{name}' updated."
|
|
: $"Schema '{name}' created.");
|
|
_editing = false;
|
|
await LoadAsync();
|
|
}
|
|
else
|
|
{
|
|
_formError = result.Error;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_busy = false;
|
|
}
|
|
}
|
|
|
|
private async Task DeleteSchema(SharedSchema schema)
|
|
{
|
|
// In-flight guard: an editor-open row action is already disabled in the markup,
|
|
// but the _busy gate is the authoritative guard against a double-invoked delete
|
|
// (and mirrors the Save path's guard).
|
|
if (_busy) return;
|
|
|
|
var confirmed = await Dialog.ConfirmAsync(
|
|
"Delete Schema",
|
|
$"Delete library schema '{schema.Name}'? References to lib:{schema.Name} will no longer resolve.",
|
|
danger: true);
|
|
if (!confirmed) return;
|
|
|
|
_busy = true;
|
|
try
|
|
{
|
|
var result = await SchemaLibraryService.DeleteAsync(schema.Id);
|
|
if (result.Success)
|
|
{
|
|
_toast.ShowSuccess($"Schema '{schema.Name}' deleted.");
|
|
await LoadAsync();
|
|
}
|
|
else
|
|
{
|
|
_toast.ShowError(result.Error ?? "Delete failed.");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_busy = false;
|
|
}
|
|
}
|
|
}
|