feat(ws): add workspace creation image step frontend (#241)
Signed-off-by: paulovmr <832830+paulovmr@users.noreply.github.com>
This commit is contained in:
parent
f0638441d6
commit
657ac9f56f
|
@ -16,6 +16,7 @@ import { WorkspaceCreationImageSelection } from '~/app/pages/Workspaces/Creation
|
||||||
import { WorkspaceCreationKindSelection } from '~/app/pages/Workspaces/Creation/WorkspaceCreationKindSelection';
|
import { WorkspaceCreationKindSelection } from '~/app/pages/Workspaces/Creation/WorkspaceCreationKindSelection';
|
||||||
import { WorkspaceCreationPropertiesSelection } from '~/app/pages/Workspaces/Creation/WorkspaceCreationPropertiesSelection';
|
import { WorkspaceCreationPropertiesSelection } from '~/app/pages/Workspaces/Creation/WorkspaceCreationPropertiesSelection';
|
||||||
import { WorkspaceCreationPodConfigSelection } from '~/app/pages/Workspaces/Creation/WorkspaceCreationPodConfigSelection';
|
import { WorkspaceCreationPodConfigSelection } from '~/app/pages/Workspaces/Creation/WorkspaceCreationPodConfigSelection';
|
||||||
|
import { WorkspaceImage, WorkspaceKind } from '~/shared/types';
|
||||||
|
|
||||||
enum WorkspaceCreationSteps {
|
enum WorkspaceCreationSteps {
|
||||||
KindSelection,
|
KindSelection,
|
||||||
|
@ -28,6 +29,8 @@ const WorkspaceCreation: React.FunctionComponent = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [currentStep, setCurrentStep] = useState(WorkspaceCreationSteps.KindSelection);
|
const [currentStep, setCurrentStep] = useState(WorkspaceCreationSteps.KindSelection);
|
||||||
|
const [selectedKind, setSelectedKind] = useState<WorkspaceKind>();
|
||||||
|
const [selectedImage, setSelectedImage] = useState<WorkspaceImage>();
|
||||||
|
|
||||||
const getStepVariant = useCallback(
|
const getStepVariant = useCallback(
|
||||||
(step: WorkspaceCreationSteps) => {
|
(step: WorkspaceCreationSteps) => {
|
||||||
|
@ -54,6 +57,11 @@ const WorkspaceCreation: React.FunctionComponent = () => {
|
||||||
navigate('/workspaces');
|
navigate('/workspaces');
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
|
const onSelectWorkspaceKind = useCallback((newWorkspaceKind: WorkspaceKind) => {
|
||||||
|
setSelectedKind(newWorkspaceKind);
|
||||||
|
setSelectedImage(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageGroup stickyOnBreakpoint={{ default: 'top' }}>
|
<PageGroup stickyOnBreakpoint={{ default: 'top' }}>
|
||||||
|
@ -124,9 +132,18 @@ const WorkspaceCreation: React.FunctionComponent = () => {
|
||||||
</PageSection>
|
</PageSection>
|
||||||
</PageGroup>
|
</PageGroup>
|
||||||
<PageSection isFilled>
|
<PageSection isFilled>
|
||||||
{currentStep === WorkspaceCreationSteps.KindSelection && <WorkspaceCreationKindSelection />}
|
{currentStep === WorkspaceCreationSteps.KindSelection && (
|
||||||
|
<WorkspaceCreationKindSelection
|
||||||
|
selectedKind={selectedKind}
|
||||||
|
onSelect={onSelectWorkspaceKind}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{currentStep === WorkspaceCreationSteps.ImageSelection && (
|
{currentStep === WorkspaceCreationSteps.ImageSelection && (
|
||||||
<WorkspaceCreationImageSelection />
|
<WorkspaceCreationImageSelection
|
||||||
|
selectedImage={selectedImage}
|
||||||
|
images={selectedKind?.podTemplate.options.imageConfig.values ?? []}
|
||||||
|
onSelect={setSelectedImage}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{currentStep === WorkspaceCreationSteps.PodConfigSelection && (
|
{currentStep === WorkspaceCreationSteps.PodConfigSelection && (
|
||||||
<WorkspaceCreationPodConfigSelection />
|
<WorkspaceCreationPodConfigSelection />
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Title } from '@patternfly/react-core';
|
||||||
|
import { WorkspaceImage } from '~/shared/types';
|
||||||
|
|
||||||
|
type WorkspaceCreationImageDetailsProps = {
|
||||||
|
workspaceImage?: WorkspaceImage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceCreationImageDetails: React.FunctionComponent<
|
||||||
|
WorkspaceCreationImageDetailsProps
|
||||||
|
> = ({ workspaceImage }) => (
|
||||||
|
<>
|
||||||
|
{!workspaceImage && <p>Select an image to view its details here.</p>}
|
||||||
|
|
||||||
|
{workspaceImage && (
|
||||||
|
<>
|
||||||
|
<Title headingLevel="h6">Image</Title>
|
||||||
|
<Title headingLevel="h3">{workspaceImage.displayName}</Title>
|
||||||
|
{Object.keys(workspaceImage.labels).map((labelKey) => (
|
||||||
|
<p key={labelKey}>
|
||||||
|
{labelKey}={workspaceImage.labels[labelKey]}
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
|
@ -0,0 +1,79 @@
|
||||||
|
import React, { useCallback, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
FilterSidePanel,
|
||||||
|
FilterSidePanelCategory,
|
||||||
|
FilterSidePanelCategoryItem,
|
||||||
|
} from '@patternfly/react-catalog-view-extension';
|
||||||
|
import { WorkspaceImage } from '~/shared/types';
|
||||||
|
import '@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css';
|
||||||
|
|
||||||
|
type WorkspaceCreationImageFilterProps = {
|
||||||
|
images: WorkspaceImage[];
|
||||||
|
selectedLabels: Map<string, Set<string>>;
|
||||||
|
onSelect: (labels: Map<string, Set<string>>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceCreationImageFilter: React.FunctionComponent<
|
||||||
|
WorkspaceCreationImageFilterProps
|
||||||
|
> = ({ images, selectedLabels, onSelect }) => {
|
||||||
|
const filterMap = useMemo(() => {
|
||||||
|
const labelsMap = new Map<string, Set<string>>();
|
||||||
|
images.forEach((image) => {
|
||||||
|
Object.keys(image.labels).forEach((labelKey) => {
|
||||||
|
const labelValue = image.labels[labelKey];
|
||||||
|
if (!labelsMap.has(labelKey)) {
|
||||||
|
labelsMap.set(labelKey, new Set<string>());
|
||||||
|
}
|
||||||
|
labelsMap.get(labelKey).add(labelValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return labelsMap;
|
||||||
|
}, [images]);
|
||||||
|
|
||||||
|
const isChecked = useCallback(
|
||||||
|
(label, labelValue) => selectedLabels.get(label)?.has(labelValue),
|
||||||
|
[selectedLabels],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(labelKey, labelValue, event) => {
|
||||||
|
const { checked } = event.currentTarget;
|
||||||
|
const newSelectedLabels: Map<string, Set<string>> = new Map(selectedLabels);
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
if (!newSelectedLabels.has(labelKey)) {
|
||||||
|
newSelectedLabels.set(labelKey, new Set<string>());
|
||||||
|
}
|
||||||
|
newSelectedLabels.get(labelKey).add(labelValue);
|
||||||
|
} else {
|
||||||
|
const labelValues = newSelectedLabels.get(labelKey);
|
||||||
|
labelValues.delete(labelValue);
|
||||||
|
if (labelValues.size === 0) {
|
||||||
|
newSelectedLabels.delete(labelKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(newSelectedLabels);
|
||||||
|
console.error(newSelectedLabels);
|
||||||
|
},
|
||||||
|
[selectedLabels, onSelect],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FilterSidePanel id="filter-panel">
|
||||||
|
{[...filterMap.keys()].map((label) => (
|
||||||
|
<FilterSidePanelCategory key={label} title={label}>
|
||||||
|
{Array.from(filterMap.get(label).values()).map((labelValue) => (
|
||||||
|
<FilterSidePanelCategoryItem
|
||||||
|
key={`${label}|||${labelValue}`}
|
||||||
|
checked={isChecked(label, labelValue)}
|
||||||
|
onClick={(e) => onChange(label, labelValue, e)}
|
||||||
|
>
|
||||||
|
{labelValue}
|
||||||
|
</FilterSidePanelCategoryItem>
|
||||||
|
))}
|
||||||
|
</FilterSidePanelCategory>
|
||||||
|
))}
|
||||||
|
</FilterSidePanel>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,143 @@
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
CardTitle,
|
||||||
|
Gallery,
|
||||||
|
PageSection,
|
||||||
|
Toolbar,
|
||||||
|
ToolbarContent,
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
EmptyState,
|
||||||
|
EmptyStateBody,
|
||||||
|
CardBody,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { SearchIcon } from '@patternfly/react-icons/dist/esm/icons/search-icon';
|
||||||
|
import { WorkspaceImage } from '~/shared/types';
|
||||||
|
import Filter, { FilteredColumn } from '~/shared/components/Filter';
|
||||||
|
|
||||||
|
type WorkspaceCreationImageListProps = {
|
||||||
|
images: WorkspaceImage[];
|
||||||
|
selectedLabels: Map<string, Set<string>>;
|
||||||
|
selectedImage: WorkspaceImage | undefined;
|
||||||
|
onSelect: (workspaceImage: WorkspaceImage) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WorkspaceCreationImageList: React.FunctionComponent<
|
||||||
|
WorkspaceCreationImageListProps
|
||||||
|
> = ({ images, selectedLabels, selectedImage, onSelect }) => {
|
||||||
|
const [workspaceImages, setWorkspaceImages] = useState<WorkspaceImage[]>(images);
|
||||||
|
const [filters, setFilters] = useState<FilteredColumn[]>([]);
|
||||||
|
|
||||||
|
const filterableColumns = useMemo(
|
||||||
|
() => ({
|
||||||
|
name: 'Name',
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getFilteredWorkspaceImagesByLabels = useCallback(
|
||||||
|
(unfilteredImages: WorkspaceImage[]) =>
|
||||||
|
unfilteredImages.filter((image) =>
|
||||||
|
Object.keys(image.labels).reduce((accumulator, labelKey) => {
|
||||||
|
const labelValue = image.labels[labelKey];
|
||||||
|
if (selectedLabels.has(labelKey)) {
|
||||||
|
return accumulator && selectedLabels.get(labelKey).has(labelValue);
|
||||||
|
}
|
||||||
|
return accumulator;
|
||||||
|
}, true),
|
||||||
|
),
|
||||||
|
[selectedLabels],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChange = useCallback(
|
||||||
|
(event: React.FormEvent<HTMLInputElement>) => {
|
||||||
|
const newSelectedWorkspaceImage = workspaceImages.find(
|
||||||
|
(image) => image.displayName === event.currentTarget.name,
|
||||||
|
);
|
||||||
|
onSelect(newSelectedWorkspaceImage);
|
||||||
|
},
|
||||||
|
[workspaceImages, onSelect],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Search name with search value
|
||||||
|
let filteredWorkspaceImages = images;
|
||||||
|
|
||||||
|
filters.forEach((filter) => {
|
||||||
|
let searchValueInput: RegExp;
|
||||||
|
try {
|
||||||
|
searchValueInput = new RegExp(filter.value, 'i');
|
||||||
|
} catch {
|
||||||
|
searchValueInput = new RegExp(filter.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredWorkspaceImages = filteredWorkspaceImages.filter((image) => {
|
||||||
|
if (filter.value === '') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (filter.columnName) {
|
||||||
|
case filterableColumns.name:
|
||||||
|
return (
|
||||||
|
image.id.search(searchValueInput) >= 0 ||
|
||||||
|
image.displayName.search(searchValueInput) >= 0
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setWorkspaceImages(getFilteredWorkspaceImagesByLabels(filteredWorkspaceImages));
|
||||||
|
}, [filterableColumns, filters, images, selectedLabels, getFilteredWorkspaceImagesByLabels]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageSection>
|
||||||
|
<Toolbar id="toolbar-group-types">
|
||||||
|
<ToolbarContent>
|
||||||
|
<Filter
|
||||||
|
id="filter-workspace-images"
|
||||||
|
onFilter={setFilters}
|
||||||
|
columnNames={filterableColumns}
|
||||||
|
/>
|
||||||
|
</ToolbarContent>
|
||||||
|
</Toolbar>
|
||||||
|
</PageSection>
|
||||||
|
<PageSection isFilled>
|
||||||
|
{workspaceImages.length === 0 && (
|
||||||
|
<EmptyState titleText="No results found" headingLevel="h4" icon={SearchIcon}>
|
||||||
|
<EmptyStateBody>
|
||||||
|
No results match the filter criteria. Clear all filters and try again.
|
||||||
|
</EmptyStateBody>
|
||||||
|
</EmptyState>
|
||||||
|
)}
|
||||||
|
{workspaceImages.length > 0 && (
|
||||||
|
<Gallery hasGutter aria-label="Selectable card container">
|
||||||
|
{workspaceImages.map((image) => (
|
||||||
|
<Card
|
||||||
|
isCompact
|
||||||
|
isSelectable
|
||||||
|
key={image.id}
|
||||||
|
id={image.id.replace(/ /g, '-')}
|
||||||
|
isSelected={image.id === selectedImage?.id}
|
||||||
|
>
|
||||||
|
<CardHeader
|
||||||
|
selectableActions={{
|
||||||
|
selectableActionId: `selectable-actions-item-${image.id.replace(/ /g, '-')}`,
|
||||||
|
selectableActionAriaLabelledby: image.displayName.replace(/ /g, '-'),
|
||||||
|
name: image.displayName,
|
||||||
|
variant: 'single',
|
||||||
|
onChange,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardTitle>{image.displayName}</CardTitle>
|
||||||
|
<CardBody>{image.id}</CardBody>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Gallery>
|
||||||
|
)}
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,10 +1,56 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Content } from '@patternfly/react-core';
|
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { WorkspaceImage } from '~/shared/types';
|
||||||
|
import { WorkspaceCreationImageDetails } from '~/app/pages/Workspaces/Creation/WorkspaceCreationImageDetails';
|
||||||
|
import { WorkspaceCreationImageList } from '~/app/pages/Workspaces/Creation/WorkspaceCreationImageList';
|
||||||
|
import { WorkspaceCreationImageFilter } from '~/app/pages/Workspaces/Creation/WorkspaceCreationImageFilter';
|
||||||
|
|
||||||
const WorkspaceCreationImageSelection: React.FunctionComponent = () => (
|
interface WorkspaceCreationImageSelectionProps {
|
||||||
<Content>
|
images: WorkspaceImage[];
|
||||||
<p>Select a workspace image and image version to use for the workspace.</p>
|
selectedImage: WorkspaceImage | undefined;
|
||||||
</Content>
|
onSelect: (image: WorkspaceImage) => void;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
const WorkspaceCreationImageSelection: React.FunctionComponent<
|
||||||
|
WorkspaceCreationImageSelectionProps
|
||||||
|
> = ({ images, selectedImage, onSelect }) => {
|
||||||
|
const [selectedLabels, setSelectedLabels] = useState<Map<string, Set<string>>>(new Map());
|
||||||
|
|
||||||
|
const imageFilterContent = useMemo(
|
||||||
|
() => (
|
||||||
|
<WorkspaceCreationImageFilter
|
||||||
|
images={images}
|
||||||
|
selectedLabels={selectedLabels}
|
||||||
|
onSelect={setSelectedLabels}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[images, selectedLabels, setSelectedLabels],
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageDetailsContent = useMemo(
|
||||||
|
() => <WorkspaceCreationImageDetails workspaceImage={selectedImage} />,
|
||||||
|
[selectedImage],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Content style={{ height: '100%' }}>
|
||||||
|
<p>Select a workspace image and image version to use for the workspace.</p>
|
||||||
|
<Divider />
|
||||||
|
<Split hasGutter>
|
||||||
|
<SplitItem style={{ minWidth: '200px' }}>{imageFilterContent}</SplitItem>
|
||||||
|
<SplitItem isFilled>
|
||||||
|
<WorkspaceCreationImageList
|
||||||
|
images={images}
|
||||||
|
selectedLabels={selectedLabels}
|
||||||
|
selectedImage={selectedImage}
|
||||||
|
onSelect={onSelect}
|
||||||
|
/>
|
||||||
|
</SplitItem>
|
||||||
|
<SplitItem style={{ minWidth: '200px' }}>{imageDetailsContent}</SplitItem>
|
||||||
|
</Split>
|
||||||
|
</Content>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export { WorkspaceCreationImageSelection };
|
export { WorkspaceCreationImageSelection };
|
||||||
|
|
|
@ -17,15 +17,16 @@ import Filter, { FilteredColumn } from '~/shared/components/Filter';
|
||||||
|
|
||||||
type WorkspaceCreationKindListProps = {
|
type WorkspaceCreationKindListProps = {
|
||||||
allWorkspaceKinds: WorkspaceKind[];
|
allWorkspaceKinds: WorkspaceKind[];
|
||||||
|
selectedKind: WorkspaceKind | undefined;
|
||||||
onSelect: (workspaceKind: WorkspaceKind) => void;
|
onSelect: (workspaceKind: WorkspaceKind) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WorkspaceCreationKindList: React.FunctionComponent<WorkspaceCreationKindListProps> = ({
|
export const WorkspaceCreationKindList: React.FunctionComponent<WorkspaceCreationKindListProps> = ({
|
||||||
allWorkspaceKinds,
|
allWorkspaceKinds,
|
||||||
|
selectedKind,
|
||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const [workspaceKinds, setWorkspaceKinds] = useState<WorkspaceKind[]>(allWorkspaceKinds);
|
const [workspaceKinds, setWorkspaceKinds] = useState<WorkspaceKind[]>(allWorkspaceKinds);
|
||||||
const [selectedWorkspaceKind, setSelectedWorkspaceKind] = useState<WorkspaceKind>();
|
|
||||||
|
|
||||||
const filterableColumns = useMemo(
|
const filterableColumns = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -71,7 +72,6 @@ export const WorkspaceCreationKindList: React.FunctionComponent<WorkspaceCreatio
|
||||||
const newSelectedWorkspaceKind = workspaceKinds.find(
|
const newSelectedWorkspaceKind = workspaceKinds.find(
|
||||||
(kind) => kind.name === event.currentTarget.name,
|
(kind) => kind.name === event.currentTarget.name,
|
||||||
);
|
);
|
||||||
setSelectedWorkspaceKind(newSelectedWorkspaceKind);
|
|
||||||
onSelect(newSelectedWorkspaceKind);
|
onSelect(newSelectedWorkspaceKind);
|
||||||
},
|
},
|
||||||
[workspaceKinds, onSelect],
|
[workspaceKinds, onSelect],
|
||||||
|
@ -106,11 +106,11 @@ export const WorkspaceCreationKindList: React.FunctionComponent<WorkspaceCreatio
|
||||||
isSelectable
|
isSelectable
|
||||||
key={kind.name}
|
key={kind.name}
|
||||||
id={kind.name.replace(/ /g, '-')}
|
id={kind.name.replace(/ /g, '-')}
|
||||||
isSelected={kind.name === selectedWorkspaceKind?.name}
|
isSelected={kind.name === selectedKind?.name}
|
||||||
>
|
>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
selectableActions={{
|
selectableActions={{
|
||||||
selectableActionId: `selectable-actions-item-${kind.name}`,
|
selectableActionId: `selectable-actions-item-${kind.name.replace(/ /g, '-')}`,
|
||||||
selectableActionAriaLabelledby: kind.name.replace(/ /g, '-'),
|
selectableActionAriaLabelledby: kind.name.replace(/ /g, '-'),
|
||||||
name: kind.name,
|
name: kind.name,
|
||||||
variant: 'single',
|
variant: 'single',
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
|
import { Content, Divider, Split, SplitItem } from '@patternfly/react-core';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { WorkspaceKind } from '~/shared/types';
|
import { WorkspaceKind } from '~/shared/types';
|
||||||
import { WorkspaceCreationKindDetails } from '~/app/pages/Workspaces/Creation/WorkspaceCreationKindDetails';
|
import { WorkspaceCreationKindDetails } from '~/app/pages/Workspaces/Creation/WorkspaceCreationKindDetails';
|
||||||
import { WorkspaceCreationKindList } from '~/app/pages/Workspaces/Creation/WorkspaceCreationKindList';
|
import { WorkspaceCreationKindList } from '~/app/pages/Workspaces/Creation/WorkspaceCreationKindList';
|
||||||
|
|
||||||
const WorkspaceCreationKindSelection: React.FunctionComponent = () => {
|
interface WorkspaceCreationKindSelectionProps {
|
||||||
|
selectedKind: WorkspaceKind | undefined;
|
||||||
|
onSelect: (kind: WorkspaceKind) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkspaceCreationKindSelection: React.FunctionComponent<
|
||||||
|
WorkspaceCreationKindSelectionProps
|
||||||
|
> = ({ selectedKind, onSelect }) => {
|
||||||
/* Replace mocks below for BFF call */
|
/* Replace mocks below for BFF call */
|
||||||
const mockedWorkspaceKind: WorkspaceKind = useMemo(
|
const mockedWorkspaceKind: WorkspaceKind = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -47,7 +54,7 @@ const WorkspaceCreationKindSelection: React.FunctionComponent = () => {
|
||||||
{
|
{
|
||||||
id: 'jupyterlab_scipy_190',
|
id: 'jupyterlab_scipy_190',
|
||||||
displayName: 'jupyter-scipy:v1.9.0',
|
displayName: 'jupyter-scipy:v1.9.0',
|
||||||
labels: { pythonVersion: '3.11' },
|
labels: { pythonVersion: '3.12' },
|
||||||
hidden: true,
|
hidden: true,
|
||||||
redirect: {
|
redirect: {
|
||||||
to: 'jupyterlab_scipy_200',
|
to: 'jupyterlab_scipy_200',
|
||||||
|
@ -57,6 +64,32 @@ const WorkspaceCreationKindSelection: React.FunctionComponent = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'jupyterlab_scipy_200',
|
||||||
|
displayName: 'jupyter-scipy:v2.0.0',
|
||||||
|
labels: { pythonVersion: '3.12' },
|
||||||
|
hidden: true,
|
||||||
|
redirect: {
|
||||||
|
to: 'jupyterlab_scipy_210',
|
||||||
|
message: {
|
||||||
|
text: 'This update will change...',
|
||||||
|
level: 'Warning',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'jupyterlab_scipy_210',
|
||||||
|
displayName: 'jupyter-scipy:v2.1.0',
|
||||||
|
labels: { pythonVersion: '3.13' },
|
||||||
|
hidden: true,
|
||||||
|
redirect: {
|
||||||
|
to: 'jupyterlab_scipy_220',
|
||||||
|
message: {
|
||||||
|
text: 'This update will change...',
|
||||||
|
level: 'Warning',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
podConfig: {
|
podConfig: {
|
||||||
|
@ -104,8 +137,6 @@ const WorkspaceCreationKindSelection: React.FunctionComponent = () => {
|
||||||
return kinds;
|
return kinds;
|
||||||
}, [mockedWorkspaceKind]);
|
}, [mockedWorkspaceKind]);
|
||||||
|
|
||||||
const [selectedKind, setSelectedKind] = useState<WorkspaceKind>();
|
|
||||||
|
|
||||||
const kindDetailsContent = useMemo(
|
const kindDetailsContent = useMemo(
|
||||||
() => <WorkspaceCreationKindDetails workspaceKind={selectedKind} />,
|
() => <WorkspaceCreationKindDetails workspaceKind={selectedKind} />,
|
||||||
[selectedKind],
|
[selectedKind],
|
||||||
|
@ -119,7 +150,8 @@ const WorkspaceCreationKindSelection: React.FunctionComponent = () => {
|
||||||
<SplitItem isFilled>
|
<SplitItem isFilled>
|
||||||
<WorkspaceCreationKindList
|
<WorkspaceCreationKindList
|
||||||
allWorkspaceKinds={allWorkspaceKinds}
|
allWorkspaceKinds={allWorkspaceKinds}
|
||||||
onSelect={(workspaceKind) => setSelectedKind(workspaceKind)}
|
selectedKind={selectedKind}
|
||||||
|
onSelect={onSelect}
|
||||||
/>
|
/>
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
<SplitItem style={{ minWidth: '200px' }}>{kindDetailsContent}</SplitItem>
|
<SplitItem style={{ minWidth: '200px' }}>{kindDetailsContent}</SplitItem>
|
||||||
|
|
|
@ -6,6 +6,39 @@ export interface WorkspaceLogo {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WorkspaceImage {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
labels: {
|
||||||
|
pythonVersion: string;
|
||||||
|
};
|
||||||
|
hidden: boolean;
|
||||||
|
redirect?: {
|
||||||
|
to: string;
|
||||||
|
message: {
|
||||||
|
text: string;
|
||||||
|
level: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkspacePodConfig {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
description: string;
|
||||||
|
labels: {
|
||||||
|
cpu: string;
|
||||||
|
memory: string;
|
||||||
|
};
|
||||||
|
redirect?: {
|
||||||
|
to: string;
|
||||||
|
message: {
|
||||||
|
text: string;
|
||||||
|
level: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface WorkspaceKind {
|
export interface WorkspaceKind {
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@ -30,40 +63,11 @@ export interface WorkspaceKind {
|
||||||
options: {
|
options: {
|
||||||
imageConfig: {
|
imageConfig: {
|
||||||
default: string;
|
default: string;
|
||||||
values: {
|
values: WorkspaceImage[];
|
||||||
id: string;
|
|
||||||
displayName: string;
|
|
||||||
labels: {
|
|
||||||
pythonVersion: string;
|
|
||||||
};
|
|
||||||
hidden: boolean;
|
|
||||||
redirect?: {
|
|
||||||
to: string;
|
|
||||||
message: {
|
|
||||||
text: string;
|
|
||||||
level: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
podConfig: {
|
podConfig: {
|
||||||
default: string;
|
default: string;
|
||||||
values: {
|
values: WorkspacePodConfig[];
|
||||||
id: string;
|
|
||||||
displayName: string;
|
|
||||||
description: string;
|
|
||||||
labels: {
|
|
||||||
cpu: string;
|
|
||||||
memory: string;
|
|
||||||
};
|
|
||||||
redirect?: {
|
|
||||||
to: string;
|
|
||||||
message: {
|
|
||||||
text: string;
|
|
||||||
level: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue