From a512f16a5dcce418f049491a969f85f7248f2486 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Thu, 27 Jan 2022 03:42:06 +0000 Subject: [PATCH 01/10] Workspace stub --- site/components/Workspace/Workspace.tsx | 62 +++++++++++++++++++ site/components/Workspace/index.ts | 1 + .../workspaces/[user]/[workspace]/index.tsx | 46 ++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 site/components/Workspace/Workspace.tsx create mode 100644 site/components/Workspace/index.ts create mode 100644 site/pages/workspaces/[user]/[workspace]/index.tsx diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx new file mode 100644 index 0000000000000..e4569ef9063f8 --- /dev/null +++ b/site/components/Workspace/Workspace.tsx @@ -0,0 +1,62 @@ +import Paper from "@material-ui/core/Paper" +import { makeStyles } from "@material-ui/core/styles" +import React from "react" + +import * as API from "../../api" + +export interface WorkspaceProps { + workspace: API.Workspace +} + +export const Workspace: React.FC = ({ workspace }) => { + const styles = useStyles() + + return
+ +
Hello
+
+
+ +
Apps
+
+ +
Build stuff
+
+
+
+} + +namespace Constants { + export const CardRadius = 8 + export const CardPadding = 20 +} + +export const useStyles = makeStyles((theme) => { + + const common = { + border: `1px solid ${theme.palette.divider}`, + borderRadius: Constants.CardRadius, + margin: theme.spacing(1), + padding: Constants.CardPadding + } + + return { + root: { + display: "flex", + flexDirection: "column" + }, + horizontal: { + display: "flex", + flexDirection: "row" + }, + section: common, + sideBar: { + ...common, + width: "400px" + }, + main: { + ...common, + flex: 1 + } + } +}) \ No newline at end of file diff --git a/site/components/Workspace/index.ts b/site/components/Workspace/index.ts new file mode 100644 index 0000000000000..23b9b908c9768 --- /dev/null +++ b/site/components/Workspace/index.ts @@ -0,0 +1 @@ +export * from "./Workspace" \ No newline at end of file diff --git a/site/pages/workspaces/[user]/[workspace]/index.tsx b/site/pages/workspaces/[user]/[workspace]/index.tsx new file mode 100644 index 0000000000000..3b618d14abf6f --- /dev/null +++ b/site/pages/workspaces/[user]/[workspace]/index.tsx @@ -0,0 +1,46 @@ +import React from "react" +import { makeStyles } from "@material-ui/core/styles" +import Paper from "@material-ui/core/Paper" +import { useRouter } from "next/router" +import Link from "next/link" +import { Navbar } from "../../../../components/Navbar" +import { Footer } from "../../../../components/Page" +import { useUser } from "../../../../contexts/UserContext" + +import { Workspace } from "../../../../components/Workspace" +import { MockWorkspace } from "../../../../test_helpers" + + +const ProjectsPage: React.FC = () => { + const styles = useStyles() + const router = useRouter() + const { me, signOut } = useUser(true) + + const { user, workspace } = router.query + + return ( +
+ + +
+ +
+ +
+
+ ) +} + +const useStyles = makeStyles(() => ({ + root: { + display: "flex", + flexDirection: "column", + }, + inner: { + maxWidth: "1380px", + margin: "1em auto", + width: "100%" + } +})) + +export default ProjectsPage From fa6c8c390d831ef0761d571f1b69159a004b9289 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Sat, 29 Jan 2022 23:41:55 +0000 Subject: [PATCH 02/10] More prototyping --- package.json | 2 +- site/components/Workspace/Workspace.tsx | 186 +++++++++++++++++- .../workspaces/[user]/[workspace]/index.tsx | 4 +- site/static/apple-logo.svg | 11 ++ site/static/google-storage-logo.svg | 1 + site/static/react-icon.svg | 9 + site/static/terminal.svg | 1 + site/static/vscode.svg | 1 + site/static/windows-logo.svg | 1 + yarn.lock | 12 +- 10 files changed, 213 insertions(+), 15 deletions(-) create mode 100644 site/static/apple-logo.svg create mode 100644 site/static/google-storage-logo.svg create mode 100644 site/static/react-icon.svg create mode 100644 site/static/terminal.svg create mode 100644 site/static/vscode.svg create mode 100644 site/static/windows-logo.svg diff --git a/package.json b/package.json index 6ae8df91d9cde..f919623f64ad5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "devDependencies": { "@material-ui/core": "4.9.4", "@material-ui/icons": "4.5.1", - "@material-ui/lab": "4.0.0-alpha.42", + "@material-ui/lab": "4.0.0-alpha.60", "@testing-library/react": "12.1.2", "@types/express": "4.17.13", "@types/jest": "27.4.0", diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index e4569ef9063f8..bc960b54e29a6 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -1,5 +1,6 @@ import Paper from "@material-ui/core/Paper" import { makeStyles } from "@material-ui/core/styles" +import Typography from "@material-ui/core/Typography" import React from "react" import * as API from "../../api" @@ -8,19 +9,187 @@ export interface WorkspaceProps { workspace: API.Workspace } +const useStatusStyles = makeStyles((theme) => { + + const common = { + width: theme.spacing(1), + height: theme.spacing(1), + borderRadius: "100%", + backgroundColor: theme.palette.action.disabled, + transition: "background-color 200ms ease", + }; + + return { + inactive: common, + active: { + ...common, + backgroundColor: theme.palette.primary.main, + } + } +}) + +/** + * A component that displays the Dev URL indicator. The indicator status represents + * loading, online, offline, or error. + */ +export const StatusIndicator: React.FC<{ status: ResourceStatus }> = ({ status }) => { + const styles = useStatusStyles() + + const className = status === "active" ? styles.active : styles.inactive + return ( +
+ ) +} + + +type ResourceStatus = "active" | "inactive" + +export interface ResourceRowProps { + name: string + icon: string + //href: string + status: ResourceStatus +} + +const ResourceIconSize = 20 + +export const ResourceRow: React.FC = ({ icon, /*href,*/ name, status }) => { + const styles = useResourceRowStyles() + + return
+
+ +
+
+ {name} +
+
+ +
+
+} + +const useResourceRowStyles = makeStyles((theme) => ({ + root: { + display: "flex", + flexDirection: "row", + justifyContent: "center", + alignItems: "center", + }, + iconContainer: { + width: ResourceIconSize + theme.spacing(1), + height: ResourceIconSize + theme.spacing(1), + display: "flex", + justifyContent: "center", + alignItems: "center", + flex: 0, + }, + nameContainer: { + margin: theme.spacing(1), + paddingLeft: theme.spacing(1), + flex: 1, + width: "100%", + }, + statusContainer: { + width: 24, + height: 24, + flex: 0, + display: "flex", + justifyContent: "center", + alignItems: "center", + } +})) + +export const Title: React.FC = ({ children }) => { + const styles = useTitleStyles(); + + return
+ {children} +
+} + +const useTitleStyles = makeStyles((theme) => ({ + header: { + alignItems: "center", + borderBottom: `1px solid ${theme.palette.divider}`, + display: "flex", + height: theme.spacing(6), + justifyContent: "space-between", + marginBottom: theme.spacing(2), + marginTop: -theme.spacing(1), + paddingBottom: theme.spacing(1), + paddingLeft: Constants.CardPadding + theme.spacing(1), + paddingRight: Constants.CardPadding / 2, + }, +})) + +import Timeline from "@material-ui/lab/Timeline" +import TimelineItem from '@material-ui/lab/TimelineItem'; +import TimelineSeparator from '@material-ui/lab/TimelineSeparator'; +import TimelineConnector from '@material-ui/lab/TimelineConnector'; +import TimelineContent from '@material-ui/lab/TimelineContent'; +import TimelineDot from '@material-ui/lab/TimelineDot'; + +export const WorkspaceTimeline: React.FC = () => { + return + + + + + + Eat + + + + + + + Code + + + + + + Sleep + + +} + export const Workspace: React.FC = ({ workspace }) => { const styles = useStyles() return
-
Hello
+ {workspace.name} + + {"TODO: Project"} +
- -
Apps
-
+
+ + Applications + +
+ + + + +
+
+ + Resources + +
+ + + +
+
+
-
Build stuff
+ Timeline +
@@ -49,9 +218,14 @@ export const useStyles = makeStyles((theme) => { display: "flex", flexDirection: "row" }, + vertical: { + display: "flex", + flexDirection: "column" + }, section: common, sideBar: { - ...common, + display: "flex", + flexDirection: "column", width: "400px" }, main: { diff --git a/site/pages/workspaces/[user]/[workspace]/index.tsx b/site/pages/workspaces/[user]/[workspace]/index.tsx index 3b618d14abf6f..b568e43982a59 100644 --- a/site/pages/workspaces/[user]/[workspace]/index.tsx +++ b/site/pages/workspaces/[user]/[workspace]/index.tsx @@ -11,7 +11,7 @@ import { Workspace } from "../../../../components/Workspace" import { MockWorkspace } from "../../../../test_helpers" -const ProjectsPage: React.FC = () => { +const WorkspacesPage: React.FC = () => { const styles = useStyles() const router = useRouter() const { me, signOut } = useUser(true) @@ -43,4 +43,4 @@ const useStyles = makeStyles(() => ({ } })) -export default ProjectsPage +export default WorkspacesPage diff --git a/site/static/apple-logo.svg b/site/static/apple-logo.svg new file mode 100644 index 0000000000000..2e7254d234e4d --- /dev/null +++ b/site/static/apple-logo.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/site/static/google-storage-logo.svg b/site/static/google-storage-logo.svg new file mode 100644 index 0000000000000..d30e0030858b7 --- /dev/null +++ b/site/static/google-storage-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/static/react-icon.svg b/site/static/react-icon.svg new file mode 100644 index 0000000000000..ea77a618d9486 --- /dev/null +++ b/site/static/react-icon.svg @@ -0,0 +1,9 @@ + + React Logo + + + + + + + diff --git a/site/static/terminal.svg b/site/static/terminal.svg new file mode 100644 index 0000000000000..21d039ce38fb9 --- /dev/null +++ b/site/static/terminal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/static/vscode.svg b/site/static/vscode.svg new file mode 100644 index 0000000000000..62505392b2eeb --- /dev/null +++ b/site/static/vscode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/static/windows-logo.svg b/site/static/windows-logo.svg new file mode 100644 index 0000000000000..6fac0df1bd603 --- /dev/null +++ b/site/static/windows-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index cc89e71215e95..45f21026ab8f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -606,16 +606,16 @@ dependencies: "@babel/runtime" "^7.4.4" -"@material-ui/lab@4.0.0-alpha.42": - version "4.0.0-alpha.42" - resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.42.tgz#f8789d3ba39a1e5a13f462d618c2eec53f87ae10" - integrity sha512-JbKEMIXSslh03u6HNU1Pp1VXd9ycJ1dqkI+iQK6yR+Sng2mvMKzJ80GCV5ROXAXwwNnD8zHOopLZNIpTsEAVgQ== +"@material-ui/lab@4.0.0-alpha.60": + version "4.0.0-alpha.60" + resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.60.tgz#5ad203aed5a8569b0f1753945a21a05efa2234d2" + integrity sha512-fadlYsPJF+0fx2lRuyqAuJj7hAS1tLDdIEEdov5jlrpb5pp4b+mRDUqQTUxi4inRZHS1bEXpU8QWUhO6xX88aA== dependencies: "@babel/runtime" "^7.4.4" - "@material-ui/utils" "^4.7.1" + "@material-ui/utils" "^4.11.2" clsx "^1.0.4" prop-types "^15.7.2" - react-is "^16.8.0" + react-is "^16.8.0 || ^17.0.0" "@material-ui/styles@^4.9.0": version "4.11.4" From 0e933bb1c15ff04db7b49231d51c1f2ebe68e993 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 31 Jan 2022 18:31:50 +0000 Subject: [PATCH 03/10] Route to correct path --- site/pages/projects/[organization]/[project]/create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/pages/projects/[organization]/[project]/create.tsx b/site/pages/projects/[organization]/[project]/create.tsx index ce2c66508a808..54e37d0847229 100644 --- a/site/pages/projects/[organization]/[project]/create.tsx +++ b/site/pages/projects/[organization]/[project]/create.tsx @@ -32,7 +32,7 @@ const CreateWorkspacePage: React.FC = () => { const onSubmit = async (req: API.CreateWorkspaceRequest) => { const workspace = await API.Workspace.create(req) - await router.push(`/workspaces/${workspace.id}`) + await router.push(`/workspaces/${me.username}/${workspace.name}`) return workspace } From ad56a134d46ddc50ab8b188b513c3bc55c7f4980 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 31 Jan 2022 18:38:13 +0000 Subject: [PATCH 04/10] Port over QuestionHelp component --- site/components/QuestionHelp.tsx | 23 +++++++++++++++++++++++ site/components/Workspace/Workspace.tsx | 3 ++- site/components/index.tsx | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 site/components/QuestionHelp.tsx diff --git a/site/components/QuestionHelp.tsx b/site/components/QuestionHelp.tsx new file mode 100644 index 0000000000000..b1ee4782d89d1 --- /dev/null +++ b/site/components/QuestionHelp.tsx @@ -0,0 +1,23 @@ +import { makeStyles } from "@material-ui/core/styles" +import HelpIcon from "@material-ui/icons/Help" +import * as React from "react" + +export const QuestionHelp: React.FC = () => { + const styles = useStyles() + return ( + + ) +} + +const useStyles = makeStyles((theme) => ({ + icon: { + display: "block", + height: 20, + width: 20, + color: theme.palette.text.secondary, + opacity: 0.5, + "&:hover": { + opacity: 1, + }, + }, +})) diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index bc960b54e29a6..4d8ae30c8327d 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -129,6 +129,7 @@ import TimelineSeparator from '@material-ui/lab/TimelineSeparator'; import TimelineConnector from '@material-ui/lab/TimelineConnector'; import TimelineContent from '@material-ui/lab/TimelineContent'; import TimelineDot from '@material-ui/lab/TimelineDot'; +import { QuestionHelp } from "../QuestionHelp" export const WorkspaceTimeline: React.FC = () => { return @@ -168,7 +169,7 @@ export const Workspace: React.FC = ({ workspace }) => {
- Applications + <span>Applications</span><QuestionHelp />
diff --git a/site/components/index.tsx b/site/components/index.tsx index ebb1a90188bb8..7abcb3943c90b 100644 --- a/site/components/index.tsx +++ b/site/components/index.tsx @@ -1,4 +1,5 @@ export * from "./Button" export * from "./EmptyState" export * from "./Page" +export * from "./QuestionHelp" export * from "./Redirect" From 07d5711c1143875d370b447c9c093ab41f95d44f Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Mon, 31 Jan 2022 19:00:17 +0000 Subject: [PATCH 05/10] Add spinner --- site/components/QuestionHelp.tsx | 4 +- site/components/Workspace/Workspace.tsx | 213 ++++++++++-------- site/components/Workspace/index.ts | 2 +- .../workspaces/[user]/[workspace]/index.tsx | 5 +- 4 files changed, 123 insertions(+), 101 deletions(-) diff --git a/site/components/QuestionHelp.tsx b/site/components/QuestionHelp.tsx index b1ee4782d89d1..5a4f44851c12a 100644 --- a/site/components/QuestionHelp.tsx +++ b/site/components/QuestionHelp.tsx @@ -4,9 +4,7 @@ import * as React from "react" export const QuestionHelp: React.FC = () => { const styles = useStyles() - return ( - - ) + return } const useStyles = makeStyles((theme) => ({ diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index 4d8ae30c8327d..d9a5a07d9ab7c 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -1,6 +1,7 @@ import Paper from "@material-ui/core/Paper" import { makeStyles } from "@material-ui/core/styles" import Typography from "@material-ui/core/Typography" +import OpenInNewIcon from "@material-ui/icons/OpenInNew" import React from "react" import * as API from "../../api" @@ -10,21 +11,20 @@ export interface WorkspaceProps { } const useStatusStyles = makeStyles((theme) => { - const common = { width: theme.spacing(1), height: theme.spacing(1), borderRadius: "100%", backgroundColor: theme.palette.action.disabled, transition: "background-color 200ms ease", - }; + } return { inactive: common, active: { ...common, backgroundColor: theme.palette.primary.main, - } + }, } }) @@ -35,14 +35,15 @@ const useStatusStyles = makeStyles((theme) => { export const StatusIndicator: React.FC<{ status: ResourceStatus }> = ({ status }) => { const styles = useStatusStyles() - const className = status === "active" ? styles.active : styles.inactive - return ( -
- ) + if (status == "loading") { + return + } else { + const className = status === "active" ? styles.active : styles.inactive + return
+ } } - -type ResourceStatus = "active" | "inactive" +type ResourceStatus = "active" | "inactive" | "loading" export interface ResourceRowProps { name: string @@ -53,20 +54,31 @@ export interface ResourceRowProps { const ResourceIconSize = 20 -export const ResourceRow: React.FC = ({ icon, /*href,*/ name, status }) => { +export const ResourceRow: React.FC = ({ icon, href, name, status }) => { const styles = useResourceRowStyles() - return
-
- -
-
- {name} -
-
- + return ( +
+
+ +
+
+ + {name} + + +
+
+ +
-
+ ) } const useResourceRowStyles = makeStyles((theme) => ({ @@ -97,15 +109,13 @@ const useResourceRowStyles = makeStyles((theme) => ({ display: "flex", justifyContent: "center", alignItems: "center", - } + }, })) export const Title: React.FC = ({ children }) => { - const styles = useTitleStyles(); + const styles = useTitleStyles() - return
- {children} -
+ return
{children}
} const useTitleStyles = makeStyles((theme) => ({ @@ -113,8 +123,8 @@ const useTitleStyles = makeStyles((theme) => ({ alignItems: "center", borderBottom: `1px solid ${theme.palette.divider}`, display: "flex", + flexDirection: "row", height: theme.spacing(6), - justifyContent: "space-between", marginBottom: theme.spacing(2), marginTop: -theme.spacing(1), paddingBottom: theme.spacing(1), @@ -124,76 +134,92 @@ const useTitleStyles = makeStyles((theme) => ({ })) import Timeline from "@material-ui/lab/Timeline" -import TimelineItem from '@material-ui/lab/TimelineItem'; -import TimelineSeparator from '@material-ui/lab/TimelineSeparator'; -import TimelineConnector from '@material-ui/lab/TimelineConnector'; -import TimelineContent from '@material-ui/lab/TimelineContent'; -import TimelineDot from '@material-ui/lab/TimelineDot'; +import TimelineItem from "@material-ui/lab/TimelineItem" +import TimelineSeparator from "@material-ui/lab/TimelineSeparator" +import TimelineConnector from "@material-ui/lab/TimelineConnector" +import TimelineContent from "@material-ui/lab/TimelineContent" +import TimelineDot from "@material-ui/lab/TimelineDot" import { QuestionHelp } from "../QuestionHelp" +import { CircularProgress, Link } from "@material-ui/core" export const WorkspaceTimeline: React.FC = () => { - return - - - - - - Eat - - - - - - - Code - - - - - - Sleep - - + return ( + + + + + + + Eat + + + + + + + Code + + + + + + Sleep + + + ) } export const Workspace: React.FC = ({ workspace }) => { const styles = useStyles() - return
- - {workspace.name} - - {"TODO: Project"} - - -
-
- - <span>Applications</span><QuestionHelp /> - -
- - - - -
-
- - Resources - -
- - - -
+ return ( +
+ + {workspace.name} + + {"TODO: Project"} + + +
+
+ + + <Typography variant="h6">Applications</Typography> + <div style={{ margin: "0em 1em" }}> + <QuestionHelp /> + </div> + + +
+ + + +
+
+ + + <Typography variant="h6">Resources</Typography> + <div style={{ margin: "0em 1em" }}> + <QuestionHelp /> + </div> + + +
+ + + +
+
+
+ + + <Typography variant="h6">Timeline</Typography> + +
- - Timeline - -
-
+ ) } namespace Constants { @@ -202,36 +228,35 @@ namespace Constants { } export const useStyles = makeStyles((theme) => { - const common = { border: `1px solid ${theme.palette.divider}`, borderRadius: Constants.CardRadius, margin: theme.spacing(1), - padding: Constants.CardPadding + padding: Constants.CardPadding, } return { root: { display: "flex", - flexDirection: "column" + flexDirection: "column", }, horizontal: { display: "flex", - flexDirection: "row" + flexDirection: "row", }, vertical: { display: "flex", - flexDirection: "column" + flexDirection: "column", }, section: common, sideBar: { display: "flex", flexDirection: "column", - width: "400px" + width: "400px", }, main: { ...common, - flex: 1 - } + flex: 1, + }, } -}) \ No newline at end of file +}) diff --git a/site/components/Workspace/index.ts b/site/components/Workspace/index.ts index 23b9b908c9768..4c8c38cc721c8 100644 --- a/site/components/Workspace/index.ts +++ b/site/components/Workspace/index.ts @@ -1 +1 @@ -export * from "./Workspace" \ No newline at end of file +export * from "./Workspace" diff --git a/site/pages/workspaces/[user]/[workspace]/index.tsx b/site/pages/workspaces/[user]/[workspace]/index.tsx index b568e43982a59..c019f7d7f8e90 100644 --- a/site/pages/workspaces/[user]/[workspace]/index.tsx +++ b/site/pages/workspaces/[user]/[workspace]/index.tsx @@ -10,7 +10,6 @@ import { useUser } from "../../../../contexts/UserContext" import { Workspace } from "../../../../components/Workspace" import { MockWorkspace } from "../../../../test_helpers" - const WorkspacesPage: React.FC = () => { const styles = useStyles() const router = useRouter() @@ -39,8 +38,8 @@ const useStyles = makeStyles(() => ({ inner: { maxWidth: "1380px", margin: "1em auto", - width: "100%" - } + width: "100%", + }, })) export default WorkspacesPage From 48bb588385040b55245753993da2f07a35cd19fa Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Tue, 1 Feb 2022 00:14:05 +0000 Subject: [PATCH 06/10] Model workspace with static data --- site/components/Timeline/TerminalOutput.tsx | 38 ++++ site/components/Timeline/index.tsx | 235 ++++++++++++++++++++ site/components/Workspace/Workspace.tsx | 68 ++---- 3 files changed, 297 insertions(+), 44 deletions(-) create mode 100644 site/components/Timeline/TerminalOutput.tsx create mode 100644 site/components/Timeline/index.tsx diff --git a/site/components/Timeline/TerminalOutput.tsx b/site/components/Timeline/TerminalOutput.tsx new file mode 100644 index 0000000000000..2a55322e6e363 --- /dev/null +++ b/site/components/Timeline/TerminalOutput.tsx @@ -0,0 +1,38 @@ +import { makeStyles } from "@material-ui/core/styles" +import React from "react" + +interface Props { + output: string[] + className?: string +} + +export const TerminalOutput: React.FC = ({ className, output }) => { + const styles = useStyles() + + return ( +
+ {output.map((line, idx) => ( +
+ {line} +
+ ))} +
+ ) +} +export const MONOSPACE_FONT_FAMILY = + "'Fira Code', 'Lucida Console', 'Lucida Sans Typewriter', 'Liberation Mono', 'Monaco', 'Courier New', Courier, monospace" +const useStyles = makeStyles((theme) => ({ + root: { + minHeight: 156, + background: theme.palette.background.default, + //color: theme.palette.codeBlock.contrastText, + fontFamily: MONOSPACE_FONT_FAMILY, + fontSize: 13, + wordBreak: "break-all", + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + }, + line: { + whiteSpace: "pre-wrap", + }, +})) diff --git a/site/components/Timeline/index.tsx b/site/components/Timeline/index.tsx new file mode 100644 index 0000000000000..be1f462fd7764 --- /dev/null +++ b/site/components/Timeline/index.tsx @@ -0,0 +1,235 @@ +import { Avatar, Box, SvgIcon, Typography } from "@material-ui/core" +import makeStyles from "@material-ui/styles/makeStyles"; +import React, { useState } from "react" +import { TerminalOutput } from "./TerminalOutput"; + +export interface TimelineEntry { + date: Date + title: string + description?: string +} + +const today = new Date(); +const yesterday = new Date() +yesterday.setHours(-24) +const weekAgo = new Date() +weekAgo.setHours(-24 * 7) + +const sampleOutput = ` +Successfully assigned coder/bryan-prototype-jppnd to gke-master-workspaces-1-ef039342-cybd +Container image "gke.gcr.io/istio/proxyv2:1.4.10-gke.8" already present on machine +Created container istio-init +Started container istio-init +Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" +Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s +Successfully assigned coder/bryan-prototype-jppnd to gke-master-workspaces-1-ef039342-cybd +Container image "gke.gcr.io/istio/proxyv2:1.4.10-gke.8" already present on machine +Created container istio-init +Started container istio-init +Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" +Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s +Successfully assigned coder/bryan-prototype-jppnd to gke-master-workspaces-1-ef039342-cybd +Container image "gke.gcr.io/istio/proxyv2:1.4.10-gke.8" already present on machine +Created container istio-init +Started container istio-init +Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" +Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s +`.split("\n") + +export const mockEntries: TimelineEntry[] = [{ + date: weekAgo, + description: "Created Workspace", + title: "Admin", +}, { + date: yesterday, + description: "Modified Workspace", + title: "Admin" +}, { + date: today, + description: "Modified Workspace", + title: "Admin" +}, { + date: today, + description: "Restarted Workspace", + title: "Admin" + +}] + +export interface TimelineEntryProps { + entries: TimelineEntry[] +} + +// Group timeline entry by date + +const getDateWithoutTime = (date: Date) => { + // TODO: Handle conversion to local time from UTC, as this may shift the actual day + const dateWithoutTime = new Date(date.getTime()) + dateWithoutTime.setHours(0, 0, 0, 0) + return dateWithoutTime +} + +export const groupByDate = (entries: TimelineEntry[]): Record => { + const initial: Record = {}; + return entries.reduce>((acc, curr) => { + const dateWithoutTime = getDateWithoutTime(curr.date); + const key = dateWithoutTime.getTime().toString() + const currentEntry = acc[key]; + if (currentEntry) { + return { + ...acc, + [key]: [...currentEntry, curr] + } + } else { + return { + ...acc, + [key]: [curr] + } + } + }, initial) + +} + +const formatDate = (date: Date) => { + let formatter = new Intl.DateTimeFormat("en", { + dateStyle: "long" + }); + return formatter.format(date) +} + +const formatTime = (date: Date) => { + let formatter = new Intl.DateTimeFormat("en", { + timeStyle: "short" + }); + return formatter.format(date) +} + + + +export interface EntryProps { + entry: TimelineEntry +} + +export const Entry: React.FC = ({ entry }) => { + const styles = useEntryStyles() + const [expanded, setExpanded] = useState(false) + + const toggleExpanded = () => { + setExpanded((prev: boolean) => !prev) + } + + return + + + {"A"} + + + + + {entry.title} + {formatTime(entry.date)} + + {entry.description} + + + + + + + +} + +export const useEntryStyles = makeStyles((theme) => ({ + +})) + +export type BuildLogStatus = "success" | "failure" | "pending" + +export interface BuildLogProps { + summary: string + status: BuildLogStatus + expanded?: boolean +} + +export const BuildLog: React.FC = ({ summary, status, expanded }) => { + const styles = useBuildLogStyles(status)() + + return
+ +
+ +} + +const useBuildLogStyles = (status: BuildLogStatus) => makeStyles((theme) => ({ + container: { + borderLeft: `2px solid ${status === "failure" ? theme.palette.error.main : theme.palette.info.main}`, + margin: "1em 0em", + }, + collapseButton: { + color: "inherit", + textAlign: "left", + width: "100%", + background: "none", + border: 0, + alignItems: "center", + borderRadius: theme.spacing(0.5), + cursor: "pointer", + "&:disabled": { + color: "inherit", + cursor: "initial", + }, + "&:hover:not(:disabled)": { + backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100], + }, + }, +})) + +export const Timeline: React.FC = () => { + const styles = useStyles() + + const entries = mockEntries + const groupedByDate = groupByDate(entries) + const allDates = Object.keys(groupedByDate); + const sortedDates = allDates.sort((a, b) => b.localeCompare(a)) + + const days = sortedDates.map((date) => { + + const entriesForDay = groupedByDate[date]; + + const entryElements = entriesForDay.map((entry) => ) + + + return
+ {formatDate(new Date(Number.parseInt(date)))} + {entryElements} + +
+ }) + + return
+ {days} +
+ +} + +export const useStyles = makeStyles((theme) => ({ + root: { + display: "flex", + width: "100%", + flexDirection: "column" + }, + container: { + display: "flex", + flexDirection: "column", + }, + header: { + display: "flex", + justifyContent: "center", + alignItems: "center", + //textTransform: "uppercase" + } +})) \ No newline at end of file diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index d9a5a07d9ab7c..6c4fe167fd649 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -3,6 +3,11 @@ import { makeStyles } from "@material-ui/core/styles" import Typography from "@material-ui/core/Typography" import OpenInNewIcon from "@material-ui/icons/OpenInNew" import React from "react" +import MoreVertIcon from "@material-ui/icons/MoreVert" +import { QuestionHelp } from "../QuestionHelp" +import { CircularProgress, IconButton, Link } from "@material-ui/core" + +import { Timeline as TestTimeline } from "../Timeline" import * as API from "../../api" @@ -36,7 +41,7 @@ export const StatusIndicator: React.FC<{ status: ResourceStatus }> = ({ status } const styles = useStatusStyles() if (status == "loading") { - return + return } else { const className = status === "active" ? styles.active : styles.inactive return
@@ -48,7 +53,7 @@ type ResourceStatus = "active" | "inactive" | "loading" export interface ResourceRowProps { name: string icon: string - //href: string + href?: string status: ResourceStatus } @@ -63,7 +68,7 @@ export const ResourceRow: React.FC = ({ icon, href, name, stat
- = ({ icon, href, name, stat > {name} - + : {name}}
+
+ + + +
) } @@ -110,6 +120,11 @@ const useResourceRowStyles = makeStyles((theme) => ({ justifyContent: "center", alignItems: "center", }, + action: { + margin: `0 ${theme.spacing(0.5)}px`, + opacity: 0.7, + fontSize: 16, + } })) export const Title: React.FC = ({ children }) => { @@ -133,41 +148,6 @@ const useTitleStyles = makeStyles((theme) => ({ }, })) -import Timeline from "@material-ui/lab/Timeline" -import TimelineItem from "@material-ui/lab/TimelineItem" -import TimelineSeparator from "@material-ui/lab/TimelineSeparator" -import TimelineConnector from "@material-ui/lab/TimelineConnector" -import TimelineContent from "@material-ui/lab/TimelineContent" -import TimelineDot from "@material-ui/lab/TimelineDot" -import { QuestionHelp } from "../QuestionHelp" -import { CircularProgress, Link } from "@material-ui/core" - -export const WorkspaceTimeline: React.FC = () => { - return ( - - - - - - - Eat - - - - - - - Code - - - - - - Sleep - - - ) -} export const Workspace: React.FC = ({ workspace }) => { const styles = useStyles() @@ -177,7 +157,7 @@ export const Workspace: React.FC = ({ workspace }) => { {workspace.name} - {"TODO: Project"} + test-org{" / "}test-project
@@ -191,8 +171,8 @@ export const Workspace: React.FC = ({ workspace }) => {
- - + +
@@ -205,7 +185,7 @@ export const Workspace: React.FC = ({ workspace }) => {
- +
@@ -215,7 +195,7 @@ export const Workspace: React.FC = ({ workspace }) => { <Typography variant="h6">Timeline</Typography> - +
From 8b73c2c34243934f3344f86378b88e3311f9e8f5 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Tue, 1 Feb 2022 00:28:58 +0000 Subject: [PATCH 07/10] Update timeline and components --- site/components/Timeline/index.tsx | 245 ++++++++++++++---------- site/components/Workspace/Workspace.tsx | 38 ++-- 2 files changed, 169 insertions(+), 114 deletions(-) diff --git a/site/components/Timeline/index.tsx b/site/components/Timeline/index.tsx index be1f462fd7764..5cf84335395cd 100644 --- a/site/components/Timeline/index.tsx +++ b/site/components/Timeline/index.tsx @@ -1,15 +1,23 @@ -import { Avatar, Box, SvgIcon, Typography } from "@material-ui/core" -import makeStyles from "@material-ui/styles/makeStyles"; +import { Avatar, Box, CircularProgress, SvgIcon, Typography } from "@material-ui/core" +import makeStyles from "@material-ui/styles/makeStyles" import React, { useState } from "react" -import { TerminalOutput } from "./TerminalOutput"; +import { TerminalOutput } from "./TerminalOutput" +import StageCompleteIcon from "@material-ui/icons/Done" +import StageExpandedIcon from "@material-ui/icons/KeyboardArrowDown" +import StageErrorIcon from "@material-ui/icons/Warning" + +export type BuildLogStatus = "success" | "failed" | "pending" export interface TimelineEntry { date: Date title: string description?: string + status: BuildLogStatus + buildSummary: string + buildLogs: string[] } -const today = new Date(); +const today = new Date() const yesterday = new Date() yesterday.setHours(-24) const weekAgo = new Date() @@ -36,24 +44,40 @@ Pulling image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8 Successfully pulled image "gcr.io/coder-enterprise-nightlies/coder/envbox:1.27.0-rc.0-145-g8d4ee2e9e-20220131" in 7.423772294s `.split("\n") -export const mockEntries: TimelineEntry[] = [{ - date: weekAgo, - description: "Created Workspace", - title: "Admin", -}, { - date: yesterday, - description: "Modified Workspace", - title: "Admin" -}, { - date: today, - description: "Modified Workspace", - title: "Admin" -}, { - date: today, - description: "Restarted Workspace", - title: "Admin" - -}] +export const mockEntries: TimelineEntry[] = [ + { + date: weekAgo, + description: "Created Workspace", + title: "Admin", + status: "success", + buildLogs: sampleOutput, + buildSummary: "Succeeded in 82s", + }, + { + date: yesterday, + description: "Modified Workspace", + title: "Admin", + status: "failed", + buildLogs: sampleOutput, + buildSummary: "Encountered error after 49s", + }, + { + date: today, + description: "Modified Workspace", + title: "Admin", + status: "pending", + buildLogs: sampleOutput, + buildSummary: "Operation in progress...", + }, + { + date: today, + description: "Restarted Workspace", + title: "Admin", + status: "success", + buildLogs: sampleOutput, + buildSummary: "Succeeded in 15s", + }, +] export interface TimelineEntryProps { entries: TimelineEntry[] @@ -69,42 +93,39 @@ const getDateWithoutTime = (date: Date) => { } export const groupByDate = (entries: TimelineEntry[]): Record => { - const initial: Record = {}; + const initial: Record = {} return entries.reduce>((acc, curr) => { - const dateWithoutTime = getDateWithoutTime(curr.date); + const dateWithoutTime = getDateWithoutTime(curr.date) const key = dateWithoutTime.getTime().toString() - const currentEntry = acc[key]; + const currentEntry = acc[key] if (currentEntry) { return { ...acc, - [key]: [...currentEntry, curr] + [key]: [...currentEntry, curr], } } else { return { ...acc, - [key]: [curr] + [key]: [curr], } } }, initial) - } const formatDate = (date: Date) => { let formatter = new Intl.DateTimeFormat("en", { - dateStyle: "long" - }); + dateStyle: "long", + }) return formatter.format(date) } const formatTime = (date: Date) => { let formatter = new Intl.DateTimeFormat("en", { - timeStyle: "short" - }); + timeStyle: "short", + }) return formatter.format(date) } - - export interface EntryProps { entry: TimelineEntry } @@ -117,110 +138,134 @@ export const Entry: React.FC = ({ entry }) => { setExpanded((prev: boolean) => !prev) } - return - - - {"A"} - - - - - {entry.title} - {formatTime(entry.date)} + return ( + + + + {"A"} - {entry.description} - - + + + {entry.title} + + {formatTime(entry.date)} + + + {entry.description} + + + - - + ) } -export const useEntryStyles = makeStyles((theme) => ({ - -})) - -export type BuildLogStatus = "success" | "failure" | "pending" +export const useEntryStyles = makeStyles((theme) => ({})) export interface BuildLogProps { summary: string status: BuildLogStatus expanded?: boolean } - +const STATUS_ICON_SIZE = 18 +const LOADING_SPINNER_SIZE = 14 export const BuildLog: React.FC = ({ summary, status, expanded }) => { const styles = useBuildLogStyles(status)() + let icon: JSX.Element + if (status === "failed") { + icon = + } else if (status === "pending") { + icon = + } else { + icon = + } - return
- -
- + return ( +
+ + {expanded && } +
+ ) } -const useBuildLogStyles = (status: BuildLogStatus) => makeStyles((theme) => ({ - container: { - borderLeft: `2px solid ${status === "failure" ? theme.palette.error.main : theme.palette.info.main}`, - margin: "1em 0em", - }, - collapseButton: { - color: "inherit", - textAlign: "left", - width: "100%", - background: "none", - border: 0, - alignItems: "center", - borderRadius: theme.spacing(0.5), - cursor: "pointer", - "&:disabled": { +const useBuildLogStyles = (status: BuildLogStatus) => + makeStyles((theme) => ({ + container: { + borderLeft: `2px solid ${theme.palette.info.main}`, + margin: "1em 0em", + }, + collapseButton: { color: "inherit", - cursor: "initial", + textAlign: "left", + width: "100%", + background: "none", + border: 0, + alignItems: "center", + borderRadius: theme.spacing(0.5), + cursor: "pointer", + "&:disabled": { + color: "inherit", + cursor: "initial", + }, + "&:hover:not(:disabled)": { + backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100], + }, }, - "&:hover:not(:disabled)": { - backgroundColor: theme.palette.type === "dark" ? theme.palette.grey[800] : theme.palette.grey[100], + statusIcon: { + width: STATUS_ICON_SIZE, + height: STATUS_ICON_SIZE, + color: theme.palette.text.secondary, }, - }, -})) + statusIconError: { + color: theme.palette.error.main, + }, + statusIconSuccess: { + color: theme.palette.success.main, + }, + })) export const Timeline: React.FC = () => { const styles = useStyles() const entries = mockEntries const groupedByDate = groupByDate(entries) - const allDates = Object.keys(groupedByDate); + const allDates = Object.keys(groupedByDate) const sortedDates = allDates.sort((a, b) => b.localeCompare(a)) const days = sortedDates.map((date) => { - - const entriesForDay = groupedByDate[date]; + const entriesForDay = groupedByDate[date] const entryElements = entriesForDay.map((entry) => ) - - return
- {formatDate(new Date(Number.parseInt(date)))} - {entryElements} - -
+ return ( +
+ + {formatDate(new Date(Number.parseInt(date)))} + + {entryElements} +
+ ) }) - return
- {days} -
- + return
{days}
} export const useStyles = makeStyles((theme) => ({ root: { display: "flex", width: "100%", - flexDirection: "column" + flexDirection: "column", }, container: { display: "flex", @@ -231,5 +276,5 @@ export const useStyles = makeStyles((theme) => ({ justifyContent: "center", alignItems: "center", //textTransform: "uppercase" - } -})) \ No newline at end of file + }, +})) diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index 6c4fe167fd649..6d4375964876b 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -68,16 +68,20 @@ export const ResourceRow: React.FC = ({ icon, href, name, stat
- {href ? + {href ? ( + + {name} + + + ) : ( {name} - - : {name}} + )}
@@ -124,7 +128,7 @@ const useResourceRowStyles = makeStyles((theme) => ({ margin: `0 ${theme.spacing(0.5)}px`, opacity: 0.7, fontSize: 16, - } + }, })) export const Title: React.FC = ({ children }) => { @@ -148,7 +152,6 @@ const useTitleStyles = makeStyles((theme) => ({ }, })) - export const Workspace: React.FC = ({ workspace }) => { const styles = useStyles() @@ -157,7 +160,9 @@ export const Workspace: React.FC = ({ workspace }) => { {workspace.name} - test-org{" / "}test-project + test-org + {" / "} + test-project
@@ -173,7 +178,7 @@ export const Workspace: React.FC = ({ workspace }) => {
- +
@@ -185,7 +190,12 @@ export const Workspace: React.FC = ({ workspace }) => {
- +
From 53ff3257133f2aa8dbd146a5822c11edd0c3462a Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Tue, 1 Feb 2022 01:08:48 +0000 Subject: [PATCH 08/10] More prototyping --- site/components/Workspace/Workspace.tsx | 49 ++++++++++++++++++++----- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index 6d4375964876b..94dec0ac641ac 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -1,11 +1,12 @@ +import Box from "@material-ui/core/Box" import Paper from "@material-ui/core/Paper" import { makeStyles } from "@material-ui/core/styles" import Typography from "@material-ui/core/Typography" import OpenInNewIcon from "@material-ui/icons/OpenInNew" -import React from "react" +import React, { useState } from "react" import MoreVertIcon from "@material-ui/icons/MoreVert" import { QuestionHelp } from "../QuestionHelp" -import { CircularProgress, IconButton, Link } from "@material-ui/core" +import { CircularProgress, IconButton, Link, Menu, MenuItem } from "@material-ui/core" import { Timeline as TestTimeline } from "../Timeline" @@ -62,6 +63,8 @@ const ResourceIconSize = 20 export const ResourceRow: React.FC = ({ icon, href, name, status }) => { const styles = useResourceRowStyles() + const [menuAnchorEl, setMenuAnchorEl] = useState(null) + return (
@@ -87,9 +90,26 @@ export const ResourceRow: React.FC = ({ icon, href, name, stat
- + setMenuAnchorEl(ev.currentTarget)}> + + setMenuAnchorEl(undefined)}> + { + setMenuAnchorEl(undefined) + }} + > + SSH + + { + setMenuAnchorEl(undefined) + }} + > + Remote Desktop + +
) @@ -152,18 +172,27 @@ const useTitleStyles = makeStyles((theme) => ({ }, })) +const TitleIconSize = 48 + export const Workspace: React.FC = ({ workspace }) => { const styles = useStyles() return (
- {workspace.name} - - test-org - {" / "} - test-project - +
+ + + +
+ {workspace.name} + + test-org + {" / "} + test-project + +
+
@@ -178,7 +207,7 @@ export const Workspace: React.FC = ({ workspace }) => {
- +
From de7d79beccc6b4ba4a99d7ef4f60b96be3205084 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Tue, 1 Feb 2022 01:39:09 +0000 Subject: [PATCH 09/10] Tweaks --- site/components/Workspace/Workspace.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index 94dec0ac641ac..0ce89b2182473 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -210,6 +210,7 @@ export const Workspace: React.FC = ({ workspace }) => {
+ <Typography variant="h6">Resources</Typography> From dd38938264aeff57d871692db1bcfb7a4972a1f7 Mon Sep 17 00:00:00 2001 From: Bryan Phelps <bryan@coder.com> Date: Wed, 2 Feb 2022 04:59:39 +0000 Subject: [PATCH 10/10] Experiment with resource monitor --- site/components/ResourceMonitor.tsx | 229 ++++++++++++++++++++++++ site/components/Workspace/Workspace.tsx | 50 +++++- site/package.json | 3 + site/yarn.lock | 49 ++++- 4 files changed, 322 insertions(+), 9 deletions(-) create mode 100644 site/components/ResourceMonitor.tsx diff --git a/site/components/ResourceMonitor.tsx b/site/components/ResourceMonitor.tsx new file mode 100644 index 0000000000000..c7738b78483b7 --- /dev/null +++ b/site/components/ResourceMonitor.tsx @@ -0,0 +1,229 @@ +import React from "react" +import { Bar, Line } from "react-chartjs-2" +import { Chart, ChartOptions } from "chart.js" + +const multiply = { + beforeDraw: function (chart: Chart, options: ChartOptions) { + if (chart && chart.ctx) { + chart.ctx.globalCompositeOperation = "multiply" + } + }, + afterDatasetsDraw: function (chart: Chart, options: ChartOptions) { + if (chart && chart.ctx) { + chart.ctx.globalCompositeOperation = "source-over" + } + }, +} + +function formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return "0 Bytes" + + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i] +} + +const padding = 64 + +const opts: ChartOptions = { + responsive: true, + maintainAspectRatio: false, + legend: { + fullWidth: true, + display: false, + }, + elements: { + point: { + radius: 0, + hitRadius: 8, + hoverRadius: 8, + }, + rectangle: { + borderWidth: 0, + }, + }, + layout: { + padding: { + top: padding, + bottom: padding, + }, + }, + tooltips: { + mode: "index", + axis: "y", + cornerRadius: 8, + borderWidth: 0, + titleFontStyle: "normal", + callbacks: { + label: (item: any, data: any) => { + const dataset = data.datasets[item.datasetIndex] + const num: number = dataset.data[item.index] as number + if (num) { + return dataset.label + ": " + num.toFixed(2) + "%" + } + }, + labelColor: (item: any, data: any) => { + const dataset = data.data.datasets[item.datasetIndex] + return { + // Trim off the transparent hex code. + backgroundColor: (dataset.pointBackgroundColor as string).substr(0, 7), + borderColor: "#000000", + } + }, + title: (item) => { + console.log(item[0]) + + return "Resources: " + item[0].label + }, + }, + }, + plugins: { + tooltip: { + callbacks: { + beforeTitle: (item: any) => { + console.log("BEFORE TITLE: " + item) + return "Resources" + }, + }, + }, + legend: { + display: false, + }, + }, + scales: { + xAxes: [ + { + display: false, + ticks: { + stepSize: 10, + maxTicksLimit: 4, + maxRotation: 0, + }, + }, + ], + yAxes: [ + { + gridLines: { + color: "rgba(0, 0, 0, 0.09)", + zeroLineColor: "rgba(0, 0, 0, 0.09)", + }, + ticks: { + callback: (v) => v + "%", + max: 100, + maxTicksLimit: 2, + min: 0, + padding: 4, + }, + }, + ], + }, +} + +export interface ResourceUsageSnapshot { + cpuPercentage: number + memoryUsedBytes: number + diskUsedBytes: number +} + +export interface ResourceMonitorProps { + readonly diskTotalBytes: number + readonly memoryTotalBytes: number + readonly resources: ReadonlyArray<ResourceUsageSnapshot> +} + +export const ResourceMonitor: React.FC<ResourceMonitorProps> = (props) => { + const dataF = React.useMemo(() => { + return (canvas: any) => { + // Store gradients inside the canvas object for easy access. + // This function is called everytime resources values change... + // we don't want to allocate a new gradient everytime. + if (!canvas["cpuGradient"]) { + const cpuGradient = canvas.getContext("2d").createLinearGradient(0, 0, 0, canvas.height) + cpuGradient.addColorStop(1, "#9787FF32") + cpuGradient.addColorStop(0, "#5555FFC4") + canvas["cpuGradient"] = cpuGradient + } + + if (!canvas["memGradient"]) { + const memGradient = canvas.getContext("2d").createLinearGradient(0, 0, 0, canvas.height) + memGradient.addColorStop(1, "#55FF8532") + memGradient.addColorStop(0, "#42B863C4") + canvas["memGradient"] = memGradient + } + + if (!canvas["diskGradient"]) { + const diskGradient = canvas.getContext("2d").createLinearGradient(0, 0, 0, canvas.height) + diskGradient.addColorStop(1, "#97979700") + diskGradient.addColorStop(0, "#797979C4") + canvas["diskGradient"] = diskGradient + } + + const cpuPercentages = [] + //const cpuPercentages = Array(20 - props.resources.length).fill(null) + cpuPercentages.push(...props.resources.map((r) => r.cpuPercentage)) + + //const memPercentages = Array(20 - props.resources.length).fill(null) + const memPercentages = [] + memPercentages.push(...props.resources.map((r) => (r.memoryUsedBytes / props.memoryTotalBytes) * 100)) + + const diskPercentages = [] + //const diskPercentages = Array(20 - props.resources.length).fill(null) + diskPercentages.push(...props.resources.map((r) => (r.diskUsedBytes / props.diskTotalBytes) * 100)) + + return { + labels: Array(20) + .fill(0) + .map((_, index) => (20 - index) * 3 + "s ago"), + datasets: [ + { + label: "CPU", + data: cpuPercentages, + backgroundColor: canvas["cpuGradient"], + borderColor: "transparent", + pointBackgroundColor: "#9787FF32", + pointBorderColor: "#FFFFFF", + lineTension: 0.4, + fill: true, + }, + { + label: "Memory", + data: memPercentages, + backgroundColor: canvas["memGradient"], + borderColor: "transparent", + pointBackgroundColor: "#55FF8532", + pointBorderColor: "#FFFFFF", + lineTension: 0.4, + fill: true, + }, + { + label: "Disk", + data: diskPercentages, + backgroundColor: canvas["diskGradient"], + borderColor: "transparent", + pointBackgroundColor: "#97979732", + pointBorderColor: "#FFFFFF", + lineTension: 0.4, + fill: true, + }, + ], + } + } + }, [props.resources]) + + return ( + <Line + type="line" + height={40 + padding * 2} + data={dataF} + options={opts} + plugins={[multiply]} + ref={(ref) => { + window.Chart.defaults.global.defaultFontFamily = "'Fira Code', Inter" + }} + /> + ) +} diff --git a/site/components/Workspace/Workspace.tsx b/site/components/Workspace/Workspace.tsx index 0ce89b2182473..fdb1d96a214e7 100644 --- a/site/components/Workspace/Workspace.tsx +++ b/site/components/Workspace/Workspace.tsx @@ -3,14 +3,15 @@ import Paper from "@material-ui/core/Paper" import { makeStyles } from "@material-ui/core/styles" import Typography from "@material-ui/core/Typography" import OpenInNewIcon from "@material-ui/icons/OpenInNew" -import React, { useState } from "react" +import React, { useEffect, useState } from "react" import MoreVertIcon from "@material-ui/icons/MoreVert" import { QuestionHelp } from "../QuestionHelp" import { CircularProgress, IconButton, Link, Menu, MenuItem } from "@material-ui/core" - +import { ResourceMonitor, ResourceMonitorProps, ResourceUsageSnapshot } from "../ResourceMonitor" import { Timeline as TestTimeline } from "../Timeline" import * as API from "../../api" +import { getAllByTestId } from "@testing-library/react" export interface WorkspaceProps { workspace: API.Workspace @@ -94,17 +95,17 @@ export const ResourceRow: React.FC<ResourceRowProps> = ({ icon, href, name, stat <MoreVertIcon fontSize="inherit" /> </IconButton> - <Menu anchorEl={menuAnchorEl} open={!!menuAnchorEl} onClose={() => setMenuAnchorEl(undefined)}> + <Menu anchorEl={menuAnchorEl} open={!!menuAnchorEl} onClose={() => setMenuAnchorEl(null)}> <MenuItem onClick={() => { - setMenuAnchorEl(undefined) + setMenuAnchorEl(null) }} > SSH </MenuItem> <MenuItem onClick={() => { - setMenuAnchorEl(undefined) + setMenuAnchorEl(null) }} > Remote Desktop @@ -177,6 +178,36 @@ const TitleIconSize = 48 export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => { const styles = useStyles() + const [resources, setResources] = useState<ResourceUsageSnapshot[]>([ + { + cpuPercentage: 50, + memoryUsedBytes: 8 * 1024 * 1024, + diskUsedBytes: 24 * 1024 * 1024, + }, + ]) + + useEffect(() => { + const rand = (range: number) => { + return range * 2 * (Math.random() - 0.5) + } + const timeout = window.setTimeout(() => { + setResources((res: ResourceUsageSnapshot[]) => { + const latest = res[0] + const newEntry = { + cpuPercentage: latest.cpuPercentage + rand(5), + memoryUsedBytes: latest.memoryUsedBytes + rand(256 * 1024), + diskUsedBytes: latest.diskUsedBytes + rand(512 * 1024), + } + const ret: ResourceUsageSnapshot[] = [newEntry, ...res] + return ret + }) + }, 1000) + + return () => { + window.clearTimeout(timeout) + } + }) + return ( <div className={styles.root}> <Paper elevation={0} className={styles.section}> @@ -193,6 +224,13 @@ export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => { </Typography> </div> </div> + <div style={{ height: "200px", position: "relative" }}> + <ResourceMonitor + diskTotalBytes={256 * 1024 * 1024} + memoryTotalBytes={16 * 1024 * 1024} + resources={resources} + /> + </div> </Paper> <div className={styles.horizontal}> <div className={styles.sideBar}> @@ -210,7 +248,7 @@ export const Workspace: React.FC<WorkspaceProps> = ({ workspace }) => { <ResourceRow name={"React App"} icon={"/static/react-icon.svg"} href={"placeholder"} status={"active"} /> </div> </Paper> - + <Paper elevation={0} className={styles.section}> <Title> <Typography variant="h6">Resources</Typography> diff --git a/site/package.json b/site/package.json index 5e899ffea59f6..df1b7e04db741 100644 --- a/site/package.json +++ b/site/package.json @@ -20,6 +20,7 @@ "@material-ui/icons": "4.5.1", "@material-ui/lab": "4.0.0-alpha.60", "@testing-library/react": "12.1.2", + "@types/chart.js": "^2.9.35", "@types/express": "4.17.13", "@types/jest": "27.4.0", "@types/node": "14.18.10", @@ -28,6 +29,7 @@ "@types/superagent": "4.1.15", "@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/parser": "4.33.0", + "chart.js": "2.9.4", "eslint": "7.32.0", "eslint-config-prettier": "8.3.0", "eslint-import-resolver-alias": "1.1.2", @@ -48,6 +50,7 @@ "next-router-mock": "^0.6.5", "prettier": "2.5.1", "react": "17.0.2", + "react-chartjs-2": "2.11.2", "react-dom": "17.0.2", "sql-formatter": "^4.0.2", "swr": "1.2.0", diff --git a/site/yarn.lock b/site/yarn.lock index 13998609b37b8..7ee5ef8b104df 100644 --- a/site/yarn.lock +++ b/site/yarn.lock @@ -819,6 +819,13 @@ "@types/connect" "*" "@types/node" "*" +"@types/chart.js@^2.9.35": + version "2.9.35" + resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.35.tgz#10ddee097ab9320f8eabd8a31017fda3644d9218" + integrity sha512-MWx/zZlh4wHBbM4Tm4YsZyYGb1/LkTiFLFwX/FXb0EJCvXX2xWTRHwlJ2RAAEXWxLrOdaAWP8vFtJXny+4CpEw== + dependencies: + moment "^2.10.2" + "@types/connect@*": version "3.4.35" resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" @@ -1536,6 +1543,29 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chart.js@2.9.4: + version "2.9.4" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684" + integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A== + dependencies: + chartjs-color "^2.1.0" + moment "^2.10.2" + +chartjs-color-string@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71" + integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A== + dependencies: + color-name "^1.0.0" + +chartjs-color@^2.1.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0" + integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w== + dependencies: + chartjs-color-string "^0.6.0" + color-convert "^1.9.3" + ci-info@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" @@ -1570,7 +1600,7 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== -color-convert@^1.9.0: +color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -1589,7 +1619,7 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -3590,7 +3620,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3700,6 +3730,11 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +moment@^2.10.2: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4139,6 +4174,14 @@ raw-body@2.4.2: iconv-lite "0.4.24" unpipe "1.0.0" +react-chartjs-2@2.11.2: + version "2.11.2" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.11.2.tgz#156c0d2618600561efc23bef278bd48a335cadb6" + integrity sha512-hcPS9vmRJeAALPPf0uo02BiD8BDm0HNmneJYTZVR74UKprXOpql+Jy1rVuj93rKw0Jfx77mkcRfXPxTe5K83uw== + dependencies: + lodash "^4.17.19" + prop-types "^15.7.2" + react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www.w3.org/1999/xhtml'> <head> <title>pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy