mirror of
https://github.com/ansible/awx.git
synced 2026-02-26 07:26:03 -03:30
Add possibility to select and delete HostMetrics
This commit is contained in:
committed by
John Westcott IV
parent
9f3c4f6240
commit
179868dff2
@@ -5,13 +5,16 @@ import { HostMetricsAPI } from 'api';
|
|||||||
import useRequest from 'hooks/useRequest';
|
import useRequest from 'hooks/useRequest';
|
||||||
import PaginatedTable, {
|
import PaginatedTable, {
|
||||||
HeaderRow,
|
HeaderRow,
|
||||||
HeaderCell
|
HeaderCell,
|
||||||
} from 'components/PaginatedTable';
|
} from 'components/PaginatedTable';
|
||||||
import DataListToolbar from 'components/DataListToolbar';
|
import DataListToolbar from 'components/DataListToolbar';
|
||||||
import { getQSConfig, parseQueryString } from 'util/qs';
|
import { getQSConfig, parseQueryString } from 'util/qs';
|
||||||
import {Card, PageSection} from "@patternfly/react-core";
|
import {Card, PageSection} from "@patternfly/react-core";
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import HostMetricsListItem from "./HostMetricsListItem";
|
import HostMetricsListItem from "./HostMetricsListItem";
|
||||||
|
import HostMetricsDeleteButton from "./HostMetricsDeleteButton";
|
||||||
|
import useSelected from 'hooks/useSelected';
|
||||||
|
|
||||||
|
|
||||||
function HostMetrics() {
|
function HostMetrics() {
|
||||||
|
|
||||||
@@ -46,40 +49,91 @@ function HostMetrics() {
|
|||||||
readHostMetrics();
|
readHostMetrics();
|
||||||
}, [readHostMetrics]);
|
}, [readHostMetrics]);
|
||||||
|
|
||||||
|
const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
|
||||||
|
useSelected(results);
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
<ScreenHeader
|
<ScreenHeader
|
||||||
streamType="none"
|
streamType="none"
|
||||||
breadcrumbConfig={breadcrumbConfig}
|
breadcrumbConfig={breadcrumbConfig}
|
||||||
/>
|
/>
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Card>
|
<Card>
|
||||||
<PaginatedTable
|
<PaginatedTable
|
||||||
contentError={error}
|
contentError={error}
|
||||||
hasContentLoading={isLoading}
|
hasContentLoading={isLoading}
|
||||||
items={results}
|
items={results}
|
||||||
itemCount={count}
|
itemCount={count}
|
||||||
pluralizedItemName={t`Host Metrics`}
|
pluralizedItemName={t`Host Metrics`}
|
||||||
renderRow={(item)=> (<HostMetricsListItem item={item} />)}
|
renderRow={(item, index)=> (
|
||||||
qsConfig={QS_CONFIG}
|
<HostMetricsListItem
|
||||||
toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]}
|
item={item}
|
||||||
toolbarSearchableKeys={[]}
|
isSelected={selected.some((row) => row.hostname === item.hostname)}
|
||||||
toolbarRelatedSearchableKeys={[]}
|
onSelect={() => handleSelect(item)}
|
||||||
renderToolbar={(props) => <DataListToolbar {...props} advancedSearchDisabled={true} fillWidth}
|
rowIndex={index}
|
||||||
headerRow={
|
/>
|
||||||
<HeaderRow qsConfig={QS_CONFIG}>
|
)}
|
||||||
<HeaderCell sortKey="hostname">{t`Hostname`}</HeaderCell>
|
qsConfig={QS_CONFIG}
|
||||||
<HeaderCell sortKey="first_automation" tooltip={t`When was the host first automated`}>{t`First automated`}</HeaderCell>
|
toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]}
|
||||||
<HeaderCell sortKey="last_automation" tooltip={t`When was the host last automated`}>{t`Last automated`}</HeaderCell>
|
toolbarSearchableKeys={[]}
|
||||||
<HeaderCell sortKey="automated_counter" tooltip={t`How many times was the host automated`}>{t`Automation`}</HeaderCell>
|
toolbarRelatedSearchableKeys={[]}
|
||||||
<HeaderCell sortKey="used_in_inventories" tooltip={t`How many inventories is the host in, recomputed on a weekly schedule`}>{t`Inventories`}</HeaderCell>
|
renderToolbar={(props) =>
|
||||||
<HeaderCell sortKey="deleted_counter" tooltip={t`How many times was the host deleted`}>{t`Deleted`}</HeaderCell>
|
<DataListToolbar
|
||||||
</HeaderRow>
|
{...props}
|
||||||
}
|
advancedSearchDisabled={true}
|
||||||
/>
|
fillWidth
|
||||||
</Card>
|
isAllSelected={isAllSelected}
|
||||||
</PageSection>
|
onSelectAll={selectAll}
|
||||||
</>
|
additionalControls={[
|
||||||
|
<HostMetricsDeleteButton
|
||||||
|
key="delete"
|
||||||
|
onDelete={() =>
|
||||||
|
Promise.all(selected.map((hostMetric) =>
|
||||||
|
HostMetricsAPI.destroy(hostMetric.id)))
|
||||||
|
.then(() => { readHostMetrics(); clearSelected(); })
|
||||||
|
}
|
||||||
|
itemsToDelete={selected}
|
||||||
|
pluralizedItemName={t`Host Metrics`}
|
||||||
|
/>]}
|
||||||
|
/>}
|
||||||
|
headerRow={
|
||||||
|
<HeaderRow qsConfig={QS_CONFIG}>
|
||||||
|
<HeaderCell
|
||||||
|
sortKey="hostname">
|
||||||
|
{t`Hostname`}
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell
|
||||||
|
sortKey="first_automation"
|
||||||
|
tooltip={t`When was the host first automated`}>
|
||||||
|
{t`First automated`}
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell
|
||||||
|
sortKey="last_automation"
|
||||||
|
tooltip={t`When was the host last automated`}>
|
||||||
|
{t`Last automated`}
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell
|
||||||
|
sortKey="automated_counter"
|
||||||
|
tooltip={t`How many times was the host automated`}>
|
||||||
|
{t`Automation`}
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell
|
||||||
|
sortKey="used_in_inventories"
|
||||||
|
tooltip={t`How many inventories is the host in, recomputed on a weekly schedule`}>
|
||||||
|
{t`Inventories`}
|
||||||
|
</HeaderCell>
|
||||||
|
<HeaderCell
|
||||||
|
sortKey="deleted_counter"
|
||||||
|
tooltip={t`How many times was the host deleted`}>
|
||||||
|
{t`Deleted`}
|
||||||
|
</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
220
awx/ui/src/screens/HostMetrics/HostMetricsDeleteButton.js
Normal file
220
awx/ui/src/screens/HostMetrics/HostMetricsDeleteButton.js
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
func,
|
||||||
|
node,
|
||||||
|
string,
|
||||||
|
arrayOf,
|
||||||
|
shape,
|
||||||
|
} from 'prop-types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { getRelatedResourceDeleteCounts } from 'util/getRelatedResourceDeleteDetails';
|
||||||
|
import AlertModal from '../../components/AlertModal';
|
||||||
|
|
||||||
|
import ErrorDetail from '../../components/ErrorDetail';
|
||||||
|
|
||||||
|
const WarningMessage = styled(Alert)`
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Label = styled.span`
|
||||||
|
&& {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ItemToDelete = shape({
|
||||||
|
hostname: string.isRequired,
|
||||||
|
});
|
||||||
|
|
||||||
|
function HostMetricsDeleteButton({
|
||||||
|
itemsToDelete,
|
||||||
|
pluralizedItemName,
|
||||||
|
errorMessage,
|
||||||
|
onDelete,
|
||||||
|
deleteDetailsRequests,
|
||||||
|
warningMessage,
|
||||||
|
deleteMessage,
|
||||||
|
}) {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [deleteDetails, setDeleteDetails] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const [deleteMessageError, setDeleteMessageError] = useState();
|
||||||
|
const handleDelete = () => {
|
||||||
|
console.log("Delete");
|
||||||
|
onDelete();
|
||||||
|
toggleModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleModal = async (isOpen) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setDeleteDetails(null);
|
||||||
|
if (
|
||||||
|
isOpen &&
|
||||||
|
itemsToDelete.length === 1 &&
|
||||||
|
deleteDetailsRequests?.length > 0
|
||||||
|
) {
|
||||||
|
const { results, error } = await getRelatedResourceDeleteCounts(
|
||||||
|
deleteDetailsRequests
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setDeleteMessageError(error);
|
||||||
|
} else {
|
||||||
|
setDeleteDetails(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
setIsModalOpen(isOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTooltip = () => {
|
||||||
|
if (itemsToDelete.length) {
|
||||||
|
return t`Soft delete`;
|
||||||
|
} else {
|
||||||
|
return t`Select a row to delete`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalTitle = t`Soft delete ${pluralizedItemName}?`;
|
||||||
|
|
||||||
|
const isDisabled =
|
||||||
|
itemsToDelete.length === 0;
|
||||||
|
|
||||||
|
const buildDeleteWarning = () => {
|
||||||
|
const deleteMessages = [];
|
||||||
|
if (warningMessage) {
|
||||||
|
deleteMessages.push(warningMessage);
|
||||||
|
}
|
||||||
|
if (deleteMessage) {
|
||||||
|
if (itemsToDelete.length > 1 || deleteDetails)
|
||||||
|
{
|
||||||
|
deleteMessages.push(deleteMessage);
|
||||||
|
} else if (deleteDetails || itemsToDelete.length > 1) {
|
||||||
|
deleteMessages.push(deleteMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{deleteMessages.map((message) => (
|
||||||
|
<div aria-label={message} key={message}>
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{deleteDetails &&
|
||||||
|
Object.entries(deleteDetails).map(([key, value]) => (
|
||||||
|
<div key={key} aria-label={`${key}: ${value}`}>
|
||||||
|
<Label>{key}</Label>
|
||||||
|
<Badge>{value}</Badge>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (deleteMessageError) {
|
||||||
|
return (
|
||||||
|
<AlertModal
|
||||||
|
isOpen={deleteMessageError}
|
||||||
|
title={t`Error!`}
|
||||||
|
onClose={() => {
|
||||||
|
toggleModal(false);
|
||||||
|
setDeleteMessageError();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ErrorDetail error={deleteMessageError} />
|
||||||
|
</AlertModal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const shouldShowDeleteWarning =
|
||||||
|
warningMessage ||
|
||||||
|
(itemsToDelete.length === 1 && deleteDetails) ||
|
||||||
|
(itemsToDelete.length > 1 && deleteMessage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip content={renderTooltip()} position="top">
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
isLoading={isLoading}
|
||||||
|
ouiaId="delete-button"
|
||||||
|
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
|
||||||
|
aria-label={t`Delete`}
|
||||||
|
onClick={() => toggleModal(true)}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
>
|
||||||
|
{t`Delete`}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
{isModalOpen && (
|
||||||
|
<AlertModal
|
||||||
|
variant="danger"
|
||||||
|
title={modalTitle}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={() => toggleModal(false)}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
ouiaId="delete-modal-confirm"
|
||||||
|
key="delete"
|
||||||
|
variant="danger"
|
||||||
|
aria-label={t`confirm delete`}
|
||||||
|
isDisabled={Boolean(
|
||||||
|
deleteDetails && itemsToDelete[0]?.type === 'credential_type'
|
||||||
|
)}
|
||||||
|
onClick={handleDelete}
|
||||||
|
>
|
||||||
|
{t`Delete`}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
ouiaId="delete-cancel"
|
||||||
|
key="cancel"
|
||||||
|
variant="link"
|
||||||
|
aria-label={t`cancel delete`}
|
||||||
|
onClick={() => toggleModal(false)}
|
||||||
|
>
|
||||||
|
{t`Cancel`}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div>{t`This action will soft delete the following:`}</div>
|
||||||
|
{itemsToDelete.map((item) => (
|
||||||
|
<span key={item.hostname} id={`item-to-be-deleted-${item.hostname}`}>
|
||||||
|
<strong>{item.hostname}</strong>
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
{shouldShowDeleteWarning && (
|
||||||
|
<WarningMessage
|
||||||
|
variant="warning"
|
||||||
|
isInline
|
||||||
|
title={buildDeleteWarning()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
HostMetricsDeleteButton.propTypes = {
|
||||||
|
onDelete: func.isRequired,
|
||||||
|
itemsToDelete: arrayOf(ItemToDelete).isRequired,
|
||||||
|
pluralizedItemName: string,
|
||||||
|
warningMessage: node,
|
||||||
|
};
|
||||||
|
|
||||||
|
HostMetricsDeleteButton.defaultProps = {
|
||||||
|
pluralizedItemName: 'Items',
|
||||||
|
warningMessage: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HostMetricsDeleteButton;
|
||||||
@@ -4,12 +4,13 @@ import { Tr, Td } from '@patternfly/react-table';
|
|||||||
import { formatDateString } from 'util/dates';
|
import { formatDateString } from 'util/dates';
|
||||||
import { HostMetrics } from 'types';
|
import { HostMetrics } from 'types';
|
||||||
import {t} from "@lingui/macro";
|
import {t} from "@lingui/macro";
|
||||||
|
import {bool, func} from "prop-types";
|
||||||
|
|
||||||
function HostMetricsListItem({ item }) {
|
function HostMetricsListItem({ item, isSelected, onSelect, rowIndex }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr id={`host_metrics-row-${item.hostname}`} ouiaId={`host-metrics-row-${item.hostname}`}>
|
<Tr id={`host_metrics-row-${item.hostname}`} ouiaId={`host-metrics-row-${item.hostname}`}>
|
||||||
<Td />
|
<Td select={{ rowIndex, isSelected, onSelect}} dataLabel={t`Selected`} />
|
||||||
<Td dataLabel={t`Hostname`}>{item.hostname}</Td>
|
<Td dataLabel={t`Hostname`}>{item.hostname}</Td>
|
||||||
<Td dataLabel={t`First automation`}>{formatDateString(item.first_automation)}</Td>
|
<Td dataLabel={t`First automation`}>{formatDateString(item.first_automation)}</Td>
|
||||||
<Td dataLabel={t`Last automation`}>{formatDateString(item.last_automation)}</Td>
|
<Td dataLabel={t`Last automation`}>{formatDateString(item.last_automation)}</Td>
|
||||||
@@ -22,6 +23,8 @@ function HostMetricsListItem({ item }) {
|
|||||||
|
|
||||||
HostMetricsListItem.propTypes = {
|
HostMetricsListItem.propTypes = {
|
||||||
item: HostMetrics.isRequired,
|
item: HostMetrics.isRequired,
|
||||||
|
isSelected: bool.isRequired,
|
||||||
|
onSelect: func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HostMetricsListItem;
|
export default HostMetricsListItem;
|
||||||
|
|||||||
Reference in New Issue
Block a user