Skip to content

feat: Add links to the resource card for workspace applications #2067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 96 additions & 86 deletions site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,145 +19,155 @@ import { WorkspaceBuildPage } from "./pages/WorkspaceBuildPage/WorkspaceBuildPag
import { WorkspacePage } from "./pages/WorkspacePage/WorkspacePage"
import { WorkspaceSchedulePage } from "./pages/WorkspaceSchedulePage/WorkspaceSchedulePage"

const WorkspaceAppErrorPage = lazy(() => import("./pages/WorkspaceAppErrorPage/WorkspaceAppErrorPage"))
const TerminalPage = lazy(() => import("./pages/TerminalPage/TerminalPage"))
const WorkspacesPage = lazy(() => import("./pages/WorkspacesPage/WorkspacesPage"))
const CreateWorkspacePage = lazy(() => import("./pages/CreateWorkspacePage/CreateWorkspacePage"))

export const AppRouter: FC = () => (
<Suspense fallback={<></>}>
<Routes>
<Route path="/">
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>

<Route path="login" element={<LoginPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route
path="cli-auth"
element={
<RequireAuth>
<CliAuthenticationPage />
</RequireAuth>
}
/>

<Route path="workspaces">
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
<AuthAndFrame>
<WorkspacesPage />
</AuthAndFrame>
}
/>

<Route path="login" element={<LoginPage />} />
<Route path="healthz" element={<HealthzPage />} />
<Route
path="cli-auth"
path="new"
element={
<RequireAuth>
<CliAuthenticationPage />
<CreateWorkspacePage />
</RequireAuth>
}
/>

<Route path="workspaces">
<Route path=":workspace">
<Route
index
element={
<AuthAndFrame>
<WorkspacesPage />
<WorkspacePage />
</AuthAndFrame>
}
/>

<Route
path="new"
path="schedule"
element={
<RequireAuth>
<CreateWorkspacePage />
<WorkspaceSchedulePage />
</RequireAuth>
}
/>

<Route path=":workspace">
<Route
index
element={
<AuthAndFrame>
<WorkspacePage />
</AuthAndFrame>
}
/>
<Route
path="schedule"
element={
<RequireAuth>
<WorkspaceSchedulePage />
</RequireAuth>
}
/>
</Route>
</Route>
</Route>

<Route path="templates">
<Route
index
element={
<AuthAndFrame>
<TemplatesPage />
</AuthAndFrame>
}
/>
<Route path="templates">
<Route
index
element={
<AuthAndFrame>
<TemplatesPage />
</AuthAndFrame>
}
/>

<Route
path=":template"
element={
<AuthAndFrame>
<TemplatePage />
</AuthAndFrame>
}
/>
</Route>
<Route
path=":template"
element={
<AuthAndFrame>
<TemplatePage />
</AuthAndFrame>
}
/>
</Route>

<Route path="users">
<Route
index
element={
<AuthAndFrame>
<UsersPage />
</AuthAndFrame>
}
/>
<Route path="users">
<Route
index
element={
<AuthAndFrame>
<UsersPage />
</AuthAndFrame>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateUserPage />
</RequireAuth>
}
/>
</Route>

<Route path="settings" element={<SettingsLayout />}>
<Route path="account" element={<AccountPage />} />
<Route path="security" element={<SecurityPage />} />
<Route path="ssh-keys" element={<SSHKeysPage />} />
</Route>

<Route
path="builds/:buildId"
element={
<AuthAndFrame>
<WorkspaceBuildPage />
</AuthAndFrame>
}
/>

<Route path="/@:username">
<Route path=":workspace">
<Route
path="create"
path="terminal"
element={
<RequireAuth>
<CreateUserPage />
<TerminalPage />
</RequireAuth>
}
/>
</Route>

<Route path="settings" element={<SettingsLayout />}>
<Route path="account" element={<AccountPage />} />
<Route path="security" element={<SecurityPage />} />
<Route path="ssh-keys" element={<SSHKeysPage />} />
</Route>

<Route path=":username">
<Route path=":workspace">
<Route path="apps">
<Route
path="terminal"
path=":app/*"
element={
<RequireAuth>
<TerminalPage />
</RequireAuth>
<AuthAndFrame>
<WorkspaceAppErrorPage />
</AuthAndFrame>
}
/>
</Route>
</Route>
</Route>

<Route
path="builds/:buildId"
element={
<AuthAndFrame>
<WorkspaceBuildPage />
</AuthAndFrame>
}
/>

{/* Using path="*"" means "match anything", so this route
{/* Using path="*"" means "match anything", so this route
acts like a catch-all for URLs that we don't have explicit
routes for. */}
<Route path="*" element={<NotFoundPage />} />
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Suspense>
)
25 changes: 25 additions & 0 deletions site/src/components/AppLink/AppLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Story } from "@storybook/react"
import { MockWorkspace } from "../../testHelpers/renderHelpers"
import { AppLink, AppLinkProps } from "./AppLink"

