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:
parent
c9253319d9
commit
2dddc1c6b9
80 changed files with 1267 additions and 1011 deletions
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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 && (
|
||||
|
|
|
@ -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
|
||||
),
|
||||
]);
|
||||
|
|
|
@ -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('/'),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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))];
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue