foreman_acd 0.0.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/app/controllers/foreman_acd/api/v2/app_definitions_controller.rb +1 -2
  4. data/app/controllers/foreman_acd/app_definitions_controller.rb +29 -1
  5. data/app/controllers/foreman_acd/app_instances_controller.rb +70 -21
  6. data/app/controllers/foreman_acd/concerns/app_definition_parameters.rb +1 -1
  7. data/app/controllers/foreman_acd/concerns/app_instance_parameters.rb +1 -1
  8. data/app/controllers/ui_acd_controller.rb +0 -1
  9. data/app/models/foreman_acd/app_definition.rb +0 -1
  10. data/app/views/foreman_acd/app_definitions/_form.html.erb +6 -14
  11. data/app/views/foreman_acd/app_definitions/import.html.erb +18 -0
  12. data/app/views/foreman_acd/app_definitions/index.html.erb +9 -5
  13. data/app/views/foreman_acd/app_instances/_form.html.erb +5 -5
  14. data/app/views/foreman_acd/app_instances/deploy.html.erb +19 -0
  15. data/app/views/foreman_acd/app_instances/index.html.erb +6 -5
  16. data/app/views/foreman_acd/app_instances/report.html.erb +19 -0
  17. data/app/views/ui_acd/app_definition.json.rabl +1 -1
  18. data/config/routes.rb +7 -0
  19. data/db/migrate/20190610202252_create_app_definitions.rb +1 -3
  20. data/db/migrate/20190625140305_create_app_instances.rb +1 -1
  21. data/lib/foreman_acd/plugin.rb +19 -2
  22. data/lib/foreman_acd/version.rb +1 -1
  23. data/package.json +9 -33
  24. data/test/controllers/app_definitions_controller_test.rb +1 -0
  25. data/test/controllers/app_instances_controller_test.rb +1 -0
  26. data/test/controllers/ui_acd_controller_test.rb +10 -0
  27. data/test/models/app_definition_test.rb +1 -0
  28. data/test/models/app_instance_test.rb +1 -0
  29. data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +261 -0
  30. data/webpack/components/ApplicationDefinition/ApplicationDefinition.scss +1 -0
  31. data/webpack/components/ApplicationDefinition/ApplicationDefinitionActions.js +211 -0
  32. data/webpack/components/ApplicationDefinition/ApplicationDefinitionConstants.js +9 -0
  33. data/webpack/components/ApplicationDefinition/ApplicationDefinitionReducer.js +147 -0
  34. data/webpack/components/ApplicationDefinition/ApplicationDefinitionSelectors.js +6 -0
  35. data/webpack/components/ApplicationDefinition/index.js +29 -0
  36. data/webpack/components/ApplicationInstance/ApplicationInstance.js +342 -0
  37. data/webpack/components/ApplicationInstance/ApplicationInstance.scss +11 -0
  38. data/webpack/components/ApplicationInstance/ApplicationInstanceActions.js +210 -0
  39. data/webpack/components/ApplicationInstance/ApplicationInstanceConstants.js +12 -0
  40. data/webpack/components/ApplicationInstance/ApplicationInstanceReducer.js +223 -0
  41. data/webpack/components/ApplicationInstance/ApplicationInstanceSelectors.js +8 -0
  42. data/webpack/components/ApplicationInstance/components/AppDefinitionSelector.js +49 -0
  43. data/webpack/components/ApplicationInstance/components/Service.js +30 -0
  44. data/webpack/components/ApplicationInstance/components/ServiceCounter.js +37 -0
  45. data/webpack/components/ApplicationInstance/index.js +33 -0
  46. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.js +155 -0
  47. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.scss +27 -0
  48. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportActions.js +86 -0
  49. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportConstants.js +4 -0
  50. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportReducer.js +52 -0
  51. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportSelectors.js +5 -0
  52. data/webpack/components/ApplicationInstanceReport/components/ReportViewer.js +26 -0
  53. data/webpack/components/ApplicationInstanceReport/index.js +27 -0
  54. data/webpack/components/ParameterSelection/ParameterSelection.js +65 -161
  55. data/webpack/components/ParameterSelection/ParameterSelection.scss +10 -3
  56. data/webpack/components/ParameterSelection/ParameterSelectionActions.js +42 -71
  57. data/webpack/components/ParameterSelection/ParameterSelectionConstants.js +12 -19
  58. data/webpack/components/ParameterSelection/ParameterSelectionHelper.js +3 -3
  59. data/webpack/components/ParameterSelection/ParameterSelectionReducer.js +76 -75
  60. data/webpack/components/ParameterSelection/ParameterSelectionSelectors.js +2 -6
  61. data/webpack/components/ParameterSelection/__fixtures__/parameterSelection.fixtures.js +12 -21
  62. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionData_1.fixtures.js +1 -1
  63. data/webpack/components/ParameterSelection/__fixtures__/parameterSelectionReducer.fixtures.js +3 -45
  64. data/webpack/components/ParameterSelection/__tests__/ParameterSelection.test.js +20 -0
  65. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionReducer.test.js +22 -46
  66. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionSelectors.test.js +6 -6
  67. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelection.test.js.snap +40 -265
  68. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionReducer.test.js.snap +11 -96
  69. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionSelectors.test.js.snap +3 -9
  70. data/webpack/components/ParameterSelection/index.js +4 -6
  71. data/webpack/components/common/AddTableEntry.js +30 -0
  72. data/webpack/components/common/DeleteTableEntry.js +39 -0
  73. data/webpack/components/common/ExtSelect.js +43 -0
  74. data/webpack/components/common/RailsData.js +27 -0
  75. data/webpack/components/common/__tests__/AddTableEntry.test.js +26 -0
  76. data/webpack/components/common/__tests__/DeleteTableEntry.test.js +29 -0
  77. data/webpack/components/common/__tests__/ExtSelect.test.js +38 -0
  78. data/webpack/components/common/__tests__/RailsData.test.js +16 -0
  79. data/webpack/components/common/__tests__/__snapshots__/AddParameter.test.js.snap +35 -0
  80. data/webpack/components/common/__tests__/__snapshots__/AddTableEntry.test.js.snap +35 -0
  81. data/webpack/components/common/__tests__/__snapshots__/DeleteParameter.test.js.snap +41 -0
  82. data/webpack/components/common/__tests__/__snapshots__/DeleteTableEntry.test.js.snap +41 -0
  83. data/webpack/components/common/__tests__/__snapshots__/ExtSelect.test.js.snap +18 -0
  84. data/webpack/components/common/__tests__/__snapshots__/RailsData.test.js.snap +10 -0
  85. data/webpack/helper.js +20 -0
  86. data/webpack/index.js +6 -0
  87. data/webpack/reducer.js +40 -3
  88. metadata +51 -48
