mirror of
https://github.com/ansible/awx.git
synced 2026-05-13 20:37:39 -02:30
Start inventory detail
* Create VariablesDetail for read-only variables view * Sketch out InventoryDetail * Create CardBody and TabbedCardHeader for common custom styling
This commit is contained in:
9
awx/ui_next/src/components/Card/CardBody.jsx
Normal file
9
awx/ui_next/src/components/Card/CardBody.jsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { CardBody as PFCardBody } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const CardBody = styled(PFCardBody)`
|
||||||
|
padding-top: 20px;
|
||||||
|
`;
|
||||||
|
PFCardBody.displayName = 'PFCardBody';
|
||||||
|
|
||||||
|
export default CardBody;
|
||||||
11
awx/ui_next/src/components/Card/TabbedCardHeader.js
Normal file
11
awx/ui_next/src/components/Card/TabbedCardHeader.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
import { CardHeader } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const TabbedCardHeader = styled(CardHeader)`
|
||||||
|
--pf-c-card--first-child--PaddingTop: 0;
|
||||||
|
--pf-c-card--child--PaddingLeft: 0;
|
||||||
|
--pf-c-card--child--PaddingRight: 0;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default TabbedCardHeader;
|
||||||
2
awx/ui_next/src/components/Card/index.js
Normal file
2
awx/ui_next/src/components/Card/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as CardBody } from './CardBody';
|
||||||
|
export { default as TabbedCardHeader } from './TabbedCardHeader';
|
||||||
@@ -83,7 +83,7 @@ function CodeMirrorInput({
|
|||||||
}
|
}
|
||||||
CodeMirrorInput.propTypes = {
|
CodeMirrorInput.propTypes = {
|
||||||
value: string.isRequired,
|
value: string.isRequired,
|
||||||
onChange: func.isRequired,
|
onChange: func,
|
||||||
mode: oneOf(['javascript', 'yaml', 'jinja2']).isRequired,
|
mode: oneOf(['javascript', 'yaml', 'jinja2']).isRequired,
|
||||||
readOnly: bool,
|
readOnly: bool,
|
||||||
hasErrors: bool,
|
hasErrors: bool,
|
||||||
@@ -91,6 +91,7 @@ CodeMirrorInput.propTypes = {
|
|||||||
};
|
};
|
||||||
CodeMirrorInput.defaultProps = {
|
CodeMirrorInput.defaultProps = {
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
|
onChange: () => {},
|
||||||
rows: 6,
|
rows: 6,
|
||||||
hasErrors: false,
|
hasErrors: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { string, number } from 'prop-types';
|
||||||
|
import { Split, SplitItem } from '@patternfly/react-core';
|
||||||
|
import CodeMirrorInput from './CodeMirrorInput';
|
||||||
|
import YamlJsonToggle from './YamlJsonToggle';
|
||||||
|
import { yamlToJson, jsonToYaml, isJson } from '../../util/yaml';
|
||||||
|
|
||||||
|
const YAML_MODE = 'yaml';
|
||||||
|
const JSON_MODE = 'javascript';
|
||||||
|
|
||||||
|
function VariablesDetail({ value, label, rows }) {
|
||||||
|
const [mode, setMode] = useState(isJson(value) ? JSON_MODE : YAML_MODE);
|
||||||
|
const [val, setVal] = useState(value);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div css="margin: 20px 0">
|
||||||
|
<Split gutter="sm">
|
||||||
|
<SplitItem>
|
||||||
|
<div className="pf-c-form__label">
|
||||||
|
<span
|
||||||
|
className="pf-c-form__label-text"
|
||||||
|
css="font-weight: var(--pf-global--FontWeight--bold)"
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SplitItem>
|
||||||
|
<SplitItem>
|
||||||
|
<YamlJsonToggle
|
||||||
|
mode={mode}
|
||||||
|
onChange={newMode => {
|
||||||
|
try {
|
||||||
|
const newVal =
|
||||||
|
newMode === YAML_MODE ? jsonToYaml(val) : yamlToJson(val);
|
||||||
|
setVal(newVal);
|
||||||
|
setMode(newMode);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SplitItem>
|
||||||
|
</Split>
|
||||||
|
<CodeMirrorInput
|
||||||
|
mode={mode}
|
||||||
|
value={val}
|
||||||
|
readOnly
|
||||||
|
rows={rows}
|
||||||
|
css="margin-top: 10px"
|
||||||
|
/>
|
||||||
|
{error && (
|
||||||
|
<div
|
||||||
|
css="color: var(--pf-global--danger-color--100);
|
||||||
|
font-size: var(--pf-global--FontSize--sm"
|
||||||
|
>
|
||||||
|
Error: {error.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
VariablesDetail.propTypes = {
|
||||||
|
value: string.isRequired,
|
||||||
|
label: string.isRequired,
|
||||||
|
rows: number,
|
||||||
|
};
|
||||||
|
VariablesDetail.defaultProps = {
|
||||||
|
rows: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VariablesDetail;
|
||||||
@@ -1,21 +1,16 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { string, bool } from 'prop-types';
|
import { string, bool } from 'prop-types';
|
||||||
import { Field } from 'formik';
|
import { Field } from 'formik';
|
||||||
import { Button, Split, SplitItem } from '@patternfly/react-core';
|
import { Split, SplitItem } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
|
||||||
import ButtonGroup from '../ButtonGroup';
|
|
||||||
import CodeMirrorInput from './CodeMirrorInput';
|
import CodeMirrorInput from './CodeMirrorInput';
|
||||||
|
import YamlJsonToggle from './YamlJsonToggle';
|
||||||
import { yamlToJson, jsonToYaml } from '../../util/yaml';
|
import { yamlToJson, jsonToYaml } from '../../util/yaml';
|
||||||
|
|
||||||
const YAML_MODE = 'yaml';
|
const YAML_MODE = 'yaml';
|
||||||
const JSON_MODE = 'javascript';
|
const JSON_MODE = 'javascript';
|
||||||
|
|
||||||
const SmallButton = styled(Button)`
|
|
||||||
padding: 3px 8px;
|
|
||||||
font-size: var(--pf-global--FontSize--xs);
|
|
||||||
`;
|
|
||||||
|
|
||||||
function VariablesField({ id, name, label, readOnly }) {
|
function VariablesField({ id, name, label, readOnly }) {
|
||||||
|
// TODO: detect initial mode
|
||||||
const [mode, setMode] = useState(YAML_MODE);
|
const [mode, setMode] = useState(YAML_MODE);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,40 +25,21 @@ function VariablesField({ id, name, label, readOnly }) {
|
|||||||
</label>
|
</label>
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<ButtonGroup>
|
<YamlJsonToggle
|
||||||
<SmallButton
|
mode={mode}
|
||||||
onClick={() => {
|
onChange={newMode => {
|
||||||
if (mode === YAML_MODE) {
|
try {
|
||||||
return;
|
const newVal =
|
||||||
}
|
newMode === YAML_MODE
|
||||||
try {
|
? jsonToYaml(field.value)
|
||||||
form.setFieldValue(name, jsonToYaml(field.value));
|
: yamlToJson(field.value);
|
||||||
setMode(YAML_MODE);
|
form.setFieldValue(name, newVal);
|
||||||
} catch (err) {
|
setMode(newMode);
|
||||||
form.setFieldError(name, err.message);
|
} catch (err) {
|
||||||
}
|
form.setFieldError(name, err.message);
|
||||||
}}
|
}
|
||||||
variant={mode === YAML_MODE ? 'primary' : 'secondary'}
|
}}
|
||||||
>
|
/>
|
||||||
YAML
|
|
||||||
</SmallButton>
|
|
||||||
<SmallButton
|
|
||||||
onClick={() => {
|
|
||||||
if (mode === JSON_MODE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
form.setFieldValue(name, yamlToJson(field.value));
|
|
||||||
setMode(JSON_MODE);
|
|
||||||
} catch (err) {
|
|
||||||
form.setFieldError(name, err.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
variant={mode === JSON_MODE ? 'primary' : 'secondary'}
|
|
||||||
>
|
|
||||||
JSON
|
|
||||||
</SmallButton>
|
|
||||||
</ButtonGroup>
|
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
</Split>
|
</Split>
|
||||||
<CodeMirrorInput
|
<CodeMirrorInput
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { oneOf, func } from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Button } from '@patternfly/react-core';
|
||||||
|
import ButtonGroup from '../ButtonGroup';
|
||||||
|
|
||||||
|
const SmallButton = styled(Button)`
|
||||||
|
padding: 3px 8px;
|
||||||
|
font-size: var(--pf-global--FontSize--xs);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const YAML_MODE = 'yaml';
|
||||||
|
const JSON_MODE = 'javascript';
|
||||||
|
|
||||||
|
function YamlJsonToggle({ mode, onChange }) {
|
||||||
|
const setMode = newMode => {
|
||||||
|
if (mode !== newMode) {
|
||||||
|
onChange(newMode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup>
|
||||||
|
<SmallButton
|
||||||
|
onClick={() => setMode(YAML_MODE)}
|
||||||
|
variant={mode === YAML_MODE ? 'primary' : 'secondary'}
|
||||||
|
>
|
||||||
|
YAML
|
||||||
|
</SmallButton>
|
||||||
|
<SmallButton
|
||||||
|
onClick={() => setMode(JSON_MODE)}
|
||||||
|
variant={mode === JSON_MODE ? 'primary' : 'secondary'}
|
||||||
|
>
|
||||||
|
JSON
|
||||||
|
</SmallButton>
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
YamlJsonToggle.propTypes = {
|
||||||
|
mode: oneOf([YAML_MODE, JSON_MODE]).isRequired,
|
||||||
|
onChange: func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default YamlJsonToggle;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import CodeMirrorInput from './CodeMirrorInput';
|
import CodeMirrorInput from './CodeMirrorInput';
|
||||||
|
|
||||||
export default CodeMirrorInput;
|
export default CodeMirrorInput;
|
||||||
|
export { default as VariablesDetail } from './VariablesDetail';
|
||||||
export { default as VariablesInput } from './VariablesInput';
|
export { default as VariablesInput } from './VariablesInput';
|
||||||
export { default as VariablesField } from './VariablesField';
|
export { default as VariablesField } from './VariablesField';
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { Card, CardHeader, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
|
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
|
||||||
|
import { TabbedCardHeader } from '@components/Card';
|
||||||
import CardCloseButton from '@components/CardCloseButton';
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import RoutedTabs from '@components/RoutedTabs';
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
@@ -51,10 +52,10 @@ function Inventory({ history, i18n, location, match, setBreadcrumb }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let cardHeader = hasContentLoading ? null : (
|
let cardHeader = hasContentLoading ? null : (
|
||||||
<CardHeader style={{ padding: 0 }}>
|
<TabbedCardHeader style={{ padding: 0 }}>
|
||||||
<RoutedTabs history={history} tabsArray={tabsArray} />
|
<RoutedTabs history={history} tabsArray={tabsArray} />
|
||||||
<CardCloseButton linkTo="/inventories" />
|
<CardCloseButton linkTo="/inventories" />
|
||||||
</CardHeader>
|
</TabbedCardHeader>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1,10 +1,56 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { CardBody } from '@patternfly/react-core';
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { CardBody } from '@components/Card';
|
||||||
|
import { DetailList, Detail } from '@components/DetailList';
|
||||||
|
import { VariablesDetail } from '@components/CodeMirrorInput';
|
||||||
|
import { InventoriesAPI } from '@api';
|
||||||
|
import { Inventory } from '../../../types';
|
||||||
|
|
||||||
class InventoryDetail extends Component {
|
function InventoryDetail({ inventory, i18n }) {
|
||||||
render() {
|
const [instanceGroups, setInstanceGroups] = useState([]);
|
||||||
return <CardBody>Coming soon :)</CardBody>;
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
}
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const { data } = await InventoriesAPI.readInstanceGroups(inventory.id);
|
||||||
|
setInstanceGroups(data.results);
|
||||||
|
setIsLoading(false);
|
||||||
|
})();
|
||||||
|
}, [inventory.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardBody>
|
||||||
|
<DetailList>
|
||||||
|
<Detail label={i18n._(t`Name`)} value={inventory.name} />
|
||||||
|
<Detail label={i18n._(t`Activity`)} value="Coming soon" />
|
||||||
|
<Detail label={i18n._(t`Description`)} value={inventory.description} />
|
||||||
|
<Detail label={i18n._(t`Type`)} value={inventory.kind} />
|
||||||
|
<Detail
|
||||||
|
label={i18n._(t`Organization`)}
|
||||||
|
value={inventory.summary_fields.organization.name}
|
||||||
|
/>
|
||||||
|
<Detail
|
||||||
|
fullWidth
|
||||||
|
label={i18n._(t`Instance Groups`)}
|
||||||
|
value={isLoading ? 'loading...' : instanceGroups.map(g => g.name)}
|
||||||
|
/>
|
||||||
|
</DetailList>
|
||||||
|
{inventory.variables && (
|
||||||
|
<VariablesDetail
|
||||||
|
id="job-artifacts"
|
||||||
|
value={inventory.variables}
|
||||||
|
rows={4}
|
||||||
|
label={i18n._(t`Variables`)}
|
||||||
|
/>
|
||||||
|
)}{' '}
|
||||||
|
<Detail label={i18n._(t`Description`)} value={inventory.description} />
|
||||||
|
</CardBody>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
InventoryDetail.propTypes = {
|
||||||
|
inventory: Inventory.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default InventoryDetail;
|
export default withI18n()(InventoryDetail);
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ function InventoryEdit({ history, i18n, inventory }) {
|
|||||||
} = values;
|
} = values;
|
||||||
try {
|
try {
|
||||||
await InventoriesAPI.update(inventory.id, {
|
await InventoriesAPI.update(inventory.id, {
|
||||||
insights_credential: insights_credential.id,
|
insights_credential: insights_credential
|
||||||
|
? insights_credential.id
|
||||||
|
: null,
|
||||||
organization: organization.id,
|
organization: organization.id,
|
||||||
...remainingValues,
|
...remainingValues,
|
||||||
});
|
});
|
||||||
@@ -76,13 +78,13 @@ function InventoryEdit({ history, i18n, inventory }) {
|
|||||||
);
|
);
|
||||||
await Promise.all([...associatePromises, ...disassociatePromises]);
|
await Promise.all([...associatePromises, ...disassociatePromises]);
|
||||||
}
|
}
|
||||||
|
const url =
|
||||||
|
history.location.pathname.search('smart') > -1
|
||||||
|
? `/inventories/smart_inventory/${inventory.id}/details`
|
||||||
|
: `/inventories/inventory/${inventory.id}/details`;
|
||||||
|
history.push(`${url}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err);
|
setError(err);
|
||||||
} finally {
|
|
||||||
const url = history.location.pathname.search('smart')
|
|
||||||
? `/inventories/smart_inventory/${inventory.id}/details`
|
|
||||||
: `/inventories/inventory/${inventory.id}/details`;
|
|
||||||
history.push(`${url}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (contentLoading) {
|
if (contentLoading) {
|
||||||
|
|||||||
Reference in New Issue
Block a user