user-api-key-list.svelte 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. <script lang="ts">
  2. import { api, APIKeyResponseDto } from '@api';
  3. import { onMount } from 'svelte';
  4. import PencilOutline from 'svelte-material-icons/PencilOutline.svelte';
  5. import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
  6. import { fade } from 'svelte/transition';
  7. import { handleError } from '../../utils/handle-error';
  8. import APIKeyForm from '../forms/api-key-form.svelte';
  9. import APIKeySecret from '../forms/api-key-secret.svelte';
  10. import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
  11. import {
  12. notificationController,
  13. NotificationType
  14. } from '../shared-components/notification/notification';
  15. import { locale } from '$lib/stores/preferences.store';
  16. import Button from '../elements/buttons/button.svelte';
  17. let keys: APIKeyResponseDto[] = [];
  18. let newKey: Partial<APIKeyResponseDto> | null = null;
  19. let editKey: APIKeyResponseDto | null = null;
  20. let deleteKey: APIKeyResponseDto | null = null;
  21. let secret = '';
  22. const format: Intl.DateTimeFormatOptions = {
  23. month: 'short',
  24. day: 'numeric',
  25. year: 'numeric'
  26. };
  27. onMount(() => {
  28. refreshKeys();
  29. });
  30. async function refreshKeys() {
  31. const { data } = await api.keyApi.getKeys();
  32. keys = data;
  33. }
  34. const handleCreate = async (event: CustomEvent<APIKeyResponseDto>) => {
  35. try {
  36. const dto = event.detail;
  37. const { data } = await api.keyApi.createKey(dto);
  38. secret = data.secret;
  39. } catch (error) {
  40. handleError(error, 'Unable to create a new API Key');
  41. } finally {
  42. await refreshKeys();
  43. newKey = null;
  44. }
  45. };
  46. const handleUpdate = async (event: CustomEvent<APIKeyResponseDto>) => {
  47. if (!editKey) {
  48. return;
  49. }
  50. const dto = event.detail;
  51. try {
  52. await api.keyApi.updateKey(editKey.id, { name: dto.name });
  53. notificationController.show({
  54. message: `Saved API Key`,
  55. type: NotificationType.Info
  56. });
  57. } catch (error) {
  58. handleError(error, 'Unable to save API Key');
  59. } finally {
  60. await refreshKeys();
  61. editKey = null;
  62. }
  63. };
  64. const handleDelete = async () => {
  65. if (!deleteKey) {
  66. return;
  67. }
  68. try {
  69. await api.keyApi.deleteKey(deleteKey.id);
  70. notificationController.show({
  71. message: `Removed API Key: ${deleteKey.name}`,
  72. type: NotificationType.Info
  73. });
  74. } catch (error) {
  75. handleError(error, 'Unable to remove API Key');
  76. } finally {
  77. await refreshKeys();
  78. deleteKey = null;
  79. }
  80. };
  81. </script>
  82. {#if newKey}
  83. <APIKeyForm
  84. title="New API Key"
  85. submitText="Create"
  86. apiKey={newKey}
  87. on:submit={handleCreate}
  88. on:cancel={() => (newKey = null)}
  89. />
  90. {/if}
  91. {#if secret}
  92. <APIKeySecret {secret} on:done={() => (secret = '')} />
  93. {/if}
  94. {#if editKey}
  95. <APIKeyForm
  96. submitText="Save"
  97. apiKey={editKey}
  98. on:submit={handleUpdate}
  99. on:cancel={() => (editKey = null)}
  100. />
  101. {/if}
  102. {#if deleteKey}
  103. <ConfirmDialogue
  104. prompt="Are you sure you want to delete this API Key?"
  105. on:confirm={() => handleDelete()}
  106. on:cancel={() => (deleteKey = null)}
  107. />
  108. {/if}
  109. <section class="my-4">
  110. <div class="flex flex-col gap-2" in:fade={{ duration: 500 }}>
  111. <div class="flex justify-end mb-2">
  112. <Button size="sm" on:click={() => (newKey = { name: 'API Key' })}>New API Key</Button>
  113. </div>
  114. {#if keys.length > 0}
  115. <table class="text-left w-full">
  116. <thead
  117. class="border rounded-md mb-4 bg-gray-50 flex text-immich-primary w-full h-12 dark:bg-immich-dark-gray dark:text-immich-dark-primary dark:border-immich-dark-gray"
  118. >
  119. <tr class="flex w-full place-items-center">
  120. <th class="text-center w-1/3 font-medium text-sm">Name</th>
  121. <th class="text-center w-1/3 font-medium text-sm">Created</th>
  122. <th class="text-center w-1/3 font-medium text-sm">Action</th>
  123. </tr>
  124. </thead>
  125. <tbody class="overflow-y-auto rounded-md w-full block border dark:border-immich-dark-gray">
  126. {#each keys as key, i}
  127. {#key key.id}
  128. <tr
  129. class={`text-center flex place-items-center w-full h-[80px] dark:text-immich-dark-fg ${
  130. i % 2 == 0
  131. ? 'bg-immich-gray dark:bg-immich-dark-gray/75'
  132. : 'bg-immich-bg dark:bg-immich-dark-gray/50'
  133. }`}
  134. >
  135. <td class="text-sm px-4 w-1/3 text-ellipsis">{key.name}</td>
  136. <td class="text-sm px-4 w-1/3 text-ellipsis"
  137. >{new Date(key.createdAt).toLocaleDateString($locale, format)}
  138. </td>
  139. <td class="text-sm px-4 w-1/3 text-ellipsis">
  140. <button
  141. on:click={() => (editKey = key)}
  142. class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
  143. >
  144. <PencilOutline size="16" />
  145. </button>
  146. <button
  147. on:click={() => (deleteKey = key)}
  148. class="bg-immich-primary dark:bg-immich-dark-primary text-gray-100 dark:text-gray-700 rounded-full p-3 transition-all duration-150 hover:bg-immich-primary/75"
  149. >
  150. <TrashCanOutline size="16" />
  151. </button>
  152. </td>
  153. </tr>
  154. {/key}
  155. {/each}
  156. </tbody>
  157. </table>
  158. {/if}
  159. </div>
  160. </section>