diff --git a/awx/ui_next/src/components/Card/CardBody.jsx b/awx/ui_next/src/components/Card/CardBody.jsx new file mode 100644 index 0000000000..43120cd17d --- /dev/null +++ b/awx/ui_next/src/components/Card/CardBody.jsx @@ -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; diff --git a/awx/ui_next/src/components/Card/TabbedCardHeader.js b/awx/ui_next/src/components/Card/TabbedCardHeader.js new file mode 100644 index 0000000000..0a86d11a59 --- /dev/null +++ b/awx/ui_next/src/components/Card/TabbedCardHeader.js @@ -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; diff --git a/awx/ui_next/src/components/Card/index.js b/awx/ui_next/src/components/Card/index.js new file mode 100644 index 0000000000..617e65871f --- /dev/null +++ b/awx/ui_next/src/components/Card/index.js @@ -0,0 +1,2 @@ +export { default as CardBody } from './CardBody'; +export { default as TabbedCardHeader } from './TabbedCardHeader'; diff --git a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx index 6513322f66..c84ff0ba84 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/CodeMirrorInput.jsx @@ -83,7 +83,7 @@ function CodeMirrorInput({ } CodeMirrorInput.propTypes = { value: string.isRequired, - onChange: func.isRequired, + onChange: func, mode: oneOf(['javascript', 'yaml', 'jinja2']).isRequired, readOnly: bool, hasErrors: bool, @@ -91,6 +91,7 @@ CodeMirrorInput.propTypes = { }; CodeMirrorInput.defaultProps = { readOnly: false, + onChange: () => {}, rows: 6, hasErrors: false, }; diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx new file mode 100644 index 0000000000..04c7a1ed4b --- /dev/null +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesDetail.jsx @@ -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 ( +
+ + +
+ + {label} + +
+
+ + { + try { + const newVal = + newMode === YAML_MODE ? jsonToYaml(val) : yamlToJson(val); + setVal(newVal); + setMode(newMode); + } catch (err) { + setError(err); + } + }} + /> + +
+ + {error && ( +
+ Error: {error.message} +
+ )} +
+ ); +} +VariablesDetail.propTypes = { + value: string.isRequired, + label: string.isRequired, + rows: number, +}; +VariablesDetail.defaultProps = { + rows: null, +}; + +export default VariablesDetail; diff --git a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx index 3c828c6308..96b516946e 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx +++ b/awx/ui_next/src/components/CodeMirrorInput/VariablesField.jsx @@ -1,21 +1,16 @@ import React, { useState } from 'react'; import { string, bool } from 'prop-types'; import { Field } from 'formik'; -import { Button, Split, SplitItem } from '@patternfly/react-core'; -import styled from 'styled-components'; -import ButtonGroup from '../ButtonGroup'; +import { Split, SplitItem } from '@patternfly/react-core'; import CodeMirrorInput from './CodeMirrorInput'; +import YamlJsonToggle from './YamlJsonToggle'; import { yamlToJson, jsonToYaml } from '../../util/yaml'; const YAML_MODE = 'yaml'; const JSON_MODE = 'javascript'; -const SmallButton = styled(Button)` - padding: 3px 8px; - font-size: var(--pf-global--FontSize--xs); -`; - function VariablesField({ id, name, label, readOnly }) { + // TODO: detect initial mode const [mode, setMode] = useState(YAML_MODE); return ( @@ -30,40 +25,21 @@ function VariablesField({ id, name, label, readOnly }) { - - { - if (mode === YAML_MODE) { - return; - } - try { - form.setFieldValue(name, jsonToYaml(field.value)); - setMode(YAML_MODE); - } catch (err) { - form.setFieldError(name, err.message); - } - }} - variant={mode === YAML_MODE ? 'primary' : 'secondary'} - > - YAML - - { - 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 - - + { + try { + const newVal = + newMode === YAML_MODE + ? jsonToYaml(field.value) + : yamlToJson(field.value); + form.setFieldValue(name, newVal); + setMode(newMode); + } catch (err) { + form.setFieldError(name, err.message); + } + }} + /> { + if (mode !== newMode) { + onChange(newMode); + } + }; + + return ( + + setMode(YAML_MODE)} + variant={mode === YAML_MODE ? 'primary' : 'secondary'} + > + YAML + + setMode(JSON_MODE)} + variant={mode === JSON_MODE ? 'primary' : 'secondary'} + > + JSON + + + ); +} +YamlJsonToggle.propTypes = { + mode: oneOf([YAML_MODE, JSON_MODE]).isRequired, + onChange: func.isRequired, +}; + +export default YamlJsonToggle; diff --git a/awx/ui_next/src/components/CodeMirrorInput/index.js b/awx/ui_next/src/components/CodeMirrorInput/index.js index 48940bbb37..9cad016228 100644 --- a/awx/ui_next/src/components/CodeMirrorInput/index.js +++ b/awx/ui_next/src/components/CodeMirrorInput/index.js @@ -1,5 +1,6 @@ import CodeMirrorInput from './CodeMirrorInput'; export default CodeMirrorInput; +export { default as VariablesDetail } from './VariablesDetail'; export { default as VariablesInput } from './VariablesInput'; export { default as VariablesField } from './VariablesField'; diff --git a/awx/ui_next/src/screens/Inventory/Inventory.jsx b/awx/ui_next/src/screens/Inventory/Inventory.jsx index f3e78d584b..03fd08d3fe 100644 --- a/awx/ui_next/src/screens/Inventory/Inventory.jsx +++ b/awx/ui_next/src/screens/Inventory/Inventory.jsx @@ -1,8 +1,9 @@ import React, { useEffect, useState } from 'react'; import { t } from '@lingui/macro'; 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 { TabbedCardHeader } from '@components/Card'; import CardCloseButton from '@components/CardCloseButton'; import ContentError from '@components/ContentError'; import RoutedTabs from '@components/RoutedTabs'; @@ -51,10 +52,10 @@ function Inventory({ history, i18n, location, match, setBreadcrumb }) { ]; let cardHeader = hasContentLoading ? null : ( - + - + ); if ( diff --git a/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx b/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx index 5d9d9789b6..35c6830818 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryDetail/InventoryDetail.jsx @@ -1,10 +1,56 @@ -import React, { Component } from 'react'; -import { CardBody } from '@patternfly/react-core'; +import React, { useState, useEffect } from 'react'; +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 { - render() { - return Coming soon :); - } +function InventoryDetail({ inventory, i18n }) { + const [instanceGroups, setInstanceGroups] = useState([]); + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + (async () => { + setIsLoading(true); + const { data } = await InventoriesAPI.readInstanceGroups(inventory.id); + setInstanceGroups(data.results); + setIsLoading(false); + })(); + }, [inventory.id]); + + return ( + + + + + + + + g.name)} + /> + + {inventory.variables && ( + + )}{' '} + + + ); } +InventoryDetail.propTypes = { + inventory: Inventory.isRequired, +}; -export default InventoryDetail; +export default withI18n()(InventoryDetail); diff --git a/awx/ui_next/src/screens/Inventory/InventoryEdit/InventoryEdit.jsx b/awx/ui_next/src/screens/Inventory/InventoryEdit/InventoryEdit.jsx index 2ec78aef4a..ea1d7a8088 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryEdit/InventoryEdit.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryEdit/InventoryEdit.jsx @@ -58,7 +58,9 @@ function InventoryEdit({ history, i18n, inventory }) { } = values; try { await InventoriesAPI.update(inventory.id, { - insights_credential: insights_credential.id, + insights_credential: insights_credential + ? insights_credential.id + : null, organization: organization.id, ...remainingValues, }); @@ -76,13 +78,13 @@ function InventoryEdit({ history, i18n, inventory }) { ); 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) { 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) {