mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 18:09:57 -03:30
Add possibility to select and delete HostMetrics
This commit is contained in:
parent
9f3c4f6240
commit
179868dff2
@ -5,13 +5,16 @@ import { HostMetricsAPI } from 'api';
|
||||
import useRequest from 'hooks/useRequest';
|
||||
import PaginatedTable, {
|
||||
HeaderRow,
|
||||
HeaderCell
|
||||
HeaderCell,
|
||||
} from 'components/PaginatedTable';
|
||||
import DataListToolbar from 'components/DataListToolbar';
|
||||
import { getQSConfig, parseQueryString } from 'util/qs';
|
||||
import {Card, PageSection} from "@patternfly/react-core";
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import HostMetricsListItem from "./HostMetricsListItem";
|
||||
import HostMetricsDeleteButton from "./HostMetricsDeleteButton";
|
||||
import useSelected from 'hooks/useSelected';
|
||||
|
||||
|
||||
function HostMetrics() {
|
||||
|
||||
@ -46,40 +49,91 @@ function HostMetrics() {
|
||||
readHostMetrics();
|
||||
}, [readHostMetrics]);
|
||||
|
||||
const { selected, isAllSelected, handleSelect, selectAll, clearSelected } =
|
||||
useSelected(results);
|
||||
|
||||
return(
|
||||
<>
|
||||
<>
|
||||
<ScreenHeader
|
||||
streamType="none"
|
||||
breadcrumbConfig={breadcrumbConfig}
|
||||
/>
|
||||
<PageSection>
|
||||
<Card>
|
||||
<PaginatedTable
|
||||
contentError={error}
|
||||
hasContentLoading={isLoading}
|
||||
items={results}
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Host Metrics`}
|
||||
renderRow={(item)=> (<HostMetricsListItem item={item} />)}
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]}
|
||||
toolbarSearchableKeys={[]}
|
||||
toolbarRelatedSearchableKeys={[]}
|
||||
renderToolbar={(props) => <DataListToolbar {...props} advancedSearchDisabled={true} fillWidth}
|
||||
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>
|
||||
</>
|
||||
streamType="none"
|
||||
breadcrumbConfig={breadcrumbConfig}
|
||||
/>
|
||||
<PageSection>
|
||||
<Card>
|
||||
<PaginatedTable
|
||||
contentError={error}
|
||||
hasContentLoading={isLoading}
|
||||
items={results}
|
||||
itemCount={count}
|
||||
pluralizedItemName={t`Host Metrics`}
|
||||
renderRow={(item, index)=> (
|
||||
<HostMetricsListItem
|
||||
item={item}
|
||||
isSelected={selected.some((row) => row.hostname === item.hostname)}
|
||||
onSelect={() => handleSelect(item)}
|
||||
rowIndex={index}
|
||||
/>
|
||||
)}
|
||||
qsConfig={QS_CONFIG}
|
||||
toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]}
|
||||
toolbarSearchableKeys={[]}
|
||||
toolbarRelatedSearchableKeys={[]}
|
||||
renderToolbar={(props) =>
|
||||
<DataListToolbar
|
||||
{...props}
|
||||
advancedSearchDisabled={true}
|
||||
fillWidth
|
||||
isAllSelected={isAllSelected}
|
||||
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 { HostMetrics } from 'types';
|
||||
import {t} from "@lingui/macro";
|
||||
import {bool, func} from "prop-types";
|
||||
|
||||
function HostMetricsListItem({ item }) {
|
||||
function HostMetricsListItem({ item, isSelected, onSelect, rowIndex }) {
|
||||
|
||||
return (
|
||||
<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`First automation`}>{formatDateString(item.first_automation)}</Td>
|
||||
<Td dataLabel={t`Last automation`}>{formatDateString(item.last_automation)}</Td>
|
||||
@ -22,6 +23,8 @@ function HostMetricsListItem({ item }) {
|
||||
|
||||
HostMetricsListItem.propTypes = {
|
||||
item: HostMetrics.isRequired,
|
||||
isSelected: bool.isRequired,
|
||||
onSelect: func.isRequired,
|
||||
};
|
||||
|
||||
export default HostMetricsListItem;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user