mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
Merge pull request #5664 from marshmalien/5276-credential-details
Add Credential Detail view Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
6fa4d6462d
@ -1136,7 +1136,7 @@ ManagedCredentialType(
|
||||
'help_text': ugettext_noop('The OpenShift or Kubernetes API Endpoint to authenticate with.')
|
||||
},{
|
||||
'id': 'bearer_token',
|
||||
'label': ugettext_noop('API authentication bearer token.'),
|
||||
'label': ugettext_noop('API authentication bearer token'),
|
||||
'type': 'string',
|
||||
'secret': True,
|
||||
},{
|
||||
|
||||
118
awx/ui_next/src/screens/Credential/Credential.jsx
Normal file
118
awx/ui_next/src/screens/Credential/Credential.jsx
Normal file
@ -0,0 +1,118 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
import {
|
||||
Switch,
|
||||
useParams,
|
||||
useHistory,
|
||||
useLocation,
|
||||
Route,
|
||||
Redirect,
|
||||
Link,
|
||||
} from 'react-router-dom';
|
||||
import { TabbedCardHeader } from '@components/Card';
|
||||
import CardCloseButton from '@components/CardCloseButton';
|
||||
import ContentError from '@components/ContentError';
|
||||
import RoutedTabs from '@components/RoutedTabs';
|
||||
import CredentialDetail from './CredentialDetail';
|
||||
import { CredentialsAPI } from '@api';
|
||||
|
||||
function Credential({ i18n, setBreadcrumb }) {
|
||||
const [credential, setCredential] = useState(null);
|
||||
const [contentError, setContentError] = useState(null);
|
||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const { id } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchData() {
|
||||
try {
|
||||
const { data } = await CredentialsAPI.readDetail(id);
|
||||
setBreadcrumb(data);
|
||||
setCredential(data);
|
||||
} catch (error) {
|
||||
setContentError(error);
|
||||
} finally {
|
||||
setHasContentLoading(false);
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
}, [id, setBreadcrumb]);
|
||||
|
||||
const tabsArray = [
|
||||
{ name: i18n._(t`Details`), link: `/credentials/${id}/details`, id: 0 },
|
||||
{ name: i18n._(t`Access`), link: `/credentials/${id}/access`, id: 1 },
|
||||
{
|
||||
name: i18n._(t`Notifications`),
|
||||
link: `/credentials/${id}/notifications`,
|
||||
id: 2,
|
||||
},
|
||||
];
|
||||
|
||||
let cardHeader = hasContentLoading ? null : (
|
||||
<TabbedCardHeader>
|
||||
<RoutedTabs history={history} tabsArray={tabsArray} />
|
||||
<CardCloseButton linkTo="/credentials" />
|
||||
</TabbedCardHeader>
|
||||
);
|
||||
|
||||
if (location.pathname.endsWith('edit') || location.pathname.endsWith('add')) {
|
||||
cardHeader = null;
|
||||
}
|
||||
|
||||
if (!hasContentLoading && contentError) {
|
||||
return (
|
||||
<PageSection>
|
||||
<Card className="awx-c-card">
|
||||
<ContentError error={contentError}>
|
||||
{contentError.response && contentError.response.status === 404 && (
|
||||
<span>
|
||||
{i18n._(`Credential not found.`)}{' '}
|
||||
<Link to="/credentials">{i18n._(`View all Credentials.`)}</Link>
|
||||
</span>
|
||||
)}
|
||||
</ContentError>
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PageSection>
|
||||
<Card className="awx-c-card">
|
||||
{cardHeader}
|
||||
<Switch>
|
||||
<Redirect
|
||||
from="/credentials/:id"
|
||||
to="/credentials/:id/details"
|
||||
exact
|
||||
/>
|
||||
{credential && (
|
||||
<Route path="/credentials/:id/details">
|
||||
<CredentialDetail credential={credential} />
|
||||
</Route>
|
||||
)}
|
||||
<Route
|
||||
key="not-found"
|
||||
path="*"
|
||||
render={() =>
|
||||
!hasContentLoading && (
|
||||
<ContentError isNotFound>
|
||||
{id && (
|
||||
<Link to={`/credentials/${id}/details`}>
|
||||
{i18n._(`View Credential Details`)}
|
||||
</Link>
|
||||
)}
|
||||
</ContentError>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Switch>
|
||||
</Card>
|
||||
</PageSection>
|
||||
);
|
||||
}
|
||||
|
||||
export default withI18n()(Credential);
|
||||
65
awx/ui_next/src/screens/Credential/Credential.test.jsx
Normal file
65
awx/ui_next/src/screens/Credential/Credential.test.jsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { CredentialsAPI } from '@api';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
import { mockCredentials } from './shared';
|
||||
import Credential from './Credential';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
CredentialsAPI.readDetail.mockResolvedValue({
|
||||
data: mockCredentials.results[0],
|
||||
});
|
||||
|
||||
describe('<Credential />', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Credential setBreadcrumb={() => {}} />);
|
||||
});
|
||||
});
|
||||
|
||||
test('initially renders succesfully', async () => {
|
||||
expect(wrapper.find('Credential').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should show content error when user attempts to navigate to erroneous route', async () => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: ['/credentials/1/foobar'],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Credential setBreadcrumb={() => {}} />, {
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
route: {
|
||||
location: history.location,
|
||||
match: {
|
||||
params: { id: 1 },
|
||||
url: '/credentials/1/foobar',
|
||||
path: '/credentials/1/foobar',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||
expect(wrapper.find('ContentError Title').text()).toEqual('Not Found');
|
||||
});
|
||||
|
||||
test('should show content error if api throws an error', async () => {
|
||||
CredentialsAPI.readDetail.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(<Credential setBreadcrumb={() => {}} />);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||
expect(wrapper.find('ContentError Title').text()).toEqual(
|
||||
'Something went wrong...'
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,178 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { shape } from 'prop-types';
|
||||
|
||||
import { Button, List, ListItem } from '@patternfly/react-core';
|
||||
import AlertModal from '@components/AlertModal';
|
||||
import { CardBody, CardActionsRow } from '@components/Card';
|
||||
import ContentError from '@components/ContentError';
|
||||
import ContentLoading from '@components/ContentLoading';
|
||||
import DeleteButton from '@components/DeleteButton';
|
||||
import { DetailList, Detail, UserDateDetail } from '@components/DetailList';
|
||||
import ErrorDetail from '@components/ErrorDetail';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||
import { Credential } from '@types';
|
||||
|
||||
function CredentialDetail({ i18n, credential }) {
|
||||
const {
|
||||
id: credentialId,
|
||||
name,
|
||||
description,
|
||||
inputs,
|
||||
created,
|
||||
modified,
|
||||
summary_fields: {
|
||||
credential_type,
|
||||
organization,
|
||||
created_by,
|
||||
modified_by,
|
||||
user_capabilities,
|
||||
},
|
||||
} = credential;
|
||||
|
||||
const [fields, setFields] = useState([]);
|
||||
const [managedByTower, setManagedByTower] = useState([]);
|
||||
const [contentError, setContentError] = useState(null);
|
||||
const [deletionError, setDeletionError] = useState(null);
|
||||
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setContentError(null);
|
||||
setHasContentLoading(true);
|
||||
try {
|
||||
const {
|
||||
data: { inputs: credentialTypeInputs, managed_by_tower },
|
||||
} = await CredentialTypesAPI.readDetail(credential_type.id);
|
||||
|
||||
setFields(credentialTypeInputs.fields || []);
|
||||
setManagedByTower(managed_by_tower);
|
||||
} catch (error) {
|
||||
setContentError(error);
|
||||
} finally {
|
||||
setHasContentLoading(false);
|
||||
}
|
||||
})();
|
||||
}, [credential_type]);
|
||||
|
||||
const handleDelete = async () => {
|
||||
setHasContentLoading(true);
|
||||
|
||||
try {
|
||||
await CredentialsAPI.destroy(credentialId);
|
||||
history.push('/credentials');
|
||||
} catch (error) {
|
||||
setDeletionError(error);
|
||||
}
|
||||
setHasContentLoading(false);
|
||||
};
|
||||
|
||||
const renderDetail = ({ id, label, type, secret }) => {
|
||||
let detail;
|
||||
|
||||
if (type === 'boolean') {
|
||||
detail = (
|
||||
<Detail
|
||||
key={id}
|
||||
label={i18n._(t`Options`)}
|
||||
value={<List>{inputs[id] && <ListItem>{label}</ListItem>}</List>}
|
||||
/>
|
||||
);
|
||||
} else if (secret === true) {
|
||||
detail = <Detail key={id} label={label} value={i18n._(t`Encrypted`)} />;
|
||||
} else {
|
||||
detail = <Detail key={id} label={label} value={inputs[id]} />;
|
||||
}
|
||||
|
||||
return detail;
|
||||
};
|
||||
|
||||
if (hasContentLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
|
||||
if (contentError) {
|
||||
return <ContentError error={contentError} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
<DetailList>
|
||||
<Detail label={i18n._(t`Name`)} value={name} />
|
||||
<Detail label={i18n._(t`Description`)} value={description} />
|
||||
{organization && (
|
||||
<Detail
|
||||
label={i18n._(t`Organization`)}
|
||||
value={
|
||||
<Link to={`/organizations/${organization.id}/details`}>
|
||||
{organization.name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Detail
|
||||
label={i18n._(t`Credential Type`)}
|
||||
value={
|
||||
managedByTower ? (
|
||||
credential_type.name
|
||||
) : (
|
||||
<Link to={`/credential_types/${credential_type.id}/details`}>
|
||||
{credential_type.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
{fields.map(field => renderDetail(field))}
|
||||
|
||||
<UserDateDetail
|
||||
label={i18n._(t`Created`)}
|
||||
date={created}
|
||||
user={created_by}
|
||||
/>
|
||||
<UserDateDetail
|
||||
label={i18n._(t`Last Modified`)}
|
||||
date={modified}
|
||||
user={modified_by}
|
||||
/>
|
||||
</DetailList>
|
||||
<CardActionsRow>
|
||||
{user_capabilities.edit && (
|
||||
<Button component={Link} to={`/credentials/${credentialId}/edit`}>
|
||||
{i18n._(t`Edit`)}
|
||||
</Button>
|
||||
)}
|
||||
{user_capabilities.delete && (
|
||||
<DeleteButton
|
||||
name={name}
|
||||
modalTitle={i18n._(t`Delete Credential`)}
|
||||
onConfirm={handleDelete}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
)}
|
||||
</CardActionsRow>
|
||||
{deletionError && (
|
||||
<AlertModal
|
||||
isOpen={deletionError}
|
||||
variant="danger"
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={() => setDeletionError(null)}
|
||||
>
|
||||
{i18n._(t`Failed to delete credential.`)}
|
||||
<ErrorDetail error={deletionError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
</CardBody>
|
||||
);
|
||||
}
|
||||
|
||||
CredentialDetail.propTypes = {
|
||||
credential: Credential.isRequired,
|
||||
i18n: shape({}).isRequired,
|
||||
};
|
||||
|
||||
export default withI18n()(CredentialDetail);
|
||||
@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
import CredentialDetail from './CredentialDetail';
|
||||
import { mockCredentials, mockCredentialType } from '../shared';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
const mockCredential = mockCredentials.results[0];
|
||||
|
||||
CredentialTypesAPI.readDetail.mockResolvedValue({
|
||||
data: mockCredentialType,
|
||||
});
|
||||
|
||||
function expectDetailToMatch(wrapper, label, value) {
|
||||
const detail = wrapper.find(`Detail[label="${label}"]`);
|
||||
expect(detail).toHaveLength(1);
|
||||
expect(detail.find('dd').text()).toEqual(value);
|
||||
}
|
||||
|
||||
describe('<CredentialDetail />', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialDetail credential={mockCredential} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||
});
|
||||
|
||||
test('should render successfully', () => {
|
||||
expect(wrapper.find('CredentialDetail').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should render details', () => {
|
||||
expectDetailToMatch(wrapper, 'Name', mockCredential.name);
|
||||
expectDetailToMatch(wrapper, 'Description', mockCredential.description);
|
||||
expectDetailToMatch(
|
||||
wrapper,
|
||||
'Organization',
|
||||
mockCredential.summary_fields.organization.name
|
||||
);
|
||||
expectDetailToMatch(
|
||||
wrapper,
|
||||
'Credential Type',
|
||||
mockCredential.summary_fields.credential_type.name
|
||||
);
|
||||
expectDetailToMatch(wrapper, 'Username', mockCredential.inputs.username);
|
||||
expectDetailToMatch(wrapper, 'Password', 'Encrypted');
|
||||
expectDetailToMatch(wrapper, 'SSH Private Key', 'Encrypted');
|
||||
expectDetailToMatch(wrapper, 'Signed SSH Certificate', 'Encrypted');
|
||||
expectDetailToMatch(wrapper, 'Private Key Passphrase', 'Encrypted');
|
||||
expectDetailToMatch(
|
||||
wrapper,
|
||||
'Privilege Escalation Method',
|
||||
mockCredential.inputs.become_method
|
||||
);
|
||||
expectDetailToMatch(
|
||||
wrapper,
|
||||
'Privilege Escalation Username',
|
||||
mockCredential.inputs.become_username
|
||||
);
|
||||
expect(wrapper.find(`Detail[label="Options"] ListItem`).text()).toEqual(
|
||||
'Authorize'
|
||||
);
|
||||
});
|
||||
|
||||
test('should show content error on throw', async () => {
|
||||
CredentialTypesAPI.readDetail.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<CredentialDetail credential={mockCredential} />
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
|
||||
});
|
||||
|
||||
test('handleDelete should call api', async () => {
|
||||
CredentialsAPI.destroy = jest.fn();
|
||||
await act(async () => {
|
||||
wrapper.find('DeleteButton').invoke('onConfirm')();
|
||||
});
|
||||
wrapper.update();
|
||||
expect(CredentialsAPI.destroy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should show error modal when credential is not successfully deleted from api', async () => {
|
||||
CredentialsAPI.destroy.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper.find('DeleteButton').invoke('onConfirm')();
|
||||
});
|
||||
await waitForElement(wrapper, 'ErrorDetail', el => el.length === 1);
|
||||
await act(async () => {
|
||||
wrapper.find('ModalBoxCloseButton').invoke('onClose')();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1 @@
|
||||
export { default } from './CredentialDetail';
|
||||
@ -3,7 +3,7 @@ import { act } from 'react-dom/test-utils';
|
||||
import { CredentialsAPI } from '@api';
|
||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||
import { CredentialList } from '.';
|
||||
import mockCredentials from '../shared';
|
||||
import { mockCredentials } from '../shared';
|
||||
|
||||
jest.mock('@api');
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '@testUtils/enzymeHelpers';
|
||||
import { CredentialListItem } from '.';
|
||||
import mockCredentials from '../shared';
|
||||
import { mockCredentials } from '../shared';
|
||||
|
||||
describe('<CredentialListItem />', () => {
|
||||
let wrapper;
|
||||
|
||||
@ -1,24 +1,48 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
|
||||
import { CredentialList } from './CredentialList';
|
||||
import Breadcrumbs from '@components/Breadcrumbs';
|
||||
import Credential from './Credential';
|
||||
import CredentialAdd from './CredentialAdd';
|
||||
import { CredentialList } from './CredentialList';
|
||||
|
||||
function Credentials({ i18n }) {
|
||||
const breadcrumbConfig = {
|
||||
const [breadcrumbConfig, setBreadcrumbConfig] = useState({
|
||||
'/credentials': i18n._(t`Credentials`),
|
||||
'/credentials/add': i18n._(t`Create New Credential`),
|
||||
};
|
||||
});
|
||||
|
||||
const buildBreadcrumbConfig = useCallback(
|
||||
credential => {
|
||||
if (!credential) {
|
||||
return;
|
||||
}
|
||||
|
||||
setBreadcrumbConfig({
|
||||
'/credentials': i18n._(t`Credentials`),
|
||||
'/credentials/add': i18n._(t`Create New Credential`),
|
||||
[`/credentials/${credential.id}`]: `${credential.name}`,
|
||||
[`/credentials/${credential.id}/details`]: i18n._(t`Details`),
|
||||
});
|
||||
},
|
||||
[i18n]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||
<Switch>
|
||||
<Route path="/credentials/add" render={() => <CredentialAdd />} />
|
||||
<Route path="/credentials" render={() => <CredentialList />} />
|
||||
<Route path="/credentials/add">
|
||||
<CredentialAdd />
|
||||
</Route>
|
||||
<Route path="/credentials/:id">
|
||||
<Credential setBreadcrumb={buildBreadcrumbConfig} />
|
||||
</Route>
|
||||
<Route path="/credentials">
|
||||
<CredentialList />
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
{
|
||||
"id": 1,
|
||||
"type": "credential_type",
|
||||
"url": "/api/v2/credential_types/1/",
|
||||
"related": {
|
||||
"named_url": "/api/v2/credential_types/Machine+ssh/",
|
||||
"credentials": "/api/v2/credential_types/1/credentials/",
|
||||
"activity_stream": "/api/v2/credential_types/1/activity_stream/"
|
||||
},
|
||||
"summary_fields": {
|
||||
"user_capabilities": {
|
||||
"edit": false,
|
||||
"delete": false
|
||||
}
|
||||
},
|
||||
"created": "2020-01-08T20:18:23.663144Z",
|
||||
"modified": "2020-01-08T20:18:46.056120Z",
|
||||
"name": "Machine",
|
||||
"description": "",
|
||||
"kind": "ssh",
|
||||
"namespace": "ssh",
|
||||
"managed_by_tower": true,
|
||||
"inputs": {
|
||||
"fields": [
|
||||
{
|
||||
"id": "username",
|
||||
"label": "Username",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "password",
|
||||
"label": "Password",
|
||||
"type": "string",
|
||||
"secret": true,
|
||||
"ask_at_runtime": true
|
||||
},
|
||||
{
|
||||
"id": "ssh_key_data",
|
||||
"label": "SSH Private Key",
|
||||
"type": "string",
|
||||
"format": "ssh_private_key",
|
||||
"secret": true,
|
||||
"multiline": true
|
||||
},
|
||||
{
|
||||
"id": "ssh_public_key_data",
|
||||
"label": "Signed SSH Certificate",
|
||||
"type": "string",
|
||||
"multiline": true,
|
||||
"secret": true
|
||||
},
|
||||
{
|
||||
"id": "ssh_key_unlock",
|
||||
"label": "Private Key Passphrase",
|
||||
"type": "string",
|
||||
"secret": true,
|
||||
"ask_at_runtime": true
|
||||
},
|
||||
{
|
||||
"id": "become_method",
|
||||
"label": "Privilege Escalation Method",
|
||||
"type": "string",
|
||||
"help_text": "Specify a method for \"become\" operations. This is equivalent to specifying the --become-method Ansible parameter."
|
||||
},
|
||||
{
|
||||
"id": "become_username",
|
||||
"label": "Privilege Escalation Username",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"id": "become_password",
|
||||
"label": "Privilege Escalation Password",
|
||||
"type": "string",
|
||||
"secret": true,
|
||||
"ask_at_runtime": true
|
||||
},
|
||||
{
|
||||
"id": "authorize",
|
||||
"label": "Authorize",
|
||||
"type": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"injectors": {}
|
||||
}
|
||||
@ -21,6 +21,11 @@
|
||||
"user": "/api/v2/users/7/"
|
||||
},
|
||||
"summary_fields": {
|
||||
"organization": {
|
||||
"id": 1,
|
||||
"name": "Org",
|
||||
"description": ""
|
||||
},
|
||||
"credential_type": {
|
||||
"id": 1,
|
||||
"name": "Machine",
|
||||
@ -65,8 +70,19 @@
|
||||
"created": "2019-12-17T16:12:25.258897Z",
|
||||
"modified": "2019-12-17T16:12:25.258920Z",
|
||||
"name": "Foo",
|
||||
"organization": null,
|
||||
"description": "Foo Description",
|
||||
"organization": 1,
|
||||
"credential_type": 1,
|
||||
"inputs": {
|
||||
"password": "$encrypted$",
|
||||
"username": "foo",
|
||||
"ssh_key_data": "$encrypted$",
|
||||
"become_method": "sudo",
|
||||
"become_password": "$encrypted$",
|
||||
"become_username": "bar",
|
||||
"ssh_public_key_data": "$encrypted$",
|
||||
"authorize": true
|
||||
},
|
||||
"kind": null,
|
||||
"cloud": true,
|
||||
"kubernetes": false
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export { default } from './data.credentials.json';
|
||||
export { default as mockCredentials } from './data.credentials.json';
|
||||
export { default as mockCredentialType } from './data.credential_type.json';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user