refactor: gpu service API alignment

This commit is contained in:
jialin
2026-05-25 22:42:02 +08:00
committed by jialin
parent 81c7e2ee61
commit 88ab927755
90 changed files with 3247 additions and 2023 deletions
@@ -0,0 +1,14 @@
import { PageActionType } from '@/config/types';
import { createContext } from 'react';
export interface FormContextValue {
action: PageActionType;
currentData: any;
}
export const FormContext = createContext<FormContextValue>({
action: 'create',
currentData: null
});
export const FormContextProvider = FormContext.Provider;
+79 -78
View File
@@ -3,11 +3,6 @@ import { StatusType } from '@/config/types';
import { IconFont, icons } from '@gpustack/core-ui';
import _ from 'lodash';
import React from 'react';
import {
InstanceTypeItem,
InstanceTypeResource,
InstanceTypeStatus
} from './types';
export const InstanceStatusValueMap = {
Scheduling: 'Scheduling',
@@ -45,13 +40,22 @@ export const status: Record<string, StatusType> = {
[InstanceStatusValueMap.Ready]: StatusMaps.success
};
// Lifecycle actions (logs/events/start/stop) require K8s proxy endpoints that
// the new /v2/gpu-instances API does not yet expose; keep them visible but
// disabled until backend support lands.
export const rowActionList = [
{
label: 'common.button.view',
key: 'view',
label: 'common.button.edit',
key: 'edit',
locale: true,
icon: icons.DetailInfo
icon: icons.EditOutlined
},
// {
// label: 'common.button.view',
// key: 'view',
// locale: true,
// icon: icons.DetailInfo
// },
{
label: 'common.button.viewlog',
key: 'viewlog',
@@ -64,30 +68,30 @@ export const rowActionList = [
locale: true,
icon: icons.ProfileOutlined
},
{
label: 'common.button.start',
key: 'start',
locale: true,
icon: icons.Play
},
{
label: 'common.button.stop',
key: 'stop',
locale: true,
icon: icons.Stop
},
// {
// label: 'common.button.edit',
// key: 'edit',
// label: 'common.button.start',
// key: 'start',
// locale: true,
// icon: icons.EditOutlined
// icon: icons.Play,
// props: {
// disabled: true
// }
// },
// {
// label: 'common.button.stop',
// key: 'stop',
// locale: true,
// icon: icons.Stop,
// props: {
// disabled: true
// }
// },
// {
// label: 'common.button.recreate',
// key: 'recreate',
// locale: true,
// icon: icons.RetweetOutlined
// },
{
label: 'common.button.recreate',
key: 'recreate',
locale: true,
icon: icons.RetweetOutlined
},
{
label: 'common.button.delete',
key: 'delete',
@@ -103,12 +107,18 @@ export const batchActionList = [
{
label: 'common.button.start',
key: 'start',
icon: icons.Play
icon: icons.Play,
props: {
disabled: true
}
},
{
label: 'common.button.stop',
key: 'stop',
icon: icons.Stop
icon: icons.Stop,
props: {
disabled: true
}
},
{
label: 'common.button.delete',
@@ -121,8 +131,6 @@ export const batchActionList = [
];
export const InstanceTypePhaseValueMap = {
// Available: 'Available',
// Unavailable: 'Unavailable'
PreParing: 'Preparing',
Inactive: 'Inactive',
Active: 'Active'
@@ -141,10 +149,12 @@ export const InstanceTypePhaseStatus: Record<string, StatusType> = {
};
export const StorageModeValueMap = {
Existing: 'existing',
Temporary: 'temporary'
Temporary: 'temporary',
Persistent: 'persistent'
};
export const DEFAULT_PV_CAPACITY_GB = 20;
// Constant SSH public key resource name used when SSH is enabled
export const DEFAULT_SSH_PUBLIC_KEY_NAME = 'default';
@@ -163,49 +173,40 @@ export const convertKiToGi = (value?: string): string | undefined => {
return `${_.round(Number(num) / GI_DIVISOR[unit], 2)}Gi`;
};
export const transformInstanceTypeResource = (
resource?: InstanceTypeResource
): InstanceTypeResource | undefined => {
if (!resource) return resource;
return {
...resource,
capacity: convertKiToGi(
resource.capacity
) as InstanceTypeResource['capacity'],
onceMaxRequest: convertKiToGi(
resource.onceMaxRequest
) as InstanceTypeResource['onceMaxRequest'],
remaining: convertKiToGi(
resource.remaining
) as InstanceTypeResource['remaining']
};
const parseQuantity = (value?: string | null): number => {
if (!value) return 0;
const match = /^(-?\d+(?:\.\d+)?)/.exec(String(value));
return match ? Number(match[1]) || 0 : 0;
};
export const transformInstanceType = (
item: InstanceTypeItem
): InstanceTypeItem => {
if (!item?.status) return item;
const status = item.status;
return {
...item,
spec: {
...item.spec,
manufacturer: !item.spec?.acceleratable ? 'cpu' : item.spec.manufacturer
},
status: {
...status,
accelerator: transformInstanceTypeResource(
status.accelerator
) as InstanceTypeStatus['accelerator'],
cpu: transformInstanceTypeResource(
status.cpu
) as InstanceTypeStatus['cpu'],
localStorage: transformInstanceTypeResource(
status.localStorage
) as InstanceTypeStatus['localStorage'],
ram: transformInstanceTypeResource(
status.ram
) as InstanceTypeStatus['ram']
}
};
// Returns the slider max for the accelerator count: the largest
// tier.onceMaxRequest across all acceleratorTiers (not from candidates).
export const getAcceleratorMax = (
tiers?: { onceMaxRequest: string }[] | null
) => {
if (!tiers?.length) return 0;
return tiers.reduce((acc, tier) => {
const n = parseQuantity(tier.onceMaxRequest);
return n > acc ? n : acc;
}, 0);
};
// Picks the candidate (cluster + type name) that should fulfill a requested
// accelerator count: the first candidate of the smallest tier whose
// onceMaxRequest is >= the requested count.
export const pickCandidateForAccelerator = <
C extends { cluster: string; name: string }
>(
tiers:
| { onceMaxRequest: string; candidates?: C[] | null }[]
| undefined
| null,
count: number
): C | null => {
if (!tiers?.length) return null;
const sorted = [...tiers].sort(
(a, b) => parseQuantity(a.onceMaxRequest) - parseQuantity(b.onceMaxRequest)
);
const tier = sorted.find((t) => parseQuantity(t.onceMaxRequest) >= count);
return tier?.candidates?.[0] ?? null;
};
+90 -116
View File
@@ -2,16 +2,16 @@ export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never';
// instance form data
export interface FormData {
metadata: {
name: string;
namespace: string;
};
name: string;
clusterId?: number | null;
owner_principal_id?: number | null;
displayName?: string | null;
description?: string | null;
enable_ssh?: boolean;
spec: {
type: string;
image: string;
imagePullPolicy: ImagePullPolicy;
displayName: string;
command: string[];
ports: {
port: number;
@@ -27,9 +27,11 @@ export interface FormData {
cpu?: string;
ram?: string;
localStorage?: string;
accelerator?: string;
// Stored in the form as a number so NumberSelection's strict
// equality picks up the active item. Serialized to a string at the
// API boundary in handleFinish.
accelerator?: number | string;
};
description: string;
volume: {
ephemeral?: {
capacity?: string;
@@ -37,162 +39,136 @@ export interface FormData {
persistent?: {
name: string;
};
persistentTemplate?: {
name?: string;
spec: {
type: string;
capacity: string;
};
releaseWithInstance?: boolean;
};
};
sshPublicKey: {
name: string;
};
sshPublicKeys?: { name: string }[];
};
}
export type InstanceServicePortProtocol = 'TCP' | 'UDP';
export interface InstanceIP {
ip: string;
}
export interface InstanceServicePort {
port: number;
name: string;
nodePort?: number;
protocol?: InstanceServicePortProtocol;
}
export interface InstanceStatus {
hostIPs?: {
ip: string;
}[];
namespace?: string;
phase?: string;
phaseMessage?: string;
podIPs?: {
ip: string;
}[];
ports?: {
port: number;
name: string;
nodePort?: number;
protocol?: InstanceServicePortProtocol;
}[];
hostIPs?: InstanceIP[];
podIPs?: InstanceIP[];
ports?: InstanceServicePort[];
}
// instance list item
export interface ListItem extends Omit<FormData, 'metadata'> {
export interface ListItem extends FormData {
id: number;
status?: InstanceStatus;
metadata: {
name: string;
namespace?: string;
uid: string;
resourceVersion: string;
creationTimestamp: string;
annotations?: Record<string, string>;
managedFields?: {
manager: string;
operation: string;
apiVersion: string;
time: string;
fieldsType: string;
fieldsV1: Record<string, any>;
}[];
};
created_at: string;
updated_at: string;
deleted_at?: string | null;
status?: InstanceStatus | null;
}
type Quality =
| `${number}`
| `${number}Ki`
| `${number}Mi`
| `${number}Gi`
| `${number}Ti`;
// =========== Instance Types ===========
export interface InstanceTypeResource {
capacity?: Quality;
onceMaxRequest?: Quality;
remaining?: Quality;
onceMaxRequest: string;
remaining: string;
capacity: string;
}
export interface InstanceTypeStatus {
export interface InstanceTypeCandidate {
cluster: string;
name: string;
accelerator: InstanceTypeResource;
cpu: InstanceTypeResource;
localStorage: InstanceTypeResource;
phase: string;
phaseMessage?: string;
ram: InstanceTypeResource;
localStorage: InstanceTypeResource;
}
export interface InstanceTypeMetadata {
name: string;
namespace?: string;
uid?: string;
resourceVersion?: string;
creationTimestamp?: string;
generateName?: string;
generation?: number;
finalizers?: string[];
export interface InstanceTypeTier {
onceMaxRequest: string;
candidates?: InstanceTypeCandidate[] | null;
}
export interface InstanceTypeRemainingResource {
accelerator?: string | null;
cpu: string;
ram: string;
localStorage: string;
}
export interface InstanceTypeSpec {
acceleratable: boolean;
group: string;
computeCapability?: string;
family?: string;
manufacturer?: string;
memory?: string;
product?: string;
sliced?: number;
acceleratable: boolean;
manufacturer: string;
product?: string | null;
memory?: string | null;
family?: string | null;
computeCapability?: string | null;
sliced?: string | null;
}
export interface InstanceTypeStatus {
remaining: InstanceTypeRemainingResource;
acceleratorTiers?: InstanceTypeTier[] | null;
}
export interface InstanceTypeItem {
apiVersion: string;
kind: string;
id: number;
metadata: InstanceTypeMetadata;
name: string;
spec: InstanceTypeSpec;
status: InstanceTypeStatus;
}
export interface InstanceEventObjectReference {
apiVersion?: string;
fieldPath?: string;
kind?: string;
name?: string;
namespace?: string;
resourceVersion?: string;
uid?: string;
}
export interface InstanceEventSource {
component?: string;
host?: string;
}
export interface InstanceEventSeries {
count?: number;
lastObservedTime?: string;
}
// =========== Events / logs (placeholder) ===========
// The /v2/gpu-instances API does not yet expose log/event endpoints. These
// shapes are retained as minimal placeholders so disabled UI continues to
// compile until backend support lands.
export interface InstanceEventItem {
action?: string;
apiVersion?: string;
type?: string;
reason?: string;
message?: string;
count?: number;
eventTime?: string;
firstTimestamp?: string;
involvedObject: InstanceEventObjectReference;
kind?: string;
lastTimestamp?: string;
message?: string;
metadata: {
name?: string;
namespace?: string;
uid?: string;
resourceVersion?: string;
creationTimestamp?: string;
};
reason?: string;
related?: InstanceEventObjectReference;
reportingComponent?: string;
reportingInstance?: string;
series?: InstanceEventSeries;
source?: InstanceEventSource;
type?: string;
source?: {
component?: string;
host?: string;
};
series?: {
count?: number;
lastObservedTime?: string;
};
metadata?: {
name?: string;
namespace?: string;
};
}
export interface InstanceEvents {
apiVersion?: string;
kind?: string;
metadata?: {
resourceVersion?: string;
};
items: InstanceEventItem[];
}
export type InstanceLog = string;
export interface InstanceLogQueryParams {
follow?: boolean;
limitBytes?: number;
@@ -201,5 +177,3 @@ export interface InstanceLogQueryParams {
timestamps?: boolean;
pretty?: string;
}
export type InstanceLog = string;