1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 15:29:42 +02:00

fix(be-teaser): mute styles [EE-6035] (#10349)

This commit is contained in:
Ali 2023-09-24 19:56:09 +01:00 committed by GitHub
parent ffac83864d
commit 13c48ab961
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 604 additions and 616 deletions

View file

@ -87,7 +87,7 @@
--orange-1: #e86925; --orange-1: #e86925;
--BE-only: var(--ui-warning-7); --BE-only: var(--ui-gray-6);
--text-log-viewer-color-json-grey: var(--text-log-viewer-color); --text-log-viewer-color-json-grey: var(--text-log-viewer-color);
--text-log-viewer-color-json-magenta: var(--text-log-viewer-color); --text-log-viewer-color-json-magenta: var(--text-log-viewer-color);
@ -259,8 +259,7 @@
/* Dark Theme */ /* Dark Theme */
[theme='dark'] { [theme='dark'] {
--BE-only: var(--ui-blue-8); --BE-only: var(--ui-gray-6);
--bg-BE-only: rgba(225, 223, 223, 0.08);
--text-log-viewer-color-json-grey: var(--text-log-viewer-color); --text-log-viewer-color-json-grey: var(--text-log-viewer-color);
--text-log-viewer-color-json-magenta: var(--text-log-viewer-color); --text-log-viewer-color-json-magenta: var(--text-log-viewer-color);
@ -434,6 +433,7 @@
/* High Contrast Theme */ /* High Contrast Theme */
[theme='highcontrast'] { [theme='highcontrast'] {
--BE-only: var(--ui-gray-6);
--text-log-viewer-color-json-grey: var(--text-log-viewer-color); --text-log-viewer-color-json-grey: var(--text-log-viewer-color);
--text-log-viewer-color-json-magenta: var(--text-log-viewer-color); --text-log-viewer-color-json-magenta: var(--text-log-viewer-color);
--text-log-viewer-color-json-yellow: var(--text-log-viewer-color); --text-log-viewer-color-json-yellow: var(--text-log-viewer-color);

View file

@ -1,5 +1,5 @@
<a class="vertical-center be-indicator ml-5" href="{{ $ctrl.url }}" target="_blank" rel="noopener" ng-if="$ctrl.limitedToBE"> <a class="vertical-center be-indicator ml-5" href="{{ $ctrl.url }}" target="_blank" rel="noopener" ng-if="$ctrl.limitedToBE">
<ng-transclude></ng-transclude> <ng-transclude></ng-transclude>
<pr-icon icon="'briefcase'" class-name="'space-right be-indicator-icon'"></pr-icon> <pr-icon icon="'briefcase'" class-name="'space-right be-indicator-icon'"></pr-icon>
<span class="be-indicator-label">Business Edition Feature</span> <span class="be-indicator-label">Business Feature</span>
</a> </a>

View file

@ -76,7 +76,7 @@ class PorAccessManagementController {
} }
if (this.isRoleLimitedToBE(role)) { if (this.isRoleLimitedToBE(role)) {
return `${role.Name} (Business Edition Feature)`; return `${role.Name} (Business Feature)`;
} }
return `${role.Name} (Default)`; return `${role.Name} (Default)`;

View file

@ -169,68 +169,46 @@
<oauth-providers-selector on-change="($ctrl.onSelectProvider)" value="$ctrl.state.provider"></oauth-providers-selector> <oauth-providers-selector on-change="($ctrl.onSelectProvider)" value="$ctrl.state.provider"></oauth-providers-selector>
<div class="col-sm-12 form-section-title">OAuth Configuration</div>
<div class="form-group" ng-if="$ctrl.state.provider == 'microsoft'">
<label for="oauth_microsoft_tenant_id" class="col-sm-3 col-lg-2 control-label text-left">
Tenant ID
<portainer-tooltip message="'ID of the Azure Directory you wish to authenticate against. Also known as the Directory ID'"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_microsoft_tenant_id"
placeholder="xxxxxxxxxxxxxxxxxxxx"
ng-model="$ctrl.state.microsoftTenantID"
ng-change="$ctrl.onMicrosoftTenantIDChange()"
limited-feature-dir="{{::$ctrl.limitedFeature}}"
limited-feature-class="limited-be"
limited-feature-disabled
limited-feature-tabindex="-1"
required
/>
</div>
</div>
<div class="form-group">
<label for="oauth_client_id" class="col-sm-3 col-lg-2 control-label text-left">
{{ $ctrl.state.provider == 'microsoft' ? 'Application ID' : 'Client ID' }}
<portainer-tooltip message="'Public identifier of the OAuth application'"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
id="oauth_client_id"
ng-model="$ctrl.settings.ClientID"
placeholder="xxxxxxxxxxxxxxxxxxxx"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
/>
</div>
</div>
<div class="form-group">
<label for="oauth_client_secret" class="col-sm-3 col-lg-2 control-label text-left">
{{ $ctrl.state.provider == 'microsoft' ? 'Application key' : 'Client secret' }}
</label>
<div class="col-sm-9 col-lg-10">
<input
type="password"
class="form-control"
id="oauth_client_secret"
ng-model="$ctrl.settings.ClientSecret"
placeholder="xxxxxxxxxxxxxxxxxxxx"
autocomplete="new-password"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
/>
</div>
</div>
<div ng-if="$ctrl.state.provider == 'custom' || $ctrl.state.overrideConfiguration"> <div ng-if="$ctrl.state.provider == 'custom' || $ctrl.state.overrideConfiguration">
<div class="col-sm-12 form-section-title">OAuth Configuration</div>
<div class="form-group">
<label for="oauth_client_id" class="col-sm-3 col-lg-2 control-label text-left">
{{ $ctrl.state.provider == 'microsoft' ? 'Application ID' : 'Client ID' }}
<portainer-tooltip message="'Public identifier of the OAuth application'"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
id="oauth_client_id"
ng-model="$ctrl.settings.ClientID"
placeholder="xxxxxxxxxxxxxxxxxxxx"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
/>
</div>
</div>
<div class="form-group">
<label for="oauth_client_secret" class="col-sm-3 col-lg-2 control-label text-left">
{{ $ctrl.state.provider == 'microsoft' ? 'Application key' : 'Client secret' }}
</label>
<div class="col-sm-9 col-lg-10">
<input
type="password"
class="form-control"
id="oauth_client_secret"
ng-model="$ctrl.settings.ClientSecret"
placeholder="xxxxxxxxxxxxxxxxxxxx"
autocomplete="new-password"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
/>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="oauth_authorization_uri" class="col-sm-3 col-lg-2 control-label text-left"> <label for="oauth_authorization_uri" class="col-sm-3 col-lg-2 control-label text-left">
Authorization URL Authorization URL
@ -363,26 +341,100 @@
/> />
</div> </div>
</div> </div>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="!$ctrl.isOAuthTeamMembershipFormValid() || oauthSettingsForm.$invalid"
limited-feature-id="$ctrl.limitedFeature"
limited-feature-class="$ctrl.limitedFeatureClass"
class-name="'oauth-save-settings-button'"
></save-auth-settings-button>
</div> </div>
<div class="form-group" ng-if="$ctrl.state.provider != 'custom'"> <div ng-if="$ctrl.state.provider != 'custom'" class="limited-be">
<div class="col-sm-12"> <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator></div>
<a class="small interactive vertical-center" ng-if="!$ctrl.state.overrideConfiguration" ng-click="$ctrl.state.overrideConfiguration = true;"> <div class="limited-be-content">
<pr-icon icon="'wrench'"></pr-icon> <div class="col-sm-12 form-section-title">OAuth Configuration</div>
Override default configuration
</a> <div class="form-group" ng-if="$ctrl.state.provider == 'microsoft'">
<a class="small interactive vertical-center" ng-if="$ctrl.state.overrideConfiguration" ng-click="$ctrl.useDefaultProviderConfiguration($ctrl.state.provider)"> <label for="oauth_microsoft_tenant_id" class="col-sm-3 col-lg-2 control-label text-left">
<pr-icon icon="'settings'"></pr-icon> Tenant ID
Use default configuration <portainer-tooltip message="'ID of the Azure Directory you wish to authenticate against. Also known as the Directory ID'"></portainer-tooltip>
</a> </label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="oauth_microsoft_tenant_id"
placeholder="xxxxxxxxxxxxxxxxxxxx"
ng-model="$ctrl.state.microsoftTenantID"
ng-change="$ctrl.onMicrosoftTenantIDChange()"
limited-feature-dir="{{::$ctrl.limitedFeature}}"
limited-feature-class="limited-be"
limited-feature-disabled
limited-feature-tabindex="-1"
required
/>
</div>
</div>
<div class="form-group">
<label for="oauth_client_id" class="col-sm-3 col-lg-2 control-label text-left">
{{ $ctrl.state.provider == 'microsoft' ? 'Application ID' : 'Client ID' }}
<portainer-tooltip message="'Public identifier of the OAuth application'"></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
id="oauth_client_id"
ng-model="$ctrl.settings.ClientID"
placeholder="xxxxxxxxxxxxxxxxxxxx"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
/>
</div>
</div>
<div class="form-group">
<label for="oauth_client_secret" class="col-sm-3 col-lg-2 control-label text-left">
{{ $ctrl.state.provider == 'microsoft' ? 'Application key' : 'Client secret' }}
</label>
<div class="col-sm-9 col-lg-10">
<input
type="password"
class="form-control"
id="oauth_client_secret"
ng-model="$ctrl.settings.ClientSecret"
placeholder="xxxxxxxxxxxxxxxxxxxx"
autocomplete="new-password"
ng-class="['form-control', { 'limited-be': $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' }]"
ng-disabled="$ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom'"
tabindex="{{ $ctrl.isLimitedToBE && $ctrl.state.provider !== 'custom' ? -1 : 0 }}"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<a class="small interactive vertical-center" ng-if="!$ctrl.state.overrideConfiguration" ng-click="$ctrl.state.overrideConfiguration = true;">
<pr-icon icon="'wrench'"></pr-icon>
Override default configuration
</a>
<a class="small interactive vertical-center" ng-if="$ctrl.state.overrideConfiguration" ng-click="$ctrl.useDefaultProviderConfiguration($ctrl.state.provider)">
<pr-icon icon="'settings'"></pr-icon>
Use default configuration
</a>
</div>
</div>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="!$ctrl.isOAuthTeamMembershipFormValid() || oauthSettingsForm.$invalid"
limited-feature-id="$ctrl.limitedFeature"
limited-feature-class="$ctrl.limitedFeatureClass"
class-name="'oauth-save-settings-button'"
></save-auth-settings-button>
</div> </div>
</div> </div>
<save-auth-settings-button
on-save-settings="($ctrl.onSaveSettings)"
save-button-state="($ctrl.saveButtonState)"
save-button-disabled="!$ctrl.isOAuthTeamMembershipFormValid() || oauthSettingsForm.$invalid"
limited-feature-id="$ctrl.limitedFeature"
limited-feature-class="$ctrl.limitedFeatureClass"
class-name="'oauth-save-settings-button'"
></save-auth-settings-button>
</ng-form> </ng-form>

View file

@ -71,7 +71,6 @@ export const ngModule = angular
'message', 'message',
'buttonText', 'buttonText',
'className', 'className',
'icon',
'buttonClassName', 'buttonClassName',
]) ])
) )
@ -82,7 +81,7 @@ export const ngModule = angular
.component( .component(
'portainerTooltip', 'portainerTooltip',
r2a(Tooltip, ['message', 'position', 'className', 'setHtmlMessage']) r2a(Tooltip, ['message', 'position', 'className', 'setHtmlMessage', 'size'])
) )
.component('badge', r2a(Badge, ['type', 'className'])) .component('badge', r2a(Badge, ['type', 'className']))
.component('fileUploadField', fileUploadField) .component('fileUploadField', fileUploadField)

