mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(gpu) EE-3191 Add GPU support for containers (#7146)
This commit is contained in:
parent
f0456cbf5f
commit
4997e9c7be
43 changed files with 758 additions and 10 deletions
|
@ -15,12 +15,19 @@ export function useInputGroupContext() {
|
|||
|
||||
interface Props {
|
||||
size?: Size;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function InputGroup({ children, size }: PropsWithChildren<Props>) {
|
||||
export function InputGroup({
|
||||
children,
|
||||
size,
|
||||
className,
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<Context.Provider value>
|
||||
<div className={clsx('input-group', sizeClass(size))}>{children}</div>
|
||||
<div className={clsx('input-group', sizeClass(size), className)}>
|
||||
{children}
|
||||
</div>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ interface InputGroupSubComponents {
|
|||
Addon: typeof InputGroupAddon;
|
||||
ButtonWrapper: typeof InputGroupButtonWrapper;
|
||||
Input: typeof Input;
|
||||
className: string | undefined;
|
||||
}
|
||||
|
||||
const InputGroup: typeof MainComponent & InputGroupSubComponents =
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
background-color: var(--bg-multiselect-color);
|
||||
border: 1px solid var(--border-multiselect);
|
||||
padding: 5px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.root :global .selector__option {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { Column } from 'react-table';
|
||||
|
||||
import type { DockerContainer } from '@/react/docker/containers/types';
|
||||
|
||||
export const gpus: Column<DockerContainer> = {
|
||||
Header: 'GPUs',
|
||||
accessor: 'Gpus',
|
||||
id: 'gpus',
|
||||
disableFilters: true,
|
||||
canHide: true,
|
||||
Filter: () => null,
|
||||
};
|
|
@ -10,6 +10,7 @@ import { ports } from './ports';
|
|||
import { quickActions } from './quick-actions';
|
||||
import { stack } from './stack';
|
||||
import { state } from './state';
|
||||
import { gpus } from './gpus';
|
||||
|
||||
export function useColumns() {
|
||||
return useMemo(
|
||||
|
@ -22,6 +23,7 @@ export function useColumns() {
|
|||
created,
|
||||
ip,
|
||||
host,
|
||||
gpus,
|
||||
ports,
|
||||
ownership,
|
||||
],
|
||||
|
|
|
@ -50,4 +50,5 @@ export type DockerContainer = {
|
|||
Ports: Port[];
|
||||
StackName?: string;
|
||||
Image: string;
|
||||
Gpus: string;
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Field, Form, Formik } from 'formik';
|
|||
import { useReducer } from 'react';
|
||||
|
||||
import { useCreateRemoteEnvironmentMutation } from '@/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||
import { Hardware } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/Hardware';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import {
|
||||
Environment,
|
||||
|
@ -33,6 +34,7 @@ export function APIForm({ onCreate }: Props) {
|
|||
groupId: 1,
|
||||
tagIds: [],
|
||||
},
|
||||
gpus: [],
|
||||
};
|
||||
|
||||
const mutation = useCreateRemoteEnvironmentMutation(
|
||||
|
@ -67,7 +69,9 @@ export function APIForm({ onCreate }: Props) {
|
|||
|
||||
<TLSFieldset />
|
||||
|
||||
<MoreSettingsSection />
|
||||
<MoreSettingsSection>
|
||||
<Hardware />
|
||||
</MoreSettingsSection>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
|
@ -96,6 +100,7 @@ export function APIForm({ onCreate }: Props) {
|
|||
options: {
|
||||
tls,
|
||||
meta: values.meta,
|
||||
gpus: values.gpus,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { boolean, object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
|
||||
import { nameValidation } from '../../shared/NameField';
|
||||
|
||||
|
@ -14,5 +16,6 @@ export function validation(): SchemaOf<FormValues> {
|
|||
skipVerify: boolean(),
|
||||
meta: metadataValidation(),
|
||||
...certsValidation(),
|
||||
gpus: gpusListValidation(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
import { EnvironmentMetadata } from '@/portainer/environments/environment.service/create';
|
||||
|
||||
export interface FormValues {
|
||||
|
@ -9,4 +10,5 @@ export interface FormValues {
|
|||
certFile?: File;
|
||||
keyFile?: File;
|
||||
meta: EnvironmentMetadata;
|
||||
gpus?: Gpu[];
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Field, Form, Formik, useFormikContext } from 'formik';
|
|||
import { useReducer } from 'react';
|
||||
|
||||
import { useCreateLocalDockerEnvironmentMutation } from '@/portainer/environments/queries/useCreateEnvironmentMutation';
|
||||
import { Hardware } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/Hardware';
|
||||
import { notifySuccess } from '@/portainer/services/notifications';
|
||||
import { Environment } from '@/portainer/environments/types';
|
||||
|
||||
|
@ -27,6 +28,7 @@ export function SocketForm({ onCreate }: Props) {
|
|||
socketPath: '',
|
||||
overridePath: false,
|
||||
meta: { groupId: 1, tagIds: [] },
|
||||
gpus: [],
|
||||
};
|
||||
|
||||
const mutation = useCreateLocalDockerEnvironmentMutation();
|
||||
|
@ -45,7 +47,10 @@ export function SocketForm({ onCreate }: Props) {
|
|||
|
||||
<OverrideSocketFieldset />
|
||||
|
||||
<MoreSettingsSection />
|
||||
<MoreSettingsSection>
|
||||
<Hardware />
|
||||
</MoreSettingsSection>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
<LoadingButton
|
||||
|
@ -68,6 +73,7 @@ export function SocketForm({ onCreate }: Props) {
|
|||
{
|
||||
name: values.name,
|
||||
socketPath: values.overridePath ? values.socketPath : '',
|
||||
gpus: values.gpus,
|
||||
},
|
||||
{
|
||||
onSuccess(environment) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { boolean, object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { metadataValidation } from '../../shared/MetadataFieldset/validation';
|
||||
import { nameValidation } from '../../shared/NameField';
|
||||
|
||||
|
@ -19,5 +21,6 @@ export function validation(): SchemaOf<FormValues> {
|
|||
)
|
||||
: schema
|
||||
),
|
||||
gpus: gpusListValidation(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
import { EnvironmentMetadata } from '@/portainer/environments/environment.service/create';
|
||||
|
||||
export interface FormValues {
|
||||
|
@ -5,4 +6,5 @@ export interface FormValues {
|
|||
socketPath: string;
|
||||
overridePath: boolean;
|
||||
meta: EnvironmentMetadata;
|
||||
gpus: Gpu[];
|
||||
}
|
||||
|
|
|
@ -10,12 +10,14 @@ import { LoadingButton } from '@@/buttons/LoadingButton';
|
|||
|
||||
import { NameField } from '../NameField';
|
||||
import { MoreSettingsSection } from '../MoreSettingsSection';
|
||||
import { Hardware } from '../Hardware/Hardware';
|
||||
|
||||
import { EnvironmentUrlField } from './EnvironmentUrlField';
|
||||
import { validation } from './AgentForm.validation';
|
||||
|
||||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
showGpus?: boolean;
|
||||
}
|
||||
|
||||
const initialValues: CreateAgentEnvironmentValues = {
|
||||
|
@ -25,9 +27,10 @@ const initialValues: CreateAgentEnvironmentValues = {
|
|||
groupId: 1,
|
||||
tagIds: [],
|
||||
},
|
||||
gpus: [],
|
||||
};
|
||||
|
||||
export function AgentForm({ onCreate }: Props) {
|
||||
export function AgentForm({ onCreate, showGpus = false }: Props) {
|
||||
const [formKey, clearForm] = useReducer((state) => state + 1, 0);
|
||||
|
||||
const mutation = useCreateAgentEnvironmentMutation();
|
||||
|
@ -45,7 +48,7 @@ export function AgentForm({ onCreate }: Props) {
|
|||
<NameField />
|
||||
<EnvironmentUrlField />
|
||||
|
||||
<MoreSettingsSection />
|
||||
<MoreSettingsSection>{showGpus && <Hardware />}</MoreSettingsSection>
|
||||
|
||||
<div className="form-group">
|
||||
<div className="col-sm-12">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { object, SchemaOf, string } from 'yup';
|
||||
|
||||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
import { CreateAgentEnvironmentValues } from '@/portainer/environments/environment.service/create';
|
||||
|
||||
import { metadataValidation } from '../MetadataFieldset/validation';
|
||||
|
@ -10,5 +11,6 @@ export function validation(): SchemaOf<CreateAgentEnvironmentValues> {
|
|||
name: nameValidation(),
|
||||
environmentUrl: string().required('This field is required.'),
|
||||
meta: metadataValidation(),
|
||||
gpus: gpusListValidation(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { FormSection } from '@@/form-components/FormSection';
|
|||
import { LoadingButton } from '@@/buttons/LoadingButton';
|
||||
|
||||
import { MoreSettingsSection } from '../../MoreSettingsSection';
|
||||
import { Hardware } from '../../Hardware/Hardware';
|
||||
|
||||
import { EdgeAgentFieldset } from './EdgeAgentFieldset';
|
||||
import { validationSchema } from './EdgeAgentForm.validation';
|
||||
|
@ -17,11 +18,12 @@ import { FormValues } from './types';
|
|||
interface Props {
|
||||
onCreate(environment: Environment): void;
|
||||
readonly: boolean;
|
||||
showGpus?: boolean;
|
||||
}
|
||||
|
||||
const initialValues = buildInitialValues();
|
||||
|
||||
export function EdgeAgentForm({ onCreate, readonly }: Props) {
|
||||
export function EdgeAgentForm({ onCreate, readonly, showGpus = false }: Props) {
|
||||
const createMutation = useCreateEdgeAgentEnvironmentMutation();
|
||||
|
||||
return (
|
||||
|
@ -43,6 +45,7 @@ export function EdgeAgentForm({ onCreate, readonly }: Props) {
|
|||
value={values.pollFrequency}
|
||||
/>
|
||||
</FormSection>
|
||||
{showGpus && <Hardware />}
|
||||
</MoreSettingsSection>
|
||||
|
||||
{!readonly && (
|
||||
|
@ -82,6 +85,7 @@ export function buildInitialValues(): FormValues {
|
|||
groupId: 1,
|
||||
tagIds: [],
|
||||
},
|
||||
gpus: [],
|
||||
};
|
||||
|
||||
function defaultPortainerUrl() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { number, object, SchemaOf } from 'yup';
|
||||
|
||||
import { gpusListValidation } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { metadataValidation } from '../../MetadataFieldset/validation';
|
||||
import { nameValidation } from '../../NameField';
|
||||
|
||||
|
@ -12,5 +14,6 @@ export function validationSchema(): SchemaOf<FormValues> {
|
|||
portainerUrl: urlValidation(),
|
||||
pollFrequency: number().required(),
|
||||
meta: metadataValidation(),
|
||||
gpus: gpusListValidation(),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Gpu } from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
import { EnvironmentMetadata } from '@/portainer/environments/environment.service/create';
|
||||
|
||||
export interface FormValues {
|
||||
|
@ -6,4 +7,5 @@ export interface FormValues {
|
|||
portainerUrl: string;
|
||||
pollFrequency: number;
|
||||
meta: EnvironmentMetadata;
|
||||
gpus: Gpu[];
|
||||
}
|
||||
|
|
|
@ -14,12 +14,14 @@ interface Props {
|
|||
onCreate: (environment: Environment) => void;
|
||||
commands: CommandTab[] | Partial<Record<OS, CommandTab[]>>;
|
||||
isNomadTokenVisible?: boolean;
|
||||
showGpus?: boolean;
|
||||
}
|
||||
|
||||
export function EdgeAgentTab({
|
||||
onCreate,
|
||||
commands,
|
||||
isNomadTokenVisible,
|
||||
showGpus = false,
|
||||
}: Props) {
|
||||
const [edgeInfo, setEdgeInfo] = useState<EdgeInfo>();
|
||||
|
||||
|
@ -31,6 +33,7 @@ export function EdgeAgentTab({
|
|||
onCreate={handleCreate}
|
||||
readonly={!!edgeInfo}
|
||||
key={formKey}
|
||||
showGpus={showGpus}
|
||||
/>
|
||||
|
||||
{edgeInfo && (
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { array, object, string } from 'yup';
|
||||
|
||||
import { r2a } from '@/react-tools/react2angular';
|
||||
|
||||
import { InputList } from '@@/form-components/InputList';
|
||||
import { ItemProps } from '@@/form-components/InputList/InputList';
|
||||
import { InputGroup } from '@@/form-components/InputGroup';
|
||||
|
||||
export interface Gpu {
|
||||
value: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: Gpu[];
|
||||
onChange(value: Gpu[]): void;
|
||||
}
|
||||
|
||||
function Item({ item, onChange }: ItemProps<Gpu>) {
|
||||
return (
|
||||
<div className="flex gap-2 flex-grow">
|
||||
<InputGroup size="small" className="flex-grow">
|
||||
<InputGroup.Addon>GPU Name</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
placeholder="my-gpu"
|
||||
value={item.name}
|
||||
onChange={(e) => {
|
||||
onChange({ ...item, name: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup size="small" className="flex-grow">
|
||||
<InputGroup.Addon>Index or UUID</InputGroup.Addon>
|
||||
<InputGroup.Input
|
||||
placeholder="0 or GPU-6e2c7185-c3d3-ae22-da43-bc5267b89061"
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChange({ ...item, value: e.target.value });
|
||||
}}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function GpusList({ value, onChange }: Props) {
|
||||
return (
|
||||
<InputList<Gpu>
|
||||
label="GPU"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
itemBuilder={() => ({ value: '', name: '' })}
|
||||
addLabel="add"
|
||||
item={Item}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function gpusListValidation() {
|
||||
const gpuShape = object().shape({
|
||||
name: string().required(),
|
||||
value: string().required(),
|
||||
});
|
||||
return array().of(gpuShape).default([]);
|
||||
}
|
||||
|
||||
export const GpusListAngular = r2a(GpusList, ['value', 'onChange']);
|
|
@ -0,0 +1,22 @@
|
|||
import { useField } from 'formik';
|
||||
|
||||
import {
|
||||
Gpu,
|
||||
GpusList,
|
||||
} from '@/react/portainer/environments/wizard/EnvironmentsCreationView/shared/Hardware/GpusList';
|
||||
|
||||
import { FormSection } from '@@/form-components/FormSection';
|
||||
|
||||
export function Hardware() {
|
||||
const [field, , helpers] = useField('gpus');
|
||||
|
||||
function onChange(value: Gpu[]) {
|
||||
helpers.setValue(value);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormSection title="Hardware acceleration">
|
||||
<GpusList value={field.value} onChange={onChange} />
|
||||
</FormSection>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue