foreman_acd 0.7.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/app/controllers/foreman_acd/ansible_playbooks_controller.rb +17 -2
  4. data/app/controllers/foreman_acd/app_definitions_controller.rb +104 -7
  5. data/app/controllers/foreman_acd/app_instances_controller.rb +15 -30
  6. data/app/controllers/foreman_acd/concerns/app_instance_mixins.rb +36 -0
  7. data/app/controllers/ui_acd_controller.rb +38 -1
  8. data/app/lib/actions/foreman_acd/run_configurator.rb +1 -0
  9. data/app/models/concerns/foreman_acd/host_managed_extensions.rb +15 -27
  10. data/app/models/foreman_acd/app_instance.rb +47 -2
  11. data/app/models/foreman_acd/foreman_host.rb +8 -0
  12. data/app/services/foreman_acd/app_deployer.rb +19 -2
  13. data/app/services/foreman_acd/inventory_creator.rb +11 -1
  14. data/app/views/foreman_acd/app_definitions/import.html.erb +20 -1
  15. data/app/views/foreman_acd/app_definitions/index.html.erb +3 -6
  16. data/app/views/foreman_acd/app_instances/index.html.erb +15 -11
  17. data/app/views/foreman_acd/app_instances/report.html.erb +7 -2
  18. data/app/views/ui_acd/host_report.json.rabl +4 -0
  19. data/app/views/ui_acd/report_data.json.rabl +10 -0
  20. data/app/views/ui_acd/validate_hostname.json.rabl +6 -0
  21. data/config/routes.rb +3 -0
  22. data/db/migrate/20210818125913_add_is_existing_host_to_foreman_host.rb +8 -0
  23. data/db/migrate/20210902110645_add_initial_configure_task.rb +8 -0
  24. data/lib/foreman_acd/plugin.rb +6 -6
  25. data/lib/foreman_acd/version.rb +1 -1
  26. data/lib/foreman_acd.rb +27 -9
  27. data/webpack/__mocks__/foremanReact/components/ForemanModal/ForemanModalActions.js +2 -0
  28. data/webpack/components/ApplicationDefinition/ApplicationDefinition.js +28 -9
  29. data/webpack/components/ApplicationDefinition/ApplicationDefinitionActions.js +6 -0
  30. data/webpack/components/ApplicationDefinition/ApplicationDefinitionConstants.js +1 -0
  31. data/webpack/components/ApplicationDefinition/ApplicationDefinitionReducer.js +30 -9
  32. data/webpack/components/ApplicationDefinition/ApplicationDefinitionSelectors.js +3 -0
  33. data/webpack/components/ApplicationDefinition/__tests__/ApplicationDefinition.test.js +1 -0
  34. data/webpack/components/ApplicationDefinition/__tests__/__snapshots__/ApplicationDefinition.test.js.snap +30 -5
  35. data/webpack/components/ApplicationDefinition/components/AnsiblePlaybookSelector.js +1 -1
  36. data/webpack/components/ApplicationDefinition/components/__tests__/__snapshots__/AnsiblePlaybookSelector.test.js.snap +3 -3
  37. data/webpack/components/ApplicationDefinition/index.js +6 -0
  38. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImport.js +214 -0
  39. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImport.scss +1 -0
  40. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportActions.js +161 -0
  41. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportConstants.js +6 -0
  42. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportReducer.js +79 -0
  43. data/webpack/components/ApplicationDefinitionImport/ApplicationDefinitionImportSelectors.js +8 -0
  44. data/webpack/components/ApplicationDefinitionImport/__fixtures__/applicationDefinitionImportConfData_1.fixtures.js +129 -0
  45. data/webpack/components/ApplicationDefinitionImport/__fixtures__/applicationDefinitionImportReducer.fixtures.js +29 -0
  46. data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImport.test.js +20 -0
  47. data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImportReducer.test.js +43 -0
  48. data/webpack/components/ApplicationDefinitionImport/__tests__/ApplicationDefinitionImportSelectors.test.js +29 -0
  49. data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImport.test.js.snap +62 -0
  50. data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImportReducer.test.js.snap +362 -0
  51. data/webpack/components/ApplicationDefinitionImport/__tests__/__snapshots__/ApplicationDefinitionImportSelectors.test.js.snap +130 -0
  52. data/webpack/components/ApplicationDefinitionImport/index.js +32 -0
  53. data/webpack/components/ApplicationInstance/ApplicationInstance.js +96 -25
  54. data/webpack/components/ApplicationInstance/ApplicationInstanceActions.js +112 -6
  55. data/webpack/components/ApplicationInstance/ApplicationInstanceConstants.js +4 -0
  56. data/webpack/components/ApplicationInstance/ApplicationInstanceHelper.js +15 -0
  57. data/webpack/components/ApplicationInstance/ApplicationInstanceReducer.js +71 -30
  58. data/webpack/components/ApplicationInstance/ApplicationInstanceSelectors.js +3 -0
  59. data/webpack/components/ApplicationInstance/__fixtures__/applicationInstanceReducer.fixtures.js +2 -0
  60. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstance.test.js +1 -0
  61. data/webpack/components/ApplicationInstance/__tests__/ApplicationInstanceReducer.test.js +12 -0
  62. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstance.test.js.snap +97 -7
  63. data/webpack/components/ApplicationInstance/__tests__/__snapshots__/ApplicationInstanceReducer.test.js.snap +271 -0
  64. data/webpack/components/ApplicationInstance/components/AppDefinitionSelector.js +1 -0
  65. data/webpack/components/ApplicationInstance/components/ServiceCounter.js +1 -1
  66. data/webpack/components/ApplicationInstance/helper.js +0 -0
  67. data/webpack/components/ApplicationInstance/index.js +6 -0
  68. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReport.js +81 -6
  69. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportActions.js +35 -1
  70. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportConstants.js +3 -0
  71. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportReducer.js +19 -0
  72. data/webpack/components/ApplicationInstanceReport/ApplicationInstanceReportSelectors.js +4 -0
  73. data/webpack/components/ApplicationInstanceReport/__tests__/__snapshots__/ApplicationInstanceReport.test.js.snap +1 -124
  74. data/webpack/components/ApplicationInstanceReport/index.js +8 -1
  75. data/webpack/components/ExistingHostSelection/ExistingHostSelection.js +104 -0
  76. data/webpack/components/ExistingHostSelection/ExistingHostSelection.scss +15 -0
  77. data/webpack/components/ExistingHostSelection/ExistingHostSelectionActions.js +71 -0
  78. data/webpack/components/ExistingHostSelection/ExistingHostSelectionConstants.js +4 -0
  79. data/webpack/components/ExistingHostSelection/ExistingHostSelectionHelper.js +0 -0
  80. data/webpack/components/ExistingHostSelection/ExistingHostSelectionReducer.js +90 -0
  81. data/webpack/components/ExistingHostSelection/ExistingHostSelectionSelectors.js +8 -0
  82. data/webpack/components/ExistingHostSelection/__fixtures__/existingHostSelectionConfData_1.fixtures.js +191 -0
  83. data/webpack/components/ExistingHostSelection/__fixtures__/existingHostSelectionReducer.fixtures.js +203 -0
  84. data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelection.test.js +19 -0
  85. data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelectionReducer.test.js +59 -0
  86. data/webpack/components/ExistingHostSelection/__tests__/ExistingHostSelectionSelectors.test.js +36 -0
  87. data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelection.test.js.snap +35 -0
  88. data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelectionReducer.test.js.snap +614 -0
  89. data/webpack/components/ExistingHostSelection/__tests__/__snapshots__/ExistingHostSelectionSelectors.test.js.snap +27 -0
  90. data/webpack/components/ExistingHostSelection/components/ServiceSelector.js +48 -0
  91. data/webpack/components/ExistingHostSelection/components/__tests__/ServiceSelector.test.js +35 -0
  92. data/webpack/components/ExistingHostSelection/components/__tests__/__snapshots__/ServiceSelector.test.js.snap +77 -0
  93. data/webpack/components/ExistingHostSelection/index.js +26 -0
  94. data/webpack/components/ParameterSelection/ParameterSelection.js +98 -1
  95. data/webpack/components/ParameterSelection/ParameterSelection.scss +7 -0
  96. data/webpack/components/ParameterSelection/ParameterSelectionActions.js +36 -2
  97. data/webpack/components/ParameterSelection/ParameterSelectionConstants.js +2 -0
  98. data/webpack/components/ParameterSelection/ParameterSelectionReducer.js +49 -8
  99. data/webpack/components/ParameterSelection/ParameterSelectionSelectors.js +1 -0
  100. data/webpack/components/ParameterSelection/__tests__/ParameterSelectionReducer.test.js +2 -0
  101. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelection.test.js.snap +96 -0
  102. data/webpack/components/ParameterSelection/__tests__/__snapshots__/ParameterSelectionReducer.test.js.snap +5 -0
  103. data/webpack/components/ParameterSelection/index.js +2 -1
  104. data/webpack/components/SyncGitRepo/SyncGitRepo.js +2 -10
  105. data/webpack/components/SyncGitRepo/SyncGitRepoActions.js +1 -2
  106. data/webpack/components/SyncGitRepo/SyncGitRepoConstants.js +0 -1
  107. data/webpack/components/SyncGitRepo/__tests__/__snapshots__/SyncGitRepo.test.js.snap +1 -0
  108. data/webpack/components/SyncGitRepo/components/FormTextInput.js +1 -1
  109. data/webpack/components/SyncGitRepo/components/ScmTypeSelector.js +3 -2
  110. data/webpack/components/common/DeleteTableEntry.js +16 -2
  111. data/webpack/components/common/__tests__/__snapshots__/DeleteTableEntry.test.js.snap +38 -0
  112. data/webpack/helper.js +5 -0
  113. data/webpack/index.js +5 -0
  114. data/webpack/js-yaml.js +3874 -0
  115. data/webpack/reducer.js +13 -2
  116. metadata +46 -2
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import {
4
4
  Icon,
5
5
  Button,
6
+ MessageDialog,
6
7
  } from 'patternfly-react';
