Compare commits
7 Commits
fix/EE-598
...
feat/EE-32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
424902f51d | ||
|
|
b807481f1c | ||
|
|
da27de2154 | ||
|
|
6743e4fbb2 | ||
|
|
b489ffaa63 | ||
|
|
6e12499d61 | ||
|
|
f7acbe16ba |
@@ -24,11 +24,14 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
adminRouter := h.NewRoute().Subrouter()
|
||||
adminRouter.Use(bouncer.AdminAccess)
|
||||
|
||||
restrictedRouter := h.NewRoute().Subrouter()
|
||||
restrictedRouter.Use(bouncer.RestrictedAccess)
|
||||
|
||||
teamLeaderRouter := h.NewRoute().Subrouter()
|
||||
teamLeaderRouter.Use(bouncer.TeamLeaderAccess)
|
||||
|
||||
adminRouter.Handle("/teams", httperror.LoggerHandler(h.teamCreate)).Methods(http.MethodPost)
|
||||
teamLeaderRouter.Handle("/teams", httperror.LoggerHandler(h.teamList)).Methods(http.MethodGet)
|
||||
restrictedRouter.Handle("/teams", httperror.LoggerHandler(h.teamList)).Methods(http.MethodGet)
|
||||
teamLeaderRouter.Handle("/teams/{id}", httperror.LoggerHandler(h.teamInspect)).Methods(http.MethodGet)
|
||||
adminRouter.Handle("/teams/{id}", httperror.LoggerHandler(h.teamUpdate)).Methods(http.MethodPut)
|
||||
adminRouter.Handle("/teams/{id}", httperror.LoggerHandler(h.teamDelete)).Methods(http.MethodDelete)
|
||||
|
||||
@@ -20,3 +20,4 @@ import './app.css';
|
||||
|
||||
import './theme.css';
|
||||
import './vendor-override.css';
|
||||
import '../fonts/nomad-icon.css';
|
||||
|
||||
32
app/assets/fonts/nomad-icon.css
Normal file
32
app/assets/fonts/nomad-icon.css
Normal 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';
|
||||
}
|
||||
BIN
app/assets/fonts/nomad-icon/nomad-icon.eot
Normal file
BIN
app/assets/fonts/nomad-icon/nomad-icon.eot
Normal file
Binary file not shown.
11
app/assets/fonts/nomad-icon/nomad-icon.svg
Normal file
11
app/assets/fonts/nomad-icon/nomad-icon.svg
Normal 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=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" 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 |
BIN
app/assets/fonts/nomad-icon/nomad-icon.ttf
Normal file
BIN
app/assets/fonts/nomad-icon/nomad-icon.ttf
Normal file
Binary file not shown.
BIN
app/assets/fonts/nomad-icon/nomad-icon.woff
Normal file
BIN
app/assets/fonts/nomad-icon/nomad-icon.woff
Normal file
Binary file not shown.
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
>
|
||||
<i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Remove
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.setRefferer()" ui-sref="portainer.wizard.endpoints" data-cy="endpoint-addEndpointButton">
|
||||
<button type="button" class="btn btn-sm btn-primary" ng-click="$ctrl.setReferrer()" ui-sref="portainer.wizard.endpoints" data-cy="endpoint-addEndpointButton">
|
||||
<i class="fa fa-plus-circle space-right" aria-hidden="true"></i>Add environment
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -34,8 +34,8 @@ angular.module('portainer.app').controller('EndpointsDatatableController', [
|
||||
this.paginationChanged();
|
||||
};
|
||||
|
||||
this.setRefferer = function () {
|
||||
window.localStorage.setItem('wizardRefferer', 'environments');
|
||||
this.setReferrer = function () {
|
||||
window.localStorage.setItem('wizardReferrer', 'environments');
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,7 @@ export enum EnvironmentCreationTypes {
|
||||
AzureEnvironment,
|
||||
EdgeAgentEnvironment,
|
||||
LocalKubernetesEnvironment,
|
||||
KubeConfigEnvironment,
|
||||
}
|
||||
|
||||
export enum PlatformType {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>
|
||||
{{ team.Name }}
|
||||
<span ng-if="!settings.TeamSync">{{ team.Name }}</span>
|
||||
<button class="btn btn-xs btn-danger" ng-if="isAdmin" ng-click="deleteTeam()"
|
||||
><i class="fa fa-trash-alt space-right" aria-hidden="true"></i>Delete this team</button
|
||||
>
|
||||
@@ -23,7 +23,9 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Leaders</td>
|
||||
<td>{{ leaderCount }}</td>
|
||||
<td>
|
||||
<span ng-if="!settings.TeamSync">{{ leaderCount }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total users in team</td>
|
||||
@@ -175,11 +177,11 @@
|
||||
<i ng-if="user.TeamRole === 'Leader'" class="fa fa-user-plus" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
<i ng-if="user.TeamRole === 'Member'" class="fa fa-user" aria-hidden="true" style="margin-right: 2px"></i>
|
||||
{{ user.TeamRole }}
|
||||
<span style="margin-left: 5px" ng-if="isAdmin">
|
||||
<span style="margin-left: 5px">
|
||||
<a style="margin-left: 5px" ng-click="promoteToLeader(user)" ng-if="user.TeamRole === 'Member'" ng-class="{ 'btn disabled py-0': settings.TeamSync }"
|
||||
><i class="fa fa-user-plus space-right" aria-hidden="true"></i>Leader</a
|
||||
>
|
||||
<a style="margin-left: 5px" ng-click="demoteToMember(user)" ng-if="user.TeamRole === 'Leader'" ng-class="{ 'btn disabled py-0': settings.TeamSync }"
|
||||
<a style="margin-left: 5px" ng-click="demoteToMember(user)" ng-if="isAdmin && user.TeamRole === 'Leader'" ng-class="{ 'btn disabled py-0': settings.TeamSync }"
|
||||
><i class="fa fa-user-times space-right" aria-hidden="true"></i>Member</a
|
||||
>
|
||||
</span>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.selected .mask-icon {
|
||||
color: var(--selected-item-color);
|
||||
}
|
||||
|
||||
.mask-icon {
|
||||
color: var(--bg-boxselector-color);
|
||||
transform: scale(1.2);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -118,8 +118,8 @@ export function EnvironmentCreationView() {
|
||||
])
|
||||
),
|
||||
});
|
||||
if (localStorage.getItem('wizardRefferer') === 'environments') {
|
||||
localStorage.removeItem('wizardRefferer');
|
||||
if (localStorage.getItem('wizardReferrer') === 'environments') {
|
||||
localStorage.removeItem('wizardReferrer');
|
||||
router.stateService.go('portainer.endpoints');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function WizardEndpointsList({ environmentIds }: Props) {
|
||||
|
||||
return (
|
||||
<Widget>
|
||||
<WidgetTitle icon="fa-plug" title="Connected Environments" />
|
||||
<WidgetTitle icon="fa-plug" title="New Environments" />
|
||||
<WidgetBody>
|
||||
{environments.map((environment) => (
|
||||
<div className={styles.wizardListWrapper} key={environment.Id}>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,6 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- portainer_data:/data
|
||||
ports:
|
||||
- 127.0.0.1:8000:8000
|
||||
- 127.0.0.1:9000:9000
|
||||
- 127.0.0.1:9443:9443
|
||||
volumes:
|
||||
|
||||
Reference in New Issue
Block a user