View file

@ -1,9 +1,6 @@
<ng-form class="ad-settings" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-class="limited-be"> <ng-form class="ad-settings" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-class="limited-be">
<div class="overlay"> <div>
<div class="limited-be-link vertical-center" <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeatureId"></be-feature-indicator></div>
><be-feature-indicator feature="$ctrl.limitedFeatureId"></be-feature-indicator
><portainer-tooltip message="'This feature is currently limited to Business Edition users only. '"></portainer-tooltip
></div>
<div class="limited-be-content"> <div class="limited-be-content">
<auto-user-provision-toggle ng-model="$ctrl.settings.AutoCreateUsers"> <auto-user-provision-toggle ng-model="$ctrl.settings.AutoCreateUsers">
<field-description> <field-description>

View file

@ -2,73 +2,71 @@
Auto-populate team admins <be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left" ng-if="$ctrl.isLimitedFeatureSelfContained"></be-feature-indicator> Auto-populate team admins <be-feature-indicator feature="$ctrl.limitedFeatureId" class="space-left" ng-if="$ctrl.isLimitedFeatureSelfContained"></be-feature-indicator>
</div> </div>
<rd-widget ng-repeat="config in $ctrl.settings.AdminGroupSearchSettings | limitTo: (1 - $ctrl.settings.AdminGroupSearchSettings)"> <div class="w-full px-5 pt-3" ng-repeat="config in $ctrl.settings.AdminGroupSearchSettings | limitTo: (1 - $ctrl.settings.AdminGroupSearchSettings)">
<rd-widget-body> <div class="form-group mb-3" ng-if="$index > 0">
<div class="form-group mb-3" ng-if="$index > 0"> <span class="col-sm-12 text-muted small"> Extra search configuration </span>
<span class="col-sm-12 text-muted small"> Extra search configuration </span> </div>
<div class="form-group">
<label for="ldap_admin_group_basedn_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Base DN
<portainer-tooltip message="'The distinguished name of the element from which the LDAP server will search for groups.'"></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input
type="text"
class="form-control"
id="ldap_admin_group_basedn_{{ $index }}"
ng-model="config.GroupBaseDN"
placeholder="dc=ldap,dc=domain,dc=tld"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
/>
</div> </div>
<div class="form-group"> <label for="ldap_admin_group_att_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
<label for="ldap_admin_group_basedn_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left"> Group Membership Attribute
Group Base DN <portainer-tooltip message="'LDAP attribute which denotes the group membership.'"></portainer-tooltip>
<portainer-tooltip message="'The distinguished name of the element from which the LDAP server will search for groups.'"></portainer-tooltip> </label>
</label> <div class="col-sm-8 col-md-4">
<div class="col-sm-8 col-md-4"> <input
<input type="text"
type="text" class="form-control"
class="form-control" id="ldap_admin_group_att_{{ $index }}"
id="ldap_admin_group_basedn_{{ $index }}" ng-model="config.GroupAttribute"
ng-model="config.GroupBaseDN" placeholder="member"
placeholder="dc=ldap,dc=domain,dc=tld" limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}" ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}" limited-feature-tabindex="-1"
limited-feature-tabindex="-1" />
/>
</div>
<label for="ldap_admin_group_att_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Membership Attribute
<portainer-tooltip message="'LDAP attribute which denotes the group membership.'"></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input
type="text"
class="form-control"
id="ldap_admin_group_att_{{ $index }}"
ng-model="config.GroupAttribute"
placeholder="member"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
limited-feature-tabindex="-1"
/>
</div>
</div> </div>
<div class="form-group"> </div>
<label for="ldap_admin_group_filter_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left"> <div class="form-group">
Group Filter <label for="ldap_admin_group_filter_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
<portainer-tooltip message="'The LDAP search filter used to select group elements, optional.'"></portainer-tooltip> Group Filter
</label> <portainer-tooltip message="'The LDAP search filter used to select group elements, optional.'"></portainer-tooltip>
<div class="col-sm-8 col-md-10 vertical-center"> </label>
<input <div class="col-sm-8 col-md-10 vertical-center">
type="text" <input
class="form-control" type="text"
id="ldap_admin_group_filter_{{ $index }}" class="form-control"
ng-model="config.GroupFilter" id="ldap_admin_group_filter_{{ $index }}"
placeholder="(objectClass=groupOfNames)" ng-model="config.GroupFilter"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" placeholder="(objectClass=groupOfNames)"
limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}" limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}" limited-feature-class=" {{ $ctrl.isLimitedFeatureSelfContained && 'limited-be' }}"
limited-feature-tabindex="-1" ng-disabled="{{ $ctrl.isLimitedFeatureSelfContained }}"
/> limited-feature-tabindex="-1"
<button class="btn btn-md btn-danger" type="button" ng-click="$ctrl.onRemoveClick($index)" ng-if="$index > 0"> />
<pr-icon icon="'trash-2'" size="'md'"></pr-icon> <button class="btn btn-md btn-danger" type="button" ng-click="$ctrl.onRemoveClick($index)" ng-if="$index > 0">
</button> <pr-icon icon="'trash-2'" size="'md'"></pr-icon>
</div> </button>
</div> </div>
</rd-widget-body> </div>
</rd-widget> </div>
<div class="form-group mt-3"> <div class="form-group mt-3">
<div class="col-sm-12"> <div class="col-sm-12">

