foreman_acd 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -86
  3. data/app/controllers/foreman_acd/ansible_playbooks_controller.rb +13 -11
  4. data/app/controllers/foreman_acd/api/v2/ansible_playbooks_controller.rb +4 -3
  5. data/app/controllers/foreman_acd/api/v2/app_definitions_controller.rb +1 -0
  6. data/app/controllers/foreman_acd/api/v2/app_instances_controller.rb +9 -1
  7. data/app/controllers/foreman_acd/app_definitions_controller.rb +14 -9
  8. data/app/controllers/foreman_acd/app_instances_controller.rb +97 -25
  9. data/app/controllers/foreman_acd/concerns/ansible_playbook_parameters.rb +1 -1
  10. data/app/controllers/foreman_acd/concerns/app_definition_parameters.rb +1 -1
  11. data/app/controllers/foreman_acd/concerns/app_instance_parameters.rb +1 -1
  12. data/app/controllers/foreman_acd/remote_execution_controller.rb +3 -6
  13. data/app/controllers/ui_acd_controller.rb +9 -0
  14. data/app/lib/actions/foreman_acd/deploy_all_hosts.rb +42 -0
  15. data/app/lib/actions/foreman_acd/run_configurator.rb +41 -0
  16. data/app/models/concerns/foreman_acd/host_managed_extensions.rb +51 -0
  17. data/app/models/foreman_acd/acd_provider.rb +3 -0
  18. data/app/models/foreman_acd/ansible_playbook.rb +30 -13
  19. data/app/models/foreman_acd/app_definition.rb +24 -1
  20. data/app/models/foreman_acd/app_instance.rb +40 -5
  21. data/app/models/foreman_acd/foreman_host.rb +23 -0
  22. data/app/models/foreman_acd/taxonomy_extensions.rb +17 -0
  23. data/app/services/foreman_acd/app_configurator.rb +7 -10
  24. data/app/services/foreman_acd/app_deployer.rb +59 -47
  25. data/app/services/foreman_acd/inventory_creator.rb +12 -25
  26. data/app/views/foreman_acd/ansible_playbooks/_form.html.erb +11 -2
  27. data/app/views/foreman_acd/ansible_playbooks/edit.html.erb +9 -1
  28. data/app/views/foreman_acd/ansible_playbooks/index.html.erb +3 -3
  29. data/app/views/foreman_acd/api/v2/ansible_playbooks/base.json.rabl +2 -0
  30. data/app/views/foreman_acd/api/v2/ansible_playbooks/index.json.rabl +2 -0
  31. data/app/views/foreman_acd/api/v2/ansible_playbooks/show.json.rabl +6 -0
  32. data/app/views/foreman_acd/api/v2/app_definitions/base.json.rabl +2 -0
  33. data/app/views/foreman_acd/api/v2/app_definitions/index.json.rabl +2 -0
  34. data/app/views/foreman_acd/api/v2/app_definitions/show.json.rabl +6 -0
  35. data/app/views/foreman_acd/api/v2/app_instances/base.json.rabl +3 -1
  36. data/app/views/foreman_acd/api/v2/app_instances/index.json.rabl +2 -0
  37. data/app/views/foreman_acd/api/v2/app_instances/show.json.rabl +2 -0
  38. data/app/views/foreman_acd/app_definitions/_form.html.erb +8 -0
  39. data/app/views/foreman_acd/app_definitions/edit.html.erb +10 -5
  40. data/app/views/foreman_acd/app_definitions/index.html.erb +4 -4
  41. data/app/views/foreman_acd/app_instances/_form.html.erb +3 -3
  42. data/app/views/foreman_acd/app_instances/edit.html.erb +10 -0
  43. data/app/views/foreman_acd/app_instances/index.html.erb +87 -14
  44. data/app/views/foreman_acd/app_instances/report.html.erb +5 -2
  45. data/app/views/templates/job/run_acd_ansible_playbook.erb +14 -1
  46. data/app/views/ui_acd/app_definition.json.rabl +1 -1
  47. data/config/routes.rb +1 -2
  48. data/db/migrate/20200917120220_add_ansible_playbook_id.rb +1 -1
  49. data/db/migrate/20201016002819_add_ansible_vars_all_to_app_definitions.rb +3 -0
  50. data/db/migrate/20201016104338_add_ansible_vars_all_to_app_instances.rb +3 -0
  51. data/db/migrate/20210112111548_add_organization_to_app_instance.rb +22 -0
  52. data/db/migrate/20210112113853_add_location_to_app_instance.rb +8 -0
  53. data/db/migrate/20210202141658_create_foreman_hosts.rb +24 -0
  54. data/db/migrate/20210204111306_remove_hosts_from_app_instances.rb +8 -0
  55. data/db/migrate/20210209091014_rename_acd_tables.rb +16 -0
  56. data/db/migrate/20210216083522_add_last_progress_report.rb +8 -0
  57. data/db/migrate/20210216091529_add_last_deploy_task.rb +8 -0
  58. data/db/seeds.d/62_acd_proxy_feature.rb +1 -3
  59. data/db/seeds.d/75-job_templates.rb +6 -1
  60. data/lib/foreman_acd/engine.rb +14 -3
  61. data/lib/foreman_acd/plugin.rb +49 -28
  62. data/lib/foreman_acd/version.rb +1 -1
  63. data/locale/en/foreman_acd.edit.po +326 -0
  64. data/locale/en/foreman_acd.po +232 -2
  65. data/{app/controllers/foreman_acd/api/v2/app_playbooks_controller.rb → locale/en/foreman_acd.po.time_stamp} +0 -0
  66. data/locale/foreman_acd.pot +343 -8
  67. data/test/controllers/ansible_playbooks_controller_test.rb +27 -0
  68. data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +31 -18
  69. data/webpack/components/ApplicationDefinition/ApplicationDefinitionActions.js +8 -0
  70. data/webpack/components/ApplicationDefinition/ApplicationDefinitionConstants.js +1 -0
  71. data/webpack/components/ApplicationDefinition/ApplicationDefinitionReducer.js +26 -0
  72. data/webpack/components/ApplicationDefinition/ApplicationDefinitionSelectors.js +1 -0
  73. data/webpack/components/ApplicationDefinition/index.js +2 -0
  74. data/webpack/components/ApplicationInstance/ApplicationInstance.js +67 -30
  75. data/webpack/components/ApplicationInstance/ApplicationInstanceActions.js +8 -0
  76. data/webpack/components/ApplicationInstance/ApplicationInstanceConstants.js +1 -0
  77. data/webpack/components/ApplicationInstance/ApplicationInstanceReducer.js +16 -2
  78. data/webpack/components/ApplicationInstance/ApplicationInstanceSelectors.js +1 -0
  79. data/webpack/components/ApplicationInstance/components/Service.js +1 -1
  80. data/webpack/components/ApplicationInstance/index.js +2 -0
  81. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.js +53 -60
  82. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.scss +17 -0
  83. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportActions.js +7 -51
  84. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportConstants.js +2 -4
  85. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportReducer.js +4 -18
  86. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportSelectors.js +0 -1
  87. data/webpack/components/ApplicationInstanceReport/components/ReportViewer.js +1 -1
  88. data/webpack/components/ApplicationInstanceReport/index.js +0 -2
  89. data/webpack/components/ParameterSelection/ParameterSelection.js +10 -0
  90. data/webpack/components/ParameterSelection/ParameterSelectionActions.js +8 -7
  91. data/webpack/components/common/DeleteTableEntry.js +3 -2
  92. data/webpack/components/common/EditTableEntry.js +2 -1
  93. data/webpack/components/common/LockTableEntry.js +2 -1
  94. metadata +48 -6
  95. data/app/views/foreman_acd/app_instances/deploy.html.erb +0 -19
