foreman_patch 1.1.5 → 1.1.6.alpha4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/foreman_patch/concerns/hosts_controller_extensions.rb +35 -41
- data/app/controllers/foreman_patch/react_controller.rb +12 -0
- data/app/lib/actions/foreman_patch/cycle/create.rb +5 -1
- data/app/lib/actions/foreman_patch/cycle/initiate.rb +5 -1
- data/app/lib/actions/foreman_patch/invocation/action.rb +23 -29
- data/app/lib/actions/foreman_patch/invocation/patch.rb +16 -7
- data/app/lib/actions/foreman_patch/invocation/process_logging.rb +44 -0
- data/app/lib/actions/foreman_patch/invocation/proxy_action.rb +52 -0
- data/app/lib/actions/foreman_patch/invocation/wait_for_host.rb +4 -29
- data/app/lib/actions/foreman_patch/round/patch.rb +5 -1
- data/app/lib/actions/foreman_patch/window/publish.rb +5 -1
- data/app/lib/actions/foreman_patch/window/resolve_hosts.rb +5 -1
- data/app/models/foreman_patch/event.rb +13 -0
- data/app/models/foreman_patch/invocation.rb +2 -4
- data/app/views/foreman_patch/api/v2/invocations/base.json.rabl +1 -1
- data/app/views/foreman_patch/api/v2/invocations/event.json.rabl +3 -0
- data/app/views/foreman_patch/api/v2/invocations/show.json.rabl +2 -2
- data/app/views/foreman_patch/groups/index.html.erb +1 -1
- data/app/views/foreman_patch/layouts/react.html.erb +1 -1
- data/config/api_routes.rb +26 -28
- data/config/routes.rb +40 -29
- data/db/migrate/20230706092400_nullify_group_on_delete.rb +11 -0
- data/db/migrate/20230707102800_create_invocation_events.rb +16 -0
- data/db/seeds.d/100-assign_features_with_templates.rb +6 -12
- data/lib/foreman_patch/engine.rb +11 -46
- data/lib/foreman_patch/register.rb +119 -0
- data/lib/foreman_patch/version.rb +1 -1
- data/locale/en/foreman_patch.po +1 -1
- data/locale/foreman_patch.pot +1 -1
- data/locale/gemspec.rb +1 -1
- data/package.json +4 -0
- data/public/assets/foreman_patch/calendar-b5391efda77239c4a4894e9f03f34610f6c8e2e748b2a147febfea814b857cdc.scss.gz +0 -0
- data/public/assets/foreman_patch/cycle_plans-e5667e178ba389908f5c815b24ec0ea77c340849d56bc39c5ce72bb626bd446a.scss +6 -0
- data/public/assets/foreman_patch/cycle_plans-e5667e178ba389908f5c815b24ec0ea77c340849d56bc39c5ce72bb626bd446a.scss.gz +0 -0
- data/public/assets/foreman_patch/cycle_plans-ff3d252119622a68828ff70f4a97328303963002237dbf850e92d6a706e93667.scss +6 -0
- data/public/assets/foreman_patch/cycle_plans-ff3d252119622a68828ff70f4a97328303963002237dbf850e92d6a706e93667.scss.gz +0 -0
- data/public/assets/foreman_patch/foreman_patch-410cf04bf9b09e65fee034cc3f2dd74acf2524abf881c6d6e559d5c62a615faf.css +11 -0
- data/public/assets/foreman_patch/foreman_patch-410cf04bf9b09e65fee034cc3f2dd74acf2524abf881c6d6e559d5c62a615faf.css.gz +0 -0
- data/public/assets/foreman_patch/foreman_patch-84845e54f06d3a11189828e656432d587c7312358cdf694753da7b78b7dabcee.css +11 -0
- data/public/assets/foreman_patch/foreman_patch-84845e54f06d3a11189828e656432d587c7312358cdf694753da7b78b7dabcee.css.gz +0 -0
- data/public/assets/foreman_patch/foreman_patch-a76c5fd10a5795e97c5ae4c222bfdf4ab88da49d6a7659175dba79f8fc62ab47.css +82 -0
- data/public/assets/foreman_patch/foreman_patch-a76c5fd10a5795e97c5ae4c222bfdf4ab88da49d6a7659175dba79f8fc62ab47.css.gz +0 -0
- data/public/assets/foreman_patch/foreman_patch.json +1 -0
- data/public/assets/foreman_patch/plan_edit_windows-2eb04c7e83fa62797b0a14364d3ff5b3c4336983603fdc5a276b5464eeba7e60.js +9 -0
- data/public/assets/foreman_patch/plan_edit_windows-2eb04c7e83fa62797b0a14364d3ff5b3c4336983603fdc5a276b5464eeba7e60.js.gz +0 -0
- data/public/assets/foreman_patch/plan_edit_windows-ddedd3e70fb6ef761f636be2b7b35286e86d68e126bfc37294f9365a5171a928.js +9 -0
- data/public/assets/foreman_patch/plan_edit_windows-ddedd3e70fb6ef761f636be2b7b35286e86d68e126bfc37294f9365a5171a928.js.gz +0 -0
- data/public/webpack/foreman_patch/bundle.css +1 -0
- data/public/webpack/foreman_patch/bundle.js +34173 -0
- data/public/webpack/foreman_patch/foreman_patch.css +1 -0
- data/public/webpack/foreman_patch/foreman_patch.js +34366 -0
- data/public/webpack/foreman_patch/foreman_patch:global.css +1 -0
- data/public/webpack/foreman_patch/foreman_patch:global.js +32098 -0
- data/public/webpack/foreman_patch/manifest.json +25 -0
- data/public/webpack/foreman_patch/vendor.js +5201 -0
- data/webpack/components/Invocations/InvocationsPage.js +1 -1
- data/webpack/components/Invocations/index.js +6 -6
- data/webpack/components/common/Calendar/Calendar.css +76 -0
- data/webpack/components/common/Calendar/Calendar.js +5 -4
- data/webpack/components/common/Table/index.js +28 -0
- data/webpack/global_index.js +16 -0
- data/webpack/index.js +3 -8
- data/webpack/src/Components/Invocation/Invocation.js +67 -0
- data/webpack/src/Components/Invocation/InvocationLogFooter.js +30 -0
- data/webpack/src/Components/Invocation/InvocationLogToolbar.js +80 -0
- data/webpack/{components → src/Components}/Invocation/InvocationSelectors.js +0 -3
- data/webpack/src/Components/Invocation/index.js +62 -0
- data/webpack/src/Components/InvocationStatus.js +50 -0
- data/webpack/src/Components/Loading.js +51 -0
- data/webpack/src/Extends/index.js +15 -0
- data/webpack/src/Router/routes.js +4 -2
- data/webpack/src/reducers.js +1 -1
- metadata +57 -88
- data/app/lib/actions/foreman_patch/cycle/complete.rb +0 -41
- data/app/lib/actions/foreman_patch/cycle/plan.rb +0 -73
- data/app/lib/actions/foreman_patch/round/plan.rb +0 -33
- data/app/lib/actions/foreman_patch/window/plan.rb +0 -43
- data/app/models/setting/patching.rb +0 -57
- data/app/views/foreman_patch/api/v2/invocations/phase.json.rabl +0 -7
- data/config/routes/mount_engine.rb +0 -3
- data/config/routes/overrides.rb +0 -10
- data/lib/foreman_patch/plugin.rb +0 -47
- data/webpack/components/Invocation/Invocation.js +0 -47
- data/webpack/components/Invocation/index.js +0 -36
- data/webpack/components/common/Terminal/OutputLine.js +0 -26
- data/webpack/components/common/Terminal/Terminal.js +0 -115
- data/webpack/components/common/Terminal/Terminal.scss +0 -47
- data/webpack/src/ForemanPatch.js +0 -11
- data/webpack/src/Router/index.js +0 -14
- data/webpack/src/index.js +0 -1
- /data/{webpack/components/common/Calendar/Calendar.scss → public/assets/foreman_patch/calendar-b5391efda77239c4a4894e9f03f34610f6c8e2e748b2a147febfea814b857cdc.scss} +0 -0
- /data/webpack/components/Invocations/{Invocations.scss → Invocations.css} +0 -0
- /data/webpack/{components → src/Components}/Invocation/InvocationActions.js +0 -0
- /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 }
|
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.
|
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
|
-
|
82
|
-
|
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 =>
|
98
|
+
const getData = url => withInterval(get({
|
99
99
|
key: INVOCATIONS,
|
100
100
|
url,
|
101
101
|
handleError: () => {
|
@@ -0,0 +1,76 @@
|
|
1
|
+
.calendar {
|
2
|
+
|
3
|
+
.day {
|
4
|
+
padding: 2px;
|
5
|
+
height: 120px;
|
6
|
+
}
|
7
|
+
|
8
|
+
.wday-0 {}
|
9
|
+
.wday-1 {}
|
10
|
+
.wday-2 {}
|
11
|
+
.wday-3 {}
|
12
|
+
.wday-4 {}
|
13
|
+
.wday-5 {}
|
14
|
+
.wday-6 {}
|
15
|
+
|
16
|
+
.today {
|
17
|
+
background: #ffffc0;
|
18
|
+
}
|
19
|
+
|
20
|
+
.past {
|
21
|
+
background: #f9f9f9;
|
22
|
+
}
|
23
|
+
|
24
|
+
.future {}
|
25
|
+
|
26
|
+
.start-date {}
|
27
|
+
|
28
|
+
.disabled {
|
29
|
+
background: #e8e8e8;
|
30
|
+
}
|
31
|
+
.enabled {}
|
32
|
+
td.enabled:hover {
|
33
|
+
background: #def3ff;
|
34
|
+
}
|
35
|
+
|
36
|
+
h6 {
|
37
|
+
margin: 2px 3px;
|
38
|
+
}
|
39
|
+
|
40
|
+
div.day {
|
41
|
+
margin: 0;
|
42
|
+
height: inherit;
|
43
|
+
width: inherit;
|
44
|
+
display: block;
|
45
|
+
}
|
46
|
+
|
47
|
+
div.event {
|
48
|
+
margin: 2px, 10px;
|
49
|
+
border: 1px solid;
|
50
|
+
border-color: #d1d1d1;
|
51
|
+
padding: 2px 3px;
|
52
|
+
background: #ffffff;
|
53
|
+
}
|
54
|
+
|
55
|
+
.calendar-header {
|
56
|
+
button {
|
57
|
+
font-size: 20px;
|
58
|
+
width: 100%;
|
59
|
+
}
|
60
|
+
|
61
|
+
.calendar-title {
|
62
|
+
font-size: 24px;
|
63
|
+
text-align: center;
|
64
|
+
}
|
65
|
+
|
66
|
+
.previous {
|
67
|
+
padding-left: 0px;
|
68
|
+
padding-right: 20px;
|
69
|
+
}
|
70
|
+
|
71
|
+
.next {
|
72
|
+
padding-left: 20px;
|
73
|
+
padding-right: 0px;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
}
|
@@ -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
|
-
|
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
|
+
 {__('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
|
+
*/
|
data/webpack/src/reducers.js
CHANGED