diff --git a/src/actions/userMessages.js b/src/actions/userMessages.js index fa1a00ed5..ba4cac30c 100644 --- a/src/actions/userMessages.js +++ b/src/actions/userMessages.js @@ -1,17 +1,12 @@ import { - ADD_LAST_VM_EVENTS, ADD_USER_MESSAGE, - ADD_VM_EVENTS, AUTO_ACKNOWLEDGE, CLEAR_USER_MSGS, DISMISS_EVENT, DISMISS_USER_MSG, - GET_ALL_EVENTS, - GET_VM_EVENTS, - SAVE_EVENT_FILTERS, - SET_EVENT_SORT, SET_USERMSG_NOTIFIED, SET_SERVER_MESSAGES, + GET_ALL_EVENTS, } from '_/constants' export function addUserMessage ({ message, messageDescriptor, type = '' }) { @@ -82,53 +77,3 @@ export function setServerMessages ({ messages }) { export function getAllEvents () { return { type: GET_ALL_EVENTS } } - -export function addVmEvents ({ events = [], vmId }) { - return { - type: ADD_VM_EVENTS, - payload: { - events, - vmId, - }, - } -} - -export function addLastVmEvents ({ events = [], vmId }) { - return { - type: ADD_LAST_VM_EVENTS, - payload: { - events, - vmId, - }, - } -} - -export function getVmEvents ({ vmId, vmName, newestEventId = 0, maxItems = 0 }) { - return { - type: GET_VM_EVENTS, - payload: { - vmId, - vmName, - newestEventId, - maxItems, - }, - } -} - -export function setEventSort ({ sort }) { - return { - type: SET_EVENT_SORT, - payload: { - sort, - }, - } -} - -export function saveEventFilters ({ filters }) { - return { - type: SAVE_EVENT_FILTERS, - payload: { - filters, - }, - } -} diff --git a/src/components/Events/EventFilters.js b/src/components/Events/EventFilters.js deleted file mode 100644 index 8e0d82617..000000000 --- a/src/components/Events/EventFilters.js +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useMemo } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { enumMsg, withMsg } from '_/intl' -import { saveEventFilters } from '_/actions' -import { localeCompare, toJS } from '_/helpers' -import moment from 'moment' - -import { Filters } from '_/components/Toolbar' - -export const SEVERITY = 'severity' -export const DATE = 'date' -export const MESSAGE = 'message' -export const UNKNOWN = 'unknown' - -export const EVENT_SEVERITY = { - error: 3, - warning: 2, - normal: 1, - [UNKNOWN]: 0, -} - -export function filterEvents ({ events, severityFilters, dateFilters, messageFilters }) { - return events?.filter(({ severity, time, description }) => { - const ackFromSeverity = !severityFilters?.length || severityFilters?.some(level => level === severity) - const ackFromTime = !dateFilters?.length || dateFilters?.some(isoDateStr => moment(time).isSame(isoDateStr, 'day')) - const ackFromMessage = !messageFilters?.length || messageFilters?.some(str => description?.includes(str)) - return ackFromSeverity && ackFromTime && ackFromMessage - }) -} - -const composeSeverity = (msg, locale) => { - return { - id: SEVERITY, - title: msg.severity(), - placeholder: msg.eventsFilterTypePlaceholderSeverity(), - filterValues: Object.entries( - Object.keys(EVENT_SEVERITY) - .map((status) => ({ title: enumMsg('EventSeverity', status, msg), id: status })) - .reduce((acc, { title, id }) => { - acc[title] = { ...acc[title], [id]: id } - return acc - }, {})) - .map(([title, ids]) => ({ title, ids })) - .sort((a, b) => localeCompare(a.title, b.title, locale)), - } -} - -const EventFilters = ({ msg, locale, selectedFilters = {}, onFilterUpdate }) => { - const filterTypes = useMemo(() => [ - composeSeverity(msg, locale), - { - id: DATE, - title: msg.date(), - datePicker: true, - }, - { - id: MESSAGE, - title: msg.message(), - placeholder: msg.eventsFilterTypePlaceholderMessage(), - }, - ], [msg, locale]) - return ( - - ) -} - -EventFilters.propTypes = { - selectedFilters: PropTypes.object, - onFilterUpdate: PropTypes.func.isRequired, - msg: PropTypes.object.isRequired, - locale: PropTypes.string.isRequired, -} - -export default connect( - ({ userMessages }) => ({ - selectedFilters: toJS(userMessages.get('eventFilters')), - }), - (dispatch) => ({ - onFilterUpdate: (filters) => dispatch(saveEventFilters({ filters })), - }) -)(withMsg(EventFilters)) diff --git a/src/components/Events/EventSort.js b/src/components/Events/EventSort.js deleted file mode 100644 index 96a3c8c54..000000000 --- a/src/components/Events/EventSort.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { Sort } from '_/components/Toolbar' -import { setEventSort } from '_/actions' -import { withMsg } from '_/intl' -import { toJS } from '_/helpers' - -import { SEVERITY, DATE, MESSAGE } from './EventFilters' - -export const SortFields = { - [SEVERITY]: { - id: SEVERITY, - messageDescriptor: { id: 'severity' }, - }, - [DATE]: { - id: DATE, - messageDescriptor: { id: 'date' }, - }, - [MESSAGE]: { - id: MESSAGE, - messageDescriptor: { id: 'message' }, - }, -} - -const EventSort = ({ sort = { ...SortFields[DATE], isAsc: false }, onSortChange }) => - -EventSort.propTypes = { - sort: PropTypes.shape({ - id: PropTypes.string.isRequired, - messageDescriptor: PropTypes.object.isRequired, - isAsc: PropTypes.bool, - }), - onSortChange: PropTypes.func.isRequired, -} - -export default connect( - ({ userMessages }) => ({ - sort: toJS(userMessages.get('eventSort')), - }), - (dispatch) => ({ - onSortChange: (sort) => dispatch(setEventSort({ sort })), - }) -)(withMsg(EventSort)) diff --git a/src/components/Events/EventStatus.js b/src/components/Events/EventStatus.js deleted file mode 100644 index a61aac444..000000000 --- a/src/components/Events/EventStatus.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { ExclamationTriangleIcon, ExclamationCircleIcon } from '@patternfly/react-icons/dist/esm/icons' - -export const EventStatus = ({ severity }) => { - switch (severity) { - case 'error': - return - case 'warning': - return - default: - return null - } -} - -EventStatus.propTypes = { - severity: PropTypes.oneOf(['error', 'warning', 'normal']), -} diff --git a/src/components/Events/EventsTable.js b/src/components/Events/EventsTable.js deleted file mode 100644 index 39702c513..000000000 --- a/src/components/Events/EventsTable.js +++ /dev/null @@ -1,181 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { withMsg } from '_/intl' - -import { - TableComposable, - Tbody, - Th, - Thead, - Td, - Tr, -} from '@patternfly/react-table' - -import { - Button, - EmptyState, - EmptyStateIcon, - EmptyStateBody, - Spinner, - Title, -} from '@patternfly/react-core' - -import { - SearchIcon, -} from '@patternfly/react-icons/dist/esm/icons' - -import { EventStatus } from './EventStatus' - -import style from './style.css' -import { localeCompare, toJS, translate } from '_/helpers' - -import { saveEventFilters, setEventSort } from '_/actions' - -import { SEVERITY, DATE, MESSAGE, EVENT_SEVERITY, UNKNOWN, filterEvents } from './EventFilters' - -import { SortFields } from './EventSort' - -const sortEvents = (events = [], { id, isAsc } = {}, locale) => { - if (!id) { - return - } - const direction = isAsc ? 1 : -1 - const getField = (event, id) => { - switch (id) { - case SEVERITY: - return '' + EVENT_SEVERITY[event?.severity ?? UNKNOWN] ?? EVENT_SEVERITY[UNKNOWN] - case DATE: - return '' + event?.time ?? '' - case MESSAGE: - return event?.description - } - } - - events.sort((a, b) => direction * localeCompare(getField(a, id), getField(b, id), locale)) -} - -const EventsTable = ({ - msg, - locale, - events, - eventFilters: { [SEVERITY]: severityFilters, [DATE]: dateFilters, [MESSAGE]: messageFilters }, - eventSort = { id: DATE }, - clearAllFilters, - setSort, -}) => { - const columns = [ - SortFields[SEVERITY], - SortFields[DATE], - SortFields[MESSAGE], - ].map(({ messageDescriptor, ...rest }) => ({ - ...rest, - messageDescriptor, - label: messageDescriptor?.id ? translate({ id: messageDescriptor?.id, msg }) : '', - })) - - const activeSortIndex = columns.findIndex(({ id }) => id === eventSort.id) - const activeSortDirection = eventSort.isAsc ? 'asc' : 'desc' - const buildSort = (columnIndex) => ({ - sortBy: { - index: activeSortIndex, - direction: activeSortDirection, - }, - onSort: (_event, index, direction) => { - setSort({ - isAsc: direction === 'asc', - ...SortFields?.[columns[index]?.id ?? SEVERITY], - }) - }, - columnIndex, - }) - - const filteredEvents = filterEvents({ events, severityFilters, dateFilters, messageFilters }) - - sortEvents(filteredEvents, eventSort, locale) - - return ( -
- { !filteredEvents && ( - - - - ) } - - { filteredEvents?.length === 0 && ( - - - - {msg.noEventsFound()} - - {msg.clearAllFiltersAndTryAgain()} - - - ) } - - { filteredEvents?.length > 0 && ( - - - - {columns.map(({ label }, index) => ( - - {label} - - ))} - - - - {filteredEvents.map(({ id, severity, time, description }) => ( - - - - - - {new Date(time).toLocaleString(locale)} - - - {description} - - - ))} - - - ) } -
- ) -} - -EventsTable.propTypes = { - msg: PropTypes.object.isRequired, - locale: PropTypes.string.isRequired, - events: PropTypes.array, - eventFilters: PropTypes.object.isRequired, - eventSort: PropTypes.shape({ - id: PropTypes.string.isRequired, - messageDescriptor: PropTypes.object.isRequired, - isAsc: PropTypes.bool, - }), - clearAllFilters: PropTypes.func.isRequired, - setSort: PropTypes.func.isRequired, - -} - -export default connect( - ({ userMessages }, { vmId }) => ({ - events: toJS(userMessages.getIn(['events', vmId])), - eventFilters: toJS(userMessages.getIn(['eventFilters'], {})), - eventSort: toJS(userMessages.getIn(['eventSort'])), - }), - (dispatch) => ({ - clearAllFilters: () => dispatch(saveEventFilters({ filters: {} })), - setSort: (sort) => dispatch(setEventSort({ sort })), - }) -)(withMsg(EventsTable)) diff --git a/src/components/Events/index.js b/src/components/Events/index.js deleted file mode 100644 index 99b75352d..000000000 --- a/src/components/Events/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default as EventsTable } from './EventsTable' -export * from './EventStatus' -export * from './EventFilters' -export { default as EventFilters } from './EventFilters' -export { default as EventSort } from './EventSort' diff --git a/src/components/Events/style.css b/src/components/Events/style.css deleted file mode 100644 index b53e634a5..000000000 --- a/src/components/Events/style.css +++ /dev/null @@ -1,3 +0,0 @@ -:global(#page-router-render-component) .container { - padding-top: 0; -} diff --git a/src/components/Pages/index.js b/src/components/Pages/index.js index af9ead43f..aa1db4daa 100644 --- a/src/components/Pages/index.js +++ b/src/components/Pages/index.js @@ -10,7 +10,7 @@ import VmDetails from '../VmDetails' import VmConsole from '../VmConsole' import Handler404 from '_/Handler404' import { GlobalSettings } from '../UserSettings' -import { EventsTable } from '_/components/Events' + /** * Route component (for PageRouter) to view the list of VMs and Pools */ @@ -106,18 +106,9 @@ const VmConsolePageConnected = connect( (dispatch) => ({}) )(VmConsolePage) -const VmEventsPage = ({ match }) => { - return -} - -VmEventsPage.propTypes = { - match: RouterPropTypeShapes.match.isRequired, -} - export { VmConsolePageConnected as VmConsolePage, VmDetailsPageConnected as VmDetailsPage, VmsListPage, GlobalSettingsPage, - VmEventsPage, } diff --git a/src/components/Toolbar/DatePickerFilter.js b/src/components/Toolbar/DatePickerFilter.js deleted file mode 100644 index 116661e5a..000000000 --- a/src/components/Toolbar/DatePickerFilter.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState } from 'react' -import PropTypes from 'prop-types' -import { withMsg } from '_/intl' -import { - DatePicker, - InputGroup, - ToolbarFilter, -} from '@patternfly/react-core' - -import moment from 'moment' - -const DatePickerFilter = ({ - filterId, - selectedFilters, - showToolbarItem, - title, - onFilterUpdate, - msg, -}) => { - const dateFormat = moment.localeData().longDateFormat('L') - const formatDate = date => moment(date).format(dateFormat) - const parseDate = str => moment(str, dateFormat).toDate() - const isValidDate = date => moment(date, dateFormat).isValid() - const toISO = str => moment(str, dateFormat).format('YYYY-MM-DD') - const fromISO = str => moment(str, 'YYYY-MM-DD').format(dateFormat) - - const [date, setDate] = useState(toISO(formatDate(Date.now()))) - - const clearSingleDate = (option) => { - console.warn('clearSingle ', option) - const fixed = toISO(option) - onFilterUpdate([...selectedFilters?.filter(d => d !== fixed)]) - } - - const onDateChange = (inputDate, newDate) => { - if (isValidDate(inputDate)) { - const fixed = toISO(inputDate) - setDate(fixed) - onFilterUpdate([...selectedFilters?.filter(d => d !== fixed), fixed]) - } - } - - return ( - clearSingleDate(option)} - deleteChipGroup={() => onFilterUpdate([])} - categoryName={title} - showToolbarItem={showToolbarItem} - > - - - - - ) -} - -DatePickerFilter.propTypes = { - filterId: PropTypes.string.isRequired, - selectedFilters: PropTypes.array.isRequired, - showToolbarItem: PropTypes.bool.isRequired, - title: PropTypes.string.isRequired, - onFilterUpdate: PropTypes.func.isRequired, - - msg: PropTypes.object.isRequired, -} - -export default withMsg(DatePickerFilter) diff --git a/src/components/Toolbar/Filters.js b/src/components/Toolbar/Filters.js deleted file mode 100644 index f8b108fdf..000000000 --- a/src/components/Toolbar/Filters.js +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useState } from 'react' -import PropTypes from 'prop-types' -import { withMsg } from '_/intl' -import { - Button, - ButtonVariant, - Dropdown, - DropdownItem, - DropdownPosition, - DropdownToggle, - InputGroup, - TextInput, - ToolbarGroup, - ToolbarFilter, - ToolbarItem, - ToolbarToggleGroup, -} from '@patternfly/react-core' - -import { FilterIcon, SearchIcon } from '@patternfly/react-icons/dist/esm/icons' - -import DatePickerFilter from './DatePickerFilter' -import SelectFilter from './SelectFilter' - -const Filters = ({ msg, locale, selectedFilters, onFilterUpdate, filterTypes, textBasedFilterId }) => { - const [currentFilterType, setCurrentFilterType] = useState(filterTypes[0]) - const [expanded, setExpanded] = useState(false) - const [inputValue, setInputValue] = useState('') - - const nameFilter = filterTypes.find(({ id }) => id === textBasedFilterId) - const labelToFilter = (label) => filterTypes.find(({ title }) => title === label) ?? currentFilterType - - const onFilterTypeSelect = (event) => { - setCurrentFilterType(labelToFilter(event?.target?.innerText)) - setExpanded(!expanded) - } - const onFilterTypeToggle = () => setExpanded(!expanded) - const onNameInput = (event) => { - if ((event.key && event.key !== 'Enter') || - !inputValue || - selectedFilters?.[textBasedFilterId]?.includes(inputValue)) { - return - } - onFilterUpdate({ ...selectedFilters, [textBasedFilterId]: [...(selectedFilters?.[textBasedFilterId] ?? []), inputValue] }) - setInputValue('') - } - - return ( - } breakpoint="xl"> - - - - {currentFilterType.title} - - )} - isOpen={expanded} - style={{ width: '100%' }} - dropdownItems={ - filterTypes.map(({ id, title }) => - {title}) - } - /> - - onFilterUpdate({ - ...selectedFilters, - [textBasedFilterId]: selectedFilters?.[textBasedFilterId]?.filter?.(value => value !== option) ?? [], - })} - deleteChipGroup={() => onFilterUpdate({ ...selectedFilters, [textBasedFilterId]: [] })} - categoryName={nameFilter.title} - showToolbarItem={currentFilterType.id === textBasedFilterId} - > - - - - - - { - filterTypes.filter(({ datePicker }) => datePicker).map(({ id: filterId, title }) => ( - { - console.warn('filtersToSave', filtersToSave) - onFilterUpdate({ ...selectedFilters, [filterId]: filtersToSave }) - } - } - /> - )) - } - {filterTypes.filter(({ filterValues }) => !!filterValues?.length) - ?.map(({ id, filterValues, placeholder, title }) => ( - onFilterUpdate({ ...selectedFilters, [id]: filtersToSave })} - title={title} - placeholderText={placeholder} - /> - ) - )} - - - ) -} - -Filters.propTypes = { - selectedFilters: PropTypes.object.isRequired, - filterTypes: PropTypes.array.isRequired, - textBasedFilterId: PropTypes.string.isRequired, - onFilterUpdate: PropTypes.func.isRequired, - msg: PropTypes.object.isRequired, - locale: PropTypes.string.isRequired, -} - -export default withMsg(Filters) diff --git a/src/components/Toolbar/SelectFilter.js b/src/components/Toolbar/SelectFilter.js deleted file mode 100644 index 7819de314..000000000 --- a/src/components/Toolbar/SelectFilter.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useState } from 'react' -import PropTypes from 'prop-types' -import { - Select, - SelectOption, - SelectVariant, - ToolbarFilter, -} from '@patternfly/react-core' - -const SelectFilter = ({ filterIds = [], setFilters, allSupportedFilters = [], title, placeholderText, filterColumnId, showToolbarItem }) => { - const [isExpanded, setExpanded] = useState(false) - - // one label can map to many IDs so it's easier work with labels - // and reverse map label-to-IDs on save - const toChip = ({ title }) => title - const toOption = ({ title }) => title - const toOptionNode = ({ title }) => - - - // titles are guaranteed to be unique - // return first filter with matching title - const labelToIds = (title) => { - const [{ ids = {} } = {}] = allSupportedFilters.filter(filter => filter.title === title) || [] - return ids - } - const selectedFilters = allSupportedFilters.filter(({ ids }) => filterIds.find(id => ids[id])) - const deleteFilter = (title) => { - const ids = labelToIds(title) - // delete all filter IDs linked to provided title - setFilters(filterIds.filter(id => !ids[id])) - } - - const addFilter = (title) => { - const ids = labelToIds(title) - // add all filter IDs linked - setFilters([...filterIds, ...Object.keys(ids)]) - } - return ( - deleteFilter(option)} - deleteChipGroup={() => setFilters([])} - categoryName={title} - showToolbarItem={showToolbarItem} - > - - - ) -} - -SelectFilter.propTypes = { - filterIds: PropTypes.array.isRequired, - allSupportedFilters: PropTypes.array.isRequired, - setFilters: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, - placeholderText: PropTypes.string.isRequired, - filterColumnId: PropTypes.string.isRequired, - showToolbarItem: PropTypes.bool.isRequired, -} - -export default SelectFilter diff --git a/src/components/Toolbar/Sort.js b/src/components/Toolbar/Sort.js deleted file mode 100644 index 8c7961710..000000000 --- a/src/components/Toolbar/Sort.js +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useState } from 'react' -import PropTypes from 'prop-types' - -import { withMsg } from '_/intl' -import { translate } from '_/helpers' -import { - Button, - OptionsMenu, - OptionsMenuItemGroup, - OptionsMenuSeparator, - OptionsMenuItem, - OptionsMenuToggle, - ToolbarGroup, - ToolbarItem, -} from '@patternfly/react-core' -import { SortAmountDownIcon, SortAmountDownAltIcon } from '@patternfly/react-icons/dist/esm/icons' - -const Sort = ({ sort, msg, onSortChange, SortFields }) => { - const { id: enabledSortId, isAsc } = sort || {} - const [expanded, setExpanded] = useState(false) - - const menuItems = [ - - {Object.values(SortFields) - .map(type => ({ ...type, title: translate({ ...type.messageDescriptor, msg }) })) - .map(({ title, id, messageDescriptor }) => ( - onSortChange({ ...sort, id, messageDescriptor })} - > - {title} - - )) - } - , - , - - onSortChange({ ...sort, isAsc: true })} isSelected={isAsc} id="ascending" key="ascending">{msg.ascending()} - onSortChange({ ...sort, isAsc: false })} isSelected={!isAsc} id="descending" key="descending">{msg.descending()} - , - ] - - return ( - - - setExpanded(!expanded)} - toggleTemplate={sort?.messageDescriptor ? translate({ ...sort.messageDescriptor, msg }) : msg.sortBy()} - /> - )} - isGrouped - /> - - - - - - ) -} - -Sort.propTypes = { - sort: PropTypes.shape({ - id: PropTypes.string.isRequired, - messageDescriptor: PropTypes.object.isRequired, - isAsc: PropTypes.bool, - }), - SortFields: PropTypes.objectOf(PropTypes.shape({ - id: PropTypes.string.isRequired, - messageDescriptor: PropTypes.object.isRequired, - })).isRequired, - onSortChange: PropTypes.func.isRequired, - msg: PropTypes.object.isRequired, -} - -export default withMsg(Sort) diff --git a/src/components/Toolbar/VmFilters.js b/src/components/Toolbar/VmFilters.js index 1f9116cdf..383dd7d48 100644 --- a/src/components/Toolbar/VmFilters.js +++ b/src/components/Toolbar/VmFilters.js @@ -1,11 +1,28 @@ -import React, { useMemo } from 'react' +import React, { useState, useMemo } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import { enumMsg, withMsg } from '_/intl' import { saveVmsFilters } from '_/actions' import { localeCompare, toJS } from '_/helpers' +import { + Button, + ButtonVariant, + Dropdown, + DropdownItem, + DropdownPosition, + DropdownToggle, + InputGroup, + Select, + SelectOption, + SelectVariant, + TextInput, + ToolbarGroup, + ToolbarFilter, + ToolbarItem, + ToolbarToggleGroup, +} from '@patternfly/react-core' -import Filters from './Filters' +import { FilterIcon, SearchIcon } from '@patternfly/react-icons/dist/esm/icons' const STATUS = 'status' const OS = 'os' @@ -62,6 +79,74 @@ const composeOs = (msg, locale, operatingSystems) => { }) } +const Filter = ({ filterIds = [], setFilters, allSupportedFilters = [], title, filterColumnId, showToolbarItem }) => { + const [isExpanded, setExpanded] = useState(false) + + // one label can map to many IDs so it's easier work with labels + // and reverse map label-to-IDs on save + const toChip = ({ title }) => title + const toOption = ({ title }) => title + const toOptionNode = ({ title }) => + + + // titles are guaranteed to be unique + // return first filter with matching title + const labelToIds = (title) => { + const [{ ids = {} } = {}] = allSupportedFilters.filter(filter => filter.title === title) || [] + return ids + } + const selectedFilters = allSupportedFilters.filter(({ ids }) => filterIds.find(id => ids[id])) + const deleteFilter = (title) => { + const ids = labelToIds(title) + // delete all filter IDs linked to provided title + setFilters(filterIds.filter(id => !ids[id])) + } + + const addFilter = (title) => { + const ids = labelToIds(title) + // add all filter IDs linked + setFilters([...filterIds, ...Object.keys(ids)]) + } + return ( + deleteFilter(option)} + deleteChipGroup={() => setFilters([])} + categoryName={filterColumnId} + showToolbarItem={showToolbarItem} + > + + + ) +} + +Filter.propTypes = { + filterIds: PropTypes.array.isRequired, + allSupportedFilters: PropTypes.array.isRequired, + setFilters: PropTypes.func.isRequired, + title: PropTypes.string.isRequired, + filterColumnId: PropTypes.string.isRequired, + showToolbarItem: PropTypes.bool.isRequired, +} + const VmFilters = ({ msg, locale, operatingSystems, selectedFilters, onFilterUpdate }) => { const filterTypes = useMemo(() => [ { @@ -72,13 +157,91 @@ const VmFilters = ({ msg, locale, operatingSystems, selectedFilters, onFilterUpd composeStatus(msg, locale), composeOs(msg, locale, operatingSystems), ], [msg, locale, operatingSystems]) + const [currentFilterType, setCurrentFilterType] = useState(filterTypes[0]) + const [expanded, setExpanded] = useState(false) + const [inputValue, setInputValue] = useState('') + + const nameFilter = filterTypes.find(({ id }) => id === NAME) + const labelToFilter = (label) => filterTypes.find(({ title }) => title === label) ?? currentFilterType + + const onFilterTypeSelect = (event) => { + setCurrentFilterType(labelToFilter(event?.target?.innerText)) + setExpanded(!expanded) + } + const onFilterTypeToggle = () => setExpanded(!expanded) + const onNameInput = (event) => { + if ((event.key && event.key !== 'Enter') || + !inputValue || + selectedFilters?.[NAME]?.includes(inputValue)) { + return + } + onFilterUpdate({ ...selectedFilters, [NAME]: [...(selectedFilters?.[NAME] ?? []), inputValue] }) + setInputValue('') + } + return ( - + } breakpoint="xl"> + + + + {currentFilterType.title} + + )} + isOpen={expanded} + style={{ width: '100%' }} + dropdownItems={ + filterTypes.map(({ id, title }) => + {title}) + } + /> + + onFilterUpdate({ + ...selectedFilters, + [NAME]: selectedFilters?.[NAME]?.filter?.(value => value !== option) ?? [], + })} + deleteChipGroup={() => onFilterUpdate({ ...selectedFilters, [NAME]: [] })} + categoryName={NAME} + showToolbarItem={currentFilterType.id === NAME} + > + + + + + + {filterTypes.filter(({ id }) => id !== NAME)?.map(({ id, filterValues, placeholder }) => ( + onFilterUpdate({ ...selectedFilters, [id]: filtersToSave })} + title={placeholder} + /> + ) + )} + + ) } diff --git a/src/components/Toolbar/VmSort.js b/src/components/Toolbar/VmSort.js index 6ec0e6467..7f0d8cc1a 100644 --- a/src/components/Toolbar/VmSort.js +++ b/src/components/Toolbar/VmSort.js @@ -1,20 +1,83 @@ -import React from 'react' +import React, { useState } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' -import Sort from './Sort' -import { SortFields } from '_/utils' + import { setVmSort } from '_/actions' +import { SortFields } from '_/utils' import { withMsg } from '_/intl' +import { translate } from '_/helpers' +import { + Button, + OptionsMenu, + OptionsMenuItemGroup, + OptionsMenuSeparator, + OptionsMenuItem, + OptionsMenuToggle, + ToolbarGroup, + ToolbarItem, +} from '@patternfly/react-core' +import { SortAmountDownIcon, SortAmountDownAltIcon } from '@patternfly/react-icons/dist/esm/icons' -const VmSort = ({ sort, onSortChange }) => +const VmSort = ({ sort, msg, onSortChange }) => { + const { id: enabledSortId, isAsc } = sort + const [expanded, setExpanded] = useState(false) + + const menuItems = [ + + {Object.values(SortFields) + .map(type => ({ ...type, title: translate({ ...type.messageDescriptor, msg }) })) + .map(({ title, id, messageDescriptor }) => ( + onSortChange({ ...sort, id, messageDescriptor })} + > + {title} + + )) + } + , + , + + onSortChange({ ...sort, isAsc: true })} isSelected={isAsc} id="ascending" key="ascending">{msg.ascending()} + onSortChange({ ...sort, isAsc: false })} isSelected={!isAsc} id="descending" key="descending">{msg.descending()} + , + ] + + return ( + + + setExpanded(!expanded)} + toggleTemplate={sort?.messageDescriptor ? translate({ ...sort.messageDescriptor, msg }) : msg.sortBy()} + /> + )} + isGrouped + /> + + + + + + ) +} VmSort.propTypes = { sort: PropTypes.shape({ id: PropTypes.string.isRequired, messageDescriptor: PropTypes.object.isRequired, + isNumeric: PropTypes.bool, isAsc: PropTypes.bool, }).isRequired, onSortChange: PropTypes.func.isRequired, + msg: PropTypes.object.isRequired, } export default connect( diff --git a/src/components/Toolbar/index.js b/src/components/Toolbar/index.js index ced42fe8f..580d45c08 100644 --- a/src/components/Toolbar/index.js +++ b/src/components/Toolbar/index.js @@ -6,22 +6,10 @@ import { Toolbar, ToolbarContent, ToolbarGroup, - ToolbarItem, } from '@patternfly/react-core' import { RouterPropTypeShapes } from '_/propTypeShapes' import VmActions from '../VmActions' import VmsListToolbar from './VmsListToolbar' -import { - SEVERITY, - DATE, - MESSAGE, - filterEvents, - EventFilters, - EventSort, -} from '../Events' -import { saveEventFilters } from '_/actions' -import { withMsg } from '_/intl' -import { toJS } from '_/helpers' const VmDetailToolbar = ({ match, vms }) => { if (vms.getIn(['vms', match.params.id])) { @@ -52,62 +40,10 @@ const VmDetailToolbarConnected = connect( }) )(VmDetailToolbar) -const EventsToolbar = ({ - events, - eventFilters: { [SEVERITY]: severityFilters, [DATE]: dateFilters, [MESSAGE]: messageFilters }, - msg, - onClearFilters, -}) => { - if (!events) { - return null - } - const total = events?.length ?? 0 - const hasFilters = severityFilters?.length || dateFilters?.length || messageFilters?.length - const filteredEvents = filterEvents({ events, severityFilters, dateFilters, messageFilters }) - return ( - - - - - -
- { hasFilters - ? msg.resultsOf({ total, available: filteredEvents?.length ?? 0 }) - : msg.results({ total }) - } -
-
-
-
- ) -} - -EventsToolbar.propTypes = { - events: PropTypes.array, - eventFilters: PropTypes.object.isRequired, - msg: PropTypes.object.isRequired, - - onClearFilters: PropTypes.func.isRequired, -} - -const EventsToolbarConnected = connect( - ({ userMessages }, { match }) => ({ - events: toJS(userMessages.getIn(['events', match?.params?.id])), - eventFilters: toJS(userMessages.getIn(['eventFilters'], {})), - }), - (dispatch) => ({ - onClearFilters: () => dispatch(saveEventFilters({ filters: {} })), - }) -)(withMsg(EventsToolbar)) - const SettingsToolbar = () =>
export { VmDetailToolbarConnected as VmDetailToolbar, - EventsToolbarConnected as EventsToolbar, VmsListToolbar, SettingsToolbar, } - -export { default as Sort } from './Sort' -export { default as Filters } from './Filters' diff --git a/src/components/VmDetails/cards/OverviewCard/index.js b/src/components/VmDetails/cards/OverviewCard/index.js index 724a306c0..8f4bd28ea 100644 --- a/src/components/VmDetails/cards/OverviewCard/index.js +++ b/src/components/VmDetails/cards/OverviewCard/index.js @@ -1,15 +1,13 @@ import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' -import { push } from 'connected-react-router' import sharedStyle from '../../../sharedStyle.css' import { getOsHumanName, getVmIcon, isVmNameValid, isHostNameValid } from '_/components/utils' import { enumMsg, withMsg } from '_/intl' -import { generateUnique, buildMessageFromRecord, toJS } from '_/helpers' +import { generateUnique, buildMessageFromRecord } from '_/helpers' import { formatUptimeDuration } from '_/utils' import { editVm } from '_/actions' -import { formatHowLongAgo } from '_/utils/format' import { Alert, @@ -18,19 +16,12 @@ import { FormGroup, TextArea, TextInput, - List, - ListItem, - Button, - Label, - Tooltip, } from '@patternfly/react-core' import BaseCard from '../../BaseCard' import VmIcon from '../../../VmIcon' import VmStatusIcon from '../../../VmStatusIcon' -import { EventStatus } from '_/components/Events' import style from './style.css' -import itemListStyle from '../../itemListStyle.css' /** * Overview of the VM (icon, OS type, name, state, description) @@ -205,7 +196,7 @@ class OverviewCard extends React.Component { } render () { - const { vm, icons, vms, operatingSystems, isEditable, msg, locale, lastEvents, goToEventsPage } = this.props + const { vm, icons, vms, operatingSystems, isEditable, msg } = this.props const { isEditing, correlatedMessages, nameError, updateCloudInit, disableHostnameToggle } = this.state const elapsedUptime = vm.getIn(['statistics', 'elapsedUptime', 'firstDatum'], 0) @@ -241,7 +232,7 @@ class OverviewCard extends React.Component { > {({ isEditing }) => { return ( - <> +
{getOsHumanName(vm.getIn(['os', 'type']))}
@@ -327,30 +318,7 @@ class OverviewCard extends React.Component { ) ) } - - { !isEditing && ( - <> - - - - { lastEvents.length === 0 && {msg.noEventsFound()}} - { lastEvents.map(({ id, time, description, severity }) => ( - {new Date(time).toLocaleString(locale)} {description}}> - - - - {description} - - - - )) - } - - - )} - +
) }} @@ -368,13 +336,10 @@ OverviewCard.propTypes = { operatingSystems: PropTypes.object.isRequired, // deep immutable, {[id: string]: OperatingSystem} userMessages: PropTypes.object.isRequired, templates: PropTypes.object.isRequired, - lastEvents: PropTypes.array, saveChanges: PropTypes.func.isRequired, - goToEventsPage: PropTypes.func.isRequired, msg: PropTypes.object.isRequired, - locale: PropTypes.string.isRequired, } export default connect( @@ -385,10 +350,8 @@ export default connect( userMessages: state.userMessages, templates: state.templates, isEditable: vm.get('canUserEditVm'), - lastEvents: toJS(state.userMessages.getIn(['lastEvents', vm.get('id')], [])), }), - (dispatch, { vm }) => ({ + (dispatch) => ({ saveChanges: (minimalVmChanges, correlationId) => dispatch(editVm({ vm: minimalVmChanges }, { correlationId })), - goToEventsPage: () => dispatch(push(`/vm/${vm.get('id')}/events`)), }) )(withMsg(OverviewCard)) diff --git a/src/components/VmDetails/cards/OverviewCard/style.css b/src/components/VmDetails/cards/OverviewCard/style.css index 8ed825c56..6111c3b04 100644 --- a/src/components/VmDetails/cards/OverviewCard/style.css +++ b/src/components/VmDetails/cards/OverviewCard/style.css @@ -2,31 +2,6 @@ * Styles for the Overview Card */ -.bold { - font-weight: var(--pf-global--FontWeight--bold); - } - - .fullWidth { - width: 100%; -} - -.event { - display: flex; - /* event text and time label have different height */ - align-items: center; -} - -.eventText { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - - -.event :global(.pf-c-label) { - margin-right: 5px; -} - .operating-system-label { position: absolute; top: 0; @@ -36,9 +11,7 @@ .vm-info { padding: 15px; - flex-grow: 1; - display: flex; - flex-direction: column; + flex-grow: 1 } .vm-name { @@ -84,13 +57,13 @@ } .os-icon { - padding: 15px; + padding: 15px; } .container { display: flex; overflow: visible; - flex-flow: row nowrap; + flex-flow: row wrap; } .pool-vm-label { diff --git a/src/constants/index.js b/src/constants/index.js index 9f44e2960..168810d94 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -5,13 +5,11 @@ export const ACTION_IN_PROGRESS_START = 'ACTION_IN_PROGRESS_START' export const ACTION_IN_PROGRESS_STOP = 'ACTION_IN_PROGRESS_STOP' export const ADD_ACTIVE_REQUEST = 'ADD_ACTIVE_REQUEST' export const ADD_DISK_REMOVAL_PENDING_TASK = 'ADD_DISK_REMOVAL_PENDING_TASK' -export const ADD_LAST_VM_EVENTS = 'ADD_LAST_VM_EVENTS' export const ADD_NETWORKS_TO_VNIC_PROFILES = 'ADD_NETWORKS_TO_VNIC_PROFILES' export const ADD_SNAPSHOT_ADD_PENDING_TASK = 'ADD_SNAPSHOT_ADD_PENDING_TASK' export const ADD_SNAPSHOT_REMOVAL_PENDING_TASK = 'ADD_SNAPSHOT_REMOVAL_PENDING_TASK' export const ADD_SNAPSHOT_RESTORE_PENDING_TASK = 'ADD_SNAPSHOT_RESTORE_PENDING_TASK' export const ADD_VM_NIC = 'ADD_VM_NIC' -export const ADD_VM_EVENTS = 'ADD_VM_EVENTS' export const ADD_USER_MESSAGE = 'ADD_USER_MESSAGE' export const APP_CONFIGURED = 'APP_CONFIGURED' export const AUTO_ACKNOWLEDGE = 'AUTO_ACKNOWLEDGE' @@ -46,7 +44,6 @@ export const GET_POOL = 'GET_POOL' export const GET_POOLS = 'GET_POOLS' export const GET_VM = 'GET_VM' export const GET_VM_CDROM = 'GET_VM_CDROM' -export const GET_VM_EVENTS = 'GET_VM_EVENTS' export const GET_VMS = 'GET_VMS' export const LOAD_USER_OPTIONS: 'LOAD_USER_OPTIONS' = 'LOAD_USER_OPTIONS' export const LOGIN = 'LOGIN' @@ -70,7 +67,6 @@ export const REMOVE_VM = 'REMOVE_VM' export const RESTART_VM = 'RESTART_VM' export const SAVE_CONSOLE_OPTIONS = 'SAVE_CONSOLE_OPTIONS' export const SAVE_FILTERS = 'SAVE_FILTERS' -export const SAVE_EVENT_FILTERS = 'SAVE_EVENT_FILTERS' export const SAVE_GLOBAL_OPTIONS: 'SAVE_GLOBAL_OPTIONS' = 'SAVE_GLOBAL_OPTIONS' export const SAVE_SSH_KEY = 'SAVE_SSH_KEY' export const SET_ADMINISTRATOR = 'SET_ADMINISTRATOR' @@ -85,7 +81,6 @@ export const SET_CPU_TOPOLOGY_OPTIONS = 'SET_CPU_TOPOLOGY_OPTIONS' export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE' export const SET_DATA_CENTERS = 'SET_DATA_CENTERS' export const SET_DEFAULT_TIMEZONE = 'SET_DEFAULT_TIMEZONE' -export const SET_EVENT_SORT = 'SET_EVENT_SORT' export const SET_FILTERS = 'SET_FILTERS' export const SET_HOSTS = 'SET_HOSTS' export const SET_OPERATING_SYSTEMS = 'SET_OPERATING_SYSTEMS' diff --git a/src/constants/pages.js b/src/constants/pages.js index 2bef38e4b..ad2a897b9 100644 --- a/src/constants/pages.js +++ b/src/constants/pages.js @@ -5,4 +5,3 @@ export const LIST_PAGE_TYPE = 'listPage' export const NO_REFRESH_TYPE = 'noRefreshPage' export const EMPTY_VNIC_PROFILE_ID = '' export const SETTINGS_PAGE_TYPE = 'settingsPage' -export const EVENTS_PAGE_TYPE = 'eventsPage' diff --git a/src/intl/messages.js b/src/intl/messages.js index c363f08b1..7d27d5d1e 100644 --- a/src/intl/messages.js +++ b/src/intl/messages.js @@ -80,7 +80,6 @@ export const messages: { [messageId: string]: MessageType } = { clear: 'Clear', clearAll: 'Clear all', clearAllFilters: 'Clear All Filters', - clearAllFiltersAndTryAgain: 'Clear all filters and try again', clearMessages: 'Clear Messages', clearAutoconnectVmNotAvailable: 'VM chosen for automatic console connection is no longer available. The setting will be cleared.', clickForHelp: 'Click for help', @@ -190,7 +189,6 @@ export const messages: { [messageId: string]: MessageType } = { customScript: 'Custom script', dataCenter: { message: 'Data Center', description: 'Label for the VM\'s data center' }, dataCenterChangesWithCluster: 'The data center cannot be edited from here. Please contact your administrator if you would like to edit the data center.', - date: 'Date', defaultButton: 'Default', defaultOption: { message: '(Default)', @@ -268,10 +266,6 @@ export const messages: { [messageId: string]: MessageType } = { enum_DiskInterface_ide: { message: 'IDE', description: 'IDE controller VM disk attachment interface' }, enum_DiskInterface_virtio: { message: 'VirtIO', description: 'virtio controller VM disk attachment interface' }, enum_DiskInterface_virtio_scsi: { message: 'VirtIO-SCSI', description: 'virtio SCSI controller VM disk attachment interface' }, - enum_EventSeverity_error: 'Error', - enum_EventSeverity_warning: 'Warning', - enum_EventSeverity_normal: 'Information', - enum_EventSeverity_unknown: 'Unknown', enum_NicInterface_e1000: { message: 'e1000', description: 'Display name of a NIC that provides an E1000 based interface to the VM', @@ -358,9 +352,6 @@ export const messages: { [messageId: string]: MessageType } = { }, error: 'Error', errorWhileCreatingNewDisk: 'Error while creating new disk:', - events: 'Events', - eventsFilterTypePlaceholderMessage: 'Filter by Message', - eventsFilterTypePlaceholderSeverity: 'Filter by Severity', every30Seconds: 'Every 30 seconds', everyMinute: 'Every minute', every2Minute: 'Every 2 minutes', @@ -423,13 +414,11 @@ export const messages: { [messageId: string]: MessageType } = { ifVmIsRunningClickToAccessItsGraphicsConsole: 'If the virtual machine is running, click the protocol name to access its Graphical Console.', info: 'Information', inPreview: 'In Preview', - invalidDateFormat: 'Invalid date format. The supported format is {format}.', ieNotSupported: 'Internet Explorer is not a supported browser.', ipAddress: { message: 'IP Address', description: 'Label for IP addresses reported by VM guest agent' }, isPersistMemorySnapshot: 'Content of the memory of the virtual machine is included in the snapshot.', itemDoesntExistOrDontHavePermissions: 'The item doesn\'t exist or you do not have the permissions to view it.', language: 'Language', - lastEvents: 'Last Events', less: { message: 'less', description: 'more/less pair used to control collapsible long listing', @@ -458,7 +447,6 @@ export const messages: { [messageId: string]: MessageType } = { memory: 'Memory', memoryIncluded: '(State included)', messages: 'Messages', - message: 'Message', more: { message: 'more', description: 'more/less pair used to control collapsible long listing', @@ -510,7 +498,6 @@ export const messages: { [messageId: string]: MessageType } = { noClustersAvailable: 'No Clusters available', noDisks: 'no disks', noError: 'No error', - noEventsFound: 'no events', noMessages: 'There are no notifications to display.', noneItem: '[None]', noNetwork: 'No network', @@ -635,7 +622,6 @@ export const messages: { [messageId: string]: MessageType } = { message: 'Your session is about to timeout due to inactivity.', description: 'Primary message for SessionTimeout modal component', }, - severity: 'Severity', shutdown: 'Shutdown', shutdownStatelessPoolVm: 'This virtual machine belongs to {poolName} and is stateless so any data that is currently attached to the virtual machine will be lost if it is shutdown. The virtual machine will be returned to {poolName} if shutdown.', shutdownVm: 'Shutdown the VM', @@ -697,7 +683,6 @@ export const messages: { [messageId: string]: MessageType } = { timezone: 'Timezone', thisOperationCantBeUndone: 'This operation cannot be undone.', threadsPerCores: 'Threads per Core', - toggleDatePicker: 'Toggle date picker', totalCountOfVirtualProcessorsVmWillBeEquippedWith: 'Total count of virtual processors the virtual machine will be equipped with.', totalSocketsCpuTooltipMessage: '{number} virtual sockets', totalCoresCpuTooltipMessage: '{number} cores per socket', @@ -784,7 +769,6 @@ export const messages: { [messageId: string]: MessageType } = { username: 'Username', usingRemoteViewer: 'Using a remote viewer relies on a downloaded .vv file.', vcpuTopology: 'VCPU Topology', - viewAll: 'View All', viewAllVirtualMachines: 'View All Virtual Machines', virtualMachines: 'Virtual Machines', virtualSockets: 'Virtual Sockets', diff --git a/src/intl/translated-messages.json b/src/intl/translated-messages.json index d42912dbd..65c7ecc2b 100644 --- a/src/intl/translated-messages.json +++ b/src/intl/translated-messages.json @@ -45,7 +45,6 @@ "empty": "Prázdné", "enterVmDescription": "Zadejte popis virtuálního stroje (volitelné)", "enterVmName": "Zadejte název virtuálního stroje", - "enum_EventSeverity_unknown": "Neznámý", "enum_VmStatus_down": "Vyp", "enum_VmStatus_image_locked": "Obraz disku uzamčen", "enum_VmStatus_migrating": "Migruje", @@ -354,7 +353,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "Unbekannt", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", @@ -889,7 +887,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "Desconocido", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", @@ -1424,7 +1421,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "Inconnue", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", @@ -2046,7 +2042,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "Unknown", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", @@ -2435,7 +2430,6 @@ "clear": "გასუფთავება", "clearAll": "ყველაფრის გასუფთავება", "clearAllFilters": "ყველა ფილტრის გასუფთავება", - "clearAllFiltersAndTryAgain": "გაასუფთავეთ ყველა ფილტრი და თავიდან სცადეთ", "clearAutoconnectVmNotAvailable": "კონსოლის ავტომატური დაკავშირებისთვის არჩეული ვმ-ი ხელმიუწვდომელია. პარამეტრი წაიშლება.", "clearMessages": "შეტყობინებების გასუფთავება", "clickForHelp": "დახმარებისთვის დააწკაპუნეთ", @@ -2527,7 +2521,6 @@ "customScript": "ხელით მითითებული სკრიპტი", "dataCenter": "მდც", "dataCenterChangesWithCluster": "მონაცემთა დამუშავების ცენტრის აქ ჩასწორება შეუძლებელია. მის ჩასასწორებლად დაუკავშირდით სისტემურ ადმინისტრატორს.", - "date": "თარიღი", "defaultButton": "ნაგულიხმები", "defaultOption": "(ნაგულისხმები)", "definedMemory": "აღწერილი მეხსიერება", @@ -2596,10 +2589,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_error": "შეცდომა", - "enum_EventSeverity_normal": "ინფორმაცია", - "enum_EventSeverity_unknown": "უცნობი", - "enum_EventSeverity_warning": "გაფრთხილება", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_e1000e": "e1000e", "enum_NicInterface_rtl8139": "rtl8139", @@ -2623,9 +2612,6 @@ "enum_VmStatus_wait_for_launch": "გაშვების მოლოდინი", "error": "შეცდომა", "errorWhileCreatingNewDisk": "ახალი დისკის შექმნის შეცდომა:", - "events": "მოვლენა", - "eventsFilterTypePlaceholderMessage": "შეტყობინებით გაფილტვრა", - "eventsFilterTypePlaceholderSeverity": "სიმწვავის მიხედვით გაშიფვრა", "every2Minute": "ყოველ 2 წუთში", "every30Seconds": "ყოველ 30 წამში", "every5Minute": "ყოველ 5 წუთში", @@ -2678,12 +2664,10 @@ "ifVmIsRunningClickToAccessItsGraphicsConsole": "თუ ვირტუალური მანქანა გაშვებულია, კონსოლის გასაშვებად დააწკაპუნეთ პროტოკოლის შესაბამის სახელს.", "inPreview": "გადახედვა", "info": "ინფორმაცია", - "invalidDateFormat": "თარიღის არასწორი ფორმატი. მხარდაჭერილი ფორმატია {format}.", "ipAddress": "IP მისამართი", "isPersistMemorySnapshot": "სწრაფი ასლი ვირტუალური მანქანის მეხსიერების შემცველობასაც შეიცავს.", "itemDoesntExistOrDontHavePermissions": "ჩანაწერი არ არსებობს ან არ გაქვთ მისი ნახვის უფლება.", "language": "ენა", - "lastEvents": "ბოლო მოვლენები", "less": "ნაკლები", "loadingTripleDot": "ჩატვირთვა ...", "locked": "დაბლოკილია", @@ -2702,7 +2686,6 @@ "maxNumberOfVms": "ამ პულიდან თქვენი მოხმარებისათვის მაქსიმუმ {numberOfVms} შეიძლება გამოიყოს.", "memory": "მეხსიერება", "memoryIncluded": "(შეიცავს მდგომარეობასაც)", - "message": "შეტყობინება", "messages": "შეტყობინებები", "more": "მეტი", "name": "სახელი", @@ -2740,7 +2723,6 @@ "noClustersAvailable": "კლასტერები ხელმიუწვდომელია", "noDisks": "დისკების გარეშე", "noError": "შეცდომის გარეშე", - "noEventsFound": "მოვლენების გარეშე", "noMessages": "საჩვენებელი შეტყობინებების გარეშე.", "noNetwork": "ქსელის გარეშე", "noNics": "ქსელის ინტერფეისების გარეშე", @@ -2823,7 +2805,6 @@ "serialConsole": "სერიული კონსოლი", "serialConsoleOptions": "სერიული კონსოლის მორგება", "sessionExpired": "თქვენი სესია არააქტიურობის გამო მალე დასრულდება.", - "severity": "სიმწვავე", "shutdown": "გამორთვა", "shutdownStatelessPoolVm": "ვირტუალური მანქანა ეკუთვნის პულს ( {poolName} ) და არის უნიადაგო, ამიტომ ყველა მონაცემი, რომელიც მიმაგრებულია, წაიშლება მისი გათიშვისთანავე. ვირტუალური მანქანა დაბრუნდება {poolName} -ში ", "shutdownVm": "ვმ-ის გამორთვა", @@ -2873,7 +2854,6 @@ "thisOperationCantBeUndone": "ამ ოპერაციის დაბრუნება შეუძლებელია.", "threadsPerCores": "ნაკადები თითოეული ბირთვისთვის", "timezone": "დროის სარტყელი", - "toggleDatePicker": "თარიღის ამრჩევის გადართვა", "totalCoresCpuTooltipMessage": "{number} ბირთვი თითოეულ სოკეტზე", "totalCountOfVirtualProcessorsVmWillBeEquippedWith": "ვირტუალური მანქანისთვის განკუთვნილი ვირტუალური პროცესორების რაოდენობა.", "totalMemoryVmWillBeEquippedWith": "ვირტუალური მანქანისთვის განკუთვნილი მეხსიერების რაოდენობა.", @@ -2927,7 +2907,6 @@ "utilizationNoHistoricData": "ისტორიის მონაცემები ხელმიუწვდომელია", "utilizationNoNetStats": "ამ ვმ-სთვის ქსელის გამოყენება ხელმიუწვდომელია", "vcpuTopology": "VCPU-ის ტოპოლოგია", - "viewAll": "ყველას ნახვა", "viewAllVirtualMachines": "ყველა ვირტუალური მანქანის ნახვა", "virtualMachines": "ვირტუალური მანქანები", "virtualSockets": "ვირტუალური სოკეტები", @@ -3169,7 +3148,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "알 수 없음 ", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", @@ -3704,7 +3682,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "Desconhecido", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", @@ -4239,7 +4216,6 @@ "enum_DiskInterface_ide": "IDE", "enum_DiskInterface_virtio": "VirtIO", "enum_DiskInterface_virtio_scsi": "VirtIO-SCSI", - "enum_EventSeverity_unknown": "未知", "enum_NicInterface_e1000": "e1000", "enum_NicInterface_rtl8139": "rtl8139", "enum_NicInterface_virtio": "VirtIO", diff --git a/src/ovirtapi/index.js b/src/ovirtapi/index.js index 850aae473..3af29f5ab 100644 --- a/src/ovirtapi/index.js +++ b/src/ovirtapi/index.js @@ -305,17 +305,6 @@ const OvirtApi = { assertLogin({ methodName: 'dismissEvent' }) return httpDelete({ url: `${AppConfiguration.applicationContext}/api/events/${eventId}` }) }, - eventsForVm ({ vmName, newestEventId = 0, maxItems = 0 }: Object): Promise { - // TODO generic search is expensive: extend REST API capability to fetch directly using VM ID - assertLogin({ methodName: 'eventsForVm' }) - const query = [ - 'search=' + encodeURIComponent(`vm.name=${vmName}`), - !!newestEventId && `from=${newestEventId}`, - !!maxItems && `max=${maxItems}`, - ].filter(Boolean).join('&') - - return httpGet({ url: `${AppConfiguration.applicationContext}/api/events?${query}` }) - }, checkFilter (): Promise { assertLogin({ methodName: 'checkFilter' }) diff --git a/src/reducers/userMessages.js b/src/reducers/userMessages.js index 16c5357a8..4e724e45b 100644 --- a/src/reducers/userMessages.js +++ b/src/reducers/userMessages.js @@ -1,22 +1,17 @@ // @flow import * as Immutable from 'immutable' -import { fromJS } from 'immutable' import { - ADD_LAST_VM_EVENTS, ADD_USER_MESSAGE, - ADD_VM_EVENTS, AUTO_ACKNOWLEDGE, DISMISS_USER_MSG, FAILED_EXTERNAL_ACTION, LOGIN_FAILED, - SET_EVENT_SORT, SET_USERMSG_NOTIFIED, SET_SERVER_MESSAGES, - SAVE_EVENT_FILTERS, CLEAR_USER_MSGS, } from '_/constants' import { actionReducer } from './utils' -import { localeCompare, toJS } from '_/helpers' +import { toJS } from '_/helpers' import uniqueId from 'lodash/uniqueId' import type { FailedExternalActionType } from '_/actions/types' @@ -43,8 +38,6 @@ function removeEvents (targetIds: Set, state: any): any { const initialState = Immutable.fromJS({ records: [], - events: {}, - lastEvents: {}, autoAcknowledge: false, }) @@ -104,28 +97,6 @@ const userMessages: any = actionReducer(initialState, { return state.set('autoAcknowledge', autoAcknowledge) }, - [ADD_VM_EVENTS] (state: any, { payload: { events, vmId } }: any): any { - const existingEvents = toJS(state.getIn(['events', vmId], [])) - const existingIds = new Set(existingEvents.map(({ id }) => id)) - const filteredEvents = events - // keep events unique - .filter(({ id, ...rest }) => !existingIds[id]) - // keep events sorted in descending order - .sort((a, b) => localeCompare(b?.id ?? '', a?.id ?? '', 'en')) - - // newest first - return state.setIn(['events', vmId], fromJS([...filteredEvents, ...existingEvents])) - }, - [ADD_LAST_VM_EVENTS] (state: any, { payload: { events, vmId } }: any): any { - return state.setIn(['lastEvents', vmId], fromJS(events)) - }, - [SAVE_EVENT_FILTERS] (state: any, { payload: { filters } }: any): any { - return state.setIn(['eventFilters'], fromJS(filters)) - }, - [SET_EVENT_SORT] (state: any, { payload: { sort } }: any): any { - return state.setIn(['eventSort'], fromJS(sort)) - }, - }) export default userMessages diff --git a/src/routes.js b/src/routes.js index dfc8f7497..f5154d31d 100644 --- a/src/routes.js +++ b/src/routes.js @@ -9,14 +9,12 @@ import { VmDetailToolbar, VmsListToolbar, SettingsToolbar, - EventsToolbar, } from './components/Toolbar' import { VmDetailsPage, VmsListPage, GlobalSettingsPage, VmConsolePage, - VmEventsPage, } from './components/Pages' import { @@ -25,7 +23,6 @@ import { CONSOLE_PAGE_TYPE, NO_REFRESH_TYPE, SETTINGS_PAGE_TYPE, - EVENTS_PAGE_TYPE, } from '_/constants' /** @@ -68,15 +65,6 @@ export default function getRoutes () { isToolbarFullWidth: true, type: CONSOLE_PAGE_TYPE, }, - { - path: '/vm/:id/events', - title: ({ msg }) => msg.events(), - component: VmEventsPage, - closeable: true, - toolbars: (match) => , - isToolbarFullWidth: true, - type: EVENTS_PAGE_TYPE, - }, ], }, diff --git a/src/sagas/background-refresh.js b/src/sagas/background-refresh.js index e0024aaab..51d7408bd 100644 --- a/src/sagas/background-refresh.js +++ b/src/sagas/background-refresh.js @@ -26,17 +26,14 @@ import { delay } from './utils' import { fetchAndPutSingleVm, fetchByPage, - fetchLastVmEvents, fetchPools, fetchSinglePool, fetchSingleVm, - fetchAllVmEvents, fetchVms, } from './index' import { getConsoleOptions } from './console' import { fetchIsoFiles } from './storageDomains' import { fetchUnknownIcons } from './osIcons' -import { toJS } from '_/helpers' const BACKGROUND_REFRESH = 'BACKGROUND_REFRESH' @@ -116,7 +113,6 @@ const pagesRefreshers = { [C.CREATE_PAGE_TYPE]: refreshCreatePage, [C.CONSOLE_PAGE_TYPE]: refreshConsolePage, [C.SETTINGS_PAGE_TYPE]: loadUserOptions, - [C.EVENTS_PAGE_TYPE]: refreshEventsPage, } function* refreshListPage () { @@ -211,13 +207,9 @@ function* refreshListPage () { } function* refreshDetailPage ({ id: vmId, manualRefresh }) { - const { internalVm } = yield fetchAndPutSingleVm(Actions.getSingleVm({ vmId })) + yield fetchAndPutSingleVm(Actions.getSingleVm({ vmId })) yield getConsoleOptions(Actions.getConsoleOptions({ vmId })) - if (internalVm?.name) { - yield fetchLastVmEvents(Actions.getVmEvents({ vmId, vmName: internalVm.name, maxItems: 2 })) - } - // TODO: If the VM is from a Pool, refresh the Pool as well. // Load ISO images on manual refresh click only @@ -243,21 +235,6 @@ function* refreshConsolePage ({ id: vmId }) { } } -function* refreshEventsPage ({ id: vmId }) { - if (!vmId) { - return - } - const { internalVm } = yield fetchAndPutSingleVm(Actions.getSingleVm({ vmId, shallowFetch: true })) - const [[newestEvent]] = yield select(({ userMessages }) => [ - toJS(userMessages.getIn(['events', vmId], [])), - ]) - - const vmName = internalVm?.name - if (vmName) { - yield fetchAllVmEvents(Actions.getVmEvents({ vmId, vmName, newestEventId: newestEvent?.id, maxItems: 500 })) - } -} - // // *** Scheduler/Timer Sagas *** // diff --git a/src/sagas/index.js b/src/sagas/index.js index 5ca644285..d21251e84 100644 --- a/src/sagas/index.js +++ b/src/sagas/index.js @@ -21,8 +21,6 @@ import sagasVmChanges from './vmChanges' import sagasVmSnapshots from '_/components/VmDetails/cards/SnapshotsCard/sagas' import { - addLastVmEvents, - addVmEvents, updateVms, setVmSnapshots, @@ -381,31 +379,6 @@ function* clearEvents ({ payload: { records = [] } }) { yield fetchAllEvents() } -export function* fetchAllVmEvents (action) { - const { vmId } = action.payload - const { error, event: events } = yield callExternalAction(Api.eventsForVm, action) - - if (error || !Array.isArray(events)) { - yield put(addVmEvents({ events: [], vmId })) - return - } - - const internalEvents = events.map(event => Transforms.Event.toInternal({ event })) - yield put(addVmEvents({ events: internalEvents, vmId })) -} - -export function* fetchLastVmEvents (action) { - const { vmId } = action.payload - const { error, event: events } = yield callExternalAction(Api.eventsForVm, action) - - if (error || !Array.isArray(events)) { - return - } - - const internalEvents = events.map(event => Transforms.Event.toInternal({ event })) - yield put(addLastVmEvents({ events: internalEvents, vmId })) -} - export function* fetchVmSessions ({ vmId }) { const sessions = yield callExternalAction(Api.sessions, { payload: { vmId } }) diff --git a/src/utils/vms-sort.js b/src/utils/vms-sort.js index eba1f6c36..b683a07ae 100644 --- a/src/utils/vms-sort.js +++ b/src/utils/vms-sort.js @@ -11,14 +11,17 @@ const getFieldValueMap = (msg) => ({ export const SortFields = { NAME: { id: 'name', + isNumeric: false, messageDescriptor: { id: 'name' }, }, OS: { id: 'os', + isNumeric: false, messageDescriptor: { id: 'operatingSystem' }, }, STATUS: { id: 'status', + isNumeric: false, messageDescriptor: { id: 'status' }, }, }