mirror of
https://github.com/siderolabs/omni.git
synced 2026-05-05 14:46:12 +02:00
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:
parent
0cb34323d1
commit
8a814d171c
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -21,7 +21,7 @@ type Label = {
|
||||
id: string
|
||||
value: string
|
||||
key: string
|
||||
labelClass: string
|
||||
labelClass?: string
|
||||
}
|
||||
|
||||
const { completionsResource, filterValue, filterLabels } = defineProps<{
|
||||
|
||||
@ -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,
|
||||
)),
|
||||
|
||||
@ -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>
|
||||
|
||||
89
frontend/src/views/omni/Users/Users.stories.ts
Normal file
89
frontend/src/views/omni/Users/Users.stories.ts
Normal 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,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user