mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 15:21:13 +03:00
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:
parent
d0c891764f
commit
0ab61fd3cb
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 = {
|
||||
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,
|
||||
};
|
||||
|
@ -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 { 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 }) {
|
||||
</label>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<ButtonGroup>
|
||||
<SmallButton
|
||||
onClick={() => {
|
||||
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
|
||||
</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>
|
||||
<YamlJsonToggle
|
||||
mode={mode}
|
||||
onChange={newMode => {
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SplitItem>
|
||||
</Split>
|
||||
<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';
|
||||
|
||||
export default CodeMirrorInput;
|
||||
export { default as VariablesDetail } from './VariablesDetail';
|
||||
export { default as VariablesInput } from './VariablesInput';
|
||||
export { default as VariablesField } from './VariablesField';
|
||||
|
@ -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 : (
|
||||
<CardHeader style={{ padding: 0 }}>
|
||||
<TabbedCardHeader style={{ padding: 0 }}>
|
||||
<RoutedTabs history={history} tabsArray={tabsArray} />
|
||||
<CardCloseButton linkTo="/inventories" />
|
||||
</CardHeader>
|
||||
</TabbedCardHeader>
|
||||
);
|
||||
|
||||
if (
|
||||
|
@ -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 <CardBody>Coming soon :)</CardBody>;
|
||||
}
|
||||
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 (
|
||||
<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;
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user