fix(ui): box-selector fixes [EE-3949] (#7489)

pull/7560/head
Chaim Lev-Ari 2022-08-22 11:55:48 +03:00 committed by GitHub
parent 8d304b78cb
commit ace01eac9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 770 additions and 616 deletions

View File

@ -1,4 +1,8 @@
<svg width="51" height="57" viewBox="0 0 51 57" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="auto" height="auto" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M49.4176 17.75L43.882 0.819107H7.28089L1.74531 17.75C1.74531 17.75 -4.79215 34.8935 10.4787 45.4348C24.3922 55.0404 25.5814 56.2077 25.5814 56.2077C25.5814 56.2077 26.7707 55.038 40.6842 45.4348C55.955 34.8935 49.4176 17.75 49.4176 17.75Z" fill="#F4552A"/> <path
<path d="M25.5851 0.984695L31.4835 17.6745L49.4453 18.0361L35.1283 28.7097L40.3323 45.6217L25.5851 35.5293L10.838 45.6217L16.042 28.7097L1.72498 18.0361L19.6868 17.6745L25.5851 0.984695Z" fill="white"/> d="M22.9751 15.3177L20.4526 7.52911H3.77358L1.25103 15.3177C1.25103 15.3177 -1.72806 23.2041 5.23082 28.0533C11.5711 32.4721 12.1131 33.0091 12.1131 33.0091C12.1131 33.0091 12.655 32.471 18.9953 28.0533C25.9542 23.2041 22.9751 15.3177 22.9751 15.3177Z"
</svg> fill="#F4552A" />
<path
d="M12.1152 7.60529L14.803 15.283L22.9882 15.4493L16.4639 20.3594L18.8354 28.1393L12.1152 23.4966L5.39497 28.1393L7.76642 20.3594L1.24219 15.4493L9.42731 15.283L12.1152 7.60529Z"
fill="white" />
</svg>

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 604 B

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -76,37 +76,39 @@
<!-- build-method --> <!-- build-method -->
<div class="col-sm-12 form-section-title"> Build method </div> <div class="col-sm-12 form-section-title"> Build method </div>
<div class="form-group"></div> <div class="form-group"></div>
<div class="form-group" class="mb-0"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper">
<input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" /> <div>
<label for="method_editor"> <input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" />
<div class="boxselector_header vertical-center"> <label for="method_editor">
<pr-icon icon="'edit'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
Web editor <pr-icon icon="'edit'" feather="true"></pr-icon>
</div> Web editor
<p>Use our Web editor</p> </div>
</label> <p>Use our Web editor</p>
</div> </label>
<div> </div>
<input type="radio" id="method_upload" ng-model="state.BuildType" value="upload" ng-click="saveEditorContent()" /> <div>
<label for="method_upload"> <input type="radio" id="method_upload" ng-model="state.BuildType" value="upload" ng-click="saveEditorContent()" />
<div class="boxselector_header vertical-center"> <label for="method_upload">
<pr-icon icon="'upload'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
Upload <pr-icon icon="'upload'" feather="true"></pr-icon>
</div> Upload
<p>Upload a tarball or a Dockerfile from your computer</p> </div>
</label> <p>Upload a tarball or a Dockerfile from your computer</p>
</div> </label>
<div> </div>
<input type="radio" id="method_url" ng-model="state.BuildType" value="url" ng-click="saveEditorContent()" /> <div>
<label for="method_url"> <input type="radio" id="method_url" ng-model="state.BuildType" value="url" ng-click="saveEditorContent()" />
<div class="boxselector_header vertical-center"> <label for="method_url">
<pr-icon icon="'globe'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
URL <pr-icon icon="'globe'" feather="true"></pr-icon>
</div> URL
<p>Specify a URL to a file</p> </div>
</label> <p>Specify a URL to a file</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -34,27 +34,29 @@
<!-- edge-job-method-select --> <!-- edge-job-method-select -->
<div class="col-sm-12 form-section-title"> Edge job configuration </div> <div class="col-sm-12 form-section-title"> Edge job configuration </div>
<div class="form-group"></div> <div class="form-group"></div>
<div class="form-group px-4"> <div class="form-group">
<div class="boxselector_wrapper !mt-0"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper !mt-0">
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" /> <div>
<label for="config_basic"> <input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" />
<div class="boxselector_header vertical-center"> <label for="config_basic">
<pr-icon icon="'calendar'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
Basic configuration <pr-icon icon="'calendar'" feather="true"></pr-icon>
</div> Basic configuration
<p>Select date from calendar</p> </div>
</label> <p>Select date from calendar</p>
</div> </label>
<div> </div>
<input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced" /> <div>
<label for="config_advanced"> <input type="radio" id="config_advanced" ng-model="$ctrl.formValues.cronMethod" value="advanced" />
<div class="boxselector_header vertical-center"> <label for="config_advanced">
<pr-icon icon="'edit'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
Advanced configuration <pr-icon icon="'edit'" feather="true"></pr-icon>
</div> Advanced configuration
<p>Write your own cron rule</p> </div>
</label> <p>Write your own cron rule</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -151,28 +153,29 @@
<!-- execution-method --> <!-- execution-method -->
<div ng-if="!$ctrl.model.Id"> <div ng-if="!$ctrl.model.Id">
<div class="col-sm-12 form-section-title"> Job content </div> <div class="col-sm-12 form-section-title"> Job content </div>
<div class="form-group"></div> <div class="form-group">
<div class="form-group px-4"> <div class="col-sm-12">
<div class="boxselector_wrapper !mt-0"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="method_editor" ng-model="$ctrl.formValues.method" value="editor" /> <input type="radio" id="method_editor" ng-model="$ctrl.formValues.method" value="editor" />
<label for="method_editor"> <label for="method_editor">
<div class="boxselector_header vertical-center"> <div class="boxselector_header vertical-center">
<pr-icon icon="'edit'" feather="true"></pr-icon> <pr-icon icon="'edit'" feather="true"></pr-icon>
Web editor Web editor
</div> </div>
<p>Use our Web editor</p> <p>Use our Web editor</p>
</label> </label>
</div> </div>
<div> <div>
<input type="radio" id="method_upload" ng-model="$ctrl.formValues.method" value="upload" /> <input type="radio" id="method_upload" ng-model="$ctrl.formValues.method" value="upload" />
<label for="method_upload"> <label for="method_upload">
<div class="boxselector_header vertical-center"> <div class="boxselector_header vertical-center">
<pr-icon icon="'upload'" feather="true"></pr-icon> <pr-icon icon="'upload'" feather="true"></pr-icon>
Upload Upload
</div> </div>
<p>Upload from your computer</p> <p>Upload from your computer</p>
</label> </label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,15 @@
import { compose, kubernetes } from '@@/BoxSelector/common-options/deployment-methods';
export default class EdgeStackDeploymentTypeSelectorController { export default class EdgeStackDeploymentTypeSelectorController {
/* @ngInject */ /* @ngInject */
constructor() { constructor() {
this.deploymentOptions = [ this.deploymentOptions = [
{ id: 'deployment_compose', icon: 'fab fa-docker', label: 'Compose', description: 'Docker compose format', value: 0 },
{ {
id: 'deployment_kube', ...compose,
icon: 'fa fa-cubes', value: 0,
label: 'Kubernetes', },
description: 'Kubernetes manifest format', {
...kubernetes,
value: 1, value: 1,
disabled: () => { disabled: () => {
return this.hasDockerEndpoint(); return this.hasDockerEndpoint();

View File

@ -26,27 +26,29 @@
</div> </div>
<div class="col-sm-12 form-section-title"> Group type </div> <div class="col-sm-12 form-section-title"> Group type </div>
<div class="col-sm-12 !px-0"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div class="boxselector"> <div class="boxselector_wrapper">
<input type="radio" id="static-group" ng-model="$ctrl.model.Dynamic" ng-value="false" ng-checked="!$ctrl.model.Dynamic" /> <div class="boxselector">
<label for="static-group"> <input type="radio" id="static-group" ng-model="$ctrl.model.Dynamic" ng-value="false" ng-checked="!$ctrl.model.Dynamic" />
<div class="boxselector_header vertical-center"> <label for="static-group">
<pr-icon icon="'list'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
Static <pr-icon icon="'list'" feather="true"></pr-icon>
</div> Static
<p>Manually select Edge environments</p> </div>
</label> <p>Manually select Edge environments</p>
</div> </label>
<div class="boxselector"> </div>
<input type="radio" id="dynamic-group" ng-model="$ctrl.model.Dynamic" ng-value="true" ng-checked="$ctrl.model.Dynamic" /> <div class="boxselector">
<label for="dynamic-group"> <input type="radio" id="dynamic-group" ng-model="$ctrl.model.Dynamic" ng-value="true" ng-checked="$ctrl.model.Dynamic" />
<div class="boxselector_header vertical-center"> <label for="dynamic-group">
<pr-icon icon="'tag'" feather="true" className="'feather'"></pr-icon> <div class="boxselector_header vertical-center">
Dynamic <pr-icon icon="'tag'" feather="true"></pr-icon>
</div> Dynamic
<p>Automatically associate environments via tags</p> </div>
</label> <p>Automatically associate environments via tags</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -78,27 +80,29 @@
<!-- DynamicGroup --> <!-- DynamicGroup -->
<div ng-if="$ctrl.model.Dynamic"> <div ng-if="$ctrl.model.Dynamic">
<div class="col-sm-12 form-section-title"> Tags </div> <div class="col-sm-12 form-section-title"> Tags </div>
<div ng-if="$ctrl.tags.length" class="form-group col-sm-12"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div class="boxselector"> <div class="boxselector_wrapper">
<input type="radio" id="or-selector" ng-model="$ctrl.model.PartialMatch" ng-value="true" ng-checked="$ctrl.model.PartialMatch" /> <div class="boxselector">
<label for="or-selector"> <input type="radio" id="or-selector" ng-model="$ctrl.model.PartialMatch" ng-value="true" ng-checked="$ctrl.model.PartialMatch" />
<div class="boxselector_header vertical-center"> <label for="or-selector">
<pr-icon icon="'tag'" feather="true"></pr-icon> <div class="boxselector_header vertical-center">
Partial match <pr-icon icon="'tag'" feather="true"></pr-icon>
</div> Partial match
<p>Associate any environment matching at least one of the selected tags</p> </div>
</label> <p>Associate any environment matching at least one of the selected tags</p>
</div> </label>
<div class="boxselector"> </div>
<input type="radio" id="and-selector" ng-model="$ctrl.model.PartialMatch" ng-value="false" ng-checked="!$ctrl.model.PartialMatch" /> <div class="boxselector">
<label for="and-selector"> <input type="radio" id="and-selector" ng-model="$ctrl.model.PartialMatch" ng-value="false" ng-checked="!$ctrl.model.PartialMatch" />
<div class="boxselector_header vertical-center"> <label for="and-selector">
<pr-icon icon="'tag'" feather="true" className="'feather'"></pr-icon> <div class="boxselector_header vertical-center">
Full match <pr-icon icon="'tag'" feather="true"></pr-icon>
</div> Full match
<p>Associate any environment matching all of the selected tags</p> </div>
</label> <p>Associate any environment matching all of the selected tags</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,14 +1,11 @@
import { editor, git, template, upload } from '@@/BoxSelector/common-options/build-methods';
class DockerComposeFormController { class DockerComposeFormController {
/* @ngInject */ /* @ngInject */
constructor($async, EdgeTemplateService, Notifications) { constructor($async, EdgeTemplateService, Notifications) {
Object.assign(this, { $async, EdgeTemplateService, Notifications }); Object.assign(this, { $async, EdgeTemplateService, Notifications });
this.methodOptions = [ this.methodOptions = [editor, upload, git, template];
{ id: 'method_editor', icon: 'edit', featherIcon: true, label: 'Web editor', description: 'Use our Web editor', value: 'editor' },
{ id: 'method_upload', icon: 'upload', featherIcon: true, label: 'Upload', description: 'Upload from your computer', value: 'upload' },
{ id: 'method_repository', icon: 'github', featherIcon: true, label: 'Repository', description: 'Use a git repository', value: 'repository' },
{ id: 'method_template', icon: 'file-text', featherIcon: true, label: 'Template', description: 'Use an Edge stack template', value: 'template' },
];
this.selectedTemplate = null; this.selectedTemplate = null;

View File

@ -1,13 +1,11 @@
import { editor, git, upload } from '@@/BoxSelector/common-options/build-methods';
class KubeManifestFormController { class KubeManifestFormController {
/* @ngInject */ /* @ngInject */
constructor($async) { constructor($async) {
Object.assign(this, { $async }); Object.assign(this, { $async });
this.methodOptions = [ this.methodOptions = [editor, upload, git];
{ id: 'method_editor', icon: 'edit', featherIcon: true, label: 'Web editor', description: 'Use our Web editor', value: 'editor' },
{ id: 'method_upload', icon: 'upload', featherIcon: true, label: 'Upload', description: 'Upload from your computer', value: 'upload' },
{ id: 'method_repository', icon: 'github', featherIcon: true, label: 'Repository', description: 'Use a git repository', value: 'repository' },
];
this.onChangeFileContent = this.onChangeFileContent.bind(this); this.onChangeFileContent = this.onChangeFileContent.bind(this);
this.onChangeFormValues = this.onChangeFormValues.bind(this); this.onChangeFormValues = this.onChangeFormValues.bind(this);

View File

@ -1,17 +1,14 @@
import { buildOption } from '@/portainer/components/BoxSelector';
import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel'; import { AccessControlFormData } from '@/portainer/components/accessControlForm/porAccessControlFormModel';
import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils'; import { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
import { isBE } from '@/portainer/feature-flags/feature-flags.service'; import { isBE } from '@/portainer/feature-flags/feature-flags.service';
import { editor, upload } from '@@/BoxSelector/common-options/build-methods';
class KubeCreateCustomTemplateViewController { class KubeCreateCustomTemplateViewController {
/* @ngInject */ /* @ngInject */
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService) { constructor($async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService) {
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService }); Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService });
this.methodOptions = [ this.methodOptions = [editor, upload];
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', 'editor'),
buildOption('method_upload', 'svg-upload', 'Upload', 'Upload from your computer', 'upload'),
];
this.templates = null; this.templates = null;
this.isTemplateVariablesEnabled = isBE; this.isTemplateVariablesEnabled = isBE;

View File

@ -709,83 +709,85 @@
</div> </div>
<!-- access policy options --> <!-- access policy options -->
<div class="form-group" style="margin-bottom: 0"> <div class="form-group">
<div class="boxselector_wrapper !px-[15px]"> <div class="col-sm-12">
<div <div class="boxselector_wrapper">
ng-if=" <div
(!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) || ng-if="
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED) (!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) ||
" (ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED)
> "
<input
type="radio"
id="data_access_isolated"
ng-value="ctrl.ApplicationDataAccessPolicies.ISOLATED"
ng-model="ctrl.formValues.DataAccessPolicy"
ng-change="ctrl.resetDeploymentType()"
/>
<label for="data_access_isolated">
<div class="boxselector_header">
<pr-icon icon="'svg-cubes'"></pr-icon>
Isolated
</div>
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
</label>
</div>
<div
style="color: #767676"
ng-if="
(ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED) || ctrl.state.persistedFoldersUseExistingVolumes
"
>
<input type="radio" id="data_access_isolated" disabled />
<label
for="data_access_isolated"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the data access policy is not allowed"
style="cursor: pointer; border-color: #767676"
> >
<div class="boxselector_header"> <input
<pr-icon icon="'svg-cubes'"></pr-icon> type="radio"
Isolated id="data_access_isolated"
</div> ng-value="ctrl.ApplicationDataAccessPolicies.ISOLATED"
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p> ng-model="ctrl.formValues.DataAccessPolicy"
</label> ng-change="ctrl.resetDeploymentType()"
</div> />
<div ng-if="!ctrl.state.isEdit || (ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED)"> <label for="data_access_isolated">
<input <div class="boxselector_header">
type="radio" <pr-icon icon="'svg-cubes'"></pr-icon>
id="data_access_shared" Isolated
ng-value="ctrl.ApplicationDataAccessPolicies.SHARED" </div>
ng-model="ctrl.formValues.DataAccessPolicy" <p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
ng-change="ctrl.resetDeploymentType()" </label>
/> </div>
<label for="data_access_shared"> <div
<div class="boxselector_header"> style="color: #767676"
<pr-icon icon="'box'" feather="true"></pr-icon> ng-if="
Shared (ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED) || ctrl.state.persistedFoldersUseExistingVolumes
</div> "
<p>Application will be deployed as a Deployment with a shared storage access</p>
</label>
</div>
<div style="color: #767676" ng-if="ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED">
<input type="radio" id="data_access_shared" disabled />
<label
for="data_access_shared"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the data access policy is not allowed"
style="cursor: pointer; border-color: #767676"
> >
<div class="boxselector_header"> <input type="radio" id="data_access_isolated" disabled />
<pr-icon icon="'sliders'" feather="true"></pr-icon> <label
Shared for="data_access_isolated"
</div> tooltip-append-to-body="true"
<p>Application will be deployed as a Deployment with a shared storage access</p> tooltip-placement="bottom"
</label> tooltip-class="portainer-tooltip"
uib-tooltip="Changing the data access policy is not allowed"
style="cursor: pointer; border-color: #767676"
>
<div class="boxselector_header">
<pr-icon icon="'svg-cubes'"></pr-icon>
Isolated
</div>
<p>Application will be deployed as a StatefulSet with each instantiating their own data</p>
</label>
</div>
<div ng-if="!ctrl.state.isEdit || (ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.SHARED)">
<input
type="radio"
id="data_access_shared"
ng-value="ctrl.ApplicationDataAccessPolicies.SHARED"
ng-model="ctrl.formValues.DataAccessPolicy"
ng-change="ctrl.resetDeploymentType()"
/>
<label for="data_access_shared">
<div class="boxselector_header">
<pr-icon icon="'box'" feather="true"></pr-icon>
Shared
</div>
<p>Application will be deployed as a Deployment with a shared storage access</p>
</label>
</div>
<div style="color: #767676" ng-if="ctrl.state.isEdit && ctrl.formValues.DataAccessPolicy === ctrl.ApplicationDataAccessPolicies.ISOLATED">
<input type="radio" id="data_access_shared" disabled />
<label
for="data_access_shared"
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
uib-tooltip="Changing the data access policy is not allowed"
style="cursor: pointer; border-color: #767676"
>
<div class="boxselector_header">
<pr-icon icon="'sliders'" feather="true"></pr-icon>
Shared
</div>
<p>Application will be deployed as a Deployment with a shared storage access</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -897,56 +899,58 @@
</div> </div>
<!-- deployment options --> <!-- deployment options -->
<div class="form-group" style="margin-bottom: 0"> <div class="form-group">
<div class="boxselector_wrapper !px-[15px]"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper">
<input <div>
type="radio" <input
id="deployment_replicated" type="radio"
ng-value="ctrl.ApplicationDeploymentTypes.REPLICATED" id="deployment_replicated"
ng-model="ctrl.formValues.DeploymentType" ng-value="ctrl.ApplicationDeploymentTypes.REPLICATED"
data-cy="k8sAppCreate-replicatedDeploymentButton" ng-model="ctrl.formValues.DeploymentType"
/> data-cy="k8sAppCreate-replicatedDeploymentButton"
<label for="deployment_replicated"> />
<div class="boxselector_header"> <label for="deployment_replicated">
<pr-icon icon="'sliders'" feather="true"></pr-icon> <div class="boxselector_header">
Replicated <pr-icon icon="'sliders'" feather="true"></pr-icon>
</div> Replicated
<p>Run one or multiple instances of this container</p> </div>
</label> <p>Run one or multiple instances of this container</p>
</div> </label>
<div ng-if="!ctrl.supportGlobalDeployment()"> </div>
<input type="radio" id="deployment_global" disabled /> <div ng-if="!ctrl.supportGlobalDeployment()">
<label <input type="radio" id="deployment_global" disabled />
for="deployment_global" <label
tooltip-append-to-body="true" for="deployment_global"
tooltip-placement="bottom" tooltip-append-to-body="true"
tooltip-class="portainer-tooltip" tooltip-placement="bottom"
uib-tooltip="The storage or access policy used for persisted folders cannot be used with this option" tooltip-class="portainer-tooltip"
> uib-tooltip="The storage or access policy used for persisted folders cannot be used with this option"
<div class="boxselector_header"> >
<pr-icon icon="'svg-cubes'"></pr-icon> <div class="boxselector_header">
Global <pr-icon icon="'svg-cubes'"></pr-icon>
</div> Global
<p>Application will be deployed as a DaemonSet with an instance on each node of the cluster</p> </div>
</label> <p>Application will be deployed as a DaemonSet with an instance on each node of the cluster</p>
</div> </label>
<div ng-if="ctrl.supportGlobalDeployment()"> </div>
<input <div ng-if="ctrl.supportGlobalDeployment()">
type="radio" <input
id="deployment_global" type="radio"
ng-value="ctrl.ApplicationDeploymentTypes.GLOBAL" id="deployment_global"
ng-model="ctrl.formValues.DeploymentType" ng-value="ctrl.ApplicationDeploymentTypes.GLOBAL"
ng-click="ctrl.unselectAutoScaler()" ng-model="ctrl.formValues.DeploymentType"
data-cy="k8sAppCreate-globalDeployButton" ng-click="ctrl.unselectAutoScaler()"
/> data-cy="k8sAppCreate-globalDeployButton"
<label for="deployment_global"> />
<div class="boxselector_header"> <label for="deployment_global">
<pr-icon icon="'svg-cubes'"></pr-icon> <div class="boxselector_header">
Global <pr-icon icon="'svg-cubes'"></pr-icon>
</div> Global
<p>Application will be deployed as a DaemonSet with an instance on each node of the sdfh</p> </div>
</label> <p>Application will be deployed as a DaemonSet with an instance on each node of the sdfh</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -1234,39 +1238,41 @@
</div> </div>
<!-- placement policy options --> <!-- placement policy options -->
<div class="form-group" style="margin-bottom: 0" ng-if="ctrl.formValues.Placements.length"> <div class="form-group" ng-if="ctrl.formValues.Placements.length">
<div class="boxselector_wrapper !px-[15px]"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper">
<input <div>
type="radio" <input
id="placement_hard" type="radio"
ng-value="ctrl.ApplicationPlacementTypes.MANDATORY" id="placement_hard"
ng-model="ctrl.formValues.PlacementType" ng-value="ctrl.ApplicationPlacementTypes.MANDATORY"
data-cy="k8sAppCreate-mandatoryPlacementButton" ng-model="ctrl.formValues.PlacementType"
/> data-cy="k8sAppCreate-mandatoryPlacementButton"
<label for="placement_hard"> />
<div class="boxselector_header"> <label for="placement_hard">
<pr-icon icon="'sliders'" feather="true"></pr-icon> <div class="boxselector_header">
Mandatory <pr-icon icon="'sliders'" feather="true"></pr-icon>
</div> Mandatory
<p>Schedule this application <b>ONLY</b> on nodes that match <b>ALL</b> Rules</p> </div>
</label> <p>Schedule this application <b>ONLY</b> on nodes that match <b>ALL</b> Rules</p>
</div> </label>
<div> </div>
<input <div>
type="radio" <input
id="placement_soft" type="radio"
ng-value="ctrl.ApplicationPlacementTypes.PREFERRED" id="placement_soft"
ng-model="ctrl.formValues.PlacementType" ng-value="ctrl.ApplicationPlacementTypes.PREFERRED"
data-cy="k8sAppCreate-prefferedPlacementButton" ng-model="ctrl.formValues.PlacementType"
/> data-cy="k8sAppCreate-prefferedPlacementButton"
<label for="placement_soft"> />
<div class="boxselector_header"> <label for="placement_soft">
<pr-icon icon="'align-justify'" feather="true"></pr-icon> <div class="boxselector_header">
Preferred <pr-icon icon="'align-justify'" feather="true"></pr-icon>
</div> Preferred
<p>Schedule this application on nodes that match the rules if possible</p> </div>
</label> <p>Schedule this application on nodes that match the rules if possible</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -87,27 +87,29 @@
</div> </div>
<!-- type options --> <!-- type options -->
<div class="form-group px-[15px]" style="margin-bottom: 0"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper">
<input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" /> <div>
<label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton"> <input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" />
<div class="boxselector_header"> <label for="type_basic" data-cy="k8sConfigCreate-nonSensitiveButton">
<pr-icon icon="'svg-filecode'"></pr-icon> <div class="boxselector_header">
ConfigMap <pr-icon icon="'svg-filecode'"></pr-icon>
</div> ConfigMap
<p>This configuration holds non-sensitive information</p> </div>
</label> <p>This configuration holds non-sensitive information</p>
</div> </label>
<div> </div>
<input type="radio" id="type_secret" ng-value="ctrl.KubernetesConfigurationTypes.SECRET" ng-model="ctrl.formValues.Type" /> <div>
<label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton"> <input type="radio" id="type_secret" ng-value="ctrl.KubernetesConfigurationTypes.SECRET" ng-model="ctrl.formValues.Type" />
<div class="boxselector_header"> <label for="type_secret" data-cy="k8sConfigCreate-sensitiveButton">
<pr-icon icon="'lock'" feather="true"></pr-icon> <div class="boxselector_header">
Secret <pr-icon icon="'lock'" feather="true"></pr-icon>
</div> Secret
<p>This configuration holds sensitive information</p> </div>
</label> <p>This configuration holds sensitive information</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -5,9 +5,10 @@ import uuidv4 from 'uuid/v4';
import PortainerError from '@/portainer/error'; import PortainerError from '@/portainer/error';
import { KubernetesDeployManifestTypes, KubernetesDeployBuildMethods, KubernetesDeployRequestMethods, RepositoryMechanismTypes } from 'Kubernetes/models/deploy'; import { KubernetesDeployManifestTypes, KubernetesDeployBuildMethods, KubernetesDeployRequestMethods, RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
import { buildOption } from '@/portainer/components/BoxSelector';
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils'; import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
import { isBE } from '@/portainer/feature-flags/feature-flags.service'; import { isBE } from '@/portainer/feature-flags/feature-flags.service';
import { compose, kubernetes } from '@@/BoxSelector/common-options/deployment-methods';
import { editor, git, template, url } from '@@/BoxSelector/common-options/build-methods';
class KubernetesDeployController { class KubernetesDeployController {
/* @ngInject */ /* @ngInject */
@ -27,15 +28,15 @@ class KubernetesDeployController {
this.isTemplateVariablesEnabled = isBE; this.isTemplateVariablesEnabled = isBE;
this.deployOptions = [ this.deployOptions = [
buildOption('method_kubernetes', 'svg-kubernetes', 'Kubernetes', 'Kubernetes manifest format', KubernetesDeployManifestTypes.KUBERNETES), { ...kubernetes, value: KubernetesDeployManifestTypes.KUBERNETES },
buildOption('method_compose', 'svg-dockercompose', 'Compose', 'Docker compose format', KubernetesDeployManifestTypes.COMPOSE), { ...compose, value: KubernetesDeployManifestTypes.COMPOSE },
]; ];
this.methodOptions = [ this.methodOptions = [
buildOption('method_repo', 'svg-git', 'Git Repository', 'Use a git repository', KubernetesDeployBuildMethods.GIT), { ...git, value: KubernetesDeployBuildMethods.GIT },
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', KubernetesDeployBuildMethods.WEB_EDITOR), { ...editor, value: KubernetesDeployBuildMethods.WEB_EDITOR },
buildOption('method_url', 'svg-url', 'URL', 'Specify a URL to a file', KubernetesDeployBuildMethods.URL), { ...url, value: KubernetesDeployBuildMethods.URL },
buildOption('method_template', 'svg-template', 'Custom Template', 'Use a custom template', KubernetesDeployBuildMethods.CUSTOM_TEMPLATE), { ...template, value: KubernetesDeployBuildMethods.CUSTOM_TEMPLATE },
]; ];
this.state = { this.state = {

View File

@ -47,16 +47,12 @@ export function EditDetails({
return ( return (
<> <>
<div className="form-group"> <BoxSelector
<div className="col-sm-12"> radioName={withNamespace('ownership')}
<BoxSelector value={values.ownership}
radioName={withNamespace('ownership')} options={options}
value={values.ownership} onChange={(ownership) => handleChangeOwnership(ownership)}
options={options} />
onChange={(ownership) => handleChangeOwnership(ownership)}
/>
</div>
</div>
{values.ownership === ResourceControlOwnership.RESTRICTED && ( {values.ownership === ResourceControlOwnership.RESTRICTED && (
<div aria-label="extra-options"> <div aria-label="extra-options">

View File

@ -6,6 +6,7 @@ import { ownershipIcon } from '@/portainer/filters/filters';
import { Team } from '@/portainer/teams/types'; import { Team } from '@/portainer/teams/types';
import { BoxSelectorOption } from '@@/BoxSelector/types'; import { BoxSelectorOption } from '@@/BoxSelector/types';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
import { ResourceControlOwnership } from '../types'; import { ResourceControlOwnership } from '../types';
@ -15,7 +16,7 @@ const publicOption: BoxSelectorOption<ResourceControlOwnership> = {
id: 'access_public', id: 'access_public',
description: description:
'I want any user with access to this environment to be able to manage this resource', 'I want any user with access to this environment to be able to manage this resource',
icon: ownershipIcon('public'), icon: <BadgeIcon icon={ownershipIcon('public')} />,
}; };
export function useOptions( export function useOptions(
@ -40,14 +41,14 @@ function adminOptions() {
return [ return [
buildOption( buildOption(
'access_administrators', 'access_administrators',
ownershipIcon('administrators'), <BadgeIcon icon={ownershipIcon('administrators')} />,
'Administrators', 'Administrators',
'I want to restrict the management of this resource to administrators only', 'I want to restrict the management of this resource to administrators only',
ResourceControlOwnership.ADMINISTRATORS ResourceControlOwnership.ADMINISTRATORS
), ),
buildOption( buildOption(
'access_restricted', 'access_restricted',
ownershipIcon('restricted'), <BadgeIcon icon={ownershipIcon('restricted')} />,
'Restricted', 'Restricted',
'I want to restrict the management of this resource to a set of users and/or teams', 'I want to restrict the management of this resource to a set of users and/or teams',
ResourceControlOwnership.RESTRICTED ResourceControlOwnership.RESTRICTED
@ -58,7 +59,7 @@ function nonAdminOptions(teams?: Team[]) {
return _.compact([ return _.compact([
buildOption( buildOption(
'access_private', 'access_private',
ownershipIcon('private'), <BadgeIcon icon={ownershipIcon('private')} />,
'Private', 'Private',
'I want to this resource to be manageable by myself only', 'I want to this resource to be manageable by myself only',
ResourceControlOwnership.PRIVATE ResourceControlOwnership.PRIVATE
@ -67,7 +68,7 @@ function nonAdminOptions(teams?: Team[]) {
teams.length > 0 && teams.length > 0 &&
buildOption( buildOption(
'access_restricted', 'access_restricted',
ownershipIcon('restricted'), <BadgeIcon icon={ownershipIcon('restricted')} />,
'Restricted', 'Restricted',
teams.length === 1 teams.length === 1
? `I want any member of my team (${teams[0].Name}) to be able to manage this resource` ? `I want any member of my team (${teams[0].Name}) to be able to manage this resource`

View File

@ -1,15 +1,16 @@
import { FeatureId } from '@/portainer/feature-flags/enums'; import { FeatureId } from '@/portainer/feature-flags/enums';
import { BoxSelectorOption } from '@@/BoxSelector/types'; import { BoxSelectorOption } from '@@/BoxSelector/types';
import { IconProps } from '@@/Icon';
export function buildOption<T extends number | string>( export function buildOption<T extends number | string>(
id: string, id: string,
icon: string, icon: IconProps['icon'],
label: string, label: string,
description: string, description: string,
value: T, value: T,
feature?: FeatureId, feature?: FeatureId,
featherIcon?: boolean featherIcon?: IconProps['featherIcon']
): BoxSelectorOption<T> { ): BoxSelectorOption<T> {
return { id, icon, label, description, value, feature, featherIcon }; return { id, icon, label, description, value, feature, featherIcon };
} }

View File

@ -15,51 +15,54 @@
</div> </div>
<!-- !access-control-switch --> <!-- !access-control-switch -->
<!-- restricted-access --> <!-- restricted-access -->
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled" style="margin-bottom: 0"> <div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled">
<div class="boxselector_wrapper px-[15px]"> <div class="col-sm-12">
<div ng-if="$ctrl.isAdmin"> <div class="boxselector_wrapper">
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" /> <div ng-if="$ctrl.isAdmin">
<label for="access_administrators" data-cy="portainer-selectAdminAccess"> <input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" />
<div class="boxselector_header"> <label for="access_administrators" data-cy="portainer-selectAdminAccess">
<pr-icon icon="'eye-off'" feather="true"></pr-icon> <div class="boxselector_header">
Administrators <pr-icon icon="'eye-off'" feather="true"></pr-icon>
</div> Administrators
<p class="boxselector_content">I want to restrict the management of this resource to administrators only</p> </div>
</label> <p class="boxselector_content">I want to restrict the management of this resource to administrators only</p>
</div> </label>
<div ng-if="$ctrl.isAdmin"> </div>
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" /> <div ng-if="$ctrl.isAdmin">
<label for="access_restricted" data-cy="portainer-selectRestrictedAccess"> <input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
<div class="boxselector_header"> <label for="access_restricted" data-cy="portainer-selectRestrictedAccess">
<pr-icon icon="'users'" feather="true"></pr-icon> <div class="boxselector_header">
Restricted <pr-icon icon="'users'" feather="true"></pr-icon>
</div> Restricted
<p class="boxselector_content"> I want to restrict the management of this resource to a set of users and/or teams </p> </div>
</label> <p class="boxselector_content"> I want to restrict the management of this resource to a set of users and/or teams </p>
</div> </label>
<div ng-if="!$ctrl.isAdmin"> </div>
<input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" /> <div ng-if="!$ctrl.isAdmin">
<label for="access_private"> <input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" />
<div class="boxselector_header"> <label for="access_private">
<i ng-class="'private' | ownershipicon" aria-hidden="true" style="margin-right: 2px"></i> <div class="boxselector_header">
Private <pr-icon icon="'eye-off'" feather="true"></pr-icon>
</div> Private
<p> I want to this resource to be manageable by myself only </p> </div>
</label> <p> I want to this resource to be manageable by myself only </p>
</div> </label>
<div ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 0"> </div>
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" /> <div ng-if="!$ctrl.isAdmin && $ctrl.availableTeams.length > 0">
<label for="access_restricted"> <input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
<div class="boxselector_header"> <label for="access_restricted">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px"></i> <div class="boxselector_header">
Restricted <pr-icon icon="'users'" feather="true"></pr-icon>
</div>
<p ng-if="$ctrl.availableTeams.length === 1"> Restricted
I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b </div>
>) to be able to manage this resource <p ng-if="$ctrl.availableTeams.length === 1">
</p> I want any member of my team (<b>{{ $ctrl.availableTeams[0].Name }}</b
<p ng-if="$ctrl.availableTeams.length > 1"> I want to restrict the management of this resource to one or more of my teams </p> >) to be able to manage this resource
</label> </p>
<p ng-if="$ctrl.availableTeams.length > 1"> I want to restrict the management of this resource to one or more of my teams </p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -24,47 +24,49 @@
</div> </div>
<div class="form-group"></div> <div class="form-group"></div>
<!-- endpoint-tls-mode --> <!-- endpoint-tls-mode -->
<div class="form-group" style="margin-bottom: 0" ng-if="$ctrl.formData.TLS"> <div class="form-group" ng-if="$ctrl.formData.TLS">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper">
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" /> <div>
<label for="tls_client_ca"> <input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" />
<div class="boxselector_header"> <label for="tls_client_ca">
<pr-icon icon="'shield'" feather="true"></pr-icon> <div class="boxselector_header">
TLS with server and client verification <pr-icon icon="'shield'" feather="true"></pr-icon>
</div> TLS with server and client verification
<p>Use client certificates and server verification</p> </div>
</label> <p>Use client certificates and server verification</p>
</div> </label>
<div> </div>
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca" /> <div>
<label for="tls_client_noca"> <input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca" />
<div class="boxselector_header"> <label for="tls_client_noca">
<pr-icon icon="'shield'" feather="true"></pr-icon> <div class="boxselector_header">
TLS with client verification only <pr-icon icon="'shield'" feather="true"></pr-icon>
</div> TLS with client verification only
<p>Use client certificates without server verification</p> </div>
</label> <p>Use client certificates without server verification</p>
</div> </label>
<div> </div>
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca" /> <div>
<label for="tls_ca"> <input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca" />
<div class="boxselector_header"> <label for="tls_ca">
<pr-icon icon="'shield'" feather="true"></pr-icon> <div class="boxselector_header">
TLS with server verification only <pr-icon icon="'shield'" feather="true"></pr-icon>
</div> TLS with server verification only
<p>Only verify the server certificate</p> </div>
</label> <p>Only verify the server certificate</p>
</div> </label>
<div> </div>
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only" /> <div>
<label for="tls_only"> <input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only" />
<div class="boxselector_header"> <label for="tls_only">
<pr-icon icon="'shield'" feather="true"></pr-icon> <div class="boxselector_header">
TLS only <pr-icon icon="'shield'" feather="true"></pr-icon>
</div> TLS only
<p>No server/client verification</p> </div>
</label> <p>No server/client verification</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,10 +2,8 @@
<rd-widget> <rd-widget>
<rd-widget-header icon="sliders" feather-icon="true" title-text="User theme"></rd-widget-header> <rd-widget-header icon="sliders" feather-icon="true" title-text="User theme"></rd-widget-header>
<rd-widget-body> <rd-widget-body>
<form class="theme-panel"> <form class="form-horizontal">
<!-- Theme Selector-->
<box-selector radio-name="'theme'" value="$ctrl.state.userTheme" options="$ctrl.state.availableThemes" on-change="($ctrl.setTheme)"></box-selector> <box-selector radio-name="'theme'" value="$ctrl.state.userTheme" options="$ctrl.state.availableThemes" on-change="($ctrl.setTheme)"></box-selector>
<!-- !Theme -->
</form> </form>
<p class="mt-2 vertical-center"> <p class="mt-2 vertical-center">
<pr-icon icon="'alert-circle'" class-name="'icon-primary'" feather="true"></pr-icon> <pr-icon icon="'alert-circle'" class-name="'icon-primary'" feather="true"></pr-icon>

View File

@ -2,6 +2,7 @@ import moment from 'moment';
import _ from 'lodash-es'; import _ from 'lodash-es';
import filesize from 'filesize'; import filesize from 'filesize';
import { Eye, EyeOff, Users } from 'react-feather';
import { ResourceControlOwnership as RCO } from '@/portainer/access-control/types'; import { ResourceControlOwnership as RCO } from '@/portainer/access-control/types';
export function truncateLeftRight(text, max, left, right) { export function truncateLeftRight(text, max, left, right) {
@ -106,13 +107,13 @@ export function environmentTypeIcon(type) {
export function ownershipIcon(ownership) { export function ownershipIcon(ownership) {
switch (ownership) { switch (ownership) {
case RCO.PRIVATE: case RCO.PRIVATE:
return 'eye-off'; return EyeOff;
case RCO.ADMINISTRATORS: case RCO.ADMINISTRATORS:
return 'eye-off'; return EyeOff;
case RCO.RESTRICTED: case RCO.RESTRICTED:
return 'users'; return Users;
default: default:
return 'eye'; return Eye;
} }
} }

View File

@ -1,8 +1,11 @@
import { Edit } from 'react-feather';
import { FeatureId } from '@/portainer/feature-flags/enums'; import { FeatureId } from '@/portainer/feature-flags/enums';
import Microsoft from '@/assets/ico/vendor/microsoft.svg?c'; import Microsoft from '@/assets/ico/vendor/microsoft.svg?c';
import Google from '@/assets/ico/vendor/google.svg?c'; import Google from '@/assets/ico/vendor/google.svg?c';
import Github from '@/assets/ico/vendor/github.svg?c'; import Github from '@/assets/ico/vendor/github.svg?c';
import Custom from '@/assets/ico/custom.svg?c';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
export const options = [ export const options = [
{ {
@ -32,7 +35,7 @@ export const options = [
}, },
{ {
id: 'custom', id: 'custom',
icon: Custom, icon: <BadgeIcon icon={Edit} />,
label: 'Custom', label: 'Custom',
description: 'Custom OAuth provider', description: 'Custom OAuth provider',
value: 'custom', value: 'custom',

View File

@ -15,6 +15,7 @@ import { TableColumnHeaderAngular } from '@@/datatables/TableHeaderCell';
import { DashboardItem } from '@@/DashboardItem'; import { DashboardItem } from '@@/DashboardItem';
import { SearchBar } from '@@/datatables/SearchBar'; import { SearchBar } from '@@/datatables/SearchBar';
import { FallbackImage } from '@@/FallbackImage'; import { FallbackImage } from '@@/FallbackImage';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
import { fileUploadField } from './file-upload-field'; import { fileUploadField } from './file-upload-field';
import { switchField } from './switch-field'; import { switchField } from './switch-field';
@ -83,4 +84,8 @@ export const componentsModule = angular
.component( .component(
'datatableSearchbar', 'datatableSearchbar',
r2a(SearchBar, ['data-cy', 'onChange', 'value', 'placeholder']) r2a(SearchBar, ['data-cy', 'onChange', 'value', 'placeholder'])
)
.component(
'boxSelectorBadgeIcon',
react2angular(BadgeIcon, ['featherIcon', 'icon'])
).name; ).name;

View File

@ -1,6 +1,9 @@
import { Edit } from 'react-feather';
import { FeatureId } from '@/portainer/feature-flags/enums'; import { FeatureId } from '@/portainer/feature-flags/enums';
import Openldap from '@/assets/ico/vendor/openldap.svg?c'; import Openldap from '@/assets/ico/vendor/openldap.svg?c';
import Custom from '@/assets/ico/custom.svg?c';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
const SERVER_TYPES = { const SERVER_TYPES = {
CUSTOM: 0, CUSTOM: 0,
@ -11,7 +14,7 @@ const SERVER_TYPES = {
export const options = [ export const options = [
{ {
id: 'ldap_custom', id: 'ldap_custom',
icon: Custom, icon: <BadgeIcon icon={Edit} />,
label: 'Custom', label: 'Custom',
value: SERVER_TYPES.CUSTOM, value: SERVER_TYPES.CUSTOM,
}, },

View File

@ -15,38 +15,39 @@
<!-- build-method --> <!-- build-method -->
<div ng-if="!$ctrl.state.fromStack"> <div ng-if="!$ctrl.state.fromStack">
<div class="col-sm-12 form-section-title"> Build method </div> <div class="col-sm-12 form-section-title"> Build method </div>
<div class="form-group"></div> <div class="form-group">
<div class="form-group mb-0"> <div class="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor" ng-change="$ctrl.onChangeMethod()" /> <input type="radio" id="method_editor" ng-model="$ctrl.state.Method" value="editor" ng-change="$ctrl.onChangeMethod()" />
<label for="method_editor"> <label for="method_editor">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'edit'" feather="true"></pr-icon> <pr-icon icon="'edit'" feather="true"></pr-icon>
Web editor Web editor
</div> </div>
<p>Use our Web editor</p> <p>Use our Web editor</p>
</label> </label>
</div> </div>
<div> <div>
<input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload" ng-change="$ctrl.onChangeMethod()" /> <input type="radio" id="method_upload" ng-model="$ctrl.state.Method" value="upload" ng-change="$ctrl.onChangeMethod()" />
<label for="method_upload"> <label for="method_upload">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'upload'" feather="true"></pr-icon> <pr-icon icon="'upload'" feather="true"></pr-icon>
Upload Upload
</div> </div>
<p>Upload from your computer</p> <p>Upload from your computer</p>
</label> </label>
</div> </div>
<div> <div>
<input type="radio" id="method_repository" ng-model="$ctrl.state.Method" value="repository" ng-change="$ctrl.onChangeMethod()" /> <input type="radio" id="method_repository" ng-model="$ctrl.state.Method" value="repository" ng-change="$ctrl.onChangeMethod()" />
<label for="method_repository"> <label for="method_repository">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'git-pull-request'" feather="true"></pr-icon> <pr-icon icon="'git-pull-request'" feather="true"></pr-icon>
Repository Repository
</div> </div>
<p>Use a git repository</p> <p>Use a git repository</p>
</label> </label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,18 +16,19 @@
<!-- !name-input --> <!-- !name-input -->
<!-- build-method --> <!-- build-method -->
<div class="col-sm-12 form-section-title"> Profile configuration </div> <div class="col-sm-12 form-section-title"> Profile configuration </div>
<div class="form-group"></div> <div class="form-group">
<div class="form-group" style="margin-bottom: 0"> <div class="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="method_editor" ng-model="state.method" value="editor" /> <input type="radio" id="method_editor" ng-model="state.method" value="editor" />
<label for="method_editor"> <label for="method_editor">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'edit'" feather="true"></pr-icon> <pr-icon icon="'edit'" feather="true"></pr-icon>
Web editor Web editor
</div> </div>
<p>Use our Web editor</p> <p>Use our Web editor</p>
</label> </label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -17,17 +17,19 @@
<!-- build-method --> <!-- build-method -->
<div class="col-sm-12 form-section-title"> Profile configuration </div> <div class="col-sm-12 form-section-title"> Profile configuration </div>
<div class="form-group"></div> <div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div> <div class="boxselector_wrapper">
<input type="radio" id="method_editor" ng-model="state.method" value="editor" /> <div>
<label for="method_editor"> <input type="radio" id="method_editor" ng-model="state.method" value="editor" />
<div class="boxselector_header"> <label for="method_editor">
<pr-icon icon="'edit'" feather="true"></pr-icon> <div class="boxselector_header">
Web editor <pr-icon icon="'edit'" feather="true"></pr-icon>
</div> Web editor
<p>Use our Web editor</p> </div>
</label> <p>Use our Web editor</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -138,29 +138,33 @@
</div> </div>
</div> </div>
<!-- !note --> <!-- !note -->
<div class="boxselector_wrapper"> <div class="form-group">
<div> <div class="col-sm-12">
<input type="radio" id="restore_file" checked="checked" /> <div class="boxselector_wrapper">
<label for="restore_file" data-cy="init-selectLocalFile"> <div>
<div class="boxselector_header"> <input type="radio" id="restore_file" checked="checked" />
<pr-icon icon="'upload'" feather="true"></pr-icon> <label for="restore_file" data-cy="init-selectLocalFile">
Upload backup file <div class="boxselector_header">
<pr-icon icon="'upload'" feather="true"></pr-icon>
Upload backup file
</div>
<p></p>
</label>
</div> </div>
<p></p> <div>
</label> <input type="radio" id="restore_s3" disabled />
</div> <label for="restore_s3" class="boxselector_disabled">
<div> <div class="boxselector_header">
<input type="radio" id="restore_s3" disabled /> <pr-icon icon="'download'" feather="true"></pr-icon>
<label for="restore_s3" class="boxselector_disabled"> Retrieve from S3
<div class="boxselector_header"> </div>
<pr-icon icon="'download'" feather="true"></pr-icon> <p
Retrieve from S3 >This feature is available in
<a class="hyperlink" href="https://www.portainer.io/business-upsell?from=restore-s3-form" target="_blank"> Portainer Business Edition</a></p
>
</label>
</div> </div>
<p </div>
>This feature is available in
<a class="hyperlink" href="https://www.portainer.io/business-upsell?from=restore-s3-form" target="_blank"> Portainer Business Edition</a></p
>
</label>
</div> </div>
</div> </div>
<!-- note --> <!-- note -->

View File

@ -21,17 +21,19 @@
</div> </div>
<!-- !note --> <!-- !note -->
<!-- environment-type --> <!-- environment-type -->
<div class="form-group" style="margin-bottom: 0"> <div class="form-group">
<div class="boxselector_wrapper"> <div class="col-sm-12">
<div ng-repeat="type in ctrl.endpointSections"> <div class="boxselector_wrapper">
<input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" /> <div ng-repeat="type in ctrl.endpointSections">
<label for="{{ type.Id }}"> <input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" />
<div class="boxselector_header"> <label for="{{ type.Id }}">
<i ng-class="type.Classes" aria-hidden="true" style="margin-right: 2px"></i> <div class="boxselector_header">
{{ type.Title }} <i ng-class="type.Classes" aria-hidden="true" style="margin-right: 2px"></i>
</div> {{ type.Title }}
<p>{{ type.Description }}</p> </div>
</label> <p>{{ type.Description }}</p>
</label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -7,11 +7,7 @@
<form class="form-horizontal"> <form class="form-horizontal">
<div class="col-sm-12 form-section-title"> Registry provider </div> <div class="col-sm-12 form-section-title"> Registry provider </div>
<div class="form-group"></div> <box-selector radio-name="'availableRegistry'" value="$ctrl.state.registryValue" options="$ctrl.state.availableRegistry" on-change="($ctrl.setRegistry)"></box-selector>
<div class="form-group" style="margin-bottom: 0">
<box-selector radio-name="'registry'" value="$ctrl.state.registryValue" options="$ctrl.state.availableRegistry" on-change="($ctrl.setRegistry)"></box-selector>
</div>
<registry-form-quay <registry-form-quay
ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.QUAY" ng-if="$ctrl.model.Type === $ctrl.RegistryTypes.QUAY"

View File

@ -1,10 +1,13 @@
import { Edit } from 'react-feather';
import Docker from '@/assets/ico/vendor/docker.svg?c'; import Docker from '@/assets/ico/vendor/docker.svg?c';
import Ecr from '@/assets/ico/vendor/ecr.svg?c'; import Ecr from '@/assets/ico/vendor/ecr.svg?c';
import Quay from '@/assets/ico/vendor/quay.svg?c'; import Quay from '@/assets/ico/vendor/quay.svg?c';
import Proget from '@/assets/ico/vendor/proget.svg?c'; import Proget from '@/assets/ico/vendor/proget.svg?c';
import Azure from '@/assets/ico/vendor/azure.svg?c'; import Azure from '@/assets/ico/vendor/azure.svg?c';
import Gitlab from '@/assets/ico/vendor/gitlab.svg?c'; import Gitlab from '@/assets/ico/vendor/gitlab.svg?c';
import Custom from '@/assets/ico/custom.svg?c';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
export const options = [ export const options = [
{ {
@ -51,7 +54,7 @@ export const options = [
}, },
{ {
id: 'registry_custom', id: 'registry_custom',
icon: Custom, icon: <BadgeIcon icon={Edit} />,
label: 'Custom registry', label: 'Custom registry',
description: 'Define your own registry', description: 'Define your own registry',
value: '3', value: '3',

View File

@ -1,12 +1,16 @@
import { ArrowDownCircle } from 'react-feather';
import { FeatureId } from '@/portainer/feature-flags/enums'; import { FeatureId } from '@/portainer/feature-flags/enums';
import Microsoft from '@/assets/ico/vendor/microsoft.svg?c'; import Microsoft from '@/assets/ico/vendor/microsoft.svg?c';
import Ldap from '@/assets/ico/ldap.svg?c'; import Ldap from '@/assets/ico/ldap.svg?c';
import Oauth from '@/assets/ico/oauth.svg?c'; import OAuth from '@/assets/ico/oauth.svg?c';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
export const options = [ export const options = [
{ {
id: 'auth_internal', id: 'auth_internal',
icon: 'svg-internal', icon: <BadgeIcon icon={ArrowDownCircle} />,
label: 'Internal', label: 'Internal',
description: 'Internal authentication mechanism', description: 'Internal authentication mechanism',
value: 1, value: 1,
@ -28,7 +32,7 @@ export const options = [
}, },
{ {
id: 'auth_oauth', id: 'auth_oauth',
icon: Oauth, icon: OAuth,
label: 'OAuth', label: 'OAuth',
description: 'OAuth authentication', description: 'OAuth authentication',
value: 3, value: 3,

View File

@ -29,6 +29,7 @@
</div> </div>
<div class="col-sm-12 form-section-title"> Authentication method </div> <div class="col-sm-12 form-section-title"> Authentication method </div>
<box-selector radio-name="'authOptions'" value="authMethod" options="authOptions" on-change="(onChangeAuthMethod)"></box-selector> <box-selector radio-name="'authOptions'" value="authMethod" options="authOptions" on-change="(onChangeAuthMethod)"></box-selector>
<internal-auth <internal-auth

View File

@ -1,16 +1,20 @@
import { DownloadCloud, UploadCloud } from 'react-feather';
import { FeatureId } from '@/portainer/feature-flags/enums'; import { FeatureId } from '@/portainer/feature-flags/enums';
import { BadgeIcon } from '@@/BoxSelector/BadgeIcon';
export const options = [ export const options = [
{ {
id: 'backup_file', id: 'backup_file',
icon: 'download', icon: <BadgeIcon icon={DownloadCloud} />,
featherIcon: true, featherIcon: true,
label: 'Download backup file', label: 'Download backup file',
value: 'file', value: 'file',
}, },
{ {
id: 'backup_s3', id: 'backup_s3',
icon: 'upload', icon: <BadgeIcon icon={UploadCloud} />,
featherIcon: true, featherIcon: true,
label: 'Store in S3', label: 'Store in S3',
description: 'Define a cron schedule', description: 'Define a cron schedule',

View File

@ -209,7 +209,9 @@
<rd-widget-body> <rd-widget-body>
<form class="form-horizontal" ng-submit="backupPortainer()" name="backupPortainerForm"> <form class="form-horizontal" ng-submit="backupPortainer()" name="backupPortainerForm">
<div class="col-sm-12 form-section-title"> Backup configuration </div> <div class="col-sm-12 form-section-title"> Backup configuration </div>
<box-selector options="backupOptions" value="formValues.backupFormType" on-change="(onBackupOptionsChange)" radio-name="'backupOptions'"></box-selector> <box-selector options="backupOptions" value="formValues.backupFormType" on-change="(onBackupOptionsChange)" radio-name="'backupOptions'"></box-selector>
<div ng-if="formValues.backupFormType === BACKUP_FORM_TYPES.S3"> <div ng-if="formValues.backupFormType === BACKUP_FORM_TYPES.S3">
<!-- Schedule automatic backups --> <!-- Schedule automatic backups -->
<div class="form-group mt-3"> <div class="form-group mt-3">

View File

@ -49,48 +49,49 @@
</div> </div>
<!-- build-method --> <!-- build-method -->
<div class="col-sm-12 form-section-title"> Build method </div> <div class="col-sm-12 form-section-title"> Build method </div>
<div class="form-group"></div> <div class="form-group">
<div class="form-group" style="margin-bottom: 0"> <div class="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="method_editor" ng-model="state.Method" value="editor" /> <input type="radio" id="method_editor" ng-model="state.Method" value="editor" />
<label for="method_editor"> <label for="method_editor">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'edit'" feather="true"></pr-icon> <pr-icon icon="'edit'" feather="true"></pr-icon>
Web editor Web editor
</div> </div>
<p>Use our Web editor</p> <p>Use our Web editor</p>
</label> </label>
</div> </div>
<div> <div>
<input type="radio" id="method_upload" ng-model="state.Method" value="upload" /> <input type="radio" id="method_upload" ng-model="state.Method" value="upload" />
<label for="method_upload"> <label for="method_upload">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'upload'" feather="true"></pr-icon> <pr-icon icon="'upload'" feather="true"></pr-icon>
Upload Upload
</div> </div>
<p>Upload from your computer</p> <p>Upload from your computer</p>
</label> </label>
</div> </div>
<div> <div>
<input type="radio" id="method_repository" ng-model="state.Method" value="repository" /> <input type="radio" id="method_repository" ng-model="state.Method" value="repository" />
<label for="method_repository"> <label for="method_repository">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'git-pull-request'" feather="true"></pr-icon> <pr-icon icon="'git-pull-request'" feather="true"></pr-icon>
Repository Repository
</div> </div>
<p>Use a git repository</p> <p>Use a git repository</p>
</label> </label>
</div> </div>
<div> <div>
<input type="radio" id="method_template" ng-model="state.Method" value="template" /> <input type="radio" id="method_template" ng-model="state.Method" value="template" />
<label for="method_template"> <label for="method_template">
<div class="boxselector_header"> <div class="boxselector_header">
<pr-icon icon="'edit'" feather="true"></pr-icon> <pr-icon icon="'edit'" feather="true"></pr-icon>
Custom template Custom template
</div> </div>
<p>Use a custom template</p> <p>Use a custom template</p>
</label> </label>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,19 @@
import { Icon, IconProps } from '@@/Icon';
type Props = IconProps;
export function BadgeIcon({ icon, featherIcon }: Props) {
return (
<div
className={`badge-icon
text-3xl h-14 w-14
bg-blue-3 text-blue-8
th-dark:bg-gray-9 th-dark:text-blue-3
rounded-full
inline-flex items-center justify-center
`}
>
<Icon icon={icon} feather={featherIcon} className="feather !flex" />
</div>
);
}

View File

@ -19,20 +19,27 @@ export function BoxSelector<T extends number | string>({
onChange, onChange,
}: Props<T>) { }: Props<T>) {
return ( return (
<div className={clsx('boxselector_wrapper', styles.root)} role="radiogroup"> <div className="form-group">
{options.map((option) => <div className="col-sm-12">
option.hide ? null : ( <div
<BoxSelectorItem className={clsx('boxselector_wrapper', styles.root)}
key={option.id} role="radiogroup"
radioName={radioName} >
option={option} {options
onChange={onChange} .filter((option) => !option.hide)
selectedValue={value} .map((option) => (
disabled={option.disabled && option.disabled()} <BoxSelectorItem
tooltip={option.tooltip && option.tooltip()} key={option.id}
/> radioName={radioName}
) option={option}
)} onChange={onChange}
selectedValue={value}
disabled={option.disabled && option.disabled()}
tooltip={option.tooltip && option.tooltip()}
/>
))}
</div>
</div>
</div> </div>
); );
} }

View File

@ -11,7 +11,6 @@
font-weight: bold; font-weight: bold;
user-select: none; user-select: none;
color: var(--text-boxselector-header); color: var(--text-boxselector-header);
padding: 0px 10px;
} }
.boxselector_header .fa, .boxselector_header .fa,
@ -46,6 +45,8 @@
/* not disabled */ /* not disabled */
.boxselector_wrapper input[type='radio']:not(:disabled) ~ label, .boxselector_wrapper input[type='radio']:not(:disabled) ~ label,
.box-selector-item input[type='radio']:not(:disabled) ~ label { .box-selector-item input[type='radio']:not(:disabled) ~ label {
background-color: var(--bg-boxselector-color);
box-shadow: none; box-shadow: none;
cursor: pointer; cursor: pointer;
} }
@ -70,7 +71,7 @@
/* checked */ /* checked */
.boxselector_wrapper input[type='radio']:checked + label, .boxselector_wrapper input[type='radio']:checked + label,
.box-selector-item input[type='radio']:checked + label { .box-selector-item input[type='radio']:checked + label {
@apply bg-blue-3 border-blue-6; @apply bg-blue-2 border-blue-6;
@apply th-dark:bg-blue-10 th-dark:border-blue-7; @apply th-dark:bg-blue-10 th-dark:border-blue-7;
background-image: url(../../../assets/ico/checked.svg); background-image: url(../../../assets/ico/checked.svg);
@ -106,7 +107,10 @@
.boxselector_icon, .boxselector_icon,
.boxselector_icon img { .boxselector_icon img {
font-size: 90px; font-size: 90px;
display: block; }
.boxselector_icon > svg {
margin-left: -5px;
} }
.boxselector_header pr-icon { .boxselector_header pr-icon {
@ -117,11 +121,6 @@
padding-left: 20px; padding-left: 20px;
} }
.boxselector_wrapper input[type='radio']:not(:disabled) ~ label,
.box-selector-item input[type='radio']:not(:disabled) ~ label {
background-color: var(--bg-boxselector-color);
}
.boxselector_img_container { .boxselector_img_container {
line-height: 90px; line-height: 90px;
margin-bottom: 0; margin-bottom: 0;

View File

@ -52,7 +52,7 @@ export function BoxSelectorItem<T extends number | string>({
<Icon <Icon
icon={option.icon} icon={option.icon}
feather={option.featherIcon} feather={option.featherIcon}
className="boxselector_icon space-right" className="boxselector_icon !flex items-center"
/> />
)} )}
</div> </div>

View File

@ -0,0 +1,16 @@
import { Icon, IconProps } from '@@/Icon';
type Props = IconProps;
export function LogoIcon({ icon, featherIcon }: Props) {
return (
<div
className={`
text-6xl h-14 w-14
inline-flex items-center justify-center
`}
>
<Icon icon={icon} feather={featherIcon} className="feather !flex" />
</div>
);
}

View File

@ -0,0 +1,44 @@
import { Edit, FileText, Globe, Upload } from 'react-feather';
import GitIcon from '@/assets/ico/git.svg?c';
import { BadgeIcon } from '../BadgeIcon';
import { BoxSelectorOption } from '../types';
export const editor: BoxSelectorOption<'editor'> = {
id: 'method_editor',
icon: <BadgeIcon icon={Edit} />,
label: 'Web editor',
description: 'Use our Web editor',
value: 'editor',
};
export const upload: BoxSelectorOption<'upload'> = {
id: 'method_upload',
icon: <BadgeIcon icon={Upload} />,
label: 'Upload',
description: 'Upload from your computer',
value: 'upload',
};
export const git: BoxSelectorOption<'repository'> = {
id: 'method_repository',
icon: <GitIcon />,
label: 'Repository',
description: 'Use a git repository',
value: 'repository',
};
export const template: BoxSelectorOption<'template'> = {
id: 'method_template',
icon: <BadgeIcon icon={FileText} />,
label: 'Template',
description: 'Use an Edge stack template',
value: 'template',
};
export const url: BoxSelectorOption<'url'> = {
id: 'method_url',
icon: <BadgeIcon icon={Globe} />,
label: 'URL',
description: 'Specify a URL to a file',
value: 'url',
};

View File

@ -0,0 +1,20 @@
import Kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
import DockerCompose from '@/assets/ico/vendor/docker-compose.svg?c';
import { BoxSelectorOption } from '../types';
export const kubernetes: BoxSelectorOption<'kubernetes'> = {
id: 'method_kubernetes',
icon: Kubernetes,
label: 'Kubernetes',
description: 'Kubernetes manifest format',
value: 'kubernetes',
};
export const compose: BoxSelectorOption<'compose'> = {
id: 'method_compose',
icon: DockerCompose,
label: 'Compose',
description: 'docker-compose format',
value: 'compose',
};

View File

@ -67,6 +67,9 @@ export function Icon({ icon, feather, className, mode, size }: Props) {
.map((s) => s.slice(0, 1).toUpperCase() + s.slice(1)) .map((s) => s.slice(0, 1).toUpperCase() + s.slice(1))
.join('') as keyof typeof featherIcons; .join('') as keyof typeof featherIcons;
const IconComponent = featherIcons[iconName]; const IconComponent = featherIcons[iconName];
if (!IconComponent) {
throw new Error(`Feather icon not found: ${iconName}`);
}
return <IconComponent className={classes} />; return <IconComponent className={classes} />;
} }

View File

@ -60,7 +60,7 @@ import azure from '@/assets/ico/vendor/azure.svg?c';
import civo from '@/assets/ico/vendor/civo.svg?c'; import civo from '@/assets/ico/vendor/civo.svg?c';
import digitalocean from '@/assets/ico/vendor/digitalocean.svg?c'; import digitalocean from '@/assets/ico/vendor/digitalocean.svg?c';
import docker from '@/assets/ico/vendor/docker.svg?c'; import docker from '@/assets/ico/vendor/docker.svg?c';
import dockercompose from '@/assets/ico/vendor/dockercompose.svg?c'; import dockercompose from '@/assets/ico/vendor/docker-compose.svg?c';
import ecr from '@/assets/ico/vendor/ecr.svg?c'; import ecr from '@/assets/ico/vendor/ecr.svg?c';
import github from '@/assets/ico/vendor/github.svg?c'; import github from '@/assets/ico/vendor/github.svg?c';
import gitlab from '@/assets/ico/vendor/gitlab.svg?c'; import gitlab from '@/assets/ico/vendor/gitlab.svg?c';

View File

@ -60,16 +60,12 @@ export function WizardDocker({ onCreate }: Props) {
return ( return (
<div className="form-horizontal"> <div className="form-horizontal">
<div className="form-group"> <BoxSelector
<div className="col-sm-12"> onChange={(v) => setCreationType(v)}
<BoxSelector options={options}
onChange={(v) => setCreationType(v)} value={creationType}
options={options} radioName="creation-type"
value={creationType} />
radioName="creation-type"
/>
</div>
</div>
{tab} {tab}
</div> </div>