added the log page and the datadog api
This commit is contained in:
parent
d800aedcdf
commit
428e45f148
8 changed files with 890 additions and 3 deletions
133
src/common/LogCard.tsx
Normal file
133
src/common/LogCard.tsx
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Drawer,
|
||||||
|
Grid2 as Grid,
|
||||||
|
Theme,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
import { grey, lightBlue, red, orange } from "@mui/material/colors";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import moment from "moment";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
index: number;
|
||||||
|
datalog: pond.Log;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
logbox: {
|
||||||
|
width: "100%",
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
"&:hover": {
|
||||||
|
filter: "brightness(1.15)",
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function LogCard(props: Props) {
|
||||||
|
const { datalog, index } = props;
|
||||||
|
let log = datalog;
|
||||||
|
let attributes = JSON.parse(log.attributes);
|
||||||
|
// let elementKey = event.target.getAttribute('a-key');
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const [drawer, setDrawer] = useState(false);
|
||||||
|
|
||||||
|
if (log.level === undefined) log.level = "info";
|
||||||
|
|
||||||
|
let barColor: string = lightBlue[400];
|
||||||
|
if (log.level.toLowerCase() === "debug") {
|
||||||
|
barColor = grey[400];
|
||||||
|
} else if (log.level.toLowerCase() === "warning") {
|
||||||
|
barColor = orange[500];
|
||||||
|
} else if (log.level.toLowerCase() === "error") {
|
||||||
|
barColor = red[500];
|
||||||
|
} else if (log.level.toLowerCase() === "critical") {
|
||||||
|
barColor = red["A100"];
|
||||||
|
}
|
||||||
|
let bc = index % 2 === 0 ? theme.palette.background.default : theme.palette.background.paper;
|
||||||
|
|
||||||
|
const lineSummary = () => {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" justifyContent="space-around" alignContent="center">
|
||||||
|
<Grid size={2}>
|
||||||
|
{moment(log.timestamp).format("MMM Do, h:mm:ss a")}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={1}>
|
||||||
|
{log.service}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={1}>
|
||||||
|
{log.device}
|
||||||
|
</Grid>
|
||||||
|
<Grid size={2}>
|
||||||
|
{log.message}
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography noWrap variant="body2">
|
||||||
|
{log.attributes}
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Drawer open={drawer} onClose={() => setDrawer(false)} anchor="right">
|
||||||
|
<Box maxWidth={theme.spacing(68)} minWidth={theme.spacing(62)} padding={2}>
|
||||||
|
<Grid container direction="column" justifyContent="space-evenly">
|
||||||
|
<Grid container direction="row">
|
||||||
|
<Grid size={4}>
|
||||||
|
<Typography>Device:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography>{log.device}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container direction="row">
|
||||||
|
<Grid size={4}>
|
||||||
|
<Typography>Timestamp:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography>{moment(log.timestamp).format("MMMM ddd Do, h:mm:ss a")}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container direction="row">
|
||||||
|
<Grid size={4}>
|
||||||
|
<Typography>Level:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography>{log.level}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Grid container direction="row">
|
||||||
|
<Grid size={4}>
|
||||||
|
<Typography>Service:</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography>{log.service}</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
{
|
||||||
|
<Typography variant="caption">
|
||||||
|
<pre>{JSON.stringify(attributes, null, 2)}</pre>
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
<Box
|
||||||
|
className={classes.logbox}
|
||||||
|
style={{ backgroundColor: bc, borderLeft: "2px solid " + barColor }}
|
||||||
|
onClick={() => setDrawer(true)}>
|
||||||
|
{lineSummary()}
|
||||||
|
</Box>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
609
src/common/LogsDisplay.tsx
Normal file
609
src/common/LogsDisplay.tsx
Normal file
|
|
@ -0,0 +1,609 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
CircularProgress,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
MenuItem,
|
||||||
|
TextField,
|
||||||
|
Theme,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from "@mui/material";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import LogCard from "./LogCard";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useDataDogProxyAPI } from "providers";
|
||||||
|
import { Cell, Legend, Pie, PieChart, ResponsiveContainer } from "recharts";
|
||||||
|
import { grey, lightBlue, orange, red } from "@mui/material/colors";
|
||||||
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
|
||||||
|
interface DataAmount {
|
||||||
|
dataAmount: number;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
deviceID?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const levelOptions = [
|
||||||
|
{
|
||||||
|
key: "All",
|
||||||
|
val: "all"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Info",
|
||||||
|
val: "info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Debug",
|
||||||
|
val: "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Warning",
|
||||||
|
val: "warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Error",
|
||||||
|
val: "error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Critical",
|
||||||
|
val: "critical"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const sortOptions = [
|
||||||
|
{
|
||||||
|
key: "Ascending",
|
||||||
|
val: "asc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "Descending",
|
||||||
|
val: "desc"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
sidebox: {
|
||||||
|
borderRadius: 5,
|
||||||
|
borderColor: theme.palette.text.primary,
|
||||||
|
border: "1px solid",
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
marginRight: theme.spacing(2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function LogsDisplay(props: Props) {
|
||||||
|
const { deviceID } = props;
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const [sort, setSort] = useState("desc");
|
||||||
|
const [limit, setLimit] = useState(50);
|
||||||
|
const [level, setLevel] = useState("all");
|
||||||
|
const [service, setService] = useState("all");
|
||||||
|
const [logs, setLogs] = useState<pond.Log[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [device, setDevice] = useState(0);
|
||||||
|
|
||||||
|
const [usage, setUsage] = useState<DataAmount[]>([]);
|
||||||
|
const [customQuery, setCustomQuery] = useState<string>("");
|
||||||
|
// const [pieLabel, setPieLabel] = useState("");
|
||||||
|
// const [pieData, setPieData] = useState("");
|
||||||
|
|
||||||
|
const DDAPI = useDataDogProxyAPI();
|
||||||
|
|
||||||
|
const [from, setFrom] = useState(
|
||||||
|
moment()
|
||||||
|
.subtract(1, "day")
|
||||||
|
.format("YYYY-MM-DD")
|
||||||
|
);
|
||||||
|
const [to, setTo] = useState(
|
||||||
|
moment()
|
||||||
|
.add("2", "days")
|
||||||
|
.format("YYYY-MM-DD")
|
||||||
|
);
|
||||||
|
|
||||||
|
const addToQuery = (query: string, input: string) => {
|
||||||
|
if (query.length > 0) query = query + " AND ";
|
||||||
|
query = query + input;
|
||||||
|
return query;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLogs = () => {
|
||||||
|
setLoading(true);
|
||||||
|
let query = "";
|
||||||
|
if (level !== "all")
|
||||||
|
query = addToQuery(query, "@level:(" + level + " OR " + level.toUpperCase() + ")");
|
||||||
|
if (deviceID) {
|
||||||
|
query = addToQuery(query, "@device:" + deviceID);
|
||||||
|
} else if (device > 0) {
|
||||||
|
query = addToQuery(query, "@device:" + device);
|
||||||
|
}
|
||||||
|
if (service !== "all") query = addToQuery(query, "service:" + service);
|
||||||
|
setCustomQuery(query);
|
||||||
|
DDAPI.listLogs(from, to, sort, limit, query)
|
||||||
|
.then(resp => {
|
||||||
|
setLogs(resp.data.logs);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCustomQuery = () => {
|
||||||
|
setLoading(true);
|
||||||
|
DDAPI.listLogs(from, to, sort, limit, customQuery)
|
||||||
|
.then(resp => {
|
||||||
|
setLogs(resp.data.logs);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleGrid = () => {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" alignItems="center">
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
label="From:"
|
||||||
|
type="date"
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
value={from}
|
||||||
|
onChange={e => setFrom(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
label="To:"
|
||||||
|
type="date"
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
value={to}
|
||||||
|
onChange={e => setTo(e.target.value)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
select
|
||||||
|
label="level"
|
||||||
|
value={level}
|
||||||
|
onChange={event => setLevel(event.target.value)}>
|
||||||
|
{levelOptions.map(op => (
|
||||||
|
<MenuItem key={op.key} value={op.val}>
|
||||||
|
{op.key}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
label="Number of Logs:"
|
||||||
|
type="number"
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
value={limit}
|
||||||
|
onChange={e => setLimit(+e.target.value)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
select
|
||||||
|
label="Sort Order"
|
||||||
|
value={sort}
|
||||||
|
onChange={event => setSort(event.target.value)}>
|
||||||
|
{sortOptions.map(op => (
|
||||||
|
<MenuItem key={op.key} value={op.val}>
|
||||||
|
{op.key}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
{!deviceID && (
|
||||||
|
<Grid item xs>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
type="number"
|
||||||
|
label="Device"
|
||||||
|
value={device}
|
||||||
|
onChange={event => setDevice(parseInt(event.target.value))}>
|
||||||
|
{sortOptions.map(op => (
|
||||||
|
<MenuItem key={op.key} value={op.val}>
|
||||||
|
{op.key}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</TextField>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item xs style={{ textAlign: "center" }}>
|
||||||
|
{loading ? (
|
||||||
|
<CircularProgress />
|
||||||
|
) : (
|
||||||
|
<Button variant="contained" color="primary" onClick={getLogs}>
|
||||||
|
Get Logs
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const endAdornment = () => {
|
||||||
|
// if (loading) return <CircularProgress/>
|
||||||
|
return (
|
||||||
|
<IconButton onClick={getCustomQuery} disabled={loading}>
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchBar = () => {
|
||||||
|
return (
|
||||||
|
<Grid container direction="row" alignItems="center" style={{ marginTop: theme.spacing(1) }}>
|
||||||
|
<Grid item xs>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
label="Query:"
|
||||||
|
InputLabelProps={{
|
||||||
|
shrink: true
|
||||||
|
}}
|
||||||
|
InputProps={{ endAdornment: endAdornment() }}
|
||||||
|
value={customQuery}
|
||||||
|
onChange={e => setCustomQuery(e.target.value)}
|
||||||
|
onKeyPress={e => {
|
||||||
|
if (e.key === "Enter" && !loading) {
|
||||||
|
getCustomQuery();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (logs.length < 1) {
|
||||||
|
let data = { dataAmount: 100, name: "Dummy", color: theme.palette.background.paper };
|
||||||
|
setUsage([data]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let debugData = { dataAmount: 0, name: "Debug", color: grey[500] };
|
||||||
|
let infoData = { dataAmount: 0, name: "Info", color: lightBlue[200] };
|
||||||
|
let warningData = { dataAmount: 0, name: "Warning", color: orange[500] };
|
||||||
|
let errorData = { dataAmount: 0, name: "Error", color: red[500] };
|
||||||
|
let criticalData = { dataAmount: 0, name: "Critical", color: red["A100"] };
|
||||||
|
logs.forEach(log => {
|
||||||
|
switch (log.level.toLowerCase()) {
|
||||||
|
case "debug":
|
||||||
|
debugData.dataAmount++;
|
||||||
|
break;
|
||||||
|
case "info":
|
||||||
|
infoData.dataAmount++;
|
||||||
|
break;
|
||||||
|
case "warning":
|
||||||
|
warningData.dataAmount++;
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
errorData.dataAmount++;
|
||||||
|
break;
|
||||||
|
case "critical":
|
||||||
|
criticalData.dataAmount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setUsage([debugData, infoData, warningData, errorData, criticalData]);
|
||||||
|
}, [logs, theme.palette.background.paper]);
|
||||||
|
|
||||||
|
const logPieChart = () => {
|
||||||
|
return (
|
||||||
|
<Box width={"100%"} style={{ aspectRatio: "1" }} padding={2} paddingLeft={1}>
|
||||||
|
<ResponsiveContainer>
|
||||||
|
<PieChart>
|
||||||
|
<Legend
|
||||||
|
verticalAlign="middle"
|
||||||
|
content={() => (
|
||||||
|
<Box textAlign="center" marginY={0.5}>
|
||||||
|
{/* <Typography>
|
||||||
|
hi
|
||||||
|
</Typography> */}
|
||||||
|
{logs.length < 1 && <Typography>No Logs</Typography>}
|
||||||
|
{/* {device.status.bytesTimestamp.length > 1 && (
|
||||||
|
<Typography>{pieData}</Typography>
|
||||||
|
)} */}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Pie
|
||||||
|
data={usage}
|
||||||
|
dataKey="dataAmount"
|
||||||
|
nameKey="name"
|
||||||
|
innerRadius={0}
|
||||||
|
cx="50%"
|
||||||
|
cy="50%"
|
||||||
|
outerRadius={"100%"}
|
||||||
|
stroke="none"
|
||||||
|
//cornerRadius={6}
|
||||||
|
>
|
||||||
|
{usage.map((entry, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`cell-${index}`}
|
||||||
|
fill={entry.color}
|
||||||
|
style={{ outline: "none !important" }}
|
||||||
|
onMouseOver={() => {
|
||||||
|
// setPieLabel(entry.name);
|
||||||
|
// setPieData(entry.dataAmount.toString());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// const check = (label: string) => {
|
||||||
|
// if (service === label) {
|
||||||
|
// setService("all");
|
||||||
|
// } else {
|
||||||
|
// setService(label);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
const serviceSelector = () => {
|
||||||
|
return (
|
||||||
|
<Box className={classes.sidebox} padding={2} marginTop={1}>
|
||||||
|
<Typography>Service</Typography>
|
||||||
|
<FormGroup aria-label="position">
|
||||||
|
<FormControlLabel
|
||||||
|
// value="end"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={service === "all"}
|
||||||
|
onChange={() => setService("all")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="all"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
// value="end"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={service === "pond"}
|
||||||
|
onChange={() => setService("pond")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="pond"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
// value="end"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={service === "quay"}
|
||||||
|
onChange={() => setService("quay")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="quay"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
// value="end"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={service === "traefik"}
|
||||||
|
onChange={() => setService("traefik")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="traefik"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
// value="end"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={service === "webui"}
|
||||||
|
onChange={() => setService("webui")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="webui"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
// value="end"
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
color="primary"
|
||||||
|
checked={service === "agent"}
|
||||||
|
onChange={() => setService("agent")}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="agent"
|
||||||
|
labelPlacement="end"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPercent = (dataNum: number) => {
|
||||||
|
if (logs.length < 1 || logs.length < dataNum) return 0 + "%";
|
||||||
|
let newNum = (dataNum / logs.length) * 100;
|
||||||
|
return newNum.toFixed(0) + "%";
|
||||||
|
};
|
||||||
|
|
||||||
|
const logLevels = () => {
|
||||||
|
let debugNum = 0;
|
||||||
|
let infoNum = 0;
|
||||||
|
let warningNum = 0;
|
||||||
|
let errorNum = 0;
|
||||||
|
let criticalNum = 0;
|
||||||
|
usage.forEach(data => {
|
||||||
|
switch (data.name) {
|
||||||
|
case "Debug":
|
||||||
|
debugNum = data.dataAmount;
|
||||||
|
break;
|
||||||
|
case "Info":
|
||||||
|
infoNum = data.dataAmount;
|
||||||
|
break;
|
||||||
|
case "Warning":
|
||||||
|
warningNum = data.dataAmount;
|
||||||
|
break;
|
||||||
|
case "Error":
|
||||||
|
errorNum = data.dataAmount;
|
||||||
|
break;
|
||||||
|
case "Critical":
|
||||||
|
criticalNum = data.dataAmount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Box className={classes.sidebox}>
|
||||||
|
<List dense>
|
||||||
|
<ListItem style={{ display: "flex", float: "left" }}>
|
||||||
|
<Typography style={{ color: lightBlue[400] }}>■</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: theme.spacing(1) }}>
|
||||||
|
Info: {infoNum}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: "auto" }}>
|
||||||
|
({getPercent(infoNum)})
|
||||||
|
</Typography>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Typography style={{ color: grey[500] }}>■</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: theme.spacing(1) }}>
|
||||||
|
Debug: {debugNum}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: "auto" }}>
|
||||||
|
({getPercent(debugNum)})
|
||||||
|
</Typography>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Typography style={{ color: orange[500] }}>■</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: theme.spacing(1) }}>
|
||||||
|
Warning: {warningNum}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: "auto" }}>
|
||||||
|
({getPercent(warningNum)})
|
||||||
|
</Typography>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Typography style={{ color: red[500] }}>■</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: theme.spacing(1) }}>
|
||||||
|
Error: {errorNum}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" align="right" style={{ marginLeft: "auto" }}>
|
||||||
|
({getPercent(errorNum)})
|
||||||
|
</Typography>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem>
|
||||||
|
<Typography style={{ color: red[900] }}>■</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: theme.spacing(1) }}>
|
||||||
|
Critical: {criticalNum}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" style={{ marginLeft: "auto" }}>
|
||||||
|
({getPercent(criticalNum)})
|
||||||
|
</Typography>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
|
||||||
|
{/* Title Section */}
|
||||||
|
<Box margin={2}>
|
||||||
|
{titleGrid()}
|
||||||
|
{searchBar()}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Header Section */}
|
||||||
|
<Grid container style={{ height: "100%" }}>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<Typography>Log Metrics</Typography>
|
||||||
|
{/* Pie chart and service selector will go here */}
|
||||||
|
{logPieChart()}
|
||||||
|
{logLevels()}
|
||||||
|
{/* <br /> */}
|
||||||
|
{serviceSelector()}
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={10} style={{ height: "100%", overflow: "hidden" }}>
|
||||||
|
<Grid container direction="row" justifyContent="space-around" alignItems="center">
|
||||||
|
<Grid item xs={2}>
|
||||||
|
Timestamp
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={1}>
|
||||||
|
Service
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={1}>
|
||||||
|
Device
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
Message
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs zeroMinWidth>
|
||||||
|
Content
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Scrollable List Section */}
|
||||||
|
<Box
|
||||||
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
overflowY: "auto",
|
||||||
|
textAlign: logs.length < 1 ? "center" : "left"
|
||||||
|
}}>
|
||||||
|
{logs.map((l, i) => (
|
||||||
|
<LogCard key={i} datalog={l} index={i} />
|
||||||
|
))}
|
||||||
|
{logs.length < 1 && (
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
color="textSecondary"
|
||||||
|
style={{ marginTop: theme.spacing(12), alignSelf: "center" }}>
|
||||||
|
Found logs will be displayed here
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -37,6 +37,7 @@ const BinCableEstimator = lazy(() => import("pages/BinCableEstimator"));
|
||||||
const APIDocs = lazy(() => import("pages/APIDocs"));
|
const APIDocs = lazy(() => import("pages/APIDocs"));
|
||||||
const Fields = lazy(()=> import("pages/Fields"));
|
const Fields = lazy(()=> import("pages/Fields"));
|
||||||
const Marketplace = lazy(() => import("userFeatures/UserFeatures"))
|
const Marketplace = lazy(() => import("userFeatures/UserFeatures"))
|
||||||
|
const Logs = lazy(() => import("pages/Logs"))
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
toggleTheme: () => void;
|
toggleTheme: () => void;
|
||||||
|
|
@ -314,6 +315,9 @@ export default function Router(props: Props) {
|
||||||
{user.hasFeature("developer") &&
|
{user.hasFeature("developer") &&
|
||||||
<Route path="api" element={<APIDocs />} />
|
<Route path="api" element={<APIDocs />} />
|
||||||
}
|
}
|
||||||
|
{user.hasFeature("admin") &&
|
||||||
|
<Route path="logs" element={<Logs />} />
|
||||||
|
}
|
||||||
<Route path="fields" element={<Fields />} />
|
<Route path="fields" element={<Fields />} />
|
||||||
<Route path="marketplace" element={<Marketplace />} />
|
<Route path="marketplace" element={<Marketplace />} />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import TasksIcon from "products/Construction/TasksIcon";
|
||||||
import CableIcon from "products/Bindapt/CableIcon";
|
import CableIcon from "products/Bindapt/CableIcon";
|
||||||
import FieldListIcon from "products/AgIcons/FieldList";
|
import FieldListIcon from "products/AgIcons/FieldList";
|
||||||
import MarketplaceIcon from "products/CommonIcons/marketplaceIcon";
|
import MarketplaceIcon from "products/CommonIcons/marketplaceIcon";
|
||||||
|
import DataDuckIcon from "products/Bindapt/DataDuckIcon";
|
||||||
|
|
||||||
const drawerWidth = 230;
|
const drawerWidth = 230;
|
||||||
|
|
||||||
|
|
@ -367,6 +368,20 @@ export default function SideNavigator(props: Props) {
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
|
{user.hasFeature("admin") &&
|
||||||
|
<Tooltip title="Logs" placement="right">
|
||||||
|
<ListItemButton
|
||||||
|
id="tour-logs"
|
||||||
|
onClick={() => goTo("/logs")}
|
||||||
|
classes={getClasses("/logs")}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<DataDuckIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
{open && <ListItemText primary="Logs" />}
|
||||||
|
</ListItemButton>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
<Tooltip title="Marketplace" placement="right">
|
<Tooltip title="Marketplace" placement="right">
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
id="tour-marketplace"
|
id="tour-marketplace"
|
||||||
|
|
|
||||||
15
src/pages/Logs.tsx
Normal file
15
src/pages/Logs.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Box } from "@mui/material";
|
||||||
|
import LogsDisplay from "common/LogsDisplay";
|
||||||
|
import { useMobile } from "hooks";
|
||||||
|
import PageContainer from "pages/PageContainer";
|
||||||
|
|
||||||
|
export default function Logs() {
|
||||||
|
const isMobile = useMobile();
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
<Box paddingY={isMobile ? 0.5 : 1} paddingX={isMobile ? 1 : 2} height="100%">
|
||||||
|
<LogsDisplay />
|
||||||
|
</Box>
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ export {
|
||||||
// useUsageAPI,
|
// useUsageAPI,
|
||||||
useUserAPI,
|
useUserAPI,
|
||||||
// useStripeAPI,
|
// useStripeAPI,
|
||||||
// useDataDogProxyAPI,
|
useDataDogProxyAPI,
|
||||||
useMineAPI,
|
useMineAPI,
|
||||||
// useKeyManagerAPI,
|
// useKeyManagerAPI,
|
||||||
useTerminalAPI, //TODO: update api with resolve,reject
|
useTerminalAPI, //TODO: update api with resolve,reject
|
||||||
|
|
|
||||||
107
src/providers/pond/datadogProxyAPI.tsx
Normal file
107
src/providers/pond/datadogProxyAPI.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { useHTTP } from "hooks";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { createContext, PropsWithChildren, useContext } from "react";
|
||||||
|
import { pondURL } from "./pond";
|
||||||
|
|
||||||
|
export interface IDataDogProxyAPIContext {
|
||||||
|
listLogs: (
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
sort: string,
|
||||||
|
limit: number,
|
||||||
|
level?: string
|
||||||
|
) => Promise<AxiosResponse<pond.DataDogLogsResponse>>;
|
||||||
|
listDeviceLogs: (
|
||||||
|
device: string | number,
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
sort: string,
|
||||||
|
limit: number,
|
||||||
|
level?: string
|
||||||
|
) => Promise<AxiosResponse<pond.DataDogLogsResponse>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataDogProxyAPIContext = createContext<IDataDogProxyAPIContext>(
|
||||||
|
{} as IDataDogProxyAPIContext
|
||||||
|
);
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
export default function DataDogProvider(props: PropsWithChildren<Props>) {
|
||||||
|
const { children } = props;
|
||||||
|
const { get } = useHTTP();
|
||||||
|
|
||||||
|
const listLogs = (from: string, to: string, sort: string, limit: number, query?: string) => {
|
||||||
|
query = query ? query : "";
|
||||||
|
// if (level && level !== "all") {
|
||||||
|
// query = query + " @level:" + level;
|
||||||
|
// }
|
||||||
|
return get<pond.DataDogLogsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/datadog/logs?query=" +
|
||||||
|
query +
|
||||||
|
"&from=" +
|
||||||
|
from +
|
||||||
|
"&to=" +
|
||||||
|
to +
|
||||||
|
"&sort=" +
|
||||||
|
sort +
|
||||||
|
"&limit=" +
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const listDeviceLogs = (
|
||||||
|
device: string | number,
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
sort: string,
|
||||||
|
limit: number,
|
||||||
|
level?: string
|
||||||
|
) => {
|
||||||
|
let query = "@device:" + device;
|
||||||
|
if (level && level !== "all") {
|
||||||
|
query = query + " @level:" + level;
|
||||||
|
}
|
||||||
|
// console.log(
|
||||||
|
// "/datadog/logs?query=" +
|
||||||
|
// query +
|
||||||
|
// "&from=" +
|
||||||
|
// from +
|
||||||
|
// "&to=" +
|
||||||
|
// to +
|
||||||
|
// "&sort=" +
|
||||||
|
// sort +
|
||||||
|
// "&limit=" +
|
||||||
|
// limit
|
||||||
|
// );
|
||||||
|
return get<pond.DataDogLogsResponse>(
|
||||||
|
pondURL(
|
||||||
|
"/datadog/logs?query=" +
|
||||||
|
query +
|
||||||
|
"&from=" +
|
||||||
|
from +
|
||||||
|
"&to=" +
|
||||||
|
to +
|
||||||
|
"&sort=" +
|
||||||
|
sort +
|
||||||
|
"&limit=" +
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataDogProxyAPIContext.Provider
|
||||||
|
value={{
|
||||||
|
listLogs,
|
||||||
|
listDeviceLogs
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</DataDogProxyAPIContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDataDogProxyAPI = () => useContext(DataDogProxyAPIContext);
|
||||||
|
|
@ -30,6 +30,7 @@ import ObjectHeaterProvider, {useObjectHeaterAPI} from "./ObjectHeaterAPI";
|
||||||
import MutationProvider, {useMutationAPI} from "./mutationAPI"
|
import MutationProvider, {useMutationAPI} from "./mutationAPI"
|
||||||
import PreferenceProvider, {usePreferenceAPI} from "./preferenceAPI";
|
import PreferenceProvider, {usePreferenceAPI} from "./preferenceAPI";
|
||||||
import TaskProvider, { useTaskAPI } from "./taskAPI";
|
import TaskProvider, { useTaskAPI } from "./taskAPI";
|
||||||
|
import DataDogProvider, { useDataDogProxyAPI } from "./datadogProxyAPI";
|
||||||
// import NoteProvider from "providers/noteAPI";
|
// import NoteProvider from "providers/noteAPI";
|
||||||
|
|
||||||
export const pondURL = (partial: string, demo: boolean = false): string => {
|
export const pondURL = (partial: string, demo: boolean = false): string => {
|
||||||
|
|
@ -76,7 +77,9 @@ export default function PondProvider(props: PropsWithChildren<any>) {
|
||||||
<MutationProvider>
|
<MutationProvider>
|
||||||
<PreferenceProvider>
|
<PreferenceProvider>
|
||||||
<TaskProvider>
|
<TaskProvider>
|
||||||
{children}
|
<DataDogProvider>
|
||||||
|
{children}
|
||||||
|
</DataDogProvider>
|
||||||
</TaskProvider>
|
</TaskProvider>
|
||||||
</PreferenceProvider>
|
</PreferenceProvider>
|
||||||
</MutationProvider>
|
</MutationProvider>
|
||||||
|
|
@ -139,5 +142,6 @@ export {
|
||||||
useObjectHeaterAPI,
|
useObjectHeaterAPI,
|
||||||
useMutationAPI,
|
useMutationAPI,
|
||||||
usePreferenceAPI,
|
usePreferenceAPI,
|
||||||
useTaskAPI
|
useTaskAPI,
|
||||||
|
useDataDogProxyAPI
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue