From 0b7344500bb440278be45724b9da8a44547bf7e1 Mon Sep 17 00:00:00 2001 From: phancj1 Date: Sun, 24 Aug 2025 19:44:08 -0700 Subject: [PATCH 1/3] Add Computing Unit Tab --- core/gui/src/app/app-routing.constant.ts | 1 + core/gui/src/app/app-routing.module.ts | 5 + core/gui/src/app/app.module.ts | 4 + .../app/common/util/computing-unit.util.ts | 185 +++++++++ .../component/dashboard.component.html | 11 + .../component/dashboard.component.ts | 2 + .../user/list-item/list-item.component.html | 2 +- ...er-computing-unit-list-item.component.html | 202 +++++++++ ...er-computing-unit-list-item.component.scss | 230 +++++++++++ ...user-computing-unit-list-item.component.ts | 325 +++++++++++++++ .../user-computing-unit.component.html | 233 +++++++++++ .../user-computing-unit.component.scss | 99 +++++ .../user-computing-unit.component.ts | 384 ++++++++++++++++++ .../src/app/dashboard/type/dashboard-entry.ts | 39 +- .../src/app/dashboard/type/search-result.ts | 2 +- .../src/app/dashboard/type/type-predicates.ts | 5 + core/gui/src/app/hub/service/hub.service.ts | 1 + .../computing-unit-selection.component.ts | 212 ++-------- 18 files changed, 1762 insertions(+), 180 deletions(-) create mode 100644 core/gui/src/app/common/util/computing-unit.util.ts create mode 100644 core/gui/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.html create mode 100644 core/gui/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.scss create mode 100644 core/gui/src/app/dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component.ts create mode 100644 core/gui/src/app/dashboard/component/user/user-computing-unit/user-computing-unit.component.html create mode 100644 core/gui/src/app/dashboard/component/user/user-computing-unit/user-computing-unit.component.scss create mode 100644 core/gui/src/app/dashboard/component/user/user-computing-unit/user-computing-unit.component.ts diff --git a/core/gui/src/app/app-routing.constant.ts b/core/gui/src/app/app-routing.constant.ts index a22f6439e26..582f6d865b1 100644 --- a/core/gui/src/app/app-routing.constant.ts +++ b/core/gui/src/app/app-routing.constant.ts @@ -35,6 +35,7 @@ export const DASHBOARD_USER_WORKSPACE = `${DASHBOARD_USER}/workflow`; export const DASHBOARD_USER_WORKFLOW = `${DASHBOARD_USER}/workflow`; export const DASHBOARD_USER_DATASET = `${DASHBOARD_USER}/dataset`; export const DASHBOARD_USER_DATASET_CREATE = `${DASHBOARD_USER_DATASET}/create`; +export const DASHBOARD_USER_COMPUTING_UNIT = `${DASHBOARD_USER}/unit`; export const DASHBOARD_USER_QUOTA = `${DASHBOARD_USER}/quota`; export const DASHBOARD_USER_DISCUSSION = `${DASHBOARD_USER}/discussion`; diff --git a/core/gui/src/app/app-routing.module.ts b/core/gui/src/app/app-routing.module.ts index fffe3f8e428..7251d888a7b 100644 --- a/core/gui/src/app/app-routing.module.ts +++ b/core/gui/src/app/app-routing.module.ts @@ -24,6 +24,7 @@ import { UserWorkflowComponent } from "./dashboard/component/user/user-workflow/ import { UserQuotaComponent } from "./dashboard/component/user/user-quota/user-quota.component"; import { UserProjectSectionComponent } from "./dashboard/component/user/user-project/user-project-section/user-project-section.component"; import { UserProjectComponent } from "./dashboard/component/user/user-project/user-project.component"; +import { UserComputingUnitComponent } from "./dashboard/component/user/user-computing-unit/user-computing-unit.component"; import { WorkspaceComponent } from "./workspace/component/workspace.component"; import { AboutComponent } from "./hub/component/about/about.component"; import { AuthGuardService } from "./common/service/user/auth-guard.service"; @@ -135,6 +136,10 @@ routes.push({ path: "dataset/create", component: DatasetDetailComponent, }, + { + path: "unit", + component: UserComputingUnitComponent, + }, { path: "quota", component: UserQuotaComponent, diff --git a/core/gui/src/app/app.module.ts b/core/gui/src/app/app.module.ts index 13759e6e55a..86334970ad0 100644 --- a/core/gui/src/app/app.module.ts +++ b/core/gui/src/app/app.module.ts @@ -172,6 +172,8 @@ import { AdminSettingsComponent } from "./dashboard/component/admin/settings/adm import { catchError, of } from "rxjs"; import { FormlyRepeatDndComponent } from "./common/formly/repeat-dnd/repeat-dnd.component"; import { NzInputNumberModule } from "ng-zorro-antd/input-number"; +import { UserComputingUnitComponent } from "./dashboard/component/user/user-computing-unit/user-computing-unit.component"; +import { UserComputingUnitListItemComponent } from "./dashboard/component/user/user-computing-unit/user-computing-unit-list-item/user-computing-unit-list-item.component"; registerLocaleData(en); @@ -264,6 +266,8 @@ registerLocaleData(en); HubSearchResultComponent, ComputingUnitSelectionComponent, AdminSettingsComponent, + UserComputingUnitComponent, + UserComputingUnitListItemComponent, ], imports: [ BrowserModule, diff --git a/core/gui/src/app/common/util/computing-unit.util.ts b/core/gui/src/app/common/util/computing-unit.util.ts new file mode 100644 index 00000000000..73eba77ca65 --- /dev/null +++ b/core/gui/src/app/common/util/computing-unit.util.ts @@ -0,0 +1,185 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { DashboardWorkflowComputingUnit } from "../../workspace/types/workflow-computing-unit"; + +export function buildComputingUnitMetadataTable(unit: DashboardWorkflowComputingUnit): string { + return ` + + + + + + + + + + + + + +
Name${unit.computingUnit.name}
Status${unit.status}
Type${unit.computingUnit.type}
CPU Limit${unit.computingUnit.resource.cpuLimit}
Memory Limit${unit.computingUnit.resource.memoryLimit}
GPU Limit${unit.computingUnit.resource.gpuLimit || "None"}
JVM Memory${unit.computingUnit.resource.jvmMemorySize}
Shared Memory${unit.computingUnit.resource.shmSize}
Created${new Date(unit.computingUnit.creationTime).toLocaleString()}
Access${unit.isOwner ? "Owner" : unit.accessPrivilege}
+ `; +} + +export function parseResourceUnit(resource: string): string { + // check if has a capacity (is a number followed by a unit) + if (!resource || resource === "NaN") return "NaN"; + const re = /^(\d+(\.\d+)?)([a-zA-Z]*)$/; + const match = resource.match(re); + if (match) { + return match[3] || ""; + } + return ""; +} + +export function parseResourceNumber(resource: string): number { + // check if has a capacity (is a number followed by a unit) + if (!resource || resource === "NaN") return 0; + const re = /^(\d+(\.\d+)?)([a-zA-Z]*)$/; + const match = resource.match(re); + if (match) { + return parseFloat(match[1]); + } + return 0; +} + +export function cpuResourceConversion(from: string, toUnit: string): string { + // cpu conversions + type CpuUnit = "n" | "u" | "m" | ""; + const cpuScales: { [key in CpuUnit]: number } = { + n: 1, + u: 1_000, + m: 1_000_000, + "": 1_000_000_000, + }; + const fromUnit = parseResourceUnit(from) as CpuUnit; + const fromNumber = parseResourceNumber(from); + + // Handle empty unit in input (means cores) + const effectiveFromUnit = (fromUnit || "") as CpuUnit; + const effectiveToUnit = (toUnit || "") as CpuUnit; + + // Convert to base units (nanocores) then to target unit + const fromScaled = fromNumber * (cpuScales[effectiveFromUnit] || cpuScales["m"]); + const toScaled = fromScaled / (cpuScales[effectiveToUnit] || cpuScales[""]); + + // For display purposes, use appropriate precision + if (effectiveToUnit === "") { + return toScaled.toFixed(4); // 4 decimal places for cores + } else if (effectiveToUnit === "m") { + return toScaled.toFixed(2); // 2 decimal places for millicores + } else { + return Math.round(toScaled).toString(); // Whole numbers for smaller units + } +} + +export function memoryResourceConversion(from: string, toUnit: string): string { + // memory conversion + type MemoryUnit = "Ki" | "Mi" | "Gi" | ""; + const memoryScales: { [key in MemoryUnit]: number } = { + "": 1, + Ki: 1024, + Mi: 1024 * 1024, + Gi: 1024 * 1024 * 1024, + }; + const fromUnit = parseResourceUnit(from) as MemoryUnit; + const fromNumber = parseResourceNumber(from); + + // Handle empty unit in input (means bytes) + const effectiveFromUnit = (fromUnit || "") as MemoryUnit; + const effectiveToUnit = (toUnit || "") as MemoryUnit; + + // Convert to base units (bytes) then to target unit + const fromScaled = fromNumber * (memoryScales[effectiveFromUnit] || 1); + const toScaled = fromScaled / (memoryScales[effectiveToUnit] || 1); + + // For memory, we want to show in the same format as the limit (typically X.XXX Gi) + return toScaled.toFixed(4); +} + +export function cpuPercentage(usage: string, limit: string): number { + if (usage === "N/A" || limit === "N/A") return 0; + + // Convert to the same unit for comparison + const displayUnit = ""; // Convert to cores for percentage calculation + + // Use our existing conversion method to get values in the same unit + const usageValue = parseFloat(cpuResourceConversion(usage, displayUnit)); + const limitValue = parseFloat(cpuResourceConversion(limit, displayUnit)); + + if (limitValue <= 0) return 0; + + // Calculate percentage and ensure it doesn't exceed 100% + const percentage = (usageValue / limitValue) * 100; + + return Math.min(percentage, 100); +} + +export function memoryPercentage(usage: string, limit: string): number { + if (usage === "N/A" || limit === "N/A") return 0; + + // Convert to the same unit for comparison + const displayUnit = "Gi"; // Convert to GiB for percentage calculation + + // Use our existing conversion method to get values in the same unit + const usageValue = parseFloat(memoryResourceConversion(usage, displayUnit)); + const limitValue = parseFloat(memoryResourceConversion(limit, displayUnit)); + + if (limitValue <= 0) return 0; + + // Calculate percentage and ensure it doesn't exceed 100% + const percentage = (usageValue / limitValue) * 100; + + return Math.min(percentage, 100); +} + +export function findNearestValidStep(value: number, jvmMemorySteps: number[]): number { + if (jvmMemorySteps.length === 0) return 1; + if (jvmMemorySteps.includes(value)) return value; + + // Find the closest step value + return jvmMemorySteps.reduce((prev, curr) => { + return Math.abs(curr - value) < Math.abs(prev - value) ? curr : prev; + }); +} + +export const unitTypeMessageTemplate = { + local: { + createTitle: "Connect to a Local Computing Unit", + terminateTitle: "Disconnect from Local Computing Unit", + terminateWarning: "", // no red warning + createSuccess: "Successfully connected to the local computing unit", + createFailure: "Failed to connect to the local computing unit", + terminateSuccess: "Disconnected from the local computing unit", + terminateFailure: "Failed to disconnect from the local computing unit", + terminateTooltip: "Disconnect from this computing unit", + }, + kubernetes: { + createTitle: "Create Computing Unit", + terminateTitle: "Terminate Computing Unit", + terminateWarning: + "

Warning: All execution results in this computing unit will be lost.

", + createSuccess: "Successfully created the Kubernetes computing unit", + createFailure: "Failed to create the Kubernetes computing unit", + terminateSuccess: "Terminated Kubernetes computing unit", + terminateFailure: "Failed to terminate Kubernetes computing unit", + terminateTooltip: "Terminate this computing unit", + }, +} as const; diff --git a/core/gui/src/app/dashboard/component/dashboard.component.html b/core/gui/src/app/dashboard/component/dashboard.component.html index cf490d33043..eb93ed39903 100644 --- a/core/gui/src/app/dashboard/component/dashboard.component.html +++ b/core/gui/src/app/dashboard/component/dashboard.component.html @@ -97,6 +97,17 @@ nzType="database"> Datasets +
  • + + Computing Units +
  • - {{ entry.description ? entry.description.slice(0, 200) : (hovering) ? 'Write a description...' : '' }} + {{ (entry?.description?.slice(0, 200)) || (hovering ? 'Write a description...' : '') }}