Skip to content

Commit

Permalink
Show banner if websocket connection is inactive (encoredev#616)
Browse files Browse the repository at this point in the history
* Adding banners under the nav bar to make it clear if the websocket connection to the daemon is not active or if you are viewing an app that is not currently running.
  • Loading branch information
simon-johansson authored Feb 20, 2023
1 parent e4c53fc commit 979f3c5
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 130 deletions.
13 changes: 12 additions & 1 deletion cli/daemon/dash/dashapp/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion cli/daemon/dash/dashapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"react-dom": "^18.2.0",
"react-json-tree": "^0.17.0",
"react-router-dom": "^6.3.0",
"react-router-hash-link": "^2.4.3"
"react-router-hash-link": "^2.4.3",
"reconnecting-websocket": "^4.4.0"
},
"devDependencies": {
"@babel/preset-env": "^7.18.10",
Expand Down
24 changes: 21 additions & 3 deletions cli/daemon/dash/dashapp/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import React, { FC, useEffect, useState } from "react";
import { BrowserRouter as Router, Navigate, Route, Routes, useParams } from "react-router-dom";
import {
BrowserRouter as Router,
Navigate,
Outlet,
Route,
Routes,
useParams,
} from "react-router-dom";
import Client from "~lib/client/client";
import JSONRPCConn from "~lib/client/jsonrpc";
import AppList from "~p/AppList";
import AppHome from "~p/AppHome";
import { ConnContext } from "~lib/ctx";
import { ConnContext, useConn } from "~lib/ctx";
import AppAPI from "~p/AppAPI";
import AppDiagram from "~p/AppDiagram";
import { SnippetContent, SnippetPage } from "~p/SnippetPage";
import Nav from "~c/Nav";

function App() {
const [conn, setConn] = useState<JSONRPCConn | undefined>(undefined);
Expand Down Expand Up @@ -40,7 +48,7 @@ function App() {
<Routes>
<Route path="/" element={<AppList />} />

<Route path="/:appID">
<Route path="/:appID" element={<AppWrapper />}>
<Route index element={<Redirect to="requests" />} />

<Route path="requests" element={<AppHome />} />
Expand All @@ -65,3 +73,13 @@ const Redirect: FC<{ to: string }> = ({ to }) => {
const params = useParams<{ appID: string }>();
return <Navigate to={`/${params.appID}/${to}`} replace />;
};

const AppWrapper: FC = () => {
const conn = useConn();
return (
<>
<Nav conn={conn} />
<Outlet />
</>
);
};
74 changes: 73 additions & 1 deletion cli/daemon/dash/dashapp/src/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React, { FunctionComponent, useEffect, useState } from "react";
import React, { FunctionComponent, useEffect, useRef, useState } from "react";
import { Link, matchPath, useLocation, useParams } from "react-router-dom";
import { useConn } from "~lib/ctx";
import logo from "../logo.svg";
import wordmark from "../wordmark.svg";
import JSONRPCConn from "~lib/client/jsonrpc";
import { icons } from "~c/icons";

interface NavProps {
withoutLinks?: boolean;
conn?: JSONRPCConn;
}

const menuItems: {
Expand All @@ -25,6 +28,48 @@ const Nav: FunctionComponent<NavProps> = (props) => {
const { appID } = useParams<{ appID: string }>();
const [menuOpen, setMenuOpen] = useState(false);
const [appsOpen, setAppsOpen] = useState(false);
const [isReconnecting, setIsReconnecting] = useState<boolean>(false);
const [isAppRunning, setIsAppRunning] = useState<boolean>(true);
const hasBeenRunning = useRef<boolean>(false);

useEffect(() => {
let timeoutRef: number;
const getAppStatus = () => {
if (props.conn) {
props.conn.request("status", { appID }).then((status: any) => {
if (status.running) {
// if the app is not currently running and has not been running before disconnecting
// then we want to reload the page to populate the components with data. But if the app
// has been running before disconnecting then we want to keep the users state and not reload
if (!isAppRunning && !hasBeenRunning.current) window.location.reload();
hasBeenRunning.current = true;
}
setIsAppRunning(status.running);
timeoutRef = setTimeout(getAppStatus, 2000);
});
}
};
getAppStatus();
return () => {
clearTimeout(timeoutRef);
};
}, [isAppRunning]);

useEffect(() => {
const { conn } = props;
const onClose = () => setIsReconnecting(true);
const onReconnect = () => setIsReconnecting(false);
if (conn) {
conn.on("close", onClose);
conn.on("reconnect", onReconnect);
}
return () => {
if (conn) {
conn.off("close", onClose);
conn.off("reconnect", onReconnect);
}
};
}, []);

return (
<nav className="bg-black">
Expand Down Expand Up @@ -213,6 +258,33 @@ const Nav: FunctionComponent<NavProps> = (props) => {
</div>
</div>
)}
{isReconnecting && (
<div className="prose prose-invert !w-full !max-w-full bg-red p-3 text-center text-white">
<span className="flex w-full items-center justify-center">
Disconnected from Encore Daemon. Attempting to reconnect{" "}
{icons.loading("ml-3 h-4 w-4", "#EEEEEE", "transparent", 2)}
</span>
</div>
)}
{!isAppRunning && (
<div className="prose prose-invert !w-full !max-w-full bg-codeblue p-3 text-center text-white">
<div className="flex w-full flex-col items-center justify-center">
<div className="flex items-center space-x-3">
<span>
Attempting to reconnect to <span className="font-semibold">{appID}</span>
</span>
{icons.loading("h-4 w-4", "#EEEEEE", "transparent", 2)}
</div>
<div className="flex justify-center">
<span>
Make sure the app is running by executing{" "}
<code className="border-white">encore run</code> in your terminal from the projects
root directory
</span>
</div>
</div>
</div>
)}
</nav>
);
};
Expand Down
16 changes: 9 additions & 7 deletions cli/daemon/dash/dashapp/src/lib/client/base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import JSONRPCConn from "./jsonrpc";
import { ResponseError } from "./errs";
import ReconnectingWebSocket from "reconnecting-websocket";

const DEV = import.meta.env.DEV;

Expand All @@ -22,15 +23,16 @@ export default class BaseClient {
}
}

ws(path: string): Promise<WebSocket> {
ws(path: string): Promise<ReconnectingWebSocket> {
const base = this.base;
return new Promise<WebSocket>(function (resolve, reject) {
let ws = new WebSocket(`ws://${base}${path}`);
ws.onopen = function () {
return new Promise<ReconnectingWebSocket>(function (resolve, reject) {
let ws = new ReconnectingWebSocket(`ws://${base}${path}`);
ws.addEventListener("open", () => {
ws.onerror = null;
resolve(ws);
};
ws.onerror = function (err: any) {
});

ws.addEventListener("error", (err: any) => {
if (DEV) {
reject(
new Error(
Expand All @@ -39,7 +41,7 @@ export default class BaseClient {
);
}
reject(new ResponseError(path, "network", null, err));
};
});
});
}

Expand Down
15 changes: 12 additions & 3 deletions cli/daemon/dash/dashapp/src/lib/client/jsonrpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EventEmitter } from "events";
import * as protocol from "json-rpc-protocol";
import ReconnectingWebSocket from "reconnecting-websocket";

function makeAsync<T>(fn: (msg: Message) => T): (msg: Message) => Promise<T> {
return function (msg) {
Expand Down Expand Up @@ -43,16 +44,24 @@ export type Message = RequestMsg | ResponseMsg | NotificationMsg | ErrorMsg;

export default class JSONRPCConn extends EventEmitter {
_peer: Peer;
_ws: WebSocket;
_ws: ReconnectingWebSocket;

constructor(ws: WebSocket) {
constructor(ws: ReconnectingWebSocket) {
super();
this._ws = ws;
this._peer = new Peer(
(msg) => ws.send(msg),
(msg) => this.emit("notification", msg)
);
ws.onmessage = (event) => this._peer.processMsg(event.data);
ws.addEventListener("message", (event) => {
this._peer.processMsg(event.data);
});
ws.addEventListener("close", () => {
this.emit("close", "connection closed");
});
ws.addEventListener("open", () => {
this.emit("reconnect", "reconnected");
});
}

async request<T>(method: string, params?: any): Promise<T> {
Expand Down
11 changes: 3 additions & 8 deletions cli/daemon/dash/dashapp/src/pages/AppAPI.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import React, { FunctionComponent } from "react";
import { useParams } from "react-router-dom";
import AppAPI from "~c/app/AppAPI";
import Nav from "~c/Nav";
import { useConn } from "~lib/ctx";

const API: FunctionComponent = (props) => {
const conn = useConn();
const { appID } = useParams<{ appID: string }>();

return (
<>
<Nav />

<section className="flex flex-grow flex-col items-center">
<AppAPI appID={appID!} conn={conn} />
</section>
</>
<section className="flex flex-grow flex-col items-center">
<AppAPI appID={appID!} conn={conn} />
</section>
);
};

Expand Down
8 changes: 1 addition & 7 deletions cli/daemon/dash/dashapp/src/pages/AppDiagram.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { FunctionComponent, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Nav from "~c/Nav";
import { useConn } from "~lib/ctx";
import { NotificationMsg } from "~lib/client/jsonrpc";
import { ProcessReload } from "~lib/client/client";
Expand Down Expand Up @@ -33,12 +32,7 @@ const Diagram: FunctionComponent = () => {
}, []);

return (
<>
<Nav />
<div className="h-full-minus-nav w-full">
{metaData && <FlowDiagram metaData={metaData} />}
</div>
</>
<div className="h-full-minus-nav w-full">{metaData && <FlowDiagram metaData={metaData} />}</div>
);
};

Expand Down
33 changes: 14 additions & 19 deletions cli/daemon/dash/dashapp/src/pages/AppHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,31 @@ import React, { FunctionComponent } from "react";
import { useParams } from "react-router-dom";
import AppCaller from "~c/app/AppCaller";
import AppTraces from "~c/app/AppTraces";
import Nav from "~c/Nav";
import { useConn } from "~lib/ctx";

const AppHome: FunctionComponent = (props) => {
const { appID } = useParams<{ appID: string }>();
const conn = useConn();

return (
<>
<Nav />

<section className="bg-gray-200 flex flex-grow flex-col items-center py-6">
<div className="w-full px-4 md:px-10">
<div className="md:flex md:items-stretch">
<div className="min-w-0 flex-1 md:mr-8">
<h2 className="text-lg font-medium">API Explorer</h2>
<div className="mt-2 rounded-lg">
<AppCaller key={appID} appID={appID!} conn={conn} />
</div>
<section className="bg-gray-200 flex flex-grow flex-col items-center py-6">
<div className="w-full px-4 md:px-10">
<div className="md:flex md:items-stretch">
<div className="min-w-0 flex-1 md:mr-8">
<h2 className="text-lg font-medium">API Explorer</h2>
<div className="mt-2 rounded-lg">
<AppCaller key={appID} appID={appID!} conn={conn} />
</div>
<div className="mt-4 min-w-0 flex-1 md:mt-0">
<h2 className="text-lg font-medium">Traces</h2>
<div className="mt-2 overflow-hidden rounded-lg">
<AppTraces key={appID} appID={appID!} conn={conn} />
</div>
</div>
<div className="mt-4 min-w-0 flex-1 md:mt-0">
<h2 className="text-lg font-medium">Traces</h2>
<div className="mt-2 overflow-hidden rounded-lg">
<AppTraces key={appID} appID={appID!} conn={conn} />
</div>
</div>
</div>
</section>
</>
</div>
</section>
);
};

Expand Down
Loading

0 comments on commit 979f3c5

Please sign in to comment.