foreman_patch 1.1.5 → 1.1.6.alpha4
Sign up to get free protection for your applications and to get access to all the features.
- 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