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">
<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 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"/>
<svg width="auto" height="auto" viewBox="0 0 36 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
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>

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

View File

@ -34,7 +34,8 @@
<!-- edge-job-method-select -->
<div class="col-sm-12 form-section-title"> Edge job configuration </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>
<input type="radio" id="config_basic" ng-model="$ctrl.formValues.cronMethod" value="basic" />
@ -58,6 +59,7 @@
</div>
</div>
</div>
</div>
<!-- !edge-job-method-select -->
<!-- basic-edge-job -->
<div ng-if="$ctrl.formValues.cronMethod === 'basic'">
@ -151,9 +153,9 @@
<!-- execution-method -->
<div ng-if="!$ctrl.model.Id">
<div class="col-sm-12 form-section-title"> Job content </div>
<div class="form-group"></div>
<div class="form-group px-4">
<div class="boxselector_wrapper !mt-0">
<div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="method_editor" ng-model="$ctrl.formValues.method" value="editor" />
<label for="method_editor">
@ -177,6 +179,7 @@
</div>
</div>
</div>
</div>
<!-- !execution-method -->
<!-- web-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 {
/* @ngInject */
constructor() {
this.deploymentOptions = [
{ id: 'deployment_compose', icon: 'fab fa-docker', label: 'Compose', description: 'Docker compose format', value: 0 },
{
id: 'deployment_kube',
icon: 'fa fa-cubes',
label: 'Kubernetes',
description: 'Kubernetes manifest format',
...compose,
value: 0,
},
{
...kubernetes,
value: 1,
disabled: () => {
return this.hasDockerEndpoint();

View File

@ -26,7 +26,8 @@
</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">
<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" />
<label for="dynamic-group">
<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
</div>
<p>Automatically associate environments via tags</p>
@ -50,6 +51,7 @@
</div>
</div>
</div>
</div>
<!-- StaticGroup -->
<div ng-if="!$ctrl.model.Dynamic">
@ -78,7 +80,8 @@
<!-- DynamicGroup -->
<div ng-if="$ctrl.model.Dynamic">
<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">
<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" />
<label for="and-selector">
<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
</div>
<p>Associate any environment matching all of the selected tags</p>
@ -102,6 +105,7 @@
</div>
</div>
</div>
</div>
<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 {
/* @ngInject */
constructor($async, EdgeTemplateService, Notifications) {
Object.assign(this, { $async, EdgeTemplateService, Notifications });
this.methodOptions = [
{ 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.methodOptions = [editor, upload, git, template];
this.selectedTemplate = null;

View File

@ -1,13 +1,11 @@
import { editor, git, upload } from '@@/BoxSelector/common-options/build-methods';
class KubeManifestFormController {
/* @ngInject */
constructor($async) {
Object.assign(this, { $async });
this.methodOptions = [
{ 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.methodOptions = [editor, upload, git];
this.onChangeFileContent = this.onChangeFileContent.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 { getTemplateVariables, intersectVariables } from '@/react/portainer/custom-templates/components/utils';
import { isBE } from '@/portainer/feature-flags/feature-flags.service';
import { editor, upload } from '@@/BoxSelector/common-options/build-methods';
class KubeCreateCustomTemplateViewController {
/* @ngInject */
constructor($async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService) {
Object.assign(this, { $async, $state, Authentication, CustomTemplateService, FormValidator, ModalService, Notifications, ResourceControlService });
this.methodOptions = [
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', 'editor'),
buildOption('method_upload', 'svg-upload', 'Upload', 'Upload from your computer', 'upload'),
];
this.methodOptions = [editor, upload];
this.templates = null;
this.isTemplateVariablesEnabled = isBE;

View File

@ -709,8 +709,9 @@
</div>
<!-- access policy options -->
<div class="form-group" style="margin-bottom: 0">
<div class="boxselector_wrapper !px-[15px]">
<div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper">
<div
ng-if="
(!ctrl.state.isEdit && !ctrl.state.persistedFoldersUseExistingVolumes) ||
@ -789,6 +790,7 @@
</div>
</div>
</div>
</div>
<!-- !access policy options -->
</div>
<!-- #endregion -->
@ -897,8 +899,9 @@
</div>
<!-- deployment options -->
<div class="form-group" style="margin-bottom: 0">
<div class="boxselector_wrapper !px-[15px]">
<div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper">
<div>
<input
type="radio"
@ -950,6 +953,7 @@
</div>
</div>
</div>
</div>
<!-- !deployment options -->
<!-- replica count -->
@ -1234,8 +1238,9 @@
</div>
<!-- placement policy options -->
<div class="form-group" style="margin-bottom: 0" ng-if="ctrl.formValues.Placements.length">
<div class="boxselector_wrapper !px-[15px]">
<div class="form-group" ng-if="ctrl.formValues.Placements.length">
<div class="col-sm-12">
<div class="boxselector_wrapper">
<div>
<input
type="radio"
@ -1270,6 +1275,7 @@
</div>
</div>
</div>
</div>
<!-- !placement policy options -->
</div>
<!-- #endregion -->

View File

@ -87,7 +87,8 @@
</div>
<!-- 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>
<input type="radio" id="type_basic" ng-value="ctrl.KubernetesConfigurationTypes.CONFIGMAP" ng-model="ctrl.formValues.Type" />
@ -111,6 +112,7 @@
</div>
</div>
</div>
</div>
<!-- !type options -->
<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 { KubernetesDeployManifestTypes, KubernetesDeployBuildMethods, KubernetesDeployRequestMethods, RepositoryMechanismTypes } from 'Kubernetes/models/deploy';
import { buildOption } from '@/portainer/components/BoxSelector';
import { renderTemplate } from '@/react/portainer/custom-templates/components/utils';
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 {
/* @ngInject */
@ -27,15 +28,15 @@ class KubernetesDeployController {
this.isTemplateVariablesEnabled = isBE;
this.deployOptions = [
buildOption('method_kubernetes', 'svg-kubernetes', 'Kubernetes', 'Kubernetes manifest format', KubernetesDeployManifestTypes.KUBERNETES),
buildOption('method_compose', 'svg-dockercompose', 'Compose', 'Docker compose format', KubernetesDeployManifestTypes.COMPOSE),
{ ...kubernetes, value: KubernetesDeployManifestTypes.KUBERNETES },
{ ...compose, value: KubernetesDeployManifestTypes.COMPOSE },
];
this.methodOptions = [
buildOption('method_repo', 'svg-git', 'Git Repository', 'Use a git repository', KubernetesDeployBuildMethods.GIT),
buildOption('method_editor', 'svg-custom', 'Web editor', 'Use our Web editor', KubernetesDeployBuildMethods.WEB_EDITOR),
buildOption('method_url', 'svg-url', 'URL', 'Specify a URL to a file', KubernetesDeployBuildMethods.URL),
buildOption('method_template', 'svg-template', 'Custom Template', 'Use a custom template', KubernetesDeployBuildMethods.CUSTOM_TEMPLATE),
{ ...git, value: KubernetesDeployBuildMethods.GIT },
{ ...editor, value: KubernetesDeployBuildMethods.WEB_EDITOR },
{ ...url, value: KubernetesDeployBuildMethods.URL },
{ ...template, value: KubernetesDeployBuildMethods.CUSTOM_TEMPLATE },
];
this.state = {

View File

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

View File

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

View File

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

View File

@ -15,8 +15,9 @@
</div>
<!-- !access-control-switch -->
<!-- restricted-access -->
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled" style="margin-bottom: 0">
<div class="boxselector_wrapper px-[15px]">
<div class="form-group" ng-if="$ctrl.formData.AccessControlEnabled">
<div class="col-sm-12">
<div class="boxselector_wrapper">
<div ng-if="$ctrl.isAdmin">
<input type="radio" id="access_administrators" ng-model="$ctrl.formData.Ownership" value="administrators" />
<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" />
<label for="access_private">
<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
</div>
<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" />
<label for="access_restricted">
<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
</div>
<p ng-if="$ctrl.availableTeams.length === 1">
@ -63,6 +65,7 @@
</div>
</div>
</div>
</div>
<!-- restricted-access -->
<!-- authorized-teams -->
<div

View File

@ -24,7 +24,8 @@
</div>
<div class="form-group"></div>
<!-- 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>
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca" />
@ -68,6 +69,7 @@
</div>
</div>
</div>
</div>
<!-- !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>
<!-- tls-file-upload -->

View File

@ -2,10 +2,8 @@
<rd-widget>
<rd-widget-header icon="sliders" feather-icon="true" title-text="User theme"></rd-widget-header>
<rd-widget-body>
<form class="theme-panel">
<!-- Theme Selector-->
<form class="form-horizontal">
<box-selector radio-name="'theme'" value="$ctrl.state.userTheme" options="$ctrl.state.availableThemes" on-change="($ctrl.setTheme)"></box-selector>
<!-- !Theme -->
</form>
<p class="mt-2 vertical-center">
<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 filesize from 'filesize';
import { Eye, EyeOff, Users } from 'react-feather';
import { ResourceControlOwnership as RCO } from '@/portainer/access-control/types';
export function truncateLeftRight(text, max, left, right) {
@ -106,13 +107,13 @@ export function environmentTypeIcon(type) {
export function ownershipIcon(ownership) {
switch (ownership) {
case RCO.PRIVATE:
return 'eye-off';
return EyeOff;
case RCO.ADMINISTRATORS:
return 'eye-off';
return EyeOff;
case RCO.RESTRICTED:
return 'users';
return Users;
default:
return 'eye';
return Eye;
}
}

View File

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

View File

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

View File

@ -1,6 +1,9 @@
import { Edit } from 'react-feather';
import { FeatureId } from '@/portainer/feature-flags/enums';
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 = {
CUSTOM: 0,
@ -11,7 +14,7 @@ const SERVER_TYPES = {
export const options = [
{
id: 'ldap_custom',
icon: Custom,
icon: <BadgeIcon icon={Edit} />,
label: 'Custom',
value: SERVER_TYPES.CUSTOM,
},

View File

@ -15,8 +15,8 @@
<!-- build-method -->
<div ng-if="!$ctrl.state.fromStack">
<div class="col-sm-12 form-section-title"> Build method </div>
<div class="form-group"></div>
<div class="form-group mb-0">
<div class="form-group">
<div class="col-sm-12">
<div class="boxselector_wrapper">
<div>
<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>
<!-- !build-method -->
<!-- web-editor -->
<web-editor-form

View File

@ -16,8 +16,8 @@
<!-- !name-input -->
<!-- build-method -->
<div class="col-sm-12 form-section-title"> Profile configuration </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>
<input type="radio" id="method_editor" ng-model="state.method" value="editor" />
@ -31,6 +31,7 @@
</div>
</div>
</div>
</div>
<!-- !build-method -->
<web-editor-form

View File

@ -17,7 +17,8 @@
<!-- build-method -->
<div class="col-sm-12 form-section-title"> Profile configuration </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>
<input type="radio" id="method_editor" ng-model="state.method" value="editor" />
@ -31,6 +32,7 @@
</div>
</div>
</div>
</div>
<!-- !build-method -->
<web-editor-form

View File

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

View File

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

View File

@ -7,11 +7,7 @@
<form class="form-horizontal">
<div class="col-sm-12 form-section-title"> Registry provider </div>
<div class="form-group"></div>
<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>
<box-selector radio-name="'availableRegistry'" value="$ctrl.state.registryValue" options="$ctrl.state.availableRegistry" on-change="($ctrl.setRegistry)"></box-selector>
<registry-form-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 Ecr from '@/assets/ico/vendor/ecr.svg?c';
import Quay from '@/assets/ico/vendor/quay.svg?c';
import Proget from '@/assets/ico/vendor/proget.svg?c';
import Azure from '@/assets/ico/vendor/azure.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 = [
{
@ -51,7 +54,7 @@ export const options = [
},
{
id: 'registry_custom',
icon: Custom,
icon: <BadgeIcon icon={Edit} />,
label: 'Custom registry',
description: 'Define your own registry',
value: '3',

View File

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

View File

@ -29,6 +29,7 @@
</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>
<internal-auth

View File

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

View File

@ -209,7 +209,9 @@
<rd-widget-body>
<form class="form-horizontal" ng-submit="backupPortainer()" name="backupPortainerForm">
<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>
<div ng-if="formValues.backupFormType === BACKUP_FORM_TYPES.S3">
<!-- Schedule automatic backups -->
<div class="form-group mt-3">

View File

@ -49,8 +49,8 @@
</div>
<!-- build-method -->
<div class="col-sm-12 form-section-title"> Build method </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>
<input type="radio" id="method_editor" ng-model="state.Method" value="editor" />
@ -94,6 +94,7 @@
</div>
</div>
</div>
</div>
<!-- !build-method -->
<!-- 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,
}: Props<T>) {
return (
<div className={clsx('boxselector_wrapper', styles.root)} role="radiogroup">
{options.map((option) =>
option.hide ? null : (
<div className="form-group">
<div className="col-sm-12">
<div
className={clsx('boxselector_wrapper', styles.root)}
role="radiogroup"
>
{options
.filter((option) => !option.hide)
.map((option) => (
<BoxSelectorItem
key={option.id}
radioName={radioName}
@ -31,8 +37,9 @@ export function BoxSelector<T extends number | string>({
disabled={option.disabled && option.disabled()}
tooltip={option.tooltip && option.tooltip()}
/>
)
)}
))}
</div>
</div>
</div>
);
}

View File

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

View File

@ -52,7 +52,7 @@ export function BoxSelectorItem<T extends number | string>({
<Icon
icon={option.icon}
feather={option.featherIcon}
className="boxselector_icon space-right"
className="boxselector_icon !flex items-center"
/>
)}
</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))
.join('') as keyof typeof featherIcons;
const IconComponent = featherIcons[iconName];
if (!IconComponent) {
throw new Error(`Feather icon not found: ${iconName}`);
}
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 digitalocean from '@/assets/ico/vendor/digitalocean.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 github from '@/assets/ico/vendor/github.svg?c';
import gitlab from '@/assets/ico/vendor/gitlab.svg?c';

View File

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