|
1 | 1 | <script> |
2 | 2 | import { page } from '$app/state'; |
| 3 | + import { |
| 4 | + AlertError, |
| 5 | + displayStandardErrorAlert, |
| 6 | + getValidationMessagesMap |
| 7 | + } from '$lib/common/errors'; |
| 8 | + import StandardDismissableAlert from '$lib/components/common/StandardDismissableAlert.svelte'; |
| 9 | + import { normalizePayload } from 'fractal-components'; |
3 | 10 | import BooleanIcon from 'fractal-components/common/BooleanIcon.svelte'; |
| 11 | + import { onMount } from 'svelte'; |
4 | 12 |
|
5 | 13 | /** @type {import('fractal-components/types/api').User & {group_ids_names: Array<[number, string]>}} */ |
6 | 14 | const user = $derived(page.data.user); |
7 | 15 | /** @type {import('fractal-components/types/api').ProfileInfo} */ |
8 | 16 | const profile = $derived(page.data.profile); |
| 17 | +
|
| 18 | + /** |
| 19 | + * @type {import('fractal-components/types/api').UserSettings} |
| 20 | + */ |
| 21 | + const settings = $derived(page.data.settings); |
| 22 | + const runnerBackend = $derived(page.data.runnerBackend); |
| 23 | +
|
| 24 | + /** @type {import('$lib/components/common/StandardErrorAlert.svelte').default|undefined} */ |
| 25 | + let errorAlert = undefined; |
| 26 | +
|
| 27 | + /** @type {string[]} */ |
| 28 | + let slurmAccounts = $state([]); |
| 29 | + let slurmAccountsError = $state(''); |
| 30 | +
|
| 31 | + let settingsUpdatedMessage = $state(''); |
| 32 | +
|
| 33 | + function addSlurmAccount() { |
| 34 | + slurmAccounts = [...slurmAccounts, '']; |
| 35 | + } |
| 36 | +
|
| 37 | + /** |
| 38 | + * @param {number} index |
| 39 | + */ |
| 40 | + function removeSlurmAccount(index) { |
| 41 | + slurmAccounts = slurmAccounts.filter((_, i) => i !== index); |
| 42 | + } |
| 43 | +
|
| 44 | + async function save() { |
| 45 | + if (errorAlert) { |
| 46 | + errorAlert.hide(); |
| 47 | + } |
| 48 | + settingsUpdatedMessage = ''; |
| 49 | + slurmAccountsError = ''; |
| 50 | + const headers = new Headers(); |
| 51 | + headers.set('Content-Type', 'application/json'); |
| 52 | + const payload = { |
| 53 | + slurm_accounts: slurmAccounts |
| 54 | + }; |
| 55 | + const response = await fetch(`/api/auth/current-user/settings`, { |
| 56 | + method: 'PATCH', |
| 57 | + credentials: 'include', |
| 58 | + headers, |
| 59 | + body: normalizePayload(payload, { nullifyEmptyStrings: true }) |
| 60 | + }); |
| 61 | + const result = await response.json(); |
| 62 | + if (response.ok) { |
| 63 | + initFields(result); |
| 64 | + settingsUpdatedMessage = 'User settings successfully updated'; |
| 65 | + } else { |
| 66 | + const errorMap = getValidationMessagesMap(result, response.status); |
| 67 | + let errorShown = false; |
| 68 | + if (errorMap) { |
| 69 | + if ('slurm_accounts' in errorMap) { |
| 70 | + slurmAccountsError = /** @type {string} */ (errorMap['slurm_accounts']); |
| 71 | + errorShown = true; |
| 72 | + } |
| 73 | + } |
| 74 | + if (!errorShown) { |
| 75 | + errorAlert = displayStandardErrorAlert( |
| 76 | + new AlertError(result, response.status), |
| 77 | + 'settingsUpdate-error' |
| 78 | + ); |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | +
|
| 83 | + /** |
| 84 | + * @param {import('fractal-components/types/api').UserSettings} settings |
| 85 | + */ |
| 86 | + function initFields(settings) { |
| 87 | + slurmAccounts = settings.slurm_accounts; |
| 88 | + } |
| 89 | +
|
| 90 | + onMount(() => { |
| 91 | + initFields(page.data.settings); |
| 92 | + }); |
9 | 93 | </script> |
10 | 94 |
|
11 | 95 | <div class="container mt-3"> |
|
86 | 170 | </tr> |
87 | 171 | {/if} |
88 | 172 | {/if} |
| 173 | + <tr> |
| 174 | + <th>Project dir</th> |
| 175 | + <td>{settings.project_dir || '-'}</td> |
| 176 | + </tr> |
89 | 177 | </tbody> |
90 | 178 | </table> |
91 | 179 | </div> |
92 | 180 | </div> |
| 181 | + |
| 182 | + {#if runnerBackend !== 'local'} |
| 183 | + <div class="row mb-3"> |
| 184 | + <div class="col-lg-2 col-sm-4 ms-2 fw-bold">SLURM accounts</div> |
| 185 | + <div class="col-lg-6 col-sm-8"> |
| 186 | + <div class="col-sm-9 has-validation"> |
| 187 | + <!-- eslint-disable-next-line no-unused-vars --> |
| 188 | + {#each slurmAccounts as _, i (i)} |
| 189 | + <div class="input-group mb-2" class:is-invalid={slurmAccountsError}> |
| 190 | + <input |
| 191 | + type="text" |
| 192 | + class="form-control" |
| 193 | + id={`slurmAccount-${i}`} |
| 194 | + bind:value={slurmAccounts[i]} |
| 195 | + class:is-invalid={slurmAccountsError} |
| 196 | + aria-label="SLURM account {i + 1}" |
| 197 | + required |
| 198 | + /> |
| 199 | + <button |
| 200 | + class="btn btn-outline-secondary" |
| 201 | + type="button" |
| 202 | + id="slurm_account_remove_{i}" |
| 203 | + aria-label="Remove SLURM account" |
| 204 | + onclick={() => removeSlurmAccount(i)} |
| 205 | + > |
| 206 | + <i class="bi bi-trash"></i> |
| 207 | + </button> |
| 208 | + </div> |
| 209 | + {/each} |
| 210 | + <span class="invalid-feedback mb-2">{slurmAccountsError}</span> |
| 211 | + <button class="btn btn-light" type="button" onclick={addSlurmAccount}> |
| 212 | + <i class="bi bi-plus-circle"></i> |
| 213 | + Add SLURM account |
| 214 | + </button> |
| 215 | + </div> |
| 216 | + </div> |
| 217 | + </div> |
| 218 | + |
| 219 | + <div class="row"> |
| 220 | + <div class="col-lg-6 col-sm-8 offset-lg-2 offset-sm-4"> |
| 221 | + <div id="settingsUpdate-error"></div> |
| 222 | + <StandardDismissableAlert message={settingsUpdatedMessage} /> |
| 223 | + <button class="btn btn-primary ms-2" onclick={save}> Save </button> |
| 224 | + </div> |
| 225 | + </div> |
| 226 | + {/if} |
93 | 227 | </div> |
0 commit comments