Skip to content
This repository was archived by the owner on Apr 28, 2020. It is now read-only.

Commit 6394b39

Browse files
committed
Add a component for creating baremetal hosts
1 parent 5e0862c commit 6394b39

File tree

12 files changed

+332
-0
lines changed

12 files changed

+332
-0
lines changed

sass/_components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
@import './components/ClusterOverview/health';
2929
@import './components/ClusterOverview/compliance';
3030
@import './components/ClusterOverview/utilization';
31+
@import './components/CreateBaremetalHostDialog/create-baremetal-host-dialog';
3132

3233
/*
3334
TODO: these styles should be backported to the corresponding PF-React package
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.kubevirt-create-baremetal-host-dialog--paragraph {
2+
margin-top: 20px;
3+
}
4+
5+
.kubevirt-create-baremetal-host-dialog .modal-content {
6+
max-height: 500px;
7+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Modal, Col, Button, Alert } from 'patternfly-react';
4+
5+
import { FormFactory } from '../Form/FormFactory';
6+
import { createBaremetalHost } from '../../k8s/request';
7+
import { TinyInlineLoading } from '../Loading/Loading';
8+
import { CREATE_HOST_FORM_TEXT } from './strings';
9+
10+
const formFields = {
11+
name: {
12+
id: 'name',
13+
title: 'Name',
14+
required: false,
15+
},
16+
controller: {
17+
id: 'controller',
18+
title: 'Management Controller Address',
19+
required: true,
20+
},
21+
username: {
22+
id: 'username',
23+
title: 'Username',
24+
required: true,
25+
},
26+
password: {
27+
id: 'password',
28+
title: 'Password',
29+
type: 'password',
30+
required: true,
31+
},
32+
};
33+
34+
export class CreateBaremetalHostDialog extends React.Component {
35+
state = {
36+
form: {
37+
value: {},
38+
valid: false,
39+
},
40+
isSubmitting: false,
41+
errorMessage: null,
42+
};
43+
44+
onFormChange = (newValue, target, formValid) => {
45+
this.setState(state => {
46+
const form = { ...state.form };
47+
form.value[target] = {
48+
...newValue,
49+
};
50+
form.valid = formValid;
51+
return { form, errorMessage: null };
52+
});
53+
};
54+
55+
onSubmit = async () => {
56+
this.setState(state => ({
57+
...state,
58+
isSubmitting: true,
59+
}));
60+
61+
return createBaremetalHost(this.props.k8sCreate, this.state.form.value, this.props.selectedNamespace)
62+
.then(result => {
63+
this.setState(state => ({
64+
...state,
65+
isSubmitting: false,
66+
}));
67+
this.props.onClose();
68+
return result;
69+
})
70+
.catch(({ message }) => {
71+
this.setState(state => ({
72+
...state,
73+
isSubmitting: false,
74+
errorMessage: message,
75+
}));
76+
});
77+
};
78+
79+
render() {
80+
return (
81+
<Modal show bsSize="large" onHide={this.props.onClose} className="kubevirt-create-baremetal-host-dialog">
82+
<Modal.Header closeButton>
83+
<Modal.Title>Add Host</Modal.Title>
84+
</Modal.Header>
85+
<Modal.Body>
86+
<Col sm={12}>
87+
<p className="kubevirt-create-baremetal-host-dialog--paragraph">{CREATE_HOST_FORM_TEXT}</p>
88+
<FormFactory
89+
fields={formFields}
90+
fieldsValues={this.state.form.value}
91+
onFormChange={this.onFormChange}
92+
horizontal={false}
93+
/>
94+
</Col>
95+
</Modal.Body>
96+
<Modal.Footer>
97+
{this.state.errorMessage && <Alert type="error">{this.state.errorMessage}</Alert>}
98+
{this.state.isSubmitting && <TinyInlineLoading />}
99+
<Button bsStyle="default" onClick={this.props.onClose}>
100+
Cancel
101+
</Button>
102+
<Button
103+
bsStyle="primary"
104+
disabled={!this.state.form.valid || this.state.isSubmitting}
105+
onClick={this.onSubmit}
106+
>
107+
Add Host
108+
</Button>
109+
</Modal.Footer>
110+
</Modal>
111+
);
112+
}
113+
}
114+
115+
CreateBaremetalHostDialog.defaultProps = {
116+
selectedNamespace: null,
117+
};
118+
119+
CreateBaremetalHostDialog.propTypes = {
120+
k8sCreate: PropTypes.func.isRequired,
121+
onClose: PropTypes.func.isRequired,
122+
selectedNamespace: PropTypes.object,
123+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { CreateBaremetalHostDialog } from '../CreateBaremetalHostDialog';
2+
3+
export default {
4+
component: CreateBaremetalHostDialog,
5+
props: {
6+
k8sCreate: () => {},
7+
onClose: () => {},
8+
selectedNamespace: {},
9+
},
10+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './CreateBaremetalHostDialog';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const CREATE_HOST_FORM_TEXT = `Specify the host's name, management controller IP address, and credentials to add it to the cluster.`;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
4+
import CreateBaremetalHostDialogFixture from '../fixtures/CreateBaremetalHostDialog.fixture';
5+
import { CreateBaremetalHostDialog } from '../CreateBaremetalHostDialog';
6+
7+
const testCreateBaremetalHostDialog = () => <CreateBaremetalHostDialog {...CreateBaremetalHostDialogFixture.props} />;
8+
9+
describe('<CreateBaremetalHostDialog />', () => {
10+
it('renders correctly', () => {
11+
const component = shallow(testCreateBaremetalHostDialog());
12+
expect(component).toMatchSnapshot();
13+
});
14+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`<CreateBaremetalHostDialog /> renders correctly 1`] = `
4+
<Modal
5+
animation={true}
6+
autoFocus={true}
7+
backdrop={true}
8+
bsClass="modal"
9+
bsSize="large"
10+
className="kubevirt-create-baremetal-host-dialog"
11+
dialogComponentClass={[Function]}
12+
enforceFocus={true}
13+
keyboard={true}
14+
manager={
15+
ModalManager {
16+
"add": [Function],
17+
"containers": Array [],
18+
"data": Array [],
19+
"handleContainerOverflow": true,
20+
"hideSiblingNodes": true,
21+
"isTopModal": [Function],
22+
"modals": Array [],
23+
"remove": [Function],
24+
}
25+
}
26+
onHide={[Function]}
27+
renderBackdrop={[Function]}
28+
restoreFocus={true}
29+
show={true}
30+
>
31+
<ModalHeader
32+
bsClass="modal-header"
33+
closeButton={true}
34+
closeLabel="Close"
35+
>
36+
<ModalTitle
37+
bsClass="modal-title"
38+
componentClass="h4"
39+
>
40+
Add Host
41+
</ModalTitle>
42+
</ModalHeader>
43+
<ModalBody
44+
bsClass="modal-body"
45+
componentClass="div"
46+
>
47+
<Col
48+
bsClass="col"
49+
componentClass="div"
50+
sm={12}
51+
>
52+
<p
53+
className="kubevirt-create-baremetal-host-dialog--paragraph"
54+
>
55+
Specify the host's name, management controller IP address, and credentials to add it to the cluster.
56+
</p>
57+
<FormFactory
58+
controlSize={5}
59+
fields={
60+
Object {
61+
"controller": Object {
62+
"id": "controller",
63+
"required": true,
64+
"title": "Management Controller Address",
65+
},
66+
"name": Object {
67+
"id": "name",
68+
"required": false,
69+
"title": "Name",
70+
},
71+
"password": Object {
72+
"id": "password",
73+
"required": true,
74+
"title": "Password",
75+
"type": "password",
76+
},
77+
"username": Object {
78+
"id": "username",
79+
"required": true,
80+
"title": "Username",
81+
},
82+
}
83+
}
84+
fieldsValues={Object {}}
85+
horizontal={false}
86+
labelSize={3}
87+
onFormChange={[Function]}
88+
showLabels={true}
89+
textPosition="text-right"
90+
/>
91+
</Col>
92+
</ModalBody>
93+
<ModalFooter
94+
bsClass="modal-footer"
95+
componentClass="div"
96+
>
97+
<Button
98+
active={false}
99+
block={false}
100+
bsClass="btn"
101+
bsStyle="default"
102+
disabled={false}
103+
onClick={[Function]}
104+
>
105+
Cancel
106+
</Button>
107+
<Button
108+
active={false}
109+
block={false}
110+
bsClass="btn"
111+
bsStyle="primary"
112+
disabled={true}
113+
onClick={[Function]}
114+
>
115+
Add Host
116+
</Button>
117+
</ModalFooter>
118+
</Modal>
119+
`;

src/components/Loading/Loading.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
33

44
import { prefixedId } from '../../utils';
55

6+
export const TinyInlineLoading = () => <span className="spinner spinner-xs spinner-inline" />;
7+
68
export const InlineLoading = ({ id, size }) => (
79
<div id={id} key={prefixedId(id, 'progress') || 'progress'}>
810
<div className={`spinner spinner-${size} blank-slate-pf-icon`} />

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export * from './components/Dashboard';
1010
export * from './components/Details';
1111
export * from './components/Dialog';
1212
export * from './components/Form';
13+
export * from './components/CreateBaremetalHostDialog';
1314
// Loading component group not exported
1415
// Table component group not exported
1516
export * from './components/TemplateSource';

src/k8s/request.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
getDataVolumeStorageClassName,
1313
getPvcStorageClassName,
1414
} from '../selectors';
15+
1516
import { VirtualMachineModel, ProcessedTemplatesModel, TemplateModel, DataVolumeModel, SecretModel } from '../models';
1617

1718
import {
@@ -104,6 +105,8 @@ import {
104105

105106
import { getImportProviderSecretObject } from '../components/Wizard/CreateVmWizard/providers/vmwareProviderPod';
106107

108+
export * from './requests/hosts';
109+
107110
const FALLBACK_DISK = {
108111
disk: {
109112
bus: 'virtio',

src/k8s/requests/hosts/index.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { SecretModel, BaremetalHostModel } from '../../../models';
2+
import { getName } from '../../../selectors';
3+
4+
const getSecretName = name => `${name}-secret`;
5+
6+
const getSecret = (name, namespace, username, password) => ({
7+
apiVersion: SecretModel.apiVersion,
8+
kind: SecretModel.kind,
9+
metadata: {
10+
namespace,
11+
name,
12+
},
13+
data: {
14+
username: btoa(username),
15+
password: btoa(password),
16+
},
17+
type: 'Opaque',
18+
});
19+
20+
const getBaremetalHostObject = (name, namespace, controller, secretName) => ({
21+
apiVersion: `${BaremetalHostModel.apiGroup}/${BaremetalHostModel.apiVersion}`,
22+
kind: BaremetalHostModel.kind,
23+
metadata: {
24+
name,
25+
namespace,
26+
},
27+
spec: {
28+
bmc: {
29+
address: controller,
30+
credentialsName: secretName,
31+
},
32+
},
33+
});
34+
35+
export const createBaremetalHost = async (k8sCreate, formData, selectedNamespace) => {
36+
const { name, username, password, controller } = formData;
37+
38+
const namespace = getName(selectedNamespace);
39+
const secretName = getSecretName(name.value);
40+
41+
const secret = getSecret(secretName, namespace, username.value, password.value);
42+
const bmo = getBaremetalHostObject(name.value, namespace, controller.value, secretName);
43+
44+
const results = [];
45+
46+
results.push(await k8sCreate(SecretModel, secret));
47+
results.push(await k8sCreate(BaremetalHostModel, bmo));
48+
49+
return results;
50+
};

0 commit comments

Comments
 (0)