214 lines
4.4 KiB
Vue
214 lines
4.4 KiB
Vue
<template>
|
|
<div class="flex items-center gap-2">
|
|
<div class="relative flex items-center justify-center">
|
|
<svg
|
|
class="shield-ring"
|
|
:class="ringClass"
|
|
width="48"
|
|
height="48"
|
|
viewBox="0 0 48 48"
|
|
>
|
|
<circle
|
|
class="ring-background"
|
|
cx="24"
|
|
cy="24"
|
|
r="20"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="3"
|
|
opacity="0.1"
|
|
/>
|
|
<circle
|
|
class="ring-progress"
|
|
cx="24"
|
|
cy="24"
|
|
r="20"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="3"
|
|
stroke-linecap="round"
|
|
:stroke-dasharray="circumference"
|
|
:stroke-dashoffset="progressOffset"
|
|
transform="rotate(-90 24 24)"
|
|
/>
|
|
</svg>
|
|
|
|
<Transition name="shield-pulse" mode="out-in">
|
|
<UIcon
|
|
:key="status"
|
|
:name="shieldIcon"
|
|
class="absolute shield-icon"
|
|
:class="iconClass"
|
|
/>
|
|
</Transition>
|
|
</div>
|
|
|
|
<UBadge
|
|
:label="statusLabel"
|
|
:color="badgeColor"
|
|
size="md"
|
|
variant="subtle"
|
|
class="status-badge"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ComplianceStatus } from '~~/.api-client'
|
|
|
|
const props = defineProps<{
|
|
status: ComplianceStatus
|
|
}>()
|
|
|
|
const circumference = 2 * Math.PI * 20
|
|
|
|
const shieldIcon = computed(() => {
|
|
switch (props.status) {
|
|
case ComplianceStatus.Critical:
|
|
return 'i-lucide-shield-alert'
|
|
case ComplianceStatus.Warning:
|
|
return 'i-lucide-shield-x'
|
|
case ComplianceStatus.NonCritical:
|
|
return 'i-lucide-shield-check'
|
|
default:
|
|
return 'i-lucide-shield-check'
|
|
}
|
|
})
|
|
|
|
const statusLabel = computed(() => {
|
|
switch (props.status) {
|
|
case ComplianceStatus.Critical:
|
|
return 'Kritisch'
|
|
case ComplianceStatus.Warning:
|
|
return 'Warnung'
|
|
case ComplianceStatus.NonCritical:
|
|
return 'Unkritisch'
|
|
default:
|
|
return 'Unkritisch'
|
|
}
|
|
})
|
|
|
|
const badgeColor = computed(() => {
|
|
switch (props.status) {
|
|
case ComplianceStatus.Critical:
|
|
return 'red'
|
|
case ComplianceStatus.Warning:
|
|
return 'yellow'
|
|
case ComplianceStatus.NonCritical:
|
|
return 'green'
|
|
default:
|
|
return 'green'
|
|
}
|
|
})
|
|
|
|
const ringClass = computed(() => {
|
|
switch (props.status) {
|
|
case ComplianceStatus.Critical:
|
|
return 'text-red-500'
|
|
case ComplianceStatus.Warning:
|
|
return 'text-yellow-500'
|
|
case ComplianceStatus.NonCritical:
|
|
return 'text-green-500'
|
|
default:
|
|
return 'text-green-500'
|
|
}
|
|
})
|
|
|
|
const iconClass = computed(() => {
|
|
switch (props.status) {
|
|
case ComplianceStatus.Critical:
|
|
return 'text-red-500 w-6 h-6'
|
|
case ComplianceStatus.Warning:
|
|
return 'text-yellow-500 w-6 h-6'
|
|
case ComplianceStatus.NonCritical:
|
|
return 'text-green-500 w-6 h-6'
|
|
default:
|
|
return 'text-green-500 w-6 h-6'
|
|
}
|
|
})
|
|
|
|
const progressValue = computed(() => {
|
|
switch (props.status) {
|
|
case ComplianceStatus.Critical:
|
|
return 100
|
|
case ComplianceStatus.Warning:
|
|
return 60
|
|
case ComplianceStatus.NonCritical:
|
|
return 30
|
|
default:
|
|
return 30
|
|
}
|
|
})
|
|
|
|
const progressOffset = computed(() => {
|
|
return circumference - (progressValue.value / 100) * circumference
|
|
})
|
|
|
|
watch(() => props.status, () => {
|
|
const element = document.querySelector('.shield-ring')
|
|
if (element) {
|
|
element.classList.add('status-change-pulse')
|
|
setTimeout(() => {
|
|
element.classList.remove('status-change-pulse')
|
|
}, 600)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.shield-ring {
|
|
transition: color 300ms ease-in-out;
|
|
}
|
|
|
|
.ring-progress {
|
|
transition: stroke-dashoffset 500ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.shield-icon {
|
|
transition: color 300ms ease-in-out;
|
|
}
|
|
|
|
.status-badge {
|
|
transition: all 300ms ease-in-out;
|
|
}
|
|
|
|
@keyframes pulse-ring {
|
|
0% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
transform: scale(1.1);
|
|
opacity: 0.8;
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.status-change-pulse {
|
|
animation: pulse-ring 600ms ease-in-out;
|
|
}
|
|
|
|
.shield-pulse-enter-active,
|
|
.shield-pulse-leave-active {
|
|
transition: all 300ms ease-in-out;
|
|
}
|
|
|
|
.shield-pulse-enter-from {
|
|
opacity: 0;
|
|
transform: scale(0.8);
|
|
}
|
|
|
|
.shield-pulse-leave-to {
|
|
opacity: 0;
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
.shield-icon {
|
|
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
|
|
}
|
|
</style>
|
|
|