@@ -0,0 +1 @@
1
+ @import '~@theforeman/vendor/scss/variables';
@@ -0,0 +1,211 @@
1
+ import React from 'react';
2
+ import api from 'foremanReact/API';
3
+
4
+ import {
5
+ setModalOpen,
6
+ setModalClosed,
7
+ } from 'foremanReact/components/ForemanModal/ForemanModalActions';
8
+
9
+ import {
10
+ actionHeaderCellFormatter,
11
+ } from 'patternfly-react';
12
+
13
+ import {
14
+ propsToSnakeCase,
15
+ propsToCamelCase,
16
+ } from 'foremanReact/common/helpers';
17
+
18
+ import {
19
+ APPLICATION_DEFINITION_INIT,
20
+ APPLICATION_DEFINITION_SERVICE_DELETE,
21
+ APPLICATION_DEFINITION_SERVICE_ADD,
22
+ APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE,
23
+ APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM,
24
+ APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE,
25
+ APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL,
26
+ APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_OPEN,
27
+ APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_CLOSE,
28
+ } from './ApplicationDefinitionConstants';
29
+
30
+ export const initApplicationDefinition = (
31
+ services,
32
+ headerFormatter,
33
+ inlineEditFormatter,
34
+ inlineEditButtonsFormatter,
35
+ ) => dispatch => {
36
+ const initialState = {};
37
+
38
+ initialState.columns = [
39
+ {
40
+ property: 'name',
41
+ header: {
42
+ label: 'Name',
43
+ formatters: [headerFormatter],
44
+ props: {
45
+ index: 0,
46
+ style: {
47
+ width: '20%'
48
+ }
49
+ },
50
+ },
51
+ cell: {
52
+ formatters: [inlineEditFormatter]
53
+ }
54
+ },
55
+ {
56
+ property: 'description',
57
+ header: {
58
+ label: 'Description',
59
+ formatters: [headerFormatter],
60
+ props: {
61
+ index: 1,
62
+ style: {
63
+ width: '20%'
64
+ }
65
+ },
66
+ },
67
+ cell: {
68
+ formatters: [inlineEditFormatter]
69
+ }
70
+ },
71
+ {
72
+ property: 'hostgroup',
73
+ header: {
74
+ label: 'Hostgroup',
75
+ formatters: [headerFormatter],
76
+ props: {
77
+ index: 2,
78
+ style: {
79
+ width: '20%'
80
+ }
81
+ },
82
+ },
83
+ cell: {
84
+ formatters: [inlineEditFormatter]
85
+ }
86
+ },
87
+ {
88
+ property: 'minCount',
89
+ header: {
90
+ label: 'min count',
91
+ formatters: [headerFormatter],
92
+ props: {
93
+ index: 3,
94
+ style: {
95
+ width: '10%'
96
+ }
97
+ },
98
+ },
99
+ cell: {
100
+ formatters: [inlineEditFormatter]
101
+ }
102
+ },
103
+ {
104
+ property: 'maxCount',
105
+ header: {
106
+ label: 'max count',
107
+ formatters: [headerFormatter],
108
+ props: {
109
+ index: 3,
110
+ style: {
111
+ width: '10%'
112
+ }
113
+ },
114
+ },
115
+ cell: {
116
+ formatters: [inlineEditFormatter]
117
+ }
118
+ },
119
+ {
120
+ property: 'actions',
121
+ header: {
122
+ label: 'Actions',
123
+ formatters: [actionHeaderCellFormatter],
124
+ props: {
125
+ index: 4,
126
+ style: {
127
+ width: '20%'
128
+ }
129
+ },
130
+ },
131
+ cell: {
132
+ formatters: [inlineEditButtonsFormatter]
133
+ }
134
+ }
135
+ ];
136
+
137
+ initialState.services = services;
138
+
139
+ dispatch({
140
+ type: APPLICATION_DEFINITION_INIT,
141
+ payload: initialState,
142
+ });
143
+ };
144
+
145
+ export const addApplicationDefinitionService = (additionalData) => ({
146
+ type: APPLICATION_DEFINITION_SERVICE_ADD,
147
+ payload: {
148
+ ...additionalData,
149
+ },
150
+ });
151
+
152
+ export const deleteApplicationDefinitionService = (additionalData) => ({
153
+ type: APPLICATION_DEFINITION_SERVICE_DELETE,
154
+ payload: {
155
+ ...additionalData,
156
+ },
157
+ });
158
+
159
+ export const activateEditApplicationDefinitionService = (additionalData) => ({
160
+ type: APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE,
161
+ payload: {
162
+ ...additionalData,
163
+ },
164
+ });
165
+
166
+ export const confirmEditApplicationDefinitionService = (rowData) => ({
167
+ type: APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM,
168
+ payload: {
169
+ ...rowData,
170
+ },
171
+ });
172
+
173
+ export const cancelEditApplicationDefinitionService = (rowData) => ({
174
+ type: APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL,
175
+ payload: {
176
+ ...rowData,
177
+ },
178
+ });
179
+
180
+ export const changeEditApplicationDefinitionService = (value, additionalData) => ({
181
+ type: APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE,
182
+ payload: {
183
+ value,
184
+ ...additionalData,
185
+ },
186
+ });
187
+
188
+ export const openParameterSelectionModal = (additionalData) => dispatch => {
189
+ dispatch({
190
+ type: APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_OPEN,
191
+ payload: {
192
+ ...additionalData,
193
+ }
194
+ });
195
+ dispatch(
196
+ setModalOpen({ id: 'AppDefinitionParamSelection' })
197
+ );
198
+ }
199
+
200
+ export const closeParameterSelectionModal = (additionalData) => dispatch => {
201
+ dispatch({
202
+ type: APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_CLOSE,
203
+ payload: {
204
+ ...additionalData,
205
+ }
206
+ });
207
+
208
+ dispatch(
209
+ setModalClosed({ id: 'AppDefinitionParamSelection' })
210
+ );
211
+ }
@@ -0,0 +1,9 @@
1
+ export const APPLICATION_DEFINITION_INIT = 'INIT_APPLICATION_DEFINITION_INIT';
2
+ export const APPLICATION_DEFINITION_SERVICE_DELETE = 'APPLICATION_DEFINITION_SERVICE_DELETE';
3
+ export const APPLICATION_DEFINITION_SERVICE_ADD = 'APPLICATION_DEFINITION_SERVICE_ADD';
4
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE = 'APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE';
5
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM = 'APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM';
6
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE = 'APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE';
7
+ export const APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL = 'APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL';
8
+ export const APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_OPEN';
9
+ export const APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_CLOSE';
@@ -0,0 +1,147 @@
1
+ import Immutable from 'seamless-immutable';
2
+
3
+ import {
4
+ cloneDeep,
5
+ findIndex,
6
+ findLastIndex,
7
+ } from 'lodash';
8
+
9
+ import {
10
+ APPLICATION_DEFINITION_INIT,
11
+ APPLICATION_DEFINITION_SERVICE_DELETE,
12
+ APPLICATION_DEFINITION_SERVICE_ADD,
13
+ APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE,
14
+ APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM,
15
+ APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE,
16
+ APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL,
17
+ APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_OPEN,
18
+ APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_CLOSE,
19
+ } from './ApplicationDefinitionConstants';
20
+
21
+ export const initialState = Immutable({
22
+ name: false,
23
+ error: { errorMsg: '', status: '', statusText: '' },
24
+ });
25
+
26
+ const applicationDefinitionConf = (state = initialState, action) => {
27
+ const { payload } = action;
28
+
29
+ switch (action.type) {
30
+ case APPLICATION_DEFINITION_INIT: {
31
+ return state.merge(payload);
32
+ }
33
+ case APPLICATION_DEFINITION_SERVICE_ADD: {
34
+ let services = [];
35
+ let index = 1;
36
+
37
+ if ('services' in state && state.services !== undefined && state.services.length > 0) {
38
+ services = cloneDeep(state.services);
39
+ index = Math.max(...services.map(e => e.id)) + 1;
40
+ }
41
+
42
+ const newRow = {id: index, name: "", description: '', hostgroup: '', minCount: '', maxCount: '', parameters: [], newEntry: true };
43
+ newRow.backup = cloneDeep(newRow)
44
+ services.push(newRow);
45
+
46
+ return state.merge({
47
+ editMode: true,
48
+ services: services
49
+ });
50
+ }
51
+ case APPLICATION_DEFINITION_SERVICE_DELETE: {
52
+ const services = state.services.filter(v => v.id !== payload.rowData.id);
53
+ return state.merge({
54
+ services: services,
55
+ })
56
+ }
57
+ case APPLICATION_DEFINITION_SERVICE_EDIT_ACTIVATE: {
58
+ const services = cloneDeep(state.services);
59
+ const index = findIndex(services, { id: payload.rowData.id });
60
+
61
+ services[index].backup = cloneDeep(services[index]);
62
+
63
+ return state.merge({
64
+ editMode: true,
65
+ services: services
66
+ });
67
+ }
68
+ case APPLICATION_DEFINITION_SERVICE_EDIT_CONFIRM: {
69
+ const services = cloneDeep(state.services);
70
+ const index = findIndex(services, { id: payload.rowData.id });
71
+
72
+ delete services[index].backup;
73
+ delete services[index].newEntry;
74
+
75
+ return state.merge({
76
+ editMode: false,
77
+ services: services
78
+ });
79
+ }
80
+ case APPLICATION_DEFINITION_SERVICE_EDIT_CHANGE: {
81
+ const services = cloneDeep(state.services);
82
+ const index = findIndex(services, { id: payload.rowData.id });
83
+
84
+ services[index][payload.property] = payload.value;
85
+
86
+ return state.set('services', services);
87
+ }
88
+ case APPLICATION_DEFINITION_SERVICE_EDIT_CANCEL: {
89
+ const services = cloneDeep(state.services);
90
+ const index = findIndex(services, { id: payload.rowData.id });
91
+
92
+ services[index] = cloneDeep(services[index].backup);
93
+ delete services[index].backup;
94
+
95
+ if (services[index].newEntry === true) {
96
+ services.splice(index, 1);
97
+ }
98
+
99
+ return state.merge({
100
+ editMode: false,
101
+ services: services
102
+ });
103
+ }
104
+ case APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_OPEN: {
105
+ let parametersData = {};
106
+
107
+ if (payload && payload.rowData) {
108
+ parametersData.serviceDefinition = {
109
+ id: payload.rowData.id,
110
+ name: payload.rowData.name,
111
+ hostgroup_id: payload.rowData.hostgroup
112
+ }
113
+ parametersData.parameters = payload.rowData.parameters;
114
+
115
+ if (parametersData.parameters.length > 0) {
116
+ parametersData.mode = 'editDefinition';
117
+ } else {
118
+ parametersData.mode = 'newDefinition';
119
+ }
120
+ }
121
+
122
+ return state.merge({
123
+ parametersData: parametersData,
124
+ });
125
+ }
126
+ case APPLICATION_DEFINITION_PARAMETER_SELECTION_MODAL_CLOSE: {
127
+ if (payload.mode == 'save') {
128
+ const services = cloneDeep(state.services);
129
+ const index = findIndex(services, { id: state.parametersData.serviceDefinition.id });
130
+ services[index].parameters = cloneDeep(payload.serviceParameterSelection);
131
+
132
+ return state.merge({
133
+ parametersData: null,
134
+ services: services,
135
+ });
136
+ } else {
137
+ return state.merge({
138
+ parametersData: null,
139
+ });
140
+ }
141
+ }
142
+ default:
143
+ return state;
144
+ }
145
+ };
146
+
147
+ export default applicationDefinitionConf;
@@ -0,0 +1,6 @@
1
+ const applicationDefinitionConf = state => state.foremanAcd.applicationDefinitionConf;
2
+
3
+ export const selectEditMode = state => applicationDefinitionConf(state).editMode;
4
+ export const selectServices = state => applicationDefinitionConf(state).services;
5
+ export const selectColumns = state => applicationDefinitionConf(state).columns;
6
+ export const selectParametersData = state => applicationDefinitionConf(state).parametersData;
@@ -0,0 +1,29 @@
1
+ import { bindActionCreators } from 'redux';
2
+ import { connect } from 'react-redux';
3
+
4
+ import './ApplicationDefinition.scss';
5
+ import ApplicationDefinition from './ApplicationDefinition';
6
+ import * as ApplicationDefinitionActions from './ApplicationDefinitionActions';
7
+
8
+ import {
9
+ selectEditMode,
10
+ selectServices,
11
+ selectColumns,
12
+ selectParametersData,
13
+ } from './ApplicationDefinitionSelectors';
14
+
15
+ const mapStateToProps = state => ({
16
+ editMode: selectEditMode(state),
17
+ services: selectServices(state),
18
+ columns: selectColumns(state),
19
+ parametersData: selectParametersData(state),
20
+ });
21
+
22
+ const mapDispatchToProps = dispatch =>
23
+ bindActionCreators(ApplicationDefinitionActions, dispatch);
24
+
25
+ export default connect(
26
+ mapStateToProps,
27
+ mapDispatchToProps
28
+ )(ApplicationDefinition);
29
+
@@ -0,0 +1,342 @@
1
+ import React, { useState } from 'react'
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ Icon,
5
+ Button,
6
+ } from 'patternfly-react';
7
+ import * as resolve from 'table-resolver';
8
+ import ForemanModal from 'foremanReact/components/ForemanModal';
9
+ import Select from 'foremanReact/components/common/forms/Select';
10
+ import ParameterSelection from '../ParameterSelection';
11
+ import AddTableEntry from '../common/AddTableEntry';
12
+ import DeleteTableEntry from '../common/DeleteTableEntry';
13
+ import RailsData from '../common/RailsData'
14
+ import AppDefinitionSelector from './components/AppDefinitionSelector';
15
+ import ServiceCounter from './components/ServiceCounter';
16
+ import { arrayToObject } from '../../helper';
17
+
18
+ import {
19
+ Table,
20
+ FormControl,
21
+ inlineEditFormatterFactory,
22
+ } from 'patternfly-react';
23
+
24
+ class ApplicationInstance extends React.Component {
25
+
26
+ constructor(props) {
27
+ super(props);
28
+ }
29
+
30
+ isEditing({rowData}) {
31
+ return (rowData.backup !== undefined);
32
+ }
33
+
34
+ validateParameters() {
35
+ let result = true;
36
+ let msg = "";
37
+
38
+ this.props.hosts.forEach(h => {
39
+ if (h.parameters.map(e => e.value).filter(i => i == "").length > 0) {
40
+ result = false;
41
+
42
+ if (msg == "") {
43
+ msg += "For some hosts the values for some parameters are missing. Check the values for these hosts:\n";
44
+ }
45
+ msg += "- "+ h.hostname +"\n";
46
+ }
47
+ });
48
+
49
+ const invalidMinServices = this.props.services.filter(s => (Number(s.minCount) != 0) && (s.currentCount < s.minCount));
50
+ const invalidMaxServices = this.props.services.filter(s => (Number(s.maxCount) != 0) && (s.currentCount > s.maxCount));
51
+
52
+ if (invalidMinServices.length > 0 || invalidMaxServices.length > 0) {
53
+ result = false;
54
+
55
+ if (msg != "") {
56
+ msg += "\n";
57
+ }
58
+ msg += "Unachieved service counts: \n";
59
+
60
+ invalidMinServices.map(s => { msg += "- service "+ s.name +" expects at least "+ s.minCount +" configured hosts" });
61
+ invalidMaxServices.map(s => { msg += "- service "+ s.name +" expects no more than "+ s.maxCount +" configured hosts" });
62
+ }
63
+
64
+ if (result === false) {
65
+ window.alert(msg);
66
+ }
67
+ return result;
68
+ }
69
+
70
+ componentDidMount() {
71
+ const {
72
+ data: { mode, appDefinition, hosts, loadAppDefinitionUrl },
73
+ initApplicationInstance,
74
+ addApplicationInstanceHost,
75
+ deleteApplicationInstanceHost,
76
+ activateEditApplicationInstanceHost,
77
+ changeEditApplicationInstanceHost,
78
+ openParameterSelectionModal,
79
+ closeParameterSelectionModal,
80
+ loadApplicationDefinition,
81
+ } = this.props;
82
+
83
+ if (mode === 'editInstance') {
84
+ loadApplicationDefinition(appDefinition.id, { url: loadAppDefinitionUrl });
85
+ }
86
+
87
+ const inlineEditButtonsFormatter = inlineEditFormatterFactory({
88
+ isEditing: additionalData => this.props.editMode,
89
+ renderValue: (value, additionalData) => (
90
+ <td style={{ padding: '2px' }}>
91
+ <Button
92
+ bsStyle="default"
93
+ onClick={() => activateEditApplicationInstanceHost(additionalData)}
94
+ >
95
+ <Icon type="pf" name="edit" />
96
+ </Button>
97
+ <Button
98
+ bsStyle="default"
99
+ onClick={() => openParameterSelectionModal(additionalData)}
100
+ >
101
+ <Icon type="pf" name="settings" />
102
+ </Button>
103
+ <DeleteTableEntry
104
+ hidden={false}
105
+ disabled={false}
106
+ onDeleteTableEntry={deleteApplicationInstanceHost}
107
+ additionalData={additionalData}
108
+ />
109
+ </td>
110
+ ),
111
+ renderEdit: (value, additionalData) => (
112
+ <td style={{ padding: '2px' }}>
113
+ <Button bsStyle="default" disabled>
114
+ <Icon type="pf" name="edit" />
115
+ </Button>
116
+ <Button bsStyle="default" disabled>
117
+ <Icon type="pf" name="settings" />
118
+ </Button>
119
+ <DeleteTableEntry
120
+ hidden={false}
121
+ disabled={true}
122
+ onDeleteTableEntry={deleteApplicationInstanceHost}
123
+ additionalData={additionalData}
124
+ />
125
+ </td>
126
+ )
127
+ });
128
+ this.inlineEditButtonsFormatter = inlineEditButtonsFormatter;
129
+
130
+ const headerFormatter = value => <Table.Heading>{value}</Table.Heading>;
131
+ this.headerFormatter = headerFormatter;
132
+
133
+ const inlineEditFormatterImpl = {
134
+ renderValue: (value, additionalData) => (
135
+ <td>
136
+ <span className="static">{value}</span>
137
+ </td>
138
+ ),
139
+ renderEditText: (value, additionalData, subtype='text') => (
140
+ <td className="editing">
141
+ <FormControl
142
+ type={subtype}
143
+ defaultValue={value}
144
+ onBlur={e => changeEditApplicationInstanceHost(e.target.value, additionalData) }
145
+ />
146
+ </td>
147
+ ),
148
+ renderEditSelect: (value, additionalData, options) => (
149
+ <td className="editing">
150
+ <Select
151
+ value={value.toString()}
152
+ onChange={e => changeEditApplicationInstanceHost(e.target.value, additionalData) }
153
+ options={options}
154
+ allowClear
155
+ key="key"
156
+ />
157
+ </td>
158
+ )
159
+ };
160
+
161
+ const inlineEditFormatter = inlineEditFormatterFactory({
162
+ isEditing: additionalData => this.isEditing(additionalData),
163
+ renderValue: (value, additionalData) => {
164
+ let prettyValue = value;
165
+ if (additionalData.property == 'service') {
166
+ const serviceList = arrayToObject(this.props.services, "id", "name");
167
+ prettyValue = serviceList[value];
168
+ }
169
+ return inlineEditFormatterImpl.renderValue(prettyValue, additionalData)
170
+ },
171
+ renderEdit: (value, additionalData) => {
172
+ let prettyValue = value;
173
+ if (additionalData.property == 'service') {
174
+ const availableServices = this.props.services.filter(service => ((Number(service['maxCount']) == 0) || (service['currentCount'] < service['maxCount'])));
175
+ const serviceList = arrayToObject(availableServices, "id", "name");
176
+
177
+ if (additionalData.rowData.newEntry === true) {
178
+ return inlineEditFormatterImpl.renderEditSelect(value, additionalData, serviceList);
179
+ }
180
+ prettyValue = serviceList[value];
181
+ return inlineEditFormatterImpl.renderValue(prettyValue, additionalData)
182
+ }
183
+ return inlineEditFormatterImpl.renderEditText(prettyValue, additionalData);
184
+ }
185
+ });
186
+ this.inlineEditFormatter = inlineEditFormatter;
187
+
188
+ initApplicationInstance(
189
+ appDefinition,
190
+ hosts,
191
+ this.headerFormatter,
192
+ this.inlineEditFormatter,
193
+ this.inlineEditButtonsFormatter,
194
+ );
195
+ };
196
+
197
+ render() {
198
+ const {
199
+ data: { mode, applications, organization, location, loadForemanDataUrl, loadAppDefinitionUrl },
200
+ appDefinition,
201
+ services,
202
+ hosts,
203
+ columns,
204
+ addApplicationInstanceHost,
205
+ confirmEditApplicationInstanceHost,
206
+ cancelEditApplicationInstanceHost,
207
+ openParameterSelectionModal,
208
+ closeParameterSelectionModal,
209
+ ParameterSelectionModal,
210
+ loadApplicationDefinition,
211
+ } = this.props;
212
+
213
+ // Start from validation when pressing submit. This should be in componentDidMount() but
214
+ // unfortunatley then the event wasn't fired. To make sure, that the on-click is only added
215
+ // once, there is a workaround to check if a css class "bound" exists.
216
+ $('input[type="submit"][name="commit"]:not(.bound)').addClass('bound').on('click', () => this.validateParameters());
217
+
218
+ return (
219
+ <span>
220
+ <div class="service-counter">
221
+ <ServiceCounter
222
+ title="Service counts"
223
+ serviceList={ services }
224
+ hostList={ hosts }
225
+ />
226
+ </div>
227
+ <div>
228
+ <AppDefinitionSelector
229
+ label="Application Definition"
230
+ editable={ mode == 'newInstance' }
231
+ viewText={ appDefinition.name }
232
+ options={ applications }
233
+ onChange={ loadApplicationDefinition }
234
+ selectValue={ appDefinition.id.toString() }
235
+ additionalData={{url: loadAppDefinitionUrl}}
236
+ />
237
+ </div>
238
+ <div className="form-group">
239
+ <AddTableEntry
240
+ hidden={ false }
241
+ disabled={ this.props.editMode }
242
+ onAddTableEntry={ addApplicationInstanceHost }
243
+ />
244
+ <Table.PfProvider
245
+ striped
246
+ bordered
247
+ hover
248
+ dataTable
249
+ inlineEdit
250
+ columns={columns}
251
+ components={{
252
+ body: {
253
+ row: Table.InlineEditRow,
254
+ cell: cellProps => cellProps.children
255
+ }
256
+ }}
257
+ >
258
+ <Table.Header headerRows={resolve.headerRows({ columns })} />
259
+ <Table.Body
260
+ rows={hosts}
261
+ rowKey="id"
262
+ onRow={(rowData, { rowIndex }) => ({
263
+ role: 'row',
264
+ isEditing: () => this.isEditing({ rowData }),
265
+ onCancel: () => cancelEditApplicationInstanceHost({ rowData, rowIndex }),
266
+ onConfirm: () => confirmEditApplicationInstanceHost({ rowData, rowIndex }),
267
+ last: rowIndex === services.length - 1
268
+ })}
269
+ />
270
+ </Table.PfProvider>
271
+ <AddTableEntry
272
+ hidden={ false }
273
+ disabled={ this.props.editMode }
274
+ onAddTableEntry={ addApplicationInstanceHost }
275
+ />
276
+ </div>
277
+ <div>
278
+ <ForemanModal
279
+ id="AppInstanceParamSelection"
280
+ dialogClassName="param_selection_modal"
281
+ title="Parameter specification for Application Instance"
282
+ >
283
+ <ForemanModal.Header closeButton={false}>
284
+ Parameter specification
285
+ </ForemanModal.Header>
286
+ {this.props.parametersData ? (
287
+ <ParameterSelection
288
+ location={ location }
289
+ organization={ organization }
290
+ loadForemanDataUrl= { loadForemanDataUrl }
291
+ data={ this.props.parametersData }
292
+ />
293
+ ) : (<span>Empty</span>)
294
+ }
295
+ <ForemanModal.Footer>
296
+ <div>
297
+ <Button bsStyle="primary" onClick={() => closeParameterSelectionModal({ mode: 'save' })}>Save</Button>
298
+ <Button bsStyle="default" onClick={() => closeParameterSelectionModal({ mode: 'cancel' })}>Cancel</Button>
299
+ </div>
300
+ </ForemanModal.Footer>
301
+ </ForemanModal>
302
+ </div>
303
+ <RailsData
304
+ key='applications_instance'
305
+ view='app_instance'
306
+ parameter='hosts'
307
+ value={JSON.stringify(this.props.hosts)}
308
+ />
309
+ </span>
310
+ )};
311
+ }
312
+
313
+ ApplicationInstance.defaultProps = {
314
+ error: {},
315
+ appDefinition: { "id": '', "name": '' },
316
+ editMode: false,
317
+ services: [],
318
+ hosts: [],
319
+ parametersData: {},
320
+ columns: [],
321
+ editParamsOfRowId: null,
322
+ }
323
+
324
+ ApplicationInstance.propTypes = {
325
+ initApplicationInstance: PropTypes.func,
326
+ editMode: PropTypes.bool.isRequired,
327
+ services: PropTypes.array,
328
+ appDefinition: PropTypes.object,
329
+ columns: PropTypes.array,
330
+ loadApplicationDefinition: PropTypes.func,
331
+ addApplicationInstanceHost: PropTypes.func,
332
+ deleteApplicationInstanceHost: PropTypes.func,
333
+ activateEditApplicationInstanceHost: PropTypes.func,
334
+ confirmEditApplicationInstanceHost: PropTypes.func,
335
+ cancelEditApplicationInstanceHost: PropTypes.func,
336
+ changeEditApplicationInstanceHost: PropTypes.func,
337
+ openParameterSelectionModal: PropTypes.func,
338
+ closeParameterSelectionModal: PropTypes.func,
339
+ parametersData: PropTypes.object,
340
+ };
341
+
342
+ export default ApplicationInstance;