Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions web_ui/frontend/app/director/components/DirectorCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Authenticated, secureFetch } from '@/helpers/login';
import React, { useContext, useRef, useState } from 'react';
import React, { useContext, useEffect, useRef, useState } from 'react';
import {
Avatar,
Box,
Expand All @@ -23,17 +23,21 @@ import Link from 'next/link';
import { User } from '@/index';
import { alertOnError, getErrorMessage } from '@/helpers/util';
import { DirectorDropdown } from '@/app/director/components/DirectorDropdown';
import { allowServer, filterServer } from '@/helpers/api';
import { ServerDetailed, ServerGeneral } from '@/types';
import { allowServer, filterServer, getDirectorServer } from '@/helpers/api';
import { AlertDispatchContext } from '@/components/AlertProvider';

export interface DirectorCardProps {
server: Server;
server: ServerGeneral;
authenticated?: User;
}

export const DirectorCard = ({ server, authenticated }: DirectorCardProps) => {
const [disabled, setDisabled] = useState<boolean>(false);
const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
const [detailedServer, setDetailedServer] = useState<
ServerDetailed | undefined
>();

const dispatch = useContext(AlertDispatchContext);

Expand All @@ -58,7 +62,19 @@ export const DirectorCard = ({ server, authenticated }: DirectorCardProps) => {
server.healthStatus === 'Error' ? red[100] : 'secondary.main',
p: 1,
}}
onClick={() => setDropdownOpen(!dropdownOpen)}
onClick={async () => {
setDropdownOpen(!dropdownOpen);
if (detailedServer === undefined) {
alertOnError(
async () => {
const response = await getDirectorServer(server.name);
setDetailedServer(await response.json());
},
'Failed to fetch server details',
dispatch
);
}
}}
>
<Box my={'auto'} ml={1} display={'flex'} flexDirection={'row'}>
<NamespaceIcon
Expand Down Expand Up @@ -124,7 +140,10 @@ export const DirectorCard = ({ server, authenticated }: DirectorCardProps) => {
</Box>
</Box>
</Paper>
<DirectorDropdown server={server} transition={dropdownOpen} />
<DirectorDropdown
server={detailedServer || server}
transition={dropdownOpen}
/>
</>
);
};
Expand Down
3 changes: 2 additions & 1 deletion web_ui/frontend/app/director/components/DirectorCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DirectorCard, DirectorCardProps } from './';
import { Server } from '@/index';
import { BooleanToggleButton, CardList } from '@/components';
import useFuse from '@/helpers/useFuse';
import { ServerGeneral } from '@/types';

interface DirectorCardListProps {
data: Partial<DirectorCardProps>[];
Expand Down Expand Up @@ -88,7 +89,7 @@ export function DirectorCardList({ data, cardProps }: DirectorCardListProps) {
);
}

