mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: Add page titles (#2070)
This commit is contained in:
@ -21,7 +21,6 @@
|
||||
<link rel="mask-icon" href="/static/favicon.svg" color="#000000" crossorigin="use-credentials" />
|
||||
<link rel="alternate icon" type="image/png" href="/favicon.png" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Coder</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -44,6 +44,7 @@
|
||||
"history": "5.3.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-markdown": "8.0.3",
|
||||
"react-router-dom": "6.3.0",
|
||||
"sourcemapped-stacktrace": "1.1.11",
|
||||
@ -73,6 +74,7 @@
|
||||
"@types/node": "14.18.16",
|
||||
"@types/react": "17.0.44",
|
||||
"@types/react-dom": "17.0.16",
|
||||
"@types/react-helmet": "^6.1.5",
|
||||
"@types/superagent": "4.1.15",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.23.0",
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Box from "@material-ui/core/Box"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { AuthAndFrame } from "../AuthAndFrame/AuthAndFrame"
|
||||
import { Margins } from "../Margins/Margins"
|
||||
import { TabPanel } from "../TabPanel/TabPanel"
|
||||
@ -22,6 +24,9 @@ export const SettingsLayout: FC = () => {
|
||||
return (
|
||||
<AuthAndFrame>
|
||||
<Box display="flex" flexDirection="column">
|
||||
<Helmet>
|
||||
<title>{pageTitle("Settings")}</title>
|
||||
</Helmet>
|
||||
<Margins>
|
||||
<TabPanel title={Language.settingsLabel} menuItems={menuItems}>
|
||||
<Outlet />
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { useActor } from "@xstate/react"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { getApiKey } from "../../api/api"
|
||||
import { CliAuthToken } from "../../components/CliAuthToken/CliAuthToken"
|
||||
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { XServiceContext } from "../../xServices/StateContext"
|
||||
|
||||
export const CliAuthenticationPage: React.FC = () => {
|
||||
@ -29,6 +31,9 @@ export const CliAuthenticationPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<Helmet>
|
||||
<title>{pageTitle("CLI Auth")}</title>
|
||||
</Helmet>
|
||||
<CliAuthToken sessionToken={apiKey} />
|
||||
</div>
|
||||
)
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useNavigate, useSearchParams } from "react-router-dom"
|
||||
import { Template } from "../../api/typesGenerated"
|
||||
import { useOrganizationId } from "../../hooks/useOrganizationId"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService"
|
||||
import { CreateWorkspacePageView } from "./CreateWorkspacePageView"
|
||||
|
||||
@ -21,6 +23,10 @@ const CreateWorkspacePage: FC = () => {
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Create Workspace")}</title>
|
||||
</Helmet>
|
||||
<CreateWorkspacePageView
|
||||
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
|
||||
loadingTemplateSchema={createWorkspaceState.matches("gettingTemplateSchema")}
|
||||
@ -44,6 +50,7 @@ const CreateWorkspacePage: FC = () => {
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { useActor } from "@xstate/react"
|
||||
import React, { useContext } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { Navigate, useLocation } from "react-router-dom"
|
||||
import { isApiError } from "../../api/errors"
|
||||
import { Footer } from "../../components/Footer/Footer"
|
||||
import { SignInForm } from "../../components/SignInForm/SignInForm"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { retrieveRedirect } from "../../util/redirect"
|
||||
import { XServiceContext } from "../../xServices/StateContext"
|
||||
|
||||
@ -50,6 +52,9 @@ export const LoginPage: React.FC = () => {
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Login")}</title>
|
||||
</Helmet>
|
||||
<div className={styles.layout}>
|
||||
<div className={styles.container}>
|
||||
<SignInForm
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { Loader } from "../../components/Loader/Loader"
|
||||
import { useOrganizationId } from "../../hooks/useOrganizationId"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { templateMachine } from "../../xServices/template/templateXService"
|
||||
import { TemplatePageView } from "./TemplatePageView"
|
||||
|
||||
@ -33,10 +35,15 @@ export const TemplatePage: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle(`${template.name} · Template`)}</title>
|
||||
</Helmet>
|
||||
<TemplatePageView
|
||||
template={template}
|
||||
activeTemplateVersion={activeTemplateVersion}
|
||||
templateResources={templateResources}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { useActor, useMachine } from "@xstate/react"
|
||||
import React, { useContext } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { XServiceContext } from "../../xServices/StateContext"
|
||||
import { templatesMachine } from "../../xServices/templates/templatesXService"
|
||||
import { TemplatesPageView } from "./TemplatesPageView"
|
||||
@ -10,11 +12,16 @@ const TemplatesPage: React.FC = () => {
|
||||
const [templatesState] = useMachine(templatesMachine)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Templates")}</title>
|
||||
</Helmet>
|
||||
<TemplatesPageView
|
||||
templates={templatesState.context.templates}
|
||||
canCreateTemplate={authState.context.permissions?.createTemplates}
|
||||
loading={templatesState.hasTag("loading")}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { FC, useEffect, useRef, useState } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
import * as XTerm from "xterm"
|
||||
@ -8,6 +9,7 @@ import { FitAddon } from "xterm-addon-fit"
|
||||
import { WebLinksAddon } from "xterm-addon-web-links"
|
||||
import "xterm/css/xterm.css"
|
||||
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { terminalMachine } from "../../xServices/terminal/terminalXService"
|
||||
|
||||
export const Language = {
|
||||
@ -179,6 +181,15 @@ const TerminalPage: FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{terminalState.context.workspace
|
||||
? pageTitle(
|
||||
`Terminal · ${terminalState.context.workspace.owner_name}/${terminalState.context.workspace.name}`,
|
||||
)
|
||||
: ""}
|
||||
</title>
|
||||
</Helmet>
|
||||
{/* This overlay makes it more obvious that the terminal is disconnected. */}
|
||||
{/* It's nice for situations where Coder restarts, and they are temporarily disconnected. */}
|
||||
<div className={`${styles.overlay} ${isDisconnected ? "" : "connected"}`}>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useActor, useSelector } from "@xstate/react"
|
||||
import React, { useContext } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useNavigate } from "react-router"
|
||||
import * as TypesGen from "../../../api/typesGenerated"
|
||||
import { CreateUserForm } from "../../../components/CreateUserForm/CreateUserForm"
|
||||
import { Margins } from "../../../components/Margins/Margins"
|
||||
import { pageTitle } from "../../../util/page"
|
||||
import { selectOrgId } from "../../../xServices/auth/authSelectors"
|
||||
import { XServiceContext } from "../../../xServices/StateContext"
|
||||
|
||||
@ -23,6 +25,9 @@ export const CreateUserPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Margins>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Create User")}</title>
|
||||
</Helmet>
|
||||
<CreateUserForm
|
||||
formErrors={createUserFormErrors}
|
||||
onSubmit={(user: TypesGen.CreateUserRequest) => usersSend({ type: "CREATE", user })}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useActor, useSelector } from "@xstate/react"
|
||||
import React, { useContext, useEffect } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useNavigate } from "react-router"
|
||||
import { ConfirmDialog } from "../../components/ConfirmDialog/ConfirmDialog"
|
||||
import { ResetPasswordDialog } from "../../components/ResetPasswordDialog/ResetPasswordDialog"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { selectPermissions } from "../../xServices/auth/authSelectors"
|
||||
import { XServiceContext } from "../../xServices/StateContext"
|
||||
import { UsersPageView } from "./UsersPageView"
|
||||
@ -48,6 +50,9 @@ export const UsersPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Users")}</title>
|
||||
</Helmet>
|
||||
<UsersPageView
|
||||
roles={roles}
|
||||
users={users}
|
||||
|
@ -2,6 +2,7 @@ import { makeStyles } from "@material-ui/core/styles"
|
||||
import Typography from "@material-ui/core/Typography"
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { ProvisionerJobLog } from "../../api/typesGenerated"
|
||||
import { Loader } from "../../components/Loader/Loader"
|
||||
@ -9,6 +10,7 @@ import { Margins } from "../../components/Margins/Margins"
|
||||
import { Stack } from "../../components/Stack/Stack"
|
||||
import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs"
|
||||
import { WorkspaceBuildStats } from "../../components/WorkspaceBuildStats/WorkspaceBuildStats"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { workspaceBuildMachine } from "../../xServices/workspaceBuild/workspaceBuildXService"
|
||||
|
||||
const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => {
|
||||
@ -34,6 +36,9 @@ export const WorkspaceBuildPage: FC = () => {
|
||||
|
||||
return (
|
||||
<Margins>
|
||||
<Helmet>
|
||||
<title>{build ? pageTitle(`Build #${build.build_number} · ${build.workspace_name}`) : ""}</title>
|
||||
</Helmet>
|
||||
<Stack>
|
||||
<Typography variant="h4" className={styles.title}>
|
||||
Logs
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMachine } from "@xstate/react"
|
||||
import React, { useEffect } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import { DeleteWorkspaceDialog } from "../../components/DeleteWorkspaceDialog/DeleteWorkspaceDialog"
|
||||
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
|
||||
@ -8,6 +9,7 @@ import { Margins } from "../../components/Margins/Margins"
|
||||
import { Stack } from "../../components/Stack/Stack"
|
||||
import { Workspace } from "../../components/Workspace/Workspace"
|
||||
import { firstOrItem } from "../../util/array"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { workspaceMachine } from "../../xServices/workspace/workspaceXService"
|
||||
import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedule/workspaceScheduleBannerXService"
|
||||
|
||||
@ -36,6 +38,9 @@ export const WorkspacePage: React.FC = () => {
|
||||
} else {
|
||||
return (
|
||||
<Margins>
|
||||
<Helmet>
|
||||
<title>{pageTitle(`${workspace.owner_name}/${workspace.name}`)}</title>
|
||||
</Helmet>
|
||||
<Stack spacing={4}>
|
||||
<>
|
||||
<Workspace
|
||||
|
@ -11,11 +11,13 @@ import SearchIcon from "@material-ui/icons/Search"
|
||||
import { useMachine } from "@xstate/react"
|
||||
import { FormikErrors, useFormik } from "formik"
|
||||
import { FC, useState } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
import { Link as RouterLink } from "react-router-dom"
|
||||
import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows"
|
||||
import { Margins } from "../../components/Margins/Margins"
|
||||
import { Stack } from "../../components/Stack/Stack"
|
||||
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
|
||||
import { pageTitle } from "../../util/page"
|
||||
import { workspacesMachine } from "../../xServices/workspaces/workspacesXService"
|
||||
import { WorkspacesPageView } from "./WorkspacesPageView"
|
||||
|
||||
@ -74,6 +76,9 @@ const WorkspacesPage: FC = () => {
|
||||
|
||||
return (
|
||||
<Margins>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Workspaces")}</title>
|
||||
</Helmet>
|
||||
<Stack direction="row" className={styles.workspacesHeaderContainer}>
|
||||
<Stack direction="column" className={styles.filterColumn}>
|
||||
<Stack direction="row" spacing={0} className={styles.filterContainer}>
|
||||
|
3
site/src/util/page.ts
Normal file
3
site/src/util/page.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const pageTitle = (prefix: string): string => {
|
||||
return `${prefix} – Coder`
|
||||
}
|
@ -63,7 +63,7 @@ const config: Configuration = {
|
||||
port: process.env.PORT || 8080,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:3000",
|
||||
target: "https://dev.coder.com",
|
||||
ws: true,
|
||||
secure: false,
|
||||
},
|
||||
|
@ -3041,6 +3041,13 @@
|
||||
dependencies:
|
||||
"@types/react" "^17"
|
||||
|
||||
"@types/react-helmet@^6.1.5":
|
||||
version "6.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083"
|
||||
integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-syntax-highlighter@11.0.5":
|
||||
version "11.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087"
|
||||
@ -11389,7 +11396,7 @@ react-fast-compare@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
|
||||
react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||
@ -11405,6 +11412,16 @@ react-helmet-async@^1.0.7:
|
||||
react-fast-compare "^3.2.0"
|
||||
shallowequal "^1.1.0"
|
||||
|
||||
react-helmet@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
|
||||
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.7.2"
|
||||
react-fast-compare "^3.1.1"
|
||||
react-side-effect "^2.1.0"
|
||||
|
||||
react-hot-loader@4.13.0:
|
||||
version "4.13.0"
|
||||
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202"
|
||||
@ -11506,6 +11523,11 @@ react-router@6.3.0, react-router@^6.0.0:
|
||||
dependencies:
|
||||
history "^5.2.0"
|
||||
|
||||
react-side-effect@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3"
|
||||
integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==
|
||||
|
||||
react-sizeme@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.2.tgz#4a2f167905ba8f8b8d932a9e35164e459f9020e4"
|
||||
|
Reference in New Issue
Block a user