ServiceCardComponent.razor 10 KB

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