All files / src/composables useQuotas.ts

0% Statements 0/123
0% Branches 0/1
0% Functions 0/1
0% Lines 0/123

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
  }
}