@page "/yaml/import" @using System.ComponentModel.DataAnnotations @using System.Text @using RackPeek.Domain.Api @using RackPeek.Domain.Persistence @using YamlDotNet.Core Yaml Import @inject UpsertInventoryUseCase ImportUseCase
Import YAML
@if (_error is not null) { }
@if (_isValid) {
Import Summary
@if (!_added.Any() && !_updated.Any() && !_replaced.Any()) {
No changes detected.
} @if (_added.Any()) {
+ @_added.Count added
@foreach (var name in _added) {
+ @name
@_newYaml[name]
                        
} } @if (_updated.Any()) {
~ @_updated.Count updated
@foreach (var name in _updated) {
~ @name
Current
@_oldYaml[name]
                                
Incoming (Merged)
@_newYaml[name]
                                
} } @if (_replaced.Any()) {
! @_replaced.Count replaced
@foreach (var name in _replaced) {
! @name
Current
@_oldYaml[name]
                                
Incoming (Replacement)
@_newYaml[name]
                                
} }
}
@code { private List _added = new(); private List _updated = new(); private List _replaced = new(); private Dictionary _oldYaml = new(StringComparer.OrdinalIgnoreCase); private Dictionary _newYaml = new(StringComparer.OrdinalIgnoreCase); private string _inputYaml = ""; private ImportError? _error; private bool _isValid; private MergeMode _mode = MergeMode.Merge; async Task OnModeChanged(MergeMode mode) { _mode = mode; await ComputeDiff(); } async Task ComputeDiff() { _error = null; _isValid = false; _added.Clear(); _updated.Clear(); _replaced.Clear(); _oldYaml.Clear(); _newYaml.Clear(); if (string.IsNullOrWhiteSpace(_inputYaml)) return; try { var result = await ImportUseCase.ExecuteAsync(new ImportYamlRequest { Yaml = _inputYaml, Mode = _mode, DryRun = true }); _added = result.Added; _updated = result.Updated; _replaced = result.Replaced; _oldYaml = result.OldYaml; _newYaml = result.NewYaml; _isValid = true; } catch (Exception ex) { _error = BuildError(ex, headlinePrefix: "YAML invalid"); } } async Task Apply() { if (!_isValid) return; try { await ImportUseCase.ExecuteAsync(new ImportYamlRequest { Yaml = _inputYaml, Mode = _mode, DryRun = false }); _inputYaml = ""; _isValid = false; _added.Clear(); _updated.Clear(); _replaced.Clear(); } catch (Exception ex) { _error = BuildError(ex, headlinePrefix: "Apply failed"); } } private ImportError BuildError(Exception ex, string headlinePrefix) { // YamlDotNet wraps the underlying parser error; walk InnerExceptions // to find the YamlException so we can extract Line/Column for the // user. YamlException? yamlEx = FindYamlException(ex); if (yamlEx is not null) { long? line = yamlEx.Start.Line > 0 ? yamlEx.Start.Line : null; long? col = yamlEx.Start.Column > 0 ? yamlEx.Start.Column : null; return new ImportError( $"{headlinePrefix}: {FirstLine(yamlEx.Message)}", line, col, line is long l ? ExtractSnippet(_inputYaml, (int)l) : null); } // Domain validation errors (duplicate names, missing version, etc.) // are already user-friendly — just surface them prominently. if (ex is ValidationException ve) return new ImportError(ve.Message, null, null, null); return new ImportError($"{headlinePrefix}: {FirstLine(ex.Message)}", null, null, null); } private static YamlException? FindYamlException(Exception? ex) { while (ex is not null) { if (ex is YamlException ye) return ye; ex = ex.InnerException; } return null; } private static string FirstLine(string message) { if (string.IsNullOrEmpty(message)) return string.Empty; var nl = message.IndexOf('\n'); return nl < 0 ? message.Trim() : message[..nl].Trim(); } private static string ExtractSnippet(string yaml, int lineNumber, int context = 2) { // Render `context` lines before and after the offending line, prefix // each with its 1-based line number, and mark the bad line with an // arrow so the eye lands on it instantly. var lines = yaml.Replace("\r\n", "\n").Split('\n'); if (lineNumber < 1 || lineNumber > lines.Length) return string.Empty; var start = Math.Max(1, lineNumber - context); var end = Math.Min(lines.Length, lineNumber + context); var sb = new StringBuilder(); for (int i = start; i <= end; i++) { sb.Append(i == lineNumber ? "→ " : " ") .Append(i.ToString().PadLeft(4)) .Append(" ") .Append(lines[i - 1]); if (i < end) sb.Append('\n'); } return sb.ToString(); } private sealed record ImportError(string Headline, long? Line, long? Column, string? Snippet); }