blob: 7c29bd07ac4a2ceb80fdf4900eba30bf24834c96 [file] [log] [blame]
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
import Grid from '@mui/material/Grid'
import Paper from '@mui/material/Paper'
import { loader } from 'graphql.macro'
import React from 'react'
import { useState, useEffect, useMemo } from 'react'
import {
Box,
Checkbox,
FormControl,
FormControlLabel,
InputLabel,
MenuItem,
Select
} from '@mui/material'
import Node from '../../components/Node/Node'
import { useQuery } from '@apollo/client'
import NodeInfo from '../../models/node-info'
import OsInfo from '../../models/os-info'
import NoData from '../../components/NoData/NoData'
import Loading from '../../components/Loading/Loading'
import Error from '../../components/Error/Error'
import StereotypeInfo from '../../models/stereotype-info'
import browserVersion from '../../util/browser-version'
import Capabilities from '../../models/capabilities'
import { GridConfig } from '../../config'
import { NODES_QUERY } from '../../graphql/nodes'
import { GRID_SESSIONS_QUERY } from '../../graphql/sessions'
function Overview (): JSX.Element {
const { loading, error, data } = useQuery(NODES_QUERY, {
pollInterval: GridConfig.status.xhrPollingIntervalMillis,
fetchPolicy: 'network-only'
})
const { data: sessionsData } = useQuery(GRID_SESSIONS_QUERY, {
pollInterval: GridConfig.status.xhrPollingIntervalMillis,
fetchPolicy: 'network-only'
})
function compareSlotStereotypes(a: NodeInfo, b: NodeInfo, attribute: string): number {
const joinA = a.slotStereotypes.length === 1
? a.slotStereotypes[0][attribute]
: a.slotStereotypes.slice().map(st => st[attribute]).reverse().join(',')
const joinB = b.slotStereotypes.length === 1
? b.slotStereotypes[0][attribute]
: b.slotStereotypes.slice().map(st => st[attribute]).reverse().join(',')
return joinA.localeCompare(joinB)
}
const sortProperties = {
'platformName': (a, b) => compareSlotStereotypes(a, b, 'platformName'),
'status': (a, b) => (a.status ?? '').localeCompare(b.status ?? ''),
'uri': (a, b) => (a.uri ?? '').localeCompare(b.uri ?? ''),
'browserName': (a, b) => compareSlotStereotypes(a, b, 'browserName'),
'browserVersion': (a, b) => compareSlotStereotypes(a, b, 'browserVersion'),
'slotCount': (a, b) => {
const valueA = a.slotStereotypes.reduce((sum, st) => sum + st.slotCount, 0)
const valueB = b.slotStereotypes.reduce((sum, st) => sum + st.slotCount, 0)
return valueA < valueB ? -1 : 1
},
'id': (a, b) => (a.id < b.id ? -1 : 1)
}
const sortPropertiesLabel = {
'platformName': 'Platform Name',
'status': 'Status',
'uri': 'URI',
'browserName': 'Browser Name',
'browserVersion': 'Browser Version',
'slotCount': 'Slot Count',
'id': 'ID'
}
const [sortOption, setSortOption] = useState(Object.keys(sortProperties)[0])
const [sortOrder, setSortOrder] = useState(1)
const [sortedNodes, setSortedNodes] = useState<NodeInfo[]>([])
const [isDescending, setIsDescending] = useState(false)
const handleSortChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setSortOption(event.target.value as string)
}
const handleOrderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setIsDescending(event.target.checked)
setSortOrder(event.target.checked ? -1 : 1)
}
const sortNodes = useMemo(() => {
return (nodes: NodeInfo[], option: string, order: number) => {
const sortFn = sortProperties[option] || (() => 0)
return nodes.sort((a, b) => order * sortFn(a, b))
}
}, [sortOption, sortOrder])
useEffect(() => {
if (data) {
const unSortedNodes = data.nodesInfo.nodes.map((node) => {
const osInfo: OsInfo = {
name: node.osInfo.name,
version: node.osInfo.version,
arch: node.osInfo.arch
}
interface StereoTypeData {
stereotype: Capabilities;
slots: number;
}
const slotStereotypes = (JSON.parse(
node.stereotypes) as StereoTypeData[]).map((item) => {
const slotStereotype: StereotypeInfo = {
browserName: item.stereotype.browserName ?? '',
browserVersion: browserVersion(
item.stereotype.browserVersion ?? item.stereotype.version),
platformName: (item.stereotype.platformName
?? item.stereotype.platform) ?? '',
slotCount: item.slots,
rawData: item
}
return slotStereotype
})
const newNode: NodeInfo = {
uri: node.uri,
id: node.id,
status: node.status,
maxSession: node.maxSession,
slotCount: node.slotCount,
version: node.version,
osInfo: osInfo,
sessionCount: node.sessionCount ?? 0,
slotStereotypes: slotStereotypes
}
return newNode
})
setSortedNodes(sortNodes(unSortedNodes, sortOption, sortOrder))
}
}, [data, sortOption, sortOrder])
if (error !== undefined) {
const message = 'There has been an error while loading the Nodes from the Grid.'
const errorMessage = error?.networkError?.message
return (
<Grid container spacing={3}>
<Error message={message} errorMessage={errorMessage}/>
</Grid>
)
}
if (loading) {
return (
<Grid container spacing={3}>
<Loading/>
</Grid>
)
}
if (sortedNodes.length === 0) {
const shortMessage = 'The Grid has no registered Nodes yet.'
return (
<Grid container spacing={3}>
<NoData message={shortMessage}/>
</Grid>
)
}
return (
<Grid container>
<Grid item xs={12}
style={{ display: 'flex', justifyContent: 'flex-start' }}>
<FormControl variant="outlined" style={{ marginBottom: '16px' }}>
<InputLabel>Sort By</InputLabel>
<Box display="flex" alignItems="center">
<Select value={sortOption} onChange={handleSortChange}
label="Sort By" style={{ minWidth: '170px' }}>
{Object.keys(sortProperties).map((key) => (
<MenuItem key={key} value={key}>
{sortPropertiesLabel[key]}
</MenuItem>
))}
</Select>
<FormControlLabel
control={<Checkbox checked={isDescending}
onChange={handleOrderChange}/>}
label="Descending"
style={{ marginLeft: '8px' }}
/>
</Box>
</FormControl>
</Grid>
{sortedNodes.map((node, index) => {
return (
<Grid
item
lg={6}
sm={12}
xl={4}
xs={12}
key={index}
paddingX={1}
paddingY={1}
>
<Paper
sx={{
display: 'flex',
overflow: 'auto',
flexDirection: 'column'
}}
>
<Node
node={node}
sessions={sessionsData?.sessionsInfo?.sessions?.filter(
session => session.nodeId === node.id
) || []}
origin={window.location.origin}
/>
</Paper>
</Grid>
)
})}
</Grid>
)
}
export default Overview