View file

@ -1,88 +1,78 @@
<rd-widget> <div class="w-full px-5 pt-3">
<rd-widget-body> <div ng-if="$ctrl.index > 0" style="margin-bottom: 10px">
<div ng-if="$ctrl.index > 0" style="margin-bottom: 10px"> <span class="text-muted small"> Extra search configuration </span>
<span class="text-muted small"> Extra search configuration </span> <button
<button class="btn btn-sm btn-danger"
class="btn btn-sm btn-danger" type="button"
type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)"
ng-click="$ctrl.onRemoveClick($ctrl.index)" limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1"
limited-feature-tabindex="-1" >
> <pr-icon icon="'trash-2'"></pr-icon>
<pr-icon icon="'trash-2'"></pr-icon> </button>
</button> </div>
<ldap-settings-dn-builder
label="Group Search Path (optional)"
suffix="{{ $ctrl.domainSuffix }}"
ng-model="$ctrl.config.GroupBaseDN"
on-change="($ctrl.onChangeBaseDN)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-dn-builder>
<div class="form-group">
<label class="col-sm-4 col-md-2 control-label text-left"> Group Base DN </label>
<div class="col-sm-8 col-md-10">
{{ $ctrl.config.GroupBaseDN }}
</div> </div>
</div>
<ldap-settings-dn-builder <div class="form-group">
label="Group Search Path (optional)" <div class="col-sm-12 vertical-center" style="margin-bottom: 5px">
suffix="{{ $ctrl.domainSuffix }}" <label class="control-label !pt-0 text-left">Groups</label>
ng-model="$ctrl.config.GroupBaseDN" <span class="label label-default interactive vertical-center" style="margin-left: 10px" ng-click="$ctrl.addGroup()">
on-change="($ctrl.onChangeBaseDN)" <pr-icon icon="'plus-circle'"></pr-icon>
limited-feature-id="$ctrl.limitedFeatureId" add another group
></ldap-settings-dn-builder> </span>
</div>
<div class="form-group"> <div class="col-sm-12" ng-if="$ctrl.groups.length">
<label class="col-sm-4 col-md-2 control-label text-left"> Group Base DN </label> <div class="w-full px-5 pt-3">
<div class="col-sm-8 col-md-10"> <div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.groups">
{{ $ctrl.config.GroupBaseDN }} <div class="col-sm-4">
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onGroupsChange()" limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-tabindex="-1">
<option value="ou">OU Name</option>
<option value="cn">Folder Name</option>
</select>
</div>
<div class="col-sm-5">
<input
class="form-control"
ng-model="entry.value"
ng-change="$ctrl.onGroupsChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
</div>
<div class="col-sm-3 text-right">
<button
class="btn btn-md btn-danger"
type="button"
ng-click="$ctrl.removeGroup($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<pr-icon icon="'trash-2'" size="'md'"></pr-icon>
</button>
</div>
</div>
</div> </div>
</div> </div>
</div>
<div class="form-group"> <div class="form-group no-margin-last-child">
<div class="col-sm-12 vertical-center" style="margin-bottom: 5px"> <label class="col-sm-4 col-md-2 control-label text-left"> Group Filter </label>
<label class="control-label !pt-0 text-left">Groups</label> <div class="col-sm-8 col-md-10">
<span class="label label-default interactive vertical-center" style="margin-left: 10px" ng-click="$ctrl.addGroup()"> {{ $ctrl.config.GroupFilter }}
<pr-icon icon="'plus-circle'"></pr-icon>
add another group
</span>
</div>
<div class="col-sm-12" ng-if="$ctrl.groups.length">
<rd-widget>
<rd-widget-body>
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.groups">
<div class="col-sm-4">
<select
class="form-control"
ng-model="entry.type"
ng-change="$ctrl.onGroupsChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<option value="ou">OU Name</option>
<option value="cn">Folder Name</option>
</select>
</div>
<div class="col-sm-5">
<input
class="form-control"
ng-model="entry.value"
ng-change="$ctrl.onGroupsChange()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
/>
</div>
<div class="col-sm-3 text-right">
<button
class="btn btn-md btn-danger"
type="button"
ng-click="$ctrl.removeGroup($index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<pr-icon icon="'trash-2'" size="'md'"></pr-icon>
</button>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div> </div>
</div>
<div class="form-group no-margin-last-child"> </div>
<label class="col-sm-4 col-md-2 control-label text-left"> Group Filter </label>
<div class="col-sm-8 col-md-10">
{{ $ctrl.config.GroupFilter }}
</div>
</div>
</rd-widget-body>
</rd-widget>

