forked from PelicanPlatform/pelican
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add table on director displaying top errors - Update GeoIPOverrideForm.tsx to include a map to click and drag a marker for faster geolocation
- Loading branch information
1 parent
1c987f6
commit 2333f12
Showing
9 changed files
with
307 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
web_ui/frontend/app/director/components/GeoIpErrorTable.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/** | ||
* Table printing out the errors that occurred during the GeoIP lookup. | ||
*/ | ||
|
||
import { styled } from '@mui/material/styles'; | ||
import { | ||
Paper, | ||
Table, | ||
TableCell, | ||
TableContainer, | ||
TableHead, | ||
TableRow, | ||
TableBody, | ||
Button, | ||
Modal, | ||
Typography, | ||
} from '@mui/material'; | ||
import { tableCellClasses } from '@mui/material/TableCell'; | ||
import useSWR from 'swr'; | ||
|
||
import { query_raw, VectorResponseData } from '@/components'; | ||
import { GeoIPOverride, GeoIPOverrideForm, ParameterValueRecord, submitConfigChange } from '@/components/configuration'; | ||
import { alertOnError } from '@/helpers/util'; | ||
import { Dispatch, useCallback, useContext, useMemo, useState } from 'react'; | ||
import { AlertDispatchContext, AlertReducerAction } from '@/components/AlertProvider'; | ||
import { getConfig } from '@/helpers/api'; | ||
import LatitudeLongitudePicker from '@/components/LatitudeLongitudePicker'; | ||
import ObjectModal from '@/components/configuration/Fields/ObjectField/ObjectModal'; | ||
import CircularProgress from '@mui/material/CircularProgress'; | ||
|
||
const GeoIpErrorTable = () => { | ||
|
||
const dispatch = useContext(AlertDispatchContext); | ||
const {data: ipErrors} = useSWR('geoip_errors', getIpErrorRows) | ||
|
||
const { data: config, mutate, error, isValidating } = useSWR<ParameterValueRecord | undefined>( | ||
'getConfig', | ||
async () => | ||
await alertOnError(getOverriddenGeoIps, 'Could not get config', dispatch) | ||
); | ||
const patchedIps = useMemo(() => { | ||
return config?.GeoIPOverrides === undefined ? | ||
[] : | ||
Object.values(config.GeoIPOverrides).map((x: GeoIPOverride) => x.ip) | ||
}, [config]) | ||
const [geoIPOverrides, setGeoIPOverrides] = useState<Record<string, GeoIPOverride>>({}) | ||
|
||
const [open, setOpen] = useState(false) | ||
const [ip, setIp] = useState("") | ||
|
||
const onSubmit = useCallback((x: GeoIPOverride) => { | ||
setGeoIPOverrides((p) => { | ||
return {...p, [x.ip]: x} | ||
}) | ||
setOpen(false) | ||
}, []) | ||
|
||
return ( | ||
<> | ||
<Typography variant={"h4"} pb={2}> | ||
Un-located Networks | ||
{isValidating && <CircularProgress size={"24px"} sx={{ml: 1}}/>} | ||
</Typography> | ||
<Paper sx={{overflow: "hidden"}}> | ||
<TableContainer sx={{ maxHeight: 250}}> | ||
<Table stickyHeader sx={{ minWidth: 650 }} size={"small"} aria-label="simple table"> | ||
<TableHead> | ||
<TableRow> | ||
<StyledTableCell>Un-located Network</StyledTableCell> | ||
<StyledTableCell align="right">Project</StyledTableCell> | ||
<StyledTableCell align="right">Source</StyledTableCell> | ||
<StyledTableCell align={"right"}># of Errors</StyledTableCell> | ||
<StyledTableCell align="right">Locate</StyledTableCell> | ||
</TableRow> | ||
</TableHead> | ||
<TableBody> | ||
{ipErrors && ipErrors | ||
.filter((x) => !patchedIps.includes(x.metric?.network)) | ||
.sort((a, b) => parseInt(b.value[1]) - parseInt(a.value[1])) | ||
.map((row) => ( | ||
<TableRow | ||
key={row.metric?.network} | ||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }} | ||
> | ||
<TableCell>{row.metric?.network}</TableCell> | ||
<TableCell align="right">{row.metric?.proj}</TableCell> | ||
<TableCell align="right">{row.metric?.source}</TableCell> | ||
<TableCell align="right">{parseInt(row.value[1]).toLocaleString()}</TableCell> | ||
<TableCell align="right"> | ||
<Button | ||
onClick={() => { | ||
setIp(row.metric?.network) | ||
setOpen(true) | ||
}} | ||
> | ||
{Object.keys(geoIPOverrides).includes(row.metric?.network) ? "Pending" : "Locate"} | ||
</Button> | ||
</TableCell> | ||
</TableRow> | ||
))} | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
{Object.keys(geoIPOverrides).length > 0 && ( | ||
<Button | ||
sx={{m:1}} | ||
variant={"outlined"} | ||
onClick={async () => { | ||
const overrides = [...Object.values(config?.GeoIPOverrides || []), ...Object.values(geoIPOverrides)] | ||
const value = await alertOnError(async () => submitConfigChange({GeoIPOverrides: overrides}), 'Could not submit IP patches', dispatch) | ||
if(value !== undefined) { | ||
mutate() | ||
setGeoIPOverrides({}) | ||
} | ||
}} | ||
> | ||
Submit IP Patches ({Object.keys(geoIPOverrides).length}) | ||
</Button> | ||
)} | ||
</Paper> | ||
<ObjectModal name={"Locate Network IP"} handleClose={() => setOpen(!open)} open={open}> | ||
<GeoIPOverrideForm value={{ip: ip, coordinate: {lat: "37", long: "20"}}} onSubmit={onSubmit} /> | ||
</ObjectModal> | ||
</> | ||
) | ||
} | ||
|
||
interface GeoUpdateFormProps { | ||
open: boolean; | ||
onClose: () => void; | ||
ip: string; | ||
} | ||
|
||
const StyledTableCell = styled(TableCell)(({ theme }) => ({ | ||
|
||
[`&.${tableCellClasses.head}`]: { | ||
backgroundColor: theme.palette.warning.main | ||
}, | ||
})); | ||
|
||
const getIpErrorRows = async () => { | ||
const response = await query_raw<VectorResponseData>("last_over_time(pelican_director_geoip_errors[1d])") | ||
return response.data.result | ||
} | ||
|
||
const getOverriddenGeoIps = async () => { | ||
let tries = 0 | ||
while(tries < 2) { | ||
try { | ||
const response = await getConfig() | ||
const config = await response.json() | ||
return {GeoIPOverrides: config.GeoIPOverrides} | ||
} catch(e) { | ||
tries++ | ||
await new Promise(r => setTimeout(r, (10 ** tries) * 500)); | ||
|
||
} | ||
} | ||
} | ||
|
||
export default GeoIpErrorTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './DirectorCard'; | ||
export * from './DirectorCardList'; | ||
export * from './NamespaceCard'; | ||
export { default as GeoIpErrorTable } from './GeoIpErrorTable'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
/** | ||
* A form element that allows the user to pick a latitude and longitude | ||
*/ | ||
import { DefaultMap } from '@/components/Map'; | ||
import { Box } from '@mui/material'; | ||
|
||
interface LatitudeLongitudePickerProps { | ||
latitude: number; | ||
longitude: number; | ||
setLatitude: (latitude: number) => void; | ||
setLongitude: (longitude: number) => void; | ||
} | ||
|
||
const LatitudeLongitudePicker = ({ | ||
latitude, | ||
longitude, | ||
setLatitude, | ||
setLongitude, | ||
}: LatitudeLongitudePickerProps) => { | ||
return ( | ||
<div> | ||
<Box> | ||
<DefaultMap /> | ||
</Box> | ||
<label> | ||
Latitude: | ||
<input | ||
type="number" | ||
value={latitude} | ||
onChange={(e) => setLatitude(parseFloat(e.target.value))} | ||
/> | ||
</label> | ||
<label> | ||
Longitude: | ||
<input | ||
type="number" | ||
value={longitude} | ||
onChange={(e) => setLongitude(parseFloat(e.target.value))} | ||
/> | ||
</label> | ||
</div> | ||
); | ||
} | ||
|
||
export default LatitudeLongitudePicker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* A form element that allows the user to pick a latitude and longitude | ||
*/ | ||
import { DefaultMap } from '@/components/Map'; | ||
import { Marker, NavigationControl } from 'react-map-gl/maplibre'; | ||
import { FmdGood } from '@mui/icons-material'; | ||
import React, { useCallback } from 'react'; | ||
import { LngLat } from 'maplibre-gl'; | ||
|
||
interface LatitudeLongitudePickerProps { | ||
latitude: number; | ||
longitude: number; | ||
setLatitude: (latitude: number) => void; | ||
setLongitude: (longitude: number) => void; | ||
zoom?: number; | ||
} | ||
|
||
const LatitudeLongitudePicker = ({ | ||
latitude, | ||
longitude, | ||
setLatitude, | ||
setLongitude, | ||
zoom | ||
}: LatitudeLongitudePickerProps) => { | ||
|
||
const updateLatLng = useCallback((lngLat: LngLat) => { | ||
setLatitude(parseFloat(lngLat.lat.toFixed(5))); | ||
setLongitude(parseFloat(lngLat.lng.toFixed(5))); | ||
}, []); | ||
|
||
const tempLongitude = Number.isNaN(longitude) ? 0 : longitude | ||
const tempLatitude = Number.isNaN(latitude) ? 0 : latitude | ||
|
||
return ( | ||
<DefaultMap | ||
initialViewState={{ | ||
longitude: tempLongitude, | ||
latitude: tempLatitude, | ||
zoom: zoom || 0, | ||
}} | ||
scrollZoom={false} | ||
style={{ width: '100%', height: '100%' }} | ||
onClick={(e) => updateLatLng(e.lngLat)} | ||
> | ||
<NavigationControl /> | ||
<Marker | ||
longitude={tempLongitude} | ||
latitude={tempLatitude} | ||
anchor='bottom' | ||
draggable={true} | ||
onDrag={(e) => updateLatLng(e.lngLat as LngLat)} | ||
> | ||
<FmdGood /> | ||
</Marker> | ||
</DefaultMap> | ||
); | ||
} | ||
|
||
export default LatitudeLongitudePicker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.