add sync status indicator to inventory list

This commit is contained in:
Keith Grant 2020-07-07 16:36:59 -07:00
parent f121dc59f6
commit 15ae0976dd
5 changed files with 160 additions and 8 deletions

View File

@ -0,0 +1,46 @@
import React from 'react';
import 'styled-components/macro';
import styled, { keyframes } from 'styled-components';
import { oneOf, string } from 'prop-types';
import { CloudIcon } from '@patternfly/react-icons';
const COLORS = {
success: '--pf-global--palette--green-400',
syncing: '--pf-global--palette--green-400',
error: '--pf-global--danger-color--100',
disabled: '--pf-global--disabled-color--200',
};
const Pulse = keyframes`
from {
opacity: 0;
}
to {
opacity: 1.0;
}
`;
const PulseWrapper = styled.div`
animation: ${Pulse} 1.5s linear infinite alternate;
`;
export default function SyncStatusIndicator({ status, title }) {
const color = COLORS[status] || COLORS.disabled;
if (status === 'syncing') {
return (
<PulseWrapper>
<CloudIcon color={`var(${color})`} title={title} />
</PulseWrapper>
);
}
return <CloudIcon color={`var(${color})`} title={title} />;
}
SyncStatusIndicator.propTypes = {
status: oneOf(['success', 'error', 'disabled', 'syncing']).isRequired,
title: string,
};
SyncStatusIndicator.defaultProps = {
title: null,
};

View File

@ -0,0 +1 @@
export { default } from './SyncStatusIndicator';

View File

@ -1,7 +1,6 @@
import React, { useState, useCallback, useEffect } from 'react';
import { useLocation, useRouteMatch } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Card, PageSection } from '@patternfly/react-core';
@ -13,8 +12,8 @@ import ErrorDetail from '../../../components/ErrorDetail';
import PaginatedDataList, {
ToolbarDeleteButton,
} from '../../../components/PaginatedDataList';
import { getQSConfig, parseQueryString } from '../../../util/qs';
import useWsInventories from './useWsInventories';
import AddDropDownButton from '../../../components/AddDropDownButton';
import InventoryListItem from './InventoryListItem';
@ -30,7 +29,7 @@ function InventoryList({ i18n }) {
const [selected, setSelected] = useState([]);
const {
result: { inventories, itemCount, actions },
result: { results, itemCount, actions },
error: contentError,
isLoading,
request: fetchInventories,
@ -42,13 +41,13 @@ function InventoryList({ i18n }) {
InventoriesAPI.readOptions(),
]);
return {
inventories: response.data.results,
results: response.data.results,
itemCount: response.data.count,
actions: actionsResponse.data.actions,
};
}, [location]),
{
inventories: [],
results: [],
itemCount: 0,
actions: {},
}
@ -58,6 +57,8 @@ function InventoryList({ i18n }) {
fetchInventories();
}, [fetchInventories]);
const inventories = useWsInventories(results);
const isAllSelected =
selected.length === inventories.length && selected.length > 0;
const {

View File

@ -10,16 +10,16 @@ import {
DataListItemRow,
Tooltip,
} from '@patternfly/react-core';
import { PencilAltIcon } from '@patternfly/react-icons';
import { t } from '@lingui/macro';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { PencilAltIcon } from '@patternfly/react-icons';
import { timeOfDay } from '../../../util/dates';
import { InventoriesAPI } from '../../../api';
import { Inventory } from '../../../types';
import DataListCell from '../../../components/DataListCell';
import CopyButton from '../../../components/CopyButton';
import SyncStatusIndicator from '../../../components/SyncStatusIndicator';
const DataListAction = styled(_DataListAction)`
align-items: center;
@ -52,6 +52,12 @@ function InventoryListItem({
}, [inventory.id, inventory.name, fetchInventories]);
const labelId = `check-action-${inventory.id}`;
let syncStatus = 'disabled';
if (inventory.has_inventory_sources) {
syncStatus =
inventory.inventory_sources_with_failures > 0 ? 'error' : 'success';
}
return (
<DataListItem
key={inventory.id}
@ -67,7 +73,10 @@ function InventoryListItem({
/>
<DataListItemCells
dataListCells={[
<DataListCell key="divider">
<DataListCell key="sync-status" isIcon>
<SyncStatusIndicator status={syncStatus} />
</DataListCell>,
<DataListCell key="name">
<Link to={`${detailUrl}`}>
<b>{inventory.name}</b>
</Link>

View File

@ -0,0 +1,95 @@
import { useState, useEffect, useRef } from 'react';
export default function useWsProjects(initialInventories) {
const [inventories, setInventories] = useState(initialInventories);
const [lastMessage, setLastMessage] = useState(null);
const ws = useRef(null);
useEffect(() => {
setInventories(initialInventories);
}, [initialInventories]);
// const messageExample = {
// unified_job_id: 533,
// status: 'pending',
// type: 'inventory_update',
// inventory_source_id: 53,
// inventory_id: 5,
// group_name: 'jobs',
// unified_job_template_id: 53,
// };
useEffect(() => {
if (!lastMessage?.unified_job_id || lastMessage.type !== 'project_update') {
return;
}
const index = inventories.findIndex(p => p.id === lastMessage.project_id);
if (index === -1) {
return;
}
const inventory = inventories[index];
const updatedProject = {
...inventory,
summary_fields: {
...inventory.summary_fields,
// last_job: {
// id: lastMessage.unified_job_id,
// status: lastMessage.status,
// finished: lastMessage.finished,
// },
},
};
setInventories([
...inventories.slice(0, index),
updatedProject,
...inventories.slice(index + 1),
]);
}, [lastMessage]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
ws.current = new WebSocket(`wss://${window.location.host}/websocket/`);
const connect = () => {
const xrftoken = `; ${document.cookie}`
.split('; csrftoken=')
.pop()
.split(';')
.shift();
ws.current.send(
JSON.stringify({
xrftoken,
groups: {
inventories: ['status_changed'],
jobs: ['status_changed'],
control: ['limit_reached_1'],
},
})
);
};
ws.current.onopen = connect;
ws.current.onmessage = e => {
setLastMessage(JSON.parse(e.data));
};
ws.current.onclose = e => {
// eslint-disable-next-line no-console
console.debug('Socket closed. Reconnecting...', e);
setTimeout(() => {
connect();
}, 1000);
};
ws.current.onerror = err => {
// eslint-disable-next-line no-console
console.debug('Socket error: ', err, 'Disconnecting...');
ws.current.close();
};
return () => {
ws.current.close();
};
}, []);
return inventories;
}