@@ -30,6 +30,7 @@ import {
30
30
  APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE,
31
31
  APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN,
32
32
  APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE,
33
+ APPLICATION_DEFINITION_CHANGE_PARAMETER_SELECTION_MODE,
33
34
  } from './ApplicationDefinitionConstants';
34
35
 
35
36
  import {
@@ -293,3 +294,10 @@ export const closeAnsibleParameterSelectionModal = (additionalData) => dispatch
293
294
  setModalClosed({ id: 'AppDefinitionAnsibleParamSelection' })
294
295
  );
295
296
  }
297
+
298
+ export const changeParameterSelectionMode = (additionalData) => ({
299
+ type: APPLICATION_DEFINITION_CHANGE_PARAMETER_SELECTION_MODE,
300
+ payload: {
301
+ ...additionalData,
302
+ },
303
+ })
@@ -12,3 +12,4 @@ export const APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN = 'AP
12
12
  export const APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE';
13
13
  export const APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN';
14
14
  export const APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE';
15
+ export const APPLICATION_DEFINITION_CHANGE_PARAMETER_SELECTION_MODE = 'APPLICATION_DEFINITION_CHANGE_PARAMETER_SELECTION_MODE';
@@ -21,6 +21,7 @@ import {
21
21
  APPLICATION_DEFINITION_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE,
22
22
  APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN,
23
23
  APPLICATION_DEFINITION_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE,
24
+ APPLICATION_DEFINITION_CHANGE_PARAMETER_SELECTION_MODE,
24
25
  } from './ApplicationDefinitionConstants';
