diff --git a/src/bin/BinFansStatusTable.tsx b/src/bin/BinFansStatusTable.tsx new file mode 100644 index 0000000..2457abe --- /dev/null +++ b/src/bin/BinFansStatusTable.tsx @@ -0,0 +1,265 @@ +import { + Box, + Button, + DialogActions, + DialogContent, + Grid2 as Grid, + IconButton +} from "@mui/material"; +// import { getTableIcons } from "common/ResponsiveTable"; +// import MaterialTable, { Column } from "material-table"; +import { Bin } from "models"; +import { useBinAPI, useGlobalState, useSnackbar } from "providers"; +import fans from "fans/fans_client.json"; +import React, { useState, useEffect } from "react"; +import { getThemeType } from "theme"; +import AerationFanIcon from "products/AgIcons/AerationFanIcon"; +import FanPicker from "fans/fanPicker"; +import ResponsiveDialog from "common/ResponsiveDialog"; +import { useMobile } from "hooks"; +import { makeStyles } from "@mui/styles"; +import ResponsiveTable, { Column } from "common/ResponsiveTable"; +import { cloneDeep } from "lodash"; + +const useStyles = makeStyles(() => ({ + dark: { + backgroundColor: getThemeType() === "light" ? "rgb(245, 245, 245)" : "rgb(50, 50, 50)", + padding: 5 + }, + light: { + backgroundColor: getThemeType() === "light" ? "rgb(235, 235, 235)" : "rgb(60, 60, 60)", + padding: 5 + } + }) +); + +interface jsonFan { + id: number; + name: string; + kind: string; + rpm: number; + diameter: number; + horsepower: number; +} + +interface BinRow { + bin: Bin; + fan?: jsonFan; +} + +interface Props { + isLoading: boolean; + bins: Bin[]; + getControllerInfo?: (totalControllers: number, onControllers: number) => void; +} + +export default function BinsFansStatusTable(props: Props) { + const [{ as }] = useGlobalState(); + const binAPI = useBinAPI(); + const { bins, isLoading, getControllerInfo } = props; + const { openSnack } = useSnackbar(); + const [tableData, setTableData] = useState([]); + const [displayedRows, setDisplayedRows] = useState([]); + const [fanMap, setFanMap] = useState>(new Map()); + const isMobile = useMobile(); + const [tablePage, setTablePage] = useState(0); + const [pageSize, setPageSize] = useState(10); + const [openPickerDialog, setOpenPickerDialog] = useState(false); + const [selectedBinRow, setSelectedBinRow] = useState(); + const [newFanID, setNewFanID] = useState(); + const classes = useStyles(); + + useEffect(()=>{ + let clone: BinRow[] = cloneDeep(tableData) + let rows = clone.splice(tablePage * pageSize, pageSize) + setDisplayedRows(rows) + },[tableData, tablePage, pageSize]) + + //define the columns for the desktop table + const columns: Column[] = [ + { + title: "Set Fan", + cellStyle: {padding: 2}, + render: (row) => + { + setSelectedBinRow(row); + setOpenPickerDialog(true); + }} + > + + + + }, + { + title: "Bin", + cellStyle: {padding: 2}, + render: (row) => {row.bin.settings.name} + }, + { + title: "Bin Fan", + cellStyle: {padding: 2}, + hidden: isMobile, + render: row => { + if (row.fan) { + return {row.fan.name} + } else { + return "No Fan Set on Bin" + } + } + }, + { + title: "Fan Type", + cellStyle: {padding: 2}, + hidden: isMobile, + render: (row) => {row.fan?.kind} + }, + { + title: "Horsepower", + cellStyle: {padding: 2}, + hidden: isMobile, + render: (row) => {row.fan?.horsepower} + }, + //the controllers + { + title: "Fan Controllers", + cellStyle: {padding: 2}, + render: row => { + if (row.bin.status.fans.length > 0) { + return ( + + {row.bin.status.fans.map((binFan, i) => ( + + {binFan.name} + {binFan.state ? "On" : "Off"} + + ))} + + ); + } else { + return No Controllers attached to this bin + } + } + } + ]; + + useEffect(() => { + //build the fanmap + let fanMap: Map = new Map(); + fans["bulk"].forEach(fan => { + fanMap.set(fan.id, fan); + }); + setFanMap(fanMap); + + let data: BinRow[] = []; + let dataMap: Map = new Map(); + let totalControllers = 0; + let onControllers = 0; + bins.forEach(bin => { + //put the bin in the row + let row: BinRow = { + bin: bin + }; + //if the bin has a fan id set get it from the json and put it in the row data + if (bin.fanID() !== 0) { + row.fan = fanMap.get(bin.fanID()); + } + data.push(row); + dataMap.set(bin.key(), row); + + //add to the controller numbers + bin.status.fans.forEach(fan => { + //add to the total for each fan + totalControllers++; + //if the fan is on add to the count for controllers that are on + if (fan.state) { + onControllers++; + } + }); + }); + setTableData(data); + //setTableDataMap(dataMap) + if (getControllerInfo) { + getControllerInfo(totalControllers, onControllers); + } + }, [as, bins, getControllerInfo]); + + const updateBinFan = () => { + if (selectedBinRow && newFanID) { + //use the bin api to update the bin in the selected row + let bin = selectedBinRow.bin; + bin.settings.fanId = newFanID; + binAPI + .updateBin(bin.key(), bin.settings) + .then(resp => { + //when an update succeeds update the table with the fan information + //let dataMap = tableDataMap + selectedBinRow.bin = bin; + selectedBinRow.fan = fanMap.get(newFanID); + openSnack("Updated bin with new fan"); + //close dialog + setOpenPickerDialog(false); + }) + .catch(err => { + openSnack("failed to update bin with new fan"); + }); + } + }; + + const fanPickerDialog = () => { + return ( + { + setOpenPickerDialog(false); + }}> + + { + setNewFanID(fanID); + }} + useCardView + /> + + + + + + + ); + }; + + //function to make the table + const fanStatusTable = () => { + return ( + {setTablePage(page)}} + handleRowsPerPageChange={(e)=>{setPageSize(e.target.value)}} + /> + ); + }; + return ( + + {fanStatusTable()} + {fanPickerDialog()} + + ); +} diff --git a/src/common/LogCard.tsx b/src/common/LogCard.tsx new file mode 100644 index 0000000..8e54b1e --- /dev/null +++ b/src/common/LogCard.tsx @@ -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 ( + + + {moment(log.timestamp).format("MMM Do, h:mm:ss a")} + + + {log.service} + + + {log.device} + + + {log.message} + + + + {log.attributes} + + + + ); + }; + + return ( + + setDrawer(false)} anchor="right"> + + + + + Device: + + + {log.device} + + + + + Timestamp: + + + {moment(log.timestamp).format("MMMM ddd Do, h:mm:ss a")} + + + + + Level: + + + {log.level} + + + + + Service: + + + {log.service} + + + + { + +
{JSON.stringify(attributes, null, 2)}
+
+ } +
+
+ setDrawer(true)}> + {lineSummary()} + +
+ ); +} diff --git a/src/common/LogsDisplay.tsx b/src/common/LogsDisplay.tsx new file mode 100644 index 0000000..07f712d --- /dev/null +++ b/src/common/LogsDisplay.tsx @@ -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([]); + const [loading, setLoading] = useState(false); + const [device, setDevice] = useState(0); + + const [usage, setUsage] = useState([]); + const [customQuery, setCustomQuery] = useState(""); + // 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 ( + + + setFrom(e.target.value)} + /> + + + setTo(e.target.value)} + /> + + + setLevel(event.target.value)}> + {levelOptions.map(op => ( + + {op.key} + + ))} + + + + setLimit(+e.target.value)} + /> + + + setSort(event.target.value)}> + {sortOptions.map(op => ( + + {op.key} + + ))} + + + {!deviceID && ( + + setDevice(parseInt(event.target.value))}> + {sortOptions.map(op => ( + + {op.key} + + ))} + + + )} + + {loading ? ( + + ) : ( + + )} + + + ); + }; + + const endAdornment = () => { + // if (loading) return + return ( + + + + ); + }; + + const searchBar = () => { + return ( + + + setCustomQuery(e.target.value)} + onKeyPress={e => { + if (e.key === "Enter" && !loading) { + getCustomQuery(); + } + }} + /> + + + ); + }; + + 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 ( + + + + ( + + {/* + hi + */} + {logs.length < 1 && No Logs} + {/* {device.status.bytesTimestamp.length > 1 && ( + {pieData} + )} */} + + )} + /> + + {usage.map((entry, index) => ( + { + // setPieLabel(entry.name); + // setPieData(entry.dataAmount.toString()); + }} + /> + ))} + + + + + ); + }; + + // const check = (label: string) => { + // if (service === label) { + // setService("all"); + // } else { + // setService(label); + // } + // }; + + const serviceSelector = () => { + return ( + + Service + + setService("all")} + /> + } + label="all" + labelPlacement="end" + /> + setService("pond")} + /> + } + label="pond" + labelPlacement="end" + /> + setService("quay")} + /> + } + label="quay" + labelPlacement="end" + /> + setService("traefik")} + /> + } + label="traefik" + labelPlacement="end" + /> + setService("webui")} + /> + } + label="webui" + labelPlacement="end" + /> + setService("agent")} + /> + } + label="agent" + labelPlacement="end" + /> + + + ); + }; + + 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 ( + + + + + + Info: {infoNum} + + + ({getPercent(infoNum)}) + + + + + + Debug: {debugNum} + + + ({getPercent(debugNum)}) + + + + + + Warning: {warningNum} + + + ({getPercent(warningNum)}) + + + + + + Error: {errorNum} + + + ({getPercent(errorNum)}) + + + + + + Critical: {criticalNum} + + + ({getPercent(criticalNum)}) + + + + + ); + }; + + return ( + + {/* Title Section */} + + {titleGrid()} + {searchBar()} + + + {/* Header Section */} + + + Log Metrics + {/* Pie chart and service selector will go here */} + {logPieChart()} + {logLevels()} + {/*
*/} + {serviceSelector()} +
+ + + + Timestamp + + + Service + + + Device + + + Message + + + Content + + + + {/* Scrollable List Section */} + + {logs.map((l, i) => ( + + ))} + {logs.length < 1 && ( + + Found logs will be displayed here + + )} + + +
+
+ ); +} diff --git a/src/common/QrCodeGenerator.tsx b/src/common/QrCodeGenerator.tsx new file mode 100644 index 0000000..8d5ddcb --- /dev/null +++ b/src/common/QrCodeGenerator.tsx @@ -0,0 +1,300 @@ +import { Box, Button, DialogActions, DialogContent, DialogTitle } from "@mui/material"; +// import MaterialTable from "material-table"; +import React, { useEffect, useState } from "react"; +import ResponsiveDialog from "./ResponsiveDialog"; +// import { getTableIcons } from "./ResponsiveTable"; +import { Document, Image, Page, PDFViewer, StyleSheet, Text, View } from "@react-pdf/renderer"; +import { useMobile } from "hooks"; +import { cloneDeep } from "lodash"; +import ResponsiveTable from "./ResponsiveTable"; + +export interface QrCodeKey { + name: string; + key: string; +} + +interface Props { + open: boolean; + close: () => void; + keyList: QrCodeKey[]; + requiredUrlAffix: string; + url?: string; +} + +export default function(props: Props) { + const { open, close, url, requiredUrlAffix, keyList } = props; + const [selectedKeys, setSelectedKeys] = useState([]); + const [selectedPageRows, setSelectedPageRows] = useState>(new Map())//key is the page value is an array of row indexes for that page + const [tablePage, setTablePage] = useState(0) + const [pageSize, setPageSize] = useState(10) + const isMobile = useMobile(); + const [displayedRows, setDisplayedRows] = useState([]); + const codesPerPage = 4; + const pdfStyle = StyleSheet.create({ + viewerDesktop: { + height: 500 * 1.44, + width: 500 + }, + viewerMobile: { + height: "100%", + width: "100%" + }, + page: { + padding: "3%", + textAlign: "center", + fontFamily: "Helvetica-Bold", + fontSize: 30 + }, + quadrant: { + width: "50%", + textAlign: "center", + //margin: 10, + borderRadius: 20 + }, + code: { + flexDirection: "column", + border: "2px dotted black", + margin: 10, + borderRadius: 20 + }, + image: { + padding: 20, + paddingBottom: 0 + //height: "65%" + //border: "1px solid black", //used just to see a representation of where the view is + }, + text: { + marginTop: 30, + marginBottom: 30, + textAlign: "center", + justifyContent: "center" + //border: "1px solid black", //used just to see a representation of where the view is + } + }); + + useEffect(()=>{ + let clone: QrCodeKey[] = cloneDeep(keyList) + let rows = clone.splice(tablePage * pageSize, pageSize) + setDisplayedRows(rows) + },[keyList, tablePage, pageSize]) + + const handleRowsPerPageChange = (event: any) => { + const newRowsPerPage = parseInt(event.target.value, 10); // Get selected value + setPageSize(newRowsPerPage); + setTablePage(0) + //clear the selected rows + setSelectedPageRows(new Map()) + //clear the selected keys + setSelectedKeys([]) + } + + const rowSelected = (row: QrCodeKey, index: number, checked: boolean) => { + //when a row is selected add it to the map for the page + let selected = cloneDeep(selectedKeys) + let pageMap = cloneDeep(selectedPageRows) + let pageSelect = pageMap.get(tablePage) + if(checked){ //add the page/index to the map + if(pageSelect){ + pageSelect.push(index) + }else{ + pageMap.set(tablePage, [index]) + } + //handle the row data to add it to a list of keys that are selected + selected.push(row) + }else{ //remove the page/index from the map + if(pageSelect){ + pageSelect.splice(pageSelect.indexOf(index)) + } + //remove the selected object from the array + selected.forEach((codeKey, index) => { + if(row.key === codeKey.key){ + selected.splice(index) + } + }) + } + setSelectedKeys(selected) + setSelectedPageRows(pageMap) + } + + const selectionTable = () => { + return ( + + {row.name} + } + ]} + rowSelect={rowSelected} + selectedRows={selectedPageRows.get(tablePage)} + rows={displayedRows} + page={tablePage} + pageSize={pageSize} + total={keyList.length} + setPage={(page) => {setTablePage(page)}} + handleRowsPerPageChange={handleRowsPerPageChange} + /> + {/* { + setSelectedKeys(data); + }} + columns={[ + { + cellStyle: { + padding: 0 + }, + title: "Name", + field: "name" + } + ]}> */} + + ); + }; + + const generatedCodes = () => { + let numPages = Math.ceil(selectedKeys.length / codesPerPage); + let keys = cloneDeep(selectedKeys); + let pages: JSX.Element[] = []; + + let location = url ?? document.URL; //document.URL will append the ending slash to the url string + if (!location.includes(requiredUrlAffix)) { + location = location + requiredUrlAffix; + } + + for (let i = 0; i < numPages; i++) { + let pageKeys = keys.splice(0, codesPerPage); + pages.push( + + + {pageKeys[0] && ( + + + + + + + {pageKeys[0].name} + + + + )} + {pageKeys[1] && ( + + + + + + + {pageKeys[1].name} + + + + )} + + + {pageKeys[2] && ( + + + + + + + {pageKeys[2].name} + + + + )} + {pageKeys[3] && ( + + + + + + + {pageKeys[3].name} + + + + )} + + + ); + } + + return ( + + {pages} + + ); + }; + + return ( + + Generate QR Codes + + {selectionTable()} + + + {generatedCodes()} + + + + + + + + ); +} diff --git a/src/navigation/Router.tsx b/src/navigation/Router.tsx index 6976cc8..40af56d 100644 --- a/src/navigation/Router.tsx +++ b/src/navigation/Router.tsx @@ -37,6 +37,8 @@ const BinCableEstimator = lazy(() => import("pages/BinCableEstimator")); const APIDocs = lazy(() => import("pages/APIDocs")); const Fields = lazy(()=> import("pages/Fields")); const Marketplace = lazy(() => import("userFeatures/UserFeatures")) +const Logs = lazy(() => import("pages/Logs")) + export const appendToUrl = (appendage: number | string) => { const basePath = location.pathname.replace(/\/$/, ""); return(`${basePath}/${appendage}`); @@ -304,6 +306,9 @@ export default function Router() { {user.hasFeature("developer") && } /> } + {user.hasFeature("admin") && + } /> + } } /> } /> diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index 0a2cc9d..2730a9f 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -43,6 +43,7 @@ import TasksIcon from "products/Construction/TasksIcon"; import CableIcon from "products/Bindapt/CableIcon"; import FieldListIcon from "products/AgIcons/FieldList"; import MarketplaceIcon from "products/CommonIcons/marketplaceIcon"; +import DataDuckIcon from "products/Bindapt/DataDuckIcon"; const drawerWidth = 230; @@ -367,6 +368,20 @@ export default function SideNavigator(props: Props) { } + {user.hasFeature("admin") && + + goTo("/logs")} + classes={getClasses("/logs")} + > + + + + {open && } + + + } ([]); + const [qrKeyList, setQrKeyList] = useState([]); const [totalFanControllers, setTotalFanControllers] = useState(0); const [totalFanControllersOn, setTotalFanControllersOn] = useState(0); const [yardsLoading, setYardsLoading] = useState(false); @@ -313,7 +313,7 @@ export default function Bins(props: Props) { let grain: Bin[] = []; let empty: Bin[] = []; let fert: Bin[] = []; - // let qr: QrCodeKey[] = []; + let qr: QrCodeKey[] = []; let b = resp.data.bins.map(b => Bin.any(b)); b.forEach(bin => { if (bin.empty()) { @@ -324,12 +324,12 @@ export default function Bins(props: Props) { grain.push(bin); } //build qr keys here - // qr.push({ - // key: bin.key(), - // name: bin.name() - // }); + qr.push({ + key: bin.key(), + name: bin.name() + }); }); - // setQrKeyList(qr); + setQrKeyList(qr); setGrainBins(grain); if (fert.length > 0) { setDisplayFert(true); @@ -428,7 +428,7 @@ export default function Bins(props: Props) { let empty: Bin[] = offset ? emptyBins : []; let fert: Bin[] = offset ? fertilizerBins : []; let grain: Bin[] = offset ? grainBins : []; - // let newKeyList: QrCodeKey[] = qrKeyList; + let newKeyList: QrCodeKey[] = qrKeyList; newBins.forEach(bin => { if (bin.empty()) { empty.push(bin); @@ -437,10 +437,10 @@ export default function Bins(props: Props) { } else { grain.push(bin); } - // newKeyList.push({ - // key: bin.key(), - // name: bin.name() - // }); + newKeyList.push({ + key: bin.key(), + name: bin.name() + }); }); if (fert.length > 0) { setDisplayFert(true); @@ -451,7 +451,7 @@ export default function Bins(props: Props) { setGrainBins([...grain]); setEmptyBins([...empty]); setFertilizerBins([...fert]); - // setQrKeyList([...newKeyList]); + setQrKeyList([...newKeyList]); }) .catch(err => { setPaginatedBins({ bins: [], binsOffset: 0, binsTotal: 0 }); @@ -1013,14 +1013,14 @@ export default function Bins(props: Props) { - {/* { setTotalFanControllers(total); setTotalFanControllersOn(on); }} - /> */} + /> ); @@ -1390,14 +1390,14 @@ export default function Bins(props: Props) { setAddBagOpen(false); }} /> - {/* { setOpenQrGenerator(false); }} keyList={qrKeyList} requiredUrlAffix={"bins"} - /> */} + /> {binMenu()} setAddBinOpen(true)} pulse={binsTotal < 1 && !allLoading} /> diff --git a/src/pages/Logs.tsx b/src/pages/Logs.tsx new file mode 100644 index 0000000..52b285e --- /dev/null +++ b/src/pages/Logs.tsx @@ -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 ( + + + + + + ); +} \ No newline at end of file diff --git a/src/providers/index.ts b/src/providers/index.ts index 25aebe4..6282227 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -33,7 +33,7 @@ export { // useUsageAPI, useUserAPI, // useStripeAPI, - // useDataDogProxyAPI, + useDataDogProxyAPI, useMineAPI, // useKeyManagerAPI, useTerminalAPI, //TODO: update api with resolve,reject diff --git a/src/providers/pond/datadogProxyAPI.tsx b/src/providers/pond/datadogProxyAPI.tsx new file mode 100644 index 0000000..ec957a4 --- /dev/null +++ b/src/providers/pond/datadogProxyAPI.tsx @@ -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>; + listDeviceLogs: ( + device: string | number, + from: string, + to: string, + sort: string, + limit: number, + level?: string + ) => Promise>; +} + +export const DataDogProxyAPIContext = createContext( + {} as IDataDogProxyAPIContext +); + +interface Props {} + +export default function DataDogProvider(props: PropsWithChildren) { + 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( + 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( + pondURL( + "/datadog/logs?query=" + + query + + "&from=" + + from + + "&to=" + + to + + "&sort=" + + sort + + "&limit=" + + limit + ) + ); + }; + + return ( + + {children} + + ); +} + +export const useDataDogProxyAPI = () => useContext(DataDogProxyAPIContext); diff --git a/src/providers/pond/pond.tsx b/src/providers/pond/pond.tsx index b6212e7..aa4aa1a 100644 --- a/src/providers/pond/pond.tsx +++ b/src/providers/pond/pond.tsx @@ -30,6 +30,7 @@ import ObjectHeaterProvider, {useObjectHeaterAPI} from "./ObjectHeaterAPI"; import MutationProvider, {useMutationAPI} from "./mutationAPI" import PreferenceProvider, {usePreferenceAPI} from "./preferenceAPI"; import TaskProvider, { useTaskAPI } from "./taskAPI"; +import DataDogProvider, { useDataDogProxyAPI } from "./datadogProxyAPI"; // import NoteProvider from "providers/noteAPI"; export const pondURL = (partial: string, demo: boolean = false): string => { @@ -76,7 +77,9 @@ export default function PondProvider(props: PropsWithChildren) { - {children} + + {children} + @@ -139,5 +142,6 @@ export { useObjectHeaterAPI, useMutationAPI, usePreferenceAPI, - useTaskAPI + useTaskAPI, + useDataDogProxyAPI };