Skip to content

Commit

Permalink
feat: implement the machine categories UI
Browse files Browse the repository at this point in the history
It is now possible to filter the machine by the following categories in
the UI:
- Manually joined machines.
- Machines provisioned by the infra providers.
- Machines PXE booted through the metal provider.
- Machines discovered by the metal provider, but not yet accepted by the
  system.

Machine acceptance UI also has two new modal windows for accepting and
rejecting the machines.
Also update controllers to add labels to the not accepted infra
machines.

Fixes: siderolabs#773
Fixes: siderolabs#766

Signed-off-by: Artem Chernyshev <[email protected]>
  • Loading branch information
Unix4ever committed Dec 17, 2024
1 parent b36198a commit f063c69
Show file tree
Hide file tree
Showing 19 changed files with 502 additions and 34 deletions.
6 changes: 6 additions & 0 deletions client/pkg/omni/resources/omni/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const (
LabelExposedServiceAlias = SystemLabelPrefix + "exposed-service-alias"

// LabelInfraProviderID is the infra provider ID for the resources managed by infra providers, e.g., infra.MachineRequest, infra.MachineRequestStatus.
// tsgen:LabelInfraProviderID
LabelInfraProviderID = SystemLabelPrefix + "infra-provider-id"

// LabelIsStaticInfraProvider is set on the infra.ProviderStatus resources to mark them as static providers - they do not work with MachineRequests to
Expand Down Expand Up @@ -82,7 +83,12 @@ const (
LabelNoManualAllocation = SystemLabelPrefix + "no-manual-allocation"

// LabelIsManagedByStaticInfraProvider is set on the machines managed by static infra providers.
// tsgen:LabelIsManagedByStaticInfraProvider
LabelIsManagedByStaticInfraProvider = SystemLabelPrefix + "is-managed-by-static-infra-provider"

// LabelMachinePendingAccept is added to the InfraMachine and is used to filter out the machines which are pending acceptance.
// tsgen:LabelMachinePendingAccept
LabelMachinePendingAccept = SystemLabelPrefix + "accept-pending"
)

const (
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,12 @@ export const LabelClusterMachine = "omni.sidero.dev/cluster-machine";
export const LabelMachine = "omni.sidero.dev/machine";
export const LabelSystemPatch = "omni.sidero.dev/system-patch";
export const LabelExposedServiceAlias = "omni.sidero.dev/exposed-service-alias";
export const LabelInfraProviderID = "omni.sidero.dev/infra-provider-id";
export const LabelMachineRequest = "omni.sidero.dev/machine-request";
export const LabelMachineRequestSet = "omni.sidero.dev/machine-request-set";
export const LabelNoManualAllocation = "omni.sidero.dev/no-manual-allocation";
export const LabelIsManagedByStaticInfraProvider = "omni.sidero.dev/is-managed-by-static-infra-provider";
export const LabelMachinePendingAccept = "omni.sidero.dev/accept-pending";
export const MachineStatusLabelConnected = "omni.sidero.dev/connected";
export const MachineStatusLabelDisconnected = "omni.sidero.dev/disconnected";
export const MachineStatusLabelInvalidState = "omni.sidero.dev/invalid-state";
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/SideBar/TSideBarList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ included in the LICENSE file.
:icon="item.icon"
:label="item.label"
:label-color="item.labelColor"
/>
>
</t-menu-item>
</div>
</nav>
</template>
Expand All @@ -30,7 +31,7 @@ export type SideBarItem = {
route: string | RouteLocationRaw,
icon?: IconType,
label?: string | number,
labelColor?: string
labelColor?: string,
}

type Props = {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/common/Icon/TIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
ServerIcon,
ServerStackIcon,
CalendarIcon,
CpuChipIcon,
} from "@heroicons/vue/24/outline";

import {
Expand Down Expand Up @@ -114,6 +115,7 @@ const icons = {
"rollback": defineAsyncComponent(() => import("../../icons/IconRollback.vue")),
"extensions": defineAsyncComponent(() => import("../../icons/IconExtensions.vue")),
"extensions-toggle": defineAsyncComponent(() => import("../../icons/IconExtensionsToggle.vue")),
"server-network": defineAsyncComponent(() => import("../../icons/IconServerNetwork.vue")),
"document": DocumentIcon,
"power": PowerIcon,
"users": UsersIcon,
Expand All @@ -135,6 +137,7 @@ const icons = {
"server-stack": ServerStackIcon,
"lifebuoy": LifebuoyIcon,
"calendar": CalendarIcon,
"cpu-chip": CpuChipIcon,
};

const getComponent = (icon: string): Component | undefined => {
Expand Down
49 changes: 34 additions & 15 deletions frontend/src/components/common/MenuItem/TMenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,30 @@ included in the LICENSE file.
-->
<template>
<component :is="componentType" v-bind="componentAttributes">
<div class="item">
<t-icon v-if="icon" class="item__icon" :icon="icon" :svg-base64="iconSvgBase64"/>
<p class="item__name truncate">{{ name }}</p>
<div v-if="label" class="rounded-full text-naturals-N13 bg-naturals-N4 text-xs p-1 w-6 h-6 text-center" :class="labelColor ? 'text-' + labelColor : ''">
{{ label }}
<div :class="{'border-naturals-N5': expanded, 'border-transparent': !expanded}" class="border-b border-t divide-naturals-N5 flex flex-col">
<div class="item" :class="{'sub-item': subItem}">
<t-icon v-if="icon" class="item-icon" :icon="icon" :svg-base64="iconSvgBase64"/>
<p class="item-name truncate">{{ name }}</p>
<div v-if="label" class="rounded-full text-naturals-N13 bg-naturals-N4 text-xs p-1 w-6 h-6 text-center" :class="labelColor ? 'text-' + labelColor : ''">
{{ label }}
</div>
<t-icon v-if="hasSubItems"
icon="arrow-up"
class="w-4 h-4 hover:text-naturals-N13 transition-color transition-transform duration-250"
:class="{'rotate-180': expanded}"
@click.stop.prevent="() => expanded = !expanded"
/>
</div>
<div @click.stop.prevent v-if="expanded">
<slot/>
</div>
</div>
</component>
</template>

<script setup lang="ts">
import TIcon, { IconType } from "@/components/common/Icon/TIcon.vue";
import { ref } from "vue";

type Props = {
route: string | object,
Expand All @@ -26,42 +38,49 @@ type Props = {
icon?: IconType,
iconSvgBase64?: string,
label?: string | number,
labelColor?: string
labelColor?: string,
hasSubItems?: boolean
subItem?: boolean
};

const props = defineProps<Props>();
const expanded = ref(false);

const componentType = props.regularLink ? "a" : "router-link";
const componentAttributes = props.regularLink ?
{ href: props.route, target: "_blank" } :
{ to: props.route, activeClass: "item__active" };
{ to: props.route, activeClass: "item-active" };
</script>

<style scoped>
.item {
@apply flex gap-4 border-l-2 border-transparent justify-start items-center py-1.5 my-1 px-6 transition-all duration-200 hover:bg-naturals-N4;
@apply flex gap-4 border-l-2 border-transparent justify-start items-center py-1.5 my-0.5 px-6 transition-all duration-200 hover:bg-naturals-N4;
}
.item:hover .item__icon {
.item:hover .item-icon {
@apply text-naturals-N13;
}
.item:hover .item__name {
.item:hover .item-name {
@apply text-naturals-N13;
}
.item__active .item {
.item-active .item {
@apply border-primary-P3;
}
.item__active .item__icon {
.item-active .item-icon {
@apply text-naturals-N13;
}
.item__active .item__name {
.item-active .item-name {
@apply text-naturals-N13;
}
.item__icon {
.item-icon {
@apply text-naturals-N11 transition-all duration-200;
width: 16px;
height: 16px;
}
.item__name {
.item-name {
@apply text-xs text-naturals-N11 transition-all duration-200 flex-1;
}

.item.sub-item {
@apply pl-12;
}
</style>
28 changes: 28 additions & 0 deletions frontend/src/components/icons/IconServerNetwork.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<svg viewBox="0.403 0.767 23.49 18.6225" width="21.49" height="16.6225" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 5.833 4.267 C 4.776 4.267 3.903 5.133 3.903 6.197 L 3.903 8.827 C 3.903 9.885 4.769 10.757 5.833 10.757 L 20.463 10.757 C 21.519 10.757 22.393 9.883 22.393 8.827 L 22.393 6.197 C 22.393 5.139 21.527 4.267 20.463 4.267 L 5.833 4.267 Z M 2.403 6.197 C 2.403 4.301 3.951 2.767 5.833 2.767 L 20.463 2.767 C 22.359 2.767 23.893 4.315 23.893 6.197 L 23.893 8.827 C 23.893 10.711 22.347 12.257 20.463 12.257 L 5.833 12.257 C 3.937 12.257 2.403 10.709 2.403 8.827 L 2.403 6.197 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 7.143 5.757 C 7.558 5.757 7.893 6.093 7.893 6.507 L 7.893 8.507 C 7.893 8.921 7.558 9.257 7.143 9.257 C 6.729 9.257 6.393 8.921 6.393 8.507 L 6.393 6.507 C 6.393 6.093 6.729 5.757 7.143 5.757 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 11.143 5.757 C 11.558 5.757 11.893 6.093 11.893 6.507 L 11.893 8.507 C 11.893 8.921 11.558 9.257 11.143 9.257 C 10.729 9.257 10.393 8.921 10.393 8.507 L 10.393 6.507 C 10.393 6.093 10.729 5.757 11.143 5.757 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 14.393 7.507 C 14.393 7.093 14.729 6.757 15.143 6.757 L 19.143 6.757 C 19.557 6.757 19.893 7.093 19.893 7.507 C 19.893 7.921 19.557 8.257 19.143 8.257 L 15.143 8.257 C 14.729 8.257 14.393 7.921 14.393 7.507 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 13.148 10.89 C 13.562 10.89 13.898 11.225 13.898 11.64 L 13.898 14.64 C 13.898 15.054 13.562 15.39 13.148 15.39 C 12.734 15.39 12.398 15.054 12.398 14.64 L 12.398 11.64 C 12.398 11.225 12.734 10.89 13.148 10.89 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 13.148 15.39 C 12.458 15.39 11.898 15.949 11.898 16.64 C 11.898 17.33 12.458 17.89 13.148 17.89 C 13.839 17.89 14.398 17.33 14.398 16.64 C 14.398 15.949 13.839 15.39 13.148 15.39 Z M 10.398 16.64 C 10.398 15.121 11.629 13.89 13.148 13.89 C 14.667 13.89 15.898 15.121 15.898 16.64 C 15.898 18.158 14.667 19.39 13.148 19.39 C 11.629 19.39 10.398 18.158 10.398 16.64 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 14.398 16.64 C 14.398 16.225 14.734 15.89 15.148 15.89 L 19.148 15.89 C 19.562 15.89 19.898 16.225 19.898 16.64 C 19.898 17.054 19.562 17.39 19.148 17.39 L 15.148 17.39 C 14.734 17.39 14.398 17.054 14.398 16.64 Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M 6.398 16.64 C 6.398 16.225 6.734 15.89 7.148 15.89 L 11.148 15.89 C 11.562 15.89 11.898 16.225 11.898 16.64 C 11.898 17.054 11.562 17.39 11.148 17.39 L 7.148 17.39 C 6.734 17.39 6.398 17.054 6.398 16.64 Z"
fill="currentColor" />
</svg>
</template>
50 changes: 48 additions & 2 deletions frontend/src/methods/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Runtime } from "@/api/common/omni.pb";
import { Code } from "@/api/google/rpc/code.pb";

import { Resource, ResourceService } from "@/api/grpc";
import { MachineLabelsSpec } from "@/api/omni/specs/omni.pb";
import { InfraMachineConfigSpec, InfraMachineConfigSpecAcceptanceStatus, MachineLabelsSpec } from "@/api/omni/specs/omni.pb";
import { withContext, withRuntime } from "@/api/options";
import { DefaultNamespace, MachineLabelsType, MachineLocked, MachineSetNodeType, MachineStatusType, SiderolinkResourceType, SystemLabelPrefix } from "@/api/resources";
import { DefaultNamespace, InfraMachineConfigType, MachineLabelsType, MachineLocked, MachineSetNodeType, MachineStatusType, SiderolinkResourceType, SystemLabelPrefix } from "@/api/resources";
import { MachineService } from "@/api/talos/machine/machine.pb";
import { destroyResources, getMachineConfigPatchesToDelete } from "@/methods/cluster";
import { parseLabels } from "@/methods/labels";
Expand Down Expand Up @@ -164,3 +164,49 @@ export const updateTalosMaintenance = async (machine: string, talosVersion: stri
nodes: [machine]
}));
}

export const rejectMachine = async (machine: string) => {
await updateInfraMachineConfig(machine, (r: Resource<InfraMachineConfigSpec>) => {
r.spec.acceptance_status = InfraMachineConfigSpecAcceptanceStatus.REJECTED;
});
};

export const acceptMachine = async (machine: string) => {
await updateInfraMachineConfig(machine, (r: Resource<InfraMachineConfigSpec>) => {
r.spec.acceptance_status = InfraMachineConfigSpecAcceptanceStatus.ACCEPTED;
});
};

export const updateInfraMachineConfig = async (machine: string, modify: (r: Resource<InfraMachineConfigSpec>) => void) => {
const metadata = {
id: machine,
namespace: DefaultNamespace,
type: InfraMachineConfigType,
};

try {
const resource: Resource<InfraMachineConfigSpec> = await ResourceService.Get(metadata, withRuntime(Runtime.Omni));

modify(resource);

ResourceService.Update(resource, resource.metadata.version, withRuntime(Runtime.Omni))
} catch (e) {
if (e.code === Code.NOT_FOUND) {
const resource: Resource<InfraMachineConfigSpec> = {
metadata,
spec: {}
};

modify(resource);

await ResourceService.Create<Resource<InfraMachineConfigSpec>>(resource, withRuntime(Runtime.Omni));
}
}
}

export enum MachineFilterOption {
Manual = "manual",
Unaccepted = "unaccepted",
Provisioned = "provisioned",
PXE = "pxe",
};
42 changes: 40 additions & 2 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import OmniSettings from "@/views/omni/Settings/Settings.vue";
import Authenticate from "@/views/omni/Auth/Authenticate.vue";
import OmniMachineClasses from "@/views/omni/MachineClasses/MachineClasses.vue";
import OmniMachineClass from "@/views/omni/MachineClasses/MachineClass.vue";
import OmniMachinesPending from "@/views/omni/Machines/MachinesPending.vue";
import OmniBackupStorageSettings from "@/views/omni/Settings/BackupStorage.vue";
import OIDC from "@/views/omni/Auth/OIDC.vue";

Expand Down Expand Up @@ -72,20 +73,26 @@ import UserCreate from "@/views/omni/Modals/UserCreate.vue";
import RoleEdit from "@/views/omni/Modals/RoleEdit.vue";
import ServiceAccountCreate from "@/views/omni/Modals/ServiceAccountCreate.vue";
import ServiceAccountRenew from "@/views/omni/Modals/ServiceAccountRenew.vue";
import MachineAccept from "@/views/omni/Modals/MachineAccept.vue";
import MachineReject from "@/views/omni/Modals/MachineReject.vue";

import { current } from "@/context";
import { authGuard } from "@auth0/auth0-vue";
import { AuthType, authType } from "@/methods";
import { getAuthCookies, isAuthorized } from "@/methods/key";
import { refreshTitle } from "@/methods/title";
import { loadCurrentUser } from "@/methods/auth";
import { MachineFilterOption } from "@/methods/machine";

export const FrontendAuthFlow = "frontend";

const withPrefix = (prefix: string, routes: RouteRecordRaw[], meta?: Record<string, any>) =>
routes.map((route) => {
if (meta && !route.meta) {
route.meta = meta;
if (meta) {
route.meta = {
...route.meta,
...meta,
}
}

if (!route.beforeEnter) {
Expand Down Expand Up @@ -207,6 +214,35 @@ const routes: RouteRecordRaw[] = [
name: "Machines",
component: OmniMachines,
},
{
path: "/machines/manual",
name: "MachinesManual",
component: OmniMachines,
props: {
filter: MachineFilterOption.Manual,
},
},
{
path: "/machines/provisioned",
name: "MachinesProvisioned",
component: OmniMachines,
props: {
filter: MachineFilterOption.Provisioned,
},
},
{
path: "/machines/pxe",
name: "MachinesPXE",
component: OmniMachines,
props: {
filter: MachineFilterOption.PXE,
},
},
{
path: "/machines/pending",
name: "MachinesPending",
component: OmniMachinesPending,
},
{
path: "/machine-classes",
name: "MachineClasses",
Expand Down Expand Up @@ -458,6 +494,8 @@ const modals = {
serviceAccountCreate: ServiceAccountCreate,
serviceAccountRenew: ServiceAccountRenew,
roleEdit: RoleEdit,
machineAccept: MachineAccept,
machineReject: MachineReject,
updateExtensions: UpdateExtensions,
};

Expand Down
12 changes: 10 additions & 2 deletions frontend/src/views/cluster/ClusterMachines/ClusterMachine.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ included in the LICENSE file.
<div class="w-5 pointer-events-none"/>
<div class="flex-1 grid grid-cols-4 -mr-3 items-center" @click="openNodeInfo">
<div class="col-span-2 flex items-center gap-2">
<t-icon :icon="Object.keys(machine.spec.provision_status ?? {}).length ? 'cloud-connection' : 'server'" class="w-4 h-4 ml-2"/>
<t-icon :icon="icon" class="w-4 h-4 ml-2"/>
<router-link :to="{ name: 'NodeOverview', params: { cluster: clusterName, machine: machine.metadata.id }}" class="list-item-link truncate">
{{ nodeName }}
</router-link>
Expand Down Expand Up @@ -37,7 +37,7 @@ included in the LICENSE file.
</template>

<script setup lang="ts">
import { LabelHostname, LabelCluster, MachineLocked, LabelWorkerRole, UpdateLocked } from "@/api/resources";
import { LabelHostname, LabelCluster, MachineLocked, LabelWorkerRole, UpdateLocked, LabelIsManagedByStaticInfraProvider } from "@/api/resources";
import { useRouter } from "vue-router";
import { computed, toRefs } from "vue";
import { Resource } from "@/api/grpc";
Expand All @@ -61,6 +61,14 @@ const props = defineProps<{

const { machine, machineSet } = toRefs(props);

const icon = computed(() => {
if (machine.value.metadata.labels?.[LabelIsManagedByStaticInfraProvider] !== undefined) {
return "server-network";
}

return Object.keys(machine.value.spec.provision_status ?? {}).length ? "cloud-connection" : "server";
});

const locked = computed(() => {
return machine.value?.metadata?.annotations?.[MachineLocked] !== undefined;
});
Expand Down
1 change: 1 addition & 0 deletions frontend/src/views/omni/ItemLabels/ItemLabels.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const { resource } = toRefs(props);
defineEmits(['filterLabel']);

const labelOrder = {
"is-managed-by-static-infra-provider": -1,
"machine-request-set": -1,
"no-manual-allocation": -1,
"machine-request": -1,
Expand Down
Loading

0 comments on commit f063c69

Please sign in to comment.