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"
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> </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,7 +76,8 @@
<!-- 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="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" /> <input type="radio" id="method_editor" ng-model="state.BuildType" value="editor" ng-click="toggleEditor()" />
@ -110,6 +111,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !build-method --> <!-- !build-method -->
<!-- web-editor --> <!-- web-editor -->
<div ng-show="state.BuildType === 'editor'"> <div ng-show="state.BuildType === 'editor'">

View File

@ -34,7 +34,8 @@
<!-- 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="col-sm-12">
<div class="boxselector_wrapper !mt-0"> <div class="boxselector_wrapper !mt-0">
<div> <div>
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" /> <input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" />
@ -58,6 +59,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !edge-job-method-select --> <!-- !edge-job-method-select -->
<!-- basic-edge-job --> <!-- basic-edge-job -->
<div ng-if="$ctrl.formValues.cronMethod === 'basic'"> <div ng-if="$ctrl.formValues.cronMethod === 'basic'">
@ -151,9 +153,9 @@
<!-- 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">
@ -177,6 +179,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !execution-method --> <!-- !execution-method -->
<!-- web-editor --> <!-- web-editor -->
<div ng-show="$ctrl.formValues.method === 'editor'"> <div ng-show="$ctrl.formValues.method === 'editor'">

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,7 +26,8 @@
</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="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div class="boxselector"> <div class="boxselector">
<input type="radio" id="static-group" ng-model="$ctrl.model.Dynamic" ng-value="false" ng-checked="!$ctrl.model.Dynamic" /> <input type="radio" id="static-group" ng-model="$ctrl.model.Dynamic" ng-value="false" ng-checked="!$ctrl.model.Dynamic" />
@ -42,7 +43,7 @@
<input type="radio" id="dynamic-group" ng-model="$ctrl.model.Dynamic" ng-value="true" ng-checked="$ctrl.model.Dynamic" /> <input type="radio" id="dynamic-group" ng-model="$ctrl.model.Dynamic" ng-value="true" ng-checked="$ctrl.model.Dynamic" />
<label for="dynamic-group"> <label for="dynamic-group">
<div class="boxselector_header vertical-center"> <div class="boxselector_header vertical-center">
<pr-icon icon="'tag'" feather="true" className="'feather'"></pr-icon> <pr-icon icon="'tag'" feather="true"></pr-icon>
Dynamic Dynamic
</div> </div>
<p>Automatically associate environments via tags</p> <p>Automatically associate environments via tags</p>
@ -50,6 +51,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- StaticGroup --> <!-- StaticGroup -->
<div ng-if="!$ctrl.model.Dynamic"> <div ng-if="!$ctrl.model.Dynamic">
@ -78,7 +80,8 @@
<!-- 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="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div class="boxselector"> <div class="boxselector">
<input type="radio" id="or-selector" ng-model="$ctrl.model.PartialMatch" ng-value="true" ng-checked="$ctrl.model.PartialMatch" /> <input type="radio" id="or-selector" ng-model="$ctrl.model.PartialMatch" ng-value="true" ng-checked="$ctrl.model.PartialMatch" />
@ -94,7 +97,7 @@
<input type="radio" id="and-selector" ng-model="$ctrl.model.PartialMatch" ng-value="false" ng-checked="!$ctrl.model.PartialMatch" /> <input type="radio" id="and-selector" ng-model="$ctrl.model.PartialMatch" ng-value="false" ng-checked="!$ctrl.model.PartialMatch" />
<label for="and-selector"> <label for="and-selector">
<div class="boxselector_header vertical-center"> <div class="boxselector_header vertical-center">
<pr-icon icon="'tag'" feather="true" className="'feather'"></pr-icon> <pr-icon icon="'tag'" feather="true"></pr-icon>
Full match Full match
</div> </div>
<p>Associate any environment matching all of the selected tags</p> <p>Associate any environment matching all of the selected tags</p>
@ -102,6 +105,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<tag-selector ng-if="$ctrl.model.TagIds" value="$ctrl.model.TagIds" on-change="($ctrl.onChangeTags)"> </tag-selector> <tag-selector ng-if="$ctrl.model.TagIds" value="$ctrl.model.TagIds" on-change="($ctrl.onChangeTags)"> </tag-selector>

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,8 +709,9 @@
</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 class="boxselector_wrapper">
<div <div
ng-if=" ng-if="
(!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) || (!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) ||
@ -789,6 +790,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !access policy options --> <!-- !access policy options -->
</div> </div>
<!-- #endregion --> <!-- #endregion -->
@ -897,8 +899,9 @@
</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 class="boxselector_wrapper">
<div> <div>
<input <input
type="radio" type="radio"
@ -950,6 +953,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !deployment options --> <!-- !deployment options -->
<!-- replica count --> <!-- replica count -->
@ -1234,8 +1238,9 @@
</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 class="boxselector_wrapper">
<div> <div>
<input <input
type="radio" type="radio"
@ -1270,6 +1275,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !placement policy options --> <!-- !placement policy options -->
</div> </div>
<!-- #endregion --> <!-- #endregion -->

View File