7
8
  import * as resolve from 'table-resolver';
8
9
  import ForemanModal from 'foremanReact/components/ForemanModal';
@@ -12,6 +13,7 @@ import {
12
13
  } from 'foremanReact/common/I18n';
13
14
  import Select from 'foremanReact/components/common/forms/Select';
14
15
  import ParameterSelection from '../ParameterSelection';
16
+ import ExistingHostSelection from '../ExistingHostSelection';
15
17
  import AddTableEntry from '../common/AddTableEntry';
16
18
  import DeleteTableEntry from '../common/DeleteTableEntry';
17
19
  import RailsData from '../common/RailsData';
@@ -41,7 +43,7 @@ class ApplicationInstance extends React.Component {
41
43
  return (rowData.backup !== undefined);
42
44
  }
43
45
 
44
- addTableEntryAllowed() {
46
+ changeDataAllowed() {
45
47
  return this.props.editMode || this.props.appDefinition.id == ''
46
48
  }
47
49
 
@@ -105,29 +107,36 @@ class ApplicationInstance extends React.Component {
105
107
  loadApplicationDefinition(appDefinition.id, { url: appDefinitionUrl });
106
108
  }
107
109
 
110
+ const already_deployed_msg = __("This is an already deployed host. Changing the parameters is not possible!");
111
+
108
112
  const inlineEditButtonsFormatter = inlineEditFormatterFactory({
109
113
  isEditing: additionalData => this.props.editMode,
110
114
  renderValue: (value, additionalData) => (
111
115
  <td style={{ padding: '2px' }}>
116
+ { additionalData.rowData.isExistingHost == true ? (
117
+ <Icon style={{marginRight: 8, marginLeft: 2}} type="pf" name="info" title={already_deployed_msg} />
118
+ ) : (<span></span>)}
112
119
  <Button
113
120
  bsStyle="default"
114
121
  onClick={() => activateEditApplicationInstanceHost(additionalData)}
115
122
  >
116
- <Icon type="pf" name="edit" title={__("edit entry")} />
123
+ <Icon type="pf" name="edit" title={__("Edit entry")} />
117
124
  </Button>
118
125
  &nbsp;
119
- <Button
120
- bsStyle="default"
121
- onClick={() => openForemanParameterSelectionModal(additionalData)}
122
- >
123
- <Icon type="pf" name="settings" title={__("change parameters")} />
124
- </Button>
126
+ { additionalData.rowData.isExistingHost == false ? (
127
+ <Button
128
+ bsStyle="default"
129
+ onClick={() => openForemanParameterSelectionModal(additionalData)}
130
+ >
131
+ <Icon type="pf" name="settings" title={__("Change parameters")} />
132
+ </Button>
133
+ ) : (<span></span>)}
125
134
  &nbsp;
126
135
  <Button
127
136
  bsStyle="default"
128
137
  onClick={() => openAnsibleParameterSelectionModal(additionalData)}
129
138
  >
130
- <span title={__("change ansible variables")}>A</span>
139
+ <span title={__("Change ansible variables")}>A</span>
131
140
  </Button>
132
141
  &nbsp;
133
142
  <DeleteTableEntry
@@ -140,13 +149,18 @@ class ApplicationInstance extends React.Component {
140
149
  ),
141
150
  renderEdit: (value, additionalData) => (
142
151
  <td style={{ padding: '2px' }}>
152
+ { additionalData.rowData.isExistingHost == true ? (
153
+ <Icon style={{marginRight: 8, marginLeft: 2}} type="pf" name="info" title={already_deployed_msg} />
154
+ ) : (<span></span>)}
143
155
  <Button bsStyle="default" disabled>
144
156
  <Icon type="pf" name={__("edit")} />
145
157
  </Button>
146
158
  &nbsp;
147
- <Button bsStyle="default" disabled>
148
- <Icon type="pf" name={__("settings")} />
149
- </Button>
159
+ { additionalData.rowData.isExistingHost == false ? (
160
+ <Button bsStyle="default" disabled>
161
+ <Icon type="pf" name={__("settings")} />
162
+ </Button>
163
+ ) : (<span></span>)}
150
164
  &nbsp;
151
165
  <Button bsStyle="default" disabled>
152
166
  <span>A</span>
@@ -239,6 +253,7 @@ class ApplicationInstance extends React.Component {
239
253
  render() {
240
254
  const {
241
255
  data: { mode, applications, organization, location, foremanDataUrl, appDefinitionUrl },
256
+ showAlertModal, alertModalText, alertModalTitle, closeAlertModal,
242
257
  appDefinition,
243
258
  services,
244
259
  hosts,
@@ -249,6 +264,8 @@ class ApplicationInstance extends React.Component {
249
264
  closeForemanParameterSelectionModal,
250
265
  openAnsibleParameterSelectionModal,
251
266
  closeAnsibleParameterSelectionModal,
267
+ openAddExistingHostsModal,
268
+ closeAddExistingHostsModal,
252
269
  changeParameterSelectionMode,
253
270
  loadApplicationDefinition,
254
271
  } = this.props;
@@ -263,7 +280,17 @@ class ApplicationInstance extends React.Component {
263
280
 
264
281
  return (
265
282
  <span>
266
- <div class="service-counter">
283
+ <MessageDialog
284
+ show={showAlertModal}
285
+ onHide={closeAlertModal}
286
+ primaryAction={closeAlertModal}
287
+ primaryActionButtonContent={__('OK')}
288
+ primaryActionButtonBsStyle={"danger"}
289
+ icon={<Icon type="pf" name="error-circle-o" />}
290
+ title={alertModalTitle}
291
+ primaryContent={alertModalText}
292
+ />
293
+ <div className="service-counter">
267
294
  <ServiceCounter
268
295
  title="Service counts"
269
296
  serviceList={ services }
@@ -273,6 +300,7 @@ class ApplicationInstance extends React.Component {
273
300
  <div>
274
301
  <AppDefinitionSelector
275
302
  label="Application Definition"
303
+ hidden={ false }
276
304
  editable={ mode == 'newInstance' }
277
305
  viewText={ appDefinition.name }
278
306
  options={ applications }
@@ -281,9 +309,9 @@ class ApplicationInstance extends React.Component {
281
309
  additionalData={{url: appDefinitionUrl}}
282
310
  />
283
311
  {appDefinition.id == '' ? (
284
- <p style={{ paddingTop: 25 }}>
312
+ <div style={{ paddingTop: 25 }}>
285
313
  <pre>{ "App Definition can't be blank" }</pre>
286
- </p>
314
+ </div>
287
315
  ) : (<div></div>)}
288
316
  </div>
289
317
  <div className="form-group">
@@ -309,27 +337,38 @@ class ApplicationInstance extends React.Component {
309
337
  role: 'row',
310
338
  isEditing: () => this.isEditing({ rowData }),
311
339
  onCancel: () => cancelEditApplicationInstanceHost({ rowData, rowIndex }),
312
- onConfirm: () => confirmEditApplicationInstanceHost({ rowData, rowIndex }),
340
+ onConfirm: () => confirmEditApplicationInstanceHost({ rowData, rowIndex, appDefinition }),
313
341
  last: rowIndex === services.length - 1
314
342
  })}
315
343
  />
316
344
  </Table.PfProvider>
317
345
  <AddTableEntry
318
346
  hidden={ false }
319
- disabled={ this.addTableEntryAllowed() }
347
+ disabled={ this.changeDataAllowed() }
320
348
  onAddTableEntry={ addApplicationInstanceHost }
321
349
  />
350
+ <span style={{ marginLeft: 10 }}>
351
+ <Button
352
+ bsStyle="default"
353
+ disabled={ this.changeDataAllowed() }
354
+ onClick={() => openAddExistingHostsModal({
355
+ isAllGroup: true
356
+ })}
357
+ >
358
+ <Icon title={__("Add existing hosts")} type="pf" name="server" />
359
+ </Button>
360
+ </span>
322
361
  <span style={{ marginLeft: 30 }}>
323
362
  Ansible group vars 'all':
324
363
  <Button
325
364
  style={{ marginLeft: 10 }}
326
365
  bsStyle="default"
327
- disabled={ this.props.editMode }
366
+ disabled={ this.changeDataAllowed() }
328
367
  onClick={() => openAnsibleParameterSelectionModal({
329
368
  isAllGroup: true
330
369
  })}
331
370
  >
332
- <span title={__("change ansible variables for 'all'")}>A</span>
371
+ <span title={__("Change ansible variables for 'all'")}>A</span>
333
372
  </Button>
334
373
  </span>
335
374
  </div>
@@ -340,7 +379,7 @@ class ApplicationInstance extends React.Component {
340
379
  title={__("Foreman Parameter specification for Application Instance")}
341
380
  >
342
381
  <ForemanModal.Header closeButton={false}>
343
- Parameter specification
382
+ {__("Parameter specification")}
344
383
  </ForemanModal.Header>
345
384
  {this.props.parametersData ? (
346
385
  <ParameterSelection
@@ -368,7 +407,7 @@ class ApplicationInstance extends React.Component {
368
407
  title={__("Ansible group variables for Application Instance")}
369
408
  >
370
409
  <ForemanModal.Header closeButton={false}>
371
- Parameter specification
410
+ {__("Parameter specification")}
372
411
  </ForemanModal.Header>
373
412
  {this.props.parametersData ? (
374
413
  <ParameterSelection
@@ -388,19 +427,42 @@ class ApplicationInstance extends React.Component {
388
427
  </ForemanModal.Footer>
389
428
  </ForemanModal>
390
429
  </div>
430
+ <div>
431
+ <ForemanModal
432
+ id="AppInstanceAddExistingHosts"
433
+ dialogClassName="add_existing_hosts_modal"
434
+ title={__("Add existing hosts to an Application Instance")}
435
+ >
436
+ <ForemanModal.Header closeButton={false}>
437
+ {__("Existing hosts selection")}
438
+ </ForemanModal.Header>
439
+ <ExistingHostSelection
440
+ location={ location }
441
+ organization={ organization }
442
+ services={ services }
443
+ allHosts={ this.props.hosts }
444
+ />
445
+ <ForemanModal.Footer>
446
+ <div>
447
+ <Button bsStyle="primary" disabled={this.props.paramEditMode} onClick={() => closeAddExistingHostsModal({ mode: 'save' })}>{__("Save")}</Button>
448
+ <Button bsStyle="default" disabled={this.props.paramEditMode} onClick={() => closeAddExistingHostsModal({ mode: 'cancel' })}>{__("Cancel")}</Button>
449
+ </div>
450
+ </ForemanModal.Footer>
451
+ </ForemanModal>
452
+ </div>
391
453
  {validateResult == false ? (
392
- <p style={{ paddingTop: 25 }}>
454
+ <div style={{ paddingTop: 25 }}>
393
455
  <pre>{ validateMsg }</pre>
394
- </p>
456
+ </div>
395
457
  ) : (<div></div>)}
396
458
  <RailsData
397
- key='applications_instance'
459
+ key='application_instance_hosts_data'
398
460
  view='app_instance'
399
461
  parameter='hosts'
400
462
  value={JSON.stringify(this.props.hosts)}
401
463
  />
402
464
  <RailsData
403
- key='applications_instance'
465
+ key='application_instance_ansible_data'
404
466
  view='app_instance'
405
467
  parameter='ansible_vars_all'
406
468
  value={JSON.stringify(this.props.ansibleVarsAll)}
@@ -411,6 +473,9 @@ class ApplicationInstance extends React.Component {
411
473
 
412
474
  ApplicationInstance.defaultProps = {
413
475
  error: {},
476
+ showAlertModal: false,
477
+ alertModalText: '',
478
+ alertModalTitle: '',
414
479
  appDefinition: { "id": '', "name": '' },
415
480
  editMode: false,
416
481
  services: [],
@@ -424,12 +489,16 @@ ApplicationInstance.defaultProps = {
424
489
 
425
490
  ApplicationInstance.propTypes = {
426
491
  initApplicationInstance: PropTypes.func,
492
+ showAlertModal: PropTypes.bool,
493
+ alertModalText: PropTypes.string,
494
+ alertModalTitle: PropTypes.string,
427
495
  editMode: PropTypes.bool.isRequired,
428
496
  services: PropTypes.array,
429
497
  appDefinition: PropTypes.object,
430
498
  columns: PropTypes.array,
431
499
  hosts: PropTypes.array,
432
500
  ansibleVarsAll: PropTypes.array,
501
+ closeAlertModal: PropTypes.func,
433
502
  loadApplicationDefinition: PropTypes.func,
434
503
  addApplicationInstanceHost: PropTypes.func,
435
504
  deleteApplicationInstanceHost: PropTypes.func,
@@ -441,6 +510,8 @@ ApplicationInstance.propTypes = {
441
510
  closeForemanParameterSelectionModal: PropTypes.func,
442
511
  openAnsibleParameterSelectionModal: PropTypes.func,
443
512
  closeAnsibleParameterSelectionModal: PropTypes.func,
513
+ openAddExistingHostsModal: PropTypes.func,
514
+ closeAddExistingHostsModal: PropTypes.func,
444
515
  changeParameterSelectionMode: PropTypes.func,
445
516
  parametersData: PropTypes.object,
446
517
  paramEditMode: PropTypes.bool,
@@ -16,16 +16,20 @@ import {
16
16
 
17
17
  import {
18
18
  APPLICATION_INSTANCE_INIT,
19
+ APPLICATION_INSTANCE_CLOSE_ALERT_MODAL,
19
20
  APPLICATION_INSTANCE_HOST_DELETE,
20
21
  APPLICATION_INSTANCE_HOST_ADD,
21
22
  APPLICATION_INSTANCE_HOST_EDIT_ACTIVATE,
22
23
  APPLICATION_INSTANCE_HOST_EDIT_CONFIRM,
23
24
  APPLICATION_INSTANCE_HOST_EDIT_CHANGE,
24
25
  APPLICATION_INSTANCE_HOST_EDIT_CANCEL,
26
+ APPLICATION_INSTANCE_HOST_EDIT_ERROR,
25
27
  APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN,
26
28
  APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE,
27
29
  APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN,
28
30
  APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE,
31
+ APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_OPEN,
32
+ APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_CLOSE,
29
33
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_REQUEST,
30
34
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_SUCCESS,
31
35
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_FAILURE,
@@ -127,6 +131,11 @@ const errorHandler = (msg, err) => {
127
131
  return { type: msg, payload: { error } };
128
132
  };
129
133
 
134
+ export const closeAlertModal = () => ({
135
+ type: APPLICATION_INSTANCE_CLOSE_ALERT_MODAL,
136
+ payload: {}
137
+ });
138
+
130
139
  export const loadApplicationDefinition = (
131
140
  applicationDefinitionId,
132
141
  additionalData,
@@ -167,12 +176,84 @@ export const activateEditApplicationInstanceHost = (additionalData) => ({
167
176
  },
168
177
  });
169
178
 
170
- export const confirmEditApplicationInstanceHost = (rowData) => ({
171
- type: APPLICATION_INSTANCE_HOST_EDIT_CONFIRM,
172
- payload: {
173
- ...rowData,
174
- },
175
- });
179
+ export const confirmEditApplicationInstanceHost = (
180
+ allData
181
+ ) => async(dispatch) => {
182
+
183
+ // Host name can not be empty
184
+
185
+ if (allData.rowData.hostname == '') {
186
+ dispatch({
187
+ type: APPLICATION_INSTANCE_HOST_EDIT_ERROR,
188
+ payload: __("Every host needs to have a valid name"),
189
+ });
190
+ return;
191
+ }
192
+
193
+ // Host name can only have specific characters
194
+
195
+ const hostname = allData.rowData.hostname.toLowerCase();
196
+ const hostnameRegex = /^[0-9a-z]([0-9a-z\-]{0,61}[0-9a-z])$/;
197
+
198
+ if (hostname.match(hostnameRegex) == undefined) {
199
+ dispatch({
200
+ type: APPLICATION_INSTANCE_HOST_EDIT_ERROR,
201
+ payload: __("The hostname uses not allowed characters. See https://en.wikipedia.org/wiki/Hostname#Syntax for more details."),
202
+ });
203
+ return;
204
+ }
205
+
206
+ // Service can not be empty
207
+
208
+ if (allData.rowData.service == '') {
209
+ dispatch({
210
+ type: APPLICATION_INSTANCE_HOST_EDIT_ERROR,
211
+ payload: __("Every host needs to be assigned to a service."),
212
+ });
213
+ return;
214
+ }
215
+
216
+ // Validation if host name is already used (only for new host entrys)
217
+
218
+ const url = '/acd/ui_acd_validate_hostname'
219
+ const validationData = {};
220
+
221
+ validationData['appDefId'] = allData.appDefinition.id;
222
+ validationData['serviceId'] = allData.rowData.service;
223
+ validationData['hostname'] = allData.rowData.hostname;
224
+
225
+ if (allData.rowData.newEntry === true) {
226
+ try {
227
+ const response = await api.get(url, {}, validationData);
228
+
229
+ if (response.data.result === true) {
230
+ dispatch({
231
+ type: APPLICATION_INSTANCE_HOST_EDIT_CONFIRM,
232
+ payload: {
233
+ ...allData,
234
+ }
235
+ });
236
+ } else {
237
+ dispatch({
238
+ type: APPLICATION_INSTANCE_HOST_EDIT_ERROR,
239
+ payload: __('Hostname \''+ allData.rowData.hostname +'\' is already used. This check also includes hosts outside this application instance.'),
240
+ });
241
+ }
242
+ } catch (error) {
243
+ dispatch({
244
+ type: APPLICATION_INSTANCE_HOST_EDIT_ERROR,
245
+ payload: __('Error during validation if hostname is already used.'),
246
+ });
247
+ }
248
+ } else {
249
+ dispatch({
250
+ type: APPLICATION_INSTANCE_HOST_EDIT_CONFIRM,
251
+ payload: {
252
+ ...allData,
253
+ }
254
+ });
255
+ }
256
+ };
176
257
 
177
258
  export const cancelEditApplicationInstanceHost = (rowData) => ({
178
259
  type: APPLICATION_INSTANCE_HOST_EDIT_CANCEL,
@@ -239,6 +320,31 @@ export const closeAnsibleParameterSelectionModal = (additionalData) => dispatch
239
320
  );
240
321
  }
241
322
 
323
+ export const openAddExistingHostsModal = (additionalData) => dispatch => {
324
+ dispatch({
325
+ type: APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_OPEN,
326
+ payload: {
327
+ ...additionalData,
328
+ }
329
+ });
330
+ dispatch(
331
+ setModalOpen({ id: 'AppInstanceAddExistingHosts' })
332
+ );
333
+ }
334
+
335
+ export const closeAddExistingHostsModal = (additionalData) => dispatch => {
336
+ dispatch({
337
+ type: APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_CLOSE,
338
+ payload: {
339
+ ...additionalData,
340
+ }
341
+ });
342
+
343
+ dispatch(
344
+ setModalClosed({ id: 'AppInstanceAddExistingHosts' })
345
+ );
346
+ }
347
+
242
348
  export const changeParameterSelectionMode = (additionalData) => ({
243
349
  type: APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE,
244
350
  payload: {
@@ -1,4 +1,5 @@
1
1
  export const APPLICATION_INSTANCE_INIT = 'APPLICATION_INSTANCE_INIT';
2
+ export const APPLICATION_INSTANCE_CLOSE_ALERT_MODAL = 'APPLICATION_INSTANCE_CLOSE_ALERT_MODAL';
2
3
  export const APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_REQUEST = 'APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_REQUEST';
3
4
  export const APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_SUCCESS = 'APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_SUCCESS';
4
5
  export const APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_FAILURE = 'APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_FAILURE';
@@ -8,8 +9,11 @@ export const APPLICATION_INSTANCE_HOST_EDIT_ACTIVATE = 'APPLICATION_INSTANCE_HOS
8
9
  export const APPLICATION_INSTANCE_HOST_EDIT_CONFIRM = 'APPLICATION_INSTANCE_HOST_EDIT_CONFIRM';
9
10
  export const APPLICATION_INSTANCE_HOST_EDIT_CHANGE = 'APPLICATION_INSTANCE_HOST_EDIT_CHANGE';
10
11
  export const APPLICATION_INSTANCE_HOST_EDIT_CANCEL = 'APPLICATION_INSTANCE_HOST_EDIT_CANCEL';
12
+ export const APPLICATION_INSTANCE_HOST_EDIT_ERROR = 'APPLICATION_INSTANCE_HOST_EDIT_ERROR';
11
13
  export const APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN';
12
14
  export const APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE';
13
15
  export const APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN = 'APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN';
14
16
  export const APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE = 'APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE';
17
+ export const APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_OPEN = 'APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_OPEN';
18
+ export const APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_CLOSE = 'APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_CLOSE';
15
19
  export const APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE = 'APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE';
@@ -0,0 +1,15 @@
1
+
2
+ function calculateServiceUsage(hostServiceId, services) {
3
+ const service = services.find(serv => serv['id'] == hostServiceId);
4
+ if ('currentCount' in service) {
5
+ service['currentCount'] += 1;
6
+ } else {
7
+ service['currentCount'] = 1;
8
+ }
9
+
10
+ return services;
11
+ }
12
+
13
+ export {
14
+ calculateServiceUsage,
15
+ };
@@ -1,4 +1,6 @@
1
1
  import Immutable from 'seamless-immutable';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+ import { calculateServiceUsage } from './ApplicationInstanceHelper';
2
4
 
3
5
  import {
4
6
  cloneDeep,
@@ -8,6 +10,7 @@ import {
8
10
 
9
11
  import {
10
12
  APPLICATION_INSTANCE_INIT,
13
+ APPLICATION_INSTANCE_CLOSE_ALERT_MODAL,
11
14
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_FAILURE,
12
15
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_REQUEST,
13
16
  APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_SUCCESS,
@@ -17,10 +20,13 @@ import {
17
20
  APPLICATION_INSTANCE_HOST_EDIT_CONFIRM,
18
21
  APPLICATION_INSTANCE_HOST_EDIT_CHANGE,
19
22
  APPLICATION_INSTANCE_HOST_EDIT_CANCEL,
23
+ APPLICATION_INSTANCE_HOST_EDIT_ERROR,
20
24
  APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN,
21
25
  APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_CLOSE,
22
26
  APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_OPEN,
23
27
  APPLICATION_INSTANCE_ANSIBLE_PARAMETER_SELECTION_MODAL_CLOSE,
28
+ APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_OPEN,
29
+ APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_CLOSE,
24
30
  APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE,
25
31
  } from './ApplicationInstanceConstants';
26
32
 
@@ -41,7 +47,15 @@ const applicationInstanceConf = (state = initialState, action) => {
41
47
  case APPLICATION_INSTANCE_INIT: {
42
48
  return state.merge(payload);
43
49
  }
50
+ case APPLICATION_INSTANCE_CLOSE_ALERT_MODAL: {
51
+ return state.merge({
52
+ showAlertModal: false,
53
+ alertModalTitle: '',
54
+ alertModalText: '',
55
+ });
56
+ }
44
57
  case APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_FAILURE: {
58
+ console.log("Error while loading application definition data: "+ payload.error);
45
59
  return state.merge({ error: payload.error, loading: false });
46
60
  }
47
61
  case APPLICATION_INSTANCE_LOAD_APPLICATION_DEFINITION_REQUEST: {
@@ -87,7 +101,7 @@ const applicationInstanceConf = (state = initialState, action) => {
87
101
  index = Math.max(...hosts.map(e => e.id)) + 1;
88
102
  }
89
103
 
90
- const newRow = {id: index, hostname: "", description: '', service: '', foremanParameters: [], ansibleParameters: [], newEntry: true };
104
+ const newRow = {id: index, hostname: "", description: '', service: '', isExistingHost: false, foremanParameters: [], ansibleParameters: [], newEntry: true };
91
105
  newRow.backup = cloneDeep(newRow)
92
106
  hosts.push(newRow);
93
107
 
@@ -125,48 +139,28 @@ const applicationInstanceConf = (state = initialState, action) => {
125
139
  case APPLICATION_INSTANCE_HOST_EDIT_CONFIRM: {
126
140
  const hosts = cloneDeep(state.hosts);
127
141
  const index = findIndex(hosts, { id: payload.rowData.id });
128
- const services = cloneDeep(state.services);
142
+ let services = cloneDeep(state.services);
129
143
 
130
144
  const thisHost = hosts[index];
131
145
 
132
- if (thisHost.hostname == '') {
133
- window.alert("Every host needs to have a valid name");
134
- return state;
135
- }
136
-
137
146
  // hostnames are lower case
138
147
  thisHost.hostname = thisHost.hostname.toLowerCase();
139
148
 
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
-
147
- if (thisHost.service == '') {
148
- window.alert("Every host needs to be assigned to a service.");
149
- return state;
150
- }
151
-
152
149
  if (state.hosts.filter(v => v.hostname === thisHost.hostname && v.id != thisHost.id).length > 0) {
153
- window.alert("Host name already used in this Application Instance. Please make sure that every host name is unique.");
154
- return state;
150
+ return state.merge({
151
+ showAlertModal: true,
152
+ alertModalTitle: __("Error"),
153
+ alertModalText: __("Host name already used in this Application Instance. Please make sure that every host name is unique."),
154
+ });
155
155
  }
156
156
 
157
157
  // Initialize the new Instance with the parameters of the Application Definition.
158
158
  if (thisHost.newEntry === true) {
159
- const selectedService = state.services.filter(entry => entry.id == payload.rowData.service)[0];
159
+ const hostServiceId = Number(thisHost.service);
160
+ const selectedService = services.filter(entry => entry.id == hostServiceId)[0];
160
161
  hosts[index].foremanParameters = selectedService.foremanParameters;
161
162
  hosts[index].ansibleParameters = selectedService.ansibleParameters;
162
-
163
- const hostServiceId = Number(thisHost.service);
164
- const service = services.find(serv => serv['id'] == hostServiceId);
165
- if ('currentCount' in service) {
166
- service['currentCount'] += 1;
167
- } else {
168
- service['currentCount'] = 1;
169
- }
163
+ services = calculateServiceUsage(hostServiceId, services);
170
164
  }
171
165
 
172
166
  delete hosts[index].backup;
@@ -202,6 +196,13 @@ const applicationInstanceConf = (state = initialState, action) => {
202
196
  hosts: hosts
203
197
  });
204
198
  }
199
+ case APPLICATION_INSTANCE_HOST_EDIT_ERROR: {
200
+ return state.merge({
201
+ showAlertModal: true,
202
+ alertModalTitle: __("Error"),
203
+ alertModalText: payload,
204
+ });
205
+ }
205
206
  case APPLICATION_INSTANCE_FOREMAN_PARAMETER_SELECTION_MODAL_OPEN: {
206
207
  let parametersData = {};
207
208
 
@@ -298,6 +299,46 @@ const applicationInstanceConf = (state = initialState, action) => {
298
299
  }
299
300
  return state.merge(newState);
300
301
  }
302
+ case APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_OPEN: {
303
+ }
304
+ case APPLICATION_INSTANCE_ADD_EXISTING_HOSTS_MODAL_CLOSE: {
305
+ if (payload.mode == 'save') {
306
+ let newState;
307
+ let hosts = [];
308
+ let index = 1;
309
+ let services = cloneDeep(state.services);
310
+
311
+ if ('hosts' in state && state.hosts !== undefined && state.hosts.length > 0) {
312
+ hosts = cloneDeep(state.hosts);
313
+ index = Math.max(...hosts.map(e => e.id));
314
+ }
315
+
316
+ payload.selectedHosts.forEach(host => {
317
+ if ((state.hosts == undefined) || (state.hosts.find(h => h.hostname == host.hostname) == undefined)) {
318
+ index += 1;
319
+ const selectedService = services.filter(entry => entry.id == host.serviceId)[0];
320
+ const newRow = {
321
+ id: index,
322
+ hostname: host.hostname,
323
+ description: '',
324
+ service: host.serviceId,
325
+ isExistingHost: true,
326
+ foremanParameters: [], // we will never set this because we don't want to change a already existing host.
327
+ ansibleParameters: selectedService.ansibleParameters,
328
+ }
329
+ hosts.push(newRow);
330
+ services = calculateServiceUsage(host.serviceId, services);
331
+ }
332
+ });
333
+
334
+ return state.merge({
335
+ hosts: hosts,
336
+ services, services
337
+ });
338
+ } else {
339
+ return state;
340
+ }
341
+ }
301
342
  case APPLICATION_INSTANCE_CHANGE_PARAMETER_SELECTION_MODE: {
302
343
  return state.merge({ paramEditMode: payload.mode });
303
344
  }
@@ -1,5 +1,8 @@
1
1
  const applicationInstanceConf = state => state.foremanAcd.applicationInstanceConf;
2
2
 
3
+ export const selectShowAlertModal = state => applicationInstanceConf(state).showAlertModal;
4
+ export const selectAlertModalText = state => applicationInstanceConf(state).alertModalText;
5
+ export const selectAlertModalTitle = state => applicationInstanceConf(state).alertModalTitle;
3
6
  export const selectEditMode = state => applicationInstanceConf(state).editMode;
4
7
  export const selectAppDefinition = state => applicationInstanceConf(state).appDefinition;
5
8
  export const selectHosts = state => applicationInstanceConf(state).hosts;
@@ -20,6 +20,8 @@ export const editState = Immutable(editClone);
20
20
 
21
21
  // Payload Data
22
22
  export const initApplicationInstancePayload = applicationInstanceConfData_1;
23
+
24
+ export const closeAlertModalPayload = { };
23
25
  export const addHostPayload = { };
24
26
  export const deleteHostPayload = {
25
27
  rowData: {
@@ -13,6 +13,7 @@ const fixtures = {
13
13
  hosts: [],
14
14
  ansibleVarsAll: [],
15
15
  },
16
+ closeAlertModal: noop,
16
17
  loadApplicationDefinition: noop,
17
18
  initApplicationInstance: noop,
18
19
  addApplicationInstanceHost: noop,