const serverHasError = (server?: Server) => {
const serverHasError = (server?: ServerGeneral) => {
return server?.healthStatus === 'Error';
};

Expand Down
140 changes: 54 additions & 86 deletions web_ui/frontend/app/director/components/DirectorDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { Capabilities, Server, StringTree } from '@/index';
import {
CapabilitiesChip,
CapabilitiesDisplay,
Dropdown,
InformationSpan,
} from '@/components';
import { CapabilitiesChip, Dropdown, InformationSpan } from '@/components';
import { Box, Grid, Typography } from '@mui/material';
import DirectoryTree from '@/components/DirectoryTree';
import React from 'react';
import { SinglePointMap } from '@/components/Map';
import { ServerCapabilitiesTable } from '@/components/ServerCapabilitiesTable';
import { Capabilities, ServerDetailed, ServerGeneral } from '@/types';
import { Capability } from '@/components/configuration';

interface DirectorDropdownProps {
server: Server;
server: ServerGeneral | ServerDetailed;
transition: boolean;
}

Expand All @@ -20,97 +17,68 @@ export const DirectorDropdown = ({
transition,
}: DirectorDropdownProps) => {
return (
<Dropdown transition={transition} flexDirection={'column'}>
<Grid container spacing={1}>
<Grid item xs={12} md={7}>
<InformationSpan name={'Type'} value={server.type} />
<InformationSpan name={'Status'} value={server.healthStatus} />
<InformationSpan name={'URL'} value={server.url} />
<InformationSpan
name={'Longitude'}
value={server.longitude.toString()}
/>
<InformationSpan
name={'Latitude'}
value={server.latitude.toString()}
/>
</Grid>
<Grid item xs={12} md={5}>
<Box
borderRadius={1}
height={'100%'}
minHeight={'140px'}
overflow={'hidden'}
>
{transition && (
<SinglePointMap
point={{ lat: server.latitude, lng: server.longitude }}
/>
)}
</Box>
<>
<Dropdown transition={transition} flexDirection={'column'}>
<Grid container spacing={1}>
<Grid item xs={12} md={7}>
<InformationSpan name={'Type'} value={server.type} />
<InformationSpan name={'Status'} value={server.healthStatus} />
<InformationSpan name={'URL'} value={server.url} />
<InformationSpan
name={'Longitude'}
value={server.longitude.toString()}
/>
<InformationSpan
name={'Latitude'}
value={server.latitude.toString()}
/>
</Grid>
<Grid item xs={12} md={5}>
<Box
borderRadius={1}
height={'100%'}
minHeight={'140px'}
overflow={'hidden'}
>
{transition && (
<SinglePointMap
point={{ lat: server.latitude, lng: server.longitude }}
/>
)}
</Box>
</Grid>
</Grid>
</Grid>
{server.capabilities && (
<Box mt={1}>
<CapabilitiesRow capabilities={server.capabilities} />
<Box sx={{ my: 1 }}>
<ServerCapabilitiesTable server={server} />
</Box>
)}
<Box sx={{ my: 1 }}>
<Typography
variant={'body2'}
sx={{ fontWeight: 500, display: 'inline', mr: 2 }}
>
Namespace Prefixes
</Typography>
<DirectoryTree data={directoryListToTree(server.namespacePrefixes)} />
</Box>
</Dropdown>
</Dropdown>
</>
);
};

const CapabilitiesRow = ({ capabilities }: { capabilities: Capabilities }) => {
export const CapabilitiesRow = ({
capabilities,
parentCapabilities,
}: {
capabilities: Capabilities;
parentCapabilities?: Capabilities;
}) => {
return (
<Grid container spacing={1}>
{Object.entries(capabilities).map(([key, value]) => {
const castKey = key as keyof Capabilities;
return (
<Grid item md={12 / 5} sm={12 / 4} xs={12 / 2} key={key}>
<CapabilitiesChip name={key} value={value} />
<CapabilitiesChip
name={key}
value={value}
parentValue={
parentCapabilities ? parentCapabilities[castKey] : undefined
}
/>
</Grid>
);
})}
</Grid>
);
};

const directoryListToTree = (directoryList: string[]): StringTree => {
let tree = {};
directoryList.forEach((directory) => {
const path = directory
.split('/')
.filter((x) => x != '')
.map((x) => '/' + x);
tree = directoryListToTreeHelper(path, tree);
});

return tree;
};

const directoryListToTreeHelper = (
path: string[],
tree: StringTree
): true | StringTree => {
if (path.length == 0) {
return true;
}

if (!tree[path[0]] || tree[path[0]] === true) {
tree[path[0]] = {};
}

tree[path[0]] = directoryListToTreeHelper(
path.slice(1),
tree[path[0]] as StringTree
);

return tree;
};
77 changes: 77 additions & 0 deletions web_ui/frontend/app/director/components/NamespaceCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { secureFetch } from '@/helpers/login';
import React, { useContext, useState } from 'react';
import { Box, Paper, Typography } from '@mui/material';
import { NamespaceIcon } from '@/components/Namespace/index';
import { NamespaceDropdown } from './NamespaceDropdown';
import { DirectorNamespace, ServerDetailed, ServerGeneral } from '@/types';
import { getDirectorServer } from '@/helpers/api';
import { alertOnError } from '@/helpers/util';
import { AlertDispatchContext } from '@/components/AlertProvider';

export interface NamespaceCardProps {
namespace: DirectorNamespace;
}

export const NamespaceCard = ({ namespace }: NamespaceCardProps) => {
const dispatch = useContext(AlertDispatchContext);
const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
const [servers, setServers] = useState<ServerDetailed[] | undefined>(
undefined
);

return (
<>
<Paper>
<Box
sx={{
cursor: 'pointer',
display: 'flex',
width: '100%',
justifyContent: 'space-between',
border: 'solid #ececec 1px',
borderRadius: '4px',
transition: 'background-color 0.3s',
p: 1,
}}
onClick={async () => {
setDropdownOpen(!dropdownOpen);
if (servers === undefined) {
alertOnError(
async () => setServers(await getAssociatedServers(namespace)),
'Failed to fetch servers',
dispatch
);
}
}}
>
<Box my={'auto'} ml={1} display={'flex'} flexDirection={'row'}>
<NamespaceIcon serverType={'namespace'} />
<Typography sx={{ pt: '2px' }}>{namespace.path}</Typography>
</Box>
</Box>
</Paper>
<NamespaceDropdown
namespace={namespace}
servers={servers}
transition={dropdownOpen}
/>
</>
);
};

const getAssociatedServers = async (namespace: DirectorNamespace) => {
const servers = await Promise.all(
[...namespace.origins, ...namespace.caches].map(async (name) =>
(await getDirectorServer(name)).json()
)
);

// Alert the console if any servers are undefined, as this is unlikely to happen naturally
if (servers.some((s) => s === undefined)) {
console.error('Failed to fetch all servers, some are undefined');
}

return servers.filter((s) => s !== undefined) as ServerDetailed[];
};

export default NamespaceCard;
31 changes: 31 additions & 0 deletions web_ui/frontend/app/director/components/NamespaceCardList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { useState } from 'react';
import { Box, TextField } from '@mui/material';
import { NamespaceCard, NamespaceCardProps } from './';
import { CardList } from '@/components';
import useFuse from '@/helpers/useFuse';

interface NamespaceCardListProps {
data?: Partial<NamespaceCardProps>[];
}

export function NamespaceCardList({ data }: NamespaceCardListProps) {
const [search, setSearch] = useState<string>('');

const searchedData = useFuse<Partial<NamespaceCardProps>>(data || [], search);

return (
<Box>
<Box sx={{ pb: 1 }}>
<TextField
size={'small'}
value={search}
onChange={(e) => setSearch(e.target.value)}
label='Search'
/>
</Box>
<CardList data={searchedData} Card={NamespaceCard} />
</Box>
);
}

export default NamespaceCardList;
Loading
Loading