fix(wizard): show teasers for kaas and kubeconfig features [EE-3316] (#7008)

* fix(wizard): add kubeconfig, nomad and kaas teasers
pull/7048/head
Ali 2022-06-10 09:17:13 +12:00 committed by GitHub
parent 12527aa820
commit be11dfc231
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 284 additions and 15 deletions

View File

@ -20,3 +20,4 @@ import './app.css';
import './theme.css';
import './vendor-override.css';
import '../fonts/nomad-icon.css';

View File

@ -0,0 +1,32 @@
/* created using https://icomoon.io/app */
/* https://stackoverflow.com/a/35092005/681629 */
/* for additional icons, we should create a new set that includes the existing icons */
@font-face {
font-family: 'nomad-icon';
src: url('nomad-icon/nomad-icon.eot?6tre2n');
src: url('nomad-icon/nomad-icon.eot?6tre2n#iefix') format('embedded-opentype'), url('nomad-icon/nomad-icon.ttf?6tre2n') format('truetype'),
url('nomad-icon/nomad-icon.woff?6tre2n') format('woff'), url('nomad-icon/nomad-icon.svg?6tre2n#nomad-icon') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
}
.nomad-icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'nomad-icon' !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.nomad-icon:before {
content: '\e900';
}

Binary file not shown.

View File

@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="nomad_black" d="M507.999 959.562l-443.079-255.649v-511.675l443.079-255.8 443.079 255.8v511.675l-443.079 255.649zM705.402 396.893l-118.079-67.992-142.631 77.435v-163.256l-134.095-84.839v340.865l106.369 65.121 147.617-77.813v166.202l140.894 84.612-0.076-340.336z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 738 B

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,5 @@
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { FeatureId } from '@/portainer/feature-flags/enums';
@ -6,11 +7,15 @@ import { getFeatureDetails } from './utils';
export interface Props {
featureId?: FeatureId;
showIcon?: boolean;
className?: string;
}
export function BEFeatureIndicator({
featureId,
children,
showIcon = true,
className = '',
}: PropsWithChildren<Props>) {
const { url, limitedToBE } = getFeatureDetails(featureId);
@ -20,14 +25,18 @@ export function BEFeatureIndicator({
return (
<a
className="be-indicator"
className={clsx('be-indicator', className)}
href={url}
target="_blank"
rel="noopener noreferrer"
>
{children}
<i className="fas fa-briefcase space-right be-indicator-icon" />
<span className="be-indicator-label">Business Edition Feature</span>
{showIcon && (
<i className="fas fa-briefcase space-right be-indicator-icon" />
)}
<span className="be-indicator-label break-words">
Business Edition Feature
</span>
</a>
);
}

View File

@ -83,6 +83,7 @@ export enum EnvironmentCreationTypes {
AzureEnvironment,
EdgeAgentEnvironment,
LocalKubernetesEnvironment,
KubeConfigEnvironment,
}
export enum PlatformType {

View File

@ -12,6 +12,9 @@ export enum FeatureState {
export enum FeatureId {
K8S_RESOURCE_POOL_LB_QUOTA = 'k8s-resourcepool-Ibquota',
K8S_RESOURCE_POOL_STORAGE_QUOTA = 'k8s-resourcepool-storagequota',
K8S_CREATE_FROM_KUBECONFIG = 'k8s-create-from-kubeconfig',
KAAS_PROVISIONING = 'kaas-provisioning',
NOMAD = 'nomad',
RBAC_ROLES = 'rbac-roles',
REGISTRY_MANAGEMENT = 'registry-management',
K8S_SETUP_DEFAULT = 'k8s-setup-default',

View File

@ -16,6 +16,9 @@ export async function init(edition: Edition) {
const features = {
[FeatureId.K8S_RESOURCE_POOL_LB_QUOTA]: Edition.BE,
[FeatureId.K8S_RESOURCE_POOL_STORAGE_QUOTA]: Edition.BE,
[FeatureId.K8S_CREATE_FROM_KUBECONFIG]: Edition.BE,
[FeatureId.KAAS_PROVISIONING]: Edition.BE,
[FeatureId.NOMAD]: Edition.BE,
[FeatureId.ACTIVITY_AUDIT]: Edition.BE,
[FeatureId.EXTERNAL_AUTH_LDAP]: Edition.BE,
[FeatureId.HIDE_INTERNAL_AUTH]: Edition.BE,

View File

@ -23,6 +23,7 @@ export function EnvironmentSelector({ value, onChange }: Props) {
{environmentTypes.map((eType) => (
<Option
key={eType.id}
featureId={eType.featureId}
title={eType.title}
description={eType.description}
icon={eType.icon}

View File

@ -0,0 +1,8 @@
.selected .mask-icon {
color: var(--selected-item-color);
}
.mask-icon {
color: var(--bg-boxselector-color);
transform: scale(1.2);
}

View File

@ -0,0 +1,21 @@
import clsx from 'clsx';
import styles from './KaaSIcon.module.css';
export interface Props {
selected?: boolean;
className?: string;
}
export function KaaSIcon({ selected, className }: Props) {
return (
<span
className={clsx('fa-stack fa-1x', styles.root, className, {
[styles.selected]: selected,
})}
>
<i className="fas fa-cloud fa-stack-2x" />
<i className={clsx('fas fa-dharmachakra fa-stack-1x', styles.maskIcon)} />
</span>
);
}

View File

@ -1,4 +1,16 @@
export const environmentTypes = [
import { FeatureId } from '@/portainer/feature-flags/enums';
import { KaaSIcon, Props as KaaSIconProps } from './KaaSIcon';
interface WizardEnvironmentOption {
id: string;
title: string;
icon: string | { ({ selected, className }: KaaSIconProps): JSX.Element };
description: string;
featureId?: FeatureId;
}
export const environmentTypes: WizardEnvironmentOption[] = [
{
id: 'docker',
title: 'Docker',
@ -18,4 +30,18 @@ export const environmentTypes = [
description: 'Connect to ACI environment via API',
icon: 'fab fa-microsoft',
},
] as const;
{
id: 'nomad',
title: 'Nomad',
description: 'Connect to HashiCorp Nomad environment via API',
icon: 'nomad-icon',
featureId: FeatureId.NOMAD,
},
{
id: 'kaas',
title: 'KaaS',
description: 'Provision a Kubernetes environment with a cloud provider',
icon: KaaSIcon,
featureId: FeatureId.KAAS_PROVISIONING,
},
];

View File

@ -0,0 +1,99 @@
import { Field, Form, Formik } from 'formik';
import { LoadingButton } from '@/portainer/components/Button/LoadingButton';
import { FormControl } from '@/portainer/components/form-components/FormControl';
import { FormSectionTitle } from '@/portainer/components/form-components/FormSectionTitle';
import { Input } from '@/portainer/components/form-components/Input';
import { Button } from '@/portainer/components/Button';
const initialValues = {
kubeConfig: '',
name: '',
meta: {
groupId: 1,
tagIds: [],
},
};
export function KubeConfigTeaserForm() {
return (
<Formik initialValues={initialValues} onSubmit={() => {}} validateOnMount>
{() => (
<Form className="mt-5">
<FormSectionTitle>Environment details</FormSectionTitle>
<div className="form-group">
<div className="col-sm-12">
<span className="text-primary">
<i
className="fa fa-exclamation-circle space-right"
aria-hidden="true"
/>
</span>
<span className="text-muted small">
Import the
<a
href="https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/"
target="_blank"
className="space-right space-left"
rel="noreferrer"
>
kubeconfig file
</a>
of an existing Kubernetes cluster located on-premise or on a
cloud platform. This will create a corresponding environment in
Portainer and install the agent on the cluster. Please ensure:
</span>
</div>
<div className="col-sm-12 text-muted small">
<ul className="p-2 pl-4">
<li>You have a load balancer enabled in your cluster</li>
<li>You specify current-context in your kubeconfig</li>
<li>
The kubeconfig is self-contained - including any required
credentials.
</li>
</ul>
<p>
Note: Officially supported cloud providers are Civo, Linode,
DigitalOcean and Microsoft Azure (others are not guaranteed to
work at present)
</p>
</div>
</div>
<FormControl label="Name" required>
<Field
name="name"
as={Input}
data-cy="endpointCreate-nameInput"
placeholder="e.g. docker-prod01 / kubernetes-cluster01"
readOnly
/>
</FormControl>
<FormControl
label="Kubeconfig file"
required
inputId="kubeconfig_file"
>
<Button disabled>Select a file</Button>
</FormControl>
<div className="form-group">
<div className="col-sm-12">
<LoadingButton
className="wizard-connect-button"
loadingText="Connecting environment..."
isLoading={false}
disabled
>
<i className="fa fa-plug" aria-hidden="true" /> Connect
</LoadingButton>
</div>
</div>
</Form>
)}
</Formik>
);
}

View File

@ -7,11 +7,14 @@ import {
} from '@/portainer/environments/types';
import { BoxSelectorOption } from '@/portainer/components/BoxSelector/types';
import { commandsTabs } from '@/react/edge/components/EdgeScriptForm/scripts';
import { FeatureId } from '@/portainer/feature-flags/enums';
import { BEFeatureIndicator } from '@/portainer/components/BEFeatureIndicator';
import { AnalyticsStateKey } from '../types';
import { EdgeAgentTab } from '../shared/EdgeAgentTab';
import { AgentPanel } from './AgentPanel';
import { KubeConfigTeaserForm } from './KubeConfigTeaserForm';
interface Props {
onCreate(environment: Environment, analytics: AnalyticsStateKey): void;
@ -20,6 +23,7 @@ interface Props {
const options: BoxSelectorOption<
| EnvironmentCreationTypes.AgentEnvironment
| EnvironmentCreationTypes.EdgeAgentEnvironment
| EnvironmentCreationTypes.KubeConfigEnvironment
>[] = [
{
id: 'agent_endpoint',
@ -35,6 +39,14 @@ const options: BoxSelectorOption<
description: '',
value: EnvironmentCreationTypes.EdgeAgentEnvironment,
},
{
id: 'kubeconfig_endpoint',
icon: 'fas fa-cloud-upload-alt',
label: 'Import',
value: EnvironmentCreationTypes.KubeConfigEnvironment,
description: 'Import an existing Kubernetes config',
feature: FeatureId.K8S_CREATE_FROM_KUBECONFIG,
},
];
export function WizardKubernetes({ onCreate }: Props) {
@ -72,6 +84,15 @@ export function WizardKubernetes({ onCreate }: Props) {
commands={[{ ...commandsTabs.k8sLinux, label: 'Linux' }]}
/>
);
case EnvironmentCreationTypes.KubeConfigEnvironment:
return (
<div className="px-1 py-5 border border-solid border-orange-1">
<BEFeatureIndicator
featureId={options.find((o) => o.value === type)?.feature}
/>
<KubeConfigTeaserForm />
</div>
);
default:
throw new Error('Creation type not supported');
}

View File

@ -1,22 +1,34 @@
.root {
--selected-item-color: var(--blue-2);
display: block;
width: 200px;
height: 300px;
border: 1px solid rgb(163, 163, 163);
.optionTile {
border-radius: 5px;
padding: 25px 20px;
cursor: pointer;
box-shadow: 0 3px 10px -2px rgb(161 170 166 / 60%);
margin: 0;
display: block;
width: 200px;
min-height: 300px;
}
.root:hover {
.feature {
--selected-item-color: var(--blue-2);
border: 1px solid rgb(163, 163, 163);
}
.feature:hover {
box-shadow: 0 3px 10px -2px rgb(161 170 166 / 80%);
border: 1px solid var(--blue-2);
color: #337ab7;
}
.teaser {
border: 2px solid var(--BE-only) !important;
color: var(--text-muted-color);
}
.teaser:hover {
box-shadow: 0 3px 10px -2px rgb(161 170 166 / 80%);
}
.active:hover {
color: #fff;
}

View File

@ -1,6 +1,10 @@
import clsx from 'clsx';
import { ComponentType } from 'react';
import { BEFeatureIndicator } from '@/portainer/components/BEFeatureIndicator';
import { FeatureId } from '@/portainer/feature-flags/enums';
import { isLimitedToBE } from '@/portainer/feature-flags/feature-flags.service';
import styles from './Option.module.css';
export interface SelectorItemType {
@ -12,6 +16,7 @@ export interface SelectorItemType {
interface Props extends SelectorItemType {
active?: boolean;
onClick?(): void;
featureId?: FeatureId;
}
export function Option({
@ -20,13 +25,22 @@ export function Option({
description,
title,
onClick = () => {},
featureId,
}: Props) {
const Icon = typeof icon !== 'string' ? icon : null;
const isLimited = isLimitedToBE(featureId);
return (
<button
className={clsx('border-0', styles.root, { [styles.active]: active })}
className={clsx(
styles.optionTile,
isLimited ? styles.teaser : styles.feature,
'border-0',
{
[styles.active]: active,
}
)}
type="button"
disabled={isLimited}
onClick={onClick}
>
<div className="text-center mt-2">
@ -37,9 +51,16 @@ export function Option({
)}
</div>
<div className="mt-3 text-center">
<div className="mt-3 text-center flex flex-col">
<h3>{title}</h3>
<h5>{description}</h5>
{isLimited && (
<BEFeatureIndicator
showIcon={false}
featureId={featureId}
className="!whitespace-normal"
/>
)}
</div>
</button>
);