feat(frontend): use new resource label colors

Use new resource label colors

Signed-off-by: Edward Sammut Alessi <edward.sammutalessi@siderolabs.com>
This commit is contained in:
Edward Sammut Alessi 2026-03-03 16:14:35 +01:00
parent 0cb34323d1
commit 8a814d171c
No known key found for this signature in database
GPG Key ID: 65558E016966977A
14 changed files with 148 additions and 123 deletions

View File

@ -70,7 +70,7 @@ const removeLabel = async (key: string) => {
key,
id: key,
value: label.value,
labelClass: label.labelClass ?? 'label-light6',
labelClass: label.labelClass,
removable: label.canRemove,
}"
:remove-label="removeLabel"

View File

@ -195,7 +195,11 @@ const iconData = computed((): { iconClass?: string; iconTypeValue?: IconType } =
<template>
<div class="flex items-center gap-1">
<TIcon class="size-4" :class="iconData.iconClass" :icon="iconData.iconTypeValue" />
<TIcon
class="size-4"
:class="iconData.iconClass"
:icon="iconData.iconTypeValue ?? 'action-horizontal'"
/>
<span v-if="title" class="text-xs" :class="iconData.iconClass">
{{ title }}
</span>

View File

@ -88,86 +88,35 @@ included in the LICENSE file.
}
.resource-label {
@apply rounded-sm border px-2 py-0.5;
@apply rounded-sm px-2 py-1 text-xs text-naturals-n11;
background-color: color-mix(in hsl, currentColor 18%, transparent);
border-color: color-mix(in hsl, currentColor 30%, transparent);
background-color: color-mix(in srgb, currentColor 10%, transparent);
&:is(a:not(:disabled), button:not(:disabled), [role='button']:not(:disabled)) {
@apply transition-[filter];
&:hover {
filter: brightness(120%);
@apply brightness-120;
}
&:active {
filter: brightness(80%);
@apply brightness-80;
}
}
}
.label-green {
color: hsl(142 100 69);
color: #62f05b;
}
.label-red {
color: hsl(359 97 56);
color: #f94e76;
}
.label-orange {
color: hsl(15 90 64);
color: #ff9f39;
}
.label-violet {
color: hsl(313 97 78);
}
.label-yellow {
color: hsl(48 96 70);
}
.label-cyan {
color: hsl(185 100 42);
}
.label-blue1 {
color: hsl(211 76 68);
}
.label-blue2 {
color: hsl(215 100 60);
}
.label-blue3 {
color: hsl(256 81 70);
}
.label-light1 {
color: hsl(0 65 74);
}
.label-light2 {
color: hsl(13 81 87);
}
.label-light3 {
color: hsl(48 96 87);
}
.label-light4 {
color: hsl(128 32 81);
}
.label-light5 {
color: hsl(184 29 80);
}
.label-light6 {
color: hsl(208 70 86);
}
.label-light7 {
color: hsl(257 81 87);
}
.label-grayed-out {
color: hsl(0 0 100);
.label-blue {
color: #69b4ff;
}
}

View File

