foreman_patch 1.1.5 → 1.1.6.alpha5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_patch/concerns/hosts_controller_extensions.rb +35 -41
  3. data/app/controllers/foreman_patch/react_controller.rb +12 -0
  4. data/app/helpers/foreman_patch/hosts_helper.rb +1 -1
  5. data/app/lib/actions/foreman_patch/cycle/create.rb +5 -1
  6. data/app/lib/actions/foreman_patch/cycle/initiate.rb +5 -1
  7. data/app/lib/actions/foreman_patch/invocation/action.rb +23 -29
  8. data/app/lib/actions/foreman_patch/invocation/patch.rb +16 -7
  9. data/app/lib/actions/foreman_patch/invocation/process_logging.rb +44 -0
  10. data/app/lib/actions/foreman_patch/invocation/proxy_action.rb +52 -0
  11. data/app/lib/actions/foreman_patch/invocation/wait_for_host.rb +4 -29
  12. data/app/lib/actions/foreman_patch/round/patch.rb +5 -1
  13. data/app/lib/actions/foreman_patch/window/publish.rb +5 -1
  14. data/app/lib/actions/foreman_patch/window/resolve_hosts.rb +5 -1
  15. data/app/models/foreman_patch/event.rb +13 -0
  16. data/app/models/foreman_patch/invocation.rb +2 -4
  17. data/app/views/foreman_patch/api/v2/invocations/base.json.rabl +1 -1
  18. data/app/views/foreman_patch/api/v2/invocations/event.json.rabl +3 -0
  19. data/app/views/foreman_patch/api/v2/invocations/show.json.rabl +2 -2
  20. data/app/views/foreman_patch/groups/index.html.erb +1 -1
  21. data/app/views/foreman_patch/invocations/show.html.erb +1 -26
  22. data/app/views/foreman_patch/layouts/react.html.erb +1 -1
  23. data/config/api_routes.rb +26 -28
  24. data/config/routes.rb +40 -29
  25. data/db/migrate/20230706092400_nullify_group_on_delete.rb +11 -0
  26. data/db/migrate/20230707102800_create_invocation_events.rb +16 -0
  27. data/db/seeds.d/100-assign_features_with_templates.rb +6 -12
  28. data/lib/foreman_patch/engine.rb +11 -46
  29. data/lib/foreman_patch/register.rb +119 -0
  30. data/lib/foreman_patch/version.rb +1 -1
  31. data/locale/en/foreman_patch.po +1 -1
  32. data/locale/foreman_patch.pot +1 -1
  33. data/locale/gemspec.rb +1 -1
  34. data/package.json +4 -0
  35. data/public/assets/foreman_patch/cycle_plans-ff3d252119622a68828ff70f4a97328303963002237dbf850e92d6a706e93667.scss +6 -0
  36. data/public/assets/foreman_patch/cycle_plans-ff3d252119622a68828ff70f4a97328303963002237dbf850e92d6a706e93667.scss.gz +0 -0
  37. data/public/assets/foreman_patch/foreman_patch-be2e2ba89548f4a490612e8a6cd1cdebc0473be89f8023a3df7612f05a75d301.css +1 -0
  38. data/public/assets/foreman_patch/foreman_patch-be2e2ba89548f4a490612e8a6cd1cdebc0473be89f8023a3df7612f05a75d301.css.gz +0 -0
  39. data/public/assets/foreman_patch/foreman_patch.json +1 -0
  40. data/public/assets/foreman_patch/plan_edit_windows-9ba20f84f3ecf2c4eb903acd57d30ee3e16f023a79db30bc614aa22f26442ce3.js +1 -0
  41. data/public/assets/foreman_patch/plan_edit_windows-9ba20f84f3ecf2c4eb903acd57d30ee3e16f023a79db30bc614aa22f26442ce3.js.gz +0 -0
  42. data/public/webpack/foreman_patch/bundle-831173d6ae39953b2409.css +1 -0
  43. data/public/webpack/foreman_patch/bundle-831173d6ae39953b2409.css.gz +0 -0
  44. data/public/webpack/foreman_patch/bundle-831173d6ae39953b2409.js +6 -0
  45. data/public/webpack/foreman_patch/bundle-831173d6ae39953b2409.js.gz +0 -0
  46. data/public/webpack/foreman_patch/bundle-831173d6ae39953b2409.js.map +1 -0
  47. data/public/webpack/foreman_patch/bundle-831173d6ae39953b2409.js.map.gz +0 -0
  48. data/public/webpack/foreman_patch/foreman_patch-8909c3e06f012a43f769.css +1 -0
  49. data/public/webpack/foreman_patch/foreman_patch-8909c3e06f012a43f769.css.gz +0 -0
  50. data/public/webpack/foreman_patch/foreman_patch-8909c3e06f012a43f769.js +6 -0
  51. data/public/webpack/foreman_patch/foreman_patch-8909c3e06f012a43f769.js.gz +0 -0
  52. data/public/webpack/foreman_patch/foreman_patch-8909c3e06f012a43f769.js.map +1 -0
  53. data/public/webpack/foreman_patch/foreman_patch-8909c3e06f012a43f769.js.map.gz +0 -0
  54. data/public/webpack/foreman_patch/foreman_patch:global-d71cc6e1e759f04f631a.css +1 -0
  55. data/public/webpack/foreman_patch/foreman_patch:global-d71cc6e1e759f04f631a.css.gz +0 -0
  56. data/public/webpack/foreman_patch/foreman_patch:global-d71cc6e1e759f04f631a.js +6 -0
  57. data/public/webpack/foreman_patch/foreman_patch:global-d71cc6e1e759f04f631a.js.gz +0 -0
  58. data/public/webpack/foreman_patch/foreman_patch:global-d71cc6e1e759f04f631a.js.map +1 -0
  59. data/public/webpack/foreman_patch/foreman_patch:global-d71cc6e1e759f04f631a.js.map.gz +0 -0
  60. data/public/webpack/foreman_patch/manifest.json +31 -0
  61. data/public/webpack/foreman_patch/manifest.json.gz +0 -0
  62. data/public/webpack/foreman_patch/vendor-769bd77f6be96c3c37e1.js +2 -0
  63. data/public/webpack/foreman_patch/vendor-769bd77f6be96c3c37e1.js.gz +0 -0
  64. data/public/webpack/foreman_patch/vendor-769bd77f6be96c3c37e1.js.map +1 -0
  65. data/public/webpack/foreman_patch/vendor-769bd77f6be96c3c37e1.js.map.gz +0 -0
  66. data/webpack/components/Invocations/InvocationsPage.js +1 -1
  67. data/webpack/components/Invocations/index.js +6 -6
  68. data/webpack/components/common/Calendar/Calendar.js +5 -4
  69. data/webpack/components/common/Table/index.js +28 -0
  70. data/webpack/global_index.js +16 -0
  71. data/webpack/index.js +3 -8
  72. data/webpack/src/Components/Invocation/Invocation.js +67 -0
  73. data/webpack/src/Components/Invocation/InvocationLogFooter.js +30 -0
  74. data/webpack/src/Components/Invocation/InvocationLogToolbar.js +80 -0
  75. data/webpack/{components → src/Components}/Invocation/InvocationSelectors.js +0 -3
  76. data/webpack/src/Components/Invocation/index.js +62 -0
  77. data/webpack/src/Components/InvocationStatus.js +50 -0
  78. data/webpack/src/Components/Loading.js +51 -0
  79. data/webpack/src/Extends/index.js +15 -0
  80. data/webpack/src/Router/routes.js +4 -2
  81. data/webpack/src/reducers.js +1 -1
  82. metadata +61 -86
  83. data/app/lib/actions/foreman_patch/cycle/complete.rb +0 -41
  84. data/app/lib/actions/foreman_patch/cycle/plan.rb +0 -73
  85. data/app/lib/actions/foreman_patch/round/plan.rb +0 -33
  86. data/app/lib/actions/foreman_patch/window/plan.rb +0 -43
  87. data/app/models/setting/patching.rb +0 -57
  88. data/app/views/foreman_patch/api/v2/invocations/phase.json.rabl +0 -7
  89. data/config/routes/mount_engine.rb +0 -3
  90. data/config/routes/overrides.rb +0 -10
  91. data/lib/foreman_patch/plugin.rb +0 -47
  92. data/webpack/components/Invocation/Invocation.js +0 -47
  93. data/webpack/components/Invocation/index.js +0 -36
  94. data/webpack/components/common/Terminal/OutputLine.js +0 -26
  95. data/webpack/components/common/Terminal/Terminal.js +0 -115
  96. data/webpack/components/common/Terminal/Terminal.scss +0 -47
  97. data/webpack/src/ForemanPatch.js +0 -11
  98. data/webpack/src/Router/index.js +0 -14
  99. data/webpack/src/index.js +0 -1
  100. /data/webpack/components/Invocations/{Invocations.scss → Invocations.css} +0 -0
  101. /data/webpack/components/common/Calendar/{Calendar.scss → Calendar.css} +0 -0
  102. /data/webpack/{components → src/Components}/Invocation/InvocationActions.js +0 -0
  103. /data/webpack/{components → src/Components}/Invocation/InvocationConsts.js +0 -0
