Rework the dashboard, environment topology view, header navigation, and status rendering, and standardise selects on a shadcn-vue component. Replace the thin database seeder with a SimulatedEnvironmentSeeder that builds a fully wired, mostly-running organisation (ACTIVE server fleet, managed + GHCR registries, Gitea source provider, ClipBin app with production/staging environments, services, slices, endpoints, managed variables, build artifacts, and a completed/in-progress/failed operations history) so the new UI renders against realistic data.
92 lines
2.3 KiB
Vue
92 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from "vue";
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
status?: string | null;
|
|
label?: string;
|
|
size?: "sm" | "md";
|
|
}>(),
|
|
{
|
|
status: "unknown",
|
|
size: "sm",
|
|
},
|
|
);
|
|
|
|
type Tone = "positive" | "negative" | "pending" | "neutral";
|
|
|
|
const tones: Record<string, Tone> = {
|
|
active: "positive",
|
|
running: "positive",
|
|
succeeded: "positive",
|
|
success: "positive",
|
|
completed: "positive",
|
|
ready: "positive",
|
|
verified: "positive",
|
|
enabled: "positive",
|
|
healthy: "positive",
|
|
stopped: "negative",
|
|
failed: "negative",
|
|
error: "negative",
|
|
errored: "negative",
|
|
unhealthy: "negative",
|
|
cancelled: "negative",
|
|
installing: "pending",
|
|
pending: "pending",
|
|
"in-progress": "pending",
|
|
building: "pending",
|
|
planning: "pending",
|
|
deploying: "pending",
|
|
provisioning: "pending",
|
|
queued: "pending",
|
|
};
|
|
|
|
const normalised = computed(() => (props.status ?? "unknown").toString().toLowerCase().trim());
|
|
|
|
const tone = computed<Tone>(() => tones[normalised.value] ?? "neutral");
|
|
|
|
const label = computed(
|
|
() =>
|
|
props.label ??
|
|
(props.status ?? "unknown").toString().replaceAll("-", " ").replaceAll("_", " "),
|
|
);
|
|
|
|
const dotClasses = computed(() => {
|
|
switch (tone.value) {
|
|
case "positive":
|
|
return "bg-green-500";
|
|
case "negative":
|
|
return "bg-red-500";
|
|
case "pending":
|
|
return "bg-amber-500 animate-pulse";
|
|
default:
|
|
return "bg-zinc-400 dark:bg-zinc-500";
|
|
}
|
|
});
|
|
|
|
const textClasses = computed(() => {
|
|
switch (tone.value) {
|
|
case "positive":
|
|
return "text-green-700 dark:text-green-400";
|
|
case "negative":
|
|
return "text-red-700 dark:text-red-400";
|
|
case "pending":
|
|
return "text-amber-700 dark:text-amber-400";
|
|
default:
|
|
return "text-muted-foreground";
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<span class="inline-flex items-center gap-1.5">
|
|
<span
|
|
class="inline-block rounded-full"
|
|
:class="[dotClasses, size === 'md' ? 'size-2' : 'size-1.5']"
|
|
/>
|
|
<span class="capitalize" :class="[textClasses, size === 'md' ? 'text-sm' : 'text-xs']">{{
|
|
label
|
|
}}</span>
|
|
</span>
|
|
</template>
|