25
26
 
26
27
  import {
@@ -109,6 +110,28 @@ const applicationDefinitionConf = (state = initialState, action) => {
109
110
  const services = cloneDeep(state.services);
110
111
  const index = findIndex(services, { id: payload.rowData.id });
111
112
 
113
+ const thisService = services[index];
114
+
115
+ if (thisService.name == '') {
116
+ window.alert("Every service needs to have a valid name.");
117
+ return state;
118
+ }
119
+
120
+ if (thisService.hostgroup == '') {
121
+ window.alert("Every service needs to be assigned to a hostgroup.");
122
+ return state;
123
+ }
124
+
125
+ if (thisService.ansibleGroup == '') {
126
+ window.alert("Every service needs to be assigned to a ansible group.");
127
+ return state;
128
+ }
129
+
130
+ if (state.services.filter(v => v.name === thisService.name && v.id != thisService.id).length > 0) {
131
+ window.alert("Service name already used in this Application Definition. Please make sure that every service name is unique.");
132
+ return state;
133
+ }
134
+
112
135
  delete services[index].backup;
113
136
  delete services[index].newEntry;
114
137
 
@@ -235,6 +258,9 @@ const applicationDefinitionConf = (state = initialState, action) => {
235
258
  }
236
259
  return state.merge(newState);
237
260
  }
261
+ case APPLICATION_DEFINITION_CHANGE_PARAMETER_SELECTION_MODE: {
262
+ return state.merge({ paramEditMode: payload.mode });
263
+ }
238
264
  default:
239
265
  return state;
240
266
  }
@@ -6,3 +6,4 @@ export const selectServices = state => applicationDefinitionConf(state).services
6
6
  export const selectColumns = state => applicationDefinitionConf(state).columns;
7
7
  export const selectParametersData = state => applicationDefinitionConf(state).parametersData;
8
8
  export const selectAnsibleVarsAll = state => applicationDefinitionConf(state).ansibleVarsAll;
9
+ export const selectParamEditMode = state => applicationDefinitionConf(state).paramEditMode;
@@ -12,6 +12,7 @@ import {
12
12
  selectColumns,
13
13
  selectParametersData,
14
14
  selectAnsibleVarsAll,
15
+ selectParamEditMode,
15
16
  } from './ApplicationDefinitionSelectors';
16
17
 
