SshExport.razor 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. @page "/ssh/export"
  2. @using RackPeek.Domain.UseCases.SSH
  3. @inject SshConfigExportUseCase SshUseCase
  4. <div class="border border-zinc-800 rounded p-4 bg-zinc-900 max-w-5xl mx-auto"
  5. data-testid="ssh-export-page">
  6. <div class="flex justify-between items-center mb-4">
  7. <div class="text-zinc-100 text-lg">
  8. SSH Config Export
  9. </div>
  10. <button class="text-sm bg-emerald-600 hover:bg-emerald-500 px-3 py-1 rounded text-white transition"
  11. data-testid="generate-ssh-button"
  12. @onclick="GenerateExport">
  13. Generate
  14. </button>
  15. </div>
  16. <!-- Options -->
  17. <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
  18. <!-- Include Tags -->
  19. <div>
  20. <div class="text-zinc-400 mb-1">Include Tags</div>
  21. <input class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700"
  22. placeholder="prod, linux"
  23. data-testid="ssh-include-tags-input"
  24. @bind="_includeTagsRaw"/>
  25. <div class="text-xs text-zinc-500 mt-1">
  26. Only include resources with these tags (optional)
  27. </div>
  28. </div>
  29. <!-- Default SSH User -->
  30. <div>
  31. <div class="text-zinc-400 mb-1">Default SSH User</div>
  32. <input class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700"
  33. placeholder="ubuntu"
  34. data-testid="ssh-default-user-input"
  35. @bind="_defaultUser"/>
  36. <div class="text-xs text-zinc-500 mt-1">
  37. Used if ssh_user or ansible_user label is not defined
  38. </div>
  39. </div>
  40. <!-- Default SSH Port -->
  41. <div>
  42. <div class="text-zinc-400 mb-1">Default SSH Port</div>
  43. <input type="number"
  44. class="w-full px-3 py-2 rounded-md
  45. bg-zinc-800 text-zinc-100
  46. border border-zinc-600
  47. placeholder-zinc-500
  48. focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
  49. hover:border-zinc-400
  50. transition-colors duration-150
  51. cursor-text"
  52. data-testid="ssh-default-port-input"
  53. @bind="_defaultPort"/>
  54. <div class="text-xs text-zinc-500 mt-1">
  55. Used if ssh_port or ansible_port label is not defined
  56. </div>
  57. </div>
  58. <!-- Default Identity File -->
  59. <div>
  60. <div class="text-zinc-400 mb-1">Default Identity File</div>
  61. <input class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700"
  62. placeholder="~/.ssh/id_rsa"
  63. data-testid="ssh-default-identity-input"
  64. @bind="_defaultIdentityFile"/>
  65. <div class="text-xs text-zinc-500 mt-1">
  66. Used if ssh_identity_file label is not defined
  67. </div>
  68. </div>
  69. </div>
  70. <!-- Warnings -->
  71. @if (_warnings.Any())
  72. {
  73. <div class="border border-red-700 bg-red-900/40 text-red-300 p-3 rounded mb-4"
  74. data-testid="ssh-warnings">
  75. <div class="font-semibold mb-1">Warnings</div>
  76. <ul class="list-disc ml-5 text-sm">
  77. @foreach (var warning in _warnings)
  78. {
  79. <li>@warning</li>
  80. }
  81. </ul>
  82. </div>
  83. }
  84. <!-- Output -->
  85. <div>
  86. <div class="text-zinc-400 mb-1">Generated SSH Config</div>
  87. <textarea class="w-full bg-black text-emerald-400 p-3 rounded border border-zinc-800 font-mono text-sm"
  88. rows="18"
  89. readonly
  90. data-testid="ssh-output">
  91. @_sshText
  92. </textarea>
  93. </div>
  94. </div>
  95. @code {
  96. private string _includeTagsRaw = string.Empty;
  97. private string? _defaultUser = "ubuntu";
  98. private int _defaultPort = 22;
  99. private string? _defaultIdentityFile = "~/.ssh/id_rsa";
  100. private string _sshText = string.Empty;
  101. private List<string> _warnings = new();
  102. private async Task GenerateExport()
  103. {
  104. try
  105. {
  106. _warnings.Clear();
  107. _sshText = string.Empty;
  108. var options = new SshExportOptions
  109. {
  110. IncludeTags = ParseCsv(_includeTagsRaw),
  111. DefaultUser = string.IsNullOrWhiteSpace(_defaultUser) ? null : _defaultUser,
  112. DefaultPort = _defaultPort,
  113. DefaultIdentityFile = string.IsNullOrWhiteSpace(_defaultIdentityFile)
  114. ? null
  115. : _defaultIdentityFile
  116. };
  117. var result = await SshUseCase.ExecuteAsync(options);
  118. if (result is null)
  119. {
  120. _warnings.Add("SSH export returned null.");
  121. return;
  122. }
  123. _sshText = result.ConfigText;
  124. _warnings = result.Warnings.ToList();
  125. }
  126. catch (Exception ex)
  127. {
  128. _warnings.Clear();
  129. _warnings.Add($"Unexpected error: {ex.Message}");
  130. }
  131. }
  132. private static IReadOnlyList<string> ParseCsv(string raw)
  133. {
  134. if (string.IsNullOrWhiteSpace(raw))
  135. return [];
  136. return raw.Split(',', StringSplitOptions.RemoveEmptyEntries)
  137. .Select(x => x.Trim())
  138. .Where(x => !string.IsNullOrWhiteSpace(x))
  139. .ToArray();
  140. }
  141. }