View file

@ -1,9 +1,6 @@
<ng-form limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-class="limited-be" class="ldap-settings-openldap"> <ng-form limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-class="limited-be" class="ldap-settings-openldap">
<div class="overlay"> <div>
<div class="limited-be-link vertical-center" <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeatureId"></be-feature-indicator></div>
><be-feature-indicator feature="$ctrl.limitedFeatureId"></be-feature-indicator
><portainer-tooltip message="'This feature is currently limited to Business Edition users only. '"></portainer-tooltip
></div>
<div class="limited-be-content"> <div class="limited-be-content">
<div> <div>
<div class="col-sm-12 form-section-title"> Information </div> <div class="col-sm-12 form-section-title"> Information </div>

View file

@ -1,101 +1,99 @@
<rd-widget> <div class="w-full px-5 pt-3">
<rd-widget-body> <div ng-if="$ctrl.index > 0" style="margin-bottom: 10px">
<div ng-if="$ctrl.index > 0" style="margin-bottom: 10px"> <span class="text-muted small"> Extra search configuration </span>
<span class="text-muted small"> Extra search configuration </span> <button
ng-if="$ctrl.index > 0"
class="btn btn-sm btn-danger"
type="button"
ng-click="$ctrl.onRemoveClick($ctrl.index)"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<pr-icon icon="'trash-2'"></pr-icon>
</button>
</div>
<div class="form-group" ng-if="$ctrl.showUsernameFormat">
<div class="col-sm-4" style="margin-bottom: 5px">
<label class="control-label text-left">Username Format</label>
</div>
<div class="col-sm-8">
<div class="input-group">
<div class="input-group-btn">
<button
class="btn btn-primary"
ng-model="$ctrl.config.UserNameAttribute"
uib-btn-radio="'sAMAccountName'"
style="margin-left: 0px"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>username</button
>
<button
class="btn btn-primary"
ng-model="$ctrl.config.UserNameAttribute"
uib-btn-radio="'userPrincipalName'"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>user@domainname</button
>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label text-left"> Root Domain </label>
<div class="col-sm-8">
{{ $ctrl.config.BaseDN }}
</div>
</div>
<ldap-settings-dn-builder
ng-model="$ctrl.config.BaseDN"
label="User Search Path (optional)"
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onBaseDNChange)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-dn-builder>
<div class="form-group no-margin-last-child">
<div class="col-sm-12" style="margin-bottom: 5px">
<label class="control-label text-left">Allowed Groups (optional)</label>
<button <button
ng-if="$ctrl.index > 0"
class="btn btn-sm btn-danger"
type="button" type="button"
ng-click="$ctrl.onRemoveClick($ctrl.index)" class="label label-default interactive vertical-center"
style="margin-left: 10px; border: 0"
ng-click="$ctrl.addGroup()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1" limited-feature-tabindex="-1"
> >
<pr-icon icon="'trash-2'"></pr-icon> <pr-icon icon="'plus-circle'"></pr-icon>
add another group
</button> </button>
</div> </div>
<div class="col-sm-12">
<div class="form-group" ng-if="$ctrl.showUsernameFormat"> <div ng-repeat="group in $ctrl.groups track by $index" style="margin-bottom: 10px">
<div class="col-sm-4" style="margin-bottom: 5px"> <rd-widget>
<label class="control-label text-left">Username Format</label> <rd-widget-body>
</div> <ldap-settings-group-dn-builder
<div class="col-sm-8"> ng-model="group"
<div class="input-group"> index="$index"
<div class="input-group-btn"> suffix="{{ $ctrl.domainSuffix }}"
<button on-change="($ctrl.onGroupChange)"
class="btn btn-primary" on-remove-click="($ctrl.removeGroup)"
ng-model="$ctrl.config.UserNameAttribute" limited-feature-id="$ctrl.limitedFeatureId"
uib-btn-radio="'sAMAccountName'" ></ldap-settings-group-dn-builder>
style="margin-left: 0px" </rd-widget-body>
limited-feature-dir="{{::$ctrl.limitedFeatureId}}" </rd-widget>
limited-feature-tabindex="-1"
>username</button
>
<button
class="btn btn-primary"
ng-model="$ctrl.config.UserNameAttribute"
uib-btn-radio="'userPrincipalName'"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>user@domainname</button
>
</div>
</div>
</div> </div>
</div> </div>
</div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label text-left"> Root Domain </label> <label class="col-sm-4 control-label text-left"> User Filter </label>
<div class="col-sm-8"> <div class="col-sm-8">
{{ $ctrl.config.BaseDN }} {{ $ctrl.config.Filter }}
</div>
</div> </div>
</div>
<ldap-settings-dn-builder </div>
ng-model="$ctrl.config.BaseDN"
label="User Search Path (optional)"
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onBaseDNChange)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-dn-builder>
<div class="form-group no-margin-last-child">
<div class="col-sm-12" style="margin-bottom: 5px">
<label class="control-label text-left">Allowed Groups (optional)</label>
<button
type="button"
class="label label-default interactive vertical-center"
style="margin-left: 10px; border: 0"
ng-click="$ctrl.addGroup()"
limited-feature-dir="{{::$ctrl.limitedFeatureId}}"
limited-feature-tabindex="-1"
>
<pr-icon icon="'plus-circle'"></pr-icon>
add another group
</button>
</div>
<div class="col-sm-12">
<div ng-repeat="group in $ctrl.groups track by $index" style="margin-bottom: 10px">
<rd-widget>
<rd-widget-body>
<ldap-settings-group-dn-builder
ng-model="group"
index="$index"
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onGroupChange)"
on-remove-click="($ctrl.removeGroup)"
limited-feature-id="$ctrl.limitedFeatureId"
></ldap-settings-group-dn-builder>
</rd-widget-body>
</rd-widget>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label text-left"> User Filter </label>
<div class="col-sm-8">
{{ $ctrl.config.Filter }}
</div>
</div>
</rd-widget-body>
</rd-widget>

View file

