From f2ec119d2d9b30692dbb3240119058d7dea2cb25 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 22 Aug 2024 08:41:17 -0700 Subject: [PATCH] remove awx oauth applications --- .../hooks/useGetActivityStreamRoute.tsx | 2 - .../applications/ApplicationForm.cy.tsx | 226 ------------------ .../applications/ApplicationForm.tsx | 202 ---------------- .../ApplicationClientSecretModal.cy.tsx | 52 ---- .../ApplicationClientSecretModal.tsx | 43 ---- .../ApplicationPage/ApplicationPage.cy.tsx | 69 ------ .../ApplicationPage/ApplicationPage.tsx | 71 ------ .../ApplicationPageDetails.cy.tsx | 26 -- .../ApplicationPageDetails.tsx | 55 ----- .../ApplicationPageTokens.cy.tsx | 99 -------- .../ApplicationPage/ApplicationPageTokens.tsx | 75 ------ .../applications/Applications.cy.tsx | 220 ----------------- .../applications/Applications.tsx | 27 --- .../applications/ApplicationsTable.tsx | 127 ---------- .../hooks/useApplicationActions.tsx | 58 ----- .../hooks/useApplicationsColumns.tsx | 12 +- .../hooks/useDeleteApplications.tsx | 38 --- .../applications/hooks/useDeleteTokens.tsx | 40 ---- .../applications/hooks/useTokensColumns.tsx | 27 --- .../applications/hooks/useTokensFilters.tsx | 23 -- frontend/awx/main/AwxRoutes.tsx | 7 - .../main/routes/useAwxApplicationsRoutes.tsx | 76 ------ frontend/awx/main/useAwxNavigation.tsx | 5 +- 23 files changed, 3 insertions(+), 1577 deletions(-) delete mode 100644 frontend/awx/administration/applications/ApplicationForm.cy.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationForm.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.cy.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationPage.cy.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationPage.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.cy.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.cy.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.tsx delete mode 100644 frontend/awx/administration/applications/Applications.cy.tsx delete mode 100644 frontend/awx/administration/applications/Applications.tsx delete mode 100644 frontend/awx/administration/applications/ApplicationsTable.tsx delete mode 100644 frontend/awx/administration/applications/hooks/useApplicationActions.tsx delete mode 100644 frontend/awx/administration/applications/hooks/useDeleteApplications.tsx delete mode 100644 frontend/awx/administration/applications/hooks/useDeleteTokens.tsx delete mode 100644 frontend/awx/administration/applications/hooks/useTokensColumns.tsx delete mode 100644 frontend/awx/administration/applications/hooks/useTokensFilters.tsx delete mode 100644 frontend/awx/main/routes/useAwxApplicationsRoutes.tsx diff --git a/frontend/awx/administration/activity-stream/hooks/useGetActivityStreamRoute.tsx b/frontend/awx/administration/activity-stream/hooks/useGetActivityStreamRoute.tsx index 75687279e7..7bf40e97df 100644 --- a/frontend/awx/administration/activity-stream/hooks/useGetActivityStreamRoute.tsx +++ b/frontend/awx/administration/activity-stream/hooks/useGetActivityStreamRoute.tsx @@ -30,8 +30,6 @@ export function useGetActivityStreamRoute(resource?: string | null): AwxRoute | return AwxRoute.WorkflowApprovalDetails; case 'management_job': return AwxRoute.ManagementJobSchedules; - case 'o_auth2_application': - return AwxRoute.ApplicationDetails; case 'organization': return AwxRoute.OrganizationDetails; case 'team': diff --git a/frontend/awx/administration/applications/ApplicationForm.cy.tsx b/frontend/awx/administration/applications/ApplicationForm.cy.tsx deleted file mode 100644 index b1a139b3c2..0000000000 --- a/frontend/awx/administration/applications/ApplicationForm.cy.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import { AwxItemsResponse } from '../../common/AwxItemsResponse'; -import { Application } from '../../interfaces/Application'; -import { Organization } from '../../interfaces/Organization'; -import { CreateApplication, EditApplication } from './ApplicationForm'; - -describe('Create Edit Application Form', () => { - describe('Create Application', () => { - it('should validate required fields on save', () => { - cy.mount( app} />); - cy.clickButton(/^Create application$/); - cy.contains('Name is required.').should('be.visible'); - cy.contains('Organization is required.').should('be.visible'); - cy.contains('Authorization grant type is required.').should('be.visible'); - cy.contains('Client type is required.').should('be.visible'); - }); - - it('should create application without redirect URI', () => { - cy.intercept( - { method: 'GET', url: '/api/v2/organizations/*' }, - { fixture: 'organizations.json' } - ); - cy.intercept('POST', '/api/v2/applications/', { - statusCode: 201, - fixture: 'application.json', - }).as('createApplication'); - cy.mount( app} />); - cy.get('[data-cy="name"]').type('Create Application'); - cy.get('[data-cy="description"]').type('mock application description'); - cy.selectSingleSelectOption('[data-cy="organization"]', 'Default'); - cy.selectDropdownOptionByResourceName('authorization-grant-type', 'Password'); - cy.selectDropdownOptionByResourceName('client-type', 'Confidential'); - cy.clickButton(/^Create application$/); - cy.wait('@createApplication') - .its('request.body') - .then((createdApplication: Application) => { - expect(createdApplication).to.deep.equal({ - name: 'Create Application', - description: 'mock application description', - organization: 1, - authorization_grant_type: 'password', - client_type: 'confidential', - }); - }); - }); - - it('should create application with redirect URI', () => { - cy.intercept( - { method: 'GET', url: '/api/v2/organizations/*' }, - { fixture: 'organizations.json' } - ); - cy.intercept('POST', '/api/v2/applications/', { - statusCode: 201, - fixture: 'application.json', - }).as('createApplication'); - cy.mount( app} />); - cy.get('[data-cy="name"]').type('Create Application'); - cy.get('[data-cy="description"]').type('mock application description'); - cy.selectSingleSelectOption('[data-cy="organization"]', 'Default'); - cy.selectDropdownOptionByResourceName('authorization-grant-type', 'Authorization code'); - cy.selectDropdownOptionByResourceName('client-type', 'Confidential'); - cy.get('[data-cy="redirect-uris"]').type('https://www.google.com'); - cy.clickButton(/^Create application$/); - cy.wait('@createApplication') - .its('request.body') - .then((createdApplication: Application) => { - expect(createdApplication).to.deep.equal({ - name: 'Create Application', - description: 'mock application description', - organization: 1, - authorization_grant_type: 'authorization-code', - client_type: 'confidential', - redirect_uris: 'https://www.google.com', - }); - }); - }); - - it('create application should show field error if URIs is empty and grant type is auth code', () => { - cy.intercept( - { method: 'GET', url: '/api/v2/organizations/*' }, - { fixture: 'organizations.json' } - ); - cy.intercept('POST', '/api/v2/applications/', { - statusCode: 201, - fixture: 'application.json', - }).as('createApplication'); - cy.mount( app} />); - cy.get('[data-cy="name"]').type('Create Application'); - cy.get('[data-cy="description"]').type('mock application description'); - cy.selectSingleSelectOption('[data-cy="organization"]', 'Default'); - cy.selectDropdownOptionByResourceName('authorization-grant-type', 'Authorization code'); - cy.selectDropdownOptionByResourceName('client-type', 'Confidential'); - cy.get('[data-cy="redirect-uris"]').clear(); - cy.clickButton(/^Create application$/); - cy.get('.pf-v5-c-helper-text__item-text').contains('Redirect uris is required.'); - }); - }); - - describe('Edit Application', () => { - beforeEach(() => { - cy.intercept( - { method: 'GET', url: '/api/v2/applications/*/' }, - { fixture: 'application.json' } - ); - - cy.fixture('organizations').then((organizations: AwxItemsResponse) => { - cy.intercept({ method: 'GET', url: '/api/v2/organizations/*' }, { body: organizations }); - for (const organization of organizations.results) { - cy.intercept( - { method: 'GET', url: `/api/v2/organizations/${organization.id}` }, - { body: organization } - ); - } - }); - }); - - it('should preload the form with current values', () => { - cy.mount(); - cy.verifyPageTitle('Edit Application'); - cy.get('[data-cy="name"]').should('have.value', 'test'); - cy.get('[data-cy="description"]').should('have.value', 'hello'); - cy.get('[data-cy="organization"]').should('contain', 'Default'); - cy.get('[data-cy="authorization-grant-type-form-group"]').contains('Authorization code'); - cy.get('button[disabled]').should('exist'); - cy.get('[data-cy="client-type-form-group"]').contains('Confidential'); - }); - - it('should edit application with redirect URI', () => { - cy.intercept('PATCH', '/api/v2/applications/*', { - statusCode: 201, - fixture: 'application.json', - }).as('editApplication'); - cy.mount(); - cy.get('[data-cy="name"]').should('have.value', 'test'); - cy.get('[data-cy="name"]').clear(); - cy.get('[data-cy="name"]').type('Edited Application'); - cy.get('[data-cy="description"]').should('have.value', 'hello'); - cy.get('[data-cy="description"]').clear(); - cy.get('[data-cy="description"]').type('Edited Description'); - cy.get('[data-cy="organization"]').should('contain', 'Default'); - cy.selectSingleSelectOption('[data-cy="organization"]', 'Default'); - cy.get('[data-cy="client-type-form-group"]').contains('Confidential'); - cy.selectDropdownOptionByResourceName('client-type', 'Public'); - cy.get('[data-cy="redirect-uris"]').should('have.value', 'https://www.google.com'); - cy.get('[data-cy="redirect-uris"]').clear(); - cy.get('[data-cy="redirect-uris"]').type('https://www.google-edited.com'); - cy.clickButton(/^Save application$/); - cy.wait('@editApplication') - .its('request.body') - .then((editedApplication: Application) => { - expect(editedApplication.name).to.equal('Edited Application'); - expect(editedApplication.description).to.equal('Edited Description'); - expect(editedApplication.authorization_grant_type).to.equal('authorization-code'); - expect(editedApplication.client_type).to.equal('public'); - expect(editedApplication.redirect_uris).to.equal('https://www.google-edited.com'); - }); - }); - - it('should edit application without redirect URI', () => { - cy.fixture('application') - .then((application: Application) => { - application.authorization_grant_type = 'password'; - return application; - }) - .then((application) => { - cy.intercept('PATCH', '/api/v2/applications/*', { - statusCode: 201, - body: application, - }).as('editApplication'); - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { body: application } - ); - cy.mount(, { - path: '/administration/applications/:id/*', - initialEntries: ['/administration/applications/2/edit'], - }); - cy.get('[data-cy="name"]').should('have.value', 'test'); - cy.get('[data-cy="name"]').clear(); - cy.get('[data-cy="name"]').type('Edited Application'); - cy.get('[data-cy="description"]').should('have.value', 'hello'); - cy.get('[data-cy="description"]').clear(); - cy.get('[data-cy="description"]').type('Edited Description'); - cy.get('[data-cy="organization"]').should('contain', 'Default'); - cy.selectSingleSelectOption('[data-cy="organization"]', 'Default'); - cy.get('[data-cy="client-type-form-group"]').contains('Confidential'); - cy.selectDropdownOptionByResourceName('client-type', 'Public'); - cy.get('[data-cy="redirect-uris"]').should('have.value', 'https://www.google.com'); - cy.get('[data-cy="redirect-uris"]').clear(); - cy.clickButton(/^Save application$/); - cy.wait('@editApplication') - .its('request.body') - .then((editedApplication: Application) => { - expect(editedApplication.name).to.equal('Edited Application'); - expect(editedApplication.description).to.equal('Edited Description'); - expect(editedApplication.authorization_grant_type).to.equal('password'); - expect(editedApplication.client_type).to.equal('public'); - }); - }); - }); - - it('edit application should show field error if URIs is empty and grant type is auth code', () => { - cy.intercept('PATCH', '/api/v2/applications/*', { - statusCode: 201, - fixture: 'application.json', - }).as('editApplication'); - cy.mount(); - cy.get('[data-cy="name"]').should('have.value', 'test'); - cy.get('[data-cy="name"]').clear(); - cy.get('[data-cy="name"]').type('Edited Application'); - cy.get('[data-cy="description"]').should('have.value', 'hello'); - cy.get('[data-cy="description"]').clear(); - cy.get('[data-cy="description"]').type('Edited Description'); - cy.get('[data-cy="organization"]').should('contain', 'Default'); - cy.selectSingleSelectOption('[data-cy="organization"]', 'Default'); - cy.get('[data-cy="client-type-form-group"]').contains('Confidential'); - cy.selectDropdownOptionByResourceName('client-type', 'Public'); - cy.get('[data-cy="redirect-uris"]').should('have.value', 'https://www.google.com'); - cy.get('[data-cy="redirect-uris"]').clear(); - cy.clickButton(/^Save application$/); - cy.get('.pf-v5-c-helper-text__item-text').contains('Redirect uris is required'); - }); - }); -}); diff --git a/frontend/awx/administration/applications/ApplicationForm.tsx b/frontend/awx/administration/applications/ApplicationForm.tsx deleted file mode 100644 index bf36f2ca53..0000000000 --- a/frontend/awx/administration/applications/ApplicationForm.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { useWatch } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; -import { useNavigate, useParams } from 'react-router-dom'; -import useSWR from 'swr'; -import { - PageFormSelect, - PageFormSubmitHandler, - PageHeader, - PageLayout, - useGetPageUrl, - usePageNavigate, -} from '../../../../framework'; -import { PageFormTextInput } from '../../../../framework/PageForm/Inputs/PageFormTextInput'; -import { requestGet, requestPatch, swrOptions } from '../../../common/crud/Data'; -import { usePostRequest } from '../../../common/crud/usePostRequest'; -import { PageFormSelectOrganization } from '../../access/organizations/components/PageFormOrganizationSelect'; -import { AwxPageForm } from '../../common/AwxPageForm'; -import { awxAPI } from '../../common/api/awx-utils'; -import { Application } from '../../interfaces/Application'; -import { AwxRoute } from '../../main/AwxRoutes'; - -const ClientType = { - Confidential: 'confidential', - Public: 'public', -}; - -const AuthorizationType = { - AuthorizationCode: 'authorization-code', - Password: 'password', -}; - -export function CreateApplication(props: { onSuccessfulCreate: (app: Application) => void }) { - const { t } = useTranslation(); - const navigate = useNavigate(); - const pageNavigate = usePageNavigate(); - const postRequest = usePostRequest(); - const onSubmit: PageFormSubmitHandler = async (application: Application) => { - const newApplication = await postRequest(awxAPI`/applications/`, application); - if (props.onSuccessfulCreate) props.onSuccessfulCreate(newApplication); - pageNavigate(AwxRoute.ApplicationDetails, { params: { id: newApplication.id } }); - }; - - const onCancel = () => navigate(-1); - const getPageUrl = useGetPageUrl(); - - return ( - - - - - - - ); -} - -export function EditApplication() { - const { t } = useTranslation(); - const navigate = useNavigate(); - const pageNavigate = usePageNavigate(); - const params = useParams<{ id?: string }>(); - const id = Number(params.id); - const { data: application } = useSWR( - awxAPI`/applications/${id.toString()}/`, - requestGet, - swrOptions - ); - - const onSubmit: PageFormSubmitHandler = async ( - application: Application, - setError, - setFieldError - ) => { - if ( - application.authorization_grant_type === 'authorization-code' && - (application.redirect_uris === undefined || application.redirect_uris === '') - ) { - setFieldError('redirect_uris', { - message: t('Need to pass a redirect URI if grant type is authorization code'), - }); - return false; - } - const editedApplication = await requestPatch( - awxAPI`/applications/${id.toString()}/`, - application - ); - pageNavigate(AwxRoute.ApplicationDetails, { params: { id: editedApplication.id } }); - }; - - const getPageUrl = useGetPageUrl(); - - const onCancel = () => navigate(-1); - - if (!application) { - return ( - - - - ); - } - - return ( - - - - submitText={t('Save application')} - onSubmit={onSubmit} - cancelText={t('Cancel')} - onCancel={onCancel} - defaultValue={application} - > - - - - ); -} - -function ApplicationInputs(props: { mode: 'create' | 'edit' }) { - const { mode } = props; - const { t } = useTranslation(); - const authorizationGrantType = useWatch({ - name: 'authorization_grant_type', - }); - return ( - <> - - name="name" - label={t('Name')} - placeholder={t('Enter a name')} - isRequired - maxLength={150} - /> - - name="description" - label={t('Description')} - placeholder={t('Enter a description')} - /> - name="organization" isRequired /> - - isReadOnly={mode === 'edit'} - name="authorization_grant_type" - label={t('Authorization grant type')} - placeholderText={t('Select a grant type')} - options={[ - { - label: t('Authorization code'), - value: AuthorizationType.AuthorizationCode, - }, - { - label: t('Password'), - value: AuthorizationType.Password, - }, - ]} - isRequired - /> - - name="client_type" - label={t('Client type')} - placeholderText={t('Select a client type')} - options={[ - { - label: t('Confidential'), - value: ClientType.Confidential, - }, - { - label: t('Public'), - value: ClientType.Public, - }, - ]} - isRequired - /> - - name="redirect_uris" - label={t('Redirect URIs')} - placeholder={t('Enter a redirect URI')} - isRequired={Boolean(authorizationGrantType === 'authorization-code')} - /> - - ); -} diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.cy.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.cy.tsx deleted file mode 100644 index f1a427cafc..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.cy.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable i18next/no-literal-string */ -import { Application } from '../../../interfaces/Application'; -import { ApplicationClientSecretModal } from './ApplicationClientSecretModal'; - -describe('ApplicationClientSecretModal', () => { - let application: Application; - - beforeEach(() => { - cy.fixture('application.json').then((app: Application) => { - application = app; - }); - }); - it('Displays warning', () => { - cy.mount( - undefined} - /> - ); - cy.get('.pf-v5-c-alert__title').should( - 'contain.text', - 'This is the only time the client secret will be shown.' - ); - }); - it('Displays name', () => { - cy.mount( - undefined} - /> - ); - cy.get('[data-cy="name"] > .pf-v5-c-description-list__text').should('have.text', 'test'); - }); - it('Displays client id', () => { - cy.mount( - undefined} - /> - ); - cy.get('input[id="text-input-4"]').should('have.value', application.client_id); - }); - it('Displays client secret', () => { - cy.mount( - undefined} - /> - ); - cy.get('input[id="text-input-7"]').should('have.value', application.client_secret); - }); -}); diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.tsx deleted file mode 100644 index 109875fcec..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationClientSecretModal.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Modal, ClipboardCopy, ClipboardCopyVariant } from '@patternfly/react-core'; -import { t } from 'i18next'; -import { SetStateAction } from 'react'; -import { PageDetails, PageDetail } from '../../../../../framework'; -import { Application } from '../../../interfaces/Application'; - -export function ApplicationClientSecretModal(props: { - onClose: (value: SetStateAction) => void; - applicationModalSource: Application; -}) { - const { applicationModalSource } = props; - return ( - props.onClose(undefined)} - hasNoBodyWrapper - > - - {applicationModalSource.name} - - - {applicationModalSource.client_id} - - - - - {applicationModalSource.client_secret} - - - - - ); -} diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationPage.cy.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationPage.cy.tsx deleted file mode 100644 index 900ee3caf1..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationPage.cy.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable i18next/no-literal-string */ -import { Application } from '../../../interfaces/Application'; -import { ApplicationPage } from './ApplicationPage'; - -describe('ApplicationPage', () => { - beforeEach(() => { - cy.intercept( - { method: 'GET', url: '/api/v2/applications/*/' }, - { fixture: 'application.json' } - ); - }); - it('Displays breadcrumbs back to Applications list page', () => { - cy.mount(); - cy.get('.pf-v5-c-tabs__item').eq(0).should('have.text', 'Back to OAuth Applications'); - }); - it('Should show enabled edit button', () => { - cy.mount(); - cy.get('[data-cy="edit-application"]').should('have.attr', 'aria-disabled', 'false'); - }); - it('Should show enabled delete button', () => { - cy.mount(); - cy.get('[data-cy="delete-application"]').should('have.attr', 'aria-disabled', 'false'); - }); - it('Should hide edit button', () => { - cy.fixture('application') - .then((application: Application) => { - application.summary_fields.user_capabilities.edit = false; - return application; - }) - .then((application: Application) => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { body: application } - ); - }) - .then(() => { - cy.mount(); - }) - .then(() => { - cy.get('[data-cy="edit-application"]').should('not.exist'); - }); - }); - - it('Should hide delete button', () => { - cy.fixture('application') - .then((application: Application) => { - application.summary_fields.user_capabilities.delete = false; - return application; - }) - .then((application: Application) => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { body: application } - ); - }) - .then(() => { - cy.mount(); - }) - .then(() => { - cy.get('[data-cy="delete-application"]').should('not.exist'); - }); - }); -}); diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationPage.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationPage.tsx deleted file mode 100644 index f088a01a3d..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationPage.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { DropdownPosition } from '@patternfly/react-core/deprecated'; -import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; -import { - PageActions, - PageHeader, - PageLayout, - useGetPageUrl, - usePageNavigate, -} from '../../../../../framework'; -import { LoadingPage } from '../../../../../framework/components/LoadingPage'; -import { PageRoutedTabs } from '../../../../common/PageRoutedTabs'; -import { useGetItem } from '../../../../common/crud/useGet'; -import { useViewActivityStream } from '../../../access/common/useViewActivityStream'; -import { AwxError } from '../../../common/AwxError'; -import { awxAPI } from '../../../common/api/awx-utils'; -import { Application } from '../../../interfaces/Application'; -import { AwxRoute } from '../../../main/AwxRoutes'; -import { useApplicationActions } from '../hooks/useApplicationActions'; - -export function ApplicationPage() { - const { t } = useTranslation(); - const activityStream = useViewActivityStream('o_auth2_application'); - const params = useParams<{ id: string }>(); - const { - error, - data: application, - refresh, - } = useGetItem(awxAPI`/applications`, params.id); - - const getPageUrl = useGetPageUrl(); - const pageNavigate = usePageNavigate(); - - const itemActions = useApplicationActions({ - onApplicationsDeleted: () => pageNavigate(AwxRoute.Applications), - }); - - if (error) return ; - if (!application) return ; - - return ( - - - } - /> - - - ); -} diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.cy.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.cy.tsx deleted file mode 100644 index 7b9d9b30a6..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.cy.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -/* eslint-disable i18next/no-literal-string */ -import mockApplication from '../../../../../cypress/fixtures/application.json'; -import { formatDateString } from '../../../../../framework/utils/formatDateString'; -import { Application } from '../../../interfaces/Application'; -import { ApplicationDetailInner as ApplicationDetails } from './ApplicationPageDetails'; - -describe('ApplicationDetails', () => { - it('Component renders and displays Application', () => { - cy.mount(); - }); - it('Render application detail fields', () => { - cy.mount(); - cy.get('[data-cy="name"]').should('have.text', 'test'); - cy.get('[data-cy="client-id"]').should('have.text', 'WLIG801bgwcrnAlFdg4YStryRjprRVXFHXsLL7od'); - cy.get('[data-cy="redirect-uris"]').should('have.text', 'https://www.google.com'); - cy.get('[data-cy="organization"]').should('have.text', 'Default'); - cy.get('[data-cy="authorization-grant-type"]').should('have.text', 'authorization-code'); - cy.get('[data-cy="client-type"]').should('have.text', 'confidential'); - cy.get('[data-cy="created"]').should('have.text', formatDateString(mockApplication.created)); - cy.get('[data-cy="last-modified"]').should( - 'have.text', - formatDateString(mockApplication.modified) - ); - }); -}); diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.tsx deleted file mode 100644 index 54cfb9cc7f..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageDetails.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; -import { - CopyCell, - DateTimeCell, - PageDetail, - PageDetails, - TextCell, - useGetPageUrl, -} from '../../../../../framework'; -import { useGetItem } from '../../../../common/crud/useGet'; -import { awxAPI } from '../../../common/api/awx-utils'; -import { Application } from '../../../interfaces/Application'; -import { AwxRoute } from '../../../main/AwxRoutes'; - -export function ApplicationPageDetails() { - const params = useParams<{ id: string }>(); - const { data: application } = useGetItem(awxAPI`/applications/`, params.id); - return application ? : null; -} - -export function ApplicationDetailInner(props: { application: Application }) { - const { t } = useTranslation(); - const getPageUrl = useGetPageUrl(); - return ( - - {props.application.name} - {props.application.description} - - {props.application.summary_fields.organization.name && ( - - )} - - - {props.application.authorization_grant_type} - - - - - {props.application.redirect_uris} - {props.application.client_type} - - - - - - - - ); -} diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.cy.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.cy.tsx deleted file mode 100644 index ab0587572d..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.cy.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { ApplicationTokens } from './ApplicationPageTokens'; - -describe('Application Tokens', () => { - describe('Non-empty list', () => { - beforeEach(() => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/1/tokens/*', - }, - { - fixture: 'applicationPageTokens.json', - } - ); - }); - - it('Tokens list renders', () => { - cy.mount(); - cy.get('tbody').find('tr').should('have.length', 10); - }); - - it('Filter tokens by name', () => { - cy.mount(); - cy.intercept('api/v2/applications/1/tokens/?user__username__icontains=test*').as( - 'nameFilterRequest' - ); - cy.filterTableByText('test'); - cy.wait('@nameFilterRequest'); - cy.clickButton(/^Clear all filters$/); - }); - - it('Clicking name table header sorts application tokens by name', () => { - cy.mount(); - cy.intercept('/api/v2/applications/1/tokens/?order_by=-user__username*').as( - 'nameDescSortRequest' - ); - cy.clickTableHeader(/^Name$/); - cy.wait('@nameDescSortRequest'); - cy.intercept('/api/v2/applications/1/tokens/?order_by=user__username*').as( - 'nameAscSortRequest' - ); - cy.clickTableHeader(/^Name$/); - cy.wait('@nameAscSortRequest'); - }); - - it('Clicking name table header sorts application tokens by scope', () => { - cy.mount(); - cy.intercept('api/v2/applications/1/tokens/?order_by=scope*').as('nameAscSortRequest'); - cy.clickTableHeader(/^Scope$/); - cy.wait('@nameAscSortRequest'); - cy.intercept('api/v2/applications/1/tokens/?order_by=-scope*').as('nameDescSortRequest'); - cy.clickTableHeader(/^Scope$/); - cy.wait('@nameDescSortRequest'); - }); - - it('Clicking name table header sorts application tokens by expires', () => { - cy.mount(); - cy.intercept('api/v2/applications/1/tokens/?order_by=expires*').as('nameAscSortRequest'); - cy.clickTableHeader(/^Expires$/); - cy.wait('@nameAscSortRequest'); - cy.intercept('api/v2/applications/1/tokens/?order_by=-expires*').as('nameDescSortRequest'); - cy.clickTableHeader(/^Expires$/); - cy.wait('@nameDescSortRequest'); - }); - - it('Displays error if tokens are not successfully loaded', () => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/1/tokens/*', - }, - { - statusCode: 500, - } - ); - cy.mount(); - cy.contains('Error loading tokens'); - }); - }); - - describe('Empty list', () => { - beforeEach(() => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/1/tokens/*', - }, - { - fixture: 'emptyList.json', - } - ).as('emptyList'); - }); - it('Empty state is displayed correctly', () => { - cy.mount(); - cy.contains(/^There are currently no tokens associated with this application$/); - cy.contains(/^You can create a token from your user page.$/); - }); - }); -}); diff --git a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.tsx b/frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.tsx deleted file mode 100644 index d1a5c4a37d..0000000000 --- a/frontend/awx/administration/applications/ApplicationPage/ApplicationPageTokens.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { TrashIcon } from '@patternfly/react-icons'; -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; -import { - IPageAction, - PageActionSelection, - PageActionType, - PageLayout, - PageTable, -} from '../../../../../framework'; -import { awxAPI } from '../../../common/api/awx-utils'; -import { useAwxView } from '../../../common/useAwxView'; -import { Token } from '../../../interfaces/Token'; -import { useDeleteTokens } from '../hooks/useDeleteTokens'; -import { useTokensColumns } from '../hooks/useTokensColumns'; -import { useTokensFilters } from '../hooks/useTokensFilters'; - -export function ApplicationTokens() { - const { t } = useTranslation(); - const tableColumns = useTokensColumns(); - const toolbarFilters = useTokensFilters(); - const params = useParams<{ id: string }>(); - const view = useAwxView({ - url: awxAPI`/applications/${params.id ?? ''}/tokens/`, - tableColumns, - toolbarFilters, - }); - const deleteTokens = useDeleteTokens(view.unselectItemsAndRefresh); - - const toolbarActions = useMemo[]>( - () => [ - { - type: PageActionType.Button, - selection: PageActionSelection.Multiple, - icon: TrashIcon, - label: t('Delete tokens'), - onClick: deleteTokens, - isDanger: true, - }, - ], - [deleteTokens, t] - ); - - const rowActions = useMemo[]>( - () => [ - { - type: PageActionType.Button, - selection: PageActionSelection.Single, - icon: TrashIcon, - label: t('Delete token'), - onClick: (token) => deleteTokens([token]), - isDanger: true, - }, - ], - [t, deleteTokens] - ); - - return ( - - - id="awx-applications-token-table" - toolbarFilters={toolbarFilters} - toolbarActions={toolbarActions} - tableColumns={tableColumns} - rowActions={rowActions} - errorStateTitle={t('Error loading tokens')} - emptyStateTitle={t('There are currently no tokens associated with this application')} - emptyStateDescription={t('You can create a token from your user page.')} - {...view} - defaultSubtitle={t('Token')} - /> - - ); -} diff --git a/frontend/awx/administration/applications/Applications.cy.tsx b/frontend/awx/administration/applications/Applications.cy.tsx deleted file mode 100644 index 560c86a91c..0000000000 --- a/frontend/awx/administration/applications/Applications.cy.tsx +++ /dev/null @@ -1,220 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import * as useOptions from '../../../common/crud/useOptions'; -import { Application } from '../../interfaces/Application'; -import { Applications } from './Applications'; - -describe('Applications List', () => { - describe('Non-empty list', () => { - beforeEach(() => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { - fixture: 'applications.json', - } - ); - cy.intercept('OPTIONS', '/api/v2/applications/', { fixture: 'mock_options.json' }).as( - 'getOptions' - ); - }); - - it('Applications list renders', () => { - cy.mount(); - cy.verifyPageTitle('OAuth Applications'); - cy.get('tbody').find('tr').should('have.length', 10); - }); - - it('Filter applications by name', () => { - cy.mount(); - cy.intercept('api/v2/applications/?name=test*').as('nameFilterRequest'); - cy.filterTableByMultiSelect('name', ['test']); - cy.wait('@nameFilterRequest'); - cy.clearAllFilters(); - }); - - it('Filter applications by description', () => { - cy.mount(); - cy.intercept('api/v2/applications/?description__icontains=hello*').as('descFilterRequest'); - cy.filterTableByTypeAndText(/^Description$/, 'hello'); - cy.wait('@descFilterRequest'); - cy.clickButton(/^Clear all filters$/); - }); - - it('Create application button is disabled if the user does not have permission to create application', () => { - cy.mount(); - cy.contains('button', /^Create application$/).should('have.attr', 'aria-disabled', 'true'); - }); - - it('Delete application row action is disabled if the user does not have permission to delete application', () => { - cy.fixture('applications') - .then((applications) => { - for (let i = 0; i < (applications.results as Application[]).length; i++) { - applications.results[i].summary_fields.user_capabilities.delete = false; - } - return applications; - }) - .then((applications) => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { body: applications } - ); - }) - .then(() => { - cy.mount(); - }) - .then(() => { - cy.contains('tr', 'test').within(() => { - cy.get('button.toggle-kebab').click(); - }); - cy.contains('#delete-application', /^Delete application$/).should( - 'have.attr', - 'aria-disabled', - 'true' - ); - }); - }); - - it('Edit application row action is disabled if the user does not have permission to edit application', () => { - cy.fixture('applications') - .then((applications) => { - for (let i = 0; i < (applications.results as Application[]).length; i++) { - applications.results[i].summary_fields.user_capabilities.edit = false; - } - return applications; - }) - .then((applications) => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { body: applications } - ); - }) - .then(() => { - cy.mount(); - }) - .then(() => { - cy.contains('tr', 'test').within(() => { - cy.get('[data-cy="actions-column-cell"]').within(() => { - cy.get(`[data-cy="edit-application"]`).should('have.attr', 'aria-disabled', 'true'); - }); - }); - }); - }); - - it('Create application button is enabled if the user has permission to create applications', () => { - cy.stub(useOptions, 'useOptions').callsFake(() => ({ - data: { - actions: { - POST: { - name: { - type: 'string', - required: true, - label: 'Name', - max_length: 255, - help_text: 'Name of this application.', - filterable: true, - }, - }, - }, - }, - })); - cy.mount(); - cy.contains('button', /^Create application$/).should('have.attr', 'aria-disabled', 'false'); - }); - - it('Delete application row action is enabled if the user has permission to delete application', () => { - cy.mount(); - cy.contains('tr', 'test').within(() => { - // user_capabilities.delete: false - cy.get('button.toggle-kebab').click(); - }); - cy.contains('#delete-application', /^Delete application$/).should( - 'not.have.attr', - 'aria-disabled', - 'true' - ); - }); - - it('Edit application row action is enabled if the user has permission to edit application', () => { - cy.mount(); - cy.contains('tr', 'test').within(() => { - // user_capabilities.edit: false - cy.get('[data-cy="actions-column-cell"]').within(() => { - cy.get(`[data-cy="edit-application"]`).should('have.attr', 'aria-disabled', 'false'); - }); - }); - }); - - it('Displays error if applications are not successfully loaded', () => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { - statusCode: 500, - } - ); - cy.mount(); - cy.contains('Error loading applications'); - }); - }); - - describe('Empty list', () => { - beforeEach(() => { - cy.intercept( - { - method: 'GET', - url: '/api/v2/applications/*', - }, - { - fixture: 'emptyList.json', - } - ).as('emptyList'); - }); - it('Empty state is displayed correctly for user with permission to create applications', () => { - cy.stub(useOptions, 'useOptions').callsFake(() => ({ - data: { - actions: { - POST: { - name: { - type: 'string', - required: true, - label: 'Name', - max_length: 512, - help_text: 'Name of this application.', - filterable: true, - }, - }, - }, - }, - })); - cy.mount(); - cy.contains(/^There are currently no applications added$/); - cy.contains(/^Please create an application by using the button below.$/); - cy.contains('button', /^Create application$/).should('be.visible'); - }); - it('Empty state is displayed correctly for user without permission to create applications', () => { - cy.stub(useOptions, 'useOptions').callsFake(() => ({ - data: { - actions: {}, - }, - })); - cy.mount(); - cy.contains(/^You do not have permission to create an application.$/); - cy.contains( - /^Please contact your organization administrator if there is an issue with your access.$/ - ); - cy.contains('button', /^Create application$/).should('not.exist'); - }); - }); -}); diff --git a/frontend/awx/administration/applications/Applications.tsx b/frontend/awx/administration/applications/Applications.tsx deleted file mode 100644 index a81e107e6c..0000000000 --- a/frontend/awx/administration/applications/Applications.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { PageHeader, PageLayout } from '../../../../framework'; -import { useAwxConfig } from '../../common/useAwxConfig'; -import { useGetDocsUrl } from '../../common/util/useGetDocsUrl'; -import { ActivityStreamIcon } from '../../common/ActivityStreamIcon'; -import { ApplicationsTable } from './ApplicationsTable'; - -export function Applications() { - const { t } = useTranslation(); - const config = useAwxConfig(); - - return ( - - } - /> - - - ); -} diff --git a/frontend/awx/administration/applications/ApplicationsTable.tsx b/frontend/awx/administration/applications/ApplicationsTable.tsx deleted file mode 100644 index d3f90eb64c..0000000000 --- a/frontend/awx/administration/applications/ApplicationsTable.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { ButtonVariant } from '@patternfly/react-core'; -import { EditIcon, PlusCircleIcon, TrashIcon } from '@patternfly/react-icons'; -import { useMemo } from 'react'; -import { - IPageAction, - PageActionSelection, - PageActionType, - PageTable, - usePageNavigate, -} from '../../../../framework'; -import { useOptions } from '../../../common/crud/useOptions'; -import { - cannotDeleteResource, - cannotDeleteResources, - cannotEditResource, -} from '../../../common/utils/RBAChelpers'; -import { awxAPI } from '../../common/api/awx-utils'; -import { useAwxView } from '../../common/useAwxView'; -import { Application } from '../../interfaces/Application'; -import { ActionsResponse, OptionsResponse } from '../../interfaces/OptionsResponse'; -import { AwxRoute } from '../../main/AwxRoutes'; -import { useApplicationsColumns } from './hooks/useApplicationsColumns'; -import { useApplicationsFilters } from './hooks/useApplicationsFilters'; -import { useDeleteApplications } from './hooks/useDeleteApplications'; - -export function ApplicationsTable() { - const { t } = useTranslation(); - const toolbarFilters = useApplicationsFilters(); - const tableColumns = useApplicationsColumns(); - const view = useAwxView({ - url: awxAPI`/applications/`, - toolbarFilters, - tableColumns, - }); - const deleteApplications = useDeleteApplications(view.unselectItemsAndRefresh); - const pageNavigate = usePageNavigate(); - - const { data } = useOptions>(awxAPI`/applications/`); - const canCreateApplication = Boolean(data && data.actions && data.actions['POST']); - - const toolbarActions = useMemo[]>( - () => [ - { - type: PageActionType.Button, - selection: PageActionSelection.None, - variant: ButtonVariant.primary, - isPinned: true, - icon: PlusCircleIcon, - label: t('Create application'), - isDisabled: canCreateApplication - ? undefined - : t( - 'You do not have permission to create an application. Please contact your system administrator if there is an issue with your access.' - ), - onClick: () => pageNavigate(AwxRoute.CreateApplication), - }, - { type: PageActionType.Seperator }, - { - type: PageActionType.Button, - selection: PageActionSelection.Multiple, - icon: TrashIcon, - label: t('Delete selected applications'), - isDisabled: (applications: Application[]) => cannotDeleteResources(applications, t), - onClick: deleteApplications, - isDanger: true, - }, - ], - [t, canCreateApplication, deleteApplications, pageNavigate] - ); - - const rowActions = useMemo[]>( - () => [ - { - type: PageActionType.Button, - selection: PageActionSelection.Single, - icon: EditIcon, - isPinned: true, - label: t('Edit application'), - isDisabled: (application) => cannotEditResource(application, t), - onClick: (application) => - pageNavigate(AwxRoute.EditApplication, { params: { id: application.id } }), - }, - { type: PageActionType.Seperator }, - { - type: PageActionType.Button, - selection: PageActionSelection.Single, - icon: TrashIcon, - label: t('Delete application'), - isDisabled: (application) => cannotDeleteResource(application, t), - onClick: (application) => deleteApplications([application]), - isDanger: true, - }, - ], - [t, pageNavigate, deleteApplications] - ); - - return ( - - id="awx-applications-table" - toolbarFilters={toolbarFilters} - toolbarActions={toolbarActions} - tableColumns={tableColumns} - rowActions={rowActions} - errorStateTitle={t('Error loading applications')} - emptyStateTitle={ - canCreateApplication - ? t('There are currently no applications added') - : t('You do not have permission to create an application.') - } - emptyStateDescription={ - canCreateApplication - ? t('Please create an application by using the button below.') - : t( - 'Please contact your organization administrator if there is an issue with your access.' - ) - } - emptyStateButtonIcon={} - emptyStateButtonText={canCreateApplication ? t('Create application') : undefined} - emptyStateButtonClick={ - canCreateApplication ? () => pageNavigate(AwxRoute.CreateApplication) : undefined - } - {...view} - defaultSubtitle={t('Application')} - /> - ); -} diff --git a/frontend/awx/administration/applications/hooks/useApplicationActions.tsx b/frontend/awx/administration/applications/hooks/useApplicationActions.tsx deleted file mode 100644 index 5b8c461911..0000000000 --- a/frontend/awx/administration/applications/hooks/useApplicationActions.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { PencilAltIcon, TrashIcon } from '@patternfly/react-icons'; -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { - IPageAction, - PageActionSelection, - PageActionType, - usePageNavigate, -} from '../../../../../framework'; -import { useAwxActiveUser } from '../../../common/useAwxActiveUser'; -import { Application } from '../../../interfaces/Application'; -import { AwxRoute } from '../../../main/AwxRoutes'; -import { useDeleteApplications } from '../hooks/useDeleteApplications'; - -export function useApplicationActions(options: { - onApplicationsDeleted: (applications: Application[]) => void; -}) { - const { activeAwxUser } = useAwxActiveUser(); - const { onApplicationsDeleted } = options; - const { t } = useTranslation(); - const deleteApplications = useDeleteApplications(onApplicationsDeleted); - const pageNavigate = usePageNavigate(); - - return useMemo[]>(() => { - const itemActions: IPageAction[] = [ - { - type: PageActionType.Button, - isHidden: (application) => !application?.summary_fields.user_capabilities.edit, - selection: PageActionSelection.Single, - isPinned: true, - icon: PencilAltIcon, - label: t('Edit application'), - ouiaId: 'application-detail-edit-button', - onClick: (application) => - pageNavigate(AwxRoute.EditApplication, { params: { id: application.id } }), - }, - { type: PageActionType.Seperator }, - { - type: PageActionType.Button, - selection: PageActionSelection.Single, - icon: TrashIcon, - isHidden: (application) => - !application?.summary_fields.user_capabilities.delete || activeAwxUser?.is_system_auditor - ? true - : false, - label: t('Delete application'), - onClick: (application) => { - if (!application) return; - deleteApplications([application]); - }, - ouiaId: 'application-detail-delete-button', - isDanger: true, - isPinned: true, - }, - ]; - return itemActions; - }, [t, pageNavigate, activeAwxUser?.is_system_auditor, deleteApplications]); -} diff --git a/frontend/awx/administration/applications/hooks/useApplicationsColumns.tsx b/frontend/awx/administration/applications/hooks/useApplicationsColumns.tsx index 8aae557ffe..52cf731aca 100644 --- a/frontend/awx/administration/applications/hooks/useApplicationsColumns.tsx +++ b/frontend/awx/administration/applications/hooks/useApplicationsColumns.tsx @@ -1,5 +1,5 @@ -import { useCallback, useMemo } from 'react'; -import { ITableColumn, usePageNavigate } from '../../../../../framework'; +import { useMemo } from 'react'; +import { ITableColumn } from '../../../../../framework'; import { useCreatedColumn, useDescriptionColumn, @@ -14,16 +14,8 @@ export function useApplicationsColumns(options?: { disableSort?: boolean; disableLinks?: boolean; }) { - const pageNavigate = usePageNavigate(); - - const nameClick = useCallback( - (application: Application) => - pageNavigate(AwxRoute.ApplicationDetails, { params: { id: application.id } }), - [pageNavigate] - ); const nameColumn = useNameColumn({ ...options, - onClick: nameClick, }); const descriptionColumn = useDescriptionColumn(); const organizationColumn = useOrganizationNameColumn(AwxRoute.OrganizationDetails, options); diff --git a/frontend/awx/administration/applications/hooks/useDeleteApplications.tsx b/frontend/awx/administration/applications/hooks/useDeleteApplications.tsx deleted file mode 100644 index 2dac670027..0000000000 --- a/frontend/awx/administration/applications/hooks/useDeleteApplications.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { compareStrings } from '../../../../../framework'; -import { useNameColumn } from '../../../../common/columns'; -import { getItemKey, requestDelete } from '../../../../common/crud/Data'; -import { awxAPI } from '../../../common/api/awx-utils'; -import { useAwxBulkConfirmation } from '../../../common/useAwxBulkConfirmation'; -import { Application } from '../../../interfaces/Application'; -import { useApplicationsColumns } from './useApplicationsColumns'; - -export function useDeleteApplications(onComplete: (applications: Application[]) => void) { - const { t } = useTranslation(); - const confirmationColumns = useApplicationsColumns({ - disableLinks: true, - disableSort: true, - }); - const deleteActionNameColumn = useNameColumn({ disableLinks: true, disableSort: true }); - const actionColumns = useMemo(() => [deleteActionNameColumn], [deleteActionNameColumn]); - const bulkAction = useAwxBulkConfirmation(); - const deleteApplications = (applications: Application[]) => { - bulkAction({ - title: t('Permanently delete applications', { count: applications.length }), - confirmText: t('Yes, I confirm that I want to delete these {{count}} applications.', { - count: applications.length, - }), - actionButtonText: t('Delete application', { count: applications.length }), - items: applications.sort((l, r) => compareStrings(l.name, r.name)), - keyFn: getItemKey, - isDanger: true, - confirmationColumns, - actionColumns, - onComplete, - actionFn: (application: Application, signal) => - requestDelete(awxAPI`/applications/${application.id.toString()}/`, signal), - }); - }; - return deleteApplications; -} diff --git a/frontend/awx/administration/applications/hooks/useDeleteTokens.tsx b/frontend/awx/administration/applications/hooks/useDeleteTokens.tsx deleted file mode 100644 index 0b22b8191c..0000000000 --- a/frontend/awx/administration/applications/hooks/useDeleteTokens.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { compareStrings } from '../../../../../framework'; -import { useNameColumn } from '../../../../common/columns'; -import { getItemKey, requestDelete } from '../../../../common/crud/Data'; -import { awxAPI } from '../../../common/api/awx-utils'; -import { useAwxBulkConfirmation } from '../../../common/useAwxBulkConfirmation'; -import { Token } from '../../../interfaces/Token'; -import { useTokensColumns } from './useTokensColumns'; - -export function useDeleteTokens(onComplete: (applications: Token[]) => void) { - const { t } = useTranslation(); - const confirmationColumns = useTokensColumns({ - disableLinks: true, - disableSort: true, - }); - const deleteActionNameColumn = useNameColumn({ disableLinks: true, disableSort: true }); - const actionColumns = useMemo(() => [deleteActionNameColumn], [deleteActionNameColumn]); - const bulkAction = useAwxBulkConfirmation(); - const deleteTokens = (tokens: Token[]) => { - bulkAction({ - title: tokens.length === 1 ? t('Permanently delete token') : t('Permanently delete tokens'), - confirmText: t('Yes, I confirm that I want to delete these {{count}} tokens.', { - count: tokens.length, - }), - actionButtonText: t('Delete token', { count: tokens.length }), - items: tokens.sort((l, r) => - compareStrings(l.summary_fields.user.username, r.summary_fields.user.username) - ), - keyFn: getItemKey, - isDanger: true, - confirmationColumns, - actionColumns, - onComplete, - actionFn: (token: Token, signal) => - requestDelete(awxAPI`/tokens/${token.id.toString()}/`, signal), - }); - }; - return deleteTokens; -} diff --git a/frontend/awx/administration/applications/hooks/useTokensColumns.tsx b/frontend/awx/administration/applications/hooks/useTokensColumns.tsx deleted file mode 100644 index 4a9298f1e8..0000000000 --- a/frontend/awx/administration/applications/hooks/useTokensColumns.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback, useMemo } from 'react'; -import { ITableColumn, usePageNavigate } from '../../../../../framework'; -import { useExpiresColumn, useNameColumn, useScopeColumn } from '../../../../common/columns'; -import { Token } from '../../../interfaces/Token'; -import { AwxRoute } from '../../../main/AwxRoutes'; - -export function useTokensColumns(options?: { disableSort?: boolean; disableLinks?: boolean }) { - const pageNavigate = usePageNavigate(); - - const nameClick = useCallback( - (token: Token) => pageNavigate(AwxRoute.UserDetails, { params: { id: token.user } }), - [pageNavigate] - ); - const nameColumn = useNameColumn({ - onClick: nameClick, - sort: 'user__username', - ...options, - }); - const scopeColumn = useScopeColumn(options); - const expiresColumn = useExpiresColumn(options); - - const tableColumns = useMemo[]>( - () => [nameColumn, scopeColumn, expiresColumn], - [nameColumn, scopeColumn, expiresColumn] - ); - return tableColumns; -} diff --git a/frontend/awx/administration/applications/hooks/useTokensFilters.tsx b/frontend/awx/administration/applications/hooks/useTokensFilters.tsx deleted file mode 100644 index 701d1d11f3..0000000000 --- a/frontend/awx/administration/applications/hooks/useTokensFilters.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { IToolbarFilter, ToolbarFilterType } from '../../../../../framework'; - -export function useTokensFilters() { - const nameToolbarFilter = useTokenUserToolbarFilter(); - const toolbarFilters = useMemo(() => [nameToolbarFilter], [nameToolbarFilter]); - return toolbarFilters; -} - -function useTokenUserToolbarFilter() { - const { t } = useTranslation(); - return useMemo( - () => ({ - key: 'name', - label: t('Name'), - type: ToolbarFilterType.MultiText, - query: 'user__username__icontains', - comparison: 'contains', - }), - [t] - ); -} diff --git a/frontend/awx/main/AwxRoutes.tsx b/frontend/awx/main/AwxRoutes.tsx index 9ba47d0273..001bacf155 100644 --- a/frontend/awx/main/AwxRoutes.tsx +++ b/frontend/awx/main/AwxRoutes.tsx @@ -253,13 +253,6 @@ export enum AwxRoute { InstancePeers = 'awx-instance-peers', InstanceListenerAddresses = 'awx-instance-listener-addresses', - Applications = 'awx-applications', - ApplicationPage = 'awx-application-page', - ApplicationDetails = 'awx-application-details', - ApplicationTokens = 'awx-application-tokens', - CreateApplication = 'awx-create-application', - EditApplication = 'awx-edit-application', - ExecutionEnvironments = 'awx-execution-environments', ExecutionEnvironmentPage = 'awx-execution-environments-page', ExecutionEnvironmentDetails = 'awx-execution-environments-details', diff --git a/frontend/awx/main/routes/useAwxApplicationsRoutes.tsx b/frontend/awx/main/routes/useAwxApplicationsRoutes.tsx deleted file mode 100644 index 049b7be938..0000000000 --- a/frontend/awx/main/routes/useAwxApplicationsRoutes.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PageNavigationItem } from '../../../../framework'; -import { - CreateApplication, - EditApplication, -} from '../../administration/applications/ApplicationForm'; -import { ApplicationClientSecretModal } from '../../administration/applications/ApplicationPage/ApplicationClientSecretModal'; -import { ApplicationPage } from '../../administration/applications/ApplicationPage/ApplicationPage'; -import { ApplicationPageDetails } from '../../administration/applications/ApplicationPage/ApplicationPageDetails'; -import { ApplicationTokens } from '../../administration/applications/ApplicationPage/ApplicationPageTokens'; -import { Applications } from '../../administration/applications/Applications'; -import { Application } from '../../interfaces/Application'; -import { AwxRoute } from '../AwxRoutes'; - -export function useAwxApplicationsRoutes() { - const { t } = useTranslation(); - const [applicationModalSource, setApplicationModalSource] = useState(); - - const applicationsRoutes = useMemo( - () => ({ - id: AwxRoute.Applications, - label: t('OAuth Applications'), - path: 'applications', - children: [ - { - id: AwxRoute.CreateApplication, - path: 'create', - element: ( - setApplicationModalSource(app)} - /> - ), - }, - { - id: AwxRoute.EditApplication, - path: ':id/edit', - element: , - }, - { - id: AwxRoute.ApplicationPage, - path: ':id', - element: ( - <> - - {applicationModalSource && ( - - )} - - ), - children: [ - { - id: AwxRoute.ApplicationDetails, - path: 'details', - element: , - }, - { - id: AwxRoute.ApplicationTokens, - path: 'tokens', - element: , - }, - ], - }, - { - path: '', - element: , - }, - ], - }), - [t, applicationModalSource] - ); - return applicationsRoutes; -} diff --git a/frontend/awx/main/useAwxNavigation.tsx b/frontend/awx/main/useAwxNavigation.tsx index 91a5a1db55..8b8f9b7057 100644 --- a/frontend/awx/main/useAwxNavigation.tsx +++ b/frontend/awx/main/useAwxNavigation.tsx @@ -21,7 +21,6 @@ import { AwxOverview } from '../overview/AwxOverview'; import { HostMetrics } from '../views/jobs/HostMetrics'; import { AwxRoute } from './AwxRoutes'; import { useAwxActivityStreamRoutes } from './routes/useAwxActivityStreamRoutes'; -import { useAwxApplicationsRoutes } from './routes/useAwxApplicationsRoutes'; import { useAwxCredentialRoutes } from './routes/useAwxCredentialRoutes'; import { useAwxCredentialTypesRoutes } from './routes/useAwxCredentialTypesRoutes'; import { useAwxExecutionEnvironmentRoutes } from './routes/useAwxExecutionEnironmentRoutes'; @@ -58,7 +57,6 @@ export function useAwxNavigation() { const awxManagementJobsRoutes = useAwxManagementJobsRoutes(); const awxInstanceGroupsRoutes = useAwxInstanceGroupsRoutes(); const awxInstancesRoutes = useAwxInstancesRoutes(); - const awxApplicationsRoutes = useAwxApplicationsRoutes(); const awxExecutionEnvironmentsRoutes = useAwxExecutionEnvironmentRoutes(); const awxCredentialTypesRoutes = useAwxCredentialTypesRoutes(); const { activeAwxUser } = useAwxActiveUser(); @@ -137,9 +135,8 @@ export function useAwxNavigation() { awxWorkflowApprovalRoutes, awxNotificationsRoutes, awxManagementJobsRoutes, - awxApplicationsRoutes, ] - : [awxActivityStreamRoutes, awxWorkflowApprovalRoutes, awxApplicationsRoutes], + : [awxActivityStreamRoutes, awxWorkflowApprovalRoutes], }, ]; const accessItems: PageNavigationItem[] = [