ServiceCardComponent.razor 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. @using RackPeek.Domain.Resources.Services
  2. @using RackPeek.Domain.Resources.Services.UseCases
  3. @inject UpdateServiceUseCase UpdateServiceUseCase
  4. @inject GetServiceUseCase GetServiceUseCase
  5. @inject DeleteServiceUseCase DeleteServiceUseCase
  6. @inject CloneServiceUseCase CloneServiceUseCase
  7. @inject NavigationManager Nav
  8. @inject RenameServiceUseCase RenameServiceUseCase
  9. <div class="border border-zinc-800 rounded p-4 bg-zinc-900">
  10. <div class="flex justify-between items-center mb-3">
  11. <NavLink href="@($"resources/services/{Service.Name}")" class="block">
  12. <div class="text-zinc-100 hover:text-emerald-300">
  13. @Service.Name
  14. </div>
  15. </NavLink>
  16. <div class="flex gap-3 text-xs">
  17. @if (!_isEditing)
  18. {
  19. <button class="text-zinc-400 hover:text-zinc-200"
  20. @onclick="BeginEdit">
  21. Edit
  22. </button>
  23. <button class="text-xs text-blue-400 hover:text-blue-300 transition"
  24. title="Rename service"
  25. @onclick="OpenRename">
  26. Rename
  27. </button>
  28. <button
  29. class="text-xs text-emerald-400 hover:text-emerald-300 transition"
  30. title="Clone service"
  31. @onclick="OpenClone">
  32. Clone
  33. </button>
  34. <button
  35. class="text-xs text-red-400 hover:text-red-300 transition"
  36. title="Delete server"
  37. @onclick="ConfirmDelete">
  38. Delete
  39. </button>
  40. }
  41. else
  42. {
  43. <button class="text-emerald-400 hover:text-emerald-300"
  44. @onclick="Save">
  45. Save
  46. </button>
  47. <button class="text-zinc-500 hover:text-zinc-300"
  48. @onclick="Cancel">
  49. Cancel
  50. </button>
  51. }
  52. </div>
  53. </div>
  54. <div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
  55. <!-- IP -->
  56. <div>
  57. <div class="text-zinc-400 mb-1">IP</div>
  58. @if (_isEditing)
  59. {
  60. <input
  61. class="w-full px-3 py-2 rounded-md
  62. bg-zinc-800 text-zinc-100
  63. border border-zinc-600
  64. placeholder-zinc-500
  65. focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
  66. hover:border-zinc-400
  67. transition-colors duration-150
  68. cursor-text"
  69. @bind="_edit.Ip"/>
  70. }
  71. else if (!string.IsNullOrWhiteSpace(Service.Network?.Ip))
  72. {
  73. <div class="text-zinc-300">@Service.Network!.Ip</div>
  74. }
  75. </div>
  76. <!-- Port -->
  77. <div>
  78. <div class="text-zinc-400 mb-1">Port</div>
  79. @if (_isEditing)
  80. {
  81. <input type="number"
  82. @bind="_edit.Port"/>
  83. }
  84. else if (Service.Network?.Port.HasValue == true)
  85. {
  86. <div class="text-zinc-300">@Service.Network.Port</div>
  87. }
  88. </div>
  89. <!-- Protocol -->
  90. <div>
  91. <div class="text-zinc-400 mb-1">Protocol</div>
  92. @if (_isEditing)
  93. {
  94. <input
  95. class="w-full px-3 py-2 rounded-md
  96. bg-zinc-800 text-zinc-100
  97. border border-zinc-600
  98. placeholder-zinc-500
  99. focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
  100. hover:border-zinc-400
  101. transition-colors duration-150
  102. cursor-text"
  103. @bind="_edit.Protocol"/>
  104. }
  105. else if (!string.IsNullOrWhiteSpace(Service.Network?.Protocol))
  106. {
  107. <div class="text-zinc-300">@Service.Network!.Protocol</div>
  108. }
  109. </div>
  110. <!-- URL -->
  111. <div>
  112. <div class="text-zinc-400 mb-1">URL</div>
  113. @if (_isEditing)
  114. {
  115. <input
  116. class="w-full px-3 py-2 rounded-md
  117. bg-zinc-800 text-zinc-100
  118. border border-zinc-600
  119. placeholder-zinc-500
  120. focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500
  121. hover:border-zinc-400
  122. transition-colors duration-150
  123. cursor-text"
  124. @bind="_edit.Url"/>
  125. }
  126. else if (!string.IsNullOrWhiteSpace(Service.Network?.Url))
  127. {
  128. <a href="@Service.Network!.Url"
  129. target="_blank"
  130. rel="noopener noreferrer"
  131. class="text-emerald-400 hover:underline break-all">
  132. @Service.Network.Url
  133. </a>
  134. }
  135. </div>
  136. <!-- Runs On -->
  137. <div>
  138. <div class="text-zinc-400 mb-1">Runs On</div>
  139. @if (_isEditing)
  140. {
  141. <button
  142. class="hover:text-emerald-400"
  143. title="Edit Runs On"
  144. @onclick="() => _selectParentOpen = true">
  145. @if (!string.IsNullOrWhiteSpace(Service.RunsOn))
  146. {
  147. @($"{Service.RunsOn} +")
  148. }
  149. else
  150. {
  151. @("Edit parent")
  152. }
  153. </button>
  154. }
  155. else if (!string.IsNullOrWhiteSpace(Service.RunsOn))
  156. {
  157. <NavLink href="@($"resources/systems/{Service.RunsOn}")"
  158. class="text-emerald-400">
  159. @Service.RunsOn
  160. </NavLink>
  161. }
  162. </div>
  163. <ResourceTagEditor Resource="Service" />
  164. <div class="md:col-span-2">
  165. <div class="text-zinc-400 mb-1">Notes</div>
  166. @if (_isEditing)
  167. {
  168. <MarkdownEditor
  169. @bind-Value="_edit.Notes"
  170. ShowActionButtons="false" />
  171. }
  172. else
  173. {
  174. <MarkdownViewer
  175. Value="@Service.Notes"
  176. ShowEditButton="false" />
  177. }
  178. </div>
  179. </div>
  180. </div>
  181. <SystemSelectionModal
  182. IsOpen="@_selectParentOpen"
  183. IsOpenChanged="v => _selectParentOpen = v"
  184. Title="Select a parent"
  185. Value="@SelectedParentName"
  186. OnAccept="HandleParentSelected"/>
  187. <ConfirmModal
  188. IsOpen="_confirmDeleteOpen"
  189. IsOpenChanged="v => _confirmDeleteOpen = v"
  190. Title="Delete server"
  191. ConfirmText="Delete"
  192. ConfirmClass="bg-red-600 hover:bg-red-500"
  193. OnConfirm="DeleteServer">
  194. Are you sure you want to delete <strong>@Service.Name</strong>?
  195. </ConfirmModal>
  196. <StringValueModal
  197. IsOpen="_cloneOpen"
  198. IsOpenChanged="v => _cloneOpen = v"
  199. Title="Clone service"
  200. Description="Enter a name for the cloned service"
  201. Label="New service name"
  202. Value="@($"{Service.Name}-copy")"
  203. OnSubmit="HandleCloneSubmit" />
  204. <StringValueModal
  205. IsOpen="_renameOpen"
  206. IsOpenChanged="v => _renameOpen = v"
  207. Title="Rename service"
  208. Description="Enter a new name for this service"
  209. Label="New service name"
  210. Value="@Service.Name"
  211. OnSubmit="HandleRenameSubmit" />
  212. @code
  213. {
  214. bool _cloneOpen;
  215. void OpenClone()
  216. {
  217. _cloneOpen = true;
  218. }
  219. async Task HandleCloneSubmit(string newName)
  220. {
  221. await CloneServiceUseCase.ExecuteAsync(Service.Name, newName);
  222. Nav.NavigateTo($"resources/services/{newName}");
  223. }
  224. }
  225. @code {
  226. [Parameter] [EditorRequired] public Service Service { get; set; } = default!;
  227. [Parameter] public EventCallback<ServiceEditModel> OnSave { get; set; }
  228. private bool _isEditing;
  229. private ServiceEditModel _edit = new();
  230. void BeginEdit()
  231. {
  232. _edit = ServiceEditModel.From(Service);
  233. _isEditing = true;
  234. }
  235. async Task Save()
  236. {
  237. _isEditing = false;
  238. await OnSave.InvokeAsync(_edit);
  239. }
  240. void Cancel()
  241. {
  242. _isEditing = false;
  243. }
  244. bool _selectParentOpen;
  245. string? SelectedParentName;
  246. async Task HandleParentSelected(string? name)
  247. {
  248. SelectedParentName = name;
  249. await UpdateServiceUseCase.ExecuteAsync(
  250. Service.Name,
  251. Service.Network?.Ip,
  252. Service.Network?.Port,
  253. Service.Network?.Protocol,
  254. Service.Network?.Url,
  255. name,
  256. Service.Notes);
  257. Service = await GetServiceUseCase.ExecuteAsync(Service.Name);
  258. _edit = ServiceEditModel.From(Service);
  259. }
  260. }
  261. <ConfirmModal
  262. IsOpen="_confirmDeleteOpen"
  263. IsOpenChanged="v => _confirmDeleteOpen = v"
  264. Title="Delete service"
  265. ConfirmText="Delete"
  266. ConfirmClass="bg-red-600 hover:bg-red-500"
  267. OnConfirm="DeleteServer">
  268. Are you sure you want to delete <strong>@Service.Name</strong>?
  269. <br/>
  270. </ConfirmModal>
  271. @code {
  272. private bool _confirmDeleteOpen;
  273. [Parameter] public EventCallback<string> OnDeleted { get; set; }
  274. void ConfirmDelete()
  275. {
  276. _confirmDeleteOpen = true;
  277. }
  278. async Task DeleteServer()
  279. {
  280. _confirmDeleteOpen = false;
  281. await DeleteServiceUseCase.ExecuteAsync(Service.Name);
  282. if (OnDeleted.HasDelegate)
  283. await OnDeleted.InvokeAsync(Service.Name);
  284. }
  285. }
  286. @code
  287. {
  288. bool _renameOpen;
  289. void OpenRename()
  290. {
  291. _renameOpen = true;
  292. }
  293. async Task HandleRenameSubmit(string newName)
  294. {
  295. await RenameServiceUseCase.ExecuteAsync(Service.Name, newName);
  296. Nav.NavigateTo($"resources/services/{newName}");
  297. }
  298. }