@ -1,11 +1,8 @@
<page-header title="'User Activity'" breadcrumbs="['Activity Logs']" reload="true"> </page-header> <page-header title="'User Activity'" breadcrumbs="['Activity Logs']" reload="true"> </page-header>
<div class="be-indicator-container limited-be mx-4"> <div class="be-indicator-container limited-be mx-4">
<div class="overlay"> <div>
<div class="limited-be-link vertical-center" <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator></div>
><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator
><portainer-tooltip message="'This feature is currently limited to Business Edition users only. '"></portainer-tooltip
></div>
<div class="limited-be-content"> <div class="limited-be-content">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
@ -34,11 +31,8 @@
</div> </div>
<div class="be-indicator-container limited-be mx-4"> <div class="be-indicator-container limited-be mx-4">
<div class="overlay"> <div>
<div class="limited-be-link vertical-center" <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator></div>
><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator
><portainer-tooltip message="'This feature is currently limited to Business Edition users only. '"></portainer-tooltip
></div>
<div class="limited-be-content"> <div class="limited-be-content">
<div class="row"> <div class="row">
<activity-logs-datatable <activity-logs-datatable

View file

@ -1,11 +1,8 @@
<page-header title="'User Activity'" breadcrumbs="['User authentication activity']" reload="true"> </page-header> <page-header title="'User Activity'" breadcrumbs="['User authentication activity']" reload="true"> </page-header>
<div class="be-indicator-container limited-be mx-4"> <div class="be-indicator-container limited-be mx-4">
<div class="overlay"> <div>
<div class="limited-be-link vertical-center" <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator></div>
><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator
><portainer-tooltip message="'This feature is currently limited to Business Edition users only. '"></portainer-tooltip
></div>
<div class="limited-be-content"> <div class="limited-be-content">
<rd-widget> <rd-widget>
<rd-widget-body> <rd-widget-body>
@ -33,11 +30,8 @@
</div> </div>
<div class="be-indicator-container limited-be mx-4"> <div class="be-indicator-container limited-be mx-4">
<div class="overlay"> <div>
<div class="limited-be-link vertical-center" <div class="limited-be-link vertical-center"><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator></div>
><be-feature-indicator feature="$ctrl.limitedFeature"></be-feature-indicator
><portainer-tooltip message="'This feature is currently limited to Business Edition users only. '"></portainer-tooltip
></div>
<div class="limited-be-content"> <div class="limited-be-content">
<div class="row"> <div class="row">
<auth-logs-datatable <auth-logs-datatable

View file

@ -1,5 +1,6 @@
.be-indicator { .be-indicator {
border: solid 1px var(--BE-only); @apply border-solid border border-gray-6;
@apply text-gray-6 text-xs;
border-radius: 15px; border-radius: 15px;
padding: 5px 10px; padding: 5px 10px;
font-weight: 400; font-weight: 400;
@ -9,11 +10,12 @@
} }
.be-indicator .be-indicator-icon { .be-indicator .be-indicator-icon {
@apply text-black th-highcontrast:text-white th-dark:text-blue-8; @apply text-inherit;
} }
.be-indicator:hover { .be-indicator:hover {
@apply no-underline; @apply underline;
@apply border-blue-9 text-blue-9;
} }
.be-indicator:hover .be-indicator-label { .be-indicator:hover .be-indicator-label {
@ -21,5 +23,5 @@
} }
.be-indicator-container { .be-indicator-container {
border: solid 1px var(--BE-only); @apply border-solid border border-gray-6;
} }

View file

@ -29,16 +29,14 @@ export function BEFeatureIndicator({
} }
return ( return (
<a <a
className={clsx('be-indicator vertical-center', className)} className={clsx('be-indicator vertical-center text-xs', className)}
href={url} href={url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{children} {children}
{showIcon && <Icon icon={Briefcase} className="be-indicator-icon mr-1" />} {showIcon && <Icon icon={Briefcase} className="be-indicator-icon mr-1" />}
<span className="be-indicator-label break-words"> <span className="be-indicator-label break-words">Business Feature</span>
Business Edition Feature
</span>
</a> </a>
); );
} }

View file

