From 2d6b5ecbc6350e8893476a7e5e916c10dc373f8e Mon Sep 17 00:00:00 2001 From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Date: Thu, 17 Jun 2021 19:12:52 -0700 Subject: [PATCH] Re-organize Pod Details (#821) Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> --- .../Console/Common/PageHeader/PageHeader.tsx | 20 +- portal-ui/src/screens/Console/Console.tsx | 2 +- .../Tenants/TenantDetails/PodDetails.tsx | 341 ------------------ .../Tenants/TenantDetails/pods/PodDetails.tsx | 136 +++++++ .../Tenants/TenantDetails/pods/PodEvents.tsx | 120 ++++++ .../Tenants/TenantDetails/pods/PodLogs.tsx | 197 ++++++++++ 6 files changed, 471 insertions(+), 345 deletions(-) delete mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/PodDetails.tsx create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodDetails.tsx create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodEvents.tsx create mode 100644 portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodLogs.tsx diff --git a/portal-ui/src/screens/Console/Common/PageHeader/PageHeader.tsx b/portal-ui/src/screens/Console/Common/PageHeader/PageHeader.tsx index 3c53c2954..09e9c47ee 100644 --- a/portal-ui/src/screens/Console/Common/PageHeader/PageHeader.tsx +++ b/portal-ui/src/screens/Console/Common/PageHeader/PageHeader.tsx @@ -6,6 +6,7 @@ import Typography from "@material-ui/core/Typography"; interface IPageHeader { classes: any; label: any; + actions?: any; } const styles = (theme: Theme) => @@ -31,16 +32,29 @@ const styles = (theme: Theme) => marginLeft: 55, marginTop: 8, }, + rightMenu: { + marginTop: 16, + marginRight: 8, + }, }); -const PageHeader = ({ classes, label }: IPageHeader) => { +const PageHeader = ({ classes, label, actions }: IPageHeader) => { return ( - - + + {label} + {actions && ( + + {actions} + + )} ); }; diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index efc2a2370..38d2205f4 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -56,7 +56,7 @@ import Heal from "./Heal/Heal"; import Watch from "./Watch/Watch"; import HealthInfo from "./HealthInfo/HealthInfo"; import Storage from "./Storage/Storage"; -import PodDetails from "./Tenants/TenantDetails/PodDetails"; +import PodDetails from "./Tenants/TenantDetails/pods/PodDetails"; const drawerWidth = 245; diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/PodDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/PodDetails.tsx deleted file mode 100644 index 82c5f5a2c..000000000 --- a/portal-ui/src/screens/Console/Tenants/TenantDetails/PodDetails.tsx +++ /dev/null @@ -1,341 +0,0 @@ -// This file is part of MinIO Console Server -// Copyright (c) 2021 MinIO, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -import React, { Fragment, useEffect, useState } from "react"; -import { connect } from "react-redux"; -import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; -import { - actionsTray, - buttonsStyles, - containerForHeader, - hrClass, - modalBasic, - searchField, -} from "../../Common/FormComponents/common/styleLibrary"; -import Grid from "@material-ui/core/Grid"; -import { TextField } from "@material-ui/core"; -import Tabs from "@material-ui/core/Tabs"; -import Tab from "@material-ui/core/Tab"; -import TableWrapper from "../../Common/TableWrapper/TableWrapper"; -import Paper from "@material-ui/core/Paper"; -import api from "../../../../common/api"; -import PageHeader from "../../Common/PageHeader/PageHeader"; -import { IEvent } from "../ListTenants/types"; -import { Link } from "react-router-dom"; -import { setErrorSnackMessage } from "../../../../actions"; -import InputAdornment from "@material-ui/core/InputAdornment"; -import SearchIcon from "@material-ui/icons/Search"; -import { niceDays } from "../../../../common/utils"; - -interface ITenantDetailsProps { - classes: any; - match: any; - setErrorSnackMessage: typeof setErrorSnackMessage; -} - -const styles = (theme: Theme) => - createStyles({ - logList: { - background: "#fff", - minHeight: 400, - height: "calc(100vh - 304px)", - overflow: "auto", - fontSize: 13, - padding: "25px 45px 0", - border: "1px solid #EAEDEE", - borderRadius: 4, - }, - buttonContainer: { - textAlign: "right", - }, - multiContainer: { - display: "flex", - alignItems: "center" as const, - justifyContent: "flex-start" as const, - }, - sizeFactorContainer: { - marginLeft: 8, - }, - containerHeader: { - display: "flex", - justifyContent: "space-between", - }, - paperContainer: { - padding: "15px 15px 15px 50px", - }, - infoGrid: { - display: "grid", - gridTemplateColumns: "auto auto auto auto", - gridGap: 8, - "& div": { - display: "flex", - alignItems: "center", - }, - "& div:nth-child(odd)": { - justifyContent: "flex-end", - fontWeight: 700, - }, - "& div:nth-child(2n)": { - paddingRight: 35, - }, - }, - masterActions: { - width: "25%", - minWidth: "120px", - "& div": { - margin: "5px 0px", - }, - }, - updateButton: { - backgroundColor: "transparent", - border: 0, - padding: "0 6px", - cursor: "pointer", - "&:focus, &:active": { - outline: "none", - }, - "& svg": { - height: 12, - }, - }, - poolLabel: { - color: "#666666", - }, - titleCol: { - fontWeight: "bold", - }, - breadcrumLink: { - textDecoration: "none", - color: "black", - }, - ...modalBasic, - ...actionsTray, - ...buttonsStyles, - ...searchField, - ...hrClass, - actionsTray: { - ...actionsTray.actionsTray, - padding: "15px 0 0", - }, - logerror: { - color: "#A52A2A", - }, - logerror_tab: { - color: "#A52A2A", - paddingLeft: 25, - }, - ansidefault: { - color: "#000", - }, - highlight: { - "& span": { - backgroundColor: "#082F5238", - }, - }, - ...containerForHeader(theme.spacing(4)), - }); - -const TenantDetails = ({ - classes, - match, - setErrorSnackMessage, -}: ITenantDetailsProps) => { - const [event, setEvent] = useState([]); - const [curTab, setCurTab] = useState(0); - const [highlight, setHighlight] = useState(""); - const [logLines, setLogLines] = useState([]); - const tenantNamespace = match.params["tenantNamespace"]; - const tenantName = match.params["tenantName"]; - const podName = match.params["podName"]; - - const renderLog = (logMessage: string, index: number) => { - // remove any non ascii characters, exclude any control codes - logMessage = logMessage.replace(/([^\x20-\x7F])/g, ""); - - // regex for terminal colors like e.g. `[31;4m ` - const tColorRegex = /((\[[0-9;]+m))/g; - - // get substring if there was a match for to split what - // is going to be colored and what not, here we add color - // only to the first match. - let substr = logMessage.replace(tColorRegex, ""); - - // in case highlight is set, we select the line that contains the requested string - let highlightedLine = - highlight !== "" - ? logMessage.toLowerCase().includes(highlight.toLowerCase()) - : false; - - // if starts with multiple spaces add padding - if (substr.startsWith(" ")) { - return ( -
- {substr} -
- ); - } else { - // for all remaining set default class - return ( -
- {substr} -
- ); - } - }; - - const renderLines = logLines.map((m, i) => { - return renderLog(m, i); - }); - - function a11yProps(index: any) { - return { - id: `simple-tab-${index}`, - "aria-controls": `simple-tabpanel-${index}`, - }; - } - - useEffect(() => { - api - .invoke( - "GET", - `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pods/${podName}` - ) - .then((res: string) => { - setLogLines(res.split("\n")); - }) - .catch((err) => { - setErrorSnackMessage(err); - }); - api - .invoke( - "GET", - `/api/v1/namespaces/${tenantNamespace}/tenants/${tenantName}/pods/${podName}/events` - ) - .then((res: IEvent[]) => { - for (let i = 0; i < res.length; i++) { - let currentTime = (Date.now() / 1000) | 0; - - res[i].seen = niceDays((currentTime - res[i].last_seen).toString()); - } - setEvent(res); - }) - .catch((err) => { - setErrorSnackMessage(err); - }); - }, [podName, tenantName, tenantNamespace, setErrorSnackMessage]); - - return ( - - - - Tenants - - {" > "} - - {tenantName} - - {` > Pods > ${podName}`} - - } - /> - - - - , newValue: number) => { - setCurTab(newValue); - }} - indicatorColor="primary" - textColor="primary" - aria-label="cluster-tabs" - variant="scrollable" - scrollButtons="auto" - > - - - - - {curTab === 0 && ( - - - { - setHighlight(val.target.value); - }} - InputProps={{ - disableUnderline: true, - startAdornment: ( - - - - ), - }} - /> - - -
-
- - -
{renderLines}
-
-
-
- )} - {curTab === 1 && ( - - - - )} -
-
- ); -}; - -const connector = connect(null, { - setErrorSnackMessage, -}); - -export default withStyles(styles)(connector(TenantDetails)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodDetails.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodDetails.tsx new file mode 100644 index 000000000..22cf33bdd --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodDetails.tsx @@ -0,0 +1,136 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { Fragment, useEffect, useState } from "react"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { containerForHeader } from "../../../Common/FormComponents/common/styleLibrary"; +import Grid from "@material-ui/core/Grid"; +import { IconButton } from "@material-ui/core"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import PageHeader from "../../../Common/PageHeader/PageHeader"; +import { Link } from "react-router-dom"; +import { setErrorSnackMessage } from "../../../../../actions"; +import RefreshIcon from "@material-ui/icons/Refresh"; +import PodLogs from "./PodLogs"; +import PodEvents from "./PodEvents"; + +interface IPodDetailsProps { + classes: any; + match: any; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + breadcrumLink: { + textDecoration: "none", + color: "black", + }, + ...containerForHeader(theme.spacing(4)), + }); + +const PodDetails = ({ classes, match }: IPodDetailsProps) => { + const [curTab, setCurTab] = useState(0); + const [loading, setLoading] = useState(true); + const tenantNamespace = match.params["tenantNamespace"]; + const tenantName = match.params["tenantName"]; + const podName = match.params["podName"]; + + function a11yProps(index: any) { + return { + id: `simple-tab-${index}`, + "aria-controls": `simple-tabpanel-${index}`, + }; + } + + useEffect(() => { + if (loading) { + setLoading(false); + } + }, [loading]); + + return ( + + + + Tenants + + {" > "} + + {tenantName} + + {` > Pods > ${podName}`} + + } + actions={ + { + setLoading(true); + }} + > + + + } + /> + + + + , newValue: number) => { + setCurTab(newValue); + }} + indicatorColor="primary" + textColor="primary" + aria-label="cluster-tabs" + variant="scrollable" + scrollButtons="auto" + > + + + + + {curTab === 0 && ( + + )} + {curTab === 1 && ( + + )} + + + ); +}; + +export default withStyles(styles)(PodDetails); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodEvents.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodEvents.tsx new file mode 100644 index 000000000..71048e910 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodEvents.tsx @@ -0,0 +1,120 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { + actionsTray, + buttonsStyles, + hrClass, + searchField, +} from "../../../Common/FormComponents/common/styleLibrary"; +import Grid from "@material-ui/core/Grid"; +import TableWrapper from "../../../Common/TableWrapper/TableWrapper"; +import api from "../../../../../common/api"; +import { IEvent } from "../../ListTenants/types"; +import { setErrorSnackMessage } from "../../../../../actions"; +import { niceDays } from "../../../../../common/utils"; + +interface IPodEventsProps { + classes: any; + tenant: string; + namespace: string; + podName: string; + propLoading: boolean; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + ...actionsTray, + ...buttonsStyles, + ...searchField, + ...hrClass, + actionsTray: { + ...actionsTray.actionsTray, + padding: "15px 0 0", + }, + }); + +const PodEvents = ({ + classes, + tenant, + namespace, + podName, + propLoading, + setErrorSnackMessage, +}: IPodEventsProps) => { + const [event, setEvent] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (propLoading) { + setLoading(true); + } + }, [propLoading]); + + useEffect(() => { + if (loading) { + api + .invoke( + "GET", + `/api/v1/namespaces/${namespace}/tenants/${tenant}/pods/${podName}/events` + ) + .then((res: IEvent[]) => { + for (let i = 0; i < res.length; i++) { + let currentTime = (Date.now() / 1000) | 0; + + res[i].seen = niceDays((currentTime - res[i].last_seen).toString()); + } + setEvent(res); + setLoading(false); + }) + .catch((err) => { + setErrorSnackMessage(err); + setLoading(false); + }); + } + }, [loading, podName, namespace, tenant, setErrorSnackMessage]); + + return ( + + + + + + ); +}; + +const connector = connect(null, { + setErrorSnackMessage, +}); + +export default withStyles(styles)(connector(PodEvents)); diff --git a/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodLogs.tsx b/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodLogs.tsx new file mode 100644 index 000000000..2e12760f0 --- /dev/null +++ b/portal-ui/src/screens/Console/Tenants/TenantDetails/pods/PodLogs.tsx @@ -0,0 +1,197 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2021 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { createStyles, Theme, withStyles } from "@material-ui/core/styles"; +import { + actionsTray, + buttonsStyles, + containerForHeader, + searchField, +} from "../../../Common/FormComponents/common/styleLibrary"; +import Grid from "@material-ui/core/Grid"; +import { TextField } from "@material-ui/core"; +import Paper from "@material-ui/core/Paper"; +import api from "../../../../../common/api"; +import { setErrorSnackMessage } from "../../../../../actions"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import SearchIcon from "@material-ui/icons/Search"; + +interface IPodLogsProps { + classes: any; + tenant: string; + namespace: string; + podName: string; + propLoading: boolean; + setErrorSnackMessage: typeof setErrorSnackMessage; +} + +const styles = (theme: Theme) => + createStyles({ + logList: { + background: "#fff", + minHeight: 400, + height: "calc(100vh - 304px)", + overflow: "auto", + fontSize: 13, + padding: "25px 45px 0", + border: "1px solid #EAEDEE", + borderRadius: 4, + }, + ...buttonsStyles, + ...searchField, + actionsTray: { + ...actionsTray.actionsTray, + padding: "15px 0 0", + }, + logerror: { + color: "#A52A2A", + }, + logerror_tab: { + color: "#A52A2A", + paddingLeft: 25, + }, + ansidefault: { + color: "#000", + }, + highlight: { + "& span": { + backgroundColor: "#082F5238", + }, + }, + ...containerForHeader(theme.spacing(4)), + }); + +const PodLogs = ({ + classes, + tenant, + namespace, + podName, + propLoading, + setErrorSnackMessage, +}: IPodLogsProps) => { + const [highlight, setHighlight] = useState(""); + const [logLines, setLogLines] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (propLoading) { + setLoading(true); + } + }, [propLoading]); + + const renderLog = (logMessage: string, index: number) => { + // remove any non ascii characters, exclude any control codes + logMessage = logMessage.replace(/([^\x20-\x7F])/g, ""); + + // regex for terminal colors like e.g. `[31;4m ` + const tColorRegex = /((\[[0-9;]+m))/g; + + // get substring if there was a match for to split what + // is going to be colored and what not, here we add color + // only to the first match. + let substr = logMessage.replace(tColorRegex, ""); + + // in case highlight is set, we select the line that contains the requested string + let highlightedLine = + highlight !== "" + ? logMessage.toLowerCase().includes(highlight.toLowerCase()) + : false; + + // if starts with multiple spaces add padding + if (substr.startsWith(" ")) { + return ( +
+ {substr} +
+ ); + } else { + // for all remaining set default class + return ( +
+ {substr} +
+ ); + } + }; + + const renderLines = logLines.map((m, i) => { + return renderLog(m, i); + }); + + useEffect(() => { + if (loading) { + api + .invoke( + "GET", + `/api/v1/namespaces/${namespace}/tenants/${tenant}/pods/${podName}` + ) + .then((res: string) => { + setLogLines(res.split("\n")); + setLoading(false); + }) + .catch((err) => { + setErrorSnackMessage(err); + setLoading(false); + }); + } + }, [loading, podName, namespace, tenant, setErrorSnackMessage]); + + return ( + + + { + setHighlight(val.target.value); + }} + InputProps={{ + disableUnderline: true, + startAdornment: ( + + + + ), + }} + /> + + +
+
+ + +
{renderLines}
+
+
+
+ ); +}; + +const connector = connect(null, { + setErrorSnackMessage, +}); + +export default withStyles(styles)(connector(PodLogs));