diff --git a/python/ray/dashboard/client/src/App.tsx b/python/ray/dashboard/client/src/App.tsx
index 6e9bd2d2f..29082d8e4 100644
--- a/python/ray/dashboard/client/src/App.tsx
+++ b/python/ray/dashboard/client/src/App.tsx
@@ -3,8 +3,6 @@ import React from "react";
import { Provider } from "react-redux";
import { BrowserRouter, Route } from "react-router-dom";
import Dashboard from "./pages/dashboard/Dashboard";
-import Errors from "./pages/dashboard/dialogs/errors/Errors";
-import Logs from "./pages/dashboard/dialogs/logs/Logs";
import { store } from "./store";
class App extends React.Component {
@@ -13,9 +11,7 @@ class App extends React.Component {
-
-
-
+
);
diff --git a/python/ray/dashboard/client/src/api.ts b/python/ray/dashboard/client/src/api.ts
index 18b89b194..20e45ed29 100644
--- a/python/ray/dashboard/client/src/api.ts
+++ b/python/ray/dashboard/client/src/api.ts
@@ -143,15 +143,21 @@ export interface ErrorsResponse {
}>;
}
-export const getErrors = (hostname: string, pid: string | undefined) =>
- get("/api/errors", { hostname, pid: pid || "" });
+export const getErrors = (hostname: string, pid: number | null) =>
+ get("/api/errors", {
+ hostname,
+ pid: pid === null ? "" : pid
+ });
export interface LogsResponse {
[pid: string]: string[];
}
-export const getLogs = (hostname: string, pid: string | undefined) =>
- get("/api/logs", { hostname, pid: pid || "" });
+export const getLogs = (hostname: string, pid: number | null) =>
+ get("/api/logs", {
+ hostname,
+ pid: pid === null ? "" : pid
+ });
export type LaunchProfilingResponse = string;
diff --git a/python/ray/dashboard/client/src/common/SpanButton.tsx b/python/ray/dashboard/client/src/common/SpanButton.tsx
new file mode 100644
index 000000000..f55f9df17
--- /dev/null
+++ b/python/ray/dashboard/client/src/common/SpanButton.tsx
@@ -0,0 +1,26 @@
+import { Theme } from "@material-ui/core/styles/createMuiTheme";
+import createStyles from "@material-ui/core/styles/createStyles";
+import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
+import React, { HTMLAttributes } from "react";
+
+const styles = (theme: Theme) =>
+ createStyles({
+ button: {
+ color: theme.palette.primary.main,
+ "&:hover": {
+ cursor: "pointer",
+ textDecoration: "underline"
+ }
+ }
+ });
+
+class SpanButton extends React.Component<
+ HTMLAttributes & WithStyles
+> {
+ render() {
+ const { classes, ...otherProps } = this.props;
+ return ;
+ }
+}
+
+export default withStyles(styles)(SpanButton);
diff --git a/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx b/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx
index 75c64c07c..5ff3b1e54 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/Dashboard.tsx
@@ -43,6 +43,8 @@ class Dashboard extends React.Component<
ReturnType &
typeof mapDispatchToProps
> {
+ timeoutId = 0;
+
refreshNodeAndRayletInfo = async () => {
try {
const [nodeInfo, rayletInfo, tuneAvailability] = await Promise.all([
@@ -56,7 +58,7 @@ class Dashboard extends React.Component<
} catch (error) {
this.props.setError(error.toString());
} finally {
- setTimeout(this.refreshNodeAndRayletInfo, 1000);
+ this.timeoutId = window.setTimeout(this.refreshNodeAndRayletInfo, 1000);
}
};
@@ -64,6 +66,10 @@ class Dashboard extends React.Component<
await this.refreshNodeAndRayletInfo();
}
+ componentWillUnmount() {
+ clearTimeout(this.timeoutId);
+ }
+
handleTabChange = (event: React.ChangeEvent<{}>, value: number) => {
this.props.setTab(value);
};
diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx
index a84b92e87..5284e1666 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeInfo.tsx
@@ -10,6 +10,8 @@ import Typography from "@material-ui/core/Typography";
import React from "react";
import { connect } from "react-redux";
import { StoreState } from "../../../store";
+import Errors from "./dialogs/errors/Errors";
+import Logs from "./dialogs/logs/Logs";
import NodeRowGroup from "./NodeRowGroup";
import TotalRow from "./TotalRow";
@@ -32,11 +34,38 @@ const mapStateToProps = (state: StoreState) => ({
rayletInfo: state.dashboard.rayletInfo
});
+interface State {
+ logDialog: { hostname: string; pid: number | null } | null;
+ errorDialog: { hostname: string; pid: number | null } | null;
+}
+
class NodeInfo extends React.Component<
WithStyles & ReturnType
> {
+ state: State = {
+ logDialog: null,
+ errorDialog: null
+ };
+
+ setLogDialog = (hostname: string, pid: number | null) => {
+ this.setState({ logDialog: { hostname, pid } });
+ };
+
+ clearLogDialog = () => {
+ this.setState({ logDialog: null });
+ };
+
+ setErrorDialog = (hostname: string, pid: number | null) => {
+ this.setState({ errorDialog: { hostname, pid } });
+ };
+
+ clearErrorDialog = () => {
+ this.setState({ errorDialog: null });
+ };
+
render() {
const { classes, nodeInfo, rayletInfo } = this.props;
+ const { logDialog, errorDialog } = this.state;
if (nodeInfo === null || rayletInfo === null) {
return Loading...;
@@ -88,44 +117,62 @@ class NodeInfo extends React.Component<
}
return (
-
-
-
-
- Host
- Workers
- Uptime
- CPU
- RAM
- Disk
- Sent
- Received
- Logs
- Errors
-
-
-
- {nodeInfo.clients.map(client => (
-
+
+
+
+
+ Host
+ Workers
+ Uptime
+ CPU
+ RAM
+ Disk
+ Sent
+ Received
+ Logs
+ Errors
+
+
+
+ {nodeInfo.clients.map(client => (
+
+ ))}
+
- ))}
-
+
+ {logDialog !== null && (
+
-
-
+ )}
+ {errorDialog !== null && (
+
+ )}
+
);
}
}
diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx
index 53785dfa2..620eb642c 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/NodeRowGroup.tsx
@@ -56,6 +56,8 @@ interface Props {
perWorker: { [pid: string]: number };
total: number;
};
+ setLogDialog: (hostname: string, pid: number | null) => void;
+ setErrorDialog: (hostname: string, pid: number | null) => void;
initialExpanded: boolean;
}
@@ -78,7 +80,15 @@ class NodeRowGroup extends React.Component<
};
render() {
- const { classes, node, raylet, logCounts, errorCounts } = this.props;
+ const {
+ classes,
+ node,
+ raylet,
+ logCounts,
+ errorCounts,
+ setLogDialog,
+ setErrorDialog
+ } = this.props;
const { expanded } = this.state;
const features = [
@@ -91,12 +101,12 @@ class NodeRowGroup extends React.Component<
{ NodeFeature: NodeSent, WorkerFeature: WorkerSent },
{ NodeFeature: NodeReceived, WorkerFeature: WorkerReceived },
{
- NodeFeature: makeNodeLogs(logCounts),
- WorkerFeature: makeWorkerLogs(logCounts)
+ NodeFeature: makeNodeLogs(logCounts, setLogDialog),
+ WorkerFeature: makeWorkerLogs(logCounts, setLogDialog)
},
{
- NodeFeature: makeNodeErrors(errorCounts),
- WorkerFeature: makeWorkerErrors(errorCounts)
+ NodeFeature: makeNodeErrors(errorCounts, setErrorDialog),
+ WorkerFeature: makeWorkerErrors(errorCounts, setErrorDialog)
}
];
diff --git a/python/ray/dashboard/client/src/pages/dashboard/dialogs/errors/Errors.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/dialogs/errors/Errors.tsx
similarity index 78%
rename from python/ray/dashboard/client/src/pages/dashboard/dialogs/errors/Errors.tsx
rename to python/ray/dashboard/client/src/pages/dashboard/node-info/dialogs/errors/Errors.tsx
index 97aef98c2..82da14ad5 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/dialogs/errors/Errors.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/dialogs/errors/Errors.tsx
@@ -4,10 +4,9 @@ import createStyles from "@material-ui/core/styles/createStyles";
import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import React from "react";
-import { RouteComponentProps } from "react-router";
-import { ErrorsResponse, getErrors } from "../../../../api";
-import DialogWithTitle from "../../../../common/DialogWithTitle";
-import NumberedLines from "../../../../common/NumberedLines";
+import { ErrorsResponse, getErrors } from "../../../../../api";
+import DialogWithTitle from "../../../../../common/DialogWithTitle";
+import NumberedLines from "../../../../../common/NumberedLines";
const styles = (theme: Theme) =>
createStyles({
@@ -30,29 +29,26 @@ const styles = (theme: Theme) =>
}
});
+interface Props {
+ clearErrorDialog: () => void;
+ hostname: string;
+ pid: number | null;
+}
+
interface State {
result: ErrorsResponse | null;
error: string | null;
}
-class Errors extends React.Component<
- WithStyles &
- RouteComponentProps<{ hostname: string; pid: string | undefined }>,
- State
-> {
+class Errors extends React.Component, State> {
state: State = {
result: null,
error: null
};
- handleClose = () => {
- this.props.history.push("/");
- };
-
async componentDidMount() {
try {
- const { match } = this.props;
- const { hostname, pid } = match.params;
+ const { hostname, pid } = this.props;
const result = await getErrors(hostname, pid);
this.setState({ result, error: null });
} catch (error) {
@@ -61,13 +57,11 @@ class Errors extends React.Component<
}
render() {
- const { classes, match } = this.props;
+ const { classes, clearErrorDialog, hostname } = this.props;
const { result, error } = this.state;
- const { hostname } = match.params;
-
return (
-
+
{error !== null ? (
{error}
) : result === null ? (
diff --git a/python/ray/dashboard/client/src/pages/dashboard/dialogs/logs/Logs.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/dialogs/logs/Logs.tsx
similarity index 74%
rename from python/ray/dashboard/client/src/pages/dashboard/dialogs/logs/Logs.tsx
rename to python/ray/dashboard/client/src/pages/dashboard/node-info/dialogs/logs/Logs.tsx
index 52b625567..9e18137a0 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/dialogs/logs/Logs.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/dialogs/logs/Logs.tsx
@@ -4,10 +4,9 @@ import createStyles from "@material-ui/core/styles/createStyles";
import withStyles, { WithStyles } from "@material-ui/core/styles/withStyles";
import Typography from "@material-ui/core/Typography";
import React from "react";
-import { RouteComponentProps } from "react-router";
-import { getLogs, LogsResponse } from "../../../../api";
-import DialogWithTitle from "../../../../common/DialogWithTitle";
-import NumberedLines from "../../../../common/NumberedLines";
+import { getLogs, LogsResponse } from "../../../../../api";
+import DialogWithTitle from "../../../../../common/DialogWithTitle";
+import NumberedLines from "../../../../../common/NumberedLines";
const styles = (theme: Theme) =>
createStyles({
@@ -25,29 +24,26 @@ const styles = (theme: Theme) =>
}
});
+interface Props {
+ clearLogDialog: () => void;
+ hostname: string;
+ pid: number | null;
+}
+
interface State {
result: LogsResponse | null;
error: string | null;
}
-class Logs extends React.Component<
- WithStyles &
- RouteComponentProps<{ hostname: string; pid: string | undefined }>,
- State
-> {
+class Logs extends React.Component, State> {
state: State = {
result: null,
error: null
};
- handleClose = () => {
- this.props.history.push("/");
- };
-
async componentDidMount() {
try {
- const { match } = this.props;
- const { hostname, pid } = match.params;
+ const { hostname, pid } = this.props;
const result = await getLogs(hostname, pid);
this.setState({ result, error: null });
} catch (error) {
@@ -56,13 +52,11 @@ class Logs extends React.Component<
}
render() {
- const { classes, match } = this.props;
+ const { classes, clearLogDialog, hostname } = this.props;
const { result, error } = this.state;
- const { hostname } = match.params;
-
return (
-
+
{error !== null ? (
{error}
) : result === null ? (
diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx
index 9556e24bc..1accf5972 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Errors.tsx
@@ -1,7 +1,6 @@
-import Link from "@material-ui/core/Link";
import Typography from "@material-ui/core/Typography";
import React from "react";
-import { Link as RouterLink } from "react-router-dom";
+import SpanButton from "../../../../common/SpanButton";
import {
ClusterFeatureComponent,
NodeFeatureComponent,
@@ -34,30 +33,36 @@ export const makeClusterErrors = (errorCounts: {
);
};
-export const makeNodeErrors = (errorCounts: {
- perWorker: { [pid: string]: number };
- total: number;
-}): NodeFeatureComponent => ({ node }) =>
+export const makeNodeErrors = (
+ errorCounts: {
+ perWorker: { [pid: string]: number };
+ total: number;
+ },
+ setErrorDialog: (hostname: string, pid: number | null) => void
+): NodeFeatureComponent => ({ node }) =>
errorCounts.total === 0 ? (
No errors
) : (
-
+ setErrorDialog(node.hostname, null)}>
View all errors ({errorCounts.total.toLocaleString()})
-
+
);
-export const makeWorkerErrors = (errorCounts: {
- perWorker: { [pid: string]: number };
- total: number;
-}): WorkerFeatureComponent => ({ node, worker }) =>
+export const makeWorkerErrors = (
+ errorCounts: {
+ perWorker: { [pid: string]: number };
+ total: number;
+ },
+ setErrorDialog: (hostname: string, pid: number | null) => void
+): WorkerFeatureComponent => ({ node, worker }) =>
errorCounts.perWorker[worker.pid] === 0 ? (
No errors
) : (
-
+ setErrorDialog(node.hostname, worker.pid)}>
View errors ({errorCounts.perWorker[worker.pid].toLocaleString()})
-
+
);
diff --git a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx
index 06072cb70..c73fd4910 100644
--- a/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx
+++ b/python/ray/dashboard/client/src/pages/dashboard/node-info/features/Logs.tsx
@@ -1,7 +1,6 @@
-import Link from "@material-ui/core/Link";
import Typography from "@material-ui/core/Typography";
import React from "react";
-import { Link as RouterLink } from "react-router-dom";
+import SpanButton from "../../../../common/SpanButton";
import {
ClusterFeatureComponent,
NodeFeatureComponent,
@@ -33,32 +32,38 @@ export const makeClusterLogs = (logCounts: {
);
};
-export const makeNodeLogs = (logCounts: {
- perWorker: { [pid: string]: number };
- total: number;
-}): NodeFeatureComponent => ({ node }) =>
+export const makeNodeLogs = (
+ logCounts: {
+ perWorker: { [pid: string]: number };
+ total: number;
+ },
+ setLogDialog: (hostname: string, pid: number | null) => void
+): NodeFeatureComponent => ({ node }) =>
logCounts.total === 0 ? (
No logs
) : (
-
+ setLogDialog(node.hostname, null)}>
View all logs ({logCounts.total.toLocaleString()}{" "}
{logCounts.total === 1 ? "line" : "lines"})
-
+
);
-export const makeWorkerLogs = (logCounts: {
- perWorker: { [pid: string]: number };
- total: number;
-}): WorkerFeatureComponent => ({ node, worker }) =>
+export const makeWorkerLogs = (
+ logCounts: {
+ perWorker: { [pid: string]: number };
+ total: number;
+ },
+ setLogDialog: (hostname: string, pid: number | null) => void
+): WorkerFeatureComponent => ({ node, worker }) =>
logCounts.perWorker[worker.pid] === 0 ? (
No logs
) : (
-
+ setLogDialog(node.hostname, worker.pid)}>
View log ({logCounts.perWorker[worker.pid].toLocaleString()}{" "}
{logCounts.perWorker[worker.pid] === 1 ? "line" : "lines"})
-
+
);