@ -1,16 +1,18 @@
import clsx from 'clsx';
import { FeatureId } from '@/react/portainer/feature-flags/enums'; import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { isLimitedToBE } from '@/react/portainer/feature-flags/feature-flags.service'; import { isLimitedToBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { Tooltip } from '@@/Tip/Tooltip'; import { BEFeatureIndicator } from './BEFeatureIndicator';
import { BEFeatureIndicator } from '.';
export function BEOverlay({ export function BEOverlay({
featureId, featureId,
children, children,
className,
}: { }: {
featureId: FeatureId; featureId: FeatureId;
children: React.ReactNode; children: React.ReactNode;
className?: string;
}) { }) {
const isLimited = isLimitedToBE(featureId); const isLimited = isLimitedToBE(featureId);
if (!isLimited) { if (!isLimited) {
@ -19,13 +21,10 @@ export function BEOverlay({
return ( return (
<div className="be-indicator-container limited-be"> <div className="be-indicator-container limited-be">
<div className="overlay"> <div className="limited-be-link vertical-center">
<div className="limited-be-link vertical-center"> <BEFeatureIndicator featureId={featureId} />
<BEFeatureIndicator featureId={FeatureId.CA_FILE} />
<Tooltip message="This feature is currently limited to Business Edition users only. " />
</div>
<div className="limited-be-content">{children}</div>
</div> </div>
<div className={clsx('limited-be-content', className)}>{children}</div>
</div> </div>
); );
} }

View file

@ -1,4 +1,4 @@
import { ReactNode } from 'react'; import { Briefcase } from 'lucide-react';
import { FeatureId } from '@/react/portainer/feature-flags/enums'; import { FeatureId } from '@/react/portainer/feature-flags/enums';
@ -11,7 +11,6 @@ interface Props {
message: string; message: string;
buttonText: string; buttonText: string;
className?: string; className?: string;
icon?: ReactNode;
buttonClassName?: string; buttonClassName?: string;
} }
@ -21,7 +20,6 @@ export function BETeaserButton({
message, message,
buttonText, buttonText,
className, className,
icon,
buttonClassName, buttonClassName,
}: Props) { }: Props) {
return ( return (
@ -34,9 +32,9 @@ export function BETeaserButton({
<span> <span>
<Button <Button
className={buttonClassName} className={buttonClassName}
icon={icon} icon={Briefcase}
type="button" type="button"
color="warninglight" color="default"
size="small" size="small"
onClick={() => {}} onClick={() => {}}
disabled disabled

View file

@ -38,12 +38,12 @@
padding-left: 20px; padding-left: 20px;
} }
/* used for BE teaser. Dark theme specs defined by EE-5621 */ /* used for BE teaser */
.box-selector-item.limited.business label, .box-selector-item.limited.business label,
.box-selector-item.limited.business input:checked + label { .box-selector-item.limited.business input:checked + label {
@apply border-warning-7 bg-warning-1 text-black; @apply border-gray-6 bg-gray-6 bg-opacity-10;
@apply th-dark:border-blue-8 th-dark:bg-[color:var(--bg-BE-only)] th-dark:text-white; @apply th-dark:border-gray-6 th-dark:bg-gray-6 th-dark:bg-opacity-10;
@apply th-highcontrast:bg-warning-8 th-highcontrast:bg-opacity-10 th-highcontrast:text-white; @apply th-highcontrast:border-gray-6 th-highcontrast:bg-gray-6 th-highcontrast:bg-opacity-10;
filter: none; filter: none;
} }

View file

@ -9,7 +9,7 @@ import { getFeatureDetails } from '@@/BEFeatureIndicator/utils';
import styles from './BoxSelectorItem.module.css'; import styles from './BoxSelectorItem.module.css';
import { BoxSelectorOption, Value } from './types'; import { BoxSelectorOption, Value } from './types';
import { LimitedToBeIndicator } from './LimitedToBeIndicator'; import { LimitedToBeBoxSelectorIndicator } from './LimitedToBeBoxSelectorIndicator';
import { BoxOption } from './BoxOption'; import { BoxOption } from './BoxOption';
import { LogoIcon } from './LogoIcon'; import { LogoIcon } from './LogoIcon';
@ -40,8 +40,6 @@ export function BoxSelectorItem<T extends Value>({
option.feature option.feature
); );
const beIndicatorTooltipId = `box-selector-item-${radioName}-${option.id}-limited`;
const ContentBox = slim ? 'div' : Fragment; const ContentBox = slim ? 'div' : Fragment;
return ( return (
@ -59,14 +57,9 @@ export function BoxSelectorItem<T extends Value>({
type={type} type={type}
checkIcon={checkIcon} checkIcon={checkIcon}
> >
{limitedToBE && ( {limitedToBE && <LimitedToBeBoxSelectorIndicator url={featureUrl} />}
<LimitedToBeIndicator
tooltipId={beIndicatorTooltipId}
url={featureUrl}
/>
)}
<div <div
className={clsx('flex gap-2', { className={clsx('flex min-w-[140px] gap-2', {
'opacity-30': limitedToBE, 'opacity-30': limitedToBE,
'h-full flex-col justify-start': !slim, 'h-full flex-col justify-start': !slim,
'slim items-center': slim, 'slim items-center': slim,

View file

@ -0,0 +1,30 @@
import { Briefcase } from 'lucide-react';
import { Icon } from '@@/Icon';
import { Tooltip } from '@@/Tip/Tooltip';
interface Props {
url?: string;
}
export function LimitedToBeBoxSelectorIndicator({ url }: Props) {
return (
<div className="absolute left-0 top-0 w-full">
<div className="mx-auto flex max-w-fit items-center rounded-b-lg border border-t-0 border-solid border-gray-6 bg-transparent px-3 py-1 text-gray-6">
<a
className="inline-flex items-center text-xs text-gray-6"
href={url}
target="_blank"
rel="noreferrer"
>
<Icon icon={Briefcase} className="!mr-1" />
<span>Business Feature</span>
</a>
<Tooltip
size="sm"
message="Select this option to preview this business feature."
/>
</div>
</div>
);
}

View file

@ -1,37 +0,0 @@
import { HelpCircle } from 'lucide-react';
import clsx from 'clsx';
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
interface Props {
tooltipId: string;
url?: string;
}
export function LimitedToBeIndicator({ tooltipId, url }: Props) {
return (
<div className="absolute left-0 top-0 w-full">
<div className="mx-auto flex max-w-fit items-center gap-1 rounded-b-lg bg-warning-4 px-3 py-1 text-sm th-dark:bg-[color:var(--bg-BE-only)]">
<a
className="text-warning-9 th-dark:text-blue-8"
href={url}
target="_blank"
rel="noreferrer"
>
BE Feature
</a>
<TooltipWithChildren
position="bottom"
className={clsx(tooltipId, 'portainer-tooltip')}
heading="Business Edition feature."
message="This feature is currently limited to Business Edition users only."
>
<HelpCircle
className="ml-1 !text-warning-7 th-dark:!text-blue-8"
aria-hidden="true"
/>
</TooltipWithChildren>
</div>
</div>
);
}

View file

@ -1,14 +1,26 @@
import { HelpCircle } from 'lucide-react'; import { HelpCircle } from 'lucide-react';
import { ReactNode, useMemo } from 'react'; import { ReactNode, useMemo } from 'react';
import sanitize from 'sanitize-html'; import sanitize from 'sanitize-html';
import clsx from 'clsx';
import { TooltipWithChildren, Position } from '../TooltipWithChildren'; import { TooltipWithChildren, Position } from '../TooltipWithChildren';
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
const sizeClasses: Record<Size, string> = {
xs: 'text-xs',
sm: 'text-sm',
md: 'text-base',
lg: 'text-lg',
xl: 'text-xl',
};
export interface Props { export interface Props {
position?: Position; position?: Position;
message: ReactNode; message: ReactNode;
className?: string; className?: string;
setHtmlMessage?: boolean; setHtmlMessage?: boolean;
size?: Size;
} }
export function Tooltip({ export function Tooltip({
@ -16,6 +28,7 @@ export function Tooltip({
position = 'bottom', position = 'bottom',
className, className,
setHtmlMessage, setHtmlMessage,
size = 'md',
}: Props) { }: Props) {
// allow angular views to set html messages for the tooltip // allow angular views to set html messages for the tooltip
const htmlMessage = useMemo(() => { const htmlMessage = useMemo(() => {
@ -27,14 +40,14 @@ export function Tooltip({
}, [setHtmlMessage, message]); }, [setHtmlMessage, message]);
return ( return (
<TooltipWithChildren <span className={clsx('ml-1 inline-flex items-center', sizeClasses[size])}>
message={htmlMessage || message} <TooltipWithChildren
position={position} message={htmlMessage || message}
className={className} position={position}
> className={className}
<span className="inline-flex text-base"> >
<HelpCircle className="lucide ml-1" aria-hidden="true" /> <HelpCircle className="lucide" aria-hidden="true" />
</span> </TooltipWithChildren>
</TooltipWithChildren> </span>
); );
} }

View file

@ -36,9 +36,5 @@
} }
.tooltip-beteaser { .tooltip-beteaser {
@apply text-warning-5 th-dark:text-blue-8; @apply text-blue-8 hover:text-blue-9;
}
.tooltip-beteaser:hover {
@apply text-warning-5 th-dark:text-blue-8;
} }

View file

@ -49,7 +49,7 @@ export function TooltipWithChildren({
rel="noreferrer" rel="noreferrer"
className={styles.tooltipBeteaser} className={styles.tooltipBeteaser}
> >
Business Edition Only Business Feature
</a> </a>
)} )}
</div> </div>

View file

@ -1,5 +1,3 @@
import { Plus } from 'lucide-react';
import { FeatureId } from '@/react/portainer/feature-flags/enums'; import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { BETeaserButton } from '@@/BETeaserButton'; import { BETeaserButton } from '@@/BETeaserButton';
@ -42,7 +40,6 @@ export function AnnotationsBeTeaser() {
message="Allows specifying of annotations on this resource." message="Allows specifying of annotations on this resource."
featureId={FeatureId.K8S_ANNOTATIONS} featureId={FeatureId.K8S_ANNOTATIONS}
buttonClassName="!ml-0" buttonClassName="!ml-0"
icon={Plus}
/> />
</div> </div>
</div> </div>

View file

@ -1,5 +1,3 @@
import { RefreshCw } from 'lucide-react';
import { FeatureId } from '@/react/portainer/feature-flags/enums'; import { FeatureId } from '@/react/portainer/feature-flags/enums';
import { BETeaserButton } from '@@/BETeaserButton'; import { BETeaserButton } from '@@/BETeaserButton';
@ -10,7 +8,6 @@ export function RestartApplicationButton() {
buttonClassName="!ml-0" buttonClassName="!ml-0"
data-cy="k8sAppDetail-restartButton" data-cy="k8sAppDetail-restartButton"
heading="Rolling restart" heading="Rolling restart"
icon={RefreshCw}
featureId={FeatureId.K8S_ROLLING_RESTART} featureId={FeatureId.K8S_ROLLING_RESTART}
message="A rolling restart of the application is performed." message="A rolling restart of the application is performed."
buttonText="Rolling restart" buttonText="Rolling restart"

View file

@ -81,7 +81,7 @@ export function KubeConfigTeaserForm() {
<div className="form-group"> <div className="form-group">
<div className="col-sm-12"> <div className="col-sm-12">
<LoadingButton <LoadingButton
className="wizard-connect-button" className="wizard-connect-button !ml-0"
loadingText="Connecting environment..." loadingText="Connecting environment..."
isLoading={false} isLoading={false}
disabled disabled

View file

@ -11,7 +11,7 @@ import EdgeAgentAsyncIcon from '@/react/edge/components/edge-agent-async.svg?c';
import { BoxSelectorOption } from '@@/BoxSelector/types'; import { BoxSelectorOption } from '@@/BoxSelector/types';
import { BoxSelector } from '@@/BoxSelector'; import { BoxSelector } from '@@/BoxSelector';
import { BEFeatureIndicator } from '@@/BEFeatureIndicator'; import { BEOverlay } from '@@/BEFeatureIndicator/BEOverlay';
import { AnalyticsStateKey } from '../types'; import { AnalyticsStateKey } from '../types';
import { EdgeAgentTab } from '../shared/EdgeAgentTab'; import { EdgeAgentTab } from '../shared/EdgeAgentTab';
@ -112,11 +112,10 @@ export function WizardKubernetes({ onCreate }: Props) {
); );
case 'kubeconfig': case 'kubeconfig':
return ( return (
<div className="border border-solid border-orange-1 px-1 py-5"> <div className="mb-3">
<BEFeatureIndicator <BEOverlay featureId={FeatureId.K8S_CREATE_FROM_KUBECONFIG}>
featureId={options.find((o) => o.value === type)?.feature} <KubeConfigTeaserForm />
/> </BEOverlay>
<KubeConfigTeaserForm />
</div> </div>
); );
default: default:

View file

@ -10,17 +10,19 @@ button.limited-be,
button[disabled].limited-be.oauth-save-settings-button { button[disabled].limited-be.oauth-save-settings-button {
background-color: var(--BE-only); background-color: var(--BE-only);
border-color: var(--BE-only); border-color: var(--BE-only);
margin-left: 0px;
} }
button.limited-be.oauth-save-settings-button { button.limited-be.oauth-save-settings-button {
background-color: var(--blue-2); background-color: var(--blue-2);
border-color: transparent; border-color: transparent;
margin-left: 0px;
} }
ng-form.limited-be, ng-form.limited-be,
form.limited-be, form.limited-be,
div.limited-be { div.limited-be {
border: solid 2px var(--BE-only); border: solid 1px var(--BE-only);
border-radius: 8px; border-radius: 8px;
pointer-events: none; pointer-events: none;
touch-action: none; touch-action: none;
@ -28,49 +30,31 @@ div.limited-be {
} }
.limited-be-content { .limited-be-content {
background: rgba(247, 144, 9, 0.1); @apply border-gray-6 p-2.5 text-xs opacity-50;
opacity: 0.5;
padding: 10px;
} }
.limited-be-link { .limited-be-link {
padding: 10px;
width: inherit;
z-index: 5; z-index: 5;
position: relative; position: relative;
width: 270px;
height: 40px;
top: 0px; top: 0px;
right: 0px; right: 0px;
float: right; float: right;
border-top-right-radius: 8px; border-top-right-radius: 8px;
border-bottom-left-radius: 8px; border-bottom-left-radius: 8px;
@apply bg-warning-5 text-warning-9;
@apply th-dark:bg-[color:var(--bg-BE-only)] th-dark:text-blue-8;
padding: 5px 10px;
touch-action: auto; touch-action: auto;
cursor: hand; cursor: hand;
pointer-events: auto; pointer-events: auto;
} }
.limited-be-link a { .limited-be-link a {
@apply pointer-events-auto cursor-pointer; @apply text-gray-6;
@apply text-warning-9 th-dark:text-blue-8;
} }
.limited-be-link a:hover { .limited-be-link a:hover {
@apply text-warning-9 underline th-dark:text-blue-8; @apply underline;
} @apply border-blue-9 text-blue-9;
.overlay {
@apply bg-center bg-no-repeat;
@apply bg-[url(~@/assets/ico/lock.svg)] th-dark:bg-[url(~@/assets/ico/lock-2.svg)];
}
.limited-be input,
.limited-be .widget-body {
background: rgba(247, 144, 9, 0.05);
@apply th-dark:bg-[color:var(--bg-BE-only)];
} }
.form-control.limited-be[disabled] { .form-control.limited-be[disabled] {

View file

@ -13,6 +13,7 @@ import { FormControl } from '@@/form-components/FormControl';
import { LoadingButton } from '@@/buttons/LoadingButton'; import { LoadingButton } from '@@/buttons/LoadingButton';
import { Input } from '@@/form-components/Input'; import { Input } from '@@/form-components/Input';
import { SwitchField } from '@@/form-components/SwitchField'; import { SwitchField } from '@@/form-components/SwitchField';
import { BEOverlay } from '@@/BEFeatureIndicator/BEOverlay';
import { import {
useBackupS3Settings, useBackupS3Settings,
@ -57,159 +58,159 @@ export function BackupS3Form() {
validateOnMount validateOnMount
> >
{({ values, errors, isSubmitting, setFieldValue, isValid }) => ( {({ values, errors, isSubmitting, setFieldValue, isValid }) => (
<Form className="form-horizontal"> <BEOverlay featureId={FeatureId.S3_BACKUP_SETTING}>
<div className="form-group"> <Form className="form-horizontal">
<div className="col-sm-12"> <div className="form-group">
<SwitchField <div className="col-sm-12">
name="schedule-automatic-backup" <SwitchField
labelClass="col-sm-3 col-lg-2" name="schedule-automatic-backup"
label="Schedule automatic backups" labelClass="col-sm-3 col-lg-2"
checked={values.scheduleAutomaticBackup} label="Schedule automatic backups"
featureId={FeatureId.S3_BACKUP_SETTING} checked={values.scheduleAutomaticBackup}
onChange={(e) => setFieldValue('scheduleAutomaticBackup', e)} onChange={(e) => setFieldValue('scheduleAutomaticBackup', e)}
/> />
</div>
</div> </div>
</div>
{values.scheduleAutomaticBackup && ( {values.scheduleAutomaticBackup && (
<FormControl
inputId="cron_rule"
label="Cron rule"
size="small"
errors={errors.cronRule}
required
>
<Field
id="cron_rule"
name="cronRule"
type="text"
as={Input}
placeholder="0 2 * * *"
data-cy="settings-backupCronRuleInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
)}
<FormControl <FormControl
inputId="cron_rule" label="Access key ID"
label="Cron rule" inputId="access_key_id"
size="small" errors={errors.accessKeyID}
errors={errors.cronRule}
required
> >
<Field <Field
id="cron_rule" id="access_key_id"
name="cronRule" name="accessKeyID"
type="text" type="text"
as={Input} as={Input}
placeholder="0 2 * * *" data-cy="settings-accessKeyIdInput"
data-cy="settings-backupCronRuleInput"
className={clsx({ 'limited-be': limitedToBE })} className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE} disabled={limitedToBE}
/> />
</FormControl> </FormControl>
)}
<FormControl <FormControl
label="Access key ID" label="Secret access key"
inputId="access_key_id" inputId="secret_access_key"
errors={errors.accessKeyID} errors={errors.secretAccessKey}
> >
<Field <Field
id="access_key_id" id="secret_access_key"
name="accessKeyID" name="secretAccessKey"
type="text" type="password"
as={Input} as={Input}
data-cy="settings-accessKeyIdInput" data-cy="settings-secretAccessKeyInput"
className={clsx({ 'limited-be': limitedToBE })} className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<FormControl label="Region" inputId="region" errors={errors.region}>
<Field
id="region"
name="region"
type="text"
as={Input}
placeholder="default region is us-east-1 if left empty"
data-cy="settings-backupRegionInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<FormControl
label="Bucket name"
inputId="bucket_name"
errors={errors.bucketName}
>
<Field
id="bucket_name"
name="bucketName"
type="text"
as={Input}
data-cy="settings-backupBucketNameInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<FormControl
label="S3 compatible host"
inputId="s3_compatible_host"
tooltip="Hostname of a S3 service"
errors={errors.s3CompatibleHost}
>
<Field
id="s3_compatible_host"
name="s3CompatibleHost"
type="text"
as={Input}
placeholder="leave empty for AWS S3"
data-cy="settings-backupS3CompatibleHostInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<SecurityFieldset
switchDataCy="settings-passwordProtectToggleS3"
inputDataCy="settings-backups3pw"
disabled={limitedToBE} disabled={limitedToBE}
/> />
</FormControl>
<FormControl <div className="form-group">
label="Secret access key" <div className="col-sm-12">
inputId="secret_access_key" <LoadingButton
errors={errors.secretAccessKey} type="button"
> loadingText="Exporting..."
<Field isLoading={isSubmitting}
id="secret_access_key" className={clsx('!ml-0', { 'limited-be': limitedToBE })}
name="secretAccessKey" disabled={!isValid || limitedToBE}
type="password" data-cy="settings-exportBackupS3Button"
as={Input} icon={Upload}
data-cy="settings-secretAccessKeyInput" onClick={() => {
className={clsx({ 'limited-be': limitedToBE })} handleExport(values);
disabled={limitedToBE} }}
/> >
</FormControl> Export backup
</LoadingButton>
<FormControl label="Region" inputId="region" errors={errors.region}> </div>
<Field
id="region"
name="region"
type="text"
as={Input}
placeholder="default region is us-east-1 if left empty"
data-cy="settings-backupRegionInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<FormControl
label="Bucket name"
inputId="bucket_name"
errors={errors.bucketName}
>
<Field
id="bucket_name"
name="bucketName"
type="text"
as={Input}
data-cy="settings-backupBucketNameInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<FormControl
label="S3 compatible host"
inputId="s3_compatible_host"
tooltip="Hostname of a S3 service"
errors={errors.s3CompatibleHost}
>
<Field
id="s3_compatible_host"
name="s3CompatibleHost"
type="text"
as={Input}
placeholder="leave empty for AWS S3"
data-cy="settings-backupS3CompatibleHostInput"
className={clsx({ 'limited-be': limitedToBE })}
disabled={limitedToBE}
/>
</FormControl>
<SecurityFieldset
switchDataCy="settings-passwordProtectToggleS3"
inputDataCy="settings-backups3pw"
disabled={limitedToBE}
/>
<div className="form-group">
<div className="col-sm-12">
<LoadingButton
type="button"
loadingText="Exporting..."
isLoading={isSubmitting}
className={clsx('!ml-0', { 'limited-be': limitedToBE })}
disabled={!isValid || limitedToBE}
data-cy="settings-exportBackupS3Button"
icon={Upload}
onClick={() => {
handleExport(values);
}}
>
Export backup
</LoadingButton>
</div> </div>
</div> <div className="form-group">
<div className="form-group"> <div className="col-sm-12">
<hr /> <LoadingButton
<div className="col-sm-12"> loadingText="Saving settings..."
<LoadingButton isLoading={isSubmitting}
loadingText="Saving settings..." className={clsx('!ml-0', { 'limited-be': limitedToBE })}
isLoading={isSubmitting} disabled={!isValid || limitedToBE}
className={clsx('!ml-0', { 'limited-be': limitedToBE })} data-cy="settings-saveBackupSettingsButton"
disabled={!isValid || limitedToBE} >
data-cy="settings-saveBackupSettingsButton" Save backup settings
> </LoadingButton>
Save backup settings </div>
</LoadingButton>
</div> </div>
</div> </Form>
</Form> </BEOverlay>
)} )}
</Formik> </Formik>
); );

View file

@ -30,7 +30,7 @@ export function HelmCertPanel() {
}; };
return ( return (
<BEOverlay featureId={FeatureId.CA_FILE}> <BEOverlay featureId={FeatureId.CA_FILE} className="!p-0">
<Widget> <Widget>
<Widget.Title <Widget.Title
icon={Key} icon={Key}

View file

@ -78,8 +78,7 @@ function UpgradeBEBanner() {
<ArrowUpCircle <ArrowUpCircle
className={clsx( className={clsx(
'lucide text-lg', 'lucide text-lg',
'fill-warning-6 stroke-[#023959] th-dark:stroke-black', 'fill-gray-6 stroke-[#023959] th-dark:stroke-black th-highcontrast:stroke-black'
'th-highcontrast:fill-warning-6 th-highcontrast:stroke-black'
)} )}
/> />
{isSidebarOpen && <>Upgrade to Business Edition</>} {isSidebarOpen && <>Upgrade to Business Edition</>}