fix(image): Add hide default registry teaser for CE version [EE-4038] (#7533)
* fix(image): Add hide default registry teaser for CE version [EE-4038]. * fix(image): Hide advanced mode only if there is no docker hub registries [EE-3734] * sync with EEpull/7566/head
parent
234627f278
commit
c6ab5d5717
|
@ -77,10 +77,12 @@
|
||||||
<input id="select_{{ $index }}" type="checkbox" disabled />
|
<input id="select_{{ $index }}" type="checkbox" disabled />
|
||||||
<label for="select_{{ $index }}"></label>
|
<label for="select_{{ $index }}"></label>
|
||||||
</span>
|
</span>
|
||||||
<span>DockerHub (anonymous)</span>
|
<span><default-registry-name></default-registry-name></span>
|
||||||
|
</td>
|
||||||
|
<td> <default-registry-domain></default-registry-domain> </td>
|
||||||
|
<td>
|
||||||
|
<default-registry-action ng-if="$ctrl.isAdmin && !$ctrl.endpointType"></default-registry-action>
|
||||||
</td>
|
</td>
|
||||||
<td> docker.io </td>
|
|
||||||
<td> - </td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||||
|
|
|
@ -30,4 +30,5 @@ export enum FeatureId {
|
||||||
STACK_WEBHOOK = 'stack-webhook',
|
STACK_WEBHOOK = 'stack-webhook',
|
||||||
CONTAINER_WEBHOOK = 'container-webhook',
|
CONTAINER_WEBHOOK = 'container-webhook',
|
||||||
POD_SECURITY_POLICY_CONSTRAINT = 'pod-security-policy-constraint',
|
POD_SECURITY_POLICY_CONSTRAINT = 'pod-security-policy-constraint',
|
||||||
|
HIDE_DOCKER_HUB_ANONYMOUS = 'hide-docker-hub-anonymous',
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ export async function init(edition: Edition) {
|
||||||
[FeatureId.STACK_WEBHOOK]: Edition.BE,
|
[FeatureId.STACK_WEBHOOK]: Edition.BE,
|
||||||
[FeatureId.CONTAINER_WEBHOOK]: Edition.BE,
|
[FeatureId.CONTAINER_WEBHOOK]: Edition.BE,
|
||||||
[FeatureId.POD_SECURITY_POLICY_CONSTRAINT]: Edition.BE,
|
[FeatureId.POD_SECURITY_POLICY_CONSTRAINT]: Edition.BE,
|
||||||
|
[FeatureId.HIDE_DOCKER_HUB_ANONYMOUS]: Edition.BE,
|
||||||
};
|
};
|
||||||
|
|
||||||
state.currentEdition = currentEdition;
|
state.currentEdition = currentEdition;
|
||||||
|
|
|
@ -36,6 +36,7 @@ export function PublicSettingsViewModel(settings) {
|
||||||
this.KubeconfigExpiry = settings.KubeconfigExpiry;
|
this.KubeconfigExpiry = settings.KubeconfigExpiry;
|
||||||
this.Features = settings.Features;
|
this.Features = settings.Features;
|
||||||
this.Edge = new EdgeSettingsViewModel(settings.Edge);
|
this.Edge = new EdgeSettingsViewModel(settings.Edge);
|
||||||
|
this.DefaultRegistry = settings.DefaultRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InternalAuthSettingsViewModel(data) {
|
export function InternalAuthSettingsViewModel(data) {
|
||||||
|
|
|
@ -2,11 +2,19 @@ import angular from 'angular';
|
||||||
|
|
||||||
import { r2a } from '@/react-tools/react2angular';
|
import { r2a } from '@/react-tools/react2angular';
|
||||||
import { CreateAccessToken } from '@/react/portainer/account/CreateAccessTokenView';
|
import { CreateAccessToken } from '@/react/portainer/account/CreateAccessTokenView';
|
||||||
|
import {
|
||||||
|
DefaultRegistryAction,
|
||||||
|
DefaultRegistryDomain,
|
||||||
|
DefaultRegistryName,
|
||||||
|
} from '@/react/portainer/registries/ListView/DefaultRegistry';
|
||||||
|
|
||||||
import { wizardModule } from './wizard';
|
import { wizardModule } from './wizard';
|
||||||
|
|
||||||
export const viewsModule = angular
|
export const viewsModule = angular
|
||||||
.module('portainer.app.react.views', [wizardModule])
|
.module('portainer.app.react.views', [wizardModule])
|
||||||
|
.component('defaultRegistryName', r2a(DefaultRegistryName, []))
|
||||||
|
.component('defaultRegistryAction', r2a(DefaultRegistryAction, []))
|
||||||
|
.component('defaultRegistryDomain', r2a(DefaultRegistryDomain, []))
|
||||||
.component(
|
.component(
|
||||||
'createAccessToken',
|
'createAccessToken',
|
||||||
r2a(CreateAccessToken, ['onSubmit', 'onError'])
|
r2a(CreateAccessToken, ['onSubmit', 'onError'])
|
||||||
|
|
|
@ -12,8 +12,9 @@ import {
|
||||||
getSettings,
|
getSettings,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
getPublicSettings,
|
getPublicSettings,
|
||||||
|
updateDefaultRegistry,
|
||||||
} from './settings.service';
|
} from './settings.service';
|
||||||
import { Settings } from './types';
|
import { DefaultRegistry, Settings } from './types';
|
||||||
|
|
||||||
export function usePublicSettings<T = PublicSettingsViewModel>({
|
export function usePublicSettings<T = PublicSettingsViewModel>({
|
||||||
enabled,
|
enabled,
|
||||||
|
@ -51,3 +52,15 @@ export function useUpdateSettingsMutation() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useUpdateDefaultRegistrySettingsMutation() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(payload: Partial<DefaultRegistry>) => updateDefaultRegistry(payload),
|
||||||
|
mutationOptions(
|
||||||
|
withInvalidate(queryClient, [['settings']]),
|
||||||
|
withError('Unable to update default registry settings')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { PublicSettingsViewModel } from '@/portainer/models/settings';
|
||||||
|
|
||||||
import axios, { parseAxiosError } from '../services/axios';
|
import axios, { parseAxiosError } from '../services/axios';
|
||||||
|
|
||||||
import { PublicSettingsResponse, Settings } from './types';
|
import { DefaultRegistry, PublicSettingsResponse, Settings } from './types';
|
||||||
|
|
||||||
export async function getPublicSettings() {
|
export async function getPublicSettings() {
|
||||||
try {
|
try {
|
||||||
|
@ -38,6 +38,19 @@ export async function updateSettings(settings: Partial<Settings>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateDefaultRegistry(
|
||||||
|
defaultRegistry: Partial<DefaultRegistry>
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await axios.put(buildUrl('default_registry'), defaultRegistry);
|
||||||
|
} catch (e) {
|
||||||
|
throw parseAxiosError(
|
||||||
|
e as Error,
|
||||||
|
'Unable to update default registry settings'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function buildUrl(subResource?: string, action?: string) {
|
function buildUrl(subResource?: string, action?: string) {
|
||||||
let url = 'settings';
|
let url = 'settings';
|
||||||
if (subResource) {
|
if (subResource) {
|
||||||
|
|
|
@ -89,6 +89,10 @@ enum AuthenticationMethod {
|
||||||
|
|
||||||
type Feature = string;
|
type Feature = string;
|
||||||
|
|
||||||
|
export interface DefaultRegistry {
|
||||||
|
Hide: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
LogoURL: string;
|
LogoURL: string;
|
||||||
BlackListedLabels: Pair[];
|
BlackListedLabels: Pair[];
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import {
|
||||||
|
usePublicSettings,
|
||||||
|
useUpdateDefaultRegistrySettingsMutation,
|
||||||
|
} from 'Portainer/settings/queries';
|
||||||
|
import { notifySuccess } from 'Portainer/services/notifications';
|
||||||
|
import { FeatureId } from 'Portainer/feature-flags/enums';
|
||||||
|
|
||||||
|
import { isLimitedToBE } from '@/portainer/feature-flags/feature-flags.service';
|
||||||
|
|
||||||
|
import { Tooltip } from '@@/Tip/Tooltip';
|
||||||
|
import { Button } from '@@/buttons';
|
||||||
|
import { Icon } from '@@/Icon';
|
||||||
|
import { BEFeatureIndicator } from '@@/BEFeatureIndicator';
|
||||||
|
|
||||||
|
export function DefaultRegistryAction() {
|
||||||
|
const settingsQuery = usePublicSettings({
|
||||||
|
select: (settings) => settings.DefaultRegistry?.Hide,
|
||||||
|
});
|
||||||
|
const defaultRegistryMutation = useUpdateDefaultRegistrySettingsMutation();
|
||||||
|
|
||||||
|
if (!settingsQuery.isSuccess) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const hideDefaultRegistry = settingsQuery.data;
|
||||||
|
|
||||||
|
const isLimited = isLimitedToBE(FeatureId.HIDE_DOCKER_HUB_ANONYMOUS);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!hideDefaultRegistry ? (
|
||||||
|
<div className="vertical-center">
|
||||||
|
<Button
|
||||||
|
className="btn btn-xs btn-light vertical-center"
|
||||||
|
onClick={() => handleShowOrHide(true)}
|
||||||
|
disabled={isLimited}
|
||||||
|
>
|
||||||
|
<Icon icon="eye-off" feather />
|
||||||
|
Hide for all users
|
||||||
|
</Button>
|
||||||
|
<BEFeatureIndicator featureId={FeatureId.HIDE_DOCKER_HUB_ANONYMOUS} />
|
||||||
|
{isLimited ? null : (
|
||||||
|
<Tooltip
|
||||||
|
message="This hides the option in any registry dropdown prompts but does not prevent a user from deploying anonymously from Docker Hub directly via YAML.
|
||||||
|
Note: Docker Hub (anonymous) will continue to show as the ONLY option if there are NO other registries available to the user."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="vertical-center">
|
||||||
|
<Button
|
||||||
|
className="btn btn-xs btn-success vertical-center"
|
||||||
|
onClick={() => handleShowOrHide(false)}
|
||||||
|
>
|
||||||
|
<Icon icon="eye" feather />
|
||||||
|
Show for all users
|
||||||
|
</Button>
|
||||||
|
<Tooltip
|
||||||
|
message="This reveals the option in any registry dropdown prompts.
|
||||||
|
(but note that the Docker Hub (anonymous) option only shows if there is no credentialled Docker Hub option available to the user)."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleShowOrHide(hideDefaultRegistry: boolean) {
|
||||||
|
defaultRegistryMutation.mutate(
|
||||||
|
{
|
||||||
|
Hide: hideDefaultRegistry,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess() {
|
||||||
|
notifySuccess(
|
||||||
|
'Success',
|
||||||
|
'Default registry Settings updated successfully'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { usePublicSettings } from 'Portainer/settings/queries';
|
||||||
|
|
||||||
|
export function DefaultRegistryDomain() {
|
||||||
|
const settingsQuery = usePublicSettings({
|
||||||
|
select: (settings) => settings.DefaultRegistry?.Hide,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={clsx({
|
||||||
|
'cm-strikethrough': settingsQuery.isSuccess && settingsQuery.data,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
docker.io
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { usePublicSettings } from 'Portainer/settings/queries';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
export function DefaultRegistryName() {
|
||||||
|
const settingsQuery = usePublicSettings({
|
||||||
|
select: (settings) => settings.DefaultRegistry?.Hide,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={clsx({
|
||||||
|
'cm-strikethrough': settingsQuery.isSuccess && settingsQuery.data,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Docker Hub (anonymous)
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { DefaultRegistryAction } from './DefaultRegistryAction';
|
||||||
|
export { DefaultRegistryDomain } from './DefaultRegistryDomain';
|
||||||
|
export { DefaultRegistryName } from './DefaultRegistryName';
|
Loading…
Reference in New Issue