ServiceCardComponent.razor 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. <div class="md:col-span-2">
  164. <div class="text-zinc-400 mb-1">Notes</div>
  165. @if (_isEditing)
  166. {
  167. <MarkdownEditor
  168. @bind-Value="_edit.Notes"
  169. ShowActionButtons="false" />
  170. }
  171. else
  172. {
  173. <MarkdownViewer
  174. Value="@Service.Notes"
  175. ShowEditButton="false" />
  176. }
  177. </div>
  178. </div>
  179. </div>
  180. <SystemSelectionModal
  181. IsOpen="@_selectParentOpen"
  182. IsOpenChanged="v => _selectParentOpen = v"
  183. Title="Select a parent"
  184. Value="@SelectedParentName"
  185. OnAccept="HandleParentSelected"/>
  186. <ConfirmModal
  187. IsOpen="_confirmDeleteOpen"
  188. IsOpenChanged="v => _confirmDeleteOpen = v"
  189. Title="Delete server"
  190. ConfirmText="Delete"
  191. ConfirmClass="bg-red-600 hover:bg-red-500"
  192. OnConfirm="DeleteServer">
  193. Are you sure you want to delete <strong>@Service.Name</strong>?
  194. </ConfirmModal>
  195. <StringValueModal
  196. IsOpen="_cloneOpen"
  197. IsOpenChanged="v => _cloneOpen = v"
  198. Title="Clone service"
  199. Description="Enter a name for the cloned service"
  200. Label="New service name"
  201. Value="@($"{Service.Name}-copy")"
  202. OnSubmit="HandleCloneSubmit" />
  203. <StringValueModal
  204. IsOpen="_renameOpen"
  205. IsOpenChanged="v => _renameOpen = v"
  206. Title="Rename service"
  207. Description="Enter a new name for this service"
  208. Label="New service name"
  209. Value="@Service.Name"
  210. OnSubmit="HandleRenameSubmit" />
  211. @code
  212. {
  213. bool _cloneOpen;
  214. void OpenClone()
  215. {
  216. _cloneOpen = true;
  217. }
  218. async Task HandleCloneSubmit(string newName)
  219. {
  220. await CloneServiceUseCase.ExecuteAsync(Service.Name, newName);
  221. Nav.NavigateTo($"resources/services/{newName}");
  222. }
  223. }
  224. @code {
  225. [Parameter] [EditorRequired] public Service Service { get; set; } = default!;
  226. [Parameter] public EventCallback<ServiceEditModel> OnSave { get; set; }
  227. private bool _isEditing;
  228. private ServiceEditModel _edit = new();
  229. void BeginEdit()
  230. {
  231. _edit = ServiceEditModel.From(Service);
  232. _isEditing = true;
  233. }
  234. async Task Save()
  235. {
  236. _isEditing = false;
  237. await OnSave.InvokeAsync(_edit);
  238. }
  239. void Cancel()
  240. {
  241. _isEditing = false;
  242. }
  243. bool _selectParentOpen;
  244. string? SelectedParentName;
  245. async Task HandleParentSelected(string? name)
  246. {
  247. SelectedParentName = name;
  248. await UpdateServiceUseCase.ExecuteAsync(
  249. Service.Name,
  250. Service.Network?.Ip,
  251. Service.Network?.Port,
  252. Service.Network?.Protocol,
  253. Service.Network?.Url,
  254. name,
  255. Service.Notes);
  256. Service = await GetServiceUseCase.ExecuteAsync(Service.Name);
  257. _edit = ServiceEditModel.From(Service);
  258. }
  259. }
  260. <ConfirmModal
  261. IsOpen="_confirmDeleteOpen"
  262. IsOpenChanged="v => _confirmDeleteOpen = v"
  263. Title="Delete service"
  264. ConfirmText="Delete"
  265. ConfirmClass="bg-red-600 hover:bg-red-500"
  266. OnConfirm="DeleteServer">
  267. Are you sure you want to delete <strong>@Service.Name</strong>?
  268. <br/>
  269. </ConfirmModal>
  270. @code {
  271. private bool _confirmDeleteOpen;
  272. [Parameter] public EventCallback<string> OnDeleted { get; set; }
  273. void ConfirmDelete()
  274. {
  275. _confirmDeleteOpen = true;
  276. }
  277. async Task DeleteServer()
  278. {
  279. _confirmDeleteOpen = false;
  280. await DeleteServiceUseCase.ExecuteAsync(Service.Name);
  281. if (OnDeleted.HasDelegate)
  282. await OnDeleted.InvokeAsync(Service.Name);
  283. }
  284. }
  285. @code
  286. {
  287. bool _renameOpen;
  288. void OpenRename()
  289. {
  290. _renameOpen = true;
  291. }
  292. async Task HandleRenameSubmit(string newName)
  293. {
  294. await RenameServiceUseCase.ExecuteAsync(Service.Name, newName);
  295. Nav.NavigateTo($"resources/services/{newName}");
  296. }
  297. }