diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index ab61681679033..8fa55bd9efee0 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -259,10 +259,6 @@ export const AppRouter: FC = () => { } /> - } - /> @@ -346,13 +342,17 @@ export const AppRouter: FC = () => { - {/* Terminal and CLI auth pages don't have the dashboard layout */} + {/* Pages that don't have the dashboard layout */} } /> } /> } /> + } + /> {/* Using path="*"" means "match anything", so this route diff --git a/site/src/components/WorkspaceBuildLogs/Logs.tsx b/site/src/components/WorkspaceBuildLogs/Logs.tsx index e95999806b0b6..585adf0f9788b 100644 --- a/site/src/components/WorkspaceBuildLogs/Logs.tsx +++ b/site/src/components/WorkspaceBuildLogs/Logs.tsx @@ -24,10 +24,14 @@ export const Logs: FC> = ({ className = "", }) => { return ( - + {lines.map((line, idx) => ( - + {!hideTimestamps && ( <> diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx index a0fdba3f9bb3f..c41ad894175a5 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx @@ -77,7 +77,10 @@ export const WorkspaceBuildLogs: FC = ({ return ( - + {stage} {shouldDisplayDuration && ( diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index 2a6dd37a6661c..6e5878eba5490 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -233,7 +233,6 @@ export const TemplateSettingsForm: FC = ({ diff --git a/site/src/pages/TemplateVersionEditorPage/FileTreeView.tsx b/site/src/pages/TemplateVersionEditorPage/FileTreeView.tsx index eba1649f1dc72..1922eb9cdb2c2 100644 --- a/site/src/pages/TemplateVersionEditorPage/FileTreeView.tsx +++ b/site/src/pages/TemplateVersionEditorPage/FileTreeView.tsx @@ -62,6 +62,7 @@ export const FileTreeView: FC<{ css={(theme) => css` overflow: hidden; user-select: none; + height: 32px; &:focus:not(.active) > .MuiTreeItem-content { background: ${theme.palette.action.hover}; @@ -92,7 +93,7 @@ export const FileTreeView: FC<{ &.active { & > .MuiTreeItem-content { color: ${theme.palette.text.primary}; - background: ${colors.gray[13]}; + background: ${colors.gray[14]}; pointer-events: none; } } diff --git a/site/src/pages/TemplateVersionEditorPage/MonacoEditor.tsx b/site/src/pages/TemplateVersionEditorPage/MonacoEditor.tsx index cff5950f408ba..7b3ed9ab29d2a 100644 --- a/site/src/pages/TemplateVersionEditorPage/MonacoEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/MonacoEditor.tsx @@ -1,9 +1,8 @@ import { useTheme } from "@emotion/react"; import Editor, { loader } from "@monaco-editor/react"; import * as monaco from "monaco-editor"; -import { FC, useLayoutEffect, useMemo, useState } from "react"; +import { FC, useMemo } from "react"; import { MONOSPACE_FONT_FAMILY } from "theme/constants"; -import type { editor } from "monaco-editor"; loader.config({ monaco }); @@ -13,22 +12,6 @@ export const MonacoEditor: FC<{ onChange?: (value: string) => void; }> = ({ onChange, value, path }) => { const theme = useTheme(); - const [editor, setEditor] = useState(); - useLayoutEffect(() => { - if (!editor) { - return; - } - const resizeListener = () => { - editor.layout({ - height: 0, - width: 0, - }); - }; - window.addEventListener("resize", resizeListener); - return () => { - window.removeEventListener("resize", resizeListener); - }; - }, [editor]); const language = useMemo(() => { if (path?.endsWith(".tf")) { @@ -56,7 +39,7 @@ export const MonacoEditor: FC<{ options={{ automaticLayout: true, fontFamily: MONOSPACE_FONT_FAMILY, - fontSize: 16, + fontSize: 14, wordWrap: "on", padding: { top: 16, @@ -81,8 +64,6 @@ export const MonacoEditor: FC<{ }, ); - setEditor(editor); - document.fonts.ready .then(() => { // Ensures that all text is measured properly. @@ -124,7 +105,7 @@ export const MonacoEditor: FC<{ ], colors: { "editor.foreground": theme.palette.text.primary, - "editor.background": theme.palette.background.default, + "editor.background": theme.palette.background.paper, }, }); editor.updateOptions({ diff --git a/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx b/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx index 66013982f5382..d572bcdef2be9 100644 --- a/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx +++ b/site/src/pages/TemplateVersionEditorPage/PublishTemplateVersionDialog.tsx @@ -68,44 +68,45 @@ export const PublishTemplateVersionDialog: FC< confirmText="Publish" title="Publish new version" description={ - - You are about to publish a new version of this template. - - + + + You are about to publish a new version of this template. + + - + - { - await form.setFieldValue( - "isActiveVersion", - e.target.checked, - ); - }} - name="isActiveVersion" - /> - } - /> - - + { + await form.setFieldValue( + "isActiveVersion", + e.target.checked, + ); + }} + name="isActiveVersion" + /> + } + /> + + + } /> ); diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index b07578fe63bfb..2703ae7eb0b26 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -1,10 +1,7 @@ -import Button from "@mui/material/Button"; +import Button, { ButtonProps } from "@mui/material/Button"; import IconButton from "@mui/material/IconButton"; -import Link from "@mui/material/Link"; import Tooltip from "@mui/material/Tooltip"; import CreateIcon from "@mui/icons-material/AddOutlined"; -import BuildIcon from "@mui/icons-material/BuildOutlined"; -import PreviewIcon from "@mui/icons-material/VisibilityOutlined"; import { ProvisionerJobLog, Template, @@ -16,11 +13,11 @@ import { import { Link as RouterLink } from "react-router-dom"; import { Alert, AlertDetail } from "components/Alert/Alert"; import { Avatar } from "components/Avatar/Avatar"; -import { AvatarData } from "components/AvatarData/AvatarData"; import { TemplateResourcesTable } from "components/TemplateResourcesTable/TemplateResourcesTable"; import { WorkspaceBuildLogs } from "components/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { PublishVersionData } from "pages/TemplateVersionEditorPage/types"; import { type FC, useCallback, useEffect, useRef, useState } from "react"; +import PlayArrowOutlined from "@mui/icons-material/PlayArrowOutlined"; import { createFile, existsFile, @@ -41,13 +38,13 @@ import { FileTreeView } from "./FileTreeView"; import { MissingTemplateVariablesDialog } from "./MissingTemplateVariablesDialog"; import { MonacoEditor } from "./MonacoEditor"; import { PublishTemplateVersionDialog } from "./PublishTemplateVersionDialog"; -import { - getStatus, - TemplateVersionStatusBadge, -} from "./TemplateVersionStatusBadge"; +import { TemplateVersionStatusBadge } from "./TemplateVersionStatusBadge"; import AlertTitle from "@mui/material/AlertTitle"; -import { DashboardFullPage } from "components/Dashboard/DashboardLayout"; import { type Interpolation, type Theme, useTheme } from "@emotion/react"; +import ArrowBackOutlined from "@mui/icons-material/ArrowBackOutlined"; +import CloseOutlined from "@mui/icons-material/CloseOutlined"; +import { MONOSPACE_FONT_FAMILY } from "theme/constants"; +import { Loader } from "components/Loader/Loader"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab export interface TemplateVersionEditorProps { @@ -74,8 +71,6 @@ export interface TemplateVersionEditorProps { defaultTab?: Tab; } -const topbarHeight = 80; - const findInitialFile = (fileTree: FileTree): string | undefined => { let initialFile: string | undefined; @@ -162,90 +157,196 @@ export const TemplateVersionEditor: FC = ({ ["running", "pending"].includes(previousVersion.current.job.status) && templateVersion.job.status === "succeeded" ) { - setSelectedTab("resources"); setDirty(false); } previousVersion.current = templateVersion; }, [templateVersion]); - const hasIcon = template.icon && template.icon !== ""; - const showBuildLogs = Boolean(buildLogs); const editorValue = getFileContent(activePath ?? "", fileTree) as string; + // Auto scroll + const buildLogsRef = useRef(null); useEffect(() => { - window.dispatchEvent(new Event("resize")); - }, [showBuildLogs]); + if (buildLogsRef.current) { + buildLogsRef.current.scrollTop = buildLogsRef.current.scrollHeight; + } + }, [buildLogs]); return ( <> - - - - - - ) - } - /> - + + + + + + + + - {publishedVersion && ( - - Create a workspace - - } + + + - Successfully published {publishedVersion.name}! - - )} + {template.display_name || template.name} + + / + + {templateVersion.name} + + - + {buildLogs && ( )} - + } title="Build template (Ctrl + Enter)" disabled={disablePreview} onClick={() => { triggerPreview(); }} > - Build template - + Build + - - Publish version - + Publish + - - - - Template files - + + {publishedVersion && ( + + + Create a workspace + + } + > + Successfully published {publishedVersion.name}! + + + )} + + + + + Files + + + = ({ event.currentTarget.blur(); }} > - + @@ -326,14 +427,14 @@ export const TemplateVersionEditor: FC = ({ - + {activePath ? ( = ({ )} - - - { - setSelectedTab("logs"); + + + - {templateVersion.job.status !== "succeeded" ? ( - getStatus(templateVersion).icon - ) : ( - - )} - Build Log - - - {!disableUpdate && ( { + setSelectedTab("logs"); + }} + > + Output + + + { setSelectedTab("resources"); }} > - - Workspace Preview + Resources + + + {selectedTab && ( + { + setSelectedTab(undefined); + }} + css={{ + marginLeft: "auto", + width: 36, + height: 36, + borderRadius: 0, + }} + > + + )} {templateVersion.job.error && ( @@ -412,9 +552,39 @@ export const TemplateVersionEditor: FC = ({ )} + {buildLogs && buildLogs.length === 0 && ( + + )} + {buildLogs && buildLogs.length > 0 && ( @@ -422,13 +592,25 @@ export const TemplateVersionEditor: FC = ({ {resources && ( = ({ - + = ({ ); }; -const styles = { - topbar: (theme) => ({ - padding: 16, - borderBottom: `1px solid ${theme.palette.divider}`, - display: "flex", - alignItems: "center", - justifyContent: "space-between", - height: topbarHeight, - }), - topbarSides: { - display: "flex", - alignItems: "center", - gap: 16, - }, - sidebarAndEditor: { - display: "flex", - flex: 1, - flexBasis: 0, - overflow: "hidden", - }, - sidebar: (theme) => ({ - minWidth: 256, - borderRight: `1px solid ${theme.palette.divider}`, - }), - sidebarTitle: (theme) => ({ - fontSize: 10, - textTransform: "uppercase", - padding: "8px 16px", - color: theme.palette.text.primary, - fontWeight: 500, - letterSpacing: "0.5px", - display: "flex", - alignItems: "center", - }), - sidebarActions: (theme) => ({ - marginLeft: "auto", - "& svg": { - fill: theme.palette.text.primary, - }, - }), - editor: { - flex: 1, - }, - panelWrapper: (theme) => ({ - flex: 1, - borderLeft: `1px solid ${theme.palette.divider}`, - overflow: "hidden", - display: "flex", - flexDirection: "column", - }), - panel: { - overflowY: "auto", - height: "100%", - - // Hack to access customize resource-card from here - "& .resource-card": { - border: 0, - }, - }, - tabs: (theme) => ({ - borderBottom: `1px solid ${theme.palette.divider}`, - display: "flex", - boxShadow: "#000000 0 6px 6px -6px inset", +const TopbarButton = (props: ButtonProps) => { + return ( + + ); +}; - "& .MuiTab-root": { - padding: 0, - fontSize: 14, - textTransform: "none", - letterSpacing: "unset", - }, - }), +const styles = { tab: (theme) => ({ - cursor: "pointer", + "&:not(:disabled)": { + cursor: "pointer", + }, padding: 12, fontSize: 10, textTransform: "uppercase", letterSpacing: "0.5px", - fontWeight: 600, + fontWeight: 500, background: "transparent", fontFamily: "inherit", border: 0, @@ -564,15 +692,19 @@ const styles = { display: "block", width: "100%", height: 1, - backgroundColor: theme.palette.text.primary, + backgroundColor: theme.palette.primary.main, bottom: -1, position: "absolute", }, }, - "&:hover": { + "&:not(:disabled):hover": { color: theme.palette.text.primary, }, + + "&:disabled": { + color: theme.palette.text.disabled, + }, }), tabBar: (theme) => ({ padding: "8px 16px", diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx index 5c8e5461e64eb..67ec4c92b1e21 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.test.tsx @@ -57,7 +57,7 @@ test("Use custom name, message and set it as active when publishing", async () = return jest.fn() as never; }); const buildButton = within(topbar).getByRole("button", { - name: "Build template", + name: "Build", }); await user.click(buildButton); @@ -70,7 +70,7 @@ test("Use custom name, message and set it as active when publishing", async () = .mockResolvedValue({ message: "" }); await within(topbar).findByText("Success"); const publishButton = within(topbar).getByRole("button", { - name: "Publish version", + name: "Publish", }); await user.click(publishButton); const publishDialog = await screen.findByTestId("dialog"); @@ -120,7 +120,7 @@ test("Do not mark as active if promote is not checked", async () => { return jest.fn() as never; }); const buildButton = within(topbar).getByRole("button", { - name: "Build template", + name: "Build", }); await user.click(buildButton); @@ -133,7 +133,7 @@ test("Do not mark as active if promote is not checked", async () => { .mockResolvedValue({ message: "" }); await within(topbar).findByText("Success"); const publishButton = within(topbar).getByRole("button", { - name: "Publish version", + name: "Publish", }); await user.click(publishButton); const publishDialog = await screen.findByTestId("dialog"); @@ -185,7 +185,7 @@ test("Patch request is not send when there are no changes", async () => { return jest.fn() as never; }); const buildButton = within(topbar).getByRole("button", { - name: "Build template", + name: "Build", }); await user.click(buildButton); @@ -195,11 +195,11 @@ test("Patch request is not send when there are no changes", async () => { .mockResolvedValue(MockTemplateVersionWithEmptyMessage); await within(topbar).findByText("Success"); const publishButton = within(topbar).getByRole("button", { - name: "Publish version", + name: "Publish", }); await user.click(publishButton); const publishDialog = await screen.findByTestId("dialog"); - // It is using the name from the template version + // It is using the name from the template const nameField = within(publishDialog).getByLabelText("Version name"); expect(nameField).toHaveValue(MockTemplateVersionWithEmptyMessage.name); // Publish diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx index 5680ade1218d1..f7ca854ed5fa3 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditorPage.tsx @@ -30,6 +30,7 @@ import { TemplateVersion, } from "api/typesGenerated"; import { displayError } from "components/GlobalSnackbar/utils"; +import { FullScreenLoader } from "components/Loader/FullScreenLoader"; type Params = { version: string; @@ -107,7 +108,7 @@ export const TemplateVersionEditorPage: FC = () => { Codestin Search App - {templateQuery.data && templateVersionQuery.data && fileTree && ( + {templateQuery.data && templateVersionQuery.data && fileTree ? ( { data, version: templateVersionQuery.data, }); + const publishedVersion = { + ...templateVersionQuery.data, + ...data, + }; + setCurrentVersionName(publishedVersion.name); setIsPublishingDialogOpen(false); - setLastSuccessfulPublishedVersion(templateVersionQuery.data); + setLastSuccessfulPublishedVersion(publishedVersion); + queryClient.setQueryData( + templateVersionOptions.queryKey, + publishedVersion, + ); + navigate( + `/templates/${templateName}/versions/${publishedVersion.name}/edit`, + { replace: true }, + ); }} isAskingPublishParameters={isPublishingDialogOpen} isPublishing={publishVersionMutation.isLoading} @@ -197,6 +211,8 @@ export const TemplateVersionEditorPage: FC = () => { setIsMissingVariablesDialogOpen(false); }} /> + ) : ( + )} > );
You are about to publish a new version of this template.