@ -44,36 +44,43 @@ export type Label = {
key: string
id: string
value: string
labelClass: string
labelClass?: string
removable?: boolean
description?: string
icon?: IconType
}
const labelClasses: Record<string, string> = {
[LabelCluster]: 'label-light1',
[MachineStatusLabelAvailable]: 'label-yellow',
[MachineStatusLabelInvalidState]: 'label-red',
// Cluster related
[LabelCluster]: 'label-blue',
[MachineStatusLabelAvailable]: 'label-blue',
[LabelControlPlaneRole]: 'label-blue',
[LabelWorkerRole]: 'label-blue',
// Talos
[MachineStatusLabelTalosVersion]: 'label-red',
// Connection state
[MachineStatusLabelConnected]: 'label-green',
[MachineStatusLabelDisconnected]: 'label-red',
[MachineStatusLabelPlatform]: 'label-blue1',
[MachineStatusLabelCores]: 'label-cyan',
[MachineStatusLabelMem]: 'label-blue2',
[MachineStatusLabelStorage]: 'label-violet',
[MachineStatusLabelNet]: 'label-blue3',
// Hardware
[MachineStatusLabelPlatform]: 'label-orange',
[MachineStatusLabelCores]: 'label-orange',
[MachineStatusLabelMem]: 'label-orange',
[MachineStatusLabelStorage]: 'label-orange',
[MachineStatusLabelNet]: 'label-orange',
[MachineStatusLabelCPU]: 'label-orange',
[MachineStatusLabelArch]: 'label-light2',
[MachineStatusLabelRegion]: 'label-light3',
[MachineStatusLabelZone]: 'label-light4',
[MachineStatusLabelInstance]: 'label-light5',
[MachineStatusLabelTalosVersion]: 'label-light7',
[LabelControlPlaneRole]: 'label-yellow',
[LabelWorkerRole]: 'label-yellow',
[MachineStatusLabelArch]: 'label-orange',
[MachineStatusLabelRegion]: 'label-orange',
[MachineStatusLabelZone]: 'label-orange',
[MachineStatusLabelInstance]: 'label-orange',
// Other
[MachineStatusLabelInvalidState]: 'label-red',
}
export const getLabelClass = (labelKey: string) => {
return labelClasses[labelKey] ?? 'label-light6'
}
export const getLabelClass = (labelKey: string) => labelClasses[labelKey]
export const addLabel = (dest: Label[], label: Label) => {
if (dest.find((l) => l.value === label.value && l.key === label.key)) {
@ -136,7 +143,7 @@ export const getLabelFromID = (key: string, value: string): Label => {
...label,
id: parts.at(-1) ?? '',
labelClass: 'label-green',
description: `Defined by the infra provider "${parts[1] ?? ''}""`,
description: `Defined by the infra provider "${parts[1] ?? ''}"`,
icon: 'server-network',
}
}

View File

@ -76,23 +76,7 @@ export enum PatchID {
InstallDisk = 'install-disk',
}
const labelClasses = [
'label-red',
'label-orange',
'label-violet',
'label-yellow',
'label-cyan',
'label-blue1',
'label-blue2',
'label-blue3',
'label-light1',
'label-light2',
'label-light3',
'label-light4',
'label-light5',
'label-light6',
'label-light7',
]
const labelClasses = ['label-red', 'label-orange', 'label-blue']
// Keeps the configuration for the machine set.
export interface MachineSet {

View File

@ -103,14 +103,14 @@ const clusterDestroyDialogOpen = ref(false)
<div class="flex items-center gap-2 text-naturals-n10">
<Tooltip :description="`Talos version v${item.spec.talos_version}`">
<span class="resource-label label-orange flex items-center gap-1">
<span class="resource-label label-red flex items-center gap-1">
<TIcon class="size-3.5 shrink-0" icon="talos" />
{{ item.spec.talos_version }}
</span>
</Tooltip>
<Tooltip :description="`Kubernetes version v${item.spec.kubernetes_version}`">
<span class="resource-label label-cyan flex items-center gap-1">
<span class="resource-label label-blue flex items-center gap-1">
<TIcon class="size-3.5 shrink-0" icon="kubernetes" />
{{ item.spec.kubernetes_version }}
</span>

View File

@ -5,7 +5,7 @@ Use of this software is governed by the Business Source License
included in the LICENSE file.
-->
<script setup lang="ts">
defineProps<{ machineSetId: string; labelClass: string; disabled?: boolean }>()
defineProps<{ machineSetId: string; labelClass?: string; disabled?: boolean }>()
</script>
<template>
@ -13,10 +13,7 @@ defineProps<{ machineSetId: string; labelClass: string; disabled?: boolean }>()
class="flex items-center text-center select-none"
:class="{ 'cursor-pointer': !disabled, 'cursor-not-allowed': disabled }"
>
<div
class="resource-label rounded px-1 py-0.5 font-mono font-bold"
:class="[labelClass, { 'label-grayed-out': disabled, 'opacity-50': disabled }]"
>
<div class="resource-label" :class="disabled ? 'opacity-50' : labelClass">
{{ machineSetId }}
</div>
</div>

View File

@ -17,7 +17,7 @@ import MachineSetLabel from './MachineSetLabel.vue'
export type PickerOption = {
id: string
labelClass: string
labelClass?: string
tooltip?: string
name?: string
disabled?: boolean
@ -123,7 +123,7 @@ const onSelect = (index: number) => {
as="template"
:disabled="option.disabled"
>
<div @click="() => onSelect(checked)">
<div @click="() => onSelect(index)">
<Tooltip :description="option.tooltip" placement="left">
<div class="relative">
<MachineSetLabel

View File

@ -58,9 +58,8 @@ defineProps<{
</div>
<div class="flex min-w-0 justify-center">
<span v-if="item.spec.cluster" class="resource-label label-light1 truncate">
cluster:
<span class="font-semibold">{{ item.spec.cluster }}</span>
<span v-if="item.spec.cluster" class="resource-label label-blue truncate">
cluster:{{ item.spec.cluster }}
</span>
</div>

View File

@ -33,17 +33,13 @@ const description = computed(() => {
<template>
<Tooltip :description="description" :delay-duration="500" placement="bottom-start">
<button
class="inline-flex items-center gap-1 transition-all"
class="inline-flex items-center gap-1"
:class="['resource-label', label.labelClass, small ? 'max-w-50' : 'max-w-75']"
@click.stop="$emit('filterLabel', label)"
>
<TIcon v-if="label.icon" :icon="label.icon" class="-ml-1 size-3.5 shrink-0" />
<!-- prettier-ignore -->
<span v-if="label.value" class="truncate">
{{ label.id }}:<span class="font-semibold">{{ label.value }}</span>
</span>
<span v-else class="truncate font-semibold">
{{ label.id }}
<span class="truncate">
{{ label.value ? `${label.id}:${label.value}` : label.id }}
</span>
<TIcon
v-if="label.removable && removeLabel"

View File

@ -21,7 +21,7 @@ type Label = {
id: string
value: string
key: string
labelClass: string
labelClass?: string
}
const { completionsResource, filterValue, filterLabels } = defineProps<{

View File

@ -153,7 +153,7 @@ const secureBoot = computed(() => {
RouterLink,
{
to: { name: 'ClusterOverview', params: { cluster: clusterName } },
class: 'list-item-link resource-label text-naturals-n12',
class: 'resource-label label-blue',
},
clusterName,
)),

View File

@ -67,7 +67,7 @@ const editUser = () => {
</div>
<div class="text-naturals-n10">{{ props.lastActive }}</div>
<div class="col-span-3 flex flex-wrap gap-1">
<div v-for="label in labels" :key="label" class="label-light6 resource-label text-xs">
<div v-for="label in labels" :key="label" class="resource-label">
{{ label }}
</div>
</div>

View File

@ -0,0 +1,89 @@
// Copyright (c) 2026 Sidero Labs, Inc.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
import { faker } from '@faker-js/faker'
import { createWatchStreamHandler } from '@msw/helpers'
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import type { Resource } from '@/api/grpc'
import type { IdentitySpec, UserSpec } from '@/api/omni/specs/auth.pb'
import {
DefaultNamespace,
IdentityType,
LabelIdentityUserID,
SAMLLabelPrefix,
UserType,
} from '@/api/resources'
import Users from './Users.vue'
const meta: Meta<typeof Users> = {
component: Users,
}
export default meta
type Story = StoryObj<typeof meta>
const userIds = faker.helpers.multiple(() => faker.string.uuid(), { count: 100 })
export const Default: Story = {
parameters: {
msw: {
handlers: [
createWatchStreamHandler<IdentitySpec>({
expectedOptions: {
type: IdentityType,
namespace: DefaultNamespace,
},
totalResults: userIds.length,
initialResources: ({ limit = 5, offset = 0 }) => {
faker.seed(offset)
return faker.helpers.multiple<Resource<IdentitySpec>>(
(_, i) => ({
spec: {
user_id: userIds[i + offset],
},
metadata: {
type: IdentityType,
namespace: DefaultNamespace,
id: faker.internet.email(),
labels: {
[LabelIdentityUserID]: userIds[i + offset],
...faker.helpers
.multiple(() => `${SAMLLabelPrefix}${faker.company.buzzNoun()}`, {
count: { min: 0, max: 3 },
})
.reduce((prev, curr) => ({ ...prev, [curr]: '' }), {}),
},
},
}),
{ count: limit },
)
},
}).handler,
createWatchStreamHandler<UserSpec>({
expectedOptions: {
type: UserType,
namespace: DefaultNamespace,
},
initialResources: faker.helpers.multiple<Resource<UserSpec>>(
(_, i) => ({
spec: {
role: faker.person.jobType(),
},
metadata: {
type: UserType,
namespace: DefaultNamespace,
id: userIds[i],
},
}),
{ count: userIds.length },
),
}).handler,
],
},
},
}