Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | import { defineStore } from 'pinia' import axios from 'axios' import { credentialsApi } from '@/api/credentials.api' import type { OpenStackCredentialFromYaml, OpenStackCredentialResponse, OpenStackCredentialUpsert, } from '@/types/openstack-credential' interface State { status: OpenStackCredentialResponse | null loading: boolean error: string | null } const LOCKED_REASON = 'openstack_credentials_locked' function extractError(err: unknown, fallback: string): string { if (axios.isAxiosError(err)) { const detail = err.response?.data?.detail if (typeof detail === 'string') return detail if (detail && typeof detail === 'object') { const reason = (detail as { reason?: string }).reason if (reason === LOCKED_REASON) { const n = (detail as { active_deployments?: number }).active_deployments ?? 0 return `Credentials gesperrt — ${n} aktive(s) Deployment(s)` } if (reason) return String(reason) } } return fallback } function isLockedError(err: unknown): boolean { if (!axios.isAxiosError(err)) return false if (err.response?.status !== 409) return false const detail = err.response.data?.detail return !!(detail && typeof detail === 'object' && (detail as { reason?: string }).reason === LOCKED_REASON) } // Dedupe concurrent fetch() calls — DashboardView mount, auth store // post-login, and route guards can all kick this off at the same time // on a cold load. Without this, each trigger hits the backend. let fetchPromise: Promise<OpenStackCredentialResponse | null> | null = null export const useOpenStackCredentialsStore = defineStore('openstack-credentials', { state: (): State => ({ status: null, loading: false, error: null, }), getters: { hasCredential: (s) => !!s.status?.has_credential, isValidated: (s) => !!s.status?.last_validated_at && !s.status?.last_validation_error, lastError: (s) => s.status?.last_validation_error || null, isLocked: (s) => !!s.status?.is_locked, activeDeployments: (s) => s.status?.active_deployments ?? 0, // True once the GET /me/openstack-credentials has resolved at least // once. Banners and disabled states should gate on this to avoid the // "fehlen" message flashing during the initial fetch. isResolved: (s) => s.status !== null, }, actions: { async fetch() { if (fetchPromise) return fetchPromise fetchPromise = (async () => { this.loading = true this.error = null try { const res = await credentialsApi.get() this.status = res.data return res.data } catch (err) { this.error = extractError(err, 'Failed to load OpenStack credentials') return null } finally { this.loading = false fetchPromise = null } })() return fetchPromise }, async save(payload: OpenStackCredentialUpsert) { this.loading = true this.error = null try { const res = await credentialsApi.put(payload) this.status = res.data return res.data } catch (err) { this.error = extractError(err, 'Failed to save OpenStack credentials') if (isLockedError(err)) await this.fetch() throw err } finally { this.loading = false } }, async saveFromYaml(body: OpenStackCredentialFromYaml) { this.loading = true this.error = null try { const res = await credentialsApi.putFromYaml(body) this.status = res.data return res.data } catch (err) { this.error = extractError(err, 'Failed to parse clouds.yaml') if (isLockedError(err)) await this.fetch() throw err } finally { this.loading = false } }, async remove() { this.loading = true this.error = null try { await credentialsApi.remove() await this.fetch() } catch (err) { this.error = extractError(err, 'Failed to delete OpenStack credentials') if (isLockedError(err)) await this.fetch() throw err } finally { this.loading = false } }, async test() { this.loading = true this.error = null try { const res = await credentialsApi.test() this.status = res.data return res.data } catch (err) { this.error = extractError(err, 'Failed to validate OpenStack credentials') throw err } finally { this.loading = false } }, reset() { this.status = null this.error = null this.loading = false }, }, }) |