17
18
  const mapStateToProps = state => ({
@@ -21,6 +22,7 @@ const mapStateToProps = state => ({
21
22
  columns: selectColumns(state),
22
23
  parametersData: selectParametersData(state),
23
24
  ansibleVarsAll: selectAnsibleVarsAll(state),
25
+ paramEditMode: selectParamEditMode(state),
24
26
  });
25
27
 
26
28
  const mapDispatchToProps = dispatch =>
@@ -6,6 +6,10 @@ import {
6
6
  } from 'patternfly-react';
7
7
  import * as resolve from 'table-resolver';
8
8
  import ForemanModal from 'foremanReact/components/ForemanModal';
9
+ import {
10
+ sprintf,
11
+ translate as __
12
+ } from 'foremanReact/common/I18n';
9
13
  import Select from 'foremanReact/components/common/forms/Select';
10
14
  import ParameterSelection from '../ParameterSelection';
11
15
  import AddTableEntry from '../common/AddTableEntry';
@@ -37,16 +41,20 @@ class ApplicationInstance extends React.Component {
37
41
  return (rowData.backup !== undefined);
38
42
  }
39
43
 
44
+ addTableEntryAllowed() {
45
+ return this.props.editMode || this.props.appDefinition.id == ''
46
+ }
47
+
40
48
  validateParameters() {
41
49
  let result = true;
42
50
  let msg = "";
43
51
 
44
52
  this.props.hosts.forEach(h => {
45
- if (h.parameters.map(e => e.value).filter(i => i == "").length > 0) {
53
+ if (h.foremanParameters.map(e => e.value).filter(i => i == "").length > 0) {
46
54
  result = false;
47
55
 
48
56
  if (msg == "") {
49
- msg += "For some hosts the values for some parameters are missing. Check the values for these hosts:\n";
57
+ msg += __("For some hosts the values for some parameters are missing. Check the values for these hosts:\n");
50
58
  }
51
59
  msg += "- "+ h.hostname +"\n";
52
60
  }
@@ -55,22 +63,31 @@ class ApplicationInstance extends React.Component {
55
63
  const invalidMinServices = this.props.services.filter(s => (Number(s.minCount) != 0) && (s.currentCount < s.minCount));
56
64
  const invalidMaxServices = this.props.services.filter(s => (Number(s.maxCount) != 0) && (s.currentCount > s.maxCount));
57
65
 
66
+ console.log(invalidMinServices);
67
+
58
68
  if (invalidMinServices.length > 0 || invalidMaxServices.length > 0) {
59
69
  result = false;
60
70
 
61
71
  if (msg != "") {
62
72
  msg += "\n";
63
73
  }
64
- msg += "Unachieved service counts: \n";
65
74
 
66
- invalidMinServices.map(s => { msg += "- service "+ s.name +" expects at least "+ s.minCount +" configured hosts" });
67
- invalidMaxServices.map(s => { msg += "- service "+ s.name +" expects no more than "+ s.maxCount +" configured hosts" });
75
+ msg += __("Unachieved service counts:");
76
+ msg += "\n";
77
+
78
+ invalidMinServices.map(s => { msg += sprintf(
79
+ __(`- service ${s.name} expects at ${s.minCount} least configured hosts\n`)
80
+ )});
81
+
82
+ invalidMaxServices.map(s => { msg += sprintf(
83
+ __(`- service ${s.name} expects no more than ${s.axCount} configured hosts\n`)
84
+ )});
68
85
  }
69
86
 
70
- if (result === false) {
71
- window.alert(msg);
87
+ return {
88
+ validateResult: result,
89
+ validateMsg: msg
72
90
  }
73
- return result;
74
91
  }
75
92
 
76
93
  componentDidMount() {
@@ -98,21 +115,21 @@ class ApplicationInstance extends React.Component {
98
115
  bsStyle="default"
99
116
  onClick={() => activateEditApplicationInstanceHost(additionalData)}
100
117
  >
101
- <Icon type="pf" name="edit" title="edit entry" />
118
+ <Icon type="pf" name="edit" title={__("edit entry")} />
102
119
  </Button>
103
120
  &nbsp;
104
121
  <Button
105
122
  bsStyle="default"
106
123
  onClick={() => openForemanParameterSelectionModal(additionalData)}
107
124
  >
108
- <Icon type="pf" name="settings" title="change parameters" />
125
+ <Icon type="pf" name="settings" title={__("change parameters")} />
109
126
  </Button>
110
127
  &nbsp;
111
128
  <Button
112
129
  bsStyle="default"
113
130
  onClick={() => openAnsibleParameterSelectionModal(additionalData)}
114
131
  >
115
- <span title="change ansible variables">A</span>
132
+ <span title={__("change ansible variables")}>A</span>
116
133
  </Button>
117
134
  &nbsp;
118
135
  <DeleteTableEntry
@@ -126,11 +143,11 @@ class ApplicationInstance extends React.Component {
126
143
  renderEdit: (value, additionalData) => (
127
144
  <td style={{ padding: '2px' }}>
128
145
  <Button bsStyle="default" disabled>
129
- <Icon type="pf" name="edit" />
146
+ <Icon type="pf" name={__("edit")} />
130
147
  </Button>
131
148
  &nbsp;
132
149
  <Button bsStyle="default" disabled>
133
- <Icon type="pf" name="settings" />
150
+ <Icon type="pf" name={__("settings")} />
134
151
  </Button>
135
152
  &nbsp;
136
153
  <Button bsStyle="default" disabled>
@@ -200,6 +217,12 @@ class ApplicationInstance extends React.Component {
200
217
  prettyValue = serviceList[value];
201
218
  return inlineEditFormatterImpl.renderValue(prettyValue, additionalData)
202
219
  }
220
+ if (additionalData.property == 'hostname') {
221
+ if (additionalData.rowData.newEntry === true) {
222
+ return inlineEditFormatterImpl.renderEditText(value, additionalData);
223
+ }
224
+ return inlineEditFormatterImpl.renderValue(prettyValue, additionalData)
225
+ }
203
226
  return inlineEditFormatterImpl.renderEditText(prettyValue, additionalData);
204
227
  }
205
228
  });
@@ -228,13 +251,17 @@ class ApplicationInstance extends React.Component {
228
251
  closeForemanParameterSelectionModal,
229
252
  openAnsibleParameterSelectionModal,
230
253
  closeAnsibleParameterSelectionModal,
254
+ changeParameterSelectionMode,
231
255
  loadApplicationDefinition,
232
256
  } = this.props;
233
257
 
234
- // Start from validation when pressing submit. This should be in componentDidMount() but
235
- // unfortunatley then the event wasn't fired. To make sure, that the on-click is only added
236
- // once, there is a workaround to check if a css class "bound" exists.
237
- $('input[type="submit"][name="commit"]:not(.bound)').addClass('bound').on('click', () => this.validateParameters());
258
+ let { validateResult, validateMsg } = this.validateParameters();
259
+
260
+ if (validateResult == false) {
261
+ $('input[type="submit"][name="commit"]').attr("disabled", true);
262
+ } else {
263
+ $('input[type="submit"][name="commit"]').attr("disabled", false);
264
+ }
238
265
 
239
266
  return (
240
267
  <span>
@@ -255,13 +282,13 @@ class ApplicationInstance extends React.Component {
255
282
  selectValue={ appDefinition.id.toString() }
256
283
  additionalData={{url: appDefinitionUrl}}
257
284
  />
285
+ {appDefinition.id == '' ? (
286
+ <p style={{ paddingTop: 25 }}>
287
+ <pre>{ "App Definition can't be blank" }</pre>
288
+ </p>
289
+ ) : (<div></div>)}
258
290
  </div>
259
291
  <div className="form-group">
260
- <AddTableEntry
261
- hidden={ false }
262
- disabled={ this.props.editMode }
263
- onAddTableEntry={ addApplicationInstanceHost }
264
- />
265
292
  <Table.PfProvider
266
293
  striped
267
294
  bordered
@@ -291,7 +318,7 @@ class ApplicationInstance extends React.Component {
291
318
  </Table.PfProvider>
292
319
  <AddTableEntry
293
320
  hidden={ false }
294
- disabled={ this.props.editMode }
321
+ disabled={ this.addTableEntryAllowed() }
295
322
  onAddTableEntry={ addApplicationInstanceHost }
296
323
  />
297
324
  <span style={{ marginLeft: 30 }}>
@@ -304,7 +331,7 @@ class ApplicationInstance extends React.Component {
304
331
  isAllGroup: true
305
332
  })}
306
333
  >
307
- <span title="change ansible variables for 'all'">A</span>
334
+ <span title={__("change ansible variables for 'all'")}>A</span>
308
335
  </Button>
309
336
  </span>
310
337
  </div>
@@ -312,13 +339,14 @@ class ApplicationInstance extends React.Component {
312
339
  <ForemanModal
313
340
  id="AppInstanceForemanParamSelection"
314
341
  dialogClassName="param_selection_modal"
315
- title="Foreman Parameter specification for Application Instance"
342
+ title={__("Foreman Parameter specification for Application Instance")}
316
343
  >
317
344
  <ForemanModal.Header closeButton={false}>
318
345
  Parameter specification
319
346
  </ForemanModal.Header>
320
347
  {this.props.parametersData ? (
321
348
  <ParameterSelection
349
+ editModeCallback={ (hide) => changeParameterSelectionMode({ mode: hide })}
322
350
  paramType={ PARAMETER_SELECTION_PARAM_TYPE_FOREMAN }
323
351
  location={ location }
324
352
  organization={ organization }
@@ -329,8 +357,8 @@ class ApplicationInstance extends React.Component {
329
357
  }
330
358
  <ForemanModal.Footer>
331
359
  <div>
332
- <Button bsStyle="primary" onClick={() => closeForemanParameterSelectionModal({ mode: 'save' })}>Save</Button>
333
- <Button bsStyle="default" onClick={() => closeForemanParameterSelectionModal({ mode: 'cancel' })}>Cancel</Button>
360
+ <Button bsStyle="primary" disabled={this.props.paramEditMode} onClick={() => closeForemanParameterSelectionModal({ mode: 'save' })}>{__("Save")}</Button>
361
+ <Button bsStyle="default" disabled={this.props.paramEditMode} onClick={() => closeForemanParameterSelectionModal({ mode: 'cancel' })}>{__("Cancel")}</Button>
334
362
  </div>
335
363
  </ForemanModal.Footer>
336
364
  </ForemanModal>
@@ -339,13 +367,14 @@ class ApplicationInstance extends React.Component {
339
367
  <ForemanModal
340
368
  id="AppInstanceAnsibleParamSelection"
341
369
  dialogClassName="param_selection_modal"
342
- title="Ansible group variables for Application Instance"
370
+ title={__("Ansible group variables for Application Instance")}
343
371
  >
344
372
  <ForemanModal.Header closeButton={false}>
345
373
  Parameter specification
346
374
  </ForemanModal.Header>
347
375
  {this.props.parametersData ? (
348
376
  <ParameterSelection
377
+ editModeCallback={ (hide) => changeParameterSelectionMode({ mode: hide })}
349
378
  paramType={ PARAMETER_SELECTION_PARAM_TYPE_ANSIBLE }
350
379
  location={ location }
351
380
  organization={ organization }
@@ -355,12 +384,17 @@ class ApplicationInstance extends React.Component {
355
384
  }
356
385
  <ForemanModal.Footer>
357
386
  <div>
358
- <Button bsStyle="primary" onClick={() => closeAnsibleParameterSelectionModal({ mode: 'save' })}>Save</Button>
359
- <Button bsStyle="default" onClick={() => closeAnsibleParameterSelectionModal({ mode: 'cancel' })}>Cancel</Button>
387
+ <Button bsStyle="primary" disabled={this.props.paramEditMode} onClick={() => closeAnsibleParameterSelectionModal({ mode: 'save' })}>{__("Save")}</Button>
388
+ <Button bsStyle="default" disabled={this.props.paramEditMode} onClick={() => closeAnsibleParameterSelectionModal({ mode: 'cancel' })}>{__("Cancel")}</Button>
360
389
  </div>
361
390
  </ForemanModal.Footer>
362
391
  </ForemanModal>
363
392
  </div>
393
+ {validateResult == false ? (
394
+ <p style={{ paddingTop: 25 }}>
395
+ <pre>{ validateMsg }</pre>
396
+ </p>
397
+ ) : (<div></div>)}
364
398
  <RailsData
365
399
  key='applications_instance'
366
400
  view='app_instance'
@@ -387,6 +421,7 @@ ApplicationInstance.defaultProps = {
387
421
  parametersData: {},
388
422
  columns: [],
389
423
  editParamsOfRowId: null,
424
+ paramEditMode: false,
390
425
  }
391
426
 
392
427
  ApplicationInstance.propTypes = {
@@ -408,7 +443,9 @@ ApplicationInstance.propTypes = {
408
443
  closeForemanParameterSelectionModal: PropTypes.func,
409
444
  openAnsibleParameterSelectionModal: PropTypes.func,
410
445
  closeAnsibleParameterSelectionModal: PropTypes.func,
446
+ changeParameterSelectionMode: PropTypes.func,
411
447
  parametersData: PropTypes.object,
448
+ paramEditMode: PropTypes.bool,
412
449
  };
413
450
 
414
451
  export default ApplicationInstance;
@@ -29,6 +29,7 @@ import {
29
29
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_REQUEST,
30
30
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_SUCCESS,
31
31
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_FAILURE,
32
+ APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE,
32
33
  } from './ApplicationInstanceConstants';
33
34
 
34
35
  export const initApplicationInstance = (
@@ -237,3 +238,10 @@ export const closeAnsibleParameterSelectionModal = (additionalData) => dispatch
237
238
  setModalClosed({ id: 'AppInstanceAnsibleParamSelection' })
238
239
  );
239
240
  }
241
+
242
+ export const changeParameterSelectionMode = (additionalData) => ({
243
+ type: APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE,
244
+ payload: {
245
+ ...additionalData,
246
+ },
247
+ })
@@ -12,3 +12,4 @@ export const APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN = 'APPL
12
12
  export const APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE';
13
13
  export const APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN';
14
14
  export const APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE';
15
+ export const APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE = 'APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE';
@@ -21,6 +21,7 @@ import {
21
21
  APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE,
22
22
  APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN,
23
23
  APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE,
24
+ APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE,
24
25
  } from './ApplicationInstanceConstants';
25
26
 
26
27
  import {
@@ -133,13 +134,23 @@ const applicationInstanceConf = (state = initialState, action) => {
133
134
  return state;
134
135
  }
135
136
 
137
+ // hostnames are lower case
138
+ thisHost.hostname = thisHost.hostname.toLowerCase();
139
+
140
+ const hostnameRegex = /^[0-9a-z]([0-9a-z\-]{0,61}[0-9a-z])$/;
141
+
142
+ if (thisHost.hostname.match(hostnameRegex) == undefined) {
143
+ window.alert("The hostname uses not allowed characters. See https://en.wikipedia.org/wiki/Hostname#Syntax for more details.")
144
+ return state;
145
+ }
146
+
136
147
  if (thisHost.service == '') {
137
- window.alert("Every host needs to be assigned to a service");
148
+ window.alert("Every host needs to be assigned to a service.");
138
149
  return state;
139
150
  }
140
151
 
141
152
  if (state.hosts.filter(v => v.hostname === thisHost.hostname && v.id != thisHost.id).length > 0) {
142
- window.alert("Host name already used for this Application Instance. Please make sure that every host name is unique");
153
+ window.alert("Host name already used in this Application Instance. Please make sure that every host name is unique.");
143
154
  return state;
144
155
  }
145
156
 
@@ -287,6 +298,9 @@ const applicationInstanceConf = (state = initialState, action) => {
287
298
  }
288
299
  return state.merge(newState);
289
300
  }
301
+ case APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE: {
302
+ return state.merge({ paramEditMode: payload.mode });
303
+ }
290
304
  default:
291
305
  return state;
292
306
  }
@@ -7,3 +7,4 @@ export const selectColumns = state => applicationInstanceConf(state).columns;
7
7
  export const selectServices = state => applicationInstanceConf(state).services;
8
8
  export const selectParametersData = state => applicationInstanceConf(state).parametersData;
9
9
  export const selectAnsibleVarsAll = state => applicationInstanceConf(state).ansibleVarsAll;
10
+ export const selectParamEditMode = state => applicationInstanceConf(state).paramEditMode;
@@ -9,7 +9,7 @@ const Service= ({
9
9
  }) =>{
10
10
  return (
11
11
  <div>
12
- <label>{name}:</label> {currentCount} (Min/Max: {minCount}/{maxCount})
12
+ <label>{name}:</label> {currentCount} ({__("Min/Max")}: {minCount}/{maxCount})
13
13
  </div>
14
14
  );
15
15
  };
@@ -13,6 +13,7 @@ import {
13
13
  selectColumns,
14
14
  selectParametersData,
15
15
  selectAnsibleVarsAll,
16
+ selectParamEditMode,
16
17
  } from './ApplicationInstanceSelectors';
17
18
 
18
19
  const mapStateToProps = state => ({
@@ -23,6 +24,7 @@ const mapStateToProps = state => ({
23
24
  columns: selectColumns(state),
24
25
  parametersData: selectParametersData(state),
25
26
  ansibleVarsAll: selectAnsibleVarsAll(state),
27
+ paramEditMode: selectParamEditMode(state),
26
28
  });
27
29
 
28
30
  const mapDispatchToProps = dispatch =>