mirror of
https://github.com/ansible/awx.git
synced 2026-03-26 13:25:02 -02:30
JobOutput: extract JobOutputSearch bar
This commit is contained in:
@@ -12,39 +12,25 @@ import {
|
|||||||
import Ansi from 'ansi-to-html';
|
import Ansi from 'ansi-to-html';
|
||||||
import hasAnsi from 'has-ansi';
|
import hasAnsi from 'has-ansi';
|
||||||
import { encode } from 'html-entities';
|
import { encode } from 'html-entities';
|
||||||
import {
|
import { Button } from '@patternfly/react-core';
|
||||||
Button,
|
|
||||||
Toolbar,
|
|
||||||
ToolbarContent,
|
|
||||||
ToolbarItem,
|
|
||||||
ToolbarToggleGroup,
|
|
||||||
Tooltip,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
import { SearchIcon } from '@patternfly/react-icons';
|
|
||||||
|
|
||||||
import AlertModal from 'components/AlertModal';
|
import AlertModal from 'components/AlertModal';
|
||||||
import { CardBody as _CardBody } from 'components/Card';
|
import { CardBody as _CardBody } from 'components/Card';
|
||||||
import ContentError from 'components/ContentError';
|
import ContentError from 'components/ContentError';
|
||||||
import ContentLoading from 'components/ContentLoading';
|
import ContentLoading from 'components/ContentLoading';
|
||||||
import ErrorDetail from 'components/ErrorDetail';
|
import ErrorDetail from 'components/ErrorDetail';
|
||||||
import Search from 'components/Search';
|
|
||||||
import StatusIcon from 'components/StatusIcon';
|
import StatusIcon from 'components/StatusIcon';
|
||||||
|
|
||||||
import { getJobModel, isJobRunning } from 'util/jobs';
|
import { getJobModel, isJobRunning } from 'util/jobs';
|
||||||
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
||||||
import useInterval from 'hooks/useInterval';
|
import useInterval from 'hooks/useInterval';
|
||||||
import {
|
import { parseQueryString, getQSConfig } from 'util/qs';
|
||||||
parseQueryString,
|
|
||||||
mergeParams,
|
|
||||||
removeParams,
|
|
||||||
getQSConfig,
|
|
||||||
updateQueryString,
|
|
||||||
} from 'util/qs';
|
|
||||||
import useIsMounted from 'hooks/useIsMounted';
|
import useIsMounted from 'hooks/useIsMounted';
|
||||||
import JobEvent from './JobEvent';
|
import JobEvent from './JobEvent';
|
||||||
import JobEventSkeleton from './JobEventSkeleton';
|
import JobEventSkeleton from './JobEventSkeleton';
|
||||||
import PageControls from './PageControls';
|
import PageControls from './PageControls';
|
||||||
import HostEventModal from './HostEventModal';
|
import HostEventModal from './HostEventModal';
|
||||||
|
import JobOutputSearch from './JobOutputSearch';
|
||||||
import { HostStatusBar, OutputToolbar } from './shared';
|
import { HostStatusBar, OutputToolbar } from './shared';
|
||||||
import getRowRangePageSize from './shared/jobOutputUtils';
|
import getRowRangePageSize from './shared/jobOutputUtils';
|
||||||
|
|
||||||
@@ -192,15 +178,6 @@ const OutputFooter = styled.div`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SearchToolbar = styled(Toolbar)`
|
|
||||||
position: inherit !important;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SearchToolbarContent = styled(ToolbarContent)`
|
|
||||||
padding-left: 0px !important;
|
|
||||||
padding-right: 0px !important;
|
|
||||||
`;
|
|
||||||
|
|
||||||
let ws;
|
let ws;
|
||||||
function connectJobSocket({ type, id }, onMessage) {
|
function connectJobSocket({ type, id }, onMessage) {
|
||||||
ws = new WebSocket(
|
ws = new WebSocket(
|
||||||
@@ -665,55 +642,6 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
previousWidth.current = width;
|
previousWidth.current = width;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = (key, value) => {
|
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
|
||||||
const qs = updateQueryString(
|
|
||||||
QS_CONFIG,
|
|
||||||
location.search,
|
|
||||||
mergeParams(params, { [key]: value })
|
|
||||||
);
|
|
||||||
pushHistoryState(qs);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleReplaceSearch = (key, value) => {
|
|
||||||
const qs = updateQueryString(QS_CONFIG, location.search, {
|
|
||||||
[key]: value,
|
|
||||||
});
|
|
||||||
pushHistoryState(qs);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveSearchTerm = (key, value) => {
|
|
||||||
const oldParams = parseQueryString(QS_CONFIG, location.search);
|
|
||||||
const updatedParams = removeParams(QS_CONFIG, oldParams, {
|
|
||||||
[key]: value,
|
|
||||||
});
|
|
||||||
const qs = updateQueryString(QS_CONFIG, location.search, updatedParams);
|
|
||||||
pushHistoryState(qs);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemoveAllSearchTerms = () => {
|
|
||||||
const oldParams = parseQueryString(QS_CONFIG, location.search);
|
|
||||||
Object.keys(oldParams).forEach((key) => {
|
|
||||||
oldParams[key] = null;
|
|
||||||
});
|
|
||||||
const qs = updateQueryString(QS_CONFIG, location.search, oldParams);
|
|
||||||
pushHistoryState(qs);
|
|
||||||
};
|
|
||||||
|
|
||||||
const pushHistoryState = (qs) => {
|
|
||||||
const { pathname } = history.location;
|
|
||||||
history.push(qs ? `${pathname}?${qs}` : pathname);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFollowToggle = () => {
|
|
||||||
if (isFollowModeEnabled) {
|
|
||||||
setIsFollowModeEnabled(false);
|
|
||||||
} else {
|
|
||||||
setIsFollowModeEnabled(true);
|
|
||||||
scrollToRow(remoteRowCount - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleScroll = (e) => {
|
const handleScroll = (e) => {
|
||||||
if (
|
if (
|
||||||
isFollowModeEnabled &&
|
isFollowModeEnabled &&
|
||||||
@@ -726,64 +654,6 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
scrollHeight.current = e.scrollHeight;
|
scrollHeight.current = e.scrollHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSearchComponent = () => (
|
|
||||||
<Search
|
|
||||||
qsConfig={QS_CONFIG}
|
|
||||||
columns={[
|
|
||||||
{
|
|
||||||
name: t`Stdout`,
|
|
||||||
key: 'stdout__icontains',
|
|
||||||
isDefault: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: t`Event`,
|
|
||||||
key: 'event',
|
|
||||||
options: [
|
|
||||||
['runner_on_failed', t`Host Failed`],
|
|
||||||
['runner_on_start', t`Host Started`],
|
|
||||||
['runner_on_ok', t`Host OK`],
|
|
||||||
['runner_on_error', t`Host Failure`],
|
|
||||||
['runner_on_skipped', t`Host Skipped`],
|
|
||||||
['runner_on_unreachable', t`Host Unreachable`],
|
|
||||||
['runner_on_no_hosts', t`No Hosts Remaining`],
|
|
||||||
['runner_on_async_poll', t`Host Polling`],
|
|
||||||
['runner_on_async_ok', t`Host Async OK`],
|
|
||||||
['runner_on_async_failed', t`Host Async Failure`],
|
|
||||||
['runner_item_on_ok', t`Item OK`],
|
|
||||||
['runner_item_on_failed', t`Item Failed`],
|
|
||||||
['runner_item_on_skipped', t`Item Skipped`],
|
|
||||||
['runner_retry', t`Host Retry`],
|
|
||||||
['runner_on_file_diff', t`File Difference`],
|
|
||||||
['playbook_on_start', t`Playbook Started`],
|
|
||||||
['playbook_on_notify', t`Running Handlers`],
|
|
||||||
['playbook_on_include', t`Including File`],
|
|
||||||
['playbook_on_no_hosts_matched', t`No Hosts Matched`],
|
|
||||||
['playbook_on_no_hosts_remaining', t`No Hosts Remaining`],
|
|
||||||
['playbook_on_task_start', t`Task Started`],
|
|
||||||
['playbook_on_vars_prompt', t`Variables Prompted`],
|
|
||||||
['playbook_on_setup', t`Gathering Facts`],
|
|
||||||
['playbook_on_play_start', t`Play Started`],
|
|
||||||
['playbook_on_stats', t`Playbook Complete`],
|
|
||||||
['debug', t`Debug`],
|
|
||||||
['verbose', t`Verbose`],
|
|
||||||
['deprecated', t`Deprecated`],
|
|
||||||
['warning', t`Warning`],
|
|
||||||
['system_warning', t`System Warning`],
|
|
||||||
['error', t`Error`],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ name: t`Advanced`, key: 'advanced' },
|
|
||||||
]}
|
|
||||||
searchableKeys={eventSearchableKeys}
|
|
||||||
relatedSearchableKeys={eventRelatedSearchableKeys}
|
|
||||||
onSearch={handleSearch}
|
|
||||||
onReplaceSearch={handleReplaceSearch}
|
|
||||||
onShowAdvancedSearch={() => {}}
|
|
||||||
onRemove={handleRemoveSearchTerm}
|
|
||||||
isDisabled={isJobRunning(job.status)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (contentError) {
|
if (contentError) {
|
||||||
return <ContentError error={contentError} />;
|
return <ContentError error={contentError} />;
|
||||||
}
|
}
|
||||||
@@ -812,36 +682,16 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
/>
|
/>
|
||||||
</OutputHeader>
|
</OutputHeader>
|
||||||
<HostStatusBar counts={job.host_status_counts} />
|
<HostStatusBar counts={job.host_status_counts} />
|
||||||
<SearchToolbar
|
<JobOutputSearch
|
||||||
id="job_output-toolbar"
|
qsConfig={QS_CONFIG}
|
||||||
clearAllFilters={handleRemoveAllSearchTerms}
|
job={job}
|
||||||
collapseListedFiltersBreakpoint="lg"
|
eventRelatedSearchableKeys={eventRelatedSearchableKeys}
|
||||||
clearFiltersButtonText={t`Clear all filters`}
|
eventSearchableKeys={eventSearchableKeys}
|
||||||
>
|
remoteRowCount={remoteRowCount}
|
||||||
<SearchToolbarContent>
|
scrollToRow={scrollToRow}
|
||||||
<ToolbarToggleGroup toggleIcon={<SearchIcon />} breakpoint="lg">
|
isFollowModeEnabled={isFollowModeEnabled}
|
||||||
<ToolbarItem variant="search-filter">
|
setIsFollowModeEnabled={setIsFollowModeEnabled}
|
||||||
{isJobRunning(job.status) ? (
|
/>
|
||||||
<Tooltip
|
|
||||||
content={t`Search is disabled while the job is running`}
|
|
||||||
>
|
|
||||||
{renderSearchComponent()}
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
renderSearchComponent()
|
|
||||||
)}
|
|
||||||
</ToolbarItem>
|
|
||||||
</ToolbarToggleGroup>
|
|
||||||
{isJobRunning(job.status) ? (
|
|
||||||
<Button
|
|
||||||
variant={isFollowModeEnabled ? 'secondary' : 'primary'}
|
|
||||||
onClick={handleFollowToggle}
|
|
||||||
>
|
|
||||||
{isFollowModeEnabled ? t`Unfollow` : t`Follow`}
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</SearchToolbarContent>
|
|
||||||
</SearchToolbar>
|
|
||||||
<PageControls
|
<PageControls
|
||||||
onScrollFirst={handleScrollFirst}
|
onScrollFirst={handleScrollFirst}
|
||||||
onScrollLast={handleScrollLast}
|
onScrollLast={handleScrollLast}
|
||||||
@@ -946,5 +796,4 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { JobOutput as _JobOutput };
|
|
||||||
export default JobOutput;
|
export default JobOutput;
|
||||||
|
|||||||
188
awx/ui/src/screens/Job/JobOutput/JobOutputSearch.js
Normal file
188
awx/ui/src/screens/Job/JobOutput/JobOutputSearch.js
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import {
|
||||||
|
Toolbar,
|
||||||
|
ToolbarContent,
|
||||||
|
ToolbarItem,
|
||||||
|
ToolbarToggleGroup,
|
||||||
|
Tooltip,
|
||||||
|
Button,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { SearchIcon } from '@patternfly/react-icons';
|
||||||
|
import Search from 'components/Search';
|
||||||
|
import {
|
||||||
|
parseQueryString,
|
||||||
|
mergeParams,
|
||||||
|
removeParams,
|
||||||
|
updateQueryString,
|
||||||
|
} from 'util/qs';
|
||||||
|
import { isJobRunning } from 'util/jobs';
|
||||||
|
|
||||||
|
const SearchToolbarContent = styled(ToolbarContent)`
|
||||||
|
padding-left: 0px !important;
|
||||||
|
padding-right: 0px !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function JobOutputSearch({
|
||||||
|
qsConfig,
|
||||||
|
job,
|
||||||
|
eventRelatedSearchableKeys,
|
||||||
|
eventSearchableKeys,
|
||||||
|
remoteRowCount,
|
||||||
|
scrollToRow,
|
||||||
|
isFollowModeEnabled,
|
||||||
|
setIsFollowModeEnabled,
|
||||||
|
}) {
|
||||||
|
const location = useLocation();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleSearch = (key, value) => {
|
||||||
|
const params = parseQueryString(qsConfig, location.search);
|
||||||
|
const qs = updateQueryString(
|
||||||
|
qsConfig,
|
||||||
|
location.search,
|
||||||
|
mergeParams(params, { [key]: value })
|
||||||
|
);
|
||||||
|
pushHistoryState(qs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReplaceSearch = (key, value) => {
|
||||||
|
const qs = updateQueryString(qsConfig, location.search, {
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
pushHistoryState(qs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveSearchTerm = (key, value) => {
|
||||||
|
const oldParams = parseQueryString(qsConfig, location.search);
|
||||||
|
const updatedParams = removeParams(qsConfig, oldParams, {
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
const qs = updateQueryString(qsConfig, location.search, updatedParams);
|
||||||
|
pushHistoryState(qs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveAllSearchTerms = () => {
|
||||||
|
const oldParams = parseQueryString(qsConfig, location.search);
|
||||||
|
Object.keys(oldParams).forEach((key) => {
|
||||||
|
oldParams[key] = null;
|
||||||
|
});
|
||||||
|
const qs = updateQueryString(qsConfig, location.search, oldParams);
|
||||||
|
pushHistoryState(qs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pushHistoryState = (qs) => {
|
||||||
|
const { pathname } = history.location;
|
||||||
|
history.push(qs ? `${pathname}?${qs}` : pathname);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFollowToggle = () => {
|
||||||
|
if (isFollowModeEnabled) {
|
||||||
|
setIsFollowModeEnabled(false);
|
||||||
|
} else {
|
||||||
|
setIsFollowModeEnabled(true);
|
||||||
|
scrollToRow(remoteRowCount - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: t`Stdout`,
|
||||||
|
key: 'stdout__icontains',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t`Event`,
|
||||||
|
key: 'event',
|
||||||
|
options: [
|
||||||
|
['runner_on_failed', t`Host Failed`],
|
||||||
|
['runner_on_start', t`Host Started`],
|
||||||
|
['runner_on_ok', t`Host OK`],
|
||||||
|
['runner_on_error', t`Host Failure`],
|
||||||
|
['runner_on_skipped', t`Host Skipped`],
|
||||||
|
['runner_on_unreachable', t`Host Unreachable`],
|
||||||
|
['runner_on_no_hosts', t`No Hosts Remaining`],
|
||||||
|
['runner_on_async_poll', t`Host Polling`],
|
||||||
|
['runner_on_async_ok', t`Host Async OK`],
|
||||||
|
['runner_on_async_failed', t`Host Async Failure`],
|
||||||
|
['runner_item_on_ok', t`Item OK`],
|
||||||
|
['runner_item_on_failed', t`Item Failed`],
|
||||||
|
['runner_item_on_skipped', t`Item Skipped`],
|
||||||
|
['runner_retry', t`Host Retry`],
|
||||||
|
['runner_on_file_diff', t`File Difference`],
|
||||||
|
['playbook_on_start', t`Playbook Started`],
|
||||||
|
['playbook_on_notify', t`Running Handlers`],
|
||||||
|
['playbook_on_include', t`Including File`],
|
||||||
|
['playbook_on_no_hosts_matched', t`No Hosts Matched`],
|
||||||
|
['playbook_on_no_hosts_remaining', t`No Hosts Remaining`],
|
||||||
|
['playbook_on_task_start', t`Task Started`],
|
||||||
|
['playbook_on_vars_prompt', t`Variables Prompted`],
|
||||||
|
['playbook_on_setup', t`Gathering Facts`],
|
||||||
|
['playbook_on_play_start', t`Play Started`],
|
||||||
|
['playbook_on_stats', t`Playbook Complete`],
|
||||||
|
['debug', t`Debug`],
|
||||||
|
['verbose', t`Verbose`],
|
||||||
|
['deprecated', t`Deprecated`],
|
||||||
|
['warning', t`Warning`],
|
||||||
|
['system_warning', t`System Warning`],
|
||||||
|
['error', t`Error`],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ name: t`Advanced`, key: 'advanced' },
|
||||||
|
];
|
||||||
|
const isDisabled = isJobRunning(job.status);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Toolbar
|
||||||
|
id="job_output-toolbar"
|
||||||
|
clearAllFilters={handleRemoveAllSearchTerms}
|
||||||
|
collapseListedFiltersBreakpoint="lg"
|
||||||
|
clearFiltersButtonText={t`Clear all filters`}
|
||||||
|
>
|
||||||
|
<SearchToolbarContent>
|
||||||
|
<ToolbarToggleGroup toggleIcon={<SearchIcon />} breakpoint="lg">
|
||||||
|
<ToolbarItem variant="search-filter">
|
||||||
|
{isDisabled ? (
|
||||||
|
<Tooltip content={t`Search is disabled while the job is running`}>
|
||||||
|
<Search
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
columns={columns}
|
||||||
|
searchableKeys={eventSearchableKeys}
|
||||||
|
relatedSearchableKeys={eventRelatedSearchableKeys}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
onReplaceSearch={handleReplaceSearch}
|
||||||
|
onShowAdvancedSearch={() => {}}
|
||||||
|
onRemove={handleRemoveSearchTerm}
|
||||||
|
isDisabled
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Search
|
||||||
|
qsConfig={qsConfig}
|
||||||
|
columns={columns}
|
||||||
|
searchableKeys={eventSearchableKeys}
|
||||||
|
relatedSearchableKeys={eventRelatedSearchableKeys}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
onReplaceSearch={handleReplaceSearch}
|
||||||
|
onShowAdvancedSearch={() => {}}
|
||||||
|
onRemove={handleRemoveSearchTerm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ToolbarItem>
|
||||||
|
</ToolbarToggleGroup>
|
||||||
|
{isJobRunning(job.status) ? (
|
||||||
|
<Button
|
||||||
|
variant={isFollowModeEnabled ? 'secondary' : 'primary'}
|
||||||
|
onClick={handleFollowToggle}
|
||||||
|
>
|
||||||
|
{isFollowModeEnabled ? t`Unfollow` : t`Follow`}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</SearchToolbarContent>
|
||||||
|
</Toolbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobOutputSearch;
|
||||||
@@ -2,7 +2,7 @@ import 'styled-components/macro';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button as PFButton } from '@patternfly/react-core';
|
import { Button } from '@patternfly/react-core';
|
||||||
import {
|
import {
|
||||||
AngleDoubleUpIcon,
|
AngleDoubleUpIcon,
|
||||||
AngleDoubleDownIcon,
|
AngleDoubleDownIcon,
|
||||||
@@ -14,16 +14,11 @@ import styled from 'styled-components';
|
|||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
outline: 1px solid #d7d7d7;
|
border: 1px solid #d7d7d7;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Button = styled(PFButton)`
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PageControls = ({
|
const PageControls = ({
|
||||||
onScrollFirst,
|
onScrollFirst,
|
||||||
onScrollLast,
|
onScrollLast,
|
||||||
|
|||||||
Reference in New Issue
Block a user