1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 22:05:23 +02:00

refactor(ui): remove global providers [EE-4128] (#7578)

This commit is contained in:
Chaim Lev-Ari 2022-09-20 21:14:24 +03:00 committed by GitHub
parent d3f094cb18
commit fad376b415
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 372 additions and 214 deletions

View file

@ -4,6 +4,7 @@ import clsx from 'clsx';
import styles from './HeaderContainer.module.css';
const Context = createContext<null | boolean>(null);
Context.displayName = 'PageHeaderContext';
export function useHeaderContext() {
const context = useContext(Context);

View file

@ -1,6 +1,7 @@
import { createContext, PropsWithChildren, useContext } from 'react';
const Context = createContext<null | boolean>(null);
Context.displayName = 'WidgetContext';
export function useWidgetContext() {
const context = useContext(Context);

View file

@ -2,6 +2,7 @@ import { createContext, PropsWithChildren, useContext } from 'react';
export function createRowContext<TContext>() {
const Context = createContext<TContext | null>(null);
Context.displayName = 'RowContext';
return { RowProvider, useRowContext };

View file

@ -19,6 +19,7 @@ interface TableSettingsContextInterface<T> {
const TableSettingsContext = createContext<TableSettingsContextInterface<
Record<string, unknown>
> | null>(null);
TableSettingsContext.displayName = 'TableSettingsContext';
export function useTableSettings<T>() {
const Context = getContextType<T>();

View file

@ -7,6 +7,7 @@ interface TableSettingsContextInterface<T> {
const TableSettingsContext = createContext<TableSettingsContextInterface<
Record<string, unknown>
> | null>(null);
TableSettingsContext.displayName = 'TableSettingsContext';
export function useTableSettings<T>() {
const Context = getContextType<T>();

View file

@ -2,6 +2,7 @@ import clsx from 'clsx';
import { createContext, PropsWithChildren, useContext } from 'react';
const Context = createContext<null | boolean>(null);
Context.displayName = 'InputGroupContext';
type Size = 'small' | 'large';

View file

@ -0,0 +1,242 @@
import { useMemo } from 'react';
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';
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;
useSpecific: boolean;
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>
);
}
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} />;
}
export function Gpu({
values,
onChange,
gpus = [],
usedGpus = [],
usedAllGpus,
}: Props) {
const options = useMemo(() => {
const options = (gpus || []).map((gpu) => ({
value: gpu.value,
label:
usedGpus.includes(gpu.value) || usedAllGpus
? `${gpu.name} (in use)`
: gpu.name,
}));
options.unshift({
value: 'all',
label: 'Use All GPUs',
});
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);
}
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);
}
function onChangeSelectedCaps(newValue: OnChangeValue<GpuOption, true>) {
onChangeValues(
'capabilities',
newValue.map((option) => option.value)
);
}
const gpuCmd = useMemo(() => {
const devices = values.selectedGPUs.join(',');
const deviceStr = devices === 'all' ? 'all,' : `device=${devices},`;
const caps = values.capabilities.join(',');
return `--gpus '${deviceStr}"capabilities=${caps}"'`;
}, [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}
isClearable={false}
backspaceRemovesValue={false}
isDisabled={!values.enabled}
onChange={onChangeSelectedGpus}
options={options}
components={{ MultiValueRemove }}
/>
</div>
</div>
{values.enabled && (
<>
<div className="form-group">
<div className="col-sm-3 col-lg-2 control-label text-left">
Capabilities
<Tooltip message="compute and utility capabilities are preselected by Portainer because they are used by default when you dont explicitly specify capabilities with docker CLI --gpus option." />
</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>
);
}

View file

@ -2,14 +2,14 @@ import { useQuery } from 'react-query';
import clsx from 'clsx';
import { getVersionStatus } from '@/portainer/services/api/status.service';
import { useUIState } from '@/portainer/hooks/UIStateProvider';
import { useUIState } from '@/portainer/hooks/useUIState';
import { Icon } from '@@/Icon';
import styles from './UpdateNotifications.module.css';
export function UpdateNotification() {
const [uiState, setUIState] = useUIState();
const uiStateStore = useUIState();
const query = useUpdateNotification();
if (!query.data || !query.data.UpdateAvailable) {
@ -19,9 +19,9 @@ export function UpdateNotification() {
const { LatestVersion } = query.data;
if (
uiState?.dismissedUpdateVersion?.length > 0 &&
!!uiStateStore.dismissedUpdateVersion &&
LatestVersion?.length > 0 &&
uiState?.dismissedUpdateVersion === LatestVersion
uiStateStore.dismissedUpdateVersion === LatestVersion
) {
return null;
}
@ -63,10 +63,7 @@ export function UpdateNotification() {
);
function onDismiss(version: string) {
setUIState({
...uiState,
dismissedUpdateVersion: version,
});
uiStateStore.dismissUpdateVersion(version);
}
}

View file

@ -24,6 +24,7 @@ interface State {
}
export const Context = createContext<State | null>(null);
Context.displayName = 'SidebarContext';
export function useSidebarState() {
const context = useContext(Context);