Skip to content

feat: add template editor to the ui #5963

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 23 commits into from
Feb 7, 2023
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
6 changes: 4 additions & 2 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,10 @@ const (
// interface for all RBAC operations. NOT READY FOR PRODUCTION USE.
ExperimentAuthzQuerier Experiment = "authz_querier"

// ExperimentTemplateEditor is an internal experiment that enables the template editor
// for all users.
ExperimentTemplateEditor Experiment = "template_editor"

// Add new experiments here!
// ExperimentExample Experiment = "example"
)
Expand All @@ -430,7 +434,7 @@ var (
// users to opt-in to via --experimental='*'.
// Experiments that are not ready for consumption by all users should
// not be included here and will be essentially hidden.
ExperimentsAll = Experiments{}
ExperimentsAll = Experiments{ExperimentTemplateEditor}
)

// Experiments is a list of experiments that are enabled for the deployment.
Expand Down
7 changes: 4 additions & 3 deletions docs/api/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -2770,9 +2770,10 @@ CreateParameterRequest is a structure used to create a new parameter value for a

#### Enumerated Values

| Value |
| --------------- |
| `authz_querier` |
| Value |
| ----------------- |
| `authz_querier` |
| `template_editor` |

## codersdk.Feature

Expand Down
3 changes: 3 additions & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"react-chartjs-2": "4.3.1",
"react-color": "2.19.3",
"react-dom": "18.2.0",
"react-headless-tabs": "^6.0.3",
"react-helmet-async": "1.3.0",
"react-i18next": "12.1.1",
"react-markdown": "8.0.3",
Expand All @@ -75,6 +76,7 @@
"remark-gfm": "3.0.1",
"rollup-plugin-visualizer": "5.9.0",
"sourcemapped-stacktrace": "1.1.11",
"tar-js": "^0.3.0",
"ts-prune": "0.10.3",
"tzdata": "1.0.30",
"ua-parser-js": "1.0.33",
Expand Down Expand Up @@ -125,6 +127,7 @@
"jest-esm-transformer": "^1.0.0",
"jest-runner-eslint": "1.1.0",
"jest-websocket-mock": "2.4.0",
"monaco-editor": "^0.34.1",
"msw": "0.47.0",
"prettier": "2.8.1",
"resize-observer": "1.0.4",
Expand Down
14 changes: 13 additions & 1 deletion site/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ const GitAuthPage = lazy(() => import("./pages/GitAuthPage/GitAuthPage"))
const TemplateVersionPage = lazy(
() => import("./pages/TemplateVersionPage/TemplateVersionPage"),
)
const TemplateVersionEditorPage = lazy(
() =>
import(
"./pages/TemplateVersionPage/TemplateVersionEditorPage/TemplateVersionEditorPage"
),
)
const StarterTemplatesPage = lazy(
() => import("./pages/StarterTemplatesPage/StarterTemplatesPage"),
)
Expand Down Expand Up @@ -155,7 +161,13 @@ export const AppRouter: FC = () => {
<Route path="workspace" element={<CreateWorkspacePage />} />
<Route path="settings" element={<TemplateSettingsPage />} />
<Route path="versions">
<Route path=":version" element={<TemplateVersionPage />} />
<Route path=":version">
<Route index element={<TemplateVersionPage />} />
<Route
path="edit"
element={<TemplateVersionEditorPage />}
/>
</Route>
</Route>
</Route>
</Route>
Expand Down
20 changes: 20 additions & 0 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,17 @@ export const createTemplate = async (
return response.data
}

export const updateActiveTemplateVersion = async (
templateId: string,
data: TypesGen.UpdateActiveTemplateVersion,
): Promise<Types.Message> => {
const response = await axios.patch<Types.Message>(
`/api/v2/templates/${templateId}/versions`,
data,
)
return response.data
}

export const updateTemplateMeta = async (
templateId: string,
data: TypesGen.UpdateTemplateMeta,
Expand Down Expand Up @@ -433,6 +444,15 @@ export const cancelWorkspaceBuild = async (
return response.data
}

export const cancelTemplateVersionBuild = async (
templateVersionId: TypesGen.TemplateVersion["id"],
): Promise<Types.Message> => {
const response = await axios.patch(
`/api/v2/templateversions/${templateVersionId}/cancel`,
)
return response.data
}

export const createUser = async (
user: TypesGen.CreateUserRequest,
): Promise<TypesGen.User> => {
Expand Down
4 changes: 2 additions & 2 deletions site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,8 +1082,8 @@ export const Entitlements: Entitlement[] = [
]

// From codersdk/deployment.go
export type Experiment = "authz_querier"
export const Experiments: Experiment[] = ["authz_querier"]
export type Experiment = "authz_querier" | "template_editor"
export const Experiments: Experiment[] = ["authz_querier", "template_editor"]

// From codersdk/deployment.go
export type FeatureName =
Expand Down
1 change: 0 additions & 1 deletion site/src/components/Dashboard/DashboardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,5 @@ const useStyles = makeStyles((theme) => ({
},
siteContent: {
flex: 1,
paddingBottom: theme.spacing(10),
},
}))
14 changes: 10 additions & 4 deletions site/src/components/Logs/Logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ interface Line {

export interface LogsProps {
lines: Line[]
hideTimestamps?: boolean
className?: string
}

export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
hideTimestamps,
lines,
className = "",
}) => {
Expand All @@ -27,10 +29,14 @@ export const Logs: FC<React.PropsWithChildren<LogsProps>> = ({
<div className={styles.scrollWrapper}>
{lines.map((line, idx) => (
<div className={combineClasses([styles.line, line.level])} key={idx}>
<span className={styles.time}>
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
</span>
<span className={styles.space}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
{!hideTimestamps && (
<>
<span className={styles.time}>
{dayjs(line.time).format(`HH:mm:ss.SSS`)}
</span>
<span className={styles.space}>&nbsp;&nbsp;&nbsp;&nbsp;</span>
</>
)}
<span>{line.output}</span>
</div>
))}
Expand Down
3 changes: 2 additions & 1 deletion site/src/components/SyntaxHighlighter/coderTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Theme, useTheme } from "@material-ui/core/styles"
import { useMonaco } from "@monaco-editor/react"
import { useEffect, useState } from "react"
import { hslToHex } from "util/colors"
import { editor } from "monaco-editor"

// Theme based on https://github.com/brijeshb42/monaco-themes/blob/master/themes/Dracula.json
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- The theme is not typed
export const coderTheme = (theme: Theme): Record<string, any> => ({
export const coderTheme = (theme: Theme): editor.IStandaloneThemeData => ({
base: "vs-dark",
inherit: true,
rules: [
Expand Down
1 change: 1 addition & 0 deletions site/src/components/TemplateLayout/TemplateLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Avatar } from "components/Avatar/Avatar"

const Language = {
settingsButton: "Settings",
editButton: "Edit",
createButton: "Create workspace",
noDescription: "",
}
Expand Down
155 changes: 155 additions & 0 deletions site/src/components/TemplateVersionEditor/FileDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import TextField from "@material-ui/core/TextField"
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog"
import { Stack } from "components/Stack/Stack"
import { ChangeEvent, FC, useState } from "react"
import Typography from "@material-ui/core/Typography"

export const CreateFileDialog: FC<{
onClose: () => void
checkExists: (path: string) => boolean
onConfirm: (path: string) => void
open: boolean
}> = ({ checkExists, onClose, onConfirm, open }) => {
const [pathValue, setPathValue] = useState("")
const [error, setError] = useState("")
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setPathValue(event.target.value)
}
const handleConfirm = () => {
if (pathValue === "") {
setError("You must enter a path!")
return
}
if (checkExists(pathValue)) {
setError("File already exists")
return
}
onConfirm(pathValue)
setPathValue("")
}

return (
<ConfirmDialog
open={open}
onClose={() => {
onClose()
setPathValue("")
}}
onConfirm={handleConfirm}
hideCancel={false}
type="success"
cancelText="Cancel"
confirmText="Create"
title="Create File"
description={
<Stack spacing={1}>
<Typography>
Specify the path to a file to be created. This path can contain
slashes too!
</Typography>
<TextField
autoFocus
onKeyDown={(event) => {
if (event.key === "Enter") {
handleConfirm()
}
}}
helperText={error}
name="file-path"
autoComplete="off"
id="file-path"
placeholder="main.tf"
value={pathValue}
onChange={handleChange}
label="File Path"
/>
</Stack>
}
/>
)
}

export const DeleteFileDialog: FC<{
onClose: () => void
onConfirm: () => void
open: boolean
filename: string
}> = ({ onClose, onConfirm, open, filename }) => {
return (
<ConfirmDialog
type="delete"
onClose={onClose}
open={open}
onConfirm={onConfirm}
title="Delete File"
description={`Are you sure you want to delete "${filename}"?`}
/>
)
}

export const RenameFileDialog: FC<{
onClose: () => void
onConfirm: (filename: string) => void
checkExists: (path: string) => boolean
open: boolean
filename: string
}> = ({ checkExists, onClose, onConfirm, open, filename }) => {
const [pathValue, setPathValue] = useState(filename)
const [error, setError] = useState("")
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setPathValue(event.target.value)
}
const handleConfirm = () => {
if (pathValue === "") {
setError("You must enter a path!")
return
}
if (checkExists(pathValue)) {
setError("File already exists")
return
}
onConfirm(pathValue)
setPathValue("")
}

return (
<ConfirmDialog
open={open}
onClose={() => {
onClose()
setPathValue("")
}}
onConfirm={handleConfirm}
hideCancel={false}
type="success"
cancelText="Cancel"
confirmText="Create"
title="Rename File"
description={
<Stack spacing={1}>
<Typography>
Rename {`"${filename}"`} to something else. This path can contain
slashes too!
</Typography>
<TextField
autoFocus
onKeyDown={(event) => {
if (event.key === "Enter") {
handleConfirm()
}
}}
helperText={error}
name="file-path"
autoComplete="off"
id="file-path"
placeholder="main.tf"
defaultValue={filename}
value={pathValue}
onChange={handleChange}
label="File Path"
/>
</Stack>
}
/>
)
}
Loading