AnsibleInventory.razor 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. @page "/ansible/inventory"
  2. @using RackPeek.Domain.UseCases.Ansible
  3. @inject AnsibleInventoryGeneratorUseCase InventoryUseCase
  4. <div class="border border-zinc-800 rounded p-4 bg-zinc-900 max-w-5xl mx-auto"
  5. data-testid="ansible-inventory-page">
  6. <div class="flex justify-between items-center mb-4">
  7. <div class="text-zinc-100 text-lg">
  8. Ansible Inventory Generator
  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-inventory-button"
  12. @onclick="GenerateInventory">
  13. Generate
  14. </button>
  15. </div>
  16. <!-- Options -->
  17. <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
  18. <!-- Format -->
  19. <div>
  20. <div class="text-zinc-400 mb-1">Format</div>
  21. <select class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700"
  22. data-testid="inventory-format-select"
  23. @bind="_selectedFormat">
  24. <option value="Ini">INI</option>
  25. <option value="Yaml">YAML</option>
  26. </select>
  27. <div class="text-xs text-zinc-500 mt-1">
  28. Select output format
  29. </div>
  30. </div>
  31. <!-- Tags -->
  32. <div>
  33. <div class="text-zinc-400 mb-1">Group By Tags</div>
  34. <input class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700"
  35. placeholder="prod, staging, dmz"
  36. data-testid="group-by-tags-input"
  37. @bind="_groupByTagsRaw"/>
  38. <div class="text-xs text-zinc-500 mt-1">
  39. Comma-separated tag names
  40. </div>
  41. </div>
  42. <!-- Labels -->
  43. <div>
  44. <div class="text-zinc-400 mb-1">Group By Labels</div>
  45. <input class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700"
  46. placeholder="env, site"
  47. data-testid="group-by-labels-input"
  48. @bind="_groupByLabelsRaw"/>
  49. <div class="text-xs text-zinc-500 mt-1">
  50. Creates groups like env_prod, site_london
  51. </div>
  52. </div>
  53. <!-- Global Vars -->
  54. <div class="md:col-span-2">
  55. <div class="text-zinc-400 mb-1">Global Variables</div>
  56. <textarea class="w-full bg-zinc-800 text-zinc-200 p-2 rounded border border-zinc-700 font-mono text-sm"
  57. rows="4"
  58. placeholder="ansible_user=ansible&#10;ansible_python_interpreter=/usr/bin/python3"
  59. data-testid="global-vars-input"
  60. @bind="_globalVarsRaw">
  61. </textarea>
  62. <div class="text-xs text-zinc-500 mt-1">
  63. One per line: key=value
  64. </div>
  65. </div>
  66. </div>
  67. <!-- Errors / Warnings -->
  68. @if (_warnings.Any())
  69. {
  70. <div class="border border-red-700 bg-red-900/40 text-red-300 p-3 rounded mb-4"
  71. data-testid="inventory-warnings">
  72. <div class="font-semibold mb-1">Warnings</div>
  73. <ul class="list-disc ml-5 text-sm">
  74. @foreach (var warning in _warnings)
  75. {
  76. <li>@warning</li>
  77. }
  78. </ul>
  79. </div>
  80. }
  81. <!-- Output -->
  82. <div>
  83. <div class="text-zinc-400 mb-1">Generated Inventory</div>
  84. <textarea class="w-full bg-black text-emerald-400 p-3 rounded border border-zinc-800 font-mono text-sm"
  85. rows="18"
  86. readonly
  87. data-testid="inventory-output">
  88. @_inventoryText
  89. </textarea>
  90. </div>
  91. </div>
  92. @code {
  93. private InventoryFormat _selectedFormat = InventoryFormat.Ini;
  94. private string _groupByTagsRaw = "prod, staging";
  95. private string _groupByLabelsRaw = "env, site";
  96. private string _globalVarsRaw =
  97. @"ansible_user=ansible
  98. ansible_python_interpreter=/usr/bin/python3";
  99. private string _inventoryText = string.Empty;
  100. private List<string> _warnings = new();
  101. private async Task GenerateInventory()
  102. {
  103. try
  104. {
  105. _warnings.Clear();
  106. _inventoryText = string.Empty;
  107. var options = new InventoryOptions
  108. {
  109. Format = _selectedFormat,
  110. GroupByTags = ParseCsv(_groupByTagsRaw),
  111. GroupByLabelKeys = ParseCsv(_groupByLabelsRaw),
  112. GlobalVars = ParseGlobalVars(_globalVarsRaw)
  113. };
  114. var result = await InventoryUseCase.ExecuteAsync(options);
  115. if (result is null)
  116. {
  117. _warnings.Add("Inventory generation returned null.");
  118. return;
  119. }
  120. _inventoryText = result.InventoryText;
  121. _warnings = result.Warnings.ToList();
  122. }
  123. catch (Exception ex)
  124. {
  125. _warnings.Clear();
  126. _warnings.Add($"Unexpected error: {ex.Message}");
  127. }
  128. }
  129. private static IReadOnlyList<string> ParseCsv(string raw)
  130. {
  131. if (string.IsNullOrWhiteSpace(raw))
  132. return [];
  133. return raw.Split(',', StringSplitOptions.RemoveEmptyEntries)
  134. .Select(x => x.Trim())
  135. .Where(x => !string.IsNullOrWhiteSpace(x))
  136. .ToArray();
  137. }
  138. private static IDictionary<string, string> ParseGlobalVars(string raw)
  139. {
  140. var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  141. if (string.IsNullOrWhiteSpace(raw))
  142. return dict;
  143. var lines = raw.Split('\n', StringSplitOptions.RemoveEmptyEntries);
  144. foreach (var line in lines)
  145. {
  146. var trimmed = line.Trim();
  147. if (string.IsNullOrWhiteSpace(trimmed)) continue;
  148. var parts = trimmed.Split('=', 2);
  149. if (parts.Length != 2) continue;
  150. var key = parts[0].Trim();
  151. var value = parts[1].Trim();
  152. if (!string.IsNullOrWhiteSpace(key))
  153. dict[key] = value;
  154. }
  155. return dict;
  156. }
  157. }