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

refactor(ui/box-selector): replace all selectors [EE-3856] (#7902)

This commit is contained in:
Chaim Lev-Ari 2023-02-07 09:03:57 +05:30 committed by GitHub
parent c9253319d9
commit 2dddc1c6b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 1267 additions and 1011 deletions

View file

@ -0,0 +1,34 @@
import { BoxSelector } from '@@/BoxSelector';
import { Team } from '../../users/teams/types';
import { ResourceControlOwnership } from '../types';
import { useOptions } from './useOptions';
export function AccessTypeSelector({
name,
isAdmin,
isPublicVisible,
teams,
value,
onChange,
}: {
name: string;
isAdmin: boolean;
teams: Team[];
isPublicVisible: boolean;
value: ResourceControlOwnership;
onChange(value: ResourceControlOwnership): void;
}) {
const options = useOptions(isAdmin, teams, isPublicVisible);
return (
<BoxSelector
slim
radioName={name}
value={value}
options={options}
onChange={onChange}
/>
);
}

View file

@ -4,7 +4,6 @@ import { FormikErrors } from 'formik';
import { useUser } from '@/react/hooks/useUser';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { BoxSelector } from '@@/BoxSelector';
import { FormError } from '@@/form-components/FormError';
import { ResourceControlOwnership, AccessControlFormData } from '../types';
@ -12,7 +11,7 @@ import { ResourceControlOwnership, AccessControlFormData } from '../types';
import { UsersField } from './UsersField';
import { TeamsField } from './TeamsField';
import { useLoadState } from './useLoadState';
import { useOptions } from './useOptions';
import { AccessTypeSelector } from './AccessTypeSelector';
interface Props {
values: AccessControlFormData;
@ -34,7 +33,6 @@ export function EditDetails({
const { user, isAdmin } = useUser();
const { users, teams, isLoading } = useLoadState(environmentId);
const options = useOptions(isAdmin, teams, isPublicVisible);
const handleChange = useCallback(
(partialValues: Partial<typeof values>) => {
@ -50,11 +48,13 @@ export function EditDetails({
return (
<>
<BoxSelector
radioName={withNamespace('ownership')}
<AccessTypeSelector
onChange={handleChangeOwnership}
name={withNamespace('ownership')}
value={values.ownership}
options={options}
onChange={(ownership) => handleChangeOwnership(ownership)}
isAdmin={isAdmin}
isPublicVisible={isPublicVisible}
teams={teams}
/>
{values.ownership === ResourceControlOwnership.RESTRICTED && (

View file

@ -70,9 +70,17 @@ function nonAdminOptions(teams?: Team[]) {
'access_restricted',
<BadgeIcon icon={ownershipIcon('restricted')} />,
'Restricted',
teams.length === 1
? `I want any member of my team (${teams[0].Name}) to be able to manage this resource`
: 'I want to restrict the management of this resource to one or more of my teams',
teams.length === 1 ? (
<>
I want any member of my team (<b>{teams[0].Name}</b>) to be able to
manage this resource
</>
) : (
<>
I want to restrict the management of this resource to one or more of
my teams
</>
),
ResourceControlOwnership.RESTRICTED
),
]);

View file

@ -66,7 +66,7 @@ export function EnvironmentTypeSelectView() {
trackEvent('endpoint-wizard-endpoint-select', {
category: 'portainer',
metadata: {
environment: steps.map((step) => step.title).join('/'),
environment: steps.map((step) => step.label).join('/'),
},
});

View file

@ -1,7 +1,6 @@
import { BoxSelector } from '@@/BoxSelector';
import { FormSection } from '@@/form-components/FormSection';
import { Option } from '../components/Option';
import { environmentTypes } from './environment-types';
export type EnvironmentSelectorValue = typeof environmentTypes[number]['id'];
@ -23,40 +22,26 @@ export function EnvironmentSelector({
onChange,
createEdgeDevice,
}: Props) {
const options = filterEdgeDevicesIfNeed(environmentTypes, createEdgeDevice);
return (
<div className="row">
<div className="form-horizontal">
<FormSection title="Select your environment(s)">
<p className="text-muted small">
You can onboard different types of environments, select all that
apply.
</p>
<div className="flex gap-4 flex-wrap">
{filterEdgeDevicesIfNeed(environmentTypes, createEdgeDevice).map(
(eType) => (
<Option
key={eType.id}
featureId={eType.featureId}
title={eType.title}
description={eType.description}
icon={eType.icon}
active={value.includes(eType.id)}
onClick={() => handleClick(eType.id)}
/>
)
)}
</div>
<BoxSelector
options={options}
isMulti
value={value}
onChange={onChange}
radioName="type-selector"
/>
</FormSection>
</div>
);
function handleClick(eType: EnvironmentSelectorValue) {
if (value.includes(eType)) {
onChange(value.filter((v) => v !== eType));
return;
}
onChange([...value, eType]);
}
}
function filterEdgeDevicesIfNeed(
@ -64,8 +49,8 @@ function filterEdgeDevicesIfNeed(
createEdgeDevice?: boolean
) {
if (!createEdgeDevice) {
return types;
return [...types];
}
return types.filter((eType) => hasEdge.includes(eType.id));
return [...types.filter((eType) => hasEdge.includes(eType.id))];
}

View file

@ -1,52 +1,71 @@
import { FeatureId } from '@/react/portainer/feature-flags/enums';
import DockerIcon from '@/assets/ico/vendor/docker-icon.svg?c';
import Kube from '@/assets/ico/kube.svg?c';
import MicrosoftIcon from '@/assets/ico/vendor/microsoft-icon.svg?c';
import NomadIcon from '@/assets/ico/vendor/nomad-icon.svg?c';
import Docker from '@/assets/ico/vendor/docker.svg?c';
import Kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
import Azure from '@/assets/ico/vendor/azure.svg?c';
import Nomad from '@/assets/ico/vendor/nomad.svg?c';
import KaaSIcon from './kaas-icon.svg?c';
export const environmentTypes = [
{
id: 'dockerStandalone',
title: 'Docker Standalone',
icon: DockerIcon,
value: 'dockerStandalone',
label: 'Docker Standalone',
icon: Docker,
iconType: 'logo',
description: 'Connect to Docker Standalone via URL/IP, API or Socket',
featureId: undefined,
},
{
id: 'dockerSwarm',
title: 'Docker Swarm',
icon: DockerIcon,
value: 'dockerSwarm',
label: 'Docker Swarm',
icon: Docker,
iconType: 'logo',
description: 'Connect to Docker Swarm via URL/IP, API or Socket',
featureId: undefined,
},
{
id: 'kubernetes',
title: 'Kubernetes',
icon: Kube,
value: 'kubernetes',
label: 'Kubernetes',
icon: Kubernetes,
iconType: 'logo',
description: 'Connect to a kubernetes environment via URL/IP',
featureId: undefined,
},
{
id: 'aci',
title: 'ACI',
value: 'aci',
label: 'ACI',
description: 'Connect to ACI environment via API',
icon: MicrosoftIcon,
featureId: undefined,
iconType: 'logo',
icon: Azure,
},
{
id: 'nomad',
title: 'Nomad',
value: 'nomad',
label: 'Nomad',
description: 'Connect to HashiCorp Nomad environment via API',
icon: NomadIcon,
featureId: FeatureId.NOMAD,
icon: Nomad,
iconType: 'logo',
feature: FeatureId.NOMAD,
disabledWhenLimited: true,
},
{
id: 'kaas',
title: 'KaaS',
value: 'kaas',
label: 'KaaS',
description: 'Provision a Kubernetes environment with a cloud provider',
icon: KaaSIcon,
featureId: FeatureId.KAAS_PROVISIONING,
iconType: 'logo',
feature: FeatureId.KAAS_PROVISIONING,
disabledWhenLimited: true,
},
] as const;
export const formTitles = {
dockerStandalone: 'Connect to your Docker Standalone environment',
dockerSwarm: 'Connect to your Docker Swarm environment',
kubernetes: 'Connect to your Kubernetes environment',
aci: 'Connect to your ACI environment',
nomad: 'Connect to your Nomad environment',
kaas: 'Provision a KaaS environment',
};

View file

@ -18,7 +18,10 @@ import { Button } from '@@/buttons';
import { FormSection } from '@@/form-components/FormSection';
import { Icon } from '@@/Icon';
import { environmentTypes } from '../EnvironmentTypeSelectView/environment-types';
import {
environmentTypes,
formTitles,
} from '../EnvironmentTypeSelectView/environment-types';
import { EnvironmentSelectorValue } from '../EnvironmentTypeSelectView/EnvironmentSelector';
import { WizardDocker } from './WizardDocker';
@ -77,10 +80,7 @@ export function EnvironmentCreationView() {
<Stepper steps={steps} currentStep={currentStepIndex + 1} />
<div className="mt-12">
<FormSection
title={`Connect to your ${currentStep.title}
environment`}
>
<FormSection title={formTitles[currentStep.id]}>
<Component
onCreate={handleCreateEnvironment}
isDockerStandalone={isDockerStandalone}

View file

@ -1,13 +1,12 @@
import { EnvironmentCreationTypes } from '@/react/portainer/environments/types';
import { BoxSelectorOption } from '@@/BoxSelector';
import { Value, BoxSelectorOption } from '@@/BoxSelector/types';
import { useCreateEdgeDeviceParam } from '../hooks/useCreateEdgeDeviceParam';
export function useFilterEdgeOptionsIfNeeded<T = EnvironmentCreationTypes>(
options: BoxSelectorOption<T>[],
edgeValue: T
) {
export function useFilterEdgeOptionsIfNeeded<
T extends Value = EnvironmentCreationTypes
>(options: BoxSelectorOption<T>[], edgeValue: T) {
const createEdgeDevice = useCreateEdgeDeviceParam();
if (!createEdgeDevice) {

View file

@ -20,6 +20,7 @@ export enum FeatureId {
REGISTRY_MANAGEMENT = 'registry-management',
K8S_SETUP_DEFAULT = 'k8s-setup-default',
S3_BACKUP_SETTING = 's3-backup-setting',
S3_RESTORE = 'restore-s3-form',
HIDE_INTERNAL_AUTHENTICATION_PROMPT = 'hide-internal-authentication-prompt',
TEAM_MEMBERSHIP = 'team-membership',
HIDE_INTERNAL_AUTH = 'hide-internal-auth',

View file

@ -28,6 +28,7 @@ export async function init(edition: Edition) {
[FeatureId.RBAC_ROLES]: Edition.BE,
[FeatureId.REGISTRY_MANAGEMENT]: Edition.BE,
[FeatureId.S3_BACKUP_SETTING]: Edition.BE,
[FeatureId.S3_RESTORE]: Edition.BE,
[FeatureId.TEAM_MEMBERSHIP]: Edition.BE,
[FeatureId.FORCE_REDEPLOYMENT]: Edition.BE,
[FeatureId.HIDE_AUTO_UPDATE_WINDOW]: Edition.BE,