@@ -4,7 +4,7 @@ import { Grid } from 'patternfly-react';
4
4
 
5
5
  import SearchBar from 'foremanReact/components/SearchBar';
6
6
  import Pagination from 'foremanReact/components/Pagination/PaginationWrapper';
7
- import { ActionButtons } form 'foremanReact/components/common/ActionButtons/ActionButtons';
7
+ import { ActionButtons } from 'foremanReact/components/common/ActionButtons/ActionButtons';
8
8
  import { getControllerSearchProps } from 'foremanReact/constants';
9
9
 
10
10
  import Invocations from './Invocations';
@@ -3,7 +3,7 @@ import { useSelector, useDispatch } from 'react-redux';
3
3
  import PropTypes from 'prop-types';
4
4
  import { Grid } from 'patternfly-react';
5
5
 
6
- import {
6
+ import {
7
7
  withInterval,
8
8
  stopInterval
9
9
  } from 'foremanReact/redux/middlewares/IntervalMiddleware';
@@ -26,7 +26,7 @@ import {
26
26
  import { getUrl } from './InvocationsHelpers';
27
27
  import { INVOCATIONS } from './InvocationsConstants';
28
28
 
29
- import './Invocations.scss';
29
+ import './Invocations.css';
30
30
 
31
31
  const WrappedInvocations = ({ round }) => {
32
32
  const dispatch = useDispatch();
@@ -67,7 +67,7 @@ const WrappedInvocations = ({ round }) => {
67
67
 
68
68
  const areAllSelected = selectedItems.length == items.length;
69
69
 
70
- const setItemSelected = (item, isSelecting) =>
70
+ const setItemSelected = (item, isSelecting) =>
71
71
  setSelectedItems(prevSelected => {
72
72
  const otherSelectedItems = prevSelected.filter(i => i !== item.id);
73
73
  return isSelecting ? [...otherSelectedItems, item.id] : otherSelectedItems;
@@ -78,8 +78,8 @@ const WrappedInvocations = ({ round }) => {
78
78
  const numberSelected = rowIndex - recentSelectedRowIndex;
79
79
  const intermediateIndexes =
80
80
  numberSelected > 0
81
- ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)
82
- : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);
81
+ ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)
82
+ : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);
83
83
  intermediateIndexes.forEach(index, setItemSelected(items[index], isSelecting));
84
84
  } else {
85
85
  setItemSelected(item, isSelecting);
@@ -95,7 +95,7 @@ const WrappedInvocations = ({ round }) => {
95
95
  }
96
96
  };
97
97
 
98
- const getData = url => withInterval(get({
98
+ const getData = url => withInterval(get({
99
99
  key: INVOCATIONS,
100
100
  url,
101
101
  handleError: () => {
@@ -2,12 +2,13 @@ import React, { useState } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { DndProvider } from 'react-dnd';
4
4
  import HTML5Backend from 'react-dnd-html5-backend';
5
- import './Calendar.scss';
5
+
6
+ import './Calendar.css';
6
7
 
7
8
  import { views, getView } from './View';
8
9
 
9
10
  const Calendar = (props) => {
10
- const {start, end, weekStartsOn, locale, onEventMoved} = props;
11
+ const { start, end, weekStartsOn, locale, onEventMoved } = props;
11
12
 
12
13
  const initialDate = () => {
13
14
  const { date } = props;
@@ -27,7 +28,7 @@ const Calendar = (props) => {
27
28
 
28
29
  const result = events.map(event => {
29
30
  if (event.id === updatedEvent.id) {
30
- return {...event, ...updatedEvent};
31
+ return { ...event, ...updatedEvent };
31
32
  }
32
33
  return event;
33
34
  });
@@ -78,7 +79,7 @@ Calendar.defaultProps = {
78
79
  date: new Date(),
79
80
  view: views.MONTH,
80
81
  events: [],
81
- onEventMoved: (event) => {},
82
+ onEventMoved: (event) => { },
82
83
  locale: 'en-US',
83
84
  weekStartsOn: 1,
84
85
  };
@@ -0,0 +1,28 @@
1
+ import React, { useCallback, useRef } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useDispatch } from 'react-redux';
4
+ import { PaginationVariant, Flex, FlexItem } from '@patternfly/react-core';
5
+
6
+ import { STATUS } from 'foremanReact/constants';
7
+ import { useForemanSettings } from 'foremanReact/Root/Context/ForemanContext';
8
+ import { translate as __ } from 'foremanReact/common/I18n';
9
+
10
+ import { SelectAllCheckbox }
11
+
12
+ const TableWrapper = ({
13
+
14
+ }) => {
15
+ const dispatch = useDispatch();
16
+
17
+ return (
18
+ <>
19
+ <Flex style={{ alignItems: 'center' }} className="margin-16-24">
20
+ { displaySelectAllCheckbox && !hideToolbar &&
21
+ <FlexItem alignSelf={{ default: 'alignSelfCenter' }}>
22
+ <SelectAllCheckbox
23
+ </FlexItem>
24
+ }
25
+ </Flex>
26
+ </>
27
+ );
28
+ }
@@ -0,0 +1,16 @@
1
+ import { registerReducer } from 'foremanReact/common/MountingService';
2
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
+ import { registerRoutes } from 'foremanReact/routes/RoutingService';
4
+ import Routes from './src/Router/routes';
5
+ import reducers from './src/reducers';
6
+
7
+ // register reducers
8
+ Object.entries(reducers).forEach(([key, reducer]) =>
9
+ registerReducer(key, reducer)
10
+ );
11
+
12
+ // register client routes
13
+ registerRoutes('ForemanPatch', Routes);
14
+
15
+ // register fills for extending foreman core
16
+ //addGlobalFill('host-overview-cards', 'Patching Details', );
data/webpack/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  /* eslint import/no-unresolved: [2, { ignore: [foremanReact/*] }] */
2
2
  /* eslint-disable import/no-extraneous-dependencies */
3
3
  /* eslint-disable import/extensions */
4
+ /* eslint-disable import/no-unresolved */
4
5
  import componentRegistry from 'foremanReact/components/componentRegistry';
5
- import { registerReducer } from 'foremanReact/common/MountingService';
6
- import reducers from './src/reducers';
7
- import ForemanPatch from './src/ForemanPatch';
8
6
 
9
7
  import Rounds from './components/Rounds';
10
8
  import Plan from './components/Plan';
9
+ import Invocation from './src/Components/Invocation';
11
10
  import Invocations from './components/Invocations';
12
11
  import RoundProgress from './components/RoundProgress';
13
12
  import Cycle from './components/Cycle';
@@ -18,11 +17,7 @@ const components = [
18
17
  { name: 'Invocations', type: Invocations },
19
18
  { name: 'Cycle', type: Cycle },
20
19
  { name: 'RoundProgress', type: RoundProgress },
20
+ { name: 'Invocation', type: Invocation },
21
21
  ];
22
22
 
23
- Object.entries(reducers).forEach(([key, reducer]) =>
24
- registerReducer(key, reducer)
25
- );
26
-
27
23
  components.forEach(component => componentRegistry.register(component));
28
- componentRegistry.register({ name: 'ForemanPatch', type: ForemanPatch });
@@ -0,0 +1,67 @@
1
+ import React, { useRef, useState, useMemo } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ const Invocation = ({ events, status }) => {
6
+ const viewerRef = useRef();
7
+ const [isStderrVisible, setStderrVisible] = useState(true);
8
+ const [isStdoutVisible, setStdoutVisible] = useState(true);
9
+ const [isDebugVisible, setDebugVisible] = useState(false);
10
+ const [followStatus, setFollowStatus] = useState(status === 'running' ? 'active' : 'disabled');
11
+
12
+ const lines = useMemo(() => {
13
+ return events.flatMap(event => (
14
+ event.event.replace(/\r\n/, "\n").replace(/\n$/, '').split("\n").map(line => (
15
+ { event_type: event.event_type, event: line, timestamp: event.timestamp }
16
+ )).filter((event) => (
17
+ (event.event_type === 'stdout' && isStdoutVisible) ||
18
+ (event.event_type === 'stderr' && isStderrVisible) ||
19
+ (event.event_type === 'debug' && isDebugVisible)
20
+ ))
21
+ ));
22
+ }, [events, isStderrVisible, isStdoutVisible, isDebugVisible]);
23
+
24
+ const onScroll = ({ scrollDirection, scrollOffsetToBottom, scrollUpdateWasRequested }) => {
25
+ if (!scrollUpdateWasRequested) {
26
+ if (scrollOffsetToBottom < 1) {
27
+ setFollowStatus('active');
28
+ } else if (scrollDirection === 'backward') {
29
+ setFollowStatus('paused');
30
+ }
31
+ }
32
+ };
33
+
34
+ return (
35
+ <LogViewer
36
+ ref={viewerRef}
37
+ theme='dark'
38
+ data={lines}
39
+ toolbar={<InvocationLogToolbar
40
+ isDebugVisible={isDebugVisible}
41
+ setDebugVisible={setDebugVisible}
42
+ isStderrVisible={isStderrVisible}
43
+ setStderrVisible={setStderrVisible}
44
+ isStdoutVisible={isStderrVisible}
45
+ setStdoutVisible={setStdoutVisible}
46
+ />}
47
+ footer={<InvocationLogFooter followStatus={followStatus} setFollowStatus={setFollowStatus} />}
48
+ onScroll={onScroll}
49
+ />
50
+ );
51
+ };
52
+
53
+ Invocation.propTypes = {
54
+ events: PropTypes.arrayOf(PropTypes.shape({
55
+ event_type: PropTypes.string.isRequired,
56
+ event: PropTypes.string.isRequired,
57
+ timestamp: PropTypes.string.isRequired,
58
+ })),
59
+ status: PropTypes.string,
60
+ };
61
+
62
+ Invocation.defaultProps = {
63
+ events: [],
64
+ status: 'pending',
65
+ };
66
+
67
+ export default Invocation;
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+ import { OutlinedPlayCircleIcon } from '@patternfly/react-icons';
5
+ import { Button } from '@patternfly/react-core';
6
+ import classNames from 'classnames';
7
+
8
+ const InvocationFooter = ({ followStatus, setFollowStatus }) => {
9
+ const resumeFollowing = () => setFollowStatus('active');
10
+
11
+ return (
12
+ <Button className={classNames('invocation__footer', {
13
+ 'invocation__footer--hidden': followStatus !== 'paused',
14
+ })} onClick={resumeFollowing} isBlock >
15
+ <OutlinedPlayCircleIcon />
16
+ &nbsp{__('Resume following')}
17
+ </Button>
18
+ );
19
+ };
20
+
21
+ InvocationFooter.propTypes = {
22
+ followStatus: PropTypes.string,
23
+ setFollowStatus: PropTypes.func.isRequired,
24
+ };
25
+
26
+ InvocationFooter.defaultProps = {
27
+ followStatus: 'disabled',
28
+ };
29
+
30
+ export default InvocationFooter;
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ Toolbar,
5
+ ToolbarContent,
6
+ ToolbarGroup,
7
+ ToolbarItem,
8
+ ToggleGroup,
9
+ ToggleGroupItem
10
+ } from '@patternfly/react-core';
11
+ import { LogViewerSearch } from '@patternfly/react-log-viewer';
12
+ import { translate as __ } from 'foremanReact/common/I18n';
13
+
14
+ const InvocationLogToolbar = ({
15
+ status,
16
+ isDebugVisible,
17
+ setDebugVisible,
18
+ isStderrVisible,
19
+ setStderrVisible,
20
+ isStdoutVisible,
21
+ setStdoutVisible,
22
+ }) => {
23
+ return (
24
+ <Toolbar>
25
+ <ToolbarContent>
26
+ <ToolbarGroup alignment={{ default: 'alignLeft' }}>
27
+ <ToolbarItem>
28
+ <InvocationStatus status={status} />
29
+ </ToolbarItem>
30
+ </ToolbarGroup>
31
+ <ToolbarGroup alignement={{ default: 'alignRight' }}>
32
+ <ToolbarItem>
33
+ <LogViewerSearch placeholder={__('Search')} />
34
+ </ToolbarItem>
35
+ <ToolbarItem>
36
+ <ToggleGroup>
37
+ <ToggleGroupItem
38
+ text={__('Toggle STDERR')}
39
+ isSelected={isStderrVisible}
40
+ onChange={setStderrVisible}
41
+ />
42
+ <ToggleGroupItem
43
+ text={__('Toggle STDOUT')}
44
+ isSelected={isStdoutVisible}
45
+ onChange={setStdoutVisible}
46
+ />
47
+ <ToggleGroupItem
48
+ text={__('Toggle DEBUG')}
49
+ isSelected={isDebugVisible}
50
+ onChange={setDebugVisible}
51
+ />
52
+ </ToggleGroup>
53
+ </ToolbarItem>
54
+ </ToolbarGroup>
55
+ </ToolbarContent>
56
+ </Toolbar>
57
+ );
58
+ };
59
+
60
+ InvocationLogToolbar.propTypes = {
61
+ status: PropTypes.string,
62
+ isDebugVisible: PropTypes.bool,
63
+ setDebugVisible: PropTypes.func,
64
+ isStderrVisible: PropTypes.bool,
65
+ setStderrVisible: PropTypes.func,
66
+ isStdoutVisible: PropTypes.bool,
67
+ setStdoutVisible: PropTypes.func,
68
+ };
69
+
70
+ InvocationLogToolbar.defaultProps = {
71
+ status: 'pending',
72
+ isDebugVisible: false,
73
+ setDebugVisible: Function.prototype,
74
+ isStderrVisible: true,
75
+ setStderrVisible: Function.prototype,
76
+ isStdoutVisible: true,
77
+ setStdoutVisible: Function.prototype,
78
+ };
79
+
80
+ export default InvocationLogToolbar;
@@ -7,8 +7,5 @@ import { INVOCATION } from './InvocationConsts';
7
7
  export const selectInvocation = state =>
8
8
  selectAPIResponse(state, INVOCATION) || {};
9
9
 
10
- export const selectState = state =>
11
- selectAPIResponse(state, INVOCATION).state;
12
-
13
10
  export const selectStatus = state =>
14
11
  selectAPIStatus(state, INVOCATION);
@@ -0,0 +1,62 @@
1
+ import React, { useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useSelector, useDispatch } from 'react-redux';
4
+ import { Alert } from '@patternfly/react-core';
5
+ import { stopInterval } from 'foremanReact/redux/middlewares/IntervalMiddleware';
6
+ import Invocation from './Invocation';
7
+ import {
8
+ selectInvocation,
9
+ selectStatus,
10
+ } from './InvocationSelectors';
11
+ import { getData } from './InvocationActions';
12
+ import { INVOCATION } from './InvocationConsts';
13
+ import Loading from '../Loading';
14
+
15
+ const WrappedInvocation = ({ id }) => {
16
+ const dispatch = useDispatch();
17
+ const invocation = useSelector(selectInvocation);
18
+ const loadingStatus = useSelector(selectStatus);
19
+
20
+ const isCompleted = () => {
21
+ const status = invocation.status;
22
+ return (status === 'error' || status === 'warning' || status === 'success' || status === 'cancelled');
23
+ };
24
+
25
+ useEffect(() => {
26
+ dispatch(getData(id));
27
+
28
+ return () => {
29
+ dispatch(stopInterval(INVOCATION));
30
+ };
31
+ }, [dispatch]);
32
+
33
+ useEffect(() => {
34
+ if (isCompleted()) {
35
+ dispatch(stopInterval(INVOCATION));
36
+ }
37
+ }, [state, dispatch]);
38
+
39
+ if (loadingStatus === STATUS.PENDING) {
40
+ return <Loading />;
41
+ }
42
+
43
+ if (loadingStatus === STATUS.ERROR) {
44
+ return (
45
+ <Alert type="error">
46
+ {__(
47
+ 'There was an error while updating the status, try refreshing the page.'
48
+ )}
49
+ </Alert>
50
+ );
51
+ }
52
+
53
+ return (
54
+ <Invocation {...invocation} />
55
+ );
56
+ };
57
+
58
+ WrappedInvocation.propTypes = {
59
+ id: PropTypes.number.isRequired
60
+ };
61
+
62
+ export default WrappedInvocation;
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ ExclamationCircleIcon,
5
+ ExclamationTriangleIcon,
6
+ CheckCircleIcon,
7
+ BanIcon,
8
+ InProgressIcon,
9
+ PendingIcon
10
+ } from '@patternfly/react-icons';
11
+ import { Label } from '@patternfly/react-core';
12
+
13
+ const InvocationStatus = ({ status }) => {
14
+ switch (status) {
15
+ case 'error':
16
+ return (
17
+ <Label icon={<ExclamationCircleIcon />}>{__('failed')}</Label>
18
+ );
19
+ case 'warning':
20
+ return (
21
+ <Label icon={<ExclamationTriangleIcon />}>{__('warning')}</Label>
22
+ );
23
+ case 'success':
24
+ return (
25
+ <Label icon={<CheckCircleIcon />}>{__('success')}</Label>
26
+ );
27
+ case 'cancelled':
28
+ return (
29
+ <Label icon={<BanIcon />}>{__('success')}</Label>
30
+ );
31
+ case 'running':
32
+ return (
33
+ <Label icon={<InProgressIcon />}>{__('running')}</Label>
34
+ );
35
+ default:
36
+ return (
37
+ <Label icon={<PendingIcon />}>{__('pending')}</Label>
38
+ );
39
+ }
40
+ };
41
+
42
+ InvocationStatus.propTypes = {
43
+ status: PropTypes.string,
44
+ };
45
+
46
+ InvocationStatus.defaultProps = {
47
+ status: 'pending',
48
+ };
49
+
50
+ export default InvocationStatus;
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ Bullseye,
5
+ Title,
6
+ EmptyState,
7
+ EmptyStateIcon,
8
+ Spinner,
9
+ Skeleton,
10
+ } from '@patternfly/react-core';
11
+ import { translate as __ } from 'foremanReact/common/I18n';
12
+
13
+ const Loading = ({
14
+ size,
15
+ showText,
16
+ loadingText,
17
+ skeleton,
18
+ }) => {
19
+ if (skeleton) {
20
+ return <Skeleton height="100%" />;
21
+ }
22
+
23
+ return (
24
+ <Bullseye>
25
+ <EmptyState>
26
+ <EmptyStateIcon size={size} variant="container" component={Spinner} />
27
+ {showText && (
28
+ <Title size={size} headingLevel="h4" ouiaId="loading-title">
29
+ {loadingText || __('Loading')}
30
+ </Title>
31
+ )}
32
+ </EmptyState>
33
+ </Bullseye>
34
+ );
35
+ };
36
+
37
+ Loading.propTypes = {
38
+ size: PropTypes.string,
39
+ showText: PropTypes.bool,
40
+ loadingText: PropTypes.string,
41
+ skeleton: PropTypes.bool,
42
+ };
43
+
44
+ Loading.defaultProps = {
45
+ size: 'lg',
46
+ showText: true,
47
+ loadingText: null,
48
+ skeleton: false,
49
+ };
50
+
51
+ export default Loading;
@@ -0,0 +1,15 @@
1
+ // This is an example of extending foreman-core's component via slot&fill
2
+ // http://foreman.surge.sh/?path=/docs/introduction-slot-and-fill--page
3
+ /*
4
+ import React from 'react';
5
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
6
+
7
+ addGlobalFill('slotId', 'fillId', <SomeComponent key="some-key" />, 300);
8
+
9
+ addGlobalFill(
10
+ 'slotId',
11
+ 'fillId',
12
+ { someProp: 'this is an override prop' },
13
+ 300
14
+ );
15
+ */
@@ -1,3 +1,5 @@
1
- const routes = {};
1
+ import React from 'react';
2
2
 
3
- export default routes;
3
+ const routes = [];
4
+
5
+ export default routes;
@@ -4,4 +4,4 @@ const reducers = {
4
4
  foremanPatch: combineReducers({}),
5
5
  };
6
6
 
7
- export default reducers;
7
+ export default reducers;