mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: create a workspace from any template version (#9861)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
|
||||
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout";
|
||||
import { UsersLayout } from "components/UsersLayout/UsersLayout";
|
||||
import AuditPage from "pages/AuditPage/AuditPage";
|
||||
import LoginPage from "pages/LoginPage/LoginPage";
|
||||
import { SetupPage } from "pages/SetupPage/SetupPage";
|
||||
import { TemplateLayout } from "pages/TemplatePage/TemplateLayout";
|
||||
import TemplatesPage from "pages/TemplatesPage/TemplatesPage";
|
||||
import UsersPage from "pages/UsersPage/UsersPage";
|
||||
import WorkspacesPage from "pages/WorkspacesPage/WorkspacesPage";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { MemoizedMarkdown } from "components/Markdown/Markdown";
|
||||
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import frontMatter from "front-matter";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { pageTitle } from "utils/page";
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "testHelpers/renderHelpers";
|
||||
import TemplateEmbedPage from "./TemplateEmbedPage";
|
||||
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout";
|
||||
import { TemplateLayout } from "pages/TemplatePage/TemplateLayout";
|
||||
import {
|
||||
MockTemplate,
|
||||
MockTemplateVersionParameter1 as parameter1,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getTemplateVersionRichParameters } from "api/api";
|
||||
import { Template, TemplateVersionParameter } from "api/typesGenerated";
|
||||
import { FormSection, VerticalForm } from "components/Form/Form";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import {
|
||||
ImmutableTemplateParametersSection,
|
||||
MutableTemplateParametersSection,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getPreviousTemplateVersionByName } from "api/api";
|
||||
import { TemplateVersion } from "api/typesGenerated";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { TemplateFiles } from "components/TemplateFiles/TemplateFiles";
|
||||
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { useTab } from "hooks/useTab";
|
||||
import { FC, useEffect } from "react";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { BoxProps } from "@mui/system";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getInsightsTemplate, getInsightsUserLatency } from "api/api";
|
||||
import { DAUChart, DAUTitle } from "components/DAUChart/DAUChart";
|
||||
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import {
|
||||
HelpTooltip,
|
||||
HelpTooltipTitle,
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { TemplatePageHeader } from "./TemplatePageHeader";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
const meta: Meta<typeof TemplatePageHeader> = {
|
||||
title: "components/TemplatePageHeader",
|
||||
title: "pages/TemplatePage/TemplatePageHeader",
|
||||
component: TemplatePageHeader,
|
||||
args: {
|
||||
template: MockTemplate,
|
||||
+9
-9
@@ -69,14 +69,6 @@ const TemplateMenu: FC<{
|
||||
<SettingsOutlined />
|
||||
Settings
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onMenuItemClick(() =>
|
||||
navigate(`/templates/new?fromTemplate=${templateName}`),
|
||||
)}
|
||||
>
|
||||
<FileCopyOutlined />
|
||||
Duplicate
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onMenuItemClick(() =>
|
||||
navigate(
|
||||
@@ -87,9 +79,17 @@ const TemplateMenu: FC<{
|
||||
<EditOutlined />
|
||||
Edit files
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={onMenuItemClick(() =>
|
||||
navigate(`/templates/new?fromTemplate=${templateName}`),
|
||||
)}
|
||||
>
|
||||
<FileCopyOutlined />
|
||||
Duplicate…
|
||||
</MenuItem>
|
||||
<MenuItem onClick={onMenuItemClick(onDelete)}>
|
||||
<DeleteOutlined />
|
||||
Delete
|
||||
Delete…
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { getTemplatePageTitle } from "../utils";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getTemplateVersions, updateActiveTemplateVersion } from "api/api";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { useTemplateLayoutContext } from "components/TemplateLayout/TemplateLayout";
|
||||
import { useTemplateLayoutContext } from "pages/TemplatePage/TemplateLayout";
|
||||
import { VersionsTable } from "./VersionsTable";
|
||||
import { useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
|
||||
@@ -88,7 +88,7 @@ export const VersionRow: React.FC<VersionRowProps> = ({
|
||||
onPromoteClick(version.id);
|
||||
}}
|
||||
>
|
||||
Promote
|
||||
Promote…
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMachine } from "@xstate/react";
|
||||
import { usePermissions } from "hooks/usePermissions";
|
||||
import { useOrganizationId } from "hooks/useOrganizationId";
|
||||
import { useTab } from "hooks/useTab";
|
||||
import { FC } from "react";
|
||||
import { type FC, useMemo } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { pageTitle } from "utils/page";
|
||||
@@ -21,6 +22,17 @@ export const TemplateVersionPage: FC = () => {
|
||||
context: { templateName, versionName, orgId },
|
||||
});
|
||||
const tab = useTab("file", "0");
|
||||
const permissions = usePermissions();
|
||||
|
||||
const versionId = state.context.currentVersion?.id;
|
||||
const createWorkspaceUrl = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
if (versionId) {
|
||||
params.set("version", versionId);
|
||||
return `/templates/${templateName}/workspace?${params.toString()}`;
|
||||
}
|
||||
return undefined;
|
||||
}, [templateName, versionId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -33,6 +45,9 @@ export const TemplateVersionPage: FC = () => {
|
||||
versionName={versionName}
|
||||
templateName={templateName}
|
||||
tab={tab}
|
||||
createWorkspaceUrl={
|
||||
permissions.updateTemplates ? createWorkspaceUrl : undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { Margins } from "components/Margins/Margins";
|
||||
@@ -13,7 +13,7 @@ import { Stack } from "components/Stack/Stack";
|
||||
import { Stats, StatsItem } from "components/Stats/Stats";
|
||||
import { TemplateFiles } from "components/TemplateFiles/TemplateFiles";
|
||||
import { UseTabResult } from "hooks/useTab";
|
||||
import { FC } from "react";
|
||||
import { type FC } from "react";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { createDayString } from "utils/createDayString";
|
||||
import { TemplateVersionMachineContext } from "xServices/templateVersion/templateVersionXService";
|
||||
@@ -27,6 +27,7 @@ export interface TemplateVersionPageViewProps {
|
||||
templateName: string;
|
||||
tab: UseTabResult;
|
||||
context: TemplateVersionMachineContext;
|
||||
createWorkspaceUrl?: string;
|
||||
}
|
||||
|
||||
export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
|
||||
@@ -34,6 +35,7 @@ export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
|
||||
tab,
|
||||
versionName,
|
||||
templateName,
|
||||
createWorkspaceUrl,
|
||||
}) => {
|
||||
const { currentFiles, error, currentVersion, previousFiles } = context;
|
||||
|
||||
@@ -41,12 +43,25 @@ export const TemplateVersionPageView: FC<TemplateVersionPageViewProps> = ({
|
||||
<Margins>
|
||||
<PageHeader
|
||||
actions={
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/templates/${templateName}/versions/${versionName}/edit`}
|
||||
>
|
||||
<Button startIcon={<EditIcon />}>Edit</Button>
|
||||
</Link>
|
||||
<>
|
||||
{createWorkspaceUrl && (
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
component={RouterLink}
|
||||
to={createWorkspaceUrl}
|
||||
>
|
||||
Create workspace
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
startIcon={<EditIcon />}
|
||||
component={RouterLink}
|
||||
to={`/templates/${templateName}/versions/${versionName}/edit`}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<PageHeaderCaption>Version</PageHeaderCaption>
|
||||
|
||||
@@ -2109,6 +2109,7 @@ export const MockPermissions: Permissions = {
|
||||
createTemplates: true,
|
||||
createUser: true,
|
||||
deleteTemplates: true,
|
||||
updateTemplates: true,
|
||||
readAllUsers: true,
|
||||
updateUsers: true,
|
||||
viewAuditLog: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ export const checks = {
|
||||
updateUsers: "updateUsers",
|
||||
createUser: "createUser",
|
||||
createTemplates: "createTemplates",
|
||||
updateTemplates: "updateTemplates",
|
||||
deleteTemplates: "deleteTemplates",
|
||||
viewAuditLog: "viewAuditLog",
|
||||
viewDeploymentValues: "viewDeploymentValues",
|
||||
@@ -47,6 +48,12 @@ export const permissionsToCheck = {
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
[checks.updateTemplates]: {
|
||||
object: {
|
||||
resource_type: "template",
|
||||
},
|
||||
action: "update",
|
||||
},
|
||||
[checks.deleteTemplates]: {
|
||||
object: {
|
||||
resource_type: "template",
|
||||
|
||||
Reference in New Issue
Block a user