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 | import { ref, computed } from 'vue' import axios from 'axios' import { Cpu, HardDrive, Network } from 'lucide-vue-next' import { quotasApi } from '@/api/quotas.api' import type { QuotaOverview } from '@/types/quota' // ---------------------------------------------------------------- // MODULE-SCOPED STATE (shared across all consumers) // ---------------------------------------------------------------- // Why module-scoped: the dashboard mounts/unmounts whenever the user // navigates, but we want the quota tile to keep showing the last known // numbers while the next fetch is in flight instead of flashing the // loading skeleton again. Lifting state above the composable factory is // the cheapest way to share it; sessionStorage extends that across // reloads inside the same browser session. const STORAGE_KEY = 'openstack.quotas.v1' function readCachedQuotas(): QuotaOverview | null { try { const raw = sessionStorage.getItem(STORAGE_KEY) if (!raw) return null return JSON.parse(raw) as QuotaOverview } catch { return null } } function writeCachedQuotas(value: QuotaOverview | null) { try { if (value) sessionStorage.setItem(STORAGE_KEY, JSON.stringify(value)) else sessionStorage.removeItem(STORAGE_KEY) } catch { // sessionStorage may be unavailable (private mode, quota) — degrade silently } } const quotas = ref<QuotaOverview | null>(readCachedQuotas()) const loading = ref(false) const error = ref<string | null>(null) const needsCredentials = ref(false) // ---------------------------------------------------------------- // USE QUOTAS COMPOSABLE // ---------------------------------------------------------------- export const useQuotas = () => { const getPercentage = (used: number, limit: number): number => { if (limit === 0) return 0 return Math.round((used / limit) * 100) } const getColorClass = (percentage: number): string => { if (percentage >= 90) return 'bg-red-500' if (percentage >= 75) return 'bg-orange-500' if (percentage >= 50) return 'bg-yellow-500' return 'bg-green-500' } const formattedQuotas = computed(() => { if (!quotas.value) return [] const { compute, storage, network } = quotas.value return [ { icon: Cpu, label: 'VMs / Instanzen', used: compute.instances.used, limit: compute.instances.limit, percentage: getPercentage(compute.instances.used, compute.instances.limit), unit: '' }, { icon: Cpu, label: 'vCPUs', used: compute.vcpus.used, limit: compute.vcpus.limit, percentage: getPercentage(compute.vcpus.used, compute.vcpus.limit), unit: '' }, { icon: Cpu, label: 'RAM', used: Math.round(compute.ram.used / 1024), limit: Math.round(compute.ram.limit / 1024), percentage: getPercentage(compute.ram.used, compute.ram.limit), unit: 'GB' }, { icon: HardDrive, label: 'Volumes', used: storage.volumes.used, limit: storage.volumes.limit, percentage: getPercentage(storage.volumes.used, storage.volumes.limit), unit: '' }, { icon: HardDrive, label: 'Storage', used: storage.gigabytes.used, limit: storage.gigabytes.limit, percentage: getPercentage(storage.gigabytes.used, storage.gigabytes.limit), unit: 'GB' }, { icon: Network, label: 'Floating IPs', used: network.floating_ips.used, limit: network.floating_ips.limit, percentage: getPercentage(network.floating_ips.used, network.floating_ips.limit), unit: '' } ] }) const hasCachedQuotas = computed(() => quotas.value !== null) const fetchQuotas = async () => { loading.value = true error.value = null needsCredentials.value = false try { const response = await quotasApi.getOverview() quotas.value = response.data writeCachedQuotas(response.data) } catch (err) { if (axios.isAxiosError(err) && err.response?.status === 412) { needsCredentials.value = true // Drop the cache: credentials are gone, the old numbers don't apply quotas.value = null writeCachedQuotas(null) } else { error.value = 'Failed to fetch quotas' console.error('Quota fetch error:', err) // Keep the existing cached numbers visible — a transient error // shouldn't blank out the tile. } } finally { loading.value = false } } return { quotas, loading, error, needsCredentials, formattedQuotas, hasCachedQuotas, fetchQuotas, getColorClass } } |