foreman_rh_cloud 13.0.8 → 13.0.10
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/concerns/inventory_upload/report_actions.rb +8 -1
- data/app/controllers/foreman_inventory_upload/accounts_controller.rb +82 -7
- data/app/controllers/foreman_inventory_upload/api/tasks_controller.rb +110 -0
- data/app/controllers/foreman_inventory_upload/reports_controller.rb +41 -17
- data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -9
- data/config/routes.rb +4 -2
- data/db/migrate/20251209163012_drop_task_output_tables.foreman_rh_cloud.rb +24 -0
- data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +1 -1
- data/lib/foreman_inventory_upload/async/host_inventory_report_job.rb +39 -0
- data/lib/foreman_inventory_upload/async/queue_for_upload_job.rb +1 -23
- data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +171 -0
- data/lib/foreman_inventory_upload.rb +0 -4
- data/lib/foreman_rh_cloud/plugin.rb +1 -0
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/inventory_sync/async/inventory_hosts_sync.rb +0 -2
- data/lib/tasks/rh_cloud_inventory.rake +4 -2
- data/package.json +1 -1
- data/test/controllers/accounts_controller_test.rb +10 -11
- data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +1 -2
- data/test/jobs/host_inventory_report_job_test.rb +161 -97
- data/test/jobs/queue_for_upload_job_test.rb +1 -12
- data/test/jobs/single_host_report_job_test.rb +36 -54
- data/test/jobs/upload_report_direct_job_test.rb +399 -0
- data/test/unit/rh_cloud_permissions_test.rb +2 -0
- data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +6 -6
- data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +49 -34
- data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListActions.js +2 -2
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +4 -5
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +15 -7
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +11 -11
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.fixtures.js +2 -2
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.js +10 -14
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatusHelper.js +9 -4
- data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +4 -4
- data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +15 -9
- data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +7 -7
- data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +6 -6
- data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +12 -12
- data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +37 -130
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/Dashboard.test.js +60 -17
- data/webpack/ForemanInventoryUpload/Components/Dashboard/index.js +1 -34
- data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -1
- data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +1 -26
- data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +24 -17
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +178 -8
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +2 -2
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +3 -1
- data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +69 -51
- data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +22 -9
- data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +67 -4
- data/webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js +140 -0
- data/webpack/ForemanInventoryUpload/Components/TaskHistory/index.js +1 -0
- data/webpack/ForemanInventoryUpload/Components/TaskHistory/taskHistory.scss +40 -0
- data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +340 -0
- data/webpack/ForemanInventoryUpload/Components/TaskProgress/index.js +1 -0
- data/webpack/ForemanInventoryUpload/Components/TaskProgress/taskProgress.scss +8 -0
- data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +2 -2
- data/webpack/ForemanInventoryUpload/ForemanInventoryUploadReducers.js +0 -2
- data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +1 -1
- data/webpack/ForemanRhCloudPages.js +0 -1
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +11 -19
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +1 -2
- data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -4
- data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +77 -22
- data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +14 -0
- data/webpack/__tests__/ForemanRhCloudHelpers.test.js +5 -1
- metadata +13 -68
- data/app/models/task_output_line.rb +0 -2
- data/app/models/task_output_status.rb +0 -2
- data/lib/foreman_inventory_upload/async/generate_report_job.rb +0 -61
- data/lib/foreman_inventory_upload/async/progress_output.rb +0 -38
- data/lib/foreman_inventory_upload/async/shell_process.rb +0 -77
- data/lib/foreman_inventory_upload/async/upload_report_job.rb +0 -97
- data/lib/foreman_inventory_upload/scripts/uploader.sh.erb +0 -55
- data/test/controllers/reports_controller_test.rb +0 -21
- data/test/controllers/uploads_controller_test.rb +0 -21
- data/test/jobs/generate_report_job_test.rb +0 -146
- data/test/jobs/upload_report_job_test.rb +0 -38
- data/test/unit/shell_process_job_test.rb +0 -29
- data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardActions.js +0 -88
- data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardConstants.js +0 -9
- data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardReducer.js +0 -68
- data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardSelectors.js +0 -17
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardActions.test.js +0 -51
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardIntegration.test.js +0 -17
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardReducer.test.js +0 -64
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardSelectors.test.js +0 -46
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/Dashboard.test.js.snap +0 -36
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardActions.test.js.snap +0 -76
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardReducer.test.js.snap +0 -44
- data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardSelectors.test.js.snap +0 -42
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.js +0 -55
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModalHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/FullScreenModal.test.js +0 -13
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/__snapshots__/FullScreenModal.test.js.snap +0 -65
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/fullScreenModal.scss +0 -20
- data/webpack/ForemanInventoryUpload/Components/FullScreenModal/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap +0 -36
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.fixtures.js +0 -18
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.js +0 -65
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerateHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/ReportGenerate.test.js +0 -14
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/__snapshots__/ReportGenerate.test.js.snap +0 -47
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/ReportGenerate/reportGenerate.scss +0 -0
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.fixtures.js +0 -18
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.js +0 -46
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUploadHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/ReportUpload.test.js +0 -14
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/__snapshots__/ReportUpload.test.js.snap +0 -47
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/ReportUpload/reportUpload.scss +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.fixtures.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.js +0 -31
- data/webpack/ForemanInventoryUpload/Components/TabBody/TabBodyHelper.js +0 -0
- data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/TabBody.test.js +0 -13
- data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/__snapshots__/TabBody.test.js.snap +0 -19
- data/webpack/ForemanInventoryUpload/Components/TabBody/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/TabBody/tabBody.scss +0 -5
- data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.fixtures.js +0 -10
- data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.js +0 -110
- data/webpack/ForemanInventoryUpload/Components/Terminal/TerminalHelper.js +0 -6
- data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/Terminal.test.js +0 -34
- data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/__snapshots__/Terminal.test.js.snap +0 -98
- data/webpack/ForemanInventoryUpload/Components/Terminal/index.js +0 -1
- data/webpack/ForemanInventoryUpload/Components/Terminal/terminal.scss +0 -32
- data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +0 -112
- data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +0 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import {
|
|
4
|
+
DataList,
|
|
5
|
+
DataListItem,
|
|
6
|
+
DataListItemRow,
|
|
7
|
+
DataListItemCells,
|
|
8
|
+
DataListCell,
|
|
9
|
+
EmptyState,
|
|
10
|
+
EmptyStateIcon,
|
|
11
|
+
EmptyStateBody,
|
|
12
|
+
Title,
|
|
13
|
+
} from '@patternfly/react-core';
|
|
14
|
+
import {
|
|
15
|
+
CheckCircleIcon,
|
|
16
|
+
ExclamationCircleIcon,
|
|
17
|
+
ExclamationTriangleIcon,
|
|
18
|
+
} from '@patternfly/react-icons';
|
|
19
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
20
|
+
import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
|
|
21
|
+
import './taskHistory.scss';
|
|
22
|
+
|
|
23
|
+
const TaskHistory = ({ tasks, title }) => {
|
|
24
|
+
if (!tasks || tasks.length === 0) {
|
|
25
|
+
return (
|
|
26
|
+
<EmptyState>
|
|
27
|
+
<EmptyStateIcon icon={CheckCircleIcon} />
|
|
28
|
+
<Title headingLevel="h4" size="lg" ouiaId="task-history-empty-title">
|
|
29
|
+
{__('No task history')}
|
|
30
|
+
</Title>
|
|
31
|
+
<EmptyStateBody>
|
|
32
|
+
{__('Previous tasks will appear here.')}
|
|
33
|
+
</EmptyStateBody>
|
|
34
|
+
</EmptyState>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const getResultIcon = result => {
|
|
39
|
+
if (result === 'success') {
|
|
40
|
+
return <CheckCircleIcon className="task-history-icon-success" />;
|
|
41
|
+
}
|
|
42
|
+
if (result === 'error') {
|
|
43
|
+
return <ExclamationCircleIcon className="task-history-icon-error" />;
|
|
44
|
+
}
|
|
45
|
+
if (result === 'warning') {
|
|
46
|
+
return <ExclamationTriangleIcon className="task-history-icon-warning" />;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const formatDuration = seconds => {
|
|
52
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
53
|
+
return __('N/A');
|
|
54
|
+
}
|
|
55
|
+
const mins = Math.floor(seconds / 60);
|
|
56
|
+
const secs = Math.floor(seconds % 60);
|
|
57
|
+
if (mins > 0) {
|
|
58
|
+
return `${mins}m ${secs}s`;
|
|
59
|
+
}
|
|
60
|
+
return `${secs}s`;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getResultLabel = result => {
|
|
64
|
+
if (result === 'success') return __('Success');
|
|
65
|
+
if (result === 'error') return __('Failed');
|
|
66
|
+
if (result === 'warning') return __('Warning');
|
|
67
|
+
return result || __('Unknown');
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="task-history-container">
|
|
72
|
+
{title && (
|
|
73
|
+
<Title headingLevel="h3" size="md" ouiaId="task-history-title">
|
|
74
|
+
{title}
|
|
75
|
+
</Title>
|
|
76
|
+
)}
|
|
77
|
+
<DataList aria-label="task history" className="task-history-list">
|
|
78
|
+
{tasks.map(task => (
|
|
79
|
+
<DataListItem key={task.id} aria-labelledby={`task-${task.id}`}>
|
|
80
|
+
<DataListItemRow>
|
|
81
|
+
<DataListItemCells
|
|
82
|
+
dataListCells={[
|
|
83
|
+
<DataListCell key="icon" className="task-history-icon-cell">
|
|
84
|
+
{getResultIcon(task.result)}
|
|
85
|
+
</DataListCell>,
|
|
86
|
+
<DataListCell key="time" className="task-history-time-cell">
|
|
87
|
+
<RelativeDateTime date={task.started_at} />
|
|
88
|
+
</DataListCell>,
|
|
89
|
+
<DataListCell
|
|
90
|
+
key="result"
|
|
91
|
+
className="task-history-result-cell"
|
|
92
|
+
>
|
|
93
|
+
{getResultLabel(task.result)}
|
|
94
|
+
</DataListCell>,
|
|
95
|
+
<DataListCell
|
|
96
|
+
key="duration"
|
|
97
|
+
className="task-history-duration-cell"
|
|
98
|
+
>
|
|
99
|
+
{formatDuration(task.duration)}
|
|
100
|
+
</DataListCell>,
|
|
101
|
+
<DataListCell key="link" className="task-history-link-cell">
|
|
102
|
+
<a href={`/foreman_tasks/tasks/${task.id}`}>
|
|
103
|
+
{__('Details')}
|
|
104
|
+
</a>
|
|
105
|
+
</DataListCell>,
|
|
106
|
+
]}
|
|
107
|
+
/>
|
|
108
|
+
</DataListItemRow>
|
|
109
|
+
</DataListItem>
|
|
110
|
+
))}
|
|
111
|
+
</DataList>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
TaskHistory.propTypes = {
|
|
117
|
+
tasks: PropTypes.arrayOf(
|
|
118
|
+
PropTypes.shape({
|
|
119
|
+
id: PropTypes.string.isRequired,
|
|
120
|
+
label: PropTypes.string,
|
|
121
|
+
action: PropTypes.string,
|
|
122
|
+
state: PropTypes.string,
|
|
123
|
+
result: PropTypes.string,
|
|
124
|
+
progress: PropTypes.number,
|
|
125
|
+
started_at: PropTypes.string,
|
|
126
|
+
ended_at: PropTypes.string,
|
|
127
|
+
duration: PropTypes.number,
|
|
128
|
+
humanized: PropTypes.object,
|
|
129
|
+
report_file_path: PropTypes.string,
|
|
130
|
+
})
|
|
131
|
+
),
|
|
132
|
+
title: PropTypes.string,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
TaskHistory.defaultProps = {
|
|
136
|
+
tasks: [],
|
|
137
|
+
title: null,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export default TaskHistory;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './TaskHistory';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
.task-history-container {
|
|
2
|
+
margin-top: 2rem;
|
|
3
|
+
|
|
4
|
+
.task-history-list {
|
|
5
|
+
margin-top: 1rem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.task-history-icon-cell {
|
|
9
|
+
width: 40px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.task-history-time-cell {
|
|
13
|
+
flex: 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.task-history-result-cell {
|
|
17
|
+
flex: 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.task-history-duration-cell {
|
|
21
|
+
flex: 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.task-history-link-cell {
|
|
25
|
+
flex: 1;
|
|
26
|
+
text-align: right;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.task-history-icon-success {
|
|
30
|
+
color: var(--pf-global--success-color--100);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.task-history-icon-error {
|
|
34
|
+
color: var(--pf-global--danger-color--100);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.task-history-icon-warning {
|
|
38
|
+
color: var(--pf-global--warning-color--100);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import {
|
|
5
|
+
Progress,
|
|
6
|
+
ProgressVariant,
|
|
7
|
+
Card,
|
|
8
|
+
CardTitle,
|
|
9
|
+
CardBody,
|
|
10
|
+
DescriptionList,
|
|
11
|
+
DescriptionListGroup,
|
|
12
|
+
DescriptionListTerm,
|
|
13
|
+
DescriptionListDescription,
|
|
14
|
+
Button,
|
|
15
|
+
EmptyState,
|
|
16
|
+
EmptyStateIcon,
|
|
17
|
+
EmptyStateBody,
|
|
18
|
+
Title,
|
|
19
|
+
Flex,
|
|
20
|
+
FlexItem,
|
|
21
|
+
Tooltip,
|
|
22
|
+
} from '@patternfly/react-core';
|
|
23
|
+
import { ClockIcon, DownloadIcon } from '@patternfly/react-icons';
|
|
24
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
25
|
+
import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
|
|
26
|
+
import { API } from 'foremanReact/redux/API';
|
|
27
|
+
import { addToast } from 'foremanReact/components/ToastsList';
|
|
28
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
29
|
+
import { inventoryUrl } from '../../ForemanInventoryHelpers';
|
|
30
|
+
import { selectSubscriptionConnectionEnabled } from '../InventorySettings/InventorySettingsSelectors';
|
|
31
|
+
import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
|
|
32
|
+
import './taskProgress.scss';
|
|
33
|
+
|
|
34
|
+
const TaskProgress = ({
|
|
35
|
+
task,
|
|
36
|
+
title,
|
|
37
|
+
emptyMessage,
|
|
38
|
+
organizationId,
|
|
39
|
+
taskType,
|
|
40
|
+
onTaskStart,
|
|
41
|
+
}) => {
|
|
42
|
+
const dispatch = useDispatch();
|
|
43
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
44
|
+
const [lastTaskId, setLastTaskId] = useState(null);
|
|
45
|
+
|
|
46
|
+
// Clear lastTaskId when the task data updates to match it
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (lastTaskId && task?.id === lastTaskId) {
|
|
49
|
+
setLastTaskId(null);
|
|
50
|
+
}
|
|
51
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
52
|
+
}, [task?.id, lastTaskId]);
|
|
53
|
+
|
|
54
|
+
// Track when a new task starts so we can show optimistic UI
|
|
55
|
+
// Show optimistic UI if we've started a new task but haven't received the updated task data yet
|
|
56
|
+
const isStartingNewTask = lastTaskId && task?.id !== lastTaskId;
|
|
57
|
+
|
|
58
|
+
// Check IoP mode and subscription connection settings
|
|
59
|
+
const subscriptionConnectionEnabled = useSelector(
|
|
60
|
+
selectSubscriptionConnectionEnabled
|
|
61
|
+
);
|
|
62
|
+
const isIop = Boolean(useIopConfig());
|
|
63
|
+
const isUploadDisabled = !isIop && !subscriptionConnectionEnabled;
|
|
64
|
+
const uploadDisabledTooltip = __(
|
|
65
|
+
'Upload is disabled because subscription connection is not enabled. Enable it in Administer > Settings > Content.'
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Helper to render action buttons for generate tasks
|
|
69
|
+
const renderGenerateButtons = (buttonsDisabled, ouiaIdSuffix = '') => {
|
|
70
|
+
if (taskType !== 'generate' || !organizationId) return null;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<FlexItem>
|
|
75
|
+
{isUploadDisabled ? (
|
|
76
|
+
<Tooltip content={uploadDisabledTooltip}>
|
|
77
|
+
<span>
|
|
78
|
+
<Button
|
|
79
|
+
ouiaId={`generate-and-upload-disabled-button${ouiaIdSuffix}`}
|
|
80
|
+
variant="primary"
|
|
81
|
+
onClick={() => handleGenerateReport(false)}
|
|
82
|
+
isDisabled
|
|
83
|
+
>
|
|
84
|
+
{__('Generate and upload report')}
|
|
85
|
+
</Button>
|
|
86
|
+
</span>
|
|
87
|
+
</Tooltip>
|
|
88
|
+
) : (
|
|
89
|
+
<Button
|
|
90
|
+
ouiaId={`generate-and-upload-button${ouiaIdSuffix}`}
|
|
91
|
+
variant="primary"
|
|
92
|
+
onClick={() => handleGenerateReport(false)}
|
|
93
|
+
isDisabled={buttonsDisabled}
|
|
94
|
+
>
|
|
95
|
+
{__('Generate and upload report')}
|
|
96
|
+
</Button>
|
|
97
|
+
)}
|
|
98
|
+
</FlexItem>
|
|
99
|
+
<FlexItem>
|
|
100
|
+
<Button
|
|
101
|
+
ouiaId={`generate-report-button${ouiaIdSuffix}`}
|
|
102
|
+
variant="secondary"
|
|
103
|
+
onClick={() => handleGenerateReport(true)}
|
|
104
|
+
isDisabled={buttonsDisabled}
|
|
105
|
+
>
|
|
106
|
+
{__('Generate report')}
|
|
107
|
+
</Button>
|
|
108
|
+
</FlexItem>
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const handleGenerateReport = async disconnected => {
|
|
114
|
+
setIsLoading(true);
|
|
115
|
+
try {
|
|
116
|
+
const { data } = await API.post(
|
|
117
|
+
inventoryUrl(`${organizationId}/reports`),
|
|
118
|
+
{
|
|
119
|
+
disconnected,
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Validate API response
|
|
124
|
+
if (!data || !data.id) {
|
|
125
|
+
throw new Error(__('Invalid response from server'));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Update last task ID only if it's a new task
|
|
129
|
+
if (data.id !== task?.id) {
|
|
130
|
+
setLastTaskId(data.id);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Trigger immediate poll to update UI faster
|
|
134
|
+
if (onTaskStart) {
|
|
135
|
+
onTaskStart();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Use Foreman's toast notification with task link
|
|
139
|
+
const message = disconnected
|
|
140
|
+
? __('Report generation started')
|
|
141
|
+
: __('Report generation and upload started');
|
|
142
|
+
|
|
143
|
+
dispatch(
|
|
144
|
+
addToast({
|
|
145
|
+
type: 'success',
|
|
146
|
+
message,
|
|
147
|
+
link: (
|
|
148
|
+
<a href={`/foreman_tasks/tasks/${data.id}`}>{__('View task')}</a>
|
|
149
|
+
),
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
dispatch(
|
|
154
|
+
addToast({
|
|
155
|
+
sticky: true,
|
|
156
|
+
type: 'error',
|
|
157
|
+
message: error.message,
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
setIsLoading(false);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleDownloadReport = () => {
|
|
166
|
+
window.location.href = `/api/v2/organizations/${organizationId}/rh_cloud/report`;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (!task) {
|
|
170
|
+
return (
|
|
171
|
+
<EmptyState>
|
|
172
|
+
<EmptyStateIcon icon={ClockIcon} />
|
|
173
|
+
<Title headingLevel="h4" size="lg" ouiaId="task-progress-empty-title">
|
|
174
|
+
{__('No recent tasks')}
|
|
175
|
+
</Title>
|
|
176
|
+
<EmptyStateBody>
|
|
177
|
+
{emptyMessage || __('No tasks have been run yet.')}
|
|
178
|
+
</EmptyStateBody>
|
|
179
|
+
<Flex className="task-progress-actions">
|
|
180
|
+
{renderGenerateButtons(isLoading)}
|
|
181
|
+
</Flex>
|
|
182
|
+
</EmptyState>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const getProgressVariant = () => {
|
|
187
|
+
if (task.state !== 'stopped') return ProgressVariant.info;
|
|
188
|
+
if (task.result === 'success') return ProgressVariant.success;
|
|
189
|
+
if (task.result === 'error') return ProgressVariant.danger;
|
|
190
|
+
if (task.result === 'warning') return ProgressVariant.warning;
|
|
191
|
+
return ProgressVariant.info;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const getStateLabel = () => {
|
|
195
|
+
if (task.state === 'running') return __('Running');
|
|
196
|
+
if (task.state === 'paused') return __('Paused');
|
|
197
|
+
if (task.state === 'stopped') {
|
|
198
|
+
if (task.result === 'success') return __('Completed');
|
|
199
|
+
if (task.result === 'error') return __('Failed');
|
|
200
|
+
if (task.result === 'warning') return __('Completed with warnings');
|
|
201
|
+
return __('Stopped');
|
|
202
|
+
}
|
|
203
|
+
return task.state;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const formatDuration = seconds => {
|
|
207
|
+
if (
|
|
208
|
+
seconds === null ||
|
|
209
|
+
seconds === undefined ||
|
|
210
|
+
typeof seconds !== 'number' ||
|
|
211
|
+
Number.isNaN(seconds) ||
|
|
212
|
+
seconds < 0
|
|
213
|
+
) {
|
|
214
|
+
return '';
|
|
215
|
+
}
|
|
216
|
+
const mins = Math.floor(seconds / 60);
|
|
217
|
+
const secs = Math.floor(seconds % 60);
|
|
218
|
+
if (mins > 0) {
|
|
219
|
+
return `${mins}m ${secs}s`;
|
|
220
|
+
}
|
|
221
|
+
return `${secs}s`;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const isTaskRunning = task.state === 'running' || task.state === 'paused';
|
|
225
|
+
|
|
226
|
+
// Buttons should be disabled if loading, task is running, or we're waiting for new task data
|
|
227
|
+
const areButtonsDisabled = isLoading || isTaskRunning || isStartingNewTask;
|
|
228
|
+
|
|
229
|
+
// Show 100% for completed tasks, otherwise use reported progress
|
|
230
|
+
// If starting a new task, show 0% to give immediate feedback
|
|
231
|
+
let progressValue = 0;
|
|
232
|
+
if (isStartingNewTask) {
|
|
233
|
+
progressValue = 0;
|
|
234
|
+
} else if (task.state === 'stopped') {
|
|
235
|
+
progressValue = 100;
|
|
236
|
+
} else {
|
|
237
|
+
progressValue = task.progress || 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Override state label and variant when starting new task
|
|
241
|
+
const displayStateLabel = isStartingNewTask ? __('Running') : getStateLabel();
|
|
242
|
+
const displayVariant = isStartingNewTask
|
|
243
|
+
? ProgressVariant.info
|
|
244
|
+
: getProgressVariant();
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<Card className="task-progress-card" ouiaId="task-progress-card">
|
|
248
|
+
{title && <CardTitle>{title}</CardTitle>}
|
|
249
|
+
<CardBody>
|
|
250
|
+
<Progress
|
|
251
|
+
value={progressValue}
|
|
252
|
+
title={displayStateLabel}
|
|
253
|
+
variant={displayVariant}
|
|
254
|
+
measureLocation="outside"
|
|
255
|
+
aria-label="task-progress"
|
|
256
|
+
/>
|
|
257
|
+
<DescriptionList
|
|
258
|
+
isHorizontal
|
|
259
|
+
className="task-progress-details"
|
|
260
|
+
ouiaId="task-progress-details"
|
|
261
|
+
>
|
|
262
|
+
<DescriptionListGroup>
|
|
263
|
+
<DescriptionListTerm>{__('Started')}</DescriptionListTerm>
|
|
264
|
+
<DescriptionListDescription>
|
|
265
|
+
{!isStartingNewTask && task.started_at && (
|
|
266
|
+
<RelativeDateTime date={task.started_at} />
|
|
267
|
+
)}
|
|
268
|
+
</DescriptionListDescription>
|
|
269
|
+
</DescriptionListGroup>
|
|
270
|
+
<DescriptionListGroup>
|
|
271
|
+
<DescriptionListTerm>{__('Duration')}</DescriptionListTerm>
|
|
272
|
+
<DescriptionListDescription>
|
|
273
|
+
{!isStartingNewTask && formatDuration(task.duration)}
|
|
274
|
+
</DescriptionListDescription>
|
|
275
|
+
</DescriptionListGroup>
|
|
276
|
+
<DescriptionListGroup>
|
|
277
|
+
<DescriptionListTerm>{__('Report saved to')}</DescriptionListTerm>
|
|
278
|
+
<DescriptionListDescription>
|
|
279
|
+
{!isStartingNewTask && task.report_file_path}
|
|
280
|
+
</DescriptionListDescription>
|
|
281
|
+
</DescriptionListGroup>
|
|
282
|
+
</DescriptionList>
|
|
283
|
+
<Flex className="task-progress-actions">
|
|
284
|
+
<FlexItem>
|
|
285
|
+
<Button
|
|
286
|
+
ouiaId="view-task-details-button"
|
|
287
|
+
component="a"
|
|
288
|
+
href={`/foreman_tasks/tasks/${task.id}`}
|
|
289
|
+
variant="link"
|
|
290
|
+
isInline
|
|
291
|
+
>
|
|
292
|
+
{__('View task details')}
|
|
293
|
+
</Button>
|
|
294
|
+
</FlexItem>
|
|
295
|
+
<FlexItem>
|
|
296
|
+
<Button
|
|
297
|
+
ouiaId="download-report-button"
|
|
298
|
+
variant="secondary"
|
|
299
|
+
onClick={handleDownloadReport}
|
|
300
|
+
icon={<DownloadIcon />}
|
|
301
|
+
isDisabled={!task.report_file_path || areButtonsDisabled}
|
|
302
|
+
>
|
|
303
|
+
{__('Download report')}
|
|
304
|
+
</Button>
|
|
305
|
+
</FlexItem>
|
|
306
|
+
{renderGenerateButtons(areButtonsDisabled, '-task-view')}
|
|
307
|
+
</Flex>
|
|
308
|
+
</CardBody>
|
|
309
|
+
</Card>
|
|
310
|
+
);
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
TaskProgress.propTypes = {
|
|
314
|
+
task: PropTypes.shape({
|
|
315
|
+
id: PropTypes.string.isRequired,
|
|
316
|
+
state: PropTypes.string.isRequired,
|
|
317
|
+
result: PropTypes.string,
|
|
318
|
+
progress: PropTypes.number,
|
|
319
|
+
started_at: PropTypes.string,
|
|
320
|
+
ended_at: PropTypes.string,
|
|
321
|
+
duration: PropTypes.number,
|
|
322
|
+
report_file_path: PropTypes.string,
|
|
323
|
+
}),
|
|
324
|
+
title: PropTypes.string,
|
|
325
|
+
emptyMessage: PropTypes.string,
|
|
326
|
+
organizationId: PropTypes.number,
|
|
327
|
+
taskType: PropTypes.oneOf(['generate', 'upload']),
|
|
328
|
+
onTaskStart: PropTypes.func,
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
TaskProgress.defaultProps = {
|
|
332
|
+
task: null,
|
|
333
|
+
title: null,
|
|
334
|
+
emptyMessage: null,
|
|
335
|
+
organizationId: null,
|
|
336
|
+
taskType: null,
|
|
337
|
+
onTaskStart: null,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export default TaskProgress;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './TaskProgress';
|
|
@@ -7,13 +7,13 @@ export const inventoryUrl = path =>
|
|
|
7
7
|
export const getInventoryDocsUrl = () =>
|
|
8
8
|
foremanUrl(
|
|
9
9
|
`/links/manual/?root_url=${URI.encode(
|
|
10
|
-
'https://
|
|
10
|
+
'https://docs.redhat.com/en/documentation/red_hat_lightspeed/1-latest/html-single/red_hat_lightspeed_remediations_guide/index'
|
|
11
11
|
)}`
|
|
12
12
|
);
|
|
13
13
|
|
|
14
14
|
export const getActionsHistoryUrl = () =>
|
|
15
15
|
foremanUrl(
|
|
16
|
-
'/foreman_tasks/tasks?search=label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%
|
|
16
|
+
'/foreman_tasks/tasks?search=label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AHostInventoryReportJob+or+label+%3D+ForemanInventoryUpload%3A%3AAsync%3A%3AGenerateAllReportsJob&page=1'
|
|
17
17
|
);
|
|
18
18
|
|
|
19
19
|
export const isExitCodeLoading = exitCode => {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { combineReducers } from 'redux';
|
|
2
2
|
import { reducers as accountListReducers } from './Components/AccountList';
|
|
3
|
-
import { reducers as dashboardReducers } from './Components/Dashboard';
|
|
4
3
|
import { reducers as filterReducers } from './Components/InventoryFilter';
|
|
5
4
|
|
|
6
5
|
export default {
|
|
7
6
|
inventoryUpload: combineReducers({
|
|
8
7
|
...accountListReducers,
|
|
9
|
-
...dashboardReducers,
|
|
10
8
|
...filterReducers,
|
|
11
9
|
}),
|
|
12
10
|
};
|
data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`ForemanInventoryUpload helpers should return inventory Url 1`] = `"/foreman_inventory_upload/test_path"`;
|
|
4
4
|
|
|
5
|
-
exports[`ForemanInventoryUpload helpers should return inventory docs url 1`] = `"/links/manual/?root_url=https%3A%2F%
|
|
5
|
+
exports[`ForemanInventoryUpload helpers should return inventory docs url 1`] = `"/links/manual/?root_url=https%3A%2F%2Fdocs.redhat.com%2Fen%2Fdocumentation%2Fred_hat_lightspeed%2F1-latest%2Fhtml-single%2Fred_hat_lightspeed_remediations_guide%2Findex"`;
|
|
@@ -1,26 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { rtlHelpers } from 'foremanReact/common/rtlTestHelpers';
|
|
3
3
|
import InsightsTable from '../InsightsTable';
|
|
4
4
|
import { tableProps } from './fixtures';
|
|
5
5
|
|
|
6
|
-
jest.mock('
|
|
7
|
-
useForemanContext: () => ({
|
|
8
|
-
metadata: {
|
|
9
|
-
foreman_rh_cloud: {
|
|
10
|
-
iop: true,
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
}),
|
|
14
|
-
useForemanSettings: () => ({
|
|
15
|
-
perPage: 20,
|
|
16
|
-
}),
|
|
17
|
-
}));
|
|
6
|
+
jest.mock('../../../../common/Hooks/ConfigHooks');
|
|
18
7
|
|
|
19
|
-
const
|
|
20
|
-
'render with Props': tableProps,
|
|
21
|
-
};
|
|
8
|
+
const { renderWithStore } = rtlHelpers;
|
|
22
9
|
|
|
23
10
|
describe('InsightsTable', () => {
|
|
24
|
-
|
|
25
|
-
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('renders without crashing', () => {
|
|
16
|
+
renderWithStore(<InsightsTable {...tableProps} />);
|
|
17
|
+
});
|
|
26
18
|
});
|
|
@@ -25,11 +25,11 @@ export const modifyRows = (
|
|
|
25
25
|
).map(({ id, host_id, hostname, title, resolutions, reboot }) => {
|
|
26
26
|
hostsIdsToSubmit.add(host_id);
|
|
27
27
|
const selectedResolution = resolutions[0]?.id;
|
|
28
|
-
/* eslint-disable spellcheck/spell-checker */
|
|
29
28
|
|
|
30
29
|
// For IoP:
|
|
31
30
|
// All of the values will be plain strings
|
|
32
31
|
// {
|
|
32
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
33
33
|
// hit_id: "c7c6727e-2966-4f7c-87f1-20ef14db7a2d", <-- this refers to a host by insights ID
|
|
34
34
|
// rule_id: "hardening_ssh_client_alive|OPENSSH_HARDENING_CLIENT_ALIVE",
|
|
35
35
|
// resolution_type: "less_secure",
|
|
@@ -41,7 +41,6 @@ export const modifyRows = (
|
|
|
41
41
|
// rule_id refers to an InsightsRule
|
|
42
42
|
// resolution_type and resolution_id both refer to an InsightsResolution (InsightsHit.find(xx).rule.resolutions)
|
|
43
43
|
|
|
44
|
-
/* eslint-enable spellcheck/spell-checker */
|
|
45
44
|
resolutionToSubmit.push({
|
|
46
45
|
hit_id: isIop ? host_id : id,
|
|
47
46
|
rule_id: id,
|
|
@@ -18,11 +18,10 @@ import TableEmptyState from '../../../common/table/EmptyState';
|
|
|
18
18
|
import './RemediationModal.scss';
|
|
19
19
|
import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
|
|
20
20
|
|
|
21
|
-
/* eslint-disable spellcheck/spell-checker */
|
|
22
|
-
|
|
23
21
|
// Sample iopData:
|
|
24
22
|
// const iopTestData = Immutable([
|
|
25
23
|
// {
|
|
24
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
26
25
|
// hostid: 'c7c6727e-2966-4f7c-87f1-20ef14db7a2d',
|
|
27
26
|
// host_name: 'advisor-test.local',
|
|
28
27
|
// rulename: 'hardening_cryptopol_krb5|NO_CPOL_KRB5',
|
|
@@ -38,6 +37,7 @@ import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
|
|
|
38
37
|
// description: 'Decreased security: krb5 crypto-policies overridden',
|
|
39
38
|
// },
|
|
40
39
|
// {
|
|
40
|
+
// eslint-disable-next-line spellcheck/spell-checker
|
|
41
41
|
// hostid: 'c7c6727e-2966-4f7c-87f1-20ef14db7a2d',
|
|
42
42
|
// host_name: 'advisor-test.local',
|
|
43
43
|
// rulename: 'hardening_logging_auditd|HARDENING_LOGGING_5_AUDITD',
|
|
@@ -54,8 +54,6 @@ import { useIopConfig } from '../../../common/Hooks/ConfigHooks';
|
|
|
54
54
|
// },
|
|
55
55
|
// ]);
|
|
56
56
|
|
|
57
|
-
/* eslint-enable spellcheck/spell-checker */
|
|
58
|
-
|
|
59
57
|
const RemediationModal = ({
|
|
60
58
|
iopData,
|
|
61
59
|
selectedIds,
|