2022-07-17 23:02:14 +00:00
import { useMemo } from 'react' ;
2022-08-10 21:05:27 +00:00
import { components , MultiValue } from 'react-select' ;
import { MultiValueRemoveProps } from 'react-select/dist/declarations/src/components/MultiValue' ;
import {
ActionMeta ,
OnChangeValue ,
} from 'react-select/dist/declarations/src/types' ;
2022-07-17 23:02:14 +00:00
import { OptionProps } from 'react-select/dist/declarations/src/components/Option' ;
import { Select } from '@@/form-components/ReactSelect' ;
import { Switch } from '@@/form-components/SwitchField/Switch' ;
import { Tooltip } from '@@/Tip/Tooltip' ;
interface Values {
enabled : boolean ;
2022-08-10 21:05:27 +00:00
useSpecific : boolean ;
2022-07-17 23:02:14 +00:00
selectedGPUs : string [ ] ;
capabilities : string [ ] ;
}
interface GpuOption {
value : string ;
label : string ;
description? : string ;
}
export interface GPU {
value : string ;
name : string ;
}
export interface Props {
values : Values ;
onChange ( values : Values ) : void ;
gpus : GPU [ ] ;
usedGpus : string [ ] ;
usedAllGpus : boolean ;
}
const NvidiaCapabilitiesOptions = [
// Taken from https://github.com/containerd/containerd/blob/master/contrib/nvidia/nvidia.go#L40
{
value : 'compute' ,
label : 'compute' ,
description : 'required for CUDA and OpenCL applications' ,
} ,
{
value : 'compat32' ,
label : 'compat32' ,
description : 'required for running 32-bit applications' ,
} ,
{
value : 'graphics' ,
label : 'graphics' ,
description : 'required for running OpenGL and Vulkan applications' ,
} ,
{
value : 'utility' ,
label : 'utility' ,
description : 'required for using nvidia-smi and NVML' ,
} ,
{
value : 'video' ,
label : 'video' ,
description : 'required for using the Video Codec SDK' ,
} ,
{
value : 'display' ,
label : 'display' ,
description : 'required for leveraging X11 display' ,
} ,
] ;
function Option ( props : OptionProps < GpuOption , true > ) {
const {
data : { value , description } ,
} = props ;
return (
< div >
{ /* eslint-disable-next-line react/jsx-props-no-spreading */ }
< components.Option { ...props } >
{ ` ${ value } - ${ description } ` }
< / components.Option >
< / div >
) ;
}
2022-08-10 21:05:27 +00:00
function MultiValueRemove ( props : MultiValueRemoveProps < GpuOption , true > ) {
const {
selectProps : { value } ,
} = props ;
if ( value && ( value as MultiValue < GpuOption > ) . length === 1 ) {
return null ;
}
// eslint-disable-next-line react/jsx-props-no-spreading
return < components.MultiValueRemove { ...props } / > ;
}
2022-07-17 23:02:14 +00:00
export function Gpu ( {
values ,
onChange ,
2022-07-29 04:08:17 +00:00
gpus = [ ] ,
2022-07-17 23:02:14 +00:00
usedGpus = [ ] ,
usedAllGpus ,
} : Props ) {
const options = useMemo ( ( ) = > {
2022-07-29 04:08:17 +00:00
const options = ( gpus || [ ] ) . map ( ( gpu ) = > ( {
2022-07-17 23:02:14 +00:00
value : gpu.value ,
label :
usedGpus . includes ( gpu . value ) || usedAllGpus
? ` ${ gpu . name } (in use) `
: gpu . name ,
} ) ) ;
2022-08-10 21:05:27 +00:00
options . unshift ( {
value : 'all' ,
label : 'Use All GPUs' ,
} ) ;
2022-07-17 23:02:14 +00:00
return options ;
} , [ gpus , usedGpus , usedAllGpus ] ) ;
function onChangeValues ( key : string , newValue : boolean | string [ ] ) {
const newValues = {
. . . values ,
[ key ] : newValue ,
} ;
onChange ( newValues ) ;
}
function toggleEnableGpu() {
onChangeValues ( 'enabled' , ! values . enabled ) ;
}
2022-08-10 21:05:27 +00:00
function onChangeSelectedGpus (
newValue : OnChangeValue < GpuOption , true > ,
actionMeta : ActionMeta < GpuOption >
) {
let { useSpecific } = values ;
let selectedGPUs = newValue . map ( ( option ) = > option . value ) ;
if ( actionMeta . action === 'select-option' ) {
useSpecific = actionMeta . option ? . value !== 'all' ;
selectedGPUs = selectedGPUs . filter ( ( value ) = >
useSpecific ? value !== 'all' : value === 'all'
) ;
}
const newValues = { . . . values , selectedGPUs , useSpecific } ;
onChange ( newValues ) ;
2022-07-17 23:02:14 +00:00
}
function onChangeSelectedCaps ( newValue : OnChangeValue < GpuOption , true > ) {
onChangeValues (
'capabilities' ,
newValue . map ( ( option ) = > option . value )
) ;
}
const gpuCmd = useMemo ( ( ) = > {
const devices = values . selectedGPUs . join ( ',' ) ;
2022-08-10 21:05:27 +00:00
const deviceStr = devices === 'all' ? 'all,' : ` device= ${ devices } , ` ;
2022-07-17 23:02:14 +00:00
const caps = values . capabilities . join ( ',' ) ;
2022-08-10 21:05:27 +00:00
return ` --gpus ' ${ deviceStr } "capabilities= ${ caps } "' ` ;
2022-07-17 23:02:14 +00:00
} , [ values . selectedGPUs , values . capabilities ] ) ;
const gpuValue = useMemo (
( ) = >
options . filter ( ( option ) = > values . selectedGPUs . includes ( option . value ) ) ,
[ values . selectedGPUs , options ]
) ;
const capValue = useMemo (
( ) = >
NvidiaCapabilitiesOptions . filter ( ( option ) = >
values . capabilities . includes ( option . value )
) ,
[ values . capabilities ]
) ;
return (
< div >
< div className = "form-group" >
< div className = "col-sm-3 col-lg-2 control-label text-left" >
Enable GPU
< Switch
id = "enabled"
name = "enabled"
checked = { values . enabled }
onChange = { toggleEnableGpu }
className = "ml-2"
/ >
< / div >
< div className = "col-sm-9 col-lg-10 text-left" >
< Select < GpuOption , true >
isMulti
closeMenuOnSelect
value = { gpuValue }
2022-08-10 21:05:27 +00:00
isClearable = { false }
backspaceRemovesValue = { false }
2022-07-17 23:02:14 +00:00
isDisabled = { ! values . enabled }
onChange = { onChangeSelectedGpus }
options = { options }
2022-08-10 21:05:27 +00:00
components = { { MultiValueRemove } }
2022-07-17 23:02:14 +00:00
/ >
< / div >
< / div >
{ values . enabled && (
< >
< div className = "form-group" >
< div className = "col-sm-3 col-lg-2 control-label text-left" >
Capabilities
2022-08-10 21:05:27 +00:00
< Tooltip message = "‘ compute’ and ‘ utility’ capabilities are preselected by Portainer because they are used by default when you don’ t explicitly specify capabilities with docker CLI ‘ --gpus’ option." / >
2022-07-17 23:02:14 +00:00
< / div >
< div className = "col-sm-9 col-lg-10 text-left" >
< Select < GpuOption , true >
isMulti
closeMenuOnSelect
value = { capValue }
options = { NvidiaCapabilitiesOptions }
components = { { Option } }
onChange = { onChangeSelectedCaps }
/ >
< / div >
< / div >
< div className = "form-group" >
< div className = "col-sm-3 col-lg-2 control-label text-left" >
Control
< Tooltip message = "This is the generated equivalent of the '--gpus' docker CLI parameter based on your settings." / >
< / div >
< div className = "col-sm-9 col-lg-10" >
< code > { gpuCmd } < / code >
< / div >
< / div >
< / >
) }
< / div >
) ;
}