@using RackPeek.Domain.Resources.SystemResources @inject UpdateServiceUseCase UpdateUseCase @inject IGetResourceByNameUseCase GetByNameUseCase @inject IDeleteResourceUseCase DeleteUseCase @inject ICloneResourceUseCase CloneUseCase @inject IRenameResourceUseCase RenameUseCase @inject IGetResourceByNameUseCase GetSystemByNameUseCase @inject NavigationManager Nav
@Service.Name
@if (!_isEditing) { } else { }
IP
@if (_isEditing) { } else if (!string.IsNullOrWhiteSpace(EffectiveIp)) {
@EffectiveIp @if (string.IsNullOrWhiteSpace(Service.Network?.Ip)) { (inherited) }
}
Port
@if (_isEditing) { } else if (Service.Network?.Port.HasValue == true) { var href = GetBrowsableHref(); if (href is not null) { @Service.Network.Port } else {
@Service.Network.Port
} }
Protocol
@if (_isEditing) { } else if (!string.IsNullOrWhiteSpace(Service.Network?.Protocol)) {
@Service.Network!.Protocol
}
URL
@if (_isEditing) { } else if (!string.IsNullOrWhiteSpace(EffectiveUrl)) { @EffectiveUrl @if (string.IsNullOrWhiteSpace(Service.Network?.Url)) { (derived) } }
Runs On
@if (_isEditing) { @if (Service.RunsOn?.Count > 0) { @foreach (var parent in Service.RunsOn) { } } } else if (Service.RunsOn?.Count > 0) { @foreach (var parent in Service.RunsOn) { @parent } }
Notes
@if (_isEditing) { } else { }
Are you sure you want to delete @Service.Name? @code { bool _cloneOpen; void OpenClone() { _cloneOpen = true; } async Task HandleCloneSubmit(string newName) { await CloneUseCase.ExecuteAsync(Service.Name, newName); Nav.NavigateTo($"resources/services/{Uri.EscapeDataString(newName)}"); } } @code { [Parameter] [EditorRequired] public Service Service { get; set; } = default!; [Parameter] public EventCallback OnSave { get; set; } private bool _isEditing; private ServiceEditModel _edit = new(); void BeginEdit() { _edit = ServiceEditModel.From(Service); _isEditing = true; } async Task Save() { _isEditing = false; await UpdateUseCase.ExecuteAsync( _edit.Name, _edit.Ip, _edit.Port, _edit.Protocol, _edit.Url, _edit.RunsOn, _edit.Notes ); await OnSave.InvokeAsync(Service.Name); await ResolveEffectiveIp(); } void Cancel() { _isEditing = false; } bool _selectParentOpen; string? SelectedParentName; async Task HandleParentSelected(string? name) { SelectedParentName = name; var runsOn = (_isEditing ? _edit.RunsOn : Service.RunsOn) ?? new List(); runsOn = runsOn.Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); if (!string.IsNullOrWhiteSpace(name) && !runsOn.Contains(name)) runsOn.Add(name); var ip = _isEditing ? _edit.Ip : Service.Network?.Ip; var port = _isEditing ? _edit.Port : Service.Network?.Port; var protocol = _isEditing ? _edit.Protocol : Service.Network?.Protocol; var url = _isEditing ? _edit.Url : Service.Network?.Url; var notes = _isEditing ? _edit.Notes : Service.Notes; await UpdateUseCase.ExecuteAsync(Service.Name, ip, port, protocol, url, runsOn, notes); // Refresh service from backend (optional, but if you do it, DO NOT nuke the edit buffer) Service = await GetByNameUseCase.ExecuteAsync(Service.Name); if (_isEditing) { // keep whatever the user typed; just sync RunsOn _edit.RunsOn = runsOn; } else { _edit = ServiceEditModel.From(Service); } } async Task HandleParentDeleted(string? name) { if (string.IsNullOrWhiteSpace(name)) return; SelectedParentName = name; var runsOn = (_isEditing ? _edit.RunsOn : Service.RunsOn) ?? new List(); runsOn = runsOn.Where(x => !string.IsNullOrWhiteSpace(x) && x != name).ToList(); var ip = _isEditing ? _edit.Ip : Service.Network?.Ip; var port = _isEditing ? _edit.Port : Service.Network?.Port; var protocol = _isEditing ? _edit.Protocol : Service.Network?.Protocol; var url = _isEditing ? _edit.Url : Service.Network?.Url; var notes = _isEditing ? _edit.Notes : Service.Notes; await UpdateUseCase.ExecuteAsync(Service.Name, ip, port, protocol, url, runsOn, notes); Service = await GetByNameUseCase.ExecuteAsync(Service.Name); if (_isEditing) _edit.RunsOn = runsOn; else _edit = ServiceEditModel.From(Service); } } @code { private bool _confirmDeleteOpen; [Parameter] public EventCallback OnDeleted { get; set; } void ConfirmDelete() { _confirmDeleteOpen = true; } async Task DeleteServer() { _confirmDeleteOpen = false; await DeleteUseCase.ExecuteAsync(Service.Name); if (OnDeleted.HasDelegate) await OnDeleted.InvokeAsync(Service.Name); } } @code { bool _renameOpen; void OpenRename() { _renameOpen = true; } async Task HandleRenameSubmit(string newName) { await RenameUseCase.ExecuteAsync(Service.Name, newName); Nav.NavigateTo($"resources/services/{Uri.EscapeDataString(newName)}"); } private string? GetBrowsableHref() { var ip = Service.Network?.Ip ?? EffectiveIp; var port = Service.Network?.Port; if (string.IsNullOrWhiteSpace(ip) || port is null) return null; var proto = Service.Network?.Protocol?.Trim().ToLowerInvariant(); var scheme = proto switch { "https" => "https", "http" => "http", _ => "http" }; // Build a correct absolute URL var ub = new UriBuilder(scheme, ip) { Port = port.Value }; return ub.Uri.ToString(); } } @code { private string? EffectiveUrl => !string.IsNullOrWhiteSpace(Service.Network?.Url) ? Service.Network!.Url : GetBrowsableHref(); private string? EffectiveIp; protected override async Task OnParametersSetAsync() { await ResolveEffectiveIp(); } private async Task ResolveEffectiveIp() { // If service has its own IP, use it if (!string.IsNullOrWhiteSpace(Service.Network?.Ip)) { EffectiveIp = Service.Network!.Ip; return; } // Must have exactly one parent if (Service.RunsOn?.Count != 1) { EffectiveIp = null; return; } var parentName = Service.RunsOn.First(); // Load parent system var parent = await GetSystemByNameUseCase.ExecuteAsync(parentName); EffectiveIp = string.IsNullOrWhiteSpace(parent?.Ip) ? null : parent.Ip; } }