export default {
title: "components/AppLink",
component: AppLink,
}

const Template: Story<AppLinkProps> = (args) => <AppLink {...args} />

export const WithIcon = Template.bind({})
WithIcon.args = {
userName: "developer",
workspaceName: MockWorkspace.name,
appName: "code-server",
appIcon: "/code.svg",
}

export const WithoutIcon = Template.bind({})
WithoutIcon.args = {
userName: "developer",
workspaceName: MockWorkspace.name,
appName: "code-server",
}
48 changes: 48 additions & 0 deletions site/src/components/AppLink/AppLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import React, { FC } from "react"
import * as TypesGen from "../../api/typesGenerated"
import { combineClasses } from "../../util/combineClasses"

export interface AppLinkProps {
userName: TypesGen.User["username"]
workspaceName: TypesGen.Workspace["name"]
appName: TypesGen.WorkspaceApp["name"]
appIcon: TypesGen.WorkspaceApp["icon"]
}

export const AppLink: FC<AppLinkProps> = ({ userName, workspaceName, appName, appIcon }) => {
const styles = useStyles()
const href = `/@${userName}/${workspaceName}/apps/${appName}`

return (
<Link href={href} target="_blank" className={styles.link}>
<img
className={combineClasses([styles.icon, appIcon === "" ? "empty" : ""])}
alt={`${appName} Icon`}
src={appIcon || ""}
/>
{appName}
</Link>
)
}

const useStyles = makeStyles((theme) => ({
link: {
color: theme.palette.text.secondary,
display: "flex",
alignItems: "center",
},

icon: {
width: 16,
height: 16,
marginRight: theme.spacing(1.5),

// If no icon is provided we still want the padding on the left
// to occur.
"&.empty": {
opacity: 0,
},
},
}))
30 changes: 22 additions & 8 deletions site/src/components/Resources/Resources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import useTheme from "@material-ui/styles/useTheme"
import { FC } from "react"
import { Workspace, WorkspaceResource } from "../../api/typesGenerated"
import { getDisplayAgentStatus } from "../../util/workspace"
import { AppLink } from "../AppLink/AppLink"
import { Stack } from "../Stack/Stack"
import { TableHeaderRow } from "../TableHeaders/TableHeaders"
import { TerminalLink } from "../TerminalLink/TerminalLink"
import { WorkspaceSection } from "../WorkspaceSection/WorkspaceSection"
Expand Down Expand Up @@ -83,14 +85,26 @@ export const Resources: FC<ResourcesProps> = ({ resources, getResourcesError, wo
<span className={styles.operatingSystem}>{agent.operating_system}</span>
</TableCell>
<TableCell>
{agent.status === "connected" && (
<TerminalLink
className={styles.accessLink}
workspaceName={workspace.name}
agentName={agent.name}
userName={workspace.owner_name}
/>
)}
<Stack>
{agent.status === "connected" && (
<TerminalLink
className={styles.accessLink}
workspaceName={workspace.name}
agentName={agent.name}
userName={workspace.owner_name}
/>
)}
{agent.status === "connected" &&
agent.apps.map((app) => (
<AppLink
key={app.name}
appIcon={app.icon}
appName={app.name}
userName={workspace.owner_name}
workspaceName={workspace.name}
/>
))}
</Stack>
</TableCell>
<TableCell>
<span style={{ color: getDisplayAgentStatus(theme, agent).color }}>
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/TerminalLink/TerminalLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface TerminalLinkProps {
*/
export const TerminalLink: FC<TerminalLinkProps> = ({ agentName, userName = "me", workspaceName, className }) => {
const styles = useStyles()
const href = `/${userName}/${workspaceName}${agentName ? `.${agentName}` : ""}/terminal`
const href = `/@${userName}/${workspaceName}${agentName ? `.${agentName}` : ""}/terminal`

return (
<Link
Expand Down
18 changes: 18 additions & 0 deletions site/src/pages/WorkspaceAppErrorPage/WorkspaceAppErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FC, useMemo } from "react"
import { useParams } from "react-router-dom"
import { WorkspaceAppErrorPageView } from "./WorkspaceAppErrorPageView"

const WorkspaceAppErrorView: FC = () => {
const { app } = useParams()
const message = useMemo(() => {
const tag = document.getElementById("api-response")
if (!tag) {
throw new Error("dev error: api-response meta tag not found")
}
return tag.getAttribute("data-message") as string
}, [])

return <WorkspaceAppErrorPageView appName={app as string} message={message} />
}

export default WorkspaceAppErrorView
Loading