mirror of
https://github.com/quay/quay.git
synced 2026-01-26 06:21:37 +03:00
[redhat-3.13] ui: Add proxy cache config UI to org settings (PROJQUAY-7697) (#3428)
* ui: Add proxy cache config UI to org settings (PROJQUAY-7697) * Fix alerts + reset input fields on user action * Add cypress test for proxy cache config * enable proxy cache for cypress test * Propagate backend api error to UI * Add additional cypress test coverage * Fix eslint error --------- Signed-off-by: harishsurf <hgovinda@redhat.com> Co-authored-by: harishsurf <hgovinda@redhat.com>
This commit is contained in:
committed by
GitHub
parent
046b16eacb
commit
e97bbca8a0
109
web/cypress/e2e/proxy-cache.cy.ts
Normal file
109
web/cypress/e2e/proxy-cache.cy.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
describe('Organization settings - Proxy-cache configuration', () => {
|
||||
beforeEach(() => {
|
||||
cy.exec('npm run quay:seed');
|
||||
cy.request('GET', `${Cypress.env('REACT_QUAY_APP_API_URL')}/csrf_token`)
|
||||
.then((response) => response.body.csrf_token)
|
||||
.then((token) => {
|
||||
cy.loginByCSRF(token);
|
||||
});
|
||||
cy.intercept('GET', '/config', {fixture: 'config.json'}).as('getConfig');
|
||||
|
||||
// Intercept the /validateproxycache API call
|
||||
cy.intercept('POST', '/api/v1/organization/*/validateproxycache', (req) => {
|
||||
const {upstream_registry_username, upstream_registry_password} = req.body;
|
||||
if (upstream_registry_username && upstream_registry_password) {
|
||||
req.reply({
|
||||
statusCode: 202,
|
||||
body: 'Valid',
|
||||
});
|
||||
} else {
|
||||
req.reply({
|
||||
statusCode: 202,
|
||||
body: 'Anonymous',
|
||||
});
|
||||
}
|
||||
}).as('validateProxyCache');
|
||||
|
||||
// Intercept the /proxycache API call
|
||||
cy.intercept('POST', '/api/v1/organization/*/proxycache', {
|
||||
statusCode: 201,
|
||||
body: 'Created',
|
||||
}).as('createProxyCache');
|
||||
});
|
||||
|
||||
const createAnonymousProxyCacheConfig = (cy) => {
|
||||
cy.get('[data-testid="remote-registry-input"]').type('docker.io');
|
||||
cy.get('[data-testid="save-proxy-cache-btn"]').click();
|
||||
};
|
||||
|
||||
it('can create anonymous proxy cache configuration for an organization', () => {
|
||||
cy.visit('/organization/projectquay?tab=Settings');
|
||||
cy.get('[data-testid="Proxy Cache"]').click();
|
||||
createAnonymousProxyCacheConfig(cy);
|
||||
|
||||
// Wait for the validateproxycache API call and assert the response
|
||||
cy.wait('@validateProxyCache').then((interception) => {
|
||||
expect(interception.response?.statusCode).to.eq(202);
|
||||
expect(interception.response?.body).to.eq('Anonymous');
|
||||
});
|
||||
|
||||
// Wait for the proxycache API call and assert the response
|
||||
cy.wait('@createProxyCache').then((interception) => {
|
||||
expect(interception.response?.statusCode).to.eq(201);
|
||||
expect(interception.response?.body).to.eq('Created');
|
||||
});
|
||||
|
||||
// verify success alert
|
||||
cy.get('.pf-v5-c-alert.pf-m-success')
|
||||
.contains('Successfully configured proxy cache')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
it('can create proxy cache configuration with registry credentials for an organization', () => {
|
||||
cy.visit('/organization/projectquay?tab=Settings');
|
||||
cy.get('[data-testid="Proxy Cache"]').click();
|
||||
|
||||
cy.get('[data-testid="remote-registry-input"]').type('docker.io');
|
||||
cy.get('[data-testid="remote-registry-username"]').type('testuser1');
|
||||
cy.get('[data-testid="remote-registry-password"]').type('testpass');
|
||||
cy.get('[data-testid="remote-registry-expiration"]').clear().type('76400');
|
||||
cy.get('[data-testid="remote-registry-insecure"]').check();
|
||||
|
||||
cy.get('[data-testid="save-proxy-cache-btn"]').click();
|
||||
|
||||
// Wait for the validateproxycache API call and assert the response
|
||||
cy.wait('@validateProxyCache').then((interception) => {
|
||||
expect(interception.response?.statusCode).to.eq(202);
|
||||
expect(interception.response?.body).to.eq('Valid');
|
||||
});
|
||||
|
||||
// Wait for the proxycache API call and assert the response
|
||||
cy.wait('@createProxyCache').then((interception) => {
|
||||
expect(interception.response?.statusCode).to.eq(201);
|
||||
expect(interception.response?.body).to.eq('Created');
|
||||
});
|
||||
|
||||
// verify success alert
|
||||
cy.get('.pf-v5-c-alert.pf-m-success')
|
||||
.contains('Successfully configured proxy cache')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
it('can delete proxy cache configuration for an organization', () => {
|
||||
cy.visit('/organization/prometheus?tab=Settings');
|
||||
cy.get('[data-testid="Proxy Cache"]').click();
|
||||
|
||||
cy.get('[data-testid="delete-proxy-cache-btn"]').click();
|
||||
// verify success alert
|
||||
cy.get('.pf-v5-c-alert.pf-m-success')
|
||||
.contains('Successfully deleted proxy cache configuration')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
it('proxy cache config is not shown for user organization', () => {
|
||||
cy.visit('/organization/user1?tab=Settings');
|
||||
cy.get('[data-testid="Proxy Cache"]').should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -47,7 +47,7 @@
|
||||
"DEBUG": false,
|
||||
"DOCUMENTATION_ROOT": "https://docs.projectquay.io/",
|
||||
"ENTERPRISE_LOGO_URL": "/static/img/quay-horizontal-color.svg",
|
||||
"FEATURE_PROXY_CACHE": false,
|
||||
"FEATURE_PROXY_CACHE": true,
|
||||
"FEATURE_QUOTA_MANAGEMENT": false,
|
||||
"FEATURE_EDIT_QUOTA": true,
|
||||
"FEATURE_REPO_MIRROR": false,
|
||||
@@ -106,7 +106,7 @@
|
||||
"NONSUPERUSER_TEAM_SYNCING_SETUP": false,
|
||||
"PARTIAL_USER_AUTOCOMPLETE": true,
|
||||
"PERMANENT_SESSIONS": true,
|
||||
"PROXY_CACHE": false,
|
||||
"PROXY_CACHE": true,
|
||||
"PROXY_STORAGE": false,
|
||||
"PUBLIC_CATALOG": false,
|
||||
"QUOTA_MANAGEMENT": true,
|
||||
|
||||
@@ -6301,6 +6301,7 @@ COPY public.logentry3 (id, kind_id, account_id, performer_id, repository_id, dat
|
||||
201 31 32 29 \N 2023-11-07 19:54:08.826725 172.18.0.1 {"member": "user1", "team": "teamforreadonly"}
|
||||
202 98 29 29 \N 2023-11-07 19:54:14.934915 172.18.0.1 {}
|
||||
203 97 1 \N \N 2023-11-07 19:54:19.160078 172.18.0.1 {"type": "quayauth", "useragent": "Mozilla/5.0"}
|
||||
204 76 6 1 \N 2024-11-26 13:28:52.179631 172.20.0.1 {"upstream_registry": "docker.io"}
|
||||
\.
|
||||
|
||||
|
||||
@@ -6683,6 +6684,7 @@ COPY public.permissionprototype (id, org_id, uuid, activating_user_id, delegate_
|
||||
--
|
||||
|
||||
COPY public.proxycacheconfig (id, organization_id, creation_date, upstream_registry, upstream_registry_username, upstream_registry_password, expiration_s, insecure) FROM stdin;
|
||||
1 6 2024-11-26 13:28:52.175714 docker.io \N \N 86400 f
|
||||
\.
|
||||
|
||||
|
||||
@@ -6715,8 +6717,8 @@ COPY public.quayservice (id, name) FROM stdin;
|
||||
--
|
||||
|
||||
COPY public.queueitem (id, queue_name, body, available_after, available, processing_expires, retries_remaining, state_id) FROM stdin;
|
||||
2 namespacegc/3/ {"marker_id": 2, "original_username": "clair"} 2024-09-30 17:51:28.055159 t 2024-09-30 20:46:28.041972 5 245c3f97-bac6-4742-93eb-2b61f948c6b9
|
||||
1 namespacegc/2/ {"marker_id": 1, "original_username": "quay"} 2024-09-30 17:51:33.074743 t 2024-09-30 20:46:33.062514 5 daa3897c-92bf-4678-a49a-8dbf4efed18c
|
||||
1 namespacegc/2/ {"marker_id": 1, "original_username": "quay"} 2024-11-26 13:33:24.969707 t 2024-11-26 16:28:24.944791 5 b26bfbb7-6d52-4adb-81bd-9674dfc4359d
|
||||
2 namespacegc/3/ {"marker_id": 2, "original_username": "clair"} 2024-11-26 13:33:30.012937 t 2024-11-26 16:28:29.982092 5 be21614e-6c32-4f3c-9454-6c8c94a8b672
|
||||
\.
|
||||
|
||||
|
||||
@@ -8286,7 +8288,7 @@ SELECT pg_catalog.setval('public.logentry2_id_seq', 1, false);
|
||||
-- Name: logentry3_id_seq; Type: SEQUENCE SET; Schema: public; Owner: quay
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.logentry3_id_seq', 207, true);
|
||||
SELECT pg_catalog.setval('public.logentry3_id_seq', 204, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -8440,7 +8442,7 @@ SELECT pg_catalog.setval('public.permissionprototype_id_seq', 3, true);
|
||||
-- Name: proxycacheconfig_id_seq; Type: SEQUENCE SET; Schema: public; Owner: quay
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.proxycacheconfig_id_seq', 1, false);
|
||||
SELECT pg_catalog.setval('public.proxycacheconfig_id_seq', 1, true);
|
||||
|
||||
|
||||
--
|
||||
|
||||
@@ -9,7 +9,13 @@ export function useAlerts() {
|
||||
}
|
||||
setAlerts([...alerts, alert]);
|
||||
};
|
||||
|
||||
const clearAllAlerts = () => {
|
||||
setAlerts([]);
|
||||
};
|
||||
|
||||
return {
|
||||
addAlert: addAlert,
|
||||
addAlert,
|
||||
clearAllAlerts,
|
||||
};
|
||||
}
|
||||
|
||||
111
web/src/hooks/UseProxyCache.ts
Normal file
111
web/src/hooks/UseProxyCache.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
|
||||
import {
|
||||
createProxyCacheConfig,
|
||||
deleteProxyCacheConfig,
|
||||
fetchProxyCacheConfig,
|
||||
validateProxyCacheConfig,
|
||||
} from 'src/resources/ProxyCacheResource';
|
||||
import {useCurrentUser} from './UseCurrentUser';
|
||||
import {addDisplayError} from 'src/resources/ErrorHandling';
|
||||
import {AxiosError} from 'axios';
|
||||
|
||||
export interface IProxyCacheConfig {
|
||||
upstream_registry: string;
|
||||
expiration_s: number;
|
||||
insecure?: boolean;
|
||||
org_name?: string;
|
||||
upstream_registry_username?: string;
|
||||
upstream_registry_password?: string;
|
||||
}
|
||||
|
||||
export function useFetchProxyCacheConfig(orgName: string) {
|
||||
const {user} = useCurrentUser();
|
||||
|
||||
const {
|
||||
data: fetchedProxyCacheConfig,
|
||||
isLoading: isLoadingProxyCacheConfig,
|
||||
isSuccess: isSuccessLoadingProxyCacheConfig,
|
||||
isError: errorLoadingProxyCacheConfig,
|
||||
} = useQuery<IProxyCacheConfig>(
|
||||
['proxycacheconfig'],
|
||||
({signal}) => fetchProxyCacheConfig(orgName, signal),
|
||||
{
|
||||
enabled: !(user.username === orgName),
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
fetchedProxyCacheConfig,
|
||||
isLoadingProxyCacheConfig,
|
||||
errorLoadingProxyCacheConfig,
|
||||
isSuccessLoadingProxyCacheConfig,
|
||||
};
|
||||
}
|
||||
|
||||
export function useValidateProxyCacheConfig(
|
||||
proxyCacheConfig: IProxyCacheConfig,
|
||||
{onSuccess, onError},
|
||||
) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {mutate: proxyCacheConfigValidation} = useMutation(
|
||||
async () => {
|
||||
return validateProxyCacheConfig(proxyCacheConfig);
|
||||
},
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
onSuccess(response);
|
||||
queryClient.invalidateQueries(['proxycacheconfig']);
|
||||
},
|
||||
onError: (err: AxiosError) => {
|
||||
onError(addDisplayError('proxy cache validation error', err));
|
||||
},
|
||||
},
|
||||
);
|
||||
return {
|
||||
proxyCacheConfigValidation,
|
||||
};
|
||||
}
|
||||
|
||||
export function useCreateProxyCacheConfig({onSuccess, onError}) {
|
||||
const queryClient = useQueryClient();
|
||||
const {mutate: createProxyCacheConfigMutation} = useMutation(
|
||||
async (proxyCacheConfig: IProxyCacheConfig) => {
|
||||
return createProxyCacheConfig(proxyCacheConfig);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
queryClient.invalidateQueries(['proxycacheconfig']);
|
||||
},
|
||||
onError: (err: AxiosError) => {
|
||||
onError(addDisplayError('proxy cache creation error', err));
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
createProxyCacheConfigMutation,
|
||||
};
|
||||
}
|
||||
|
||||
export function useDeleteProxyCacheConfig(orgName, {onSuccess, onError}) {
|
||||
const queryClient = useQueryClient();
|
||||
const {mutate: deleteProxyCacheConfigMutation} = useMutation(
|
||||
async () => {
|
||||
return deleteProxyCacheConfig(orgName);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
queryClient.invalidateQueries(['proxycacheconfig']);
|
||||
},
|
||||
onError: (err: AxiosError) => {
|
||||
onError(addDisplayError('proxy cache deletion error', err));
|
||||
},
|
||||
},
|
||||
);
|
||||
return {
|
||||
deleteProxyCacheConfigMutation,
|
||||
};
|
||||
}
|
||||
56
web/src/resources/ProxyCacheResource.ts
Normal file
56
web/src/resources/ProxyCacheResource.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import axios from 'src/libs/axios';
|
||||
import {assertHttpCode} from './ErrorHandling';
|
||||
import {IProxyCacheConfig} from 'src/hooks/UseProxyCache';
|
||||
import {AxiosResponse} from 'axios';
|
||||
|
||||
export async function fetchProxyCacheConfig(org: string, signal?: AbortSignal) {
|
||||
const proxyCacheConfigUrl = `/api/v1/organization/${org}/proxycache`;
|
||||
const proxyResponse = await axios.get(proxyCacheConfigUrl, {signal});
|
||||
assertHttpCode(proxyResponse.status, 200);
|
||||
return proxyResponse.data;
|
||||
}
|
||||
|
||||
export async function validateProxyCacheConfig(
|
||||
proxyCacheConfig: IProxyCacheConfig,
|
||||
) {
|
||||
const proxyCacheConfigUrl = `/api/v1/organization/${proxyCacheConfig.org_name}/validateproxycache`;
|
||||
const payload = {
|
||||
...proxyCacheConfig,
|
||||
upstream_registry_username:
|
||||
proxyCacheConfig.upstream_registry_username || null,
|
||||
upstream_registry_password:
|
||||
proxyCacheConfig.upstream_registry_password || null,
|
||||
};
|
||||
const proxyResponse = await axios.post(proxyCacheConfigUrl, payload);
|
||||
assertHttpCode(proxyResponse.status, 202);
|
||||
return proxyResponse.data;
|
||||
}
|
||||
|
||||
export async function deleteProxyCacheConfig(
|
||||
org: string,
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
const proxyCacheConfigUrl = `/api/v1/organization/${org}/proxycache`;
|
||||
const proxyResponse = await axios.delete(proxyCacheConfigUrl, {signal});
|
||||
assertHttpCode(proxyResponse.status, 201);
|
||||
return proxyResponse.data;
|
||||
}
|
||||
|
||||
export async function createProxyCacheConfig(
|
||||
proxyCacheConfig: IProxyCacheConfig,
|
||||
) {
|
||||
const createProxyCacheConfigUrl = `/api/v1/organization/${proxyCacheConfig.org_name}/proxycache`;
|
||||
const payload = {
|
||||
...proxyCacheConfig,
|
||||
upstream_registry_username:
|
||||
proxyCacheConfig.upstream_registry_username || null,
|
||||
upstream_registry_password:
|
||||
proxyCacheConfig.upstream_registry_password || null,
|
||||
};
|
||||
const response: AxiosResponse = await axios.post(
|
||||
createProxyCacheConfigUrl,
|
||||
payload,
|
||||
);
|
||||
assertHttpCode(response.status, 201);
|
||||
return response.data;
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
Checkbox,
|
||||
Flex,
|
||||
Form,
|
||||
FormGroup,
|
||||
FormHelperText,
|
||||
HelperText,
|
||||
HelperTextItem,
|
||||
Spinner,
|
||||
TextInput,
|
||||
} from '@patternfly/react-core';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {AlertVariant} from 'src/atoms/AlertState';
|
||||
import {useAlerts} from 'src/hooks/UseAlerts';
|
||||
import {
|
||||
IProxyCacheConfig,
|
||||
useCreateProxyCacheConfig,
|
||||
useDeleteProxyCacheConfig,
|
||||
useFetchProxyCacheConfig,
|
||||
useValidateProxyCacheConfig,
|
||||
} from 'src/hooks/UseProxyCache';
|
||||
import Alerts from 'src/routes/Alerts';
|
||||
|
||||
type ProxyCacheConfigProps = {
|
||||
organizationName: string;
|
||||
isUser: boolean;
|
||||
};
|
||||
|
||||
const tagExpirationInSecsForProxyCache = 86400;
|
||||
|
||||
export const ProxyCacheConfig = (props: ProxyCacheConfigProps) => {
|
||||
const defaultProxyCacheConfig = {
|
||||
upstream_registry: '',
|
||||
expiration_s: tagExpirationInSecsForProxyCache,
|
||||
insecure: false,
|
||||
org_name: props.organizationName,
|
||||
};
|
||||
|
||||
const [proxyCacheConfig, setProxyCacheConfig] = useState<IProxyCacheConfig>(
|
||||
defaultProxyCacheConfig,
|
||||
);
|
||||
const {addAlert, clearAllAlerts} = useAlerts();
|
||||
|
||||
const {fetchedProxyCacheConfig, isLoadingProxyCacheConfig} =
|
||||
useFetchProxyCacheConfig(props.organizationName);
|
||||
|
||||
useEffect(() => {
|
||||
if (fetchedProxyCacheConfig) {
|
||||
// only set values that are fetched
|
||||
setProxyCacheConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
upstream_registry: fetchedProxyCacheConfig.upstream_registry,
|
||||
expiration_s:
|
||||
fetchedProxyCacheConfig.expiration_s ||
|
||||
tagExpirationInSecsForProxyCache,
|
||||
insecure: fetchedProxyCacheConfig.insecure || false,
|
||||
}));
|
||||
} else {
|
||||
// reset the config if there's no fetchedProxyCacheConfig data
|
||||
setProxyCacheConfig(defaultProxyCacheConfig);
|
||||
}
|
||||
}, [fetchedProxyCacheConfig, props.organizationName]);
|
||||
|
||||
const {createProxyCacheConfigMutation} = useCreateProxyCacheConfig({
|
||||
onSuccess: () => {
|
||||
addAlert({
|
||||
variant: AlertVariant.Success,
|
||||
title: `Successfully configured proxy cache`,
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
addAlert({
|
||||
variant: AlertVariant.Failure,
|
||||
title: err,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// clear alerts when switching tabs
|
||||
return () => {
|
||||
clearAllAlerts();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const {proxyCacheConfigValidation} = useValidateProxyCacheConfig(
|
||||
proxyCacheConfig,
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
if (response === 'Valid' || response === 'Anonymous') {
|
||||
createProxyCacheConfigMutation(proxyCacheConfig);
|
||||
setProxyCacheConfig(proxyCacheConfig);
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
addAlert({
|
||||
variant: AlertVariant.Failure,
|
||||
title: err,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const {deleteProxyCacheConfigMutation} = useDeleteProxyCacheConfig(
|
||||
props.organizationName,
|
||||
{
|
||||
onSuccess: () => {
|
||||
addAlert({
|
||||
variant: AlertVariant.Success,
|
||||
title: 'Successfully deleted proxy cache configuration',
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
addAlert({
|
||||
variant: AlertVariant.Failure,
|
||||
title: err,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleRemoteRegistryInput = (registryName: string) => {
|
||||
setProxyCacheConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
upstream_registry: registryName,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoteRegistryUsername = (registryUsername: string) => {
|
||||
setProxyCacheConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
upstream_registry_username: registryUsername,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoteRegistryPassword = (registryPass: string) => {
|
||||
setProxyCacheConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
upstream_registry_password: registryPass,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleRemoteRegistryExpiration = (expiration: string) => {
|
||||
setProxyCacheConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
expiration_s: Number(expiration),
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInsecureProtocol = (e, checked: boolean) => {
|
||||
setProxyCacheConfig((prevConfig) => ({
|
||||
...prevConfig,
|
||||
insecure: checked,
|
||||
}));
|
||||
};
|
||||
|
||||
if (isLoadingProxyCacheConfig) {
|
||||
return <Spinner size="md" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form id="form-form" maxWidth="70%">
|
||||
<FormGroup
|
||||
isInline
|
||||
label="Remote Registry"
|
||||
fieldId="form-remote-registry"
|
||||
>
|
||||
<TextInput
|
||||
isDisabled={!!fetchedProxyCacheConfig?.upstream_registry}
|
||||
type="text"
|
||||
id="form-name"
|
||||
data-testid="remote-registry-input"
|
||||
value={proxyCacheConfig?.upstream_registry || ''}
|
||||
onChange={(_event, registryName) =>
|
||||
handleRemoteRegistryInput(registryName)
|
||||
}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
Remote registry that is to be cached. (Eg: For docker hub,
|
||||
docker.io, docker.io/library)
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
isInline
|
||||
label="Remote Registry username"
|
||||
fieldId="form-username"
|
||||
>
|
||||
<TextInput
|
||||
isDisabled={!!fetchedProxyCacheConfig?.upstream_registry_username}
|
||||
type="text"
|
||||
id="remote-registry-username"
|
||||
data-testid="remote-registry-username"
|
||||
value={proxyCacheConfig?.upstream_registry_username || ''}
|
||||
onChange={(_event, registryUsername) =>
|
||||
handleRemoteRegistryUsername(registryUsername)
|
||||
}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
Username for authenticating into the entered remote registry. For
|
||||
anonymous pulls from the upstream, leave this empty.
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
isInline
|
||||
label="Remote Registry password"
|
||||
fieldId="form-password"
|
||||
>
|
||||
<TextInput
|
||||
isDisabled={!!fetchedProxyCacheConfig?.upstream_registry_password}
|
||||
type="password"
|
||||
id="remote-registry-password"
|
||||
data-testid="remote-registry-password"
|
||||
value={proxyCacheConfig?.upstream_registry_password || ''}
|
||||
onChange={(_event, password) =>
|
||||
handleRemoteRegistryPassword(password)
|
||||
}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
Password for authenticating into the entered remote registry. For
|
||||
anonymous pulls from the upstream, leave this empty.{' '}
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup isInline label="Expiration" fieldId="form-username">
|
||||
<TextInput
|
||||
type="text"
|
||||
id="remote-registry-expiration"
|
||||
data-testid="remote-registry-expiration"
|
||||
value={proxyCacheConfig?.expiration_s}
|
||||
placeholder={tagExpirationInSecsForProxyCache.toString()}
|
||||
onChange={(_event, inputSecs) =>
|
||||
handleRemoteRegistryExpiration(inputSecs)
|
||||
}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
Default tag expiration for cached images, in seconds. This value
|
||||
is refreshed on every pull. Default is 86400 i.e, 24 hours.{' '}
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup isInline label="Insecure" fieldId="form-insecure">
|
||||
<Checkbox
|
||||
label="http"
|
||||
isChecked={proxyCacheConfig?.insecure}
|
||||
onChange={handleInsecureProtocol}
|
||||
id="controlled-check-2"
|
||||
data-testid="remote-registry-insecure"
|
||||
/>
|
||||
<FormHelperText>
|
||||
<HelperText>
|
||||
<HelperTextItem>
|
||||
If set, http (unsecure protocol) will be used. If not set, https
|
||||
(secure protocol) will be used to request the remote registry.
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormHelperText>
|
||||
</FormGroup>
|
||||
|
||||
<ActionGroup>
|
||||
<Flex
|
||||
justifyContent={{default: 'justifyContentFlexEnd'}}
|
||||
width={'100%'}
|
||||
>
|
||||
<Button
|
||||
id="save-proxy-cache"
|
||||
data-testid="save-proxy-cache-btn"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
proxyCacheConfigValidation();
|
||||
}}
|
||||
isDisabled={
|
||||
!proxyCacheConfig?.upstream_registry ||
|
||||
!!fetchedProxyCacheConfig?.upstream_registry
|
||||
}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
isDisabled={!fetchedProxyCacheConfig?.upstream_registry}
|
||||
id="delete-proxy-cache"
|
||||
data-testid="delete-proxy-cache-btn"
|
||||
variant="danger"
|
||||
type="submit"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
deleteProxyCacheConfigMutation();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Flex>
|
||||
</ActionGroup>
|
||||
<Alerts />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import AutoPruning from './AutoPruning';
|
||||
import {BillingInformation} from './BillingInformation';
|
||||
import {CliConfiguration} from './CLIConfiguration';
|
||||
import {GeneralSettings} from './GeneralSettings';
|
||||
import {ProxyCacheConfig} from './ProxyCacheConfig';
|
||||
|
||||
export default function Settings(props: SettingsProps) {
|
||||
const organizationName = location.pathname.split('/')[2];
|
||||
@@ -48,6 +49,17 @@ export default function Settings(props: SettingsProps) {
|
||||
),
|
||||
visible: quayConfig?.features?.AUTO_PRUNE,
|
||||
},
|
||||
{
|
||||
name: 'Proxy Cache',
|
||||
id: 'proxycacheconfig',
|
||||
content: (
|
||||
<ProxyCacheConfig
|
||||
organizationName={props.organizationName}
|
||||
isUser={props.isUserOrganization}
|
||||
/>
|
||||
),
|
||||
visible: quayConfig?.features?.PROXY_CACHE && !props.isUserOrganization,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -66,6 +78,7 @@ export default function Settings(props: SettingsProps) {
|
||||
<Tab
|
||||
key={tab.id}
|
||||
id={tab.id}
|
||||
data-testid={tab.name}
|
||||
eventKey={tabIndex}
|
||||
title={<TabTitleText>{tab.name}</TabTitleText>}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user