Files
gpustack-ui/src/pages/gpu-service/storage/forms/basic.tsx
T
gitlawrandjialin 644aba3fc2 fix: scope storage type picker to the target organization
The storage create form fetched persistent-volume types once with no
tenant scope, so in the platform-admin "All" view the dropdown listed
every org's types and never re-scoped when the create-scope picker
retargeted the form — letting an org with no cluster-access grant pick
another org's types. The inline "add storage" sub-drawer in the instance
create form had the same gap plus a redundant org picker that could
retarget the storage away from the instance's org.

Both surfaces now pin the storage-type list to the chosen org (via the
X-Organization-Id header); the sub-drawer inherits the instance's scope
and hides its own picker. Page/table listings stay unscoped so every
row's type label still renders.
2026-07-08 16:31:53 +08:00

117 lines
3.4 KiB
TypeScript

import PluginExtraFields from '@/components/plugin-extra-fields';
import { PageAction, validateLabelNameRegxFor63 } from '@/config';
import {
Input as CInput,
InputNumber,
Select as SealSelect,
useAppUtils
} from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Flex, Form } from 'antd';
import { useContext } from 'react';
import { FormContext } from '../config/form-context';
import { FormData } from '../config/types';
const Basic = ({
action,
open,
showOrgScope = true,
onOrgScopeChange
}: {
action: string;
open: boolean;
// Hosts that already fix the tenant scope elsewhere (e.g. the instance
// create form, which owns the org picker) hide this slot so the storage
// is created in the surrounding scope rather than a second, conflicting
// one.
showOrgScope?: boolean;
// Fired on a user selection in the create-scope picker so the host can
// reload the org-scoped storage-type list.
onOrgScopeChange?: (orgId: number | null | undefined) => void;
}) => {
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const { storageClassList } = useContext(FormContext);
return (
<>
<Form.Item<FormData>
name="name"
rules={[
{
required: true,
message: getRuleMessage('input', 'common.table.name')
},
{
pattern: validateLabelNameRegxFor63,
message: intl.formatMessage({ id: 'gpuservice.form.rule.name' })
}
]}
>
<CInput.Input
disabled={action === PageAction.EDIT}
label={intl.formatMessage({ id: 'common.table.name' })}
required
/>
</Form.Item>
<Form.Item<FormData> name="displayName">
<CInput.Input
trim={false}
label={intl.formatMessage({ id: 'common.table.displayName' })}
/>
</Form.Item>
{showOrgScope && (
<PluginExtraFields
name="CreateOrgScopeField"
context={{ action, onChange: onOrgScopeChange }}
/>
)}
<Flex gap={16}>
<div style={{ flex: 1 }}>
<Form.Item<FormData>
name={['spec', 'type']}
rules={[
{
required: true,
message: getRuleMessage('select', 'gpuservice.storage.type')
}
]}
>
<SealSelect
disabled={action === PageAction.EDIT}
label={intl.formatMessage({ id: 'gpuservice.storage.type' })}
required
options={storageClassList}
/>
</Form.Item>
</div>
<div style={{ flex: 1 }}>
<Form.Item<FormData>
name={['spec', 'capacity']}
normalize={(value) => (value ? `${value}Gi` : undefined)}
getValueProps={(value) => ({
value: value ? String(value).replace(/Gi$/, '') : ''
})}
rules={[
{
required: true,
message: getRuleMessage('input', 'gpuservice.storage.capacity')
}
]}
>
<InputNumber
disabled={action === PageAction.EDIT}
label={intl.formatMessage({
id: 'gpuservice.storage.persistentVolume.capacity'
})}
required
/>
</Form.Item>
</div>
</Flex>
</>
);
};
export default Basic;