@ -87,7 +87,8 @@
</div> </div>
<!-- type options --> <!-- type options -->
<div class="form-group px-[15px]" style="margin-bottom: 0"> <div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" /> <input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" />
@ -111,6 +112,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !type options --> <!-- !type options -->
<div class="col-sm-12 form-section-title" ng-if="ctrl.formValues.Type == ctrl.KubernetesConfigurationTypes.SECRET"> Information </div> <div class="col-sm-12 form-section-title" ng-if="ctrl.formValues.Type == ctrl.KubernetesConfigurationTypes.SECRET"> Information </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">
<div className="col-sm-12">
<BoxSelector <BoxSelector
radioName={withNamespace('ownership')} radioName={withNamespace('ownership')}
value={values.ownership} value={values.ownership}
options={options} options={options}
onChange={(ownership) => handleChangeOwnership(ownership)} 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,8 +15,9 @@
</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 class="boxselector_wrapper">
<div ng-if="$ctrl.isAdmin"> <div ng-if="$ctrl.isAdmin">
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" /> <input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" />
<label for="access_administrators" data-cy="portainer-selectAdminAccess"> <label for="access_administrators" data-cy="portainer-selectAdminAccess">
@ -41,7 +42,7 @@
<input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" /> <input type="radio" id="access_private" ng-model="$ctrl.formData.Ownership" value="private" />
<label for="access_private"> <label for="access_private">
<div class="boxselector_header"> <div class="boxselector_header">
<i ng-class="'private' | ownershipicon" aria-hidden="true" style="margin-right: 2px"></i> <pr-icon icon="'eye-off'" feather="true"></pr-icon>
Private Private
</div> </div>
<p> I want to this resource to be manageable by myself only </p> <p> I want to this resource to be manageable by myself only </p>
@ -51,7 +52,8 @@
<input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" /> <input type="radio" id="access_restricted" ng-model="$ctrl.formData.Ownership" value="restricted" />
<label for="access_restricted"> <label for="access_restricted">
<div class="boxselector_header"> <div class="boxselector_header">
<i ng-class="'restricted' | ownershipicon" aria-hidden="true" style="margin-right: 2px"></i> <pr-icon icon="'users'" feather="true"></pr-icon>
Restricted Restricted
</div> </div>
<p ng-if="$ctrl.availableTeams.length === 1"> <p ng-if="$ctrl.availableTeams.length === 1">
@ -63,6 +65,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- restricted-access --> <!-- restricted-access -->
<!-- authorized-teams --> <!-- authorized-teams -->
<div <div

View File

@ -24,7 +24,8 @@
</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="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" /> <input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" />
@ -68,6 +69,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !endpoint-tls-mode --> <!-- !endpoint-tls-mode -->
<div class="col-sm-12 form-section-title" ng-if="$ctrl.formData.TLS && $ctrl.formData.TLSMode !== 'tls_only'"> Required TLS files </div> <div class="col-sm-12 form-section-title" ng-if="$ctrl.formData.TLS && $ctrl.formData.TLSMode !== 'tls_only'"> Required TLS files </div>
<!-- tls-file-upload --> <!-- tls-file-upload -->

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,8 +15,8 @@
<!-- 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()" />
@ -51,6 +51,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !build-method --> <!-- !build-method -->
<!-- web-editor --> <!-- web-editor -->
<web-editor-form <web-editor-form

View File

@ -16,8 +16,8 @@
<!-- !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" />
@ -31,6 +31,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !build-method --> <!-- !build-method -->
<web-editor-form <web-editor-form

View File

@ -17,7 +17,8 @@
<!-- 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="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" />
@ -31,6 +32,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !build-method --> <!-- !build-method -->
<web-editor-form <web-editor-form

View File

@ -138,6 +138,8 @@
</div> </div>
</div> </div>
<!-- !note --> <!-- !note -->
<div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div> <div>
<input type="radio" id="restore_file" checked="checked" /> <input type="radio" id="restore_file" checked="checked" />
@ -163,6 +165,8 @@
</label> </label>
</div> </div>
</div> </div>
</div>
</div>
<!-- note --> <!-- note -->
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-sm-12">

View File

@ -21,7 +21,8 @@
</div> </div>
<!-- !note --> <!-- !note -->
<!-- environment-type --> <!-- environment-type -->
<div class="form-group" style="margin-bottom: 0"> <div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper"> <div class="boxselector_wrapper">
<div ng-repeat="type in ctrl.endpointSections"> <div ng-repeat="type in ctrl.endpointSections">
<input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" /> <input type="radio" id="{{ type.Id }}" ng-model="ctrl.formValues.ConnectionType" ng-value="type.Value" />
@ -35,6 +36,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !environment-type --> <!-- !environment-type -->
<!-- environment-type-details --> <!-- environment-type-details -->
<div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.DOCKER_LOCAL"> <div ng-if="ctrl.formValues.ConnectionType === ctrl.PortainerEndpointConnectionTypes.DOCKER_LOCAL">

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,8 +49,8 @@
</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" />
@ -94,6 +94,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- !build-method --> <!-- !build-method -->
<!-- upload --> <!-- upload -->

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,9 +19,15 @@ 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
className={clsx('boxselector_wrapper', styles.root)}
role="radiogroup"
>
{options
.filter((option) => !option.hide)
.map((option) => (
<BoxSelectorItem <BoxSelectorItem
key={option.id} key={option.id}
radioName={radioName} radioName={radioName}
@ -31,8 +37,9 @@ export function BoxSelector<T extends number | string>({
disabled={option.disabled && option.disabled()} disabled={option.disabled && option.disabled()}
tooltip={option.tooltip && option.tooltip()} 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">
<div className="col-sm-12">
<BoxSelector <BoxSelector
onChange={(v) => setCreationType(v)} onChange={(v) => setCreationType(v)}
options={options} options={options}
value={creationType} value={creationType}
radioName="creation-type" radioName="creation-type"
/> />
</div>
</div>
{tab} {tab}
</div> </div>