mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
chore: use tabs for prettier and biome (#14283)
This commit is contained in:
committed by
GitHub
parent
db2d0596d4
commit
95a7c0c4f0
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "Development environments on your infrastructure",
|
||||
"image": "codercom/oss-dogfood:latest",
|
||||
"name": "Development environments on your infrastructure",
|
||||
"image": "codercom/oss-dogfood:latest",
|
||||
|
||||
"features": {
|
||||
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"moby": "false"
|
||||
}
|
||||
},
|
||||
// SYS_PTRACE to enable go debugging
|
||||
"runArgs": ["--cap-add=SYS_PTRACE"]
|
||||
"features": {
|
||||
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"moby": "false"
|
||||
}
|
||||
},
|
||||
// SYS_PTRACE to enable go debugging
|
||||
"runArgs": ["--cap-add=SYS_PTRACE"]
|
||||
}
|
||||
|
||||
@@ -95,10 +95,6 @@ updates:
|
||||
- "@emotion*"
|
||||
exclude-patterns:
|
||||
- "jest-runner-eslint"
|
||||
eslint:
|
||||
patterns:
|
||||
- "eslint*"
|
||||
- "@typescript-eslint*"
|
||||
jest:
|
||||
patterns:
|
||||
- "jest"
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"ignorePatterns": [
|
||||
{
|
||||
"pattern": "://localhost"
|
||||
},
|
||||
{
|
||||
"pattern": "://.*.?example\\.com"
|
||||
},
|
||||
{
|
||||
"pattern": "developer.github.com"
|
||||
},
|
||||
{
|
||||
"pattern": "docs.github.com"
|
||||
},
|
||||
{
|
||||
"pattern": "support.google.com"
|
||||
},
|
||||
{
|
||||
"pattern": "tailscale.com"
|
||||
},
|
||||
{
|
||||
"pattern": "wireguard.com"
|
||||
}
|
||||
],
|
||||
"aliveStatusCodes": [200, 0]
|
||||
"ignorePatterns": [
|
||||
{
|
||||
"pattern": "://localhost"
|
||||
},
|
||||
{
|
||||
"pattern": "://.*.?example\\.com"
|
||||
},
|
||||
{
|
||||
"pattern": "developer.github.com"
|
||||
},
|
||||
{
|
||||
"pattern": "docs.github.com"
|
||||
},
|
||||
{
|
||||
"pattern": "support.google.com"
|
||||
},
|
||||
{
|
||||
"pattern": "tailscale.com"
|
||||
},
|
||||
{
|
||||
"pattern": "wireguard.com"
|
||||
}
|
||||
],
|
||||
"aliveStatusCodes": [200, 0]
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
printWidth: 80
|
||||
proseWrap: always
|
||||
trailingComma: all
|
||||
useTabs: false
|
||||
useTabs: true
|
||||
tabWidth: 2
|
||||
overrides:
|
||||
- files:
|
||||
|
||||
Vendored
+13
-13
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"github.vscode-codeql",
|
||||
"golang.go",
|
||||
"hashicorp.terraform",
|
||||
"esbenp.prettier-vscode",
|
||||
"foxundermoon.shell-format",
|
||||
"emeraldwalk.runonsave",
|
||||
"zxh404.vscode-proto3",
|
||||
"redhat.vscode-yaml",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"EditorConfig.EditorConfig",
|
||||
"biomejs.biome"
|
||||
]
|
||||
"recommendations": [
|
||||
"github.vscode-codeql",
|
||||
"golang.go",
|
||||
"hashicorp.terraform",
|
||||
"esbenp.prettier-vscode",
|
||||
"foxundermoon.shell-format",
|
||||
"emeraldwalk.runonsave",
|
||||
"zxh404.vscode-proto3",
|
||||
"redhat.vscode-yaml",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"EditorConfig.EditorConfig",
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+234
-234
@@ -1,238 +1,238 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"afero",
|
||||
"agentsdk",
|
||||
"apps",
|
||||
"ASKPASS",
|
||||
"authcheck",
|
||||
"autostop",
|
||||
"awsidentity",
|
||||
"bodyclose",
|
||||
"buildinfo",
|
||||
"buildname",
|
||||
"circbuf",
|
||||
"cliflag",
|
||||
"cliui",
|
||||
"codecov",
|
||||
"coderd",
|
||||
"coderdenttest",
|
||||
"coderdtest",
|
||||
"codersdk",
|
||||
"contravariance",
|
||||
"cronstrue",
|
||||
"databasefake",
|
||||
"dbgen",
|
||||
"dbmem",
|
||||
"dbtype",
|
||||
"DERP",
|
||||
"derphttp",
|
||||
"derpmap",
|
||||
"devel",
|
||||
"devtunnel",
|
||||
"dflags",
|
||||
"drpc",
|
||||
"drpcconn",
|
||||
"drpcmux",
|
||||
"drpcserver",
|
||||
"Dsts",
|
||||
"embeddedpostgres",
|
||||
"enablements",
|
||||
"enterprisemeta",
|
||||
"errgroup",
|
||||
"eventsourcemock",
|
||||
"externalauth",
|
||||
"Failf",
|
||||
"fatih",
|
||||
"Formik",
|
||||
"gitauth",
|
||||
"gitsshkey",
|
||||
"goarch",
|
||||
"gographviz",
|
||||
"goleak",
|
||||
"gonet",
|
||||
"gossh",
|
||||
"gsyslog",
|
||||
"GTTY",
|
||||
"hashicorp",
|
||||
"hclsyntax",
|
||||
"httpapi",
|
||||
"httpmw",
|
||||
"idtoken",
|
||||
"Iflag",
|
||||
"incpatch",
|
||||
"initialisms",
|
||||
"ipnstate",
|
||||
"isatty",
|
||||
"Jobf",
|
||||
"Keygen",
|
||||
"kirsle",
|
||||
"Kubernetes",
|
||||
"ldflags",
|
||||
"magicsock",
|
||||
"manifoldco",
|
||||
"mapstructure",
|
||||
"mattn",
|
||||
"mitchellh",
|
||||
"moby",
|
||||
"namesgenerator",
|
||||
"namespacing",
|
||||
"netaddr",
|
||||
"netip",
|
||||
"netmap",
|
||||
"netns",
|
||||
"netstack",
|
||||
"nettype",
|
||||
"nfpms",
|
||||
"nhooyr",
|
||||
"nmcfg",
|
||||
"nolint",
|
||||
"nosec",
|
||||
"ntqry",
|
||||
"OIDC",
|
||||
"oneof",
|
||||
"opty",
|
||||
"paralleltest",
|
||||
"parameterscopeid",
|
||||
"pqtype",
|
||||
"prometheusmetrics",
|
||||
"promhttp",
|
||||
"protobuf",
|
||||
"provisionerd",
|
||||
"provisionerdserver",
|
||||
"provisionersdk",
|
||||
"ptty",
|
||||
"ptys",
|
||||
"ptytest",
|
||||
"quickstart",
|
||||
"reconfig",
|
||||
"replicasync",
|
||||
"retrier",
|
||||
"rpty",
|
||||
"SCIM",
|
||||
"sdkproto",
|
||||
"sdktrace",
|
||||
"Signup",
|
||||
"slogtest",
|
||||
"sourcemapped",
|
||||
"spinbutton",
|
||||
"Srcs",
|
||||
"stdbuf",
|
||||
"stretchr",
|
||||
"STTY",
|
||||
"stuntest",
|
||||
"tailbroker",
|
||||
"tailcfg",
|
||||
"tailexchange",
|
||||
"tailnet",
|
||||
"tailnettest",
|
||||
"Tailscale",
|
||||
"tanstack",
|
||||
"tbody",
|
||||
"TCGETS",
|
||||
"tcpip",
|
||||
"TCSETS",
|
||||
"templateversions",
|
||||
"testdata",
|
||||
"testid",
|
||||
"testutil",
|
||||
"tfexec",
|
||||
"tfjson",
|
||||
"tfplan",
|
||||
"tfstate",
|
||||
"thead",
|
||||
"tios",
|
||||
"tmpdir",
|
||||
"tokenconfig",
|
||||
"Topbar",
|
||||
"tparallel",
|
||||
"trialer",
|
||||
"trimprefix",
|
||||
"tsdial",
|
||||
"tslogger",
|
||||
"tstun",
|
||||
"turnconn",
|
||||
"typegen",
|
||||
"typesafe",
|
||||
"unconvert",
|
||||
"Untar",
|
||||
"Userspace",
|
||||
"VMID",
|
||||
"walkthrough",
|
||||
"weblinks",
|
||||
"webrtc",
|
||||
"wgcfg",
|
||||
"wgconfig",
|
||||
"wgengine",
|
||||
"wgmonitor",
|
||||
"wgnet",
|
||||
"workspaceagent",
|
||||
"workspaceagents",
|
||||
"workspaceapp",
|
||||
"workspaceapps",
|
||||
"workspacebuilds",
|
||||
"workspacename",
|
||||
"wsjson",
|
||||
"xerrors",
|
||||
"xlarge",
|
||||
"xsmall",
|
||||
"yamux"
|
||||
],
|
||||
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
||||
"emeraldwalk.runonsave": {
|
||||
"commands": [
|
||||
{
|
||||
"match": "database/queries/*.sql",
|
||||
"cmd": "make gen"
|
||||
},
|
||||
{
|
||||
"match": "provisionerd/proto/provisionerd.proto",
|
||||
"cmd": "make provisionerd/proto/provisionerd.pb.go"
|
||||
}
|
||||
]
|
||||
},
|
||||
"search.exclude": {
|
||||
"**.pb.go": true,
|
||||
"**/*.gen.json": true,
|
||||
"**/testdata/*": true,
|
||||
"coderd/apidoc/**": true,
|
||||
"docs/reference/api/*.md": true,
|
||||
"docs/reference/cli/*.md": true,
|
||||
"docs/templates/*.md": true,
|
||||
"LICENSE": true,
|
||||
"scripts/metricsdocgen/metrics": true,
|
||||
"site/out/**": true,
|
||||
"site/storybook-static/**": true,
|
||||
"**.map": true,
|
||||
"pnpm-lock.yaml": true
|
||||
},
|
||||
// Ensure files always have a newline.
|
||||
"files.insertFinalNewline": true,
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": ["--fast"],
|
||||
"go.coverageDecorator": {
|
||||
"type": "gutter",
|
||||
"coveredGutterStyle": "blockgreen",
|
||||
"uncoveredGutterStyle": "blockred"
|
||||
},
|
||||
// The codersdk is used by coderd another other packages extensively.
|
||||
// To reduce redundancy in tests, it's covered by other packages.
|
||||
// Since package coverage pairing can't be defined, all packages cover
|
||||
// all other packages.
|
||||
"go.testFlags": ["-short", "-coverpkg=./..."],
|
||||
// We often use a version of TypeScript that's ahead of the version shipped
|
||||
// with VS Code.
|
||||
"typescript.tsdk": "./site/node_modules/typescript/lib",
|
||||
// Playwright tests in VSCode will open a browser to live "view" the test.
|
||||
"playwright.reuseBrowser": true,
|
||||
"cSpell.words": [
|
||||
"afero",
|
||||
"agentsdk",
|
||||
"apps",
|
||||
"ASKPASS",
|
||||
"authcheck",
|
||||
"autostop",
|
||||
"awsidentity",
|
||||
"bodyclose",
|
||||
"buildinfo",
|
||||
"buildname",
|
||||
"circbuf",
|
||||
"cliflag",
|
||||
"cliui",
|
||||
"codecov",
|
||||
"coderd",
|
||||
"coderdenttest",
|
||||
"coderdtest",
|
||||
"codersdk",
|
||||
"contravariance",
|
||||
"cronstrue",
|
||||
"databasefake",
|
||||
"dbgen",
|
||||
"dbmem",
|
||||
"dbtype",
|
||||
"DERP",
|
||||
"derphttp",
|
||||
"derpmap",
|
||||
"devel",
|
||||
"devtunnel",
|
||||
"dflags",
|
||||
"drpc",
|
||||
"drpcconn",
|
||||
"drpcmux",
|
||||
"drpcserver",
|
||||
"Dsts",
|
||||
"embeddedpostgres",
|
||||
"enablements",
|
||||
"enterprisemeta",
|
||||
"errgroup",
|
||||
"eventsourcemock",
|
||||
"externalauth",
|
||||
"Failf",
|
||||
"fatih",
|
||||
"Formik",
|
||||
"gitauth",
|
||||
"gitsshkey",
|
||||
"goarch",
|
||||
"gographviz",
|
||||
"goleak",
|
||||
"gonet",
|
||||
"gossh",
|
||||
"gsyslog",
|
||||
"GTTY",
|
||||
"hashicorp",
|
||||
"hclsyntax",
|
||||
"httpapi",
|
||||
"httpmw",
|
||||
"idtoken",
|
||||
"Iflag",
|
||||
"incpatch",
|
||||
"initialisms",
|
||||
"ipnstate",
|
||||
"isatty",
|
||||
"Jobf",
|
||||
"Keygen",
|
||||
"kirsle",
|
||||
"Kubernetes",
|
||||
"ldflags",
|
||||
"magicsock",
|
||||
"manifoldco",
|
||||
"mapstructure",
|
||||
"mattn",
|
||||
"mitchellh",
|
||||
"moby",
|
||||
"namesgenerator",
|
||||
"namespacing",
|
||||
"netaddr",
|
||||
"netip",
|
||||
"netmap",
|
||||
"netns",
|
||||
"netstack",
|
||||
"nettype",
|
||||
"nfpms",
|
||||
"nhooyr",
|
||||
"nmcfg",
|
||||
"nolint",
|
||||
"nosec",
|
||||
"ntqry",
|
||||
"OIDC",
|
||||
"oneof",
|
||||
"opty",
|
||||
"paralleltest",
|
||||
"parameterscopeid",
|
||||
"pqtype",
|
||||
"prometheusmetrics",
|
||||
"promhttp",
|
||||
"protobuf",
|
||||
"provisionerd",
|
||||
"provisionerdserver",
|
||||
"provisionersdk",
|
||||
"ptty",
|
||||
"ptys",
|
||||
"ptytest",
|
||||
"quickstart",
|
||||
"reconfig",
|
||||
"replicasync",
|
||||
"retrier",
|
||||
"rpty",
|
||||
"SCIM",
|
||||
"sdkproto",
|
||||
"sdktrace",
|
||||
"Signup",
|
||||
"slogtest",
|
||||
"sourcemapped",
|
||||
"spinbutton",
|
||||
"Srcs",
|
||||
"stdbuf",
|
||||
"stretchr",
|
||||
"STTY",
|
||||
"stuntest",
|
||||
"tailbroker",
|
||||
"tailcfg",
|
||||
"tailexchange",
|
||||
"tailnet",
|
||||
"tailnettest",
|
||||
"Tailscale",
|
||||
"tanstack",
|
||||
"tbody",
|
||||
"TCGETS",
|
||||
"tcpip",
|
||||
"TCSETS",
|
||||
"templateversions",
|
||||
"testdata",
|
||||
"testid",
|
||||
"testutil",
|
||||
"tfexec",
|
||||
"tfjson",
|
||||
"tfplan",
|
||||
"tfstate",
|
||||
"thead",
|
||||
"tios",
|
||||
"tmpdir",
|
||||
"tokenconfig",
|
||||
"Topbar",
|
||||
"tparallel",
|
||||
"trialer",
|
||||
"trimprefix",
|
||||
"tsdial",
|
||||
"tslogger",
|
||||
"tstun",
|
||||
"turnconn",
|
||||
"typegen",
|
||||
"typesafe",
|
||||
"unconvert",
|
||||
"Untar",
|
||||
"Userspace",
|
||||
"VMID",
|
||||
"walkthrough",
|
||||
"weblinks",
|
||||
"webrtc",
|
||||
"wgcfg",
|
||||
"wgconfig",
|
||||
"wgengine",
|
||||
"wgmonitor",
|
||||
"wgnet",
|
||||
"workspaceagent",
|
||||
"workspaceagents",
|
||||
"workspaceapp",
|
||||
"workspaceapps",
|
||||
"workspacebuilds",
|
||||
"workspacename",
|
||||
"wsjson",
|
||||
"xerrors",
|
||||
"xlarge",
|
||||
"xsmall",
|
||||
"yamux"
|
||||
],
|
||||
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
||||
"emeraldwalk.runonsave": {
|
||||
"commands": [
|
||||
{
|
||||
"match": "database/queries/*.sql",
|
||||
"cmd": "make gen"
|
||||
},
|
||||
{
|
||||
"match": "provisionerd/proto/provisionerd.proto",
|
||||
"cmd": "make provisionerd/proto/provisionerd.pb.go"
|
||||
}
|
||||
]
|
||||
},
|
||||
"search.exclude": {
|
||||
"**.pb.go": true,
|
||||
"**/*.gen.json": true,
|
||||
"**/testdata/*": true,
|
||||
"coderd/apidoc/**": true,
|
||||
"docs/reference/api/*.md": true,
|
||||
"docs/reference/cli/*.md": true,
|
||||
"docs/templates/*.md": true,
|
||||
"LICENSE": true,
|
||||
"scripts/metricsdocgen/metrics": true,
|
||||
"site/out/**": true,
|
||||
"site/storybook-static/**": true,
|
||||
"**.map": true,
|
||||
"pnpm-lock.yaml": true
|
||||
},
|
||||
// Ensure files always have a newline.
|
||||
"files.insertFinalNewline": true,
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": ["--fast"],
|
||||
"go.coverageDecorator": {
|
||||
"type": "gutter",
|
||||
"coveredGutterStyle": "blockgreen",
|
||||
"uncoveredGutterStyle": "blockred"
|
||||
},
|
||||
// The codersdk is used by coderd another other packages extensively.
|
||||
// To reduce redundancy in tests, it's covered by other packages.
|
||||
// Since package coverage pairing can't be defined, all packages cover
|
||||
// all other packages.
|
||||
"go.testFlags": ["-short", "-coverpkg=./..."],
|
||||
// We often use a version of TypeScript that's ahead of the version shipped
|
||||
// with VS Code.
|
||||
"typescript.tsdk": "./site/node_modules/typescript/lib",
|
||||
// Playwright tests in VSCode will open a browser to live "view" the test.
|
||||
"playwright.reuseBrowser": true,
|
||||
|
||||
"[javascript][javascriptreact][json][jsonc][typescript][typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
// "editor.codeActionsOnSave": {
|
||||
// "source.organizeImports.biome": "explicit"
|
||||
// }
|
||||
},
|
||||
"[javascript][javascriptreact][json][jsonc][typescript][typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
// "editor.codeActionsOnSave": {
|
||||
// "source.organizeImports.biome": "explicit"
|
||||
// }
|
||||
},
|
||||
|
||||
"[css][html][markdown][yaml]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
"[css][html][markdown][yaml]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ gen: \
|
||||
site/src/api/typesGenerated.ts \
|
||||
coderd/rbac/object_gen.go \
|
||||
codersdk/rbacresources_gen.go \
|
||||
site/src/api/rbacresources_gen.ts \
|
||||
site/src/api/rbacresourcesGenerated.ts \
|
||||
docs/admin/prometheus.md \
|
||||
docs/reference/cli/README.md \
|
||||
docs/admin/audit-logs.md \
|
||||
@@ -520,7 +520,7 @@ gen/mark-fresh:
|
||||
site/src/api/typesGenerated.ts \
|
||||
coderd/rbac/object_gen.go \
|
||||
codersdk/rbacresources_gen.go \
|
||||
site/src/api/rbacresources_gen.ts \
|
||||
site/src/api/rbacresourcesGenerated.ts \
|
||||
docs/admin/prometheus.md \
|
||||
docs/reference/cli/README.md \
|
||||
docs/admin/audit-logs.md \
|
||||
@@ -621,8 +621,8 @@ coderd/rbac/object_gen.go: scripts/rbacgen/rbacobject.gotmpl scripts/rbacgen/mai
|
||||
codersdk/rbacresources_gen.go: scripts/rbacgen/codersdk.gotmpl scripts/rbacgen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
|
||||
go run scripts/rbacgen/main.go codersdk > codersdk/rbacresources_gen.go
|
||||
|
||||
site/src/api/rbacresources_gen.ts: scripts/rbacgen/codersdk.gotmpl scripts/rbacgen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
|
||||
go run scripts/rbacgen/main.go typescript > site/src/api/rbacresources_gen.ts
|
||||
site/src/api/rbacresourcesGenerated.ts: scripts/rbacgen/codersdk.gotmpl scripts/rbacgen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
|
||||
go run scripts/rbacgen/main.go typescript > "$@"
|
||||
|
||||
|
||||
docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/metrics
|
||||
|
||||
Generated
+14297
-14297
File diff suppressed because it is too large
Load Diff
+44
-44
@@ -1,46 +1,46 @@
|
||||
{
|
||||
"action": "never-match-action",
|
||||
"object": {
|
||||
"id": "9046b041-58ed-47a3-9c3a-de302577875a",
|
||||
"owner": "00000000-0000-0000-0000-000000000000",
|
||||
"org_owner": "bf7b72bd-a2b1-4ef2-962c-1d698e0483f6",
|
||||
"type": "workspace",
|
||||
"acl_user_list": {
|
||||
"f041847d-711b-40da-a89a-ede39f70dc7f": ["create"]
|
||||
},
|
||||
"acl_group_list": {}
|
||||
},
|
||||
"subject": {
|
||||
"id": "10d03e62-7703-4df5-a358-4f76577d4e2f",
|
||||
"roles": [
|
||||
{
|
||||
"name": "owner",
|
||||
"display_name": "Owner",
|
||||
"site": [
|
||||
{
|
||||
"negate": false,
|
||||
"resource_type": "*",
|
||||
"action": "*"
|
||||
}
|
||||
],
|
||||
"org": {},
|
||||
"user": []
|
||||
}
|
||||
],
|
||||
"groups": ["b617a647-b5d0-4cbe-9e40-26f89710bf18"],
|
||||
"scope": {
|
||||
"name": "Scope_all",
|
||||
"display_name": "All operations",
|
||||
"site": [
|
||||
{
|
||||
"negate": false,
|
||||
"resource_type": "*",
|
||||
"action": "*"
|
||||
}
|
||||
],
|
||||
"org": {},
|
||||
"user": [],
|
||||
"allow_list": ["*"]
|
||||
}
|
||||
}
|
||||
"action": "never-match-action",
|
||||
"object": {
|
||||
"id": "9046b041-58ed-47a3-9c3a-de302577875a",
|
||||
"owner": "00000000-0000-0000-0000-000000000000",
|
||||
"org_owner": "bf7b72bd-a2b1-4ef2-962c-1d698e0483f6",
|
||||
"type": "workspace",
|
||||
"acl_user_list": {
|
||||
"f041847d-711b-40da-a89a-ede39f70dc7f": ["create"]
|
||||
},
|
||||
"acl_group_list": {}
|
||||
},
|
||||
"subject": {
|
||||
"id": "10d03e62-7703-4df5-a358-4f76577d4e2f",
|
||||
"roles": [
|
||||
{
|
||||
"name": "owner",
|
||||
"display_name": "Owner",
|
||||
"site": [
|
||||
{
|
||||
"negate": false,
|
||||
"resource_type": "*",
|
||||
"action": "*"
|
||||
}
|
||||
],
|
||||
"org": {},
|
||||
"user": []
|
||||
}
|
||||
],
|
||||
"groups": ["b617a647-b5d0-4cbe-9e40-26f89710bf18"],
|
||||
"scope": {
|
||||
"name": "Scope_all",
|
||||
"display_name": "All operations",
|
||||
"site": [
|
||||
{
|
||||
"negate": false,
|
||||
"resource_type": "*",
|
||||
"action": "*"
|
||||
}
|
||||
],
|
||||
"org": {},
|
||||
"user": [],
|
||||
"allow_list": ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-28
@@ -82,34 +82,34 @@ entry:
|
||||
|
||||
```json
|
||||
{
|
||||
"ts": "2023-06-13T03:45:37.294730279Z",
|
||||
"level": "INFO",
|
||||
"msg": "audit_log",
|
||||
"caller": "/home/runner/work/coder/coder/enterprise/audit/backends/slog.go:36",
|
||||
"func": "github.com/coder/coder/enterprise/audit/backends.slogBackend.Export",
|
||||
"logger_names": ["coderd"],
|
||||
"fields": {
|
||||
"ID": "033a9ffa-b54d-4c10-8ec3-2aaf9e6d741a",
|
||||
"Time": "2023-06-13T03:45:37.288506Z",
|
||||
"UserID": "6c405053-27e3-484a-9ad7-bcb64e7bfde6",
|
||||
"OrganizationID": "00000000-0000-0000-0000-000000000000",
|
||||
"Ip": "{IPNet:{IP:\u003cnil\u003e Mask:\u003cnil\u003e} Valid:false}",
|
||||
"UserAgent": "{String: Valid:false}",
|
||||
"ResourceType": "workspace_build",
|
||||
"ResourceID": "ca5647e0-ef50-4202-a246-717e04447380",
|
||||
"ResourceTarget": "",
|
||||
"Action": "start",
|
||||
"Diff": {},
|
||||
"StatusCode": 200,
|
||||
"AdditionalFields": {
|
||||
"workspace_name": "linux-container",
|
||||
"build_number": "9",
|
||||
"build_reason": "initiator",
|
||||
"workspace_owner": ""
|
||||
},
|
||||
"RequestID": "bb791ac3-f6ee-4da8-8ec2-f54e87013e93",
|
||||
"ResourceIcon": ""
|
||||
}
|
||||
"ts": "2023-06-13T03:45:37.294730279Z",
|
||||
"level": "INFO",
|
||||
"msg": "audit_log",
|
||||
"caller": "/home/runner/work/coder/coder/enterprise/audit/backends/slog.go:36",
|
||||
"func": "github.com/coder/coder/enterprise/audit/backends.slogBackend.Export",
|
||||
"logger_names": ["coderd"],
|
||||
"fields": {
|
||||
"ID": "033a9ffa-b54d-4c10-8ec3-2aaf9e6d741a",
|
||||
"Time": "2023-06-13T03:45:37.288506Z",
|
||||
"UserID": "6c405053-27e3-484a-9ad7-bcb64e7bfde6",
|
||||
"OrganizationID": "00000000-0000-0000-0000-000000000000",
|
||||
"Ip": "{IPNet:{IP:\u003cnil\u003e Mask:\u003cnil\u003e} Valid:false}",
|
||||
"UserAgent": "{String: Valid:false}",
|
||||
"ResourceType": "workspace_build",
|
||||
"ResourceID": "ca5647e0-ef50-4202-a246-717e04447380",
|
||||
"ResourceTarget": "",
|
||||
"Action": "start",
|
||||
"Diff": {},
|
||||
"StatusCode": 200,
|
||||
"AdditionalFields": {
|
||||
"workspace_name": "linux-container",
|
||||
"build_number": "9",
|
||||
"build_reason": "initiator",
|
||||
"workspace_owner": ""
|
||||
},
|
||||
"RequestID": "bb791ac3-f6ee-4da8-8ec2-f54e87013e93",
|
||||
"ResourceIcon": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -316,7 +316,7 @@ OIDC provider will be added to the `myCoderGroupName` group in Coder.
|
||||
> **Note:** Groups are only updated on login.
|
||||
|
||||
[azure-gids]:
|
||||
https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
|
||||
https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
|
||||
|
||||
### Group allowlist
|
||||
|
||||
|
||||
@@ -125,17 +125,17 @@ within the component's story.
|
||||
|
||||
```tsx
|
||||
export const WithQuota: Story = {
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: getWorkspaceQuotaQueryKey(MockUser.username),
|
||||
data: {
|
||||
credits_consumed: 2,
|
||||
budget: 40,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
parameters: {
|
||||
queries: [
|
||||
{
|
||||
key: getWorkspaceQuotaQueryKey(MockUser.username),
|
||||
data: {
|
||||
credits_consumed: 2,
|
||||
budget: 40,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
@@ -150,12 +150,12 @@ example below:
|
||||
|
||||
```ts
|
||||
export const getAgentListeningPorts = async (
|
||||
agentID: string,
|
||||
agentID: string,
|
||||
): Promise<TypesGen.ListeningPortsResponse> => {
|
||||
const response = await axiosInstance.get(
|
||||
`/api/v2/workspaceagents/${agentID}/listening-ports`,
|
||||
);
|
||||
return response.data;
|
||||
const response = await axiosInstance.get(
|
||||
`/api/v2/workspaceagents/${agentID}/listening-ports`,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -164,10 +164,10 @@ wrap it as a single function.
|
||||
|
||||
```ts
|
||||
export const updateWorkspaceVersion = async (
|
||||
workspace: TypesGen.Workspace,
|
||||
workspace: TypesGen.Workspace,
|
||||
): Promise<TypesGen.WorkspaceBuild> => {
|
||||
const template = await getTemplate(workspace.template_id);
|
||||
return startWorkspace(workspace.id, template.active_version_id);
|
||||
const template = await getTemplate(workspace.template_id);
|
||||
return startWorkspace(workspace.id, template.active_version_id);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -214,10 +214,10 @@ inside the component itself using MUI's `visuallyHidden` utility function.
|
||||
import { visuallyHidden } from "@mui/utils";
|
||||
|
||||
<Button>
|
||||
<GearIcon />
|
||||
<Box component="span" sx={visuallyHidden}>
|
||||
Settings
|
||||
</Box>
|
||||
<GearIcon />
|
||||
<Box component="span" sx={visuallyHidden}>
|
||||
Settings
|
||||
</Box>
|
||||
</Button>;
|
||||
```
|
||||
|
||||
|
||||
+59
-59
@@ -39,21 +39,21 @@ following:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Federated": "accounts.google.com"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"accounts.google.com:aud": "<enter-OAuth-client-ID-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Principal": {
|
||||
"Federated": "accounts.google.com"
|
||||
},
|
||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"accounts.google.com:aud": "<enter-OAuth-client-ID-here"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -64,50 +64,50 @@ following policy to the role:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@ actual Docker registry URL, username, and password.
|
||||
|
||||
```json
|
||||
{
|
||||
"auths": {
|
||||
"<your-registry>": {
|
||||
"username": "<your-username>",
|
||||
"password": "<your-password>"
|
||||
}
|
||||
}
|
||||
"auths": {
|
||||
"<your-registry>": {
|
||||
"username": "<your-username>",
|
||||
"password": "<your-password>"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -54,13 +54,13 @@ The output should look similar to this:
|
||||
|
||||
```json
|
||||
{
|
||||
"auths": {
|
||||
"your.private.registry.com": {
|
||||
"username": "ericpaulsen",
|
||||
"password": "xxxx",
|
||||
"auth": "c3R...zE2"
|
||||
}
|
||||
}
|
||||
"auths": {
|
||||
"your.private.registry.com": {
|
||||
"username": "ericpaulsen",
|
||||
"password": "xxxx",
|
||||
"auth": "c3R...zE2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+1182
-1182
File diff suppressed because it is too large
Load Diff
Generated
+213
-213
@@ -38,8 +38,8 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/aws-instance-identi
|
||||
|
||||
```json
|
||||
{
|
||||
"document": "string",
|
||||
"signature": "string"
|
||||
"document": "string",
|
||||
"signature": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -55,7 +55,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/aws-instance-identi
|
||||
|
||||
```json
|
||||
{
|
||||
"session_token": "string"
|
||||
"session_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -85,8 +85,8 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/azure-instance-iden
|
||||
|
||||
```json
|
||||
{
|
||||
"encoding": "string",
|
||||
"signature": "string"
|
||||
"encoding": "string",
|
||||
"signature": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/azure-instance-iden
|
||||
|
||||
```json
|
||||
{
|
||||
"session_token": "string"
|
||||
"session_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -132,7 +132,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/google-instance-ide
|
||||
|
||||
```json
|
||||
{
|
||||
"json_web_token": "string"
|
||||
"json_web_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,7 +148,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/google-instance-ide
|
||||
|
||||
```json
|
||||
{
|
||||
"session_token": "string"
|
||||
"session_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -187,12 +187,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/external-auth?mat
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "string",
|
||||
"password": "string",
|
||||
"token_extra": {},
|
||||
"type": "string",
|
||||
"url": "string",
|
||||
"username": "string"
|
||||
"access_token": "string",
|
||||
"password": "string",
|
||||
"token_extra": {},
|
||||
"type": "string",
|
||||
"url": "string",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -231,12 +231,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitauth?match=str
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "string",
|
||||
"password": "string",
|
||||
"token_extra": {},
|
||||
"type": "string",
|
||||
"url": "string",
|
||||
"username": "string"
|
||||
"access_token": "string",
|
||||
"password": "string",
|
||||
"token_extra": {},
|
||||
"type": "string",
|
||||
"url": "string",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -267,8 +267,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitsshkey \
|
||||
|
||||
```json
|
||||
{
|
||||
"private_key": "string",
|
||||
"public_key": "string"
|
||||
"private_key": "string",
|
||||
"public_key": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -298,9 +298,9 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/log-source \
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "string"
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -316,11 +316,11 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/log-source \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -350,14 +350,14 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/logs \
|
||||
|
||||
```json
|
||||
{
|
||||
"log_source_id": "string",
|
||||
"logs": [
|
||||
{
|
||||
"created_at": "string",
|
||||
"level": "trace",
|
||||
"output": "string"
|
||||
}
|
||||
]
|
||||
"log_source_id": "string",
|
||||
"logs": [
|
||||
{
|
||||
"created_at": "string",
|
||||
"level": "trace",
|
||||
"output": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -373,14 +373,14 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/logs \
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -417,91 +417,91 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \
|
||||
|
||||
```json
|
||||
{
|
||||
"api_version": "string",
|
||||
"apps": [
|
||||
{
|
||||
"command": "string",
|
||||
"display_name": "string",
|
||||
"external": true,
|
||||
"health": "disabled",
|
||||
"healthcheck": {
|
||||
"interval": 0,
|
||||
"threshold": 0,
|
||||
"url": "string"
|
||||
},
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"sharing_level": "owner",
|
||||
"slug": "string",
|
||||
"subdomain": true,
|
||||
"subdomain_name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"architecture": "string",
|
||||
"connection_timeout_seconds": 0,
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"directory": "string",
|
||||
"disconnected_at": "2019-08-24T14:15:22Z",
|
||||
"display_apps": ["vscode"],
|
||||
"environment_variables": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"expanded_directory": "string",
|
||||
"first_connected_at": "2019-08-24T14:15:22Z",
|
||||
"health": {
|
||||
"healthy": false,
|
||||
"reason": "agent has lost connection"
|
||||
},
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"instance_id": "string",
|
||||
"last_connected_at": "2019-08-24T14:15:22Z",
|
||||
"latency": {
|
||||
"property1": {
|
||||
"latency_ms": 0,
|
||||
"preferred": true
|
||||
},
|
||||
"property2": {
|
||||
"latency_ms": 0,
|
||||
"preferred": true
|
||||
}
|
||||
},
|
||||
"lifecycle_state": "created",
|
||||
"log_sources": [
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
||||
}
|
||||
],
|
||||
"logs_length": 0,
|
||||
"logs_overflowed": true,
|
||||
"name": "string",
|
||||
"operating_system": "string",
|
||||
"ready_at": "2019-08-24T14:15:22Z",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"scripts": [
|
||||
{
|
||||
"cron": "string",
|
||||
"log_path": "string",
|
||||
"log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a",
|
||||
"run_on_start": true,
|
||||
"run_on_stop": true,
|
||||
"script": "string",
|
||||
"start_blocks_login": true,
|
||||
"timeout": 0
|
||||
}
|
||||
],
|
||||
"started_at": "2019-08-24T14:15:22Z",
|
||||
"startup_script_behavior": "blocking",
|
||||
"status": "connecting",
|
||||
"subsystems": ["envbox"],
|
||||
"troubleshooting_url": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string"
|
||||
"api_version": "string",
|
||||
"apps": [
|
||||
{
|
||||
"command": "string",
|
||||
"display_name": "string",
|
||||
"external": true,
|
||||
"health": "disabled",
|
||||
"healthcheck": {
|
||||
"interval": 0,
|
||||
"threshold": 0,
|
||||
"url": "string"
|
||||
},
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"sharing_level": "owner",
|
||||
"slug": "string",
|
||||
"subdomain": true,
|
||||
"subdomain_name": "string",
|
||||
"url": "string"
|
||||
}
|
||||
],
|
||||
"architecture": "string",
|
||||
"connection_timeout_seconds": 0,
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"directory": "string",
|
||||
"disconnected_at": "2019-08-24T14:15:22Z",
|
||||
"display_apps": ["vscode"],
|
||||
"environment_variables": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"expanded_directory": "string",
|
||||
"first_connected_at": "2019-08-24T14:15:22Z",
|
||||
"health": {
|
||||
"healthy": false,
|
||||
"reason": "agent has lost connection"
|
||||
},
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"instance_id": "string",
|
||||
"last_connected_at": "2019-08-24T14:15:22Z",
|
||||
"latency": {
|
||||
"property1": {
|
||||
"latency_ms": 0,
|
||||
"preferred": true
|
||||
},
|
||||
"property2": {
|
||||
"latency_ms": 0,
|
||||
"preferred": true
|
||||
}
|
||||
},
|
||||
"lifecycle_state": "created",
|
||||
"log_sources": [
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
||||
}
|
||||
],
|
||||
"logs_length": 0,
|
||||
"logs_overflowed": true,
|
||||
"name": "string",
|
||||
"operating_system": "string",
|
||||
"ready_at": "2019-08-24T14:15:22Z",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"scripts": [
|
||||
{
|
||||
"cron": "string",
|
||||
"log_path": "string",
|
||||
"log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a",
|
||||
"run_on_start": true,
|
||||
"run_on_stop": true,
|
||||
"script": "string",
|
||||
"start_blocks_login": true,
|
||||
"timeout": 0
|
||||
}
|
||||
],
|
||||
"started_at": "2019-08-24T14:15:22Z",
|
||||
"startup_script_behavior": "blocking",
|
||||
"status": "connecting",
|
||||
"subsystems": ["envbox"],
|
||||
"troubleshooting_url": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -538,67 +538,67 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/con
|
||||
|
||||
```json
|
||||
{
|
||||
"derp_force_websockets": true,
|
||||
"derp_map": {
|
||||
"homeParams": {
|
||||
"regionScore": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
}
|
||||
},
|
||||
"omitDefaultRegions": true,
|
||||
"regions": {
|
||||
"property1": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"property2": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable_direct_connections": true
|
||||
"derp_force_websockets": true,
|
||||
"derp_map": {
|
||||
"homeParams": {
|
||||
"regionScore": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
}
|
||||
},
|
||||
"omitDefaultRegions": true,
|
||||
"regions": {
|
||||
"property1": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"property2": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"disable_direct_connections": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -661,13 +661,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/lis
|
||||
|
||||
```json
|
||||
{
|
||||
"ports": [
|
||||
{
|
||||
"network": "string",
|
||||
"port": 0,
|
||||
"process_name": "string"
|
||||
}
|
||||
]
|
||||
"ports": [
|
||||
{
|
||||
"network": "string",
|
||||
"port": 0,
|
||||
"process_name": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -708,13 +708,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/log
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": 0,
|
||||
"level": "trace",
|
||||
"output": "string",
|
||||
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
||||
}
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": 0,
|
||||
"level": "trace",
|
||||
"output": "string",
|
||||
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -804,13 +804,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": 0,
|
||||
"level": "trace",
|
||||
"output": "string",
|
||||
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
||||
}
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": 0,
|
||||
"level": "trace",
|
||||
"output": "string",
|
||||
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -45,7 +45,7 @@ curl -X GET http://coder-server:8080/api/v2/applications/host \
|
||||
|
||||
```json
|
||||
{
|
||||
"host": "string"
|
||||
"host": "string"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+60
-60
@@ -27,66 +27,66 @@ curl -X GET http://coder-server:8080/api/v2/audit?limit=0 \
|
||||
|
||||
```json
|
||||
{
|
||||
"audit_logs": [
|
||||
{
|
||||
"action": "create",
|
||||
"additional_fields": [0],
|
||||
"description": "string",
|
||||
"diff": {
|
||||
"property1": {
|
||||
"new": null,
|
||||
"old": null,
|
||||
"secret": true
|
||||
},
|
||||
"property2": {
|
||||
"new": null,
|
||||
"old": null,
|
||||
"secret": true
|
||||
}
|
||||
},
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"ip": "string",
|
||||
"is_deleted": true,
|
||||
"organization": {
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string"
|
||||
},
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||
"resource_icon": "string",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"resource_link": "string",
|
||||
"resource_target": "string",
|
||||
"resource_type": "template",
|
||||
"status_code": 0,
|
||||
"time": "2019-08-24T14:15:22Z",
|
||||
"user": {
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
},
|
||||
"user_agent": "string"
|
||||
}
|
||||
],
|
||||
"count": 0
|
||||
"audit_logs": [
|
||||
{
|
||||
"action": "create",
|
||||
"additional_fields": [0],
|
||||
"description": "string",
|
||||
"diff": {
|
||||
"property1": {
|
||||
"new": null,
|
||||
"old": null,
|
||||
"secret": true
|
||||
},
|
||||
"property2": {
|
||||
"new": null,
|
||||
"old": null,
|
||||
"secret": true
|
||||
}
|
||||
},
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"ip": "string",
|
||||
"is_deleted": true,
|
||||
"organization": {
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string"
|
||||
},
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||
"resource_icon": "string",
|
||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||
"resource_link": "string",
|
||||
"resource_target": "string",
|
||||
"resource_type": "template",
|
||||
"status_code": 0,
|
||||
"time": "2019-08-24T14:15:22Z",
|
||||
"user": {
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
},
|
||||
"user_agent": "string"
|
||||
}
|
||||
],
|
||||
"count": 0
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+33
-33
@@ -18,28 +18,28 @@ curl -X POST http://coder-server:8080/api/v2/authcheck \
|
||||
|
||||
```json
|
||||
{
|
||||
"checks": {
|
||||
"property1": {
|
||||
"action": "create",
|
||||
"object": {
|
||||
"any_org": true,
|
||||
"organization_id": "string",
|
||||
"owner_id": "string",
|
||||
"resource_id": "string",
|
||||
"resource_type": "*"
|
||||
}
|
||||
},
|
||||
"property2": {
|
||||
"action": "create",
|
||||
"object": {
|
||||
"any_org": true,
|
||||
"organization_id": "string",
|
||||
"owner_id": "string",
|
||||
"resource_id": "string",
|
||||
"resource_type": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
"checks": {
|
||||
"property1": {
|
||||
"action": "create",
|
||||
"object": {
|
||||
"any_org": true,
|
||||
"organization_id": "string",
|
||||
"owner_id": "string",
|
||||
"resource_id": "string",
|
||||
"resource_type": "*"
|
||||
}
|
||||
},
|
||||
"property2": {
|
||||
"action": "create",
|
||||
"object": {
|
||||
"any_org": true,
|
||||
"organization_id": "string",
|
||||
"owner_id": "string",
|
||||
"resource_id": "string",
|
||||
"resource_type": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -55,8 +55,8 @@ curl -X POST http://coder-server:8080/api/v2/authcheck \
|
||||
|
||||
```json
|
||||
{
|
||||
"property1": true,
|
||||
"property2": true
|
||||
"property1": true,
|
||||
"property2": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -85,8 +85,8 @@ curl -X POST http://coder-server:8080/api/v2/users/login \
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "string"
|
||||
"email": "user@example.com",
|
||||
"password": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ curl -X POST http://coder-server:8080/api/v2/users/login \
|
||||
|
||||
```json
|
||||
{
|
||||
"session_token": "string"
|
||||
"session_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -130,8 +130,8 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/convert-login \
|
||||
|
||||
```json
|
||||
{
|
||||
"password": "string",
|
||||
"to_type": ""
|
||||
"password": "string",
|
||||
"to_type": ""
|
||||
}
|
||||
```
|
||||
|
||||
@@ -148,10 +148,10 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/convert-login \
|
||||
|
||||
```json
|
||||
{
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"state_string": "string",
|
||||
"to_type": "",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"state_string": "string",
|
||||
"to_type": "",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+871
-871
File diff suppressed because it is too large
Load Diff
Generated
+329
-329
@@ -45,332 +45,332 @@ curl -X GET http://coder-server:8080/api/v2/debug/health \
|
||||
|
||||
```json
|
||||
{
|
||||
"access_url": {
|
||||
"access_url": "string",
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"healthz_response": "string",
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"status_code": 0,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"coder_version": "string",
|
||||
"database": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"latency": "string",
|
||||
"latency_ms": 0,
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"threshold_ms": 0,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"derp": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"netcheck": {
|
||||
"captivePortal": "string",
|
||||
"globalV4": "string",
|
||||
"globalV6": "string",
|
||||
"hairPinning": "string",
|
||||
"icmpv4": true,
|
||||
"ipv4": true,
|
||||
"ipv4CanSend": true,
|
||||
"ipv6": true,
|
||||
"ipv6CanSend": true,
|
||||
"mappingVariesByDestIP": "string",
|
||||
"oshasIPv6": true,
|
||||
"pcp": "string",
|
||||
"pmp": "string",
|
||||
"preferredDERP": 0,
|
||||
"regionLatency": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
},
|
||||
"regionV4Latency": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
},
|
||||
"regionV6Latency": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
},
|
||||
"udp": true,
|
||||
"upnP": "string"
|
||||
},
|
||||
"netcheck_err": "string",
|
||||
"netcheck_logs": ["string"],
|
||||
"regions": {
|
||||
"property1": {
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node_reports": [
|
||||
{
|
||||
"can_exchange_messages": true,
|
||||
"client_errs": [["string"]],
|
||||
"client_logs": [["string"]],
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node": {
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
},
|
||||
"node_info": {
|
||||
"tokenBucketBytesBurst": 0,
|
||||
"tokenBucketBytesPerSecond": 0
|
||||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
"error": "string"
|
||||
},
|
||||
"uses_websocket": true,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"region": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"property2": {
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node_reports": [
|
||||
{
|
||||
"can_exchange_messages": true,
|
||||
"client_errs": [["string"]],
|
||||
"client_logs": [["string"]],
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node": {
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
},
|
||||
"node_info": {
|
||||
"tokenBucketBytesBurst": 0,
|
||||
"tokenBucketBytesPerSecond": 0
|
||||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
"error": "string"
|
||||
},
|
||||
"uses_websocket": true,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"region": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"healthy": true,
|
||||
"provisioner_daemons": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"items": [
|
||||
{
|
||||
"provisioner_daemon": {
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"provisioners": ["string"],
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"version": "string"
|
||||
},
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"severity": "ok",
|
||||
"time": "2019-08-24T14:15:22Z",
|
||||
"websocket": {
|
||||
"body": "string",
|
||||
"code": 0,
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace_proxy": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"workspace_proxies": {
|
||||
"regions": [
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"path_app_url": "string",
|
||||
"status": {
|
||||
"checked_at": "2019-08-24T14:15:22Z",
|
||||
"report": {
|
||||
"errors": ["string"],
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"access_url": {
|
||||
"access_url": "string",
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"healthz_response": "string",
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"status_code": 0,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"coder_version": "string",
|
||||
"database": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"latency": "string",
|
||||
"latency_ms": 0,
|
||||
"reachable": true,
|
||||
"severity": "ok",
|
||||
"threshold_ms": 0,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"derp": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"netcheck": {
|
||||
"captivePortal": "string",
|
||||
"globalV4": "string",
|
||||
"globalV6": "string",
|
||||
"hairPinning": "string",
|
||||
"icmpv4": true,
|
||||
"ipv4": true,
|
||||
"ipv4CanSend": true,
|
||||
"ipv6": true,
|
||||
"ipv6CanSend": true,
|
||||
"mappingVariesByDestIP": "string",
|
||||
"oshasIPv6": true,
|
||||
"pcp": "string",
|
||||
"pmp": "string",
|
||||
"preferredDERP": 0,
|
||||
"regionLatency": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
},
|
||||
"regionV4Latency": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
},
|
||||
"regionV6Latency": {
|
||||
"property1": 0,
|
||||
"property2": 0
|
||||
},
|
||||
"udp": true,
|
||||
"upnP": "string"
|
||||
},
|
||||
"netcheck_err": "string",
|
||||
"netcheck_logs": ["string"],
|
||||
"regions": {
|
||||
"property1": {
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node_reports": [
|
||||
{
|
||||
"can_exchange_messages": true,
|
||||
"client_errs": [["string"]],
|
||||
"client_logs": [["string"]],
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node": {
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
},
|
||||
"node_info": {
|
||||
"tokenBucketBytesBurst": 0,
|
||||
"tokenBucketBytesPerSecond": 0
|
||||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
"error": "string"
|
||||
},
|
||||
"uses_websocket": true,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"region": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"property2": {
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node_reports": [
|
||||
{
|
||||
"can_exchange_messages": true,
|
||||
"client_errs": [["string"]],
|
||||
"client_logs": [["string"]],
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"node": {
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
},
|
||||
"node_info": {
|
||||
"tokenBucketBytesBurst": 0,
|
||||
"tokenBucketBytesPerSecond": 0
|
||||
},
|
||||
"round_trip_ping": "string",
|
||||
"round_trip_ping_ms": 0,
|
||||
"severity": "ok",
|
||||
"stun": {
|
||||
"canSTUN": true,
|
||||
"enabled": true,
|
||||
"error": "string"
|
||||
},
|
||||
"uses_websocket": true,
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"region": {
|
||||
"avoid": true,
|
||||
"embeddedRelay": true,
|
||||
"nodes": [
|
||||
{
|
||||
"canPort80": true,
|
||||
"certName": "string",
|
||||
"derpport": 0,
|
||||
"forceHTTP": true,
|
||||
"hostName": "string",
|
||||
"insecureForTests": true,
|
||||
"ipv4": "string",
|
||||
"ipv6": "string",
|
||||
"name": "string",
|
||||
"regionID": 0,
|
||||
"stunonly": true,
|
||||
"stunport": 0,
|
||||
"stuntestIP": "string"
|
||||
}
|
||||
],
|
||||
"regionCode": "string",
|
||||
"regionID": 0,
|
||||
"regionName": "string"
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"healthy": true,
|
||||
"provisioner_daemons": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"items": [
|
||||
{
|
||||
"provisioner_daemon": {
|
||||
"api_version": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"provisioners": ["string"],
|
||||
"tags": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"version": "string"
|
||||
},
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"severity": "ok",
|
||||
"time": "2019-08-24T14:15:22Z",
|
||||
"websocket": {
|
||||
"body": "string",
|
||||
"code": 0,
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"workspace_proxy": {
|
||||
"dismissed": true,
|
||||
"error": "string",
|
||||
"healthy": true,
|
||||
"severity": "ok",
|
||||
"warnings": [
|
||||
{
|
||||
"code": "EUNKNOWN",
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"workspace_proxies": {
|
||||
"regions": [
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"deleted": true,
|
||||
"derp_enabled": true,
|
||||
"derp_only": true,
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"path_app_url": "string",
|
||||
"status": {
|
||||
"checked_at": "2019-08-24T14:15:22Z",
|
||||
"report": {
|
||||
"errors": ["string"],
|
||||
"warnings": ["string"]
|
||||
},
|
||||
"status": "ok"
|
||||
},
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"version": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -401,7 +401,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health/settings \
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["DERP"]
|
||||
"dismissed_healthchecks": ["DERP"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -431,7 +431,7 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["DERP"]
|
||||
"dismissed_healthchecks": ["DERP"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -447,7 +447,7 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \
|
||||
|
||||
```json
|
||||
{
|
||||
"dismissed_healthchecks": ["DERP"]
|
||||
"dismissed_healthchecks": ["DERP"]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+647
-647
File diff suppressed because it is too large
Load Diff
Generated
+1
-1
@@ -34,7 +34,7 @@ file: string
|
||||
|
||||
```json
|
||||
{
|
||||
"hash": "19686d84-b10d-4f90-b18e-84fd3fa038fd"
|
||||
"hash": "19686d84-b10d-4f90-b18e-84fd3fa038fd"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+419
-419
@@ -18,14 +18,14 @@ curl -X GET http://coder-server:8080/api/v2/ \
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -53,14 +53,14 @@ curl -X GET http://coder-server:8080/api/v2/buildinfo \
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_api_version": "string",
|
||||
"dashboard_url": "string",
|
||||
"deployment_id": "string",
|
||||
"external_url": "string",
|
||||
"telemetry": true,
|
||||
"upgrade_message": "string",
|
||||
"version": "string",
|
||||
"workspace_proxy": true
|
||||
"agent_api_version": "string",
|
||||
"dashboard_url": "string",
|
||||
"deployment_id": "string",
|
||||
"external_url": "string",
|
||||
"telemetry": true,
|
||||
"upgrade_message": "string",
|
||||
"version": "string",
|
||||
"workspace_proxy": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -87,7 +87,7 @@ curl -X POST http://coder-server:8080/api/v2/csp/reports \
|
||||
|
||||
```json
|
||||
{
|
||||
"csp-report": {}
|
||||
"csp-report": {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -124,377 +124,377 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"access_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"agent_fallback_troubleshooting_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"agent_stat_refresh_interval": 0,
|
||||
"allow_workspace_renames": true,
|
||||
"autobuild_poll_interval": 0,
|
||||
"browser_only": true,
|
||||
"cache_directory": "string",
|
||||
"cli_upgrade_message": "string",
|
||||
"config": "string",
|
||||
"config_ssh": {
|
||||
"deploymentName": "string",
|
||||
"sshconfigOptions": ["string"]
|
||||
},
|
||||
"dangerous": {
|
||||
"allow_all_cors": true,
|
||||
"allow_path_app_sharing": true,
|
||||
"allow_path_app_site_owner_access": true
|
||||
},
|
||||
"derp": {
|
||||
"config": {
|
||||
"block_direct": true,
|
||||
"force_websockets": true,
|
||||
"path": "string",
|
||||
"url": "string"
|
||||
},
|
||||
"server": {
|
||||
"enable": true,
|
||||
"region_code": "string",
|
||||
"region_id": 0,
|
||||
"region_name": "string",
|
||||
"relay_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"stun_addresses": ["string"]
|
||||
}
|
||||
},
|
||||
"disable_owner_workspace_exec": true,
|
||||
"disable_password_auth": true,
|
||||
"disable_path_apps": true,
|
||||
"docs_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"experiments": ["string"],
|
||||
"external_auth": {
|
||||
"value": [
|
||||
{
|
||||
"app_install_url": "string",
|
||||
"app_installations_url": "string",
|
||||
"auth_url": "string",
|
||||
"client_id": "string",
|
||||
"device_code_url": "string",
|
||||
"device_flow": true,
|
||||
"display_icon": "string",
|
||||
"display_name": "string",
|
||||
"id": "string",
|
||||
"no_refresh": true,
|
||||
"regex": "string",
|
||||
"scopes": ["string"],
|
||||
"token_url": "string",
|
||||
"type": "string",
|
||||
"validate_url": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"external_token_encryption_keys": ["string"],
|
||||
"healthcheck": {
|
||||
"refresh": 0,
|
||||
"threshold_database": 0
|
||||
},
|
||||
"http_address": "string",
|
||||
"in_memory_database": true,
|
||||
"job_hang_detector_interval": 0,
|
||||
"logging": {
|
||||
"human": "string",
|
||||
"json": "string",
|
||||
"log_filter": ["string"],
|
||||
"stackdriver": "string"
|
||||
},
|
||||
"metrics_cache_refresh_interval": 0,
|
||||
"notifications": {
|
||||
"dispatch_timeout": 0,
|
||||
"email": {
|
||||
"auth": {
|
||||
"identity": "string",
|
||||
"password": "string",
|
||||
"password_file": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"force_tls": true,
|
||||
"from": "string",
|
||||
"hello": "string",
|
||||
"smarthost": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"tls": {
|
||||
"ca_file": "string",
|
||||
"cert_file": "string",
|
||||
"insecure_skip_verify": true,
|
||||
"key_file": "string",
|
||||
"server_name": "string",
|
||||
"start_tls": true
|
||||
}
|
||||
},
|
||||
"fetch_interval": 0,
|
||||
"lease_count": 0,
|
||||
"lease_period": 0,
|
||||
"max_send_attempts": 0,
|
||||
"method": "string",
|
||||
"retry_interval": 0,
|
||||
"sync_buffer_size": 0,
|
||||
"sync_interval": 0,
|
||||
"webhook": {
|
||||
"endpoint": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"github": {
|
||||
"allow_everyone": true,
|
||||
"allow_signups": true,
|
||||
"allowed_orgs": ["string"],
|
||||
"allowed_teams": ["string"],
|
||||
"client_id": "string",
|
||||
"client_secret": "string",
|
||||
"enterprise_base_url": "string"
|
||||
}
|
||||
},
|
||||
"oidc": {
|
||||
"allow_signups": true,
|
||||
"auth_url_params": {},
|
||||
"client_cert_file": "string",
|
||||
"client_id": "string",
|
||||
"client_key_file": "string",
|
||||
"client_secret": "string",
|
||||
"email_domain": ["string"],
|
||||
"email_field": "string",
|
||||
"group_allow_list": ["string"],
|
||||
"group_auto_create": true,
|
||||
"group_mapping": {},
|
||||
"group_regex_filter": {},
|
||||
"groups_field": "string",
|
||||
"icon_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"ignore_email_verified": true,
|
||||
"ignore_user_info": true,
|
||||
"issuer_url": "string",
|
||||
"name_field": "string",
|
||||
"scopes": ["string"],
|
||||
"sign_in_text": "string",
|
||||
"signups_disabled_text": "string",
|
||||
"skip_issuer_checks": true,
|
||||
"user_role_field": "string",
|
||||
"user_role_mapping": {},
|
||||
"user_roles_default": ["string"],
|
||||
"username_field": "string"
|
||||
},
|
||||
"pg_auth": "string",
|
||||
"pg_connection_url": "string",
|
||||
"pprof": {
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"enable": true
|
||||
},
|
||||
"prometheus": {
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"aggregate_agent_stats_by": ["string"],
|
||||
"collect_agent_stats": true,
|
||||
"collect_db_metrics": true,
|
||||
"enable": true
|
||||
},
|
||||
"provisioner": {
|
||||
"daemon_poll_interval": 0,
|
||||
"daemon_poll_jitter": 0,
|
||||
"daemon_psk": "string",
|
||||
"daemon_types": ["string"],
|
||||
"daemons": 0,
|
||||
"force_cancel_interval": 0
|
||||
},
|
||||
"proxy_health_status_interval": 0,
|
||||
"proxy_trusted_headers": ["string"],
|
||||
"proxy_trusted_origins": ["string"],
|
||||
"rate_limit": {
|
||||
"api": 0,
|
||||
"disable_all": true
|
||||
},
|
||||
"redirect_to_access_url": true,
|
||||
"scim_api_key": "string",
|
||||
"secure_auth_cookie": true,
|
||||
"session_lifetime": {
|
||||
"default_duration": 0,
|
||||
"disable_expiry_refresh": true,
|
||||
"max_token_lifetime": 0
|
||||
},
|
||||
"ssh_keygen_algorithm": "string",
|
||||
"strict_transport_security": 0,
|
||||
"strict_transport_security_options": ["string"],
|
||||
"support": {
|
||||
"links": {
|
||||
"value": [
|
||||
{
|
||||
"icon": "bug",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"swagger": {
|
||||
"enable": true
|
||||
},
|
||||
"telemetry": {
|
||||
"enable": true,
|
||||
"trace": true,
|
||||
"url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
}
|
||||
},
|
||||
"terms_of_service_url": "string",
|
||||
"tls": {
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"allow_insecure_ciphers": true,
|
||||
"cert_file": ["string"],
|
||||
"client_auth": "string",
|
||||
"client_ca_file": "string",
|
||||
"client_cert_file": "string",
|
||||
"client_key_file": "string",
|
||||
"enable": true,
|
||||
"key_file": ["string"],
|
||||
"min_version": "string",
|
||||
"redirect_http": true,
|
||||
"supported_ciphers": ["string"]
|
||||
},
|
||||
"trace": {
|
||||
"capture_logs": true,
|
||||
"data_dog": true,
|
||||
"enable": true,
|
||||
"honeycomb_api_key": "string"
|
||||
},
|
||||
"update_check": true,
|
||||
"user_quiet_hours_schedule": {
|
||||
"allow_user_custom": true,
|
||||
"default_schedule": "string"
|
||||
},
|
||||
"verbose": true,
|
||||
"web_terminal_renderer": "string",
|
||||
"wgtunnel_host": "string",
|
||||
"wildcard_access_url": "string",
|
||||
"write_config": true
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"annotations": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"default": "string",
|
||||
"description": "string",
|
||||
"env": "string",
|
||||
"flag": "string",
|
||||
"flag_shorthand": "string",
|
||||
"group": {
|
||||
"description": "string",
|
||||
"name": "string",
|
||||
"parent": {
|
||||
"description": "string",
|
||||
"name": "string",
|
||||
"parent": {},
|
||||
"yaml": "string"
|
||||
},
|
||||
"yaml": "string"
|
||||
},
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"required": true,
|
||||
"use_instead": [{}],
|
||||
"value": null,
|
||||
"value_source": "",
|
||||
"yaml": "string"
|
||||
}
|
||||
]
|
||||
"config": {
|
||||
"access_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"agent_fallback_troubleshooting_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"agent_stat_refresh_interval": 0,
|
||||
"allow_workspace_renames": true,
|
||||
"autobuild_poll_interval": 0,
|
||||
"browser_only": true,
|
||||
"cache_directory": "string",
|
||||
"cli_upgrade_message": "string",
|
||||
"config": "string",
|
||||
"config_ssh": {
|
||||
"deploymentName": "string",
|
||||
"sshconfigOptions": ["string"]
|
||||
},
|
||||
"dangerous": {
|
||||
"allow_all_cors": true,
|
||||
"allow_path_app_sharing": true,
|
||||
"allow_path_app_site_owner_access": true
|
||||
},
|
||||
"derp": {
|
||||
"config": {
|
||||
"block_direct": true,
|
||||
"force_websockets": true,
|
||||
"path": "string",
|
||||
"url": "string"
|
||||
},
|
||||
"server": {
|
||||
"enable": true,
|
||||
"region_code": "string",
|
||||
"region_id": 0,
|
||||
"region_name": "string",
|
||||
"relay_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"stun_addresses": ["string"]
|
||||
}
|
||||
},
|
||||
"disable_owner_workspace_exec": true,
|
||||
"disable_password_auth": true,
|
||||
"disable_path_apps": true,
|
||||
"docs_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"experiments": ["string"],
|
||||
"external_auth": {
|
||||
"value": [
|
||||
{
|
||||
"app_install_url": "string",
|
||||
"app_installations_url": "string",
|
||||
"auth_url": "string",
|
||||
"client_id": "string",
|
||||
"device_code_url": "string",
|
||||
"device_flow": true,
|
||||
"display_icon": "string",
|
||||
"display_name": "string",
|
||||
"id": "string",
|
||||
"no_refresh": true,
|
||||
"regex": "string",
|
||||
"scopes": ["string"],
|
||||
"token_url": "string",
|
||||
"type": "string",
|
||||
"validate_url": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"external_token_encryption_keys": ["string"],
|
||||
"healthcheck": {
|
||||
"refresh": 0,
|
||||
"threshold_database": 0
|
||||
},
|
||||
"http_address": "string",
|
||||
"in_memory_database": true,
|
||||
"job_hang_detector_interval": 0,
|
||||
"logging": {
|
||||
"human": "string",
|
||||
"json": "string",
|
||||
"log_filter": ["string"],
|
||||
"stackdriver": "string"
|
||||
},
|
||||
"metrics_cache_refresh_interval": 0,
|
||||
"notifications": {
|
||||
"dispatch_timeout": 0,
|
||||
"email": {
|
||||
"auth": {
|
||||
"identity": "string",
|
||||
"password": "string",
|
||||
"password_file": "string",
|
||||
"username": "string"
|
||||
},
|
||||
"force_tls": true,
|
||||
"from": "string",
|
||||
"hello": "string",
|
||||
"smarthost": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"tls": {
|
||||
"ca_file": "string",
|
||||
"cert_file": "string",
|
||||
"insecure_skip_verify": true,
|
||||
"key_file": "string",
|
||||
"server_name": "string",
|
||||
"start_tls": true
|
||||
}
|
||||
},
|
||||
"fetch_interval": 0,
|
||||
"lease_count": 0,
|
||||
"lease_period": 0,
|
||||
"max_send_attempts": 0,
|
||||
"method": "string",
|
||||
"retry_interval": 0,
|
||||
"sync_buffer_size": 0,
|
||||
"sync_interval": 0,
|
||||
"webhook": {
|
||||
"endpoint": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"oauth2": {
|
||||
"github": {
|
||||
"allow_everyone": true,
|
||||
"allow_signups": true,
|
||||
"allowed_orgs": ["string"],
|
||||
"allowed_teams": ["string"],
|
||||
"client_id": "string",
|
||||
"client_secret": "string",
|
||||
"enterprise_base_url": "string"
|
||||
}
|
||||
},
|
||||
"oidc": {
|
||||
"allow_signups": true,
|
||||
"auth_url_params": {},
|
||||
"client_cert_file": "string",
|
||||
"client_id": "string",
|
||||
"client_key_file": "string",
|
||||
"client_secret": "string",
|
||||
"email_domain": ["string"],
|
||||
"email_field": "string",
|
||||
"group_allow_list": ["string"],
|
||||
"group_auto_create": true,
|
||||
"group_mapping": {},
|
||||
"group_regex_filter": {},
|
||||
"groups_field": "string",
|
||||
"icon_url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
},
|
||||
"ignore_email_verified": true,
|
||||
"ignore_user_info": true,
|
||||
"issuer_url": "string",
|
||||
"name_field": "string",
|
||||
"scopes": ["string"],
|
||||
"sign_in_text": "string",
|
||||
"signups_disabled_text": "string",
|
||||
"skip_issuer_checks": true,
|
||||
"user_role_field": "string",
|
||||
"user_role_mapping": {},
|
||||
"user_roles_default": ["string"],
|
||||
"username_field": "string"
|
||||
},
|
||||
"pg_auth": "string",
|
||||
"pg_connection_url": "string",
|
||||
"pprof": {
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"enable": true
|
||||
},
|
||||
"prometheus": {
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"aggregate_agent_stats_by": ["string"],
|
||||
"collect_agent_stats": true,
|
||||
"collect_db_metrics": true,
|
||||
"enable": true
|
||||
},
|
||||
"provisioner": {
|
||||
"daemon_poll_interval": 0,
|
||||
"daemon_poll_jitter": 0,
|
||||
"daemon_psk": "string",
|
||||
"daemon_types": ["string"],
|
||||
"daemons": 0,
|
||||
"force_cancel_interval": 0
|
||||
},
|
||||
"proxy_health_status_interval": 0,
|
||||
"proxy_trusted_headers": ["string"],
|
||||
"proxy_trusted_origins": ["string"],
|
||||
"rate_limit": {
|
||||
"api": 0,
|
||||
"disable_all": true
|
||||
},
|
||||
"redirect_to_access_url": true,
|
||||
"scim_api_key": "string",
|
||||
"secure_auth_cookie": true,
|
||||
"session_lifetime": {
|
||||
"default_duration": 0,
|
||||
"disable_expiry_refresh": true,
|
||||
"max_token_lifetime": 0
|
||||
},
|
||||
"ssh_keygen_algorithm": "string",
|
||||
"strict_transport_security": 0,
|
||||
"strict_transport_security_options": ["string"],
|
||||
"support": {
|
||||
"links": {
|
||||
"value": [
|
||||
{
|
||||
"icon": "bug",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"swagger": {
|
||||
"enable": true
|
||||
},
|
||||
"telemetry": {
|
||||
"enable": true,
|
||||
"trace": true,
|
||||
"url": {
|
||||
"forceQuery": true,
|
||||
"fragment": "string",
|
||||
"host": "string",
|
||||
"omitHost": true,
|
||||
"opaque": "string",
|
||||
"path": "string",
|
||||
"rawFragment": "string",
|
||||
"rawPath": "string",
|
||||
"rawQuery": "string",
|
||||
"scheme": "string",
|
||||
"user": {}
|
||||
}
|
||||
},
|
||||
"terms_of_service_url": "string",
|
||||
"tls": {
|
||||
"address": {
|
||||
"host": "string",
|
||||
"port": "string"
|
||||
},
|
||||
"allow_insecure_ciphers": true,
|
||||
"cert_file": ["string"],
|
||||
"client_auth": "string",
|
||||
"client_ca_file": "string",
|
||||
"client_cert_file": "string",
|
||||
"client_key_file": "string",
|
||||
"enable": true,
|
||||
"key_file": ["string"],
|
||||
"min_version": "string",
|
||||
"redirect_http": true,
|
||||
"supported_ciphers": ["string"]
|
||||
},
|
||||
"trace": {
|
||||
"capture_logs": true,
|
||||
"data_dog": true,
|
||||
"enable": true,
|
||||
"honeycomb_api_key": "string"
|
||||
},
|
||||
"update_check": true,
|
||||
"user_quiet_hours_schedule": {
|
||||
"allow_user_custom": true,
|
||||
"default_schedule": "string"
|
||||
},
|
||||
"verbose": true,
|
||||
"web_terminal_renderer": "string",
|
||||
"wgtunnel_host": "string",
|
||||
"wildcard_access_url": "string",
|
||||
"write_config": true
|
||||
},
|
||||
"options": [
|
||||
{
|
||||
"annotations": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
},
|
||||
"default": "string",
|
||||
"description": "string",
|
||||
"env": "string",
|
||||
"flag": "string",
|
||||
"flag_shorthand": "string",
|
||||
"group": {
|
||||
"description": "string",
|
||||
"name": "string",
|
||||
"parent": {
|
||||
"description": "string",
|
||||
"name": "string",
|
||||
"parent": {},
|
||||
"yaml": "string"
|
||||
},
|
||||
"yaml": "string"
|
||||
},
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"required": true,
|
||||
"use_instead": [{}],
|
||||
"value": null,
|
||||
"value_source": "",
|
||||
"yaml": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -525,11 +525,11 @@ curl -X GET http://coder-server:8080/api/v2/deployment/ssh \
|
||||
|
||||
```json
|
||||
{
|
||||
"hostname_prefix": "string",
|
||||
"ssh_config_options": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
}
|
||||
"hostname_prefix": "string",
|
||||
"ssh_config_options": {
|
||||
"property1": "string",
|
||||
"property2": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -560,28 +560,28 @@ curl -X GET http://coder-server:8080/api/v2/deployment/stats \
|
||||
|
||||
```json
|
||||
{
|
||||
"aggregated_from": "2019-08-24T14:15:22Z",
|
||||
"collected_at": "2019-08-24T14:15:22Z",
|
||||
"next_update_at": "2019-08-24T14:15:22Z",
|
||||
"session_count": {
|
||||
"jetbrains": 0,
|
||||
"reconnecting_pty": 0,
|
||||
"ssh": 0,
|
||||
"vscode": 0
|
||||
},
|
||||
"workspaces": {
|
||||
"building": 0,
|
||||
"connection_latency_ms": {
|
||||
"p50": 0,
|
||||
"p95": 0
|
||||
},
|
||||
"failed": 0,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"rx_bytes": 0,
|
||||
"stopped": 0,
|
||||
"tx_bytes": 0
|
||||
}
|
||||
"aggregated_from": "2019-08-24T14:15:22Z",
|
||||
"collected_at": "2019-08-24T14:15:22Z",
|
||||
"next_update_at": "2019-08-24T14:15:22Z",
|
||||
"session_count": {
|
||||
"jetbrains": 0,
|
||||
"reconnecting_pty": 0,
|
||||
"ssh": 0,
|
||||
"vscode": 0
|
||||
},
|
||||
"workspaces": {
|
||||
"building": 0,
|
||||
"connection_latency_ms": {
|
||||
"p50": 0,
|
||||
"p95": 0
|
||||
},
|
||||
"failed": 0,
|
||||
"pending": 0,
|
||||
"running": 0,
|
||||
"rx_bytes": 0,
|
||||
"stopped": 0,
|
||||
"tx_bytes": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -685,9 +685,9 @@ curl -X GET http://coder-server:8080/api/v2/updatecheck \
|
||||
|
||||
```json
|
||||
{
|
||||
"current": true,
|
||||
"url": "string",
|
||||
"version": "string"
|
||||
"current": true,
|
||||
"url": "string",
|
||||
"version": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -722,7 +722,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens/tokenconfig
|
||||
|
||||
```json
|
||||
{
|
||||
"max_token_lifetime": 0
|
||||
"max_token_lifetime": 0
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+37
-37
@@ -19,13 +19,13 @@ curl -X GET http://coder-server:8080/api/v2/external-auth \
|
||||
|
||||
```json
|
||||
{
|
||||
"authenticated": true,
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires": "2019-08-24T14:15:22Z",
|
||||
"has_refresh_token": true,
|
||||
"provider_id": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"validate_error": "string"
|
||||
"authenticated": true,
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires": "2019-08-24T14:15:22Z",
|
||||
"has_refresh_token": true,
|
||||
"provider_id": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"validate_error": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -62,31 +62,31 @@ curl -X GET http://coder-server:8080/api/v2/external-auth/{externalauth} \
|
||||
|
||||
```json
|
||||
{
|
||||
"app_install_url": "string",
|
||||
"app_installable": true,
|
||||
"authenticated": true,
|
||||
"device": true,
|
||||
"display_name": "string",
|
||||
"installations": [
|
||||
{
|
||||
"account": {
|
||||
"avatar_url": "string",
|
||||
"id": 0,
|
||||
"login": "string",
|
||||
"name": "string",
|
||||
"profile_url": "string"
|
||||
},
|
||||
"configure_url": "string",
|
||||
"id": 0
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"avatar_url": "string",
|
||||
"id": 0,
|
||||
"login": "string",
|
||||
"name": "string",
|
||||
"profile_url": "string"
|
||||
}
|
||||
"app_install_url": "string",
|
||||
"app_installable": true,
|
||||
"authenticated": true,
|
||||
"device": true,
|
||||
"display_name": "string",
|
||||
"installations": [
|
||||
{
|
||||
"account": {
|
||||
"avatar_url": "string",
|
||||
"id": 0,
|
||||
"login": "string",
|
||||
"name": "string",
|
||||
"profile_url": "string"
|
||||
},
|
||||
"configure_url": "string",
|
||||
"id": 0
|
||||
}
|
||||
],
|
||||
"user": {
|
||||
"avatar_url": "string",
|
||||
"id": 0,
|
||||
"login": "string",
|
||||
"name": "string",
|
||||
"profile_url": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -149,11 +149,11 @@ curl -X GET http://coder-server:8080/api/v2/external-auth/{externalauth}/device
|
||||
|
||||
```json
|
||||
{
|
||||
"device_code": "string",
|
||||
"expires_in": 0,
|
||||
"interval": 0,
|
||||
"user_code": "string",
|
||||
"verification_uri": "string"
|
||||
"device_code": "string",
|
||||
"expires_in": 0,
|
||||
"interval": 0,
|
||||
"user_code": "string",
|
||||
"verification_uri": "string"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+87
-87
@@ -25,13 +25,13 @@ curl -X GET http://coder-server:8080/api/v2/insights/daus?tz_offset=0 \
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"amount": 0,
|
||||
"date": "string"
|
||||
}
|
||||
],
|
||||
"tz_hour_offset": 0
|
||||
"entries": [
|
||||
{
|
||||
"amount": 0,
|
||||
"date": "string"
|
||||
}
|
||||
],
|
||||
"tz_hour_offset": 0
|
||||
}
|
||||
```
|
||||
|
||||
@@ -78,55 +78,55 @@ curl -X GET http://coder-server:8080/api/v2/insights/templates?start_time=2019-0
|
||||
|
||||
```json
|
||||
{
|
||||
"interval_reports": [
|
||||
{
|
||||
"active_users": 14,
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"interval": "week",
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
||||
}
|
||||
],
|
||||
"report": {
|
||||
"active_users": 22,
|
||||
"apps_usage": [
|
||||
{
|
||||
"display_name": "Visual Studio Code",
|
||||
"icon": "string",
|
||||
"seconds": 80500,
|
||||
"slug": "vscode",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"times_used": 2,
|
||||
"type": "builtin"
|
||||
}
|
||||
],
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"parameters_usage": [
|
||||
{
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"options": [
|
||||
{
|
||||
"description": "string",
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
],
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"type": "string",
|
||||
"values": [
|
||||
{
|
||||
"count": 0,
|
||||
"value": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
||||
}
|
||||
"interval_reports": [
|
||||
{
|
||||
"active_users": 14,
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"interval": "week",
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
||||
}
|
||||
],
|
||||
"report": {
|
||||
"active_users": 22,
|
||||
"apps_usage": [
|
||||
{
|
||||
"display_name": "Visual Studio Code",
|
||||
"icon": "string",
|
||||
"seconds": 80500,
|
||||
"slug": "vscode",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"times_used": 2,
|
||||
"type": "builtin"
|
||||
}
|
||||
],
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"parameters_usage": [
|
||||
{
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"options": [
|
||||
{
|
||||
"description": "string",
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
],
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"type": "string",
|
||||
"values": [
|
||||
{
|
||||
"count": 0,
|
||||
"value": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -165,20 +165,20 @@ curl -X GET http://coder-server:8080/api/v2/insights/user-activity?start_time=20
|
||||
|
||||
```json
|
||||
{
|
||||
"report": {
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"users": [
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"seconds": 80500,
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
"report": {
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"users": [
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"seconds": 80500,
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -217,23 +217,23 @@ curl -X GET http://coder-server:8080/api/v2/insights/user-latency?start_time=201
|
||||
|
||||
```json
|
||||
{
|
||||
"report": {
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"users": [
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"latency_ms": {
|
||||
"p50": 31.312,
|
||||
"p95": 119.832
|
||||
},
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
"report": {
|
||||
"end_time": "2019-08-24T14:15:22Z",
|
||||
"start_time": "2019-08-24T14:15:22Z",
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"users": [
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"latency_ms": {
|
||||
"p50": 31.312,
|
||||
"p95": 119.832
|
||||
},
|
||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+227
-227
@@ -25,30 +25,30 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"avatar_url": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "string",
|
||||
"global_roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
{
|
||||
"avatar_url": "string",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "string",
|
||||
"global_roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -106,34 +106,34 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -229,29 +229,29 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -268,32 +268,32 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -387,29 +387,29 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -426,32 +426,32 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -553,32 +553,32 @@ curl -X DELETE http://coder-server:8080/api/v2/organizations/{organization}/memb
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -680,17 +680,17 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -747,7 +747,7 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
```json
|
||||
{
|
||||
"roles": ["string"]
|
||||
"roles": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -765,17 +765,17 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -806,34 +806,34 @@ curl -X GET http://coder-server:8080/api/v2/users/roles \
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"assignable": true,
|
||||
"built_in": true,
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"organization_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"site_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
],
|
||||
"user_permissions": [
|
||||
{
|
||||
"action": "application_connect",
|
||||
"negate": true,
|
||||
"resource_type": "*"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
Generated
+31
-31
@@ -19,10 +19,10 @@ curl -X GET http://coder-server:8080/api/v2/notifications/dispatch-methods \
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"available": ["string"],
|
||||
"default": "string"
|
||||
}
|
||||
{
|
||||
"available": ["string"],
|
||||
"default": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -63,7 +63,7 @@ curl -X GET http://coder-server:8080/api/v2/notifications/settings \
|
||||
|
||||
```json
|
||||
{
|
||||
"notifier_paused": true
|
||||
"notifier_paused": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -93,7 +93,7 @@ curl -X PUT http://coder-server:8080/api/v2/notifications/settings \
|
||||
|
||||
```json
|
||||
{
|
||||
"notifier_paused": true
|
||||
"notifier_paused": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -109,7 +109,7 @@ curl -X PUT http://coder-server:8080/api/v2/notifications/settings \
|
||||
|
||||
```json
|
||||
{
|
||||
"notifier_paused": true
|
||||
"notifier_paused": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -141,16 +141,16 @@ curl -X GET http://coder-server:8080/api/v2/notifications/templates/system \
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"actions": "string",
|
||||
"body_template": "string",
|
||||
"group": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"kind": "string",
|
||||
"method": "string",
|
||||
"name": "string",
|
||||
"title_template": "string"
|
||||
}
|
||||
{
|
||||
"actions": "string",
|
||||
"body_template": "string",
|
||||
"group": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"kind": "string",
|
||||
"method": "string",
|
||||
"name": "string",
|
||||
"title_template": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -203,11 +203,11 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/notifications/preferenc
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"disabled": true,
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
{
|
||||
"disabled": true,
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -248,10 +248,10 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/notifications/preferenc
|
||||
|
||||
```json
|
||||
{
|
||||
"template_disabled_map": {
|
||||
"property1": true,
|
||||
"property2": true
|
||||
}
|
||||
"template_disabled_map": {
|
||||
"property1": true,
|
||||
"property2": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -268,11 +268,11 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/notifications/preferenc
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"disabled": true,
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
{
|
||||
"disabled": true,
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
Generated
+63
-63
@@ -18,7 +18,7 @@ curl -X POST http://coder-server:8080/api/v2/licenses \
|
||||
|
||||
```json
|
||||
{
|
||||
"license": "string"
|
||||
"license": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -34,10 +34,10 @@ curl -X POST http://coder-server:8080/api/v2/licenses \
|
||||
|
||||
```json
|
||||
{
|
||||
"claims": {},
|
||||
"id": 0,
|
||||
"uploaded_at": "2019-08-24T14:15:22Z",
|
||||
"uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f"
|
||||
"claims": {},
|
||||
"id": 0,
|
||||
"uploaded_at": "2019-08-24T14:15:22Z",
|
||||
"uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -68,14 +68,14 @@ curl -X POST http://coder-server:8080/api/v2/licenses/refresh-entitlements \
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -106,16 +106,16 @@ curl -X GET http://coder-server:8080/api/v2/organizations \
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -161,10 +161,10 @@ curl -X POST http://coder-server:8080/api/v2/organizations \
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"name": "string"
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"name": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -180,14 +180,14 @@ curl -X POST http://coder-server:8080/api/v2/organizations \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -224,14 +224,14 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization} \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -268,14 +268,14 @@ curl -X DELETE http://coder-server:8080/api/v2/organizations/{organization} \
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -305,10 +305,10 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
|
||||
|
||||
```json
|
||||
{
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"name": "string"
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"name": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -325,14 +325,14 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+11
-11
@@ -17,8 +17,8 @@ curl -X DELETE http://coder-server:8080/api/v2/workspaces/{workspace}/port-share
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_name": "string",
|
||||
"port": 0
|
||||
"agent_name": "string",
|
||||
"port": 0
|
||||
}
|
||||
```
|
||||
|
||||
@@ -55,10 +55,10 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/port-share \
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_name": "string",
|
||||
"port": 0,
|
||||
"protocol": "http",
|
||||
"share_level": "owner"
|
||||
"agent_name": "string",
|
||||
"port": 0,
|
||||
"protocol": "http",
|
||||
"share_level": "owner"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -75,11 +75,11 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/port-share \
|
||||
|
||||
```json
|
||||
{
|
||||
"agent_name": "string",
|
||||
"port": 0,
|
||||
"protocol": "http",
|
||||
"share_level": "owner",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
|
||||
"agent_name": "string",
|
||||
"port": 0,
|
||||
"protocol": "http",
|
||||
"share_level": "owner",
|
||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+4379
-4379
File diff suppressed because it is too large
Load Diff
Generated
+951
-951
File diff suppressed because it is too large
Load Diff
Generated
+301
-301
@@ -28,30 +28,30 @@ curl -X GET http://coder-server:8080/api/v2/users \
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 0,
|
||||
"users": [
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
"count": 0,
|
||||
"users": [
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -81,13 +81,13 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
||||
|
||||
```json
|
||||
{
|
||||
"disable_login": true,
|
||||
"email": "user@example.com",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"password": "string",
|
||||
"username": "string"
|
||||
"disable_login": true,
|
||||
"email": "user@example.com",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"password": "string",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -103,25 +103,25 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -152,18 +152,18 @@ curl -X GET http://coder-server:8080/api/v2/users/authmethods \
|
||||
|
||||
```json
|
||||
{
|
||||
"github": {
|
||||
"enabled": true
|
||||
},
|
||||
"oidc": {
|
||||
"enabled": true,
|
||||
"iconUrl": "string",
|
||||
"signInText": "string"
|
||||
},
|
||||
"password": {
|
||||
"enabled": true
|
||||
},
|
||||
"terms_of_service_url": "string"
|
||||
"github": {
|
||||
"enabled": true
|
||||
},
|
||||
"oidc": {
|
||||
"enabled": true,
|
||||
"iconUrl": "string",
|
||||
"signInText": "string"
|
||||
},
|
||||
"password": {
|
||||
"enabled": true
|
||||
},
|
||||
"terms_of_service_url": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -194,14 +194,14 @@ curl -X GET http://coder-server:8080/api/v2/users/first \
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -231,20 +231,20 @@ curl -X POST http://coder-server:8080/api/v2/users/first \
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"password": "string",
|
||||
"trial": true,
|
||||
"trial_info": {
|
||||
"company_name": "string",
|
||||
"country": "string",
|
||||
"developers": "string",
|
||||
"first_name": "string",
|
||||
"job_title": "string",
|
||||
"last_name": "string",
|
||||
"phone_number": "string"
|
||||
},
|
||||
"username": "string"
|
||||
"email": "string",
|
||||
"name": "string",
|
||||
"password": "string",
|
||||
"trial": true,
|
||||
"trial_info": {
|
||||
"company_name": "string",
|
||||
"country": "string",
|
||||
"developers": "string",
|
||||
"first_name": "string",
|
||||
"job_title": "string",
|
||||
"last_name": "string",
|
||||
"phone_number": "string"
|
||||
},
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -260,8 +260,8 @@ curl -X POST http://coder-server:8080/api/v2/users/first \
|
||||
|
||||
```json
|
||||
{
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -292,14 +292,14 @@ curl -X POST http://coder-server:8080/api/v2/users/logout \
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
"detail": "string",
|
||||
"message": "string",
|
||||
"validations": [
|
||||
{
|
||||
"detail": "string",
|
||||
"field": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -376,25 +376,25 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -450,7 +450,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/appearance \
|
||||
|
||||
```json
|
||||
{
|
||||
"theme_preference": "string"
|
||||
"theme_preference": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -467,25 +467,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/appearance \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -523,10 +523,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/autofill-parameters?tem
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
{
|
||||
"name": "string",
|
||||
"value": "string"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -573,10 +573,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/gitsshkey \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"public_key": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"public_key": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -613,10 +613,10 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/gitsshkey \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"public_key": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"public_key": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -653,7 +653,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/keys \
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "string"
|
||||
"key": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -690,18 +690,18 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
"last_used": "2019-08-24T14:15:22Z",
|
||||
"lifetime_seconds": 0,
|
||||
"login_type": "password",
|
||||
"scope": "all",
|
||||
"token_name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
"last_used": "2019-08-24T14:15:22Z",
|
||||
"lifetime_seconds": 0,
|
||||
"login_type": "password",
|
||||
"scope": "all",
|
||||
"token_name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -760,9 +760,9 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
||||
|
||||
```json
|
||||
{
|
||||
"lifetime": 0,
|
||||
"scope": "all",
|
||||
"token_name": "string"
|
||||
"lifetime": 0,
|
||||
"scope": "all",
|
||||
"token_name": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -779,7 +779,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "string"
|
||||
"key": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -817,16 +817,16 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens/{keyname} \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
"last_used": "2019-08-24T14:15:22Z",
|
||||
"lifetime_seconds": 0,
|
||||
"login_type": "password",
|
||||
"scope": "all",
|
||||
"token_name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
"last_used": "2019-08-24T14:15:22Z",
|
||||
"lifetime_seconds": 0,
|
||||
"login_type": "password",
|
||||
"scope": "all",
|
||||
"token_name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -864,16 +864,16 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/{keyid} \
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
"last_used": "2019-08-24T14:15:22Z",
|
||||
"lifetime_seconds": 0,
|
||||
"login_type": "password",
|
||||
"scope": "all",
|
||||
"token_name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"expires_at": "2019-08-24T14:15:22Z",
|
||||
"id": "string",
|
||||
"last_used": "2019-08-24T14:15:22Z",
|
||||
"lifetime_seconds": 0,
|
||||
"login_type": "password",
|
||||
"scope": "all",
|
||||
"token_name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -937,7 +937,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/login-type \
|
||||
|
||||
```json
|
||||
{
|
||||
"login_type": ""
|
||||
"login_type": ""
|
||||
}
|
||||
```
|
||||
|
||||
@@ -974,16 +974,16 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/organizations \
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -1037,14 +1037,14 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/organizations/{organiza
|
||||
|
||||
```json
|
||||
{
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"description": "string",
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"is_default": true,
|
||||
"name": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1073,8 +1073,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/password \
|
||||
|
||||
```json
|
||||
{
|
||||
"old_password": "string",
|
||||
"password": "string"
|
||||
"old_password": "string",
|
||||
"password": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1111,8 +1111,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
"name": "string",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1129,25 +1129,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1184,25 +1184,25 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/roles \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1232,7 +1232,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
|
||||
|
||||
```json
|
||||
{
|
||||
"roles": ["string"]
|
||||
"roles": ["string"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1249,25 +1249,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1304,25 +1304,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/activate \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1359,25 +1359,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/suspend \
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
"avatar_url": "http://example.com",
|
||||
"created_at": "2019-08-24T14:15:22Z",
|
||||
"email": "user@example.com",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||
"login_type": "",
|
||||
"name": "string",
|
||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||
"roles": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"name": "string",
|
||||
"organization_id": "string"
|
||||
}
|
||||
],
|
||||
"status": "active",
|
||||
"theme_preference": "string",
|
||||
"updated_at": "2019-08-24T14:15:22Z",
|
||||
"username": "string"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+11
-11
@@ -19,17 +19,17 @@ curl -X GET http://coder-server:8080/api/v2/regions \
|
||||
|
||||
```json
|
||||
{
|
||||
"regions": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"path_app_url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
]
|
||||
"regions": [
|
||||
{
|
||||
"display_name": "string",
|
||||
"healthy": true,
|
||||
"icon_url": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string",
|
||||
"path_app_url": "string",
|
||||
"wildcard_hostname": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Generated
+1104
-1104
File diff suppressed because it is too large
Load Diff
Vendored
+22
-22
@@ -254,28 +254,28 @@ The raw logs will look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"ts": "2022-02-28T20:29:38.038452202Z",
|
||||
"level": "INFO",
|
||||
"msg": "exec",
|
||||
"fields": {
|
||||
"labels": {
|
||||
"user_email": "jessie@coder.com",
|
||||
"user_id": "5e876e9a-121663f01ebd1522060d5270",
|
||||
"username": "jessie",
|
||||
"workspace_id": "621d2e52-a6987ef6c56210058ee2593c",
|
||||
"workspace_name": "main"
|
||||
},
|
||||
"cmdline": "uname -a",
|
||||
"event": {
|
||||
"filename": "/usr/bin/uname",
|
||||
"argv": ["uname", "-a"],
|
||||
"truncated": false,
|
||||
"pid": 920684,
|
||||
"uid": 101000,
|
||||
"gid": 101000,
|
||||
"comm": "bash"
|
||||
}
|
||||
}
|
||||
"ts": "2022-02-28T20:29:38.038452202Z",
|
||||
"level": "INFO",
|
||||
"msg": "exec",
|
||||
"fields": {
|
||||
"labels": {
|
||||
"user_email": "jessie@coder.com",
|
||||
"user_id": "5e876e9a-121663f01ebd1522060d5270",
|
||||
"username": "jessie",
|
||||
"workspace_id": "621d2e52-a6987ef6c56210058ee2593c",
|
||||
"workspace_name": "main"
|
||||
},
|
||||
"cmdline": "uname -a",
|
||||
"event": {
|
||||
"filename": "/usr/bin/uname",
|
||||
"argv": ["uname", "-a"],
|
||||
"truncated": false,
|
||||
"pid": 920684,
|
||||
"uid": 101000,
|
||||
"gid": 101000,
|
||||
"comm": "bash"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "Develop Coder on Coder using Envbuilder",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"name": "Develop Coder on Coder using Envbuilder",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
|
||||
"features": {},
|
||||
"runArgs": ["--cap-add=SYS_PTRACE"]
|
||||
"features": {},
|
||||
"runArgs": ["--cap-add=SYS_PTRACE"]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"registry-mirrors": ["https://mirror.gcr.io"]
|
||||
"registry-mirrors": ["https://mirror.gcr.io"]
|
||||
}
|
||||
|
||||
+166
-166
@@ -1,169 +1,169 @@
|
||||
// Code generated by examplegen. DO NOT EDIT.
|
||||
[
|
||||
{
|
||||
"id": "aws-devcontainer",
|
||||
"url": "",
|
||||
"name": "AWS EC2 (Devcontainer)",
|
||||
"description": "Provision AWS EC2 VMs with a devcontainer as Coder workspaces",
|
||||
"icon": "/icon/aws.svg",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"aws",
|
||||
"persistent",
|
||||
"devcontainer"
|
||||
],
|
||||
"markdown": "\n# Remote Development on AWS EC2 VMs using a Devcontainer\n\nProvision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs) with this example template.\n\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"VisualEditor0\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:GetDefaultCreditSpecification\",\n \"ec2:DescribeIamInstanceProfileAssociations\",\n \"ec2:DescribeTags\",\n \"ec2:DescribeInstances\",\n \"ec2:DescribeInstanceTypes\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:DescribeInstanceCreditSpecifications\",\n \"ec2:DescribeImages\",\n \"ec2:ModifyDefaultCreditSpecification\",\n \"ec2:DescribeVolumes\"\n ],\n \"Resource\": \"*\"\n },\n {\n \"Sid\": \"CoderResources\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:DescribeInstanceAttribute\",\n \"ec2:UnmonitorInstances\",\n \"ec2:TerminateInstances\",\n \"ec2:StartInstances\",\n \"ec2:StopInstances\",\n \"ec2:DeleteTags\",\n \"ec2:MonitorInstances\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:ModifyInstanceAttribute\",\n \"ec2:ModifyInstanceCreditSpecification\"\n ],\n \"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n \"Condition\": {\n \"StringEquals\": {\n \"aws:ResourceTag/Coder_Provisioned\": \"true\"\n }\n }\n }\n ]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. For a list of all modules and templates pplease check [Coder Registry](https://registry.coder.com).\n"
|
||||
},
|
||||
{
|
||||
"id": "aws-linux",
|
||||
"url": "",
|
||||
"name": "AWS EC2 (Linux)",
|
||||
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
||||
"icon": "/icon/aws.svg",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"aws",
|
||||
"persistent-vm"
|
||||
],
|
||||
"markdown": "\n# Remote Development on AWS EC2 VMs (Linux)\n\nProvision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"VisualEditor0\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:GetDefaultCreditSpecification\",\n \"ec2:DescribeIamInstanceProfileAssociations\",\n \"ec2:DescribeTags\",\n \"ec2:DescribeInstances\",\n \"ec2:DescribeInstanceTypes\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:DescribeInstanceCreditSpecifications\",\n \"ec2:DescribeImages\",\n \"ec2:ModifyDefaultCreditSpecification\",\n \"ec2:DescribeVolumes\"\n ],\n \"Resource\": \"*\"\n },\n {\n \"Sid\": \"CoderResources\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:DescribeInstanceAttribute\",\n \"ec2:UnmonitorInstances\",\n \"ec2:TerminateInstances\",\n \"ec2:StartInstances\",\n \"ec2:StopInstances\",\n \"ec2:DeleteTags\",\n \"ec2:MonitorInstances\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:ModifyInstanceAttribute\",\n \"ec2:ModifyInstanceCreditSpecification\"\n ],\n \"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n \"Condition\": {\n \"StringEquals\": {\n \"aws:ResourceTag/Coder_Provisioned\": \"true\"\n }\n }\n }\n ]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "aws-windows",
|
||||
"url": "",
|
||||
"name": "AWS EC2 (Windows)",
|
||||
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
||||
"icon": "/icon/aws.svg",
|
||||
"tags": [
|
||||
"vm",
|
||||
"windows",
|
||||
"aws"
|
||||
],
|
||||
"markdown": "\n# Remote Development on AWS EC2 VMs (Windows)\n\nProvision AWS EC2 Windows VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS with using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"VisualEditor0\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:GetDefaultCreditSpecification\",\n \"ec2:DescribeIamInstanceProfileAssociations\",\n \"ec2:DescribeTags\",\n \"ec2:DescribeInstances\",\n \"ec2:DescribeInstanceTypes\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:DescribeInstanceCreditSpecifications\",\n \"ec2:DescribeImages\",\n \"ec2:ModifyDefaultCreditSpecification\",\n \"ec2:DescribeVolumes\"\n ],\n \"Resource\": \"*\"\n },\n {\n \"Sid\": \"CoderResources\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ec2:DescribeInstanceAttribute\",\n \"ec2:UnmonitorInstances\",\n \"ec2:TerminateInstances\",\n \"ec2:StartInstances\",\n \"ec2:StopInstances\",\n \"ec2:DeleteTags\",\n \"ec2:MonitorInstances\",\n \"ec2:CreateTags\",\n \"ec2:RunInstances\",\n \"ec2:ModifyInstanceAttribute\",\n \"ec2:ModifyInstanceCreditSpecification\"\n ],\n \"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n \"Condition\": {\n \"StringEquals\": {\n \"aws:ResourceTag/Coder_Provisioned\": \"true\"\n }\n }\n }\n ]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "azure-linux",
|
||||
"url": "",
|
||||
"name": "Azure VM (Linux)",
|
||||
"description": "Provision Azure VMs as Coder workspaces",
|
||||
"icon": "/icon/azure.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"azure"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Azure VMs (Linux)\n\nProvision Azure Linux VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Azure. For example, run `az login` then `az account set --subscription=\u003cid\u003e`\nto import credentials on the system and user running coderd. For other ways to\nauthenticate, [consult the Terraform docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure).\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Azure VM (ephemeral, deleted on stop)\n- Managed disk (persistent, mounted to `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script). Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "do-linux",
|
||||
"url": "",
|
||||
"name": "DigitalOcean Droplet (Linux)",
|
||||
"description": "Provision DigitalOcean Droplets as Coder workspaces",
|
||||
"icon": "/icon/do.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"digitalocean"
|
||||
],
|
||||
"markdown": "\n# Remote Development on DigitalOcean Droplets\n\nProvision DigitalOcean Droplets as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\nTo deploy workspaces as DigitalOcean Droplets, you'll need:\n\n- DigitalOcean [personal access token (PAT)](https://docs.digitalocean.com/reference/api/create-personal-access-token/)\n\n- DigitalOcean project ID (you can get your project information via the `doctl`\n CLI by running `doctl projects list`)\n\n- Remove the following sections from the `main.tf` file if you don't want to\n associate your workspaces with a project:\n\n - `variable \"step2_do_project_id\"`\n - `resource \"digitalocean_project_resources\" \"project\"`\n\n- **Optional:** DigitalOcean SSH key ID (obtain via the `doctl` CLI by running\n `doctl compute ssh-key list`)\n\n- Note that this is only required for Fedora images to work.\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Digital Ocean. Obtain a [Digital Ocean Personal Access\nToken](https://cloud.digitalocean.com/account/api/tokens) and set the\nenvironment variable `DIGITALOCEAN_TOKEN` to the access token before starting\ncoderd. For other ways to authenticate [consult the Terraform docs](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs).\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Azure VM (ephemeral, deleted on stop)\n- Managed disk (persistent, mounted to `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script).\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n"
|
||||
},
|
||||
{
|
||||
"id": "docker",
|
||||
"url": "",
|
||||
"name": "Docker Containers",
|
||||
"description": "Provision Docker containers as Coder workspaces",
|
||||
"icon": "/icon/docker.png",
|
||||
"tags": [
|
||||
"docker",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Docker Containers\n\nProvision Docker containers as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Infrastructure\n\nThe VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:\n\n```sh\n# Add coder user to Docker group\nsudo adduser coder docker\n\n# Restart Coder server\nsudo systemctl restart coder\n\n# Test Docker\nsudo -u coder docker ps\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Docker image (built by Docker socket and kept locally)\n- Docker container pod (ephemeral)\n- Docker volume (persistent on `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n### Editing the image\n\nEdit the `Dockerfile` and run `coder templates push` to update workspaces.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-devcontainer",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (Devcontainer)",
|
||||
"description": "Provision a Devcontainer on Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"gcp",
|
||||
"devcontainer"
|
||||
],
|
||||
"markdown": "\n# Remote Development in a Devcontainer on Google Compute Engine\n\n\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (persistent)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. Please check [Coder Registry](https://registry.coder.com) for a list of all modules and templates.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-linux",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (Linux)",
|
||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"gcp"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Google Compute Engine (Linux)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (ephemeral)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-vm-container",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (VM Container)",
|
||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm-container",
|
||||
"linux",
|
||||
"gcp"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Google Compute Engine (VM Container)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (ephemeral, deleted on stop)\n - Container in VM\n- Managed disk (persistent, mounted to `/home/coder` in container)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script).\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-windows",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (Windows)",
|
||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"windows",
|
||||
"gcp"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Google Compute Engine (Windows)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (ephemeral)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "kubernetes",
|
||||
"url": "",
|
||||
"name": "Kubernetes (Deployment)",
|
||||
"description": "Provision Kubernetes Deployments as Coder workspaces",
|
||||
"icon": "/icon/k8s.png",
|
||||
"tags": [
|
||||
"kubernetes",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Kubernetes Pods\n\nProvision Kubernetes Pods as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Infrastructure\n\n**Cluster**: This template requires an existing Kubernetes cluster\n\n**Container Image**: This template uses the [codercom/enterprise-base:ubuntu image](https://github.com/coder/enterprise-images/tree/main/images/base) with some dev tools preinstalled. To add additional tools, extend this image or build it yourself.\n\n### Authentication\n\nThis template authenticates using a `~/.kube/config`, if present on the server, or via built-in authentication if the Coder provisioner is running on Kubernetes with an authorized ServiceAccount. To use another [authentication method](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication), edit the template.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Kubernetes pod (ephemeral)\n- Kubernetes persistent volume claim (persistent on `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n"
|
||||
},
|
||||
{
|
||||
"id": "nomad-docker",
|
||||
"url": "",
|
||||
"name": "Nomad",
|
||||
"description": "Provision Nomad Jobs as Coder workspaces",
|
||||
"icon": "/icon/nomad.svg",
|
||||
"tags": [
|
||||
"nomad",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Nomad\n\nProvision Nomad Jobs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. This example shows how to use Nomad service tasks to be used as a development environment using docker and host csi volumes.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Prerequisites\n\n- [Nomad](https://www.nomadproject.io/downloads)\n- [Docker](https://docs.docker.com/get-docker/)\n\n## Setup\n\n### 1. Start the CSI Host Volume Plugin\n\nThe CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This is useful for development environments where you want to mount persistent volumes into your container workspace.\n\n1. Login to the Nomad server using SSH.\n\n2. Append the following stanza to your Nomad server configuration file and restart the nomad service.\n\n ```hcl\n plugin \"docker\" {\n config {\n allow_privileged = true\n }\n }\n ```\n\n ```shell\n sudo systemctl restart nomad\n ```\n\n3. Create a file `hostpath.nomad` with following content:\n\n ```hcl\n job \"hostpath-csi-plugin\" {\n datacenters = [\"dc1\"]\n type = \"system\"\n\n group \"csi\" {\n task \"plugin\" {\n driver = \"docker\"\n\n config {\n image = \"registry.k8s.io/sig-storage/hostpathplugin:v1.10.0\"\n\n args = [\n \"--drivername=csi-hostpath\",\n \"--v=5\",\n \"--endpoint=${CSI_ENDPOINT}\",\n \"--nodeid=node-${NOMAD_ALLOC_INDEX}\",\n ]\n\n privileged = true\n }\n\n csi_plugin {\n id = \"hostpath\"\n type = \"monolith\"\n mount_dir = \"/csi\"\n }\n\n resources {\n cpu = 256\n memory = 128\n }\n }\n }\n }\n ```\n\n4. Run the job:\n\n ```shell\n nomad job run hostpath.nomad\n ```\n\n### 2. Setup the Nomad Template\n\n1. Create the template by running the following command:\n\n ```shell\n coder template init nomad-docker\n cd nomad-docker\n coder template push\n ```\n\n2. Set up Nomad server address and optional authentication:\n\n3. Create a new workspace and start developing.\n"
|
||||
},
|
||||
{
|
||||
"id": "scratch",
|
||||
"url": "",
|
||||
"name": "Scratch",
|
||||
"description": "A minimal starter template for Coder",
|
||||
"icon": "/emojis/1f4e6.png",
|
||||
"tags": [],
|
||||
"markdown": "\n# A minimal Scaffolding for a Coder Template\n\nUse this starter template as a basis to create your own unique template from scratch.\n"
|
||||
}
|
||||
{
|
||||
"id": "aws-devcontainer",
|
||||
"url": "",
|
||||
"name": "AWS EC2 (Devcontainer)",
|
||||
"description": "Provision AWS EC2 VMs with a devcontainer as Coder workspaces",
|
||||
"icon": "/icon/aws.svg",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"aws",
|
||||
"persistent",
|
||||
"devcontainer"
|
||||
],
|
||||
"markdown": "\n# Remote Development on AWS EC2 VMs using a Devcontainer\n\nProvision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs) with this example template.\n\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Sid\": \"VisualEditor0\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:GetDefaultCreditSpecification\",\n\t\t\t\t\"ec2:DescribeIamInstanceProfileAssociations\",\n\t\t\t\t\"ec2:DescribeTags\",\n\t\t\t\t\"ec2:DescribeInstances\",\n\t\t\t\t\"ec2:DescribeInstanceTypes\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:DescribeInstanceCreditSpecifications\",\n\t\t\t\t\"ec2:DescribeImages\",\n\t\t\t\t\"ec2:ModifyDefaultCreditSpecification\",\n\t\t\t\t\"ec2:DescribeVolumes\"\n\t\t\t],\n\t\t\t\"Resource\": \"*\"\n\t\t},\n\t\t{\n\t\t\t\"Sid\": \"CoderResources\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:DescribeInstanceAttribute\",\n\t\t\t\t\"ec2:UnmonitorInstances\",\n\t\t\t\t\"ec2:TerminateInstances\",\n\t\t\t\t\"ec2:StartInstances\",\n\t\t\t\t\"ec2:StopInstances\",\n\t\t\t\t\"ec2:DeleteTags\",\n\t\t\t\t\"ec2:MonitorInstances\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:ModifyInstanceAttribute\",\n\t\t\t\t\"ec2:ModifyInstanceCreditSpecification\"\n\t\t\t],\n\t\t\t\"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n\t\t\t\"Condition\": {\n\t\t\t\t\"StringEquals\": {\n\t\t\t\t\t\"aws:ResourceTag/Coder_Provisioned\": \"true\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. For a list of all modules and templates pplease check [Coder Registry](https://registry.coder.com).\n"
|
||||
},
|
||||
{
|
||||
"id": "aws-linux",
|
||||
"url": "",
|
||||
"name": "AWS EC2 (Linux)",
|
||||
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
||||
"icon": "/icon/aws.svg",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"aws",
|
||||
"persistent-vm"
|
||||
],
|
||||
"markdown": "\n# Remote Development on AWS EC2 VMs (Linux)\n\nProvision AWS EC2 VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Sid\": \"VisualEditor0\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:GetDefaultCreditSpecification\",\n\t\t\t\t\"ec2:DescribeIamInstanceProfileAssociations\",\n\t\t\t\t\"ec2:DescribeTags\",\n\t\t\t\t\"ec2:DescribeInstances\",\n\t\t\t\t\"ec2:DescribeInstanceTypes\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:DescribeInstanceCreditSpecifications\",\n\t\t\t\t\"ec2:DescribeImages\",\n\t\t\t\t\"ec2:ModifyDefaultCreditSpecification\",\n\t\t\t\t\"ec2:DescribeVolumes\"\n\t\t\t],\n\t\t\t\"Resource\": \"*\"\n\t\t},\n\t\t{\n\t\t\t\"Sid\": \"CoderResources\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:DescribeInstanceAttribute\",\n\t\t\t\t\"ec2:UnmonitorInstances\",\n\t\t\t\t\"ec2:TerminateInstances\",\n\t\t\t\t\"ec2:StartInstances\",\n\t\t\t\t\"ec2:StopInstances\",\n\t\t\t\t\"ec2:DeleteTags\",\n\t\t\t\t\"ec2:MonitorInstances\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:ModifyInstanceAttribute\",\n\t\t\t\t\"ec2:ModifyInstanceCreditSpecification\"\n\t\t\t],\n\t\t\t\"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n\t\t\t\"Condition\": {\n\t\t\t\t\"StringEquals\": {\n\t\t\t\t\t\"aws:ResourceTag/Coder_Provisioned\": \"true\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "aws-windows",
|
||||
"url": "",
|
||||
"name": "AWS EC2 (Windows)",
|
||||
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
||||
"icon": "/icon/aws.svg",
|
||||
"tags": [
|
||||
"vm",
|
||||
"windows",
|
||||
"aws"
|
||||
],
|
||||
"markdown": "\n# Remote Development on AWS EC2 VMs (Windows)\n\nProvision AWS EC2 Windows VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nBy default, this template authenticates to AWS with using the provider's default [authentication methods](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration).\n\nThe simplest way (without making changes to the template) is via environment variables (e.g. `AWS_ACCESS_KEY_ID`) or a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-format). If you are running Coder on a VM, this file must be in `/home/coder/aws/credentials`.\n\nTo use another [authentication method](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication), edit the template.\n\n## Required permissions / policy\n\nThe following sample policy allows Coder to create EC2 instances and modify\ninstances provisioned by Coder:\n\n```json\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Sid\": \"VisualEditor0\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:GetDefaultCreditSpecification\",\n\t\t\t\t\"ec2:DescribeIamInstanceProfileAssociations\",\n\t\t\t\t\"ec2:DescribeTags\",\n\t\t\t\t\"ec2:DescribeInstances\",\n\t\t\t\t\"ec2:DescribeInstanceTypes\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:DescribeInstanceCreditSpecifications\",\n\t\t\t\t\"ec2:DescribeImages\",\n\t\t\t\t\"ec2:ModifyDefaultCreditSpecification\",\n\t\t\t\t\"ec2:DescribeVolumes\"\n\t\t\t],\n\t\t\t\"Resource\": \"*\"\n\t\t},\n\t\t{\n\t\t\t\"Sid\": \"CoderResources\",\n\t\t\t\"Effect\": \"Allow\",\n\t\t\t\"Action\": [\n\t\t\t\t\"ec2:DescribeInstanceAttribute\",\n\t\t\t\t\"ec2:UnmonitorInstances\",\n\t\t\t\t\"ec2:TerminateInstances\",\n\t\t\t\t\"ec2:StartInstances\",\n\t\t\t\t\"ec2:StopInstances\",\n\t\t\t\t\"ec2:DeleteTags\",\n\t\t\t\t\"ec2:MonitorInstances\",\n\t\t\t\t\"ec2:CreateTags\",\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:ModifyInstanceAttribute\",\n\t\t\t\t\"ec2:ModifyInstanceCreditSpecification\"\n\t\t\t],\n\t\t\t\"Resource\": \"arn:aws:ec2:*:*:instance/*\",\n\t\t\t\"Condition\": {\n\t\t\t\t\"StringEquals\": {\n\t\t\t\t\t\"aws:ResourceTag/Coder_Provisioned\": \"true\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n}\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- AWS Instance\n\nCoder uses `aws_ec2_instance_state` to start and stop the VM. This example template is fully persistent, meaning the full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "azure-linux",
|
||||
"url": "",
|
||||
"name": "Azure VM (Linux)",
|
||||
"description": "Provision Azure VMs as Coder workspaces",
|
||||
"icon": "/icon/azure.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"azure"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Azure VMs (Linux)\n\nProvision Azure Linux VMs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Azure. For example, run `az login` then `az account set --subscription=\u003cid\u003e`\nto import credentials on the system and user running coderd. For other ways to\nauthenticate, [consult the Terraform docs](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure).\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Azure VM (ephemeral, deleted on stop)\n- Managed disk (persistent, mounted to `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script). Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "do-linux",
|
||||
"url": "",
|
||||
"name": "DigitalOcean Droplet (Linux)",
|
||||
"description": "Provision DigitalOcean Droplets as Coder workspaces",
|
||||
"icon": "/icon/do.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"digitalocean"
|
||||
],
|
||||
"markdown": "\n# Remote Development on DigitalOcean Droplets\n\nProvision DigitalOcean Droplets as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\nTo deploy workspaces as DigitalOcean Droplets, you'll need:\n\n- DigitalOcean [personal access token (PAT)](https://docs.digitalocean.com/reference/api/create-personal-access-token/)\n\n- DigitalOcean project ID (you can get your project information via the `doctl`\n CLI by running `doctl projects list`)\n\n- Remove the following sections from the `main.tf` file if you don't want to\n associate your workspaces with a project:\n\n - `variable \"step2_do_project_id\"`\n - `resource \"digitalocean_project_resources\" \"project\"`\n\n- **Optional:** DigitalOcean SSH key ID (obtain via the `doctl` CLI by running\n `doctl compute ssh-key list`)\n\n- Note that this is only required for Fedora images to work.\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Digital Ocean. Obtain a [Digital Ocean Personal Access\nToken](https://cloud.digitalocean.com/account/api/tokens) and set the\nenvironment variable `DIGITALOCEAN_TOKEN` to the access token before starting\ncoderd. For other ways to authenticate [consult the Terraform docs](https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs).\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Azure VM (ephemeral, deleted on stop)\n- Managed disk (persistent, mounted to `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the VM image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script).\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n"
|
||||
},
|
||||
{
|
||||
"id": "docker",
|
||||
"url": "",
|
||||
"name": "Docker Containers",
|
||||
"description": "Provision Docker containers as Coder workspaces",
|
||||
"icon": "/icon/docker.png",
|
||||
"tags": [
|
||||
"docker",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Docker Containers\n\nProvision Docker containers as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Infrastructure\n\nThe VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:\n\n```sh\n# Add coder user to Docker group\nsudo adduser coder docker\n\n# Restart Coder server\nsudo systemctl restart coder\n\n# Test Docker\nsudo -u coder docker ps\n```\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Docker image (built by Docker socket and kept locally)\n- Docker container pod (ephemeral)\n- Docker volume (persistent on `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n### Editing the image\n\nEdit the `Dockerfile` and run `coder templates push` to update workspaces.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-devcontainer",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (Devcontainer)",
|
||||
"description": "Provision a Devcontainer on Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"gcp",
|
||||
"devcontainer"
|
||||
],
|
||||
"markdown": "\n# Remote Development in a Devcontainer on Google Compute Engine\n\n\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (persistent)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the [`code-server`](https://registry.coder.com/modules/code-server) registry module. Please check [Coder Registry](https://registry.coder.com) for a list of all modules and templates.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-linux",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (Linux)",
|
||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"linux",
|
||||
"gcp"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Google Compute Engine (Linux)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (ephemeral)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-vm-container",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (VM Container)",
|
||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm-container",
|
||||
"linux",
|
||||
"gcp"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Google Compute Engine (VM Container)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (ephemeral, deleted on stop)\n - Container in VM\n- Managed disk (persistent, mounted to `/home/coder` in container)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image, or use a [startup script](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/script).\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "gcp-windows",
|
||||
"url": "",
|
||||
"name": "Google Compute Engine (Windows)",
|
||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||
"icon": "/icon/gcp.png",
|
||||
"tags": [
|
||||
"vm",
|
||||
"windows",
|
||||
"gcp"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Google Compute Engine (Windows)\n\n## Prerequisites\n\n### Authentication\n\nThis template assumes that coderd is run in an environment that is authenticated\nwith Google Cloud. For example, run `gcloud auth application-default login` to\nimport credentials on the system and user running coderd. For other ways to\nauthenticate [consult the Terraform\ndocs](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials).\n\nCoder requires a Google Cloud Service Account to provision workspaces. To create\na service account:\n\n1. Navigate to the [CGP\n console](https://console.cloud.google.com/projectselector/iam-admin/serviceaccounts/create),\n and select your Cloud project (if you have more than one project associated\n with your account)\n\n1. Provide a service account name (this name is used to generate the service\n account ID)\n\n1. Click **Create and continue**, and choose the following IAM roles to grant to\n the service account:\n\n - Compute Admin\n - Service Account User\n\n Click **Continue**.\n\n1. Click on the created key, and navigate to the **Keys** tab.\n\n1. Click **Add key** \u003e **Create new key**.\n\n1. Generate a **JSON private key**, which will be what you provide to Coder\n during the setup process.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- GCP VM (ephemeral)\n- GCP Disk (persistent, mounted to root)\n\nCoder persists the root volume. The full filesystem is preserved when the workspace restarts. See this [community example](https://github.com/bpmct/coder-templates/tree/main/aws-linux-ephemeral) of an ephemeral AWS instance.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## code-server\n\n`code-server` is installed via the `startup_script` argument in the `coder_agent`\nresource block. The `coder_app` resource is defined to access `code-server` through\nthe dashboard UI over `localhost:13337`.\n"
|
||||
},
|
||||
{
|
||||
"id": "kubernetes",
|
||||
"url": "",
|
||||
"name": "Kubernetes (Deployment)",
|
||||
"description": "Provision Kubernetes Deployments as Coder workspaces",
|
||||
"icon": "/icon/k8s.png",
|
||||
"tags": [
|
||||
"kubernetes",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Kubernetes Pods\n\nProvision Kubernetes Pods as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n## Prerequisites\n\n### Infrastructure\n\n**Cluster**: This template requires an existing Kubernetes cluster\n\n**Container Image**: This template uses the [codercom/enterprise-base:ubuntu image](https://github.com/coder/enterprise-images/tree/main/images/base) with some dev tools preinstalled. To add additional tools, extend this image or build it yourself.\n\n### Authentication\n\nThis template authenticates using a `~/.kube/config`, if present on the server, or via built-in authentication if the Coder provisioner is running on Kubernetes with an authorized ServiceAccount. To use another [authentication method](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication), edit the template.\n\n## Architecture\n\nThis template provisions the following resources:\n\n- Kubernetes pod (ephemeral)\n- Kubernetes persistent volume claim (persistent on `/home/coder`)\n\nThis means, when the workspace restarts, any tools or files outside of the home directory are not persisted. To pre-bake tools into the workspace (e.g. `python3`), modify the container image. Alternatively, individual developers can [personalize](https://coder.com/docs/dotfiles) their workspaces with dotfiles.\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n"
|
||||
},
|
||||
{
|
||||
"id": "nomad-docker",
|
||||
"url": "",
|
||||
"name": "Nomad",
|
||||
"description": "Provision Nomad Jobs as Coder workspaces",
|
||||
"icon": "/icon/nomad.svg",
|
||||
"tags": [
|
||||
"nomad",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Nomad\n\nProvision Nomad Jobs as [Coder workspaces](https://coder.com/docs/workspaces) with this example template. This example shows how to use Nomad service tasks to be used as a development environment using docker and host csi volumes.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Prerequisites\n\n- [Nomad](https://www.nomadproject.io/downloads)\n- [Docker](https://docs.docker.com/get-docker/)\n\n## Setup\n\n### 1. Start the CSI Host Volume Plugin\n\nThe CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This is useful for development environments where you want to mount persistent volumes into your container workspace.\n\n1. Login to the Nomad server using SSH.\n\n2. Append the following stanza to your Nomad server configuration file and restart the nomad service.\n\n ```hcl\n plugin \"docker\" {\n config {\n allow_privileged = true\n }\n }\n ```\n\n ```shell\n sudo systemctl restart nomad\n ```\n\n3. Create a file `hostpath.nomad` with following content:\n\n ```hcl\n job \"hostpath-csi-plugin\" {\n datacenters = [\"dc1\"]\n type = \"system\"\n\n group \"csi\" {\n task \"plugin\" {\n driver = \"docker\"\n\n config {\n image = \"registry.k8s.io/sig-storage/hostpathplugin:v1.10.0\"\n\n args = [\n \"--drivername=csi-hostpath\",\n \"--v=5\",\n \"--endpoint=${CSI_ENDPOINT}\",\n \"--nodeid=node-${NOMAD_ALLOC_INDEX}\",\n ]\n\n privileged = true\n }\n\n csi_plugin {\n id = \"hostpath\"\n type = \"monolith\"\n mount_dir = \"/csi\"\n }\n\n resources {\n cpu = 256\n memory = 128\n }\n }\n }\n }\n ```\n\n4. Run the job:\n\n ```shell\n nomad job run hostpath.nomad\n ```\n\n### 2. Setup the Nomad Template\n\n1. Create the template by running the following command:\n\n ```shell\n coder template init nomad-docker\n cd nomad-docker\n coder template push\n ```\n\n2. Set up Nomad server address and optional authentication:\n\n3. Create a new workspace and start developing.\n"
|
||||
},
|
||||
{
|
||||
"id": "scratch",
|
||||
"url": "",
|
||||
"name": "Scratch",
|
||||
"description": "A minimal starter template for Coder",
|
||||
"icon": "/emojis/1f4e6.png",
|
||||
"tags": [],
|
||||
"markdown": "\n# A minimal Scaffolding for a Coder Template\n\nUse this starter template as a basis to create your own unique template from scratch.\n"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,50 +31,50 @@ instances provisioned by Coder:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -30,50 +30,50 @@ instances provisioned by Coder:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -30,50 +30,50 @@ instances provisioned by Coder:
|
||||
|
||||
```json
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Sid": "VisualEditor0",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:GetDefaultCreditSpecification",
|
||||
"ec2:DescribeIamInstanceProfileAssociations",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeInstances",
|
||||
"ec2:DescribeInstanceTypes",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:DescribeInstanceCreditSpecifications",
|
||||
"ec2:DescribeImages",
|
||||
"ec2:ModifyDefaultCreditSpecification",
|
||||
"ec2:DescribeVolumes"
|
||||
],
|
||||
"Resource": "*"
|
||||
},
|
||||
{
|
||||
"Sid": "CoderResources",
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"ec2:DescribeInstanceAttribute",
|
||||
"ec2:UnmonitorInstances",
|
||||
"ec2:TerminateInstances",
|
||||
"ec2:StartInstances",
|
||||
"ec2:StopInstances",
|
||||
"ec2:DeleteTags",
|
||||
"ec2:MonitorInstances",
|
||||
"ec2:CreateTags",
|
||||
"ec2:RunInstances",
|
||||
"ec2:ModifyInstanceAttribute",
|
||||
"ec2:ModifyInstanceCreditSpecification"
|
||||
],
|
||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
reactStrictMode: true,
|
||||
trailingSlash: true,
|
||||
output: "export",
|
||||
reactStrictMode: true,
|
||||
trailingSlash: true,
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
+43
-43
@@ -1,45 +1,45 @@
|
||||
{
|
||||
"name": "coder-docs-generator",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm copy-images && next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "pnpm copy-images && next build",
|
||||
"copy-images": "sh ./scripts/copyImages.sh",
|
||||
"lint": "pnpm run lint:types",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"format": "prettier --cache --write './**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||
"format:check": "prettier --cache --check './**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "2.8.2",
|
||||
"@emotion/react": "11.11.4",
|
||||
"@emotion/styled": "11.11.5",
|
||||
"archiver": "6.0.2",
|
||||
"framer-motion": "^10.17.6",
|
||||
"front-matter": "4.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"next": "14.2.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-icons": "4.12.0",
|
||||
"react-markdown": "9.0.1",
|
||||
"rehype-raw": "7.0.0",
|
||||
"remark-gfm": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "4.14.196",
|
||||
"@types/node": "18.19.0",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-next": "14.0.1",
|
||||
"prettier": "3.3.3",
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=9.0.0 <10.0.0",
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
}
|
||||
"name": "coder-docs-generator",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "pnpm copy-images && next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "pnpm copy-images && next build",
|
||||
"copy-images": "sh ./scripts/copyImages.sh",
|
||||
"lint": "pnpm run lint:types",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"format": "prettier --cache --write './**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
||||
"format:check": "prettier --cache --check './**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "2.8.2",
|
||||
"@emotion/react": "11.11.4",
|
||||
"@emotion/styled": "11.11.5",
|
||||
"archiver": "6.0.2",
|
||||
"framer-motion": "^10.17.6",
|
||||
"front-matter": "4.0.2",
|
||||
"lodash": "4.17.21",
|
||||
"next": "14.2.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-icons": "4.12.0",
|
||||
"react-markdown": "9.0.1",
|
||||
"rehype-raw": "7.0.0",
|
||||
"remark-gfm": "4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "4.14.196",
|
||||
"@types/node": "18.19.0",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-next": "14.0.1",
|
||||
"prettier": "3.3.3",
|
||||
"typescript": "5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=9.0.0 <10.0.0",
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+419
-419
@@ -1,29 +1,29 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Code,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerCloseButton,
|
||||
DrawerContent,
|
||||
DrawerOverlay,
|
||||
Flex,
|
||||
Grid,
|
||||
GridProps,
|
||||
Heading,
|
||||
Icon,
|
||||
Img,
|
||||
Link,
|
||||
OrderedList,
|
||||
Table,
|
||||
TableContainer,
|
||||
Td,
|
||||
Text,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
UnorderedList,
|
||||
useDisclosure,
|
||||
Box,
|
||||
Button,
|
||||
Code,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerCloseButton,
|
||||
DrawerContent,
|
||||
DrawerOverlay,
|
||||
Flex,
|
||||
Grid,
|
||||
GridProps,
|
||||
Heading,
|
||||
Icon,
|
||||
Img,
|
||||
Link,
|
||||
OrderedList,
|
||||
Table,
|
||||
TableContainer,
|
||||
Td,
|
||||
Text,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
UnorderedList,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import fm from "front-matter";
|
||||
import { readFileSync } from "fs";
|
||||
@@ -42,19 +42,19 @@ import remarkGfm from "remark-gfm";
|
||||
type FilePath = string;
|
||||
type UrlPath = string;
|
||||
type Route = {
|
||||
path: FilePath;
|
||||
title: string;
|
||||
description?: string;
|
||||
children?: Route[];
|
||||
path: FilePath;
|
||||
title: string;
|
||||
description?: string;
|
||||
children?: Route[];
|
||||
};
|
||||
type Manifest = { versions: string[]; routes: Route[] };
|
||||
type NavItem = { title: string; path: UrlPath; children?: NavItem[] };
|
||||
type Nav = NavItem[];
|
||||
|
||||
const readContentFile = (filePath: string) => {
|
||||
const baseDir = process.cwd();
|
||||
const docsPath = path.join(baseDir, "..", "docs");
|
||||
return readFileSync(path.join(docsPath, filePath), { encoding: "utf-8" });
|
||||
const baseDir = process.cwd();
|
||||
const docsPath = path.join(baseDir, "..", "docs");
|
||||
return readFileSync(path.join(docsPath, filePath), { encoding: "utf-8" });
|
||||
};
|
||||
|
||||
const removeTrailingSlash = (path: string) => path.replace(/\/+$/, "");
|
||||
@@ -62,19 +62,19 @@ const removeTrailingSlash = (path: string) => path.replace(/\/+$/, "");
|
||||
const removeMkdExtension = (path: string) => path.replace(/\.md/g, "");
|
||||
|
||||
const removeIndexFilename = (path: string) => {
|
||||
if (path.endsWith("index")) {
|
||||
path = path.replace("index", "");
|
||||
}
|
||||
if (path.endsWith("index")) {
|
||||
path = path.replace("index", "");
|
||||
}
|
||||
|
||||
return path;
|
||||
return path;
|
||||
};
|
||||
|
||||
const removeREADMEName = (path: string) => {
|
||||
if (path.startsWith("README")) {
|
||||
path = path.replace("README", "");
|
||||
}
|
||||
if (path.startsWith("README")) {
|
||||
path = path.replace("README", "");
|
||||
}
|
||||
|
||||
return path;
|
||||
return path;
|
||||
};
|
||||
|
||||
// transformLinkUri converts the links in the markdown file to
|
||||
@@ -87,466 +87,466 @@ const removeREADMEName = (path: string) => {
|
||||
// file.md -> ./subdir/file = ../subdir/file
|
||||
// file.md -> ../file-next-to-file = ../file-next-to-file
|
||||
const transformLinkUriSource = (sourceFile: string) => {
|
||||
return (href = "") => {
|
||||
const isExternal = href.startsWith("http") || href.startsWith("https");
|
||||
if (!isExternal) {
|
||||
// Remove .md form the path
|
||||
href = removeMkdExtension(href);
|
||||
return (href = "") => {
|
||||
const isExternal = href.startsWith("http") || href.startsWith("https");
|
||||
if (!isExternal) {
|
||||
// Remove .md form the path
|
||||
href = removeMkdExtension(href);
|
||||
|
||||
// Add the extra '..' if not an index file.
|
||||
sourceFile = removeMkdExtension(sourceFile);
|
||||
if (!sourceFile.endsWith("index")) {
|
||||
href = "../" + href;
|
||||
}
|
||||
// Add the extra '..' if not an index file.
|
||||
sourceFile = removeMkdExtension(sourceFile);
|
||||
if (!sourceFile.endsWith("index")) {
|
||||
href = "../" + href;
|
||||
}
|
||||
|
||||
// Remove the index path
|
||||
href = removeIndexFilename(href);
|
||||
href = removeREADMEName(href);
|
||||
}
|
||||
return href;
|
||||
};
|
||||
// Remove the index path
|
||||
href = removeIndexFilename(href);
|
||||
href = removeREADMEName(href);
|
||||
}
|
||||
return href;
|
||||
};
|
||||
};
|
||||
|
||||
const transformFilePathToUrlPath = (filePath: string) => {
|
||||
// Remove markdown extension
|
||||
let urlPath = removeMkdExtension(filePath);
|
||||
// Remove markdown extension
|
||||
let urlPath = removeMkdExtension(filePath);
|
||||
|
||||
// Remove relative path
|
||||
if (urlPath.startsWith("./")) {
|
||||
urlPath = urlPath.replace("./", "");
|
||||
}
|
||||
// Remove relative path
|
||||
if (urlPath.startsWith("./")) {
|
||||
urlPath = urlPath.replace("./", "");
|
||||
}
|
||||
|
||||
// Remove index from the root file
|
||||
urlPath = removeIndexFilename(urlPath);
|
||||
urlPath = removeREADMEName(urlPath);
|
||||
// Remove index from the root file
|
||||
urlPath = removeIndexFilename(urlPath);
|
||||
urlPath = removeREADMEName(urlPath);
|
||||
|
||||
// Remove trailing slash
|
||||
if (urlPath.endsWith("/")) {
|
||||
urlPath = removeTrailingSlash(urlPath);
|
||||
}
|
||||
// Remove trailing slash
|
||||
if (urlPath.endsWith("/")) {
|
||||
urlPath = removeTrailingSlash(urlPath);
|
||||
}
|
||||
|
||||
return urlPath;
|
||||
return urlPath;
|
||||
};
|
||||
|
||||
const mapRoutes = (manifest: Manifest): Record<UrlPath, Route> => {
|
||||
const paths: Record<UrlPath, Route> = {};
|
||||
const paths: Record<UrlPath, Route> = {};
|
||||
|
||||
const addPaths = (routes: Route[]) => {
|
||||
for (const route of routes) {
|
||||
paths[transformFilePathToUrlPath(route.path)] = route;
|
||||
const addPaths = (routes: Route[]) => {
|
||||
for (const route of routes) {
|
||||
paths[transformFilePathToUrlPath(route.path)] = route;
|
||||
|
||||
if (route.children) {
|
||||
addPaths(route.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (route.children) {
|
||||
addPaths(route.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addPaths(manifest.routes);
|
||||
addPaths(manifest.routes);
|
||||
|
||||
return paths;
|
||||
return paths;
|
||||
};
|
||||
|
||||
let manifest: Manifest | undefined;
|
||||
|
||||
const getManifest = () => {
|
||||
if (manifest) {
|
||||
return manifest;
|
||||
}
|
||||
if (manifest) {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
const manifestContent = readContentFile("manifest.json");
|
||||
manifest = JSON.parse(manifestContent) as Manifest;
|
||||
return manifest;
|
||||
const manifestContent = readContentFile("manifest.json");
|
||||
manifest = JSON.parse(manifestContent) as Manifest;
|
||||
return manifest;
|
||||
};
|
||||
|
||||
let navigation: Nav | undefined;
|
||||
|
||||
const getNavigation = (manifest: Manifest): Nav => {
|
||||
if (navigation) {
|
||||
return navigation;
|
||||
}
|
||||
if (navigation) {
|
||||
return navigation;
|
||||
}
|
||||
|
||||
const getNavItem = (route: Route, parentPath?: UrlPath): NavItem => {
|
||||
const path = parentPath
|
||||
? `${parentPath}/${transformFilePathToUrlPath(route.path)}`
|
||||
: transformFilePathToUrlPath(route.path);
|
||||
const navItem: NavItem = {
|
||||
title: route.title,
|
||||
path,
|
||||
};
|
||||
const getNavItem = (route: Route, parentPath?: UrlPath): NavItem => {
|
||||
const path = parentPath
|
||||
? `${parentPath}/${transformFilePathToUrlPath(route.path)}`
|
||||
: transformFilePathToUrlPath(route.path);
|
||||
const navItem: NavItem = {
|
||||
title: route.title,
|
||||
path,
|
||||
};
|
||||
|
||||
if (route.children) {
|
||||
navItem.children = [];
|
||||
if (route.children) {
|
||||
navItem.children = [];
|
||||
|
||||
for (const childRoute of route.children) {
|
||||
navItem.children.push(getNavItem(childRoute));
|
||||
}
|
||||
}
|
||||
for (const childRoute of route.children) {
|
||||
navItem.children.push(getNavItem(childRoute));
|
||||
}
|
||||
}
|
||||
|
||||
return navItem;
|
||||
};
|
||||
return navItem;
|
||||
};
|
||||
|
||||
navigation = [];
|
||||
navigation = [];
|
||||
|
||||
for (const route of manifest.routes) {
|
||||
navigation.push(getNavItem(route));
|
||||
}
|
||||
for (const route of manifest.routes) {
|
||||
navigation.push(getNavItem(route));
|
||||
}
|
||||
|
||||
return navigation;
|
||||
return navigation;
|
||||
};
|
||||
|
||||
const removeHtmlComments = (string: string) => {
|
||||
return string.replace(/<!--[\s\S]*?-->/g, "");
|
||||
return string.replace(/<!--[\s\S]*?-->/g, "");
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = () => {
|
||||
const manifest = getManifest();
|
||||
const routes = mapRoutes(manifest);
|
||||
const paths = Object.keys(routes).map((urlPath) => ({
|
||||
params: { slug: urlPath.split("/") },
|
||||
}));
|
||||
const manifest = getManifest();
|
||||
const routes = mapRoutes(manifest);
|
||||
const paths = Object.keys(routes).map((urlPath) => ({
|
||||
params: { slug: urlPath.split("/") },
|
||||
}));
|
||||
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
return {
|
||||
paths,
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps = (context) => {
|
||||
// When it is home page, the slug is undefined because there is no url path
|
||||
// so we make it an empty string to work good with the mapRoutes
|
||||
const { slug = [""] } = context.params as { slug: string[] };
|
||||
const manifest = getManifest();
|
||||
const routes = mapRoutes(manifest);
|
||||
const urlPath = slug.join("/");
|
||||
const route = routes[urlPath];
|
||||
const { body } = fm(readContentFile(route.path));
|
||||
// Serialize MDX to support custom components
|
||||
const content = removeHtmlComments(body);
|
||||
const navigation = getNavigation(manifest);
|
||||
const version = manifest.versions[0];
|
||||
// When it is home page, the slug is undefined because there is no url path
|
||||
// so we make it an empty string to work good with the mapRoutes
|
||||
const { slug = [""] } = context.params as { slug: string[] };
|
||||
const manifest = getManifest();
|
||||
const routes = mapRoutes(manifest);
|
||||
const urlPath = slug.join("/");
|
||||
const route = routes[urlPath];
|
||||
const { body } = fm(readContentFile(route.path));
|
||||
// Serialize MDX to support custom components
|
||||
const content = removeHtmlComments(body);
|
||||
const navigation = getNavigation(manifest);
|
||||
const version = manifest.versions[0];
|
||||
|
||||
return {
|
||||
props: {
|
||||
content,
|
||||
navigation,
|
||||
route,
|
||||
version,
|
||||
},
|
||||
};
|
||||
return {
|
||||
props: {
|
||||
content,
|
||||
navigation,
|
||||
route,
|
||||
version,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const SidebarNavItem: React.FC<{ item: NavItem; nav: Nav }> = ({
|
||||
item,
|
||||
nav,
|
||||
item,
|
||||
nav,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
let isActive = router.asPath.startsWith(`/${item.path}`);
|
||||
const router = useRouter();
|
||||
let isActive = router.asPath.startsWith(`/${item.path}`);
|
||||
|
||||
// Special case to handle the home path
|
||||
if (item.path === "") {
|
||||
isActive = router.asPath === "/";
|
||||
// Special case to handle the home path
|
||||
if (item.path === "") {
|
||||
isActive = router.asPath === "/";
|
||||
|
||||
// Special case to handle the home path children
|
||||
const homeNav = nav.find((navItem) => navItem.path === "") as NavItem;
|
||||
const homeNavPaths =
|
||||
homeNav.children?.map((item) => `/${item.path}/`) ?? [];
|
||||
if (homeNavPaths.includes(router.asPath)) {
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
// Special case to handle the home path children
|
||||
const homeNav = nav.find((navItem) => navItem.path === "") as NavItem;
|
||||
const homeNavPaths =
|
||||
homeNav.children?.map((item) => `/${item.path}/`) ?? [];
|
||||
if (homeNavPaths.includes(router.asPath)) {
|
||||
isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<NextLink href={"/" + item.path} passHref legacyBehavior>
|
||||
<Link
|
||||
fontWeight={isActive ? 600 : 400}
|
||||
color={isActive ? "gray.900" : "gray.700"}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
</NextLink>
|
||||
return (
|
||||
<Box>
|
||||
<NextLink href={"/" + item.path} passHref legacyBehavior>
|
||||
<Link
|
||||
fontWeight={isActive ? 600 : 400}
|
||||
color={isActive ? "gray.900" : "gray.700"}
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
</NextLink>
|
||||
|
||||
{isActive && item.children && (
|
||||
<Grid
|
||||
as="nav"
|
||||
pt={2}
|
||||
pl={3}
|
||||
maxW="sm"
|
||||
autoFlow="row"
|
||||
gap={2}
|
||||
autoRows="min-content"
|
||||
>
|
||||
{item.children.map((subItem) => (
|
||||
<SidebarNavItem key={subItem.path} item={subItem} nav={nav} />
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
{isActive && item.children && (
|
||||
<Grid
|
||||
as="nav"
|
||||
pt={2}
|
||||
pl={3}
|
||||
maxW="sm"
|
||||
autoFlow="row"
|
||||
gap={2}
|
||||
autoRows="min-content"
|
||||
>
|
||||
{item.children.map((subItem) => (
|
||||
<SidebarNavItem key={subItem.path} item={subItem} nav={nav} />
|
||||
))}
|
||||
</Grid>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarNav: React.FC<{ nav: Nav; version: string } & GridProps> = ({
|
||||
nav,
|
||||
version,
|
||||
...gridProps
|
||||
nav,
|
||||
version,
|
||||
...gridProps
|
||||
}) => {
|
||||
return (
|
||||
<Grid
|
||||
h="100vh"
|
||||
overflowY="scroll"
|
||||
as="nav"
|
||||
p={8}
|
||||
w="300px"
|
||||
autoFlow="row"
|
||||
gap={2}
|
||||
autoRows="min-content"
|
||||
bgColor="white"
|
||||
borderRightWidth={1}
|
||||
borderColor="gray.200"
|
||||
borderStyle="solid"
|
||||
{...gridProps}
|
||||
>
|
||||
<Box mb={6}>
|
||||
<Img src="/logo.svg" alt="Coder logo" />
|
||||
</Box>
|
||||
return (
|
||||
<Grid
|
||||
h="100vh"
|
||||
overflowY="scroll"
|
||||
as="nav"
|
||||
p={8}
|
||||
w="300px"
|
||||
autoFlow="row"
|
||||
gap={2}
|
||||
autoRows="min-content"
|
||||
bgColor="white"
|
||||
borderRightWidth={1}
|
||||
borderColor="gray.200"
|
||||
borderStyle="solid"
|
||||
{...gridProps}
|
||||
>
|
||||
<Box mb={6}>
|
||||
<Img src="/logo.svg" alt="Coder logo" />
|
||||
</Box>
|
||||
|
||||
{nav.map((navItem) => (
|
||||
<SidebarNavItem key={navItem.path} item={navItem} nav={nav} />
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
{nav.map((navItem) => (
|
||||
<SidebarNavItem key={navItem.path} item={navItem} nav={nav} />
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const MobileNavbar: React.FC<{ nav: Nav; version: string }> = ({
|
||||
nav,
|
||||
version,
|
||||
nav,
|
||||
version,
|
||||
}) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
bgColor="white"
|
||||
px={6}
|
||||
alignItems="center"
|
||||
h={16}
|
||||
borderBottomWidth={1}
|
||||
>
|
||||
<Img src="/logo.svg" alt="Coder logo" w={28} />
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
bgColor="white"
|
||||
px={6}
|
||||
alignItems="center"
|
||||
h={16}
|
||||
borderBottomWidth={1}
|
||||
>
|
||||
<Img src="/logo.svg" alt="Coder logo" w={28} />
|
||||
|
||||
<Button variant="ghost" ml="auto" onClick={onOpen}>
|
||||
<Icon as={MdMenu} fontSize="2xl" />
|
||||
</Button>
|
||||
</Flex>
|
||||
<Button variant="ghost" ml="auto" onClick={onOpen}>
|
||||
<Icon as={MdMenu} fontSize="2xl" />
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Drawer onClose={onClose} isOpen={isOpen}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerCloseButton />
|
||||
<DrawerBody p={0}>
|
||||
<SidebarNav nav={nav} version={version} border={0} />
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
<Drawer onClose={onClose} isOpen={isOpen}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerCloseButton />
|
||||
<DrawerBody p={0}>
|
||||
<SidebarNav nav={nav} version={version} border={0} />
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const slugifyTitle = (titleSource: ReactNode) => {
|
||||
if (Array.isArray(titleSource) && typeof titleSource[0] === "string") {
|
||||
return _.kebabCase(titleSource[0].toLowerCase());
|
||||
}
|
||||
if (Array.isArray(titleSource) && typeof titleSource[0] === "string") {
|
||||
return _.kebabCase(titleSource[0].toLowerCase());
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getImageUrl = (src: string | undefined) => {
|
||||
if (src === undefined) {
|
||||
return "";
|
||||
}
|
||||
const assetPath = src.split("images/")[1];
|
||||
return `/images/${assetPath}`;
|
||||
if (src === undefined) {
|
||||
return "";
|
||||
}
|
||||
const assetPath = src.split("images/")[1];
|
||||
return `/images/${assetPath}`;
|
||||
};
|
||||
|
||||
const DocsPage: NextPage<{
|
||||
content: string;
|
||||
navigation: Nav;
|
||||
route: Route;
|
||||
version: string;
|
||||
content: string;
|
||||
navigation: Nav;
|
||||
route: Route;
|
||||
version: string;
|
||||
}> = ({ content, navigation, route, version }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{route.title}</title>
|
||||
<meta name="source" content={route.path} />
|
||||
</Head>
|
||||
<Box
|
||||
display={{ md: "grid" }}
|
||||
gridTemplateColumns="max-content 1fr"
|
||||
fontSize="md"
|
||||
color="gray.700"
|
||||
>
|
||||
<Box display={{ base: "none", md: "block" }}>
|
||||
<SidebarNav nav={navigation} version={version} />
|
||||
</Box>
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{route.title}</title>
|
||||
<meta name="source" content={route.path} />
|
||||
</Head>
|
||||
<Box
|
||||
display={{ md: "grid" }}
|
||||
gridTemplateColumns="max-content 1fr"
|
||||
fontSize="md"
|
||||
color="gray.700"
|
||||
>
|
||||
<Box display={{ base: "none", md: "block" }}>
|
||||
<SidebarNav nav={navigation} version={version} />
|
||||
</Box>
|
||||
|
||||
<Box display={{ base: "block", md: "none" }}>
|
||||
<MobileNavbar nav={navigation} version={version} />
|
||||
</Box>
|
||||
<Box display={{ base: "block", md: "none" }}>
|
||||
<MobileNavbar nav={navigation} version={version} />
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
as="main"
|
||||
w="full"
|
||||
pb={20}
|
||||
px={{ base: 6, md: 10 }}
|
||||
pl={{ base: 6, md: 20 }}
|
||||
h="100vh"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Box maxW="872">
|
||||
<Box lineHeight="tall">
|
||||
{/* Some docs don't have the title */}
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize="4xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
// Hide this title if the doc has the title already
|
||||
sx={{ "& + h1": { display: "none" } }}
|
||||
>
|
||||
{route.title}
|
||||
</Heading>
|
||||
<Box
|
||||
as="main"
|
||||
w="full"
|
||||
pb={20}
|
||||
px={{ base: 6, md: 10 }}
|
||||
pl={{ base: 6, md: 20 }}
|
||||
h="100vh"
|
||||
overflowY="auto"
|
||||
>
|
||||
<Box maxW="872">
|
||||
<Box lineHeight="tall">
|
||||
{/* Some docs don't have the title */}
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize="4xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
// Hide this title if the doc has the title already
|
||||
sx={{ "& + h1": { display: "none" } }}
|
||||
>
|
||||
{route.title}
|
||||
</Heading>
|
||||
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
urlTransform={transformLinkUriSource(route.path)}
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize="4xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
id={slugifyTitle(children)}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
remarkPlugins={[remarkGfm]}
|
||||
urlTransform={transformLinkUriSource(route.path)}
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<Heading
|
||||
as="h1"
|
||||
fontSize="4xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
id={slugifyTitle(children)}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
|
||||
h2: ({ children }) => (
|
||||
<Heading
|
||||
as="h2"
|
||||
fontSize="3xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
id={slugifyTitle(children)}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<Heading
|
||||
as="h3"
|
||||
fontSize="2xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
id={slugifyTitle(children)}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
img: ({ src }) => (
|
||||
<Img
|
||||
src={getImageUrl(src)}
|
||||
mb={2}
|
||||
borderWidth={1}
|
||||
borderColor="gray.200"
|
||||
borderStyle="solid"
|
||||
rounded="md"
|
||||
height="auto"
|
||||
/>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<Text pt={2} pb={2}>
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<UnorderedList
|
||||
mb={4}
|
||||
display="grid"
|
||||
gridAutoFlow="row"
|
||||
gap={2}
|
||||
>
|
||||
{children}
|
||||
</UnorderedList>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<OrderedList
|
||||
mb={4}
|
||||
display="grid"
|
||||
gridAutoFlow="row"
|
||||
gap={2}
|
||||
>
|
||||
{children}
|
||||
</OrderedList>
|
||||
),
|
||||
a: ({ children, href = "" }) => {
|
||||
const isExternal =
|
||||
href.startsWith("http") || href.startsWith("https");
|
||||
h2: ({ children }) => (
|
||||
<Heading
|
||||
as="h2"
|
||||
fontSize="3xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
id={slugifyTitle(children)}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<Heading
|
||||
as="h3"
|
||||
fontSize="2xl"
|
||||
pt={10}
|
||||
pb={2}
|
||||
id={slugifyTitle(children)}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
),
|
||||
img: ({ src }) => (
|
||||
<Img
|
||||
src={getImageUrl(src)}
|
||||
mb={2}
|
||||
borderWidth={1}
|
||||
borderColor="gray.200"
|
||||
borderStyle="solid"
|
||||
rounded="md"
|
||||
height="auto"
|
||||
/>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<Text pt={2} pb={2}>
|
||||
{children}
|
||||
</Text>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<UnorderedList
|
||||
mb={4}
|
||||
display="grid"
|
||||
gridAutoFlow="row"
|
||||
gap={2}
|
||||
>
|
||||
{children}
|
||||
</UnorderedList>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<OrderedList
|
||||
mb={4}
|
||||
display="grid"
|
||||
gridAutoFlow="row"
|
||||
gap={2}
|
||||
>
|
||||
{children}
|
||||
</OrderedList>
|
||||
),
|
||||
a: ({ children, href = "" }) => {
|
||||
const isExternal =
|
||||
href.startsWith("http") || href.startsWith("https");
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
target={isExternal ? "_blank" : undefined}
|
||||
fontWeight={500}
|
||||
color="blue.600"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
code: ({ node, ...props }) => (
|
||||
<Code {...props} bgColor="gray.100" />
|
||||
),
|
||||
pre: ({ children }) => (
|
||||
<Box
|
||||
as="pre"
|
||||
w="full"
|
||||
sx={{ "& > code": { w: "full", p: 4, rounded: "md" } }}
|
||||
mb={2}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<TableContainer
|
||||
mt={1}
|
||||
mb={2}
|
||||
bgColor="white"
|
||||
rounded="md"
|
||||
borderWidth={1}
|
||||
borderColor="gray.100"
|
||||
borderStyle="solid"
|
||||
>
|
||||
<Table variant="simple">{children}</Table>
|
||||
</TableContainer>
|
||||
),
|
||||
thead: ({ children }) => <Thead>{children}</Thead>,
|
||||
th: ({ children }) => <Th>{children}</Th>,
|
||||
td: ({ children }) => <Td>{children}</Td>,
|
||||
tr: ({ children }) => <Tr>{children}</Tr>,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
target={isExternal ? "_blank" : undefined}
|
||||
fontWeight={500}
|
||||
color="blue.600"
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
code: ({ node, ...props }) => (
|
||||
<Code {...props} bgColor="gray.100" />
|
||||
),
|
||||
pre: ({ children }) => (
|
||||
<Box
|
||||
as="pre"
|
||||
w="full"
|
||||
sx={{ "& > code": { w: "full", p: 4, rounded: "md" } }}
|
||||
mb={2}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
),
|
||||
table: ({ children }) => (
|
||||
<TableContainer
|
||||
mt={1}
|
||||
mb={2}
|
||||
bgColor="white"
|
||||
rounded="md"
|
||||
borderWidth={1}
|
||||
borderColor="gray.100"
|
||||
borderStyle="solid"
|
||||
>
|
||||
<Table variant="simple">{children}</Table>
|
||||
</TableContainer>
|
||||
),
|
||||
thead: ({ children }) => <Thead>{children}</Thead>,
|
||||
th: ({ children }) => <Th>{children}</Th>,
|
||||
td: ({ children }) => <Td>{children}</Td>,
|
||||
tr: ({ children }) => <Tr>{children}</Tr>,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocsPage;
|
||||
|
||||
+18
-18
@@ -3,27 +3,27 @@ import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
|
||||
const theme = extendTheme({
|
||||
styles: {
|
||||
global: {
|
||||
body: {
|
||||
bg: "gray.50",
|
||||
},
|
||||
},
|
||||
},
|
||||
styles: {
|
||||
global: {
|
||||
body: {
|
||||
bg: "gray.50",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000" />
|
||||
<link rel="alternate icon" type="image/png" href="/favicon.png" />
|
||||
</Head>
|
||||
<ChakraProvider theme={theme}>
|
||||
<Component {...pageProps} />
|
||||
</ChakraProvider>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000" />
|
||||
<link rel="alternate icon" type="image/png" href="/favicon.png" />
|
||||
</Head>
|
||||
<ChakraProvider theme={theme}>
|
||||
<Component {...pageProps} />
|
||||
</ChakraProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyApp;
|
||||
|
||||
+18
-18
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "docs"]
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "docs"]
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"_comment": "This version doesn't matter, it's just to allow importing from other repos.",
|
||||
"name": "coder",
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247",
|
||||
"scripts": {
|
||||
"format": "prettier --cache --write '**/*.{css,html,json,md,yaml,yml}'",
|
||||
"format:check": "prettier --cache --check '**/*.{css,html,json,md,yaml,yml}'",
|
||||
"storybook": "pnpm run -C site/ storybook"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.3.3"
|
||||
}
|
||||
"_comment": "This version doesn't matter, it's just to allow importing from other repos.",
|
||||
"name": "coder",
|
||||
"version": "0.0.0",
|
||||
"packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247",
|
||||
"scripts": {
|
||||
"format": "prettier --cache --write '**/*.{css,html,json,md,yaml,yml}'",
|
||||
"format:check": "prettier --cache --check '**/*.{css,html,json,md,yaml,yml}'",
|
||||
"storybook": "pnpm run -C site/ storybook"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
+5057
-5057
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"widdershins": "^4.0.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"semver": "7.5.3",
|
||||
"jsonpointer": "5.0.1"
|
||||
}
|
||||
"dependencies": {
|
||||
"widdershins": "^4.0.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"semver": "7.5.3",
|
||||
"jsonpointer": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ var (
|
||||
// CLI option types:
|
||||
"github.com/coder/serpent",
|
||||
}
|
||||
indent = " "
|
||||
indent = "\t"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
+4
-4
@@ -1,17 +1,17 @@
|
||||
// From codersdk/genericmap.go
|
||||
export interface Buzz {
|
||||
readonly foo: Foo
|
||||
readonly bazz: string
|
||||
readonly foo: Foo
|
||||
readonly bazz: string
|
||||
}
|
||||
|
||||
// From codersdk/genericmap.go
|
||||
export interface Foo {
|
||||
readonly bar: string
|
||||
readonly bar: string
|
||||
}
|
||||
|
||||
// From codersdk/genericmap.go
|
||||
export interface FooBuzz<R extends Custom> {
|
||||
readonly something: (readonly R[])
|
||||
readonly something: (readonly R[])
|
||||
}
|
||||
|
||||
// From codersdk/genericmap.go
|
||||
|
||||
+14
-14
@@ -1,35 +1,35 @@
|
||||
// From codersdk/generics.go
|
||||
export interface Complex<C extends comparable, S extends Single, T extends Custom> {
|
||||
readonly dynamic: Fields<C, boolean, string, S>
|
||||
readonly order: FieldsDiffOrder<C, string, S, T>
|
||||
readonly comparable: C
|
||||
readonly single: S
|
||||
readonly static: Static
|
||||
readonly dynamic: Fields<C, boolean, string, S>
|
||||
readonly order: FieldsDiffOrder<C, string, S, T>
|
||||
readonly comparable: C
|
||||
readonly single: S
|
||||
readonly static: Static
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface Dynamic<A extends any, S extends Single> {
|
||||
readonly dynamic: Fields<boolean, A, string, S>
|
||||
readonly comparable: boolean
|
||||
readonly dynamic: Fields<boolean, A, string, S>
|
||||
readonly comparable: boolean
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface Fields<C extends comparable, A extends any, T extends Custom, S extends Single> {
|
||||
readonly comparable: C
|
||||
readonly any: A
|
||||
readonly custom: T
|
||||
readonly again: T
|
||||
readonly single_constraint: S
|
||||
readonly comparable: C
|
||||
readonly any: A
|
||||
readonly custom: T
|
||||
readonly again: T
|
||||
readonly single_constraint: S
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface FieldsDiffOrder<A extends any, C extends comparable, S extends Single, T extends Custom> {
|
||||
readonly Fields: Fields<C, A, T, S>
|
||||
readonly Fields: Fields<C, A, T, S>
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface Static {
|
||||
readonly static: Fields<string, number, number, string>
|
||||
readonly static: Fields<string, number, number, string>
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// From codersdk/genericslice.go
|
||||
export interface Bar {
|
||||
readonly Bar: string
|
||||
readonly Bar: string
|
||||
}
|
||||
|
||||
// From codersdk/genericslice.go
|
||||
export interface Foo<R extends any> {
|
||||
readonly Slice: (readonly R[])
|
||||
readonly TwoD: (readonly (readonly R[])[])
|
||||
readonly Slice: (readonly R[])
|
||||
readonly TwoD: (readonly (readonly R[])[])
|
||||
}
|
||||
@@ -98,7 +98,7 @@ func run(lint bool) error {
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
enc.SetIndent("", "\t")
|
||||
return enc.Encode(examples)
|
||||
}
|
||||
|
||||
|
||||
+39
-43
@@ -1,45 +1,41 @@
|
||||
{
|
||||
"files": {
|
||||
"ignore": ["**/*Generated.ts"]
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"a11y": {
|
||||
"noSvgWithoutTitle": { "level": "off" },
|
||||
"useButtonType": { "level": "off" }
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": { "level": "off" },
|
||||
"noParameterAssign": { "level": "off" },
|
||||
"useDefaultParameterLast": { "level": "off" },
|
||||
"useSelfClosingElements": { "level": "off" }
|
||||
},
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": { "level": "off" },
|
||||
"noThenProperty": { "level": "off" }
|
||||
},
|
||||
"nursery": {
|
||||
"noRestrictedImports": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"paths": {
|
||||
"@mui/material": "Use @mui/material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
||||
"@mui/icons-material": "Use @mui/icons-material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
||||
"@mui/material/Avatar": "Use components/Avatar/Avatar instead.",
|
||||
"@mui/material/Alert": "Use components/Alert/Alert instead.",
|
||||
"@mui/material/Popover": "Use components/Popover/Popover instead.",
|
||||
"@mui/material/Typography": "Use native HTML elements instead. Eg: <span>, <p>, <h1>, etc.",
|
||||
"@mui/material/Box": "Use a <div> instead.",
|
||||
"@mui/material/styles": "Import from @emotion/react instead.",
|
||||
"lodash": "Use lodash/<name> instead."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"files": {
|
||||
"ignore": ["**/*Generated.ts"]
|
||||
},
|
||||
"linter": {
|
||||
"rules": {
|
||||
"a11y": {
|
||||
"noSvgWithoutTitle": { "level": "off" },
|
||||
"useButtonType": { "level": "off" }
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": { "level": "off" },
|
||||
"noParameterAssign": { "level": "off" },
|
||||
"useDefaultParameterLast": { "level": "off" },
|
||||
"useSelfClosingElements": { "level": "off" }
|
||||
},
|
||||
"suspicious": {
|
||||
"noArrayIndexKey": { "level": "off" },
|
||||
"noThenProperty": { "level": "off" }
|
||||
},
|
||||
"nursery": {
|
||||
"noRestrictedImports": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"paths": {
|
||||
"@mui/material": "Use @mui/material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
||||
"@mui/icons-material": "Use @mui/icons-material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
||||
"@mui/material/Avatar": "Use components/Avatar/Avatar instead.",
|
||||
"@mui/material/Alert": "Use components/Alert/Alert instead.",
|
||||
"@mui/material/Popover": "Use components/Popover/Popover instead.",
|
||||
"@mui/material/Typography": "Use native HTML elements instead. Eg: <span>, <p>, <h1>, etc.",
|
||||
"@mui/material/Box": "Use a <div> instead.",
|
||||
"@mui/material/styles": "Import from @emotion/react instead.",
|
||||
"lodash": "Use lodash/<name> instead."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+122
-122
@@ -9,174 +9,174 @@ import { findSessionToken, randomName } from "./helpers";
|
||||
let currentOrgId: string;
|
||||
|
||||
export const setupApiCalls = async (page: Page) => {
|
||||
try {
|
||||
const token = await findSessionToken(page);
|
||||
API.setSessionToken(token);
|
||||
} catch {
|
||||
// If this fails, we have an unauthenticated client.
|
||||
}
|
||||
try {
|
||||
const token = await findSessionToken(page);
|
||||
API.setSessionToken(token);
|
||||
} catch {
|
||||
// If this fails, we have an unauthenticated client.
|
||||
}
|
||||
|
||||
API.setHost(`http://127.0.0.1:${coderPort}`);
|
||||
API.setHost(`http://127.0.0.1:${coderPort}`);
|
||||
};
|
||||
|
||||
export const getCurrentOrgId = async (): Promise<string> => {
|
||||
if (currentOrgId) {
|
||||
return currentOrgId;
|
||||
}
|
||||
const currentUser = await API.getAuthenticatedUser();
|
||||
currentOrgId = currentUser.organization_ids[0];
|
||||
return currentOrgId;
|
||||
if (currentOrgId) {
|
||||
return currentOrgId;
|
||||
}
|
||||
const currentUser = await API.getAuthenticatedUser();
|
||||
currentOrgId = currentUser.organization_ids[0];
|
||||
return currentOrgId;
|
||||
};
|
||||
|
||||
export const createUser = async (orgId: string) => {
|
||||
const name = randomName();
|
||||
const user = await API.createUser({
|
||||
email: `${name}@coder.com`,
|
||||
username: name,
|
||||
name: name,
|
||||
password: "s3cure&password!",
|
||||
login_type: "password",
|
||||
disable_login: false,
|
||||
organization_id: orgId,
|
||||
});
|
||||
return user;
|
||||
const name = randomName();
|
||||
const user = await API.createUser({
|
||||
email: `${name}@coder.com`,
|
||||
username: name,
|
||||
name: name,
|
||||
password: "s3cure&password!",
|
||||
login_type: "password",
|
||||
disable_login: false,
|
||||
organization_id: orgId,
|
||||
});
|
||||
return user;
|
||||
};
|
||||
|
||||
export const createGroup = async (orgId: string) => {
|
||||
const name = randomName();
|
||||
const group = await API.createGroup(orgId, {
|
||||
name,
|
||||
display_name: `Display ${name}`,
|
||||
avatar_url: "/emojis/1f60d.png",
|
||||
quota_allowance: 0,
|
||||
});
|
||||
return group;
|
||||
const name = randomName();
|
||||
const group = await API.createGroup(orgId, {
|
||||
name,
|
||||
display_name: `Display ${name}`,
|
||||
avatar_url: "/emojis/1f60d.png",
|
||||
quota_allowance: 0,
|
||||
});
|
||||
return group;
|
||||
};
|
||||
|
||||
export const createOrganization = async () => {
|
||||
const name = randomName();
|
||||
const org = await API.createOrganization({
|
||||
name,
|
||||
display_name: `Org ${name}`,
|
||||
description: `Org description ${name}`,
|
||||
icon: "/emojis/1f957.png",
|
||||
});
|
||||
return org;
|
||||
const name = randomName();
|
||||
const org = await API.createOrganization({
|
||||
name,
|
||||
display_name: `Org ${name}`,
|
||||
description: `Org description ${name}`,
|
||||
icon: "/emojis/1f957.png",
|
||||
});
|
||||
return org;
|
||||
};
|
||||
|
||||
export async function verifyConfigFlagBoolean(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
) {
|
||||
const opt = findConfigOption(config, flag);
|
||||
const type = opt.value ? "option-enabled" : "option-disabled";
|
||||
const value = opt.value ? "Enabled" : "Disabled";
|
||||
const opt = findConfigOption(config, flag);
|
||||
const type = opt.value ? "option-enabled" : "option-disabled";
|
||||
const value = opt.value ? "Enabled" : "Disabled";
|
||||
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .${type}`,
|
||||
);
|
||||
await expect(configOption).toHaveText(value);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .${type}`,
|
||||
);
|
||||
await expect(configOption).toHaveText(value);
|
||||
}
|
||||
|
||||
export async function verifyConfigFlagNumber(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
) {
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-number`,
|
||||
);
|
||||
await expect(configOption).toHaveText(String(opt.value));
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-number`,
|
||||
);
|
||||
await expect(configOption).toHaveText(String(opt.value));
|
||||
}
|
||||
|
||||
export async function verifyConfigFlagString(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
) {
|
||||
const opt = findConfigOption(config, flag);
|
||||
const opt = findConfigOption(config, flag);
|
||||
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-string`,
|
||||
);
|
||||
await expect(configOption).toHaveText(opt.value);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-string`,
|
||||
);
|
||||
await expect(configOption).toHaveText(opt.value);
|
||||
}
|
||||
|
||||
export async function verifyConfigFlagEmpty(page: Page, flag: string) {
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-empty`,
|
||||
);
|
||||
await expect(configOption).toHaveText("Not set");
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-empty`,
|
||||
);
|
||||
await expect(configOption).toHaveText("Not set");
|
||||
}
|
||||
|
||||
export async function verifyConfigFlagArray(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
) {
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-array`,
|
||||
);
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-array`,
|
||||
);
|
||||
|
||||
// Verify array of options with simple dots
|
||||
for (const item of opt.value) {
|
||||
await expect(configOption.locator("li", { hasText: item })).toBeVisible();
|
||||
}
|
||||
// Verify array of options with simple dots
|
||||
for (const item of opt.value) {
|
||||
await expect(configOption.locator("li", { hasText: item })).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifyConfigFlagEntries(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
) {
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-array`,
|
||||
);
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-array`,
|
||||
);
|
||||
|
||||
// Verify array of options with green marks.
|
||||
Object.entries(opt.value)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.map(async ([item]) => {
|
||||
await expect(
|
||||
configOption.locator(`.option-array-item-${item}.option-enabled`, {
|
||||
hasText: item,
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
// Verify array of options with green marks.
|
||||
Object.entries(opt.value)
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.map(async ([item]) => {
|
||||
await expect(
|
||||
configOption.locator(`.option-array-item-${item}.option-enabled`, {
|
||||
hasText: item,
|
||||
}),
|
||||
).toBeVisible();
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyConfigFlagDuration(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
) {
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-string`,
|
||||
);
|
||||
await expect(configOption).toHaveText(
|
||||
formatDuration(
|
||||
// intervalToDuration takes ms, so convert nanoseconds to ms
|
||||
intervalToDuration({
|
||||
start: 0,
|
||||
end: (opt.value as number) / 1e6,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const opt = findConfigOption(config, flag);
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-string`,
|
||||
);
|
||||
await expect(configOption).toHaveText(
|
||||
formatDuration(
|
||||
// intervalToDuration takes ms, so convert nanoseconds to ms
|
||||
intervalToDuration({
|
||||
start: 0,
|
||||
end: (opt.value as number) / 1e6,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function findConfigOption(
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
config: DeploymentConfig,
|
||||
flag: string,
|
||||
): SerpentOption {
|
||||
const opt = config.options.find((option) => option.flag === flag);
|
||||
if (opt === undefined) {
|
||||
// must be undefined as `false` is expected
|
||||
throw new Error(`Option with env ${flag} has undefined value.`);
|
||||
}
|
||||
return opt;
|
||||
const opt = config.options.find((option) => option.flag === flag);
|
||||
if (opt === undefined) {
|
||||
// must be undefined as `false` is expected
|
||||
throw new Error(`Option with env ${flag} has undefined value.`);
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
|
||||
+15
-15
@@ -4,8 +4,8 @@ export const coderMain = path.join(__dirname, "../../enterprise/cmd/coder");
|
||||
|
||||
// Default port from the server
|
||||
export const coderPort = process.env.CODER_E2E_PORT
|
||||
? Number(process.env.CODER_E2E_PORT)
|
||||
: 3111;
|
||||
? Number(process.env.CODER_E2E_PORT)
|
||||
: 3111;
|
||||
export const prometheusPort = 2114;
|
||||
export const workspaceProxyPort = 3112;
|
||||
|
||||
@@ -19,23 +19,23 @@ export const password = "SomeSecurePassword!";
|
||||
export const email = "admin@coder.com";
|
||||
|
||||
export const gitAuth = {
|
||||
deviceProvider: "device",
|
||||
webProvider: "web",
|
||||
// These ports need to be hardcoded so that they can be
|
||||
// used in `playwright.config.ts` to set the environment
|
||||
// variables for the server.
|
||||
devicePort: 50515,
|
||||
webPort: 50516,
|
||||
deviceProvider: "device",
|
||||
webProvider: "web",
|
||||
// These ports need to be hardcoded so that they can be
|
||||
// used in `playwright.config.ts` to set the environment
|
||||
// variables for the server.
|
||||
devicePort: 50515,
|
||||
webPort: 50516,
|
||||
|
||||
authPath: "/auth",
|
||||
tokenPath: "/token",
|
||||
codePath: "/code",
|
||||
validatePath: "/validate",
|
||||
installationsPath: "/installations",
|
||||
authPath: "/auth",
|
||||
tokenPath: "/token",
|
||||
codePath: "/code",
|
||||
validatePath: "/validate",
|
||||
installationsPath: "/installations",
|
||||
};
|
||||
|
||||
export const requireEnterpriseTests = Boolean(
|
||||
process.env.CODER_E2E_REQUIRE_ENTERPRISE_TESTS,
|
||||
process.env.CODER_E2E_REQUIRE_ENTERPRISE_TESTS,
|
||||
);
|
||||
export const enterpriseLicense = process.env.CODER_E2E_ENTERPRISE_LICENSE ?? "";
|
||||
|
||||
|
||||
+30
-30
@@ -3,35 +3,35 @@ import { type Page, expect } from "@playwright/test";
|
||||
type PollingOptions = { timeout?: number; intervals?: number[] };
|
||||
|
||||
export const expectUrl = expect.extend({
|
||||
/**
|
||||
* toHavePathName is an alternative to `toHaveURL` that won't fail if the URL contains query parameters.
|
||||
*/
|
||||
async toHavePathName(page: Page, expected: string, options?: PollingOptions) {
|
||||
let actual: string = new URL(page.url()).pathname;
|
||||
let pass: boolean;
|
||||
try {
|
||||
await expect
|
||||
.poll(() => {
|
||||
actual = new URL(page.url()).pathname;
|
||||
return actual;
|
||||
}, options)
|
||||
.toBe(expected);
|
||||
pass = true;
|
||||
} catch {
|
||||
pass = false;
|
||||
}
|
||||
/**
|
||||
* toHavePathName is an alternative to `toHaveURL` that won't fail if the URL contains query parameters.
|
||||
*/
|
||||
async toHavePathName(page: Page, expected: string, options?: PollingOptions) {
|
||||
let actual: string = new URL(page.url()).pathname;
|
||||
let pass: boolean;
|
||||
try {
|
||||
await expect
|
||||
.poll(() => {
|
||||
actual = new URL(page.url()).pathname;
|
||||
return actual;
|
||||
}, options)
|
||||
.toBe(expected);
|
||||
pass = true;
|
||||
} catch {
|
||||
pass = false;
|
||||
}
|
||||
|
||||
return {
|
||||
name: "toHavePathName",
|
||||
pass,
|
||||
actual,
|
||||
expected,
|
||||
message: () =>
|
||||
`The page does not have the expected URL pathname.\nExpected: ${
|
||||
this.isNot ? "not" : ""
|
||||
}${this.utils.printExpected(
|
||||
expected,
|
||||
)}\nActual: ${this.utils.printReceived(actual)}`,
|
||||
};
|
||||
},
|
||||
return {
|
||||
name: "toHavePathName",
|
||||
pass,
|
||||
actual,
|
||||
expected,
|
||||
message: () =>
|
||||
`The page does not have the expected URL pathname.\nExpected: ${
|
||||
this.isNot ? "not" : ""
|
||||
}${this.utils.printExpected(
|
||||
expected,
|
||||
)}\nActual: ${this.utils.printReceived(actual)}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
+30
-30
@@ -7,41 +7,41 @@ import { expectUrl } from "./expectUrl";
|
||||
import { storageState } from "./playwright.config";
|
||||
|
||||
test("setup deployment", async ({ page }) => {
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await setupApiCalls(page);
|
||||
const exists = await API.hasFirstUser();
|
||||
// First user already exists, abort early. All tests execute this as a dependency,
|
||||
// if you run multiple tests in the UI, this will fail unless we check this.
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await setupApiCalls(page);
|
||||
const exists = await API.hasFirstUser();
|
||||
// First user already exists, abort early. All tests execute this as a dependency,
|
||||
// if you run multiple tests in the UI, this will fail unless we check this.
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup first user
|
||||
await page.getByLabel(Language.usernameLabel).fill(constants.username);
|
||||
await page.getByLabel(Language.emailLabel).fill(constants.email);
|
||||
await page.getByLabel(Language.passwordLabel).fill(constants.password);
|
||||
await page.getByTestId("create").click();
|
||||
// Setup first user
|
||||
await page.getByLabel(Language.usernameLabel).fill(constants.username);
|
||||
await page.getByLabel(Language.emailLabel).fill(constants.email);
|
||||
await page.getByLabel(Language.passwordLabel).fill(constants.password);
|
||||
await page.getByTestId("create").click();
|
||||
|
||||
await expectUrl(page).toHavePathName("/workspaces");
|
||||
await page.context().storageState({ path: storageState });
|
||||
await expectUrl(page).toHavePathName("/workspaces");
|
||||
await page.context().storageState({ path: storageState });
|
||||
|
||||
await page.getByTestId("button-select-template").isVisible();
|
||||
await page.getByTestId("button-select-template").isVisible();
|
||||
|
||||
// Setup license
|
||||
if (constants.requireEnterpriseTests || constants.enterpriseLicense) {
|
||||
// Make sure that we have something that looks like a real license
|
||||
expect(constants.enterpriseLicense).toBeTruthy();
|
||||
expect(constants.enterpriseLicense.length).toBeGreaterThan(92); // the signature alone should be this long
|
||||
expect(constants.enterpriseLicense.split(".").length).toBe(3); // otherwise it's invalid
|
||||
// Setup license
|
||||
if (constants.requireEnterpriseTests || constants.enterpriseLicense) {
|
||||
// Make sure that we have something that looks like a real license
|
||||
expect(constants.enterpriseLicense).toBeTruthy();
|
||||
expect(constants.enterpriseLicense.length).toBeGreaterThan(92); // the signature alone should be this long
|
||||
expect(constants.enterpriseLicense.split(".").length).toBe(3); // otherwise it's invalid
|
||||
|
||||
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
|
||||
|
||||
await page.getByText("Add a license").click();
|
||||
await page.getByRole("textbox").fill(constants.enterpriseLicense);
|
||||
await page.getByText("Upload License").click();
|
||||
await page.getByText("Add a license").click();
|
||||
await page.getByRole("textbox").fill(constants.enterpriseLicense);
|
||||
await page.getByText("Upload License").click();
|
||||
|
||||
await expect(
|
||||
page.getByText("You have successfully added a license"),
|
||||
).toBeVisible();
|
||||
}
|
||||
await expect(
|
||||
page.getByText("You have successfully added a license"),
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
+706
-706
File diff suppressed because it is too large
Load Diff
+68
-68
@@ -3,88 +3,88 @@ import type { BrowserContext, Page } from "@playwright/test";
|
||||
import { coderPort, gitAuth } from "./constants";
|
||||
|
||||
export const beforeCoderTest = async (page: Page) => {
|
||||
// eslint-disable-next-line no-console -- Show everything that was printed with console.log()
|
||||
page.on("console", (msg) => console.log(`[onConsole] ${msg.text()}`));
|
||||
// eslint-disable-next-line no-console -- Show everything that was printed with console.log()
|
||||
page.on("console", (msg) => console.log(`[onConsole] ${msg.text()}`));
|
||||
|
||||
page.on("request", (request) => {
|
||||
if (!isApiCall(request.url())) {
|
||||
return;
|
||||
}
|
||||
page.on("request", (request) => {
|
||||
if (!isApiCall(request.url())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
||||
console.log(
|
||||
`[onRequest] method=${request.method()} url=${request.url()} postData=${
|
||||
request.postData() ? request.postData() : ""
|
||||
}`,
|
||||
);
|
||||
});
|
||||
page.on("response", async (response) => {
|
||||
if (!isApiCall(response.url())) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
||||
console.log(
|
||||
`[onRequest] method=${request.method()} url=${request.url()} postData=${
|
||||
request.postData() ? request.postData() : ""
|
||||
}`,
|
||||
);
|
||||
});
|
||||
page.on("response", async (response) => {
|
||||
if (!isApiCall(response.url())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldLogResponse =
|
||||
!response.url().endsWith("/api/v2/deployment/config") &&
|
||||
!response.url().endsWith("/api/v2/debug/health?force=false");
|
||||
const shouldLogResponse =
|
||||
!response.url().endsWith("/api/v2/deployment/config") &&
|
||||
!response.url().endsWith("/api/v2/debug/health?force=false");
|
||||
|
||||
let responseText = "";
|
||||
try {
|
||||
if (shouldLogResponse) {
|
||||
const buffer = await response.body();
|
||||
responseText = buffer.toString("utf-8");
|
||||
responseText = responseText.replace(/\n$/g, "");
|
||||
} else {
|
||||
responseText = "skipped...";
|
||||
}
|
||||
} catch (error) {
|
||||
responseText = "not_available";
|
||||
}
|
||||
let responseText = "";
|
||||
try {
|
||||
if (shouldLogResponse) {
|
||||
const buffer = await response.body();
|
||||
responseText = buffer.toString("utf-8");
|
||||
responseText = responseText.replace(/\n$/g, "");
|
||||
} else {
|
||||
responseText = "skipped...";
|
||||
}
|
||||
} catch (error) {
|
||||
responseText = "not_available";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
||||
console.log(
|
||||
`[onResponse] url=${response.url()} status=${response.status()} body=${responseText}`,
|
||||
);
|
||||
});
|
||||
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
||||
console.log(
|
||||
`[onResponse] url=${response.url()} status=${response.status()} body=${responseText}`,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const resetExternalAuthKey = async (context: BrowserContext) => {
|
||||
// Find the session token so we can destroy the external auth link between tests, to ensure valid authentication happens each time.
|
||||
const cookies = await context.cookies();
|
||||
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
||||
const options = {
|
||||
method: "DELETE",
|
||||
hostname: "127.0.0.1",
|
||||
port: coderPort,
|
||||
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
||||
};
|
||||
// Find the session token so we can destroy the external auth link between tests, to ensure valid authentication happens each time.
|
||||
const cookies = await context.cookies();
|
||||
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
||||
const options = {
|
||||
method: "DELETE",
|
||||
hostname: "127.0.0.1",
|
||||
port: coderPort,
|
||||
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
const req = http.request(options, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
// Both 200 (key deleted successfully) and 500 (key was not found) are valid responses.
|
||||
if (res.statusCode !== 200 && res.statusCode !== 500) {
|
||||
console.error("failed to delete external auth link", data);
|
||||
throw new Error(
|
||||
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
res.on("end", () => {
|
||||
// Both 200 (key deleted successfully) and 500 (key was not found) are valid responses.
|
||||
if (res.statusCode !== 200 && res.statusCode !== 500) {
|
||||
console.error("failed to delete external auth link", data);
|
||||
throw new Error(
|
||||
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", (err) => {
|
||||
throw err.message;
|
||||
});
|
||||
req.on("error", (err) => {
|
||||
throw err.message;
|
||||
});
|
||||
|
||||
req.end();
|
||||
req.end();
|
||||
};
|
||||
|
||||
const isApiCall = (urlString: string): boolean => {
|
||||
const url = new URL(urlString);
|
||||
const apiPath = "/api/v2";
|
||||
const url = new URL(urlString);
|
||||
const apiPath = "/api/v2";
|
||||
|
||||
return url.pathname.startsWith(apiPath);
|
||||
return url.pathname.startsWith(apiPath);
|
||||
};
|
||||
|
||||
+107
-107
@@ -3,162 +3,162 @@ import type { RichParameter } from "./provisionerGenerated";
|
||||
// Rich parameters
|
||||
|
||||
export const emptyParameter: RichParameter = {
|
||||
name: "",
|
||||
description: "",
|
||||
type: "",
|
||||
mutable: false,
|
||||
defaultValue: "",
|
||||
icon: "",
|
||||
options: [],
|
||||
validationRegex: "",
|
||||
validationError: "",
|
||||
validationMin: undefined,
|
||||
validationMax: undefined,
|
||||
validationMonotonic: "",
|
||||
required: false,
|
||||
displayName: "",
|
||||
order: 0,
|
||||
ephemeral: false,
|
||||
name: "",
|
||||
description: "",
|
||||
type: "",
|
||||
mutable: false,
|
||||
defaultValue: "",
|
||||
icon: "",
|
||||
options: [],
|
||||
validationRegex: "",
|
||||
validationError: "",
|
||||
validationMin: undefined,
|
||||
validationMax: undefined,
|
||||
validationMonotonic: "",
|
||||
required: false,
|
||||
displayName: "",
|
||||
order: 0,
|
||||
ephemeral: false,
|
||||
};
|
||||
|
||||
// firstParameter is mutable string with a default value (parameter value not required).
|
||||
export const firstParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "first_parameter",
|
||||
displayName: "First parameter",
|
||||
type: "number",
|
||||
description: "This is first parameter.",
|
||||
icon: "/emojis/1f310.png",
|
||||
defaultValue: "123",
|
||||
mutable: true,
|
||||
order: 1,
|
||||
name: "first_parameter",
|
||||
displayName: "First parameter",
|
||||
type: "number",
|
||||
description: "This is first parameter.",
|
||||
icon: "/emojis/1f310.png",
|
||||
defaultValue: "123",
|
||||
mutable: true,
|
||||
order: 1,
|
||||
};
|
||||
|
||||
// secondParameter is immutable string with a default value (parameter value not required).
|
||||
export const secondParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "second_parameter",
|
||||
displayName: "Second parameter",
|
||||
type: "string",
|
||||
description: "This is second parameter.",
|
||||
defaultValue: "abc",
|
||||
order: 2,
|
||||
name: "second_parameter",
|
||||
displayName: "Second parameter",
|
||||
type: "string",
|
||||
description: "This is second parameter.",
|
||||
defaultValue: "abc",
|
||||
order: 2,
|
||||
};
|
||||
|
||||
// thirdParameter is mutable string with an empty default value (parameter value not required).
|
||||
export const thirdParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "third_parameter",
|
||||
type: "string",
|
||||
description: "This is third parameter.",
|
||||
defaultValue: "",
|
||||
mutable: true,
|
||||
order: 3,
|
||||
name: "third_parameter",
|
||||
type: "string",
|
||||
description: "This is third parameter.",
|
||||
defaultValue: "",
|
||||
mutable: true,
|
||||
order: 3,
|
||||
};
|
||||
|
||||
// fourthParameter is immutable boolean with a default "true" value (parameter value not required).
|
||||
export const fourthParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "fourth_parameter",
|
||||
type: "bool",
|
||||
description: "This is fourth parameter.",
|
||||
defaultValue: "true",
|
||||
order: 3,
|
||||
name: "fourth_parameter",
|
||||
type: "bool",
|
||||
description: "This is fourth parameter.",
|
||||
defaultValue: "true",
|
||||
order: 3,
|
||||
};
|
||||
|
||||
// fifthParameter is immutable "string with options", with a default option selected (parameter value not required).
|
||||
export const fifthParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "fifth_parameter",
|
||||
displayName: "Fifth parameter",
|
||||
type: "string",
|
||||
options: [
|
||||
{
|
||||
name: "ABC",
|
||||
description: "This is ABC",
|
||||
value: "abc",
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "DEF",
|
||||
description: "This is DEF",
|
||||
value: "def",
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "GHI",
|
||||
description: "This is GHI",
|
||||
value: "ghi",
|
||||
icon: "",
|
||||
},
|
||||
],
|
||||
description: "This is fifth parameter.",
|
||||
defaultValue: "def",
|
||||
order: 3,
|
||||
name: "fifth_parameter",
|
||||
displayName: "Fifth parameter",
|
||||
type: "string",
|
||||
options: [
|
||||
{
|
||||
name: "ABC",
|
||||
description: "This is ABC",
|
||||
value: "abc",
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "DEF",
|
||||
description: "This is DEF",
|
||||
value: "def",
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "GHI",
|
||||
description: "This is GHI",
|
||||
value: "ghi",
|
||||
icon: "",
|
||||
},
|
||||
],
|
||||
description: "This is fifth parameter.",
|
||||
defaultValue: "def",
|
||||
order: 3,
|
||||
};
|
||||
|
||||
// sixthParameter is mutable string without a default value (parameter value is required).
|
||||
export const sixthParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "sixth_parameter",
|
||||
displayName: "Sixth parameter",
|
||||
type: "number",
|
||||
description: "This is sixth parameter.",
|
||||
icon: "/emojis/1f310.png",
|
||||
required: true,
|
||||
mutable: true,
|
||||
order: 1,
|
||||
name: "sixth_parameter",
|
||||
displayName: "Sixth parameter",
|
||||
type: "number",
|
||||
description: "This is sixth parameter.",
|
||||
icon: "/emojis/1f310.png",
|
||||
required: true,
|
||||
mutable: true,
|
||||
order: 1,
|
||||
};
|
||||
|
||||
// seventhParameter is immutable string without a default value (parameter value is required).
|
||||
export const seventhParameter: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "seventh_parameter",
|
||||
displayName: "Seventh parameter",
|
||||
type: "string",
|
||||
description: "This is seventh parameter.",
|
||||
required: true,
|
||||
order: 1,
|
||||
name: "seventh_parameter",
|
||||
displayName: "Seventh parameter",
|
||||
type: "string",
|
||||
description: "This is seventh parameter.",
|
||||
required: true,
|
||||
order: 1,
|
||||
};
|
||||
|
||||
// randParamName returns a new parameter with a random name.
|
||||
// It helps to avoid cross-test interference when user-auto-fill triggers on
|
||||
// the same parameter name.
|
||||
export const randParamName = (p: RichParameter): RichParameter => {
|
||||
const name = `${p.name}_${Math.random().toString(36).substring(7)}`;
|
||||
return { ...p, name: name };
|
||||
const name = `${p.name}_${Math.random().toString(36).substring(7)}`;
|
||||
return { ...p, name: name };
|
||||
};
|
||||
|
||||
// Build options
|
||||
|
||||
export const firstBuildOption: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "first_build_option",
|
||||
displayName: "First build option",
|
||||
type: "string",
|
||||
description: "This is first build option.",
|
||||
icon: "/emojis/1f310.png",
|
||||
defaultValue: "ABCDEF",
|
||||
mutable: true,
|
||||
ephemeral: true,
|
||||
name: "first_build_option",
|
||||
displayName: "First build option",
|
||||
type: "string",
|
||||
description: "This is first build option.",
|
||||
icon: "/emojis/1f310.png",
|
||||
defaultValue: "ABCDEF",
|
||||
mutable: true,
|
||||
ephemeral: true,
|
||||
};
|
||||
|
||||
export const secondBuildOption: RichParameter = {
|
||||
...emptyParameter,
|
||||
...emptyParameter,
|
||||
|
||||
name: "second_build_option",
|
||||
displayName: "Second build option",
|
||||
type: "bool",
|
||||
description: "This is second build option.",
|
||||
defaultValue: "false",
|
||||
mutable: true,
|
||||
ephemeral: true,
|
||||
name: "second_build_option",
|
||||
displayName: "Second build option",
|
||||
type: "bool",
|
||||
description: "This is second build option.",
|
||||
defaultValue: "false",
|
||||
mutable: true,
|
||||
ephemeral: true,
|
||||
};
|
||||
|
||||
+127
-127
@@ -2,13 +2,13 @@ import { execSync } from "node:child_process";
|
||||
import * as path from "node:path";
|
||||
import { defineConfig } from "@playwright/test";
|
||||
import {
|
||||
coderMain,
|
||||
coderPort,
|
||||
coderdPProfPort,
|
||||
e2eFakeExperiment1,
|
||||
e2eFakeExperiment2,
|
||||
gitAuth,
|
||||
requireTerraformTests,
|
||||
coderMain,
|
||||
coderPort,
|
||||
coderdPProfPort,
|
||||
e2eFakeExperiment1,
|
||||
e2eFakeExperiment2,
|
||||
gitAuth,
|
||||
requireTerraformTests,
|
||||
} from "./constants";
|
||||
|
||||
export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT;
|
||||
@@ -24,141 +24,141 @@ export const storageState = path.join(__dirname, ".auth.json");
|
||||
let hasTerraform = false;
|
||||
let hasDocker = false;
|
||||
try {
|
||||
execSync("terraform --version");
|
||||
hasTerraform = true;
|
||||
execSync("terraform --version");
|
||||
hasTerraform = true;
|
||||
} catch {
|
||||
/* empty */
|
||||
/* empty */
|
||||
}
|
||||
|
||||
try {
|
||||
execSync("docker --version");
|
||||
hasDocker = true;
|
||||
execSync("docker --version");
|
||||
hasDocker = true;
|
||||
} catch {
|
||||
/* empty */
|
||||
/* empty */
|
||||
}
|
||||
|
||||
if (!hasTerraform || !hasDocker) {
|
||||
const msg = `Terraform provisioners require docker & terraform binaries to function. \n${
|
||||
hasTerraform
|
||||
? ""
|
||||
: "\tThe `terraform` executable is not present in the runtime environment.\n"
|
||||
}${
|
||||
hasDocker
|
||||
? ""
|
||||
: "\tThe `docker` executable is not present in the runtime environment.\n"
|
||||
}`;
|
||||
throw new Error(msg);
|
||||
const msg = `Terraform provisioners require docker & terraform binaries to function. \n${
|
||||
hasTerraform
|
||||
? ""
|
||||
: "\tThe `terraform` executable is not present in the runtime environment.\n"
|
||||
}${
|
||||
hasDocker
|
||||
? ""
|
||||
: "\tThe `docker` executable is not present in the runtime environment.\n"
|
||||
}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
const localURL = (port: number, path: string): string => {
|
||||
return `http://localhost:${port}${path}`;
|
||||
return `http://localhost:${port}${path}`;
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
projects: [
|
||||
{
|
||||
name: "testsSetup",
|
||||
testMatch: /global.setup\.ts/,
|
||||
},
|
||||
{
|
||||
name: "tests",
|
||||
testMatch: /.*\.spec\.ts/,
|
||||
dependencies: ["testsSetup"],
|
||||
use: { storageState },
|
||||
timeout: 50_000,
|
||||
},
|
||||
],
|
||||
reporter: [["./reporter.ts"]],
|
||||
use: {
|
||||
baseURL: `http://localhost:${coderPort}`,
|
||||
video: "retain-on-failure",
|
||||
...(wsEndpoint
|
||||
? {
|
||||
connectOptions: {
|
||||
wsEndpoint: wsEndpoint,
|
||||
},
|
||||
}
|
||||
: {
|
||||
launchOptions: {
|
||||
args: ["--disable-webgl"],
|
||||
},
|
||||
}),
|
||||
},
|
||||
webServer: {
|
||||
url: `http://localhost:${coderPort}/api/v2/deployment/config`,
|
||||
command: [
|
||||
`go run -tags embed ${coderMain} server`,
|
||||
"--global-config $(mktemp -d -t e2e-XXXXXXXXXX)",
|
||||
`--access-url=http://localhost:${coderPort}`,
|
||||
`--http-address=0.0.0.0:${coderPort}`,
|
||||
"--in-memory",
|
||||
"--telemetry=false",
|
||||
"--dangerous-disable-rate-limits",
|
||||
"--provisioner-daemons 10",
|
||||
// TODO: Enable some terraform provisioners
|
||||
`--provisioner-types=echo${requireTerraformTests ? ",terraform" : ""}`,
|
||||
"--provisioner-daemons=10",
|
||||
"--web-terminal-renderer=dom",
|
||||
"--pprof-enable",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" "),
|
||||
env: {
|
||||
...process.env,
|
||||
// Otherwise, the runner fails on Mac with: could not determine kind of name for C.uuid_string_t
|
||||
CGO_ENABLED: "0",
|
||||
projects: [
|
||||
{
|
||||
name: "testsSetup",
|
||||
testMatch: /global.setup\.ts/,
|
||||
},
|
||||
{
|
||||
name: "tests",
|
||||
testMatch: /.*\.spec\.ts/,
|
||||
dependencies: ["testsSetup"],
|
||||
use: { storageState },
|
||||
timeout: 50_000,
|
||||
},
|
||||
],
|
||||
reporter: [["./reporter.ts"]],
|
||||
use: {
|
||||
baseURL: `http://localhost:${coderPort}`,
|
||||
video: "retain-on-failure",
|
||||
...(wsEndpoint
|
||||
? {
|
||||
connectOptions: {
|
||||
wsEndpoint: wsEndpoint,
|
||||
},
|
||||
}
|
||||
: {
|
||||
launchOptions: {
|
||||
args: ["--disable-webgl"],
|
||||
},
|
||||
}),
|
||||
},
|
||||
webServer: {
|
||||
url: `http://localhost:${coderPort}/api/v2/deployment/config`,
|
||||
command: [
|
||||
`go run -tags embed ${coderMain} server`,
|
||||
"--global-config $(mktemp -d -t e2e-XXXXXXXXXX)",
|
||||
`--access-url=http://localhost:${coderPort}`,
|
||||
`--http-address=0.0.0.0:${coderPort}`,
|
||||
"--in-memory",
|
||||
"--telemetry=false",
|
||||
"--dangerous-disable-rate-limits",
|
||||
"--provisioner-daemons 10",
|
||||
// TODO: Enable some terraform provisioners
|
||||
`--provisioner-types=echo${requireTerraformTests ? ",terraform" : ""}`,
|
||||
"--provisioner-daemons=10",
|
||||
"--web-terminal-renderer=dom",
|
||||
"--pprof-enable",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" "),
|
||||
env: {
|
||||
...process.env,
|
||||
// Otherwise, the runner fails on Mac with: could not determine kind of name for C.uuid_string_t
|
||||
CGO_ENABLED: "0",
|
||||
|
||||
// This is the test provider for git auth with devices!
|
||||
CODER_GITAUTH_0_ID: gitAuth.deviceProvider,
|
||||
CODER_GITAUTH_0_TYPE: "github",
|
||||
CODER_GITAUTH_0_CLIENT_ID: "client",
|
||||
CODER_GITAUTH_0_CLIENT_SECRET: "secret",
|
||||
CODER_GITAUTH_0_DEVICE_FLOW: "true",
|
||||
CODER_GITAUTH_0_APP_INSTALL_URL:
|
||||
"https://github.com/apps/coder/installations/new",
|
||||
CODER_GITAUTH_0_APP_INSTALLATIONS_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.installationsPath,
|
||||
),
|
||||
CODER_GITAUTH_0_TOKEN_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.tokenPath,
|
||||
),
|
||||
CODER_GITAUTH_0_DEVICE_CODE_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.codePath,
|
||||
),
|
||||
CODER_GITAUTH_0_VALIDATE_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.validatePath,
|
||||
),
|
||||
// This is the test provider for git auth with devices!
|
||||
CODER_GITAUTH_0_ID: gitAuth.deviceProvider,
|
||||
CODER_GITAUTH_0_TYPE: "github",
|
||||
CODER_GITAUTH_0_CLIENT_ID: "client",
|
||||
CODER_GITAUTH_0_CLIENT_SECRET: "secret",
|
||||
CODER_GITAUTH_0_DEVICE_FLOW: "true",
|
||||
CODER_GITAUTH_0_APP_INSTALL_URL:
|
||||
"https://github.com/apps/coder/installations/new",
|
||||
CODER_GITAUTH_0_APP_INSTALLATIONS_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.installationsPath,
|
||||
),
|
||||
CODER_GITAUTH_0_TOKEN_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.tokenPath,
|
||||
),
|
||||
CODER_GITAUTH_0_DEVICE_CODE_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.codePath,
|
||||
),
|
||||
CODER_GITAUTH_0_VALIDATE_URL: localURL(
|
||||
gitAuth.devicePort,
|
||||
gitAuth.validatePath,
|
||||
),
|
||||
|
||||
CODER_GITAUTH_1_ID: gitAuth.webProvider,
|
||||
CODER_GITAUTH_1_TYPE: "github",
|
||||
CODER_GITAUTH_1_CLIENT_ID: "client",
|
||||
CODER_GITAUTH_1_CLIENT_SECRET: "secret",
|
||||
CODER_GITAUTH_1_AUTH_URL: localURL(gitAuth.webPort, gitAuth.authPath),
|
||||
CODER_GITAUTH_1_TOKEN_URL: localURL(gitAuth.webPort, gitAuth.tokenPath),
|
||||
CODER_GITAUTH_1_DEVICE_CODE_URL: localURL(
|
||||
gitAuth.webPort,
|
||||
gitAuth.codePath,
|
||||
),
|
||||
CODER_GITAUTH_1_VALIDATE_URL: localURL(
|
||||
gitAuth.webPort,
|
||||
gitAuth.validatePath,
|
||||
),
|
||||
CODER_PPROF_ADDRESS: `127.0.0.1:${coderdPProfPort}`,
|
||||
CODER_EXPERIMENTS: `multi-organization,${e2eFakeExperiment1},${e2eFakeExperiment2}`,
|
||||
CODER_GITAUTH_1_ID: gitAuth.webProvider,
|
||||
CODER_GITAUTH_1_TYPE: "github",
|
||||
CODER_GITAUTH_1_CLIENT_ID: "client",
|
||||
CODER_GITAUTH_1_CLIENT_SECRET: "secret",
|
||||
CODER_GITAUTH_1_AUTH_URL: localURL(gitAuth.webPort, gitAuth.authPath),
|
||||
CODER_GITAUTH_1_TOKEN_URL: localURL(gitAuth.webPort, gitAuth.tokenPath),
|
||||
CODER_GITAUTH_1_DEVICE_CODE_URL: localURL(
|
||||
gitAuth.webPort,
|
||||
gitAuth.codePath,
|
||||
),
|
||||
CODER_GITAUTH_1_VALIDATE_URL: localURL(
|
||||
gitAuth.webPort,
|
||||
gitAuth.validatePath,
|
||||
),
|
||||
CODER_PPROF_ADDRESS: `127.0.0.1:${coderdPProfPort}`,
|
||||
CODER_EXPERIMENTS: `multi-organization,${e2eFakeExperiment1},${e2eFakeExperiment2}`,
|
||||
|
||||
// Tests for Deployment / User Authentication / OIDC
|
||||
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
||||
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
||||
CODER_OIDC_CLIENT_ID: "1234567890",
|
||||
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
||||
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
||||
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
||||
CODER_OIDC_ICON_URL: "/icon/google.svg",
|
||||
},
|
||||
reuseExistingServer: false,
|
||||
},
|
||||
// Tests for Deployment / User Authentication / OIDC
|
||||
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
||||
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
||||
CODER_OIDC_CLIENT_ID: "1234567890",
|
||||
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
||||
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
||||
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
||||
CODER_OIDC_ICON_URL: "/icon/google.svg",
|
||||
},
|
||||
reuseExistingServer: false,
|
||||
},
|
||||
});
|
||||
|
||||
Generated
+847
-847
File diff suppressed because it is too large
Load Diff
+28
-28
@@ -3,36 +3,36 @@ import { coderMain, coderPort, workspaceProxyPort } from "./constants";
|
||||
import { waitUntilUrlIsNotResponding } from "./helpers";
|
||||
|
||||
export const startWorkspaceProxy = async (
|
||||
token: string,
|
||||
token: string,
|
||||
): Promise<ChildProcess> => {
|
||||
const cp = spawn("go", ["run", coderMain, "wsproxy", "server"], {
|
||||
env: {
|
||||
...process.env,
|
||||
CODER_PRIMARY_ACCESS_URL: `http://127.0.0.1:${coderPort}`,
|
||||
CODER_PROXY_SESSION_TOKEN: token,
|
||||
CODER_HTTP_ADDRESS: `localhost:${workspaceProxyPort}`,
|
||||
},
|
||||
});
|
||||
cp.stdout.on("data", (data: Buffer) => {
|
||||
// eslint-disable-next-line no-console -- Log wsproxy activity
|
||||
console.log(
|
||||
`[wsproxy] [stdout] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
||||
);
|
||||
});
|
||||
cp.stderr.on("data", (data: Buffer) => {
|
||||
// eslint-disable-next-line no-console -- Log wsproxy activity
|
||||
console.log(
|
||||
`[wsproxy] [stderr] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
||||
);
|
||||
});
|
||||
return cp;
|
||||
const cp = spawn("go", ["run", coderMain, "wsproxy", "server"], {
|
||||
env: {
|
||||
...process.env,
|
||||
CODER_PRIMARY_ACCESS_URL: `http://127.0.0.1:${coderPort}`,
|
||||
CODER_PROXY_SESSION_TOKEN: token,
|
||||
CODER_HTTP_ADDRESS: `localhost:${workspaceProxyPort}`,
|
||||
},
|
||||
});
|
||||
cp.stdout.on("data", (data: Buffer) => {
|
||||
// eslint-disable-next-line no-console -- Log wsproxy activity
|
||||
console.log(
|
||||
`[wsproxy] [stdout] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
||||
);
|
||||
});
|
||||
cp.stderr.on("data", (data: Buffer) => {
|
||||
// eslint-disable-next-line no-console -- Log wsproxy activity
|
||||
console.log(
|
||||
`[wsproxy] [stderr] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
||||
);
|
||||
});
|
||||
return cp;
|
||||
};
|
||||
|
||||
export const stopWorkspaceProxy = async (cp: ChildProcess, goRun = true) => {
|
||||
exec(goRun ? `pkill -P ${cp.pid}` : `kill ${cp.pid}`, (error) => {
|
||||
if (error) {
|
||||
throw new Error(`exec error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
});
|
||||
await waitUntilUrlIsNotResponding(`http://127.0.0.1:${workspaceProxyPort}`);
|
||||
exec(goRun ? `pkill -P ${cp.pid}` : `kill ${cp.pid}`, (error) => {
|
||||
if (error) {
|
||||
throw new Error(`exec error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
});
|
||||
await waitUntilUrlIsNotResponding(`http://127.0.0.1:${workspaceProxyPort}`);
|
||||
};
|
||||
|
||||
+135
-135
@@ -2,172 +2,172 @@ import * as fs from "node:fs/promises";
|
||||
import type { Writable } from "node:stream";
|
||||
/* eslint-disable no-console -- Logging is sort of the whole point here */
|
||||
import type {
|
||||
FullConfig,
|
||||
FullResult,
|
||||
Reporter,
|
||||
Suite,
|
||||
TestCase,
|
||||
TestError,
|
||||
TestResult,
|
||||
FullConfig,
|
||||
FullResult,
|
||||
Reporter,
|
||||
Suite,
|
||||
TestCase,
|
||||
TestError,
|
||||
TestResult,
|
||||
} from "@playwright/test/reporter";
|
||||
import { API } from "api/api";
|
||||
import { coderdPProfPort, enterpriseLicense } from "./constants";
|
||||
|
||||
class CoderReporter implements Reporter {
|
||||
config: FullConfig | null = null;
|
||||
testOutput = new Map<string, Array<[Writable, string]>>();
|
||||
passedCount = 0;
|
||||
skippedCount = 0;
|
||||
failedTests: TestCase[] = [];
|
||||
timedOutTests: TestCase[] = [];
|
||||
config: FullConfig | null = null;
|
||||
testOutput = new Map<string, Array<[Writable, string]>>();
|
||||
passedCount = 0;
|
||||
skippedCount = 0;
|
||||
failedTests: TestCase[] = [];
|
||||
timedOutTests: TestCase[] = [];
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
this.config = config;
|
||||
console.log(`==> Running ${suite.allTests().length} tests`);
|
||||
}
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
this.config = config;
|
||||
console.log(`==> Running ${suite.allTests().length} tests`);
|
||||
}
|
||||
|
||||
onTestBegin(test: TestCase) {
|
||||
this.testOutput.set(test.id, []);
|
||||
console.log(`==> Starting test ${test.title}`);
|
||||
}
|
||||
onTestBegin(test: TestCase) {
|
||||
this.testOutput.set(test.id, []);
|
||||
console.log(`==> Starting test ${test.title}`);
|
||||
}
|
||||
|
||||
onStdOut(chunk: string, test?: TestCase, _?: TestResult): void {
|
||||
// If there's no associated test, just print it now
|
||||
if (!test) {
|
||||
for (const line of logLines(chunk)) {
|
||||
console.log(`[stdout] ${line}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Will be printed if the test fails
|
||||
this.testOutput.get(test.id)!.push([process.stdout, chunk]);
|
||||
}
|
||||
onStdOut(chunk: string, test?: TestCase, _?: TestResult): void {
|
||||
// If there's no associated test, just print it now
|
||||
if (!test) {
|
||||
for (const line of logLines(chunk)) {
|
||||
console.log(`[stdout] ${line}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Will be printed if the test fails
|
||||
this.testOutput.get(test.id)!.push([process.stdout, chunk]);
|
||||
}
|
||||
|
||||
onStdErr(chunk: string, test?: TestCase, _?: TestResult): void {
|
||||
// If there's no associated test, just print it now
|
||||
if (!test) {
|
||||
for (const line of logLines(chunk)) {
|
||||
console.error(`[stderr] ${line}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Will be printed if the test fails
|
||||
this.testOutput.get(test.id)!.push([process.stderr, chunk]);
|
||||
}
|
||||
onStdErr(chunk: string, test?: TestCase, _?: TestResult): void {
|
||||
// If there's no associated test, just print it now
|
||||
if (!test) {
|
||||
for (const line of logLines(chunk)) {
|
||||
console.error(`[stderr] ${line}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Will be printed if the test fails
|
||||
this.testOutput.get(test.id)!.push([process.stderr, chunk]);
|
||||
}
|
||||
|
||||
async onTestEnd(test: TestCase, result: TestResult) {
|
||||
try {
|
||||
if (test.expectedStatus === "skipped") {
|
||||
console.log(`==> Skipping test ${test.title}`);
|
||||
this.skippedCount++;
|
||||
return;
|
||||
}
|
||||
async onTestEnd(test: TestCase, result: TestResult) {
|
||||
try {
|
||||
if (test.expectedStatus === "skipped") {
|
||||
console.log(`==> Skipping test ${test.title}`);
|
||||
this.skippedCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`==> Finished test ${test.title}: ${result.status}`);
|
||||
console.log(`==> Finished test ${test.title}: ${result.status}`);
|
||||
|
||||
if (result.status === "passed") {
|
||||
this.passedCount++;
|
||||
return;
|
||||
}
|
||||
if (result.status === "passed") {
|
||||
this.passedCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.status === "failed") {
|
||||
this.failedTests.push(test);
|
||||
}
|
||||
if (result.status === "failed") {
|
||||
this.failedTests.push(test);
|
||||
}
|
||||
|
||||
if (result.status === "timedOut") {
|
||||
this.timedOutTests.push(test);
|
||||
}
|
||||
if (result.status === "timedOut") {
|
||||
this.timedOutTests.push(test);
|
||||
}
|
||||
|
||||
const fsTestTitle = test.title.replaceAll(" ", "-");
|
||||
const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`;
|
||||
await exportDebugPprof(outputFile);
|
||||
const fsTestTitle = test.title.replaceAll(" ", "-");
|
||||
const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`;
|
||||
await exportDebugPprof(outputFile);
|
||||
|
||||
console.log(`Data from pprof has been saved to ${outputFile}`);
|
||||
console.log("==> Output");
|
||||
const output = this.testOutput.get(test.id)!;
|
||||
for (const [target, chunk] of output) {
|
||||
target.write(`${chunk.replace(/\n$/g, "")}\n`);
|
||||
}
|
||||
console.log(`Data from pprof has been saved to ${outputFile}`);
|
||||
console.log("==> Output");
|
||||
const output = this.testOutput.get(test.id)!;
|
||||
for (const [target, chunk] of output) {
|
||||
target.write(`${chunk.replace(/\n$/g, "")}\n`);
|
||||
}
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.log("==> Errors");
|
||||
for (const error of result.errors) {
|
||||
reportError(error);
|
||||
}
|
||||
}
|
||||
if (result.errors.length > 0) {
|
||||
console.log("==> Errors");
|
||||
for (const error of result.errors) {
|
||||
reportError(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.attachments.length > 0) {
|
||||
console.log("==> Attachments");
|
||||
for (const attachment of result.attachments) {
|
||||
console.log(attachment);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.testOutput.delete(test.id);
|
||||
}
|
||||
}
|
||||
if (result.attachments.length > 0) {
|
||||
console.log("==> Attachments");
|
||||
for (const attachment of result.attachments) {
|
||||
console.log(attachment);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.testOutput.delete(test.id);
|
||||
}
|
||||
}
|
||||
|
||||
onEnd(result: FullResult) {
|
||||
console.log(`==> Tests ${result.status}`);
|
||||
if (!enterpriseLicense) {
|
||||
console.log(
|
||||
"==> Enterprise tests were skipped, because no license was provided",
|
||||
);
|
||||
}
|
||||
console.log(`${this.passedCount} passed`);
|
||||
if (this.skippedCount > 0) {
|
||||
console.log(`${this.skippedCount} skipped`);
|
||||
}
|
||||
if (this.failedTests.length > 0) {
|
||||
console.log(`${this.failedTests.length} failed`);
|
||||
for (const test of this.failedTests) {
|
||||
console.log(` ${test.location.file} › ${test.title}`);
|
||||
}
|
||||
}
|
||||
if (this.timedOutTests.length > 0) {
|
||||
console.log(`${this.timedOutTests.length} timed out`);
|
||||
for (const test of this.timedOutTests) {
|
||||
console.log(` ${test.location.file} › ${test.title}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
onEnd(result: FullResult) {
|
||||
console.log(`==> Tests ${result.status}`);
|
||||
if (!enterpriseLicense) {
|
||||
console.log(
|
||||
"==> Enterprise tests were skipped, because no license was provided",
|
||||
);
|
||||
}
|
||||
console.log(`${this.passedCount} passed`);
|
||||
if (this.skippedCount > 0) {
|
||||
console.log(`${this.skippedCount} skipped`);
|
||||
}
|
||||
if (this.failedTests.length > 0) {
|
||||
console.log(`${this.failedTests.length} failed`);
|
||||
for (const test of this.failedTests) {
|
||||
console.log(` ${test.location.file} › ${test.title}`);
|
||||
}
|
||||
}
|
||||
if (this.timedOutTests.length > 0) {
|
||||
console.log(`${this.timedOutTests.length} timed out`);
|
||||
for (const test of this.timedOutTests) {
|
||||
console.log(` ${test.location.file} › ${test.title}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logLines = (chunk: string | Buffer): string[] => {
|
||||
if (chunk instanceof Buffer) {
|
||||
// When running in a debugger, the input to this is a Buffer instead of a string.
|
||||
// Unsure why, but this prevents the `trimEnd` from throwing an error.
|
||||
return [chunk.toString()];
|
||||
}
|
||||
return chunk.trimEnd().split("\n");
|
||||
if (chunk instanceof Buffer) {
|
||||
// When running in a debugger, the input to this is a Buffer instead of a string.
|
||||
// Unsure why, but this prevents the `trimEnd` from throwing an error.
|
||||
return [chunk.toString()];
|
||||
}
|
||||
return chunk.trimEnd().split("\n");
|
||||
};
|
||||
|
||||
const exportDebugPprof = async (outputFile: string) => {
|
||||
const axiosInstance = API.getAxiosInstance();
|
||||
const response = await axiosInstance.get(
|
||||
`http://127.0.0.1:${coderdPProfPort}/debug/pprof/goroutine?debug=1`,
|
||||
);
|
||||
const axiosInstance = API.getAxiosInstance();
|
||||
const response = await axiosInstance.get(
|
||||
`http://127.0.0.1:${coderdPProfPort}/debug/pprof/goroutine?debug=1`,
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Error: Received status code ${response.status}`);
|
||||
}
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Error: Received status code ${response.status}`);
|
||||
}
|
||||
|
||||
await fs.writeFile(outputFile, response.data);
|
||||
await fs.writeFile(outputFile, response.data);
|
||||
};
|
||||
|
||||
const reportError = (error: TestError) => {
|
||||
if (error.location) {
|
||||
console.log(`${error.location.file}:${error.location.line}:`);
|
||||
}
|
||||
if (error.snippet) {
|
||||
console.log(error.snippet);
|
||||
}
|
||||
if (error.location) {
|
||||
console.log(`${error.location.file}:${error.location.line}:`);
|
||||
}
|
||||
if (error.snippet) {
|
||||
console.log(error.snippet);
|
||||
}
|
||||
|
||||
if (error.message) {
|
||||
console.log(error.message);
|
||||
} else {
|
||||
console.log(error);
|
||||
}
|
||||
if (error.message) {
|
||||
console.log(error.message);
|
||||
} else {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars -- Playwright config uses it
|
||||
|
||||
+53
-53
@@ -2,65 +2,65 @@ import { randomUUID } from "node:crypto";
|
||||
import * as http from "node:http";
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
stopWorkspace,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
stopWorkspace,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("app", async ({ context, page }) => {
|
||||
const appContent = "Hello World";
|
||||
const token = randomUUID();
|
||||
const srv = http
|
||||
.createServer((req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(appContent);
|
||||
})
|
||||
.listen(0);
|
||||
const addr = srv.address();
|
||||
if (typeof addr !== "object" || !addr) {
|
||||
throw new Error("Expected addr to be an object");
|
||||
}
|
||||
const appName = "test-app";
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
apps: [
|
||||
{
|
||||
url: `http://localhost:${addr.port}`,
|
||||
displayName: appName,
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const agent = await startAgent(page, token);
|
||||
const appContent = "Hello World";
|
||||
const token = randomUUID();
|
||||
const srv = http
|
||||
.createServer((req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(appContent);
|
||||
})
|
||||
.listen(0);
|
||||
const addr = srv.address();
|
||||
if (typeof addr !== "object" || !addr) {
|
||||
throw new Error("Expected addr to be an object");
|
||||
}
|
||||
const appName = "test-app";
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
apps: [
|
||||
{
|
||||
url: `http://localhost:${addr.port}`,
|
||||
displayName: appName,
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const agent = await startAgent(page, token);
|
||||
|
||||
// Wait for the web terminal to open in a new tab
|
||||
const pagePromise = context.waitForEvent("page");
|
||||
await page.getByText(appName).click();
|
||||
const app = await pagePromise;
|
||||
await app.waitForLoadState("domcontentloaded");
|
||||
await app.getByText(appContent).isVisible();
|
||||
// Wait for the web terminal to open in a new tab
|
||||
const pagePromise = context.waitForEvent("page");
|
||||
await page.getByText(appName).click();
|
||||
const app = await pagePromise;
|
||||
await app.waitForLoadState("domcontentloaded");
|
||||
await app.getByText(appContent).isVisible();
|
||||
|
||||
await stopWorkspace(page, workspaceName);
|
||||
await stopAgent(agent);
|
||||
await stopWorkspace(page, workspaceName);
|
||||
await stopAgent(agent);
|
||||
});
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
requiresEnterpriseLicense,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
requiresEnterpriseLicense,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("inspecting and filtering audit logs", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
const userName = "admin";
|
||||
// Do some stuff that should show up in the audit logs
|
||||
const templateName = await createTemplate(page);
|
||||
const workspaceName = await createWorkspace(page, templateName);
|
||||
const userName = "admin";
|
||||
// Do some stuff that should show up in the audit logs
|
||||
const templateName = await createTemplate(page);
|
||||
const workspaceName = await createWorkspace(page, templateName);
|
||||
|
||||
// Go to the audit history
|
||||
await page.goto("/audit");
|
||||
// Go to the audit history
|
||||
await page.goto("/audit");
|
||||
|
||||
// Make sure those things we did all actually show up
|
||||
await expect(page.getByText(`${userName} logged in`)).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${userName} created template ${templateName}`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${userName} created workspace ${workspaceName}`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${userName} started workspace ${workspaceName}`),
|
||||
).toBeVisible();
|
||||
// Make sure those things we did all actually show up
|
||||
await expect(page.getByText(`${userName} logged in`)).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${userName} created template ${templateName}`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${userName} created workspace ${workspaceName}`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByText(`${userName} started workspace ${workspaceName}`),
|
||||
).toBeVisible();
|
||||
|
||||
// Make sure we can inspect the details of the log item
|
||||
const createdWorkspace = page.locator(".MuiTableRow-root", {
|
||||
hasText: `${userName} created workspace ${workspaceName}`,
|
||||
});
|
||||
await createdWorkspace.getByLabel("open-dropdown").click();
|
||||
await expect(
|
||||
createdWorkspace.getByText(`automatic_updates: "never"`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
createdWorkspace.getByText(`name: "${workspaceName}"`),
|
||||
).toBeVisible();
|
||||
// Make sure we can inspect the details of the log item
|
||||
const createdWorkspace = page.locator(".MuiTableRow-root", {
|
||||
hasText: `${userName} created workspace ${workspaceName}`,
|
||||
});
|
||||
await createdWorkspace.getByLabel("open-dropdown").click();
|
||||
await expect(
|
||||
createdWorkspace.getByText(`automatic_updates: "never"`),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
createdWorkspace.getByText(`name: "${workspaceName}"`),
|
||||
).toBeVisible();
|
||||
|
||||
const startedWorkspaceMessage = `${userName} started workspace ${workspaceName}`;
|
||||
const loginMessage = `${userName} logged in`;
|
||||
const startedWorkspaceMessage = `${userName} started workspace ${workspaceName}`;
|
||||
const loginMessage = `${userName} logged in`;
|
||||
|
||||
// Filter by resource type
|
||||
await page.getByText("All resource types").click();
|
||||
await page.getByRole("menu").getByText("Workspace Build").click();
|
||||
// Our workspace build should be visible
|
||||
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||
// Logins should no longer be visible
|
||||
await expect(page.getByText(loginMessage)).not.toBeVisible();
|
||||
// Filter by resource type
|
||||
await page.getByText("All resource types").click();
|
||||
await page.getByRole("menu").getByText("Workspace Build").click();
|
||||
// Our workspace build should be visible
|
||||
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||
// Logins should no longer be visible
|
||||
await expect(page.getByText(loginMessage)).not.toBeVisible();
|
||||
|
||||
// Clear filters, everything should be visible again
|
||||
await page.getByLabel("Clear filter").click();
|
||||
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||
await expect(page.getByText(loginMessage)).toBeVisible();
|
||||
// Clear filters, everything should be visible again
|
||||
await page.getByLabel("Clear filter").click();
|
||||
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||
await expect(page.getByText(loginMessage)).toBeVisible();
|
||||
|
||||
// Filter by action type
|
||||
await page.getByText("All actions").click();
|
||||
await page.getByRole("menu").getByText("Login").click();
|
||||
// Logins should be visible
|
||||
await expect(page.getByText(loginMessage)).toBeVisible();
|
||||
// Our workspace build should no longer be visible
|
||||
await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
|
||||
// Filter by action type
|
||||
await page.getByText("All actions").click();
|
||||
await page.getByRole("menu").getByText("Login").click();
|
||||
// Logins should be visible
|
||||
await expect(page.getByText(loginMessage)).toBeVisible();
|
||||
// Our workspace build should no longer be visible
|
||||
await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
|
||||
});
|
||||
|
||||
@@ -3,80 +3,80 @@ import { expectUrl } from "../../expectUrl";
|
||||
import { randomName, requiresEnterpriseLicense } from "../../helpers";
|
||||
|
||||
test("set application name", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
await page.goto("/deployment/appearance", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/appearance", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const applicationName = randomName();
|
||||
const applicationName = randomName();
|
||||
|
||||
// Fill out the form
|
||||
const form = page.locator("form", { hasText: "Application name" });
|
||||
await form
|
||||
.getByLabel("Application name", { exact: true })
|
||||
.fill(applicationName);
|
||||
await form.getByRole("button", { name: "Submit" }).click();
|
||||
// Fill out the form
|
||||
const form = page.locator("form", { hasText: "Application name" });
|
||||
await form
|
||||
.getByLabel("Application name", { exact: true })
|
||||
.fill(applicationName);
|
||||
await form.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
// Open a new session without cookies to see the login page
|
||||
const browser = await chromium.launch();
|
||||
const incognitoContext = await browser.newContext();
|
||||
await incognitoContext.clearCookies();
|
||||
const incognitoPage = await incognitoContext.newPage();
|
||||
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
||||
// Open a new session without cookies to see the login page
|
||||
const browser = await chromium.launch();
|
||||
const incognitoContext = await browser.newContext();
|
||||
await incognitoContext.clearCookies();
|
||||
const incognitoPage = await incognitoContext.newPage();
|
||||
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// Verify the application name
|
||||
const name = incognitoPage.locator("h1", { hasText: applicationName });
|
||||
await expect(name).toBeVisible();
|
||||
// Verify the application name
|
||||
const name = incognitoPage.locator("h1", { hasText: applicationName });
|
||||
await expect(name).toBeVisible();
|
||||
|
||||
// Shut down browser
|
||||
await incognitoPage.close();
|
||||
await browser.close();
|
||||
// Shut down browser
|
||||
await incognitoPage.close();
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test("set application logo", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
await page.goto("/deployment/appearance", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/appearance", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const imageLink = "/icon/azure.png";
|
||||
const imageLink = "/icon/azure.png";
|
||||
|
||||
// Fill out the form
|
||||
const form = page.locator("form", { hasText: "Logo URL" });
|
||||
await form.getByLabel("Logo URL", { exact: true }).fill(imageLink);
|
||||
await form.getByRole("button", { name: "Submit" }).click();
|
||||
// Fill out the form
|
||||
const form = page.locator("form", { hasText: "Logo URL" });
|
||||
await form.getByLabel("Logo URL", { exact: true }).fill(imageLink);
|
||||
await form.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
// Open a new session without cookies to see the login page
|
||||
const browser = await chromium.launch();
|
||||
const incognitoContext = await browser.newContext();
|
||||
await incognitoContext.clearCookies();
|
||||
const incognitoPage = await incognitoContext.newPage();
|
||||
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
||||
// Open a new session without cookies to see the login page
|
||||
const browser = await chromium.launch();
|
||||
const incognitoContext = await browser.newContext();
|
||||
await incognitoContext.clearCookies();
|
||||
const incognitoPage = await incognitoContext.newPage();
|
||||
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// Verify banner
|
||||
const logo = incognitoPage.locator("img.application-logo");
|
||||
await expect(logo).toHaveAttribute("src", imageLink);
|
||||
// Verify banner
|
||||
const logo = incognitoPage.locator("img.application-logo");
|
||||
await expect(logo).toHaveAttribute("src", imageLink);
|
||||
|
||||
// Shut down browser
|
||||
await incognitoPage.close();
|
||||
await browser.close();
|
||||
// Shut down browser
|
||||
await incognitoPage.close();
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test("set service banner", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
await page.goto("/deployment/appearance", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/appearance", { waitUntil: "domcontentloaded" });
|
||||
|
||||
const message = "Mary has a little lamb.";
|
||||
const message = "Mary has a little lamb.";
|
||||
|
||||
// Fill out the form
|
||||
const form = page.locator("form", { hasText: "Service Banner" });
|
||||
await form.getByLabel("Enabled", { exact: true }).check();
|
||||
await form.getByLabel("Message", { exact: true }).fill(message);
|
||||
await form.getByRole("button", { name: "Submit" }).click();
|
||||
// Fill out the form
|
||||
const form = page.locator("form", { hasText: "Service Banner" });
|
||||
await form.getByLabel("Enabled", { exact: true }).check();
|
||||
await form.getByLabel("Message", { exact: true }).fill(message);
|
||||
await form.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
// Verify service banner
|
||||
await page.goto("/workspaces", { waitUntil: "domcontentloaded" });
|
||||
await expectUrl(page).toHavePathName("/workspaces");
|
||||
// Verify service banner
|
||||
await page.goto("/workspaces", { waitUntil: "domcontentloaded" });
|
||||
await expectUrl(page).toHavePathName("/workspaces");
|
||||
|
||||
const bar = page.locator("div.service-banner", { hasText: message });
|
||||
await expect(bar).toBeVisible();
|
||||
const bar = page.locator("div.service-banner", { hasText: message });
|
||||
await expect(bar).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -4,36 +4,36 @@ import { setupApiCalls } from "../../api";
|
||||
import { e2eFakeExperiment1, e2eFakeExperiment2 } from "../../constants";
|
||||
|
||||
test("experiments", async ({ page }) => {
|
||||
await setupApiCalls(page);
|
||||
await setupApiCalls(page);
|
||||
|
||||
// Load experiments from backend API
|
||||
const availableExperiments = await API.getAvailableExperiments();
|
||||
// Load experiments from backend API
|
||||
const availableExperiments = await API.getAvailableExperiments();
|
||||
|
||||
// Verify if the site lists the same experiments
|
||||
await page.goto("/deployment/general", { waitUntil: "networkidle" });
|
||||
// Verify if the site lists the same experiments
|
||||
await page.goto("/deployment/general", { waitUntil: "networkidle" });
|
||||
|
||||
const experimentsLocator = page.locator(
|
||||
"div.options-table tr.option-experiments ul.option-array",
|
||||
);
|
||||
await expect(experimentsLocator).toBeVisible();
|
||||
const experimentsLocator = page.locator(
|
||||
"div.options-table tr.option-experiments ul.option-array",
|
||||
);
|
||||
await expect(experimentsLocator).toBeVisible();
|
||||
|
||||
// Firstly, check if all enabled experiments are listed
|
||||
expect(
|
||||
experimentsLocator.locator(
|
||||
`li.option-array-item-${e2eFakeExperiment1}.option-enabled`,
|
||||
),
|
||||
).toBeVisible;
|
||||
expect(
|
||||
experimentsLocator.locator(
|
||||
`li.option-array-item-${e2eFakeExperiment2}.option-enabled`,
|
||||
),
|
||||
).toBeVisible;
|
||||
// Firstly, check if all enabled experiments are listed
|
||||
expect(
|
||||
experimentsLocator.locator(
|
||||
`li.option-array-item-${e2eFakeExperiment1}.option-enabled`,
|
||||
),
|
||||
).toBeVisible;
|
||||
expect(
|
||||
experimentsLocator.locator(
|
||||
`li.option-array-item-${e2eFakeExperiment2}.option-enabled`,
|
||||
),
|
||||
).toBeVisible;
|
||||
|
||||
// Secondly, check if available experiments are listed
|
||||
for (const experiment of availableExperiments.safe) {
|
||||
const experimentLocator = experimentsLocator.locator(
|
||||
`li.option-array-item-${experiment}`,
|
||||
);
|
||||
await expect(experimentLocator).toBeVisible();
|
||||
}
|
||||
// Secondly, check if available experiments are listed
|
||||
for (const experiment of availableExperiments.safe) {
|
||||
const experimentLocator = experimentsLocator.locator(
|
||||
`li.option-array-item-${experiment}`,
|
||||
);
|
||||
await expect(experimentLocator).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,29 +2,29 @@ import { expect, test } from "@playwright/test";
|
||||
import { requiresEnterpriseLicense } from "../../helpers";
|
||||
|
||||
test("license was added successfully", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
|
||||
const firstLicense = page.locator(".licenses > .license-card", {
|
||||
hasText: "#1",
|
||||
});
|
||||
await expect(firstLicense).toBeVisible();
|
||||
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
|
||||
const firstLicense = page.locator(".licenses > .license-card", {
|
||||
hasText: "#1",
|
||||
});
|
||||
await expect(firstLicense).toBeVisible();
|
||||
|
||||
// Trial vs. Enterprise?
|
||||
const accountType = firstLicense.locator(".account-type");
|
||||
await expect(accountType).toHaveText("Enterprise");
|
||||
// Trial vs. Enterprise?
|
||||
const accountType = firstLicense.locator(".account-type");
|
||||
await expect(accountType).toHaveText("Enterprise");
|
||||
|
||||
// User limit 1/1
|
||||
const userLimit = firstLicense.locator(".user-limit");
|
||||
await expect(userLimit).toHaveText("1 / 1");
|
||||
// User limit 1/1
|
||||
const userLimit = firstLicense.locator(".user-limit");
|
||||
await expect(userLimit).toHaveText("1 / 1");
|
||||
|
||||
// License should not be expired yet
|
||||
const licenseExpires = firstLicense.locator(".license-expires");
|
||||
const licenseExpiresDate = new Date(await licenseExpires.innerText());
|
||||
const now = new Date();
|
||||
expect(licenseExpiresDate.getTime()).toBeGreaterThan(now.getTime());
|
||||
// License should not be expired yet
|
||||
const licenseExpires = firstLicense.locator(".license-expires");
|
||||
const licenseExpiresDate = new Date(await licenseExpires.innerText());
|
||||
const now = new Date();
|
||||
expect(licenseExpiresDate.getTime()).toBeGreaterThan(now.getTime());
|
||||
|
||||
// "Remove" button should be visible
|
||||
const removeButton = firstLicense.locator(".remove-button");
|
||||
await expect(removeButton).toBeVisible();
|
||||
// "Remove" button should be visible
|
||||
const removeButton = firstLicense.locator(".remove-button");
|
||||
await expect(removeButton).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { API } from "api/api";
|
||||
import {
|
||||
setupApiCalls,
|
||||
verifyConfigFlagArray,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagDuration,
|
||||
verifyConfigFlagNumber,
|
||||
verifyConfigFlagString,
|
||||
setupApiCalls,
|
||||
verifyConfigFlagArray,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagDuration,
|
||||
verifyConfigFlagNumber,
|
||||
verifyConfigFlagString,
|
||||
} from "../../api";
|
||||
|
||||
test("enabled network settings", async ({ page }) => {
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
|
||||
await page.goto("/deployment/network", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/network", { waitUntil: "domcontentloaded" });
|
||||
|
||||
await verifyConfigFlagString(page, config, "access-url");
|
||||
await verifyConfigFlagBoolean(page, config, "block-direct-connections");
|
||||
await verifyConfigFlagBoolean(page, config, "browser-only");
|
||||
await verifyConfigFlagBoolean(page, config, "derp-force-websockets");
|
||||
await verifyConfigFlagBoolean(page, config, "derp-server-enable");
|
||||
await verifyConfigFlagString(page, config, "derp-server-region-code");
|
||||
await verifyConfigFlagString(page, config, "derp-server-region-code");
|
||||
await verifyConfigFlagNumber(page, config, "derp-server-region-id");
|
||||
await verifyConfigFlagString(page, config, "derp-server-region-name");
|
||||
await verifyConfigFlagArray(page, config, "derp-server-stun-addresses");
|
||||
await verifyConfigFlagBoolean(page, config, "disable-password-auth");
|
||||
await verifyConfigFlagBoolean(page, config, "disable-session-expiry-refresh");
|
||||
await verifyConfigFlagDuration(page, config, "max-token-lifetime");
|
||||
await verifyConfigFlagDuration(page, config, "proxy-health-interval");
|
||||
await verifyConfigFlagBoolean(page, config, "redirect-to-access-url");
|
||||
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
||||
await verifyConfigFlagDuration(page, config, "session-duration");
|
||||
await verifyConfigFlagString(page, config, "tls-address");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
||||
await verifyConfigFlagString(page, config, "tls-client-auth");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
||||
await verifyConfigFlagString(page, config, "tls-min-version");
|
||||
await verifyConfigFlagString(page, config, "access-url");
|
||||
await verifyConfigFlagBoolean(page, config, "block-direct-connections");
|
||||
await verifyConfigFlagBoolean(page, config, "browser-only");
|
||||
await verifyConfigFlagBoolean(page, config, "derp-force-websockets");
|
||||
await verifyConfigFlagBoolean(page, config, "derp-server-enable");
|
||||
await verifyConfigFlagString(page, config, "derp-server-region-code");
|
||||
await verifyConfigFlagString(page, config, "derp-server-region-code");
|
||||
await verifyConfigFlagNumber(page, config, "derp-server-region-id");
|
||||
await verifyConfigFlagString(page, config, "derp-server-region-name");
|
||||
await verifyConfigFlagArray(page, config, "derp-server-stun-addresses");
|
||||
await verifyConfigFlagBoolean(page, config, "disable-password-auth");
|
||||
await verifyConfigFlagBoolean(page, config, "disable-session-expiry-refresh");
|
||||
await verifyConfigFlagDuration(page, config, "max-token-lifetime");
|
||||
await verifyConfigFlagDuration(page, config, "proxy-health-interval");
|
||||
await verifyConfigFlagBoolean(page, config, "redirect-to-access-url");
|
||||
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
||||
await verifyConfigFlagDuration(page, config, "session-duration");
|
||||
await verifyConfigFlagString(page, config, "tls-address");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
||||
await verifyConfigFlagString(page, config, "tls-client-auth");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
||||
await verifyConfigFlagString(page, config, "tls-min-version");
|
||||
});
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { API } from "api/api";
|
||||
import {
|
||||
setupApiCalls,
|
||||
verifyConfigFlagArray,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagDuration,
|
||||
verifyConfigFlagEmpty,
|
||||
verifyConfigFlagString,
|
||||
setupApiCalls,
|
||||
verifyConfigFlagArray,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagDuration,
|
||||
verifyConfigFlagEmpty,
|
||||
verifyConfigFlagString,
|
||||
} from "../../api";
|
||||
|
||||
test("enabled observability settings", async ({ page }) => {
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
|
||||
await page.goto("/deployment/observability", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.goto("/deployment/observability", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
await verifyConfigFlagBoolean(page, config, "trace-logs");
|
||||
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
||||
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
||||
await verifyConfigFlagDuration(page, config, "health-check-refresh");
|
||||
await verifyConfigFlagEmpty(page, "health-check-threshold-database");
|
||||
await verifyConfigFlagString(page, config, "log-human");
|
||||
await verifyConfigFlagString(page, config, "prometheus-address");
|
||||
await verifyConfigFlagArray(
|
||||
page,
|
||||
config,
|
||||
"prometheus-aggregate-agent-stats-by",
|
||||
);
|
||||
await verifyConfigFlagBoolean(page, config, "prometheus-collect-agent-stats");
|
||||
await verifyConfigFlagBoolean(page, config, "prometheus-collect-db-metrics");
|
||||
await verifyConfigFlagBoolean(page, config, "prometheus-enable");
|
||||
await verifyConfigFlagBoolean(page, config, "trace-datadog");
|
||||
await verifyConfigFlagBoolean(page, config, "trace");
|
||||
await verifyConfigFlagBoolean(page, config, "verbose");
|
||||
await verifyConfigFlagBoolean(page, config, "pprof-enable");
|
||||
await verifyConfigFlagBoolean(page, config, "trace-logs");
|
||||
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
||||
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
||||
await verifyConfigFlagDuration(page, config, "health-check-refresh");
|
||||
await verifyConfigFlagEmpty(page, "health-check-threshold-database");
|
||||
await verifyConfigFlagString(page, config, "log-human");
|
||||
await verifyConfigFlagString(page, config, "prometheus-address");
|
||||
await verifyConfigFlagArray(
|
||||
page,
|
||||
config,
|
||||
"prometheus-aggregate-agent-stats-by",
|
||||
);
|
||||
await verifyConfigFlagBoolean(page, config, "prometheus-collect-agent-stats");
|
||||
await verifyConfigFlagBoolean(page, config, "prometheus-collect-db-metrics");
|
||||
await verifyConfigFlagBoolean(page, config, "prometheus-enable");
|
||||
await verifyConfigFlagBoolean(page, config, "trace-datadog");
|
||||
await verifyConfigFlagBoolean(page, config, "trace");
|
||||
await verifyConfigFlagBoolean(page, config, "verbose");
|
||||
await verifyConfigFlagBoolean(page, config, "pprof-enable");
|
||||
});
|
||||
|
||||
@@ -2,45 +2,45 @@ import type { Page } from "@playwright/test";
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { API, type DeploymentConfig } from "api/api";
|
||||
import {
|
||||
findConfigOption,
|
||||
setupApiCalls,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagNumber,
|
||||
verifyConfigFlagString,
|
||||
findConfigOption,
|
||||
setupApiCalls,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagNumber,
|
||||
verifyConfigFlagString,
|
||||
} from "../../api";
|
||||
|
||||
test("enabled security settings", async ({ page }) => {
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
|
||||
await page.goto("/deployment/security", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/security", { waitUntil: "domcontentloaded" });
|
||||
|
||||
await verifyConfigFlagString(page, config, "ssh-keygen-algorithm");
|
||||
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
||||
await verifyConfigFlagBoolean(page, config, "disable-owner-workspace-access");
|
||||
await verifyConfigFlagString(page, config, "ssh-keygen-algorithm");
|
||||
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
||||
await verifyConfigFlagBoolean(page, config, "disable-owner-workspace-access");
|
||||
|
||||
await verifyConfigFlagBoolean(page, config, "tls-redirect-http-to-https");
|
||||
await verifyStrictTransportSecurity(page, config);
|
||||
await verifyConfigFlagString(page, config, "tls-address");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
||||
await verifyConfigFlagString(page, config, "tls-client-auth");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
||||
await verifyConfigFlagString(page, config, "tls-min-version");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-redirect-http-to-https");
|
||||
await verifyStrictTransportSecurity(page, config);
|
||||
await verifyConfigFlagString(page, config, "tls-address");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
||||
await verifyConfigFlagString(page, config, "tls-client-auth");
|
||||
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
||||
await verifyConfigFlagString(page, config, "tls-min-version");
|
||||
});
|
||||
|
||||
async function verifyStrictTransportSecurity(
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
page: Page,
|
||||
config: DeploymentConfig,
|
||||
) {
|
||||
const flag = "strict-transport-security";
|
||||
const opt = findConfigOption(config, flag);
|
||||
if (opt.value !== 0) {
|
||||
await verifyConfigFlagNumber(page, config, flag);
|
||||
return;
|
||||
}
|
||||
const flag = "strict-transport-security";
|
||||
const opt = findConfigOption(config, flag);
|
||||
if (opt.value !== 0) {
|
||||
await verifyConfigFlagNumber(page, config, flag);
|
||||
return;
|
||||
}
|
||||
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-string`,
|
||||
);
|
||||
await expect(configOption).toHaveText("Disabled");
|
||||
const configOption = page.locator(
|
||||
`div.options-table .option-${flag} .option-value-string`,
|
||||
);
|
||||
await expect(configOption).toHaveText("Disabled");
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { API } from "api/api";
|
||||
import {
|
||||
setupApiCalls,
|
||||
verifyConfigFlagArray,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagEntries,
|
||||
verifyConfigFlagString,
|
||||
setupApiCalls,
|
||||
verifyConfigFlagArray,
|
||||
verifyConfigFlagBoolean,
|
||||
verifyConfigFlagEntries,
|
||||
verifyConfigFlagString,
|
||||
} from "../../api";
|
||||
|
||||
test("login with OIDC", async ({ page }) => {
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
await setupApiCalls(page);
|
||||
const config = await API.getDeploymentConfig();
|
||||
|
||||
await page.goto("/deployment/userauth", { waitUntil: "domcontentloaded" });
|
||||
await page.goto("/deployment/userauth", { waitUntil: "domcontentloaded" });
|
||||
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-group-auto-create");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-allow-signups");
|
||||
await verifyConfigFlagEntries(page, config, "oidc-auth-url-params");
|
||||
await verifyConfigFlagString(page, config, "oidc-client-id");
|
||||
await verifyConfigFlagArray(page, config, "oidc-email-domain");
|
||||
await verifyConfigFlagString(page, config, "oidc-email-field");
|
||||
await verifyConfigFlagEntries(page, config, "oidc-group-mapping");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-ignore-email-verified");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-ignore-userinfo");
|
||||
await verifyConfigFlagString(page, config, "oidc-issuer-url");
|
||||
await verifyConfigFlagString(page, config, "oidc-group-regex-filter");
|
||||
await verifyConfigFlagArray(page, config, "oidc-scopes");
|
||||
await verifyConfigFlagEntries(page, config, "oidc-user-role-mapping");
|
||||
await verifyConfigFlagString(page, config, "oidc-username-field");
|
||||
await verifyConfigFlagString(page, config, "oidc-sign-in-text");
|
||||
await verifyConfigFlagString(page, config, "oidc-icon-url");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-group-auto-create");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-allow-signups");
|
||||
await verifyConfigFlagEntries(page, config, "oidc-auth-url-params");
|
||||
await verifyConfigFlagString(page, config, "oidc-client-id");
|
||||
await verifyConfigFlagArray(page, config, "oidc-email-domain");
|
||||
await verifyConfigFlagString(page, config, "oidc-email-field");
|
||||
await verifyConfigFlagEntries(page, config, "oidc-group-mapping");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-ignore-email-verified");
|
||||
await verifyConfigFlagBoolean(page, config, "oidc-ignore-userinfo");
|
||||
await verifyConfigFlagString(page, config, "oidc-issuer-url");
|
||||
await verifyConfigFlagString(page, config, "oidc-group-regex-filter");
|
||||
await verifyConfigFlagArray(page, config, "oidc-scopes");
|
||||
await verifyConfigFlagEntries(page, config, "oidc-user-role-mapping");
|
||||
await verifyConfigFlagString(page, config, "oidc-username-field");
|
||||
await verifyConfigFlagString(page, config, "oidc-sign-in-text");
|
||||
await verifyConfigFlagString(page, config, "oidc-icon-url");
|
||||
});
|
||||
|
||||
@@ -6,100 +6,100 @@ import { randomName, requiresEnterpriseLicense } from "../../helpers";
|
||||
import { startWorkspaceProxy, stopWorkspaceProxy } from "../../proxy";
|
||||
|
||||
test("default proxy is online", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
|
||||
await page.goto("/deployment/workspace-proxies", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.goto("/deployment/workspace-proxies", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
// Verify if the default proxy is healthy
|
||||
const workspaceProxyPrimary = page.locator(
|
||||
`table.MuiTable-root tr[data-testid="primary"]`,
|
||||
);
|
||||
// Verify if the default proxy is healthy
|
||||
const workspaceProxyPrimary = page.locator(
|
||||
`table.MuiTable-root tr[data-testid="primary"]`,
|
||||
);
|
||||
|
||||
const workspaceProxyName = workspaceProxyPrimary.locator("td.name span");
|
||||
const workspaceProxyURL = workspaceProxyPrimary.locator("td.url");
|
||||
const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span");
|
||||
const workspaceProxyName = workspaceProxyPrimary.locator("td.name span");
|
||||
const workspaceProxyURL = workspaceProxyPrimary.locator("td.url");
|
||||
const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span");
|
||||
|
||||
await expect(workspaceProxyName).toHaveText("Default");
|
||||
await expect(workspaceProxyURL).toHaveText(`http://localhost:${coderPort}`);
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||
await expect(workspaceProxyName).toHaveText("Default");
|
||||
await expect(workspaceProxyURL).toHaveText(`http://localhost:${coderPort}`);
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||
});
|
||||
|
||||
test("custom proxy is online", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
|
||||
const proxyName = randomName();
|
||||
const proxyName = randomName();
|
||||
|
||||
// Register workspace proxy
|
||||
const proxyResponse = await API.createWorkspaceProxy({
|
||||
name: proxyName,
|
||||
display_name: "",
|
||||
icon: "/emojis/1f1e7-1f1f7.png",
|
||||
});
|
||||
expect(proxyResponse.proxy_token).toBeDefined();
|
||||
// Register workspace proxy
|
||||
const proxyResponse = await API.createWorkspaceProxy({
|
||||
name: proxyName,
|
||||
display_name: "",
|
||||
icon: "/emojis/1f1e7-1f1f7.png",
|
||||
});
|
||||
expect(proxyResponse.proxy_token).toBeDefined();
|
||||
|
||||
// Start "wsproxy server"
|
||||
const proxyServer = await startWorkspaceProxy(proxyResponse.proxy_token);
|
||||
await waitUntilWorkspaceProxyIsHealthy(page, proxyName);
|
||||
// Start "wsproxy server"
|
||||
const proxyServer = await startWorkspaceProxy(proxyResponse.proxy_token);
|
||||
await waitUntilWorkspaceProxyIsHealthy(page, proxyName);
|
||||
|
||||
// Verify if custom proxy is healthy
|
||||
await page.goto("/deployment/workspace-proxies", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
// Verify if custom proxy is healthy
|
||||
await page.goto("/deployment/workspace-proxies", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||
hasText: proxyName,
|
||||
});
|
||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||
hasText: proxyName,
|
||||
});
|
||||
|
||||
const workspaceProxyName = workspaceProxy.locator("td.name span");
|
||||
const workspaceProxyURL = workspaceProxy.locator("td.url");
|
||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||
const workspaceProxyName = workspaceProxy.locator("td.name span");
|
||||
const workspaceProxyURL = workspaceProxy.locator("td.url");
|
||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||
|
||||
await expect(workspaceProxyName).toHaveText(proxyName);
|
||||
await expect(workspaceProxyURL).toHaveText(
|
||||
`http://127.0.0.1:${workspaceProxyPort}`,
|
||||
);
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||
await expect(workspaceProxyName).toHaveText(proxyName);
|
||||
await expect(workspaceProxyURL).toHaveText(
|
||||
`http://127.0.0.1:${workspaceProxyPort}`,
|
||||
);
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||
|
||||
// Tear down the proxy
|
||||
await stopWorkspaceProxy(proxyServer);
|
||||
// Tear down the proxy
|
||||
await stopWorkspaceProxy(proxyServer);
|
||||
});
|
||||
|
||||
const waitUntilWorkspaceProxyIsHealthy = async (
|
||||
page: Page,
|
||||
proxyName: string,
|
||||
page: Page,
|
||||
proxyName: string,
|
||||
) => {
|
||||
await page.goto("/deployment/workspace-proxies", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.goto("/deployment/workspace-proxies", {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
const maxRetries = 30;
|
||||
const retryIntervalMs = 1000;
|
||||
let retries = 0;
|
||||
while (retries < maxRetries) {
|
||||
await page.reload();
|
||||
const maxRetries = 30;
|
||||
const retryIntervalMs = 1000;
|
||||
let retries = 0;
|
||||
while (retries < maxRetries) {
|
||||
await page.reload();
|
||||
|
||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||
hasText: proxyName,
|
||||
});
|
||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||
hasText: proxyName,
|
||||
});
|
||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||
|
||||
try {
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy", {
|
||||
timeout: 1_000,
|
||||
});
|
||||
return; // healthy!
|
||||
} catch {
|
||||
retries++;
|
||||
await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Workspace proxy "${proxyName}" is unhealthy after ${
|
||||
maxRetries * retryIntervalMs
|
||||
}ms`,
|
||||
);
|
||||
try {
|
||||
await expect(workspaceProxyStatus).toHaveText("Healthy", {
|
||||
timeout: 1_000,
|
||||
});
|
||||
return; // healthy!
|
||||
} catch {
|
||||
retries++;
|
||||
await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Workspace proxy "${proxyName}" is unhealthy after ${
|
||||
maxRetries * retryIntervalMs
|
||||
}ms`,
|
||||
);
|
||||
};
|
||||
|
||||
+131
-131
@@ -3,32 +3,32 @@ import { test } from "@playwright/test";
|
||||
import type { ExternalAuthDevice } from "api/typesGenerated";
|
||||
import { gitAuth } from "../constants";
|
||||
import {
|
||||
Awaiter,
|
||||
createServer,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithExternalAuth,
|
||||
Awaiter,
|
||||
createServer,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithExternalAuth,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest, resetExternalAuthKey } from "../hooks";
|
||||
|
||||
test.beforeAll(async ({ baseURL }) => {
|
||||
const srv = await createServer(gitAuth.webPort);
|
||||
const srv = await createServer(gitAuth.webPort);
|
||||
|
||||
// The GitHub validate endpoint returns the currently authenticated user!
|
||||
srv.use(gitAuth.validatePath, (req, res) => {
|
||||
res.write(JSON.stringify(ghUser));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||
const r = (Math.random() + 1).toString(36).substring(7);
|
||||
res.write(JSON.stringify({ access_token: r }));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.authPath, (req, res) => {
|
||||
res.redirect(
|
||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=${req.query.state}`,
|
||||
);
|
||||
});
|
||||
// The GitHub validate endpoint returns the currently authenticated user!
|
||||
srv.use(gitAuth.validatePath, (req, res) => {
|
||||
res.write(JSON.stringify(ghUser));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||
const r = (Math.random() + 1).toString(36).substring(7);
|
||||
res.write(JSON.stringify({ access_token: r }));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.authPath, (req, res) => {
|
||||
res.redirect(
|
||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=${req.query.state}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ context }) => resetExternalAuthKey(context));
|
||||
@@ -37,130 +37,130 @@ test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
// Ensures that a Git auth provider with the device flow functions and completes!
|
||||
test("external auth device", async ({ page }) => {
|
||||
const device: ExternalAuthDevice = {
|
||||
device_code: "1234",
|
||||
user_code: "1234-5678",
|
||||
expires_in: 900,
|
||||
interval: 1,
|
||||
verification_uri: "",
|
||||
};
|
||||
const device: ExternalAuthDevice = {
|
||||
device_code: "1234",
|
||||
user_code: "1234-5678",
|
||||
expires_in: 900,
|
||||
interval: 1,
|
||||
verification_uri: "",
|
||||
};
|
||||
|
||||
// Start a server to mock the GitHub API.
|
||||
const srv = await createServer(gitAuth.devicePort);
|
||||
srv.use(gitAuth.validatePath, (req, res) => {
|
||||
res.write(JSON.stringify(ghUser));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.codePath, (req, res) => {
|
||||
res.write(JSON.stringify(device));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.installationsPath, (req, res) => {
|
||||
res.write(JSON.stringify(ghInstall));
|
||||
res.end();
|
||||
});
|
||||
// Start a server to mock the GitHub API.
|
||||
const srv = await createServer(gitAuth.devicePort);
|
||||
srv.use(gitAuth.validatePath, (req, res) => {
|
||||
res.write(JSON.stringify(ghUser));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.codePath, (req, res) => {
|
||||
res.write(JSON.stringify(device));
|
||||
res.end();
|
||||
});
|
||||
srv.use(gitAuth.installationsPath, (req, res) => {
|
||||
res.write(JSON.stringify(ghInstall));
|
||||
res.end();
|
||||
});
|
||||
|
||||
const token = {
|
||||
access_token: "",
|
||||
error: "authorization_pending",
|
||||
error_description: "",
|
||||
};
|
||||
// First we send a result from the API that the token hasn't been
|
||||
// authorized yet to ensure the UI reacts properly.
|
||||
const sentPending = new Awaiter();
|
||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||
res.write(JSON.stringify(token));
|
||||
res.end();
|
||||
sentPending.done();
|
||||
});
|
||||
const token = {
|
||||
access_token: "",
|
||||
error: "authorization_pending",
|
||||
error_description: "",
|
||||
};
|
||||
// First we send a result from the API that the token hasn't been
|
||||
// authorized yet to ensure the UI reacts properly.
|
||||
const sentPending = new Awaiter();
|
||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||
res.write(JSON.stringify(token));
|
||||
res.end();
|
||||
sentPending.done();
|
||||
});
|
||||
|
||||
await page.goto(`/external-auth/${gitAuth.deviceProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.getByText(device.user_code).isVisible();
|
||||
await sentPending.wait();
|
||||
// Update the token to be valid and ensure the UI updates!
|
||||
token.error = "";
|
||||
token.access_token = "hello-world";
|
||||
await page.waitForSelector("text=1 organization authorized");
|
||||
await page.goto(`/external-auth/${gitAuth.deviceProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.getByText(device.user_code).isVisible();
|
||||
await sentPending.wait();
|
||||
// Update the token to be valid and ensure the UI updates!
|
||||
token.error = "";
|
||||
token.access_token = "hello-world";
|
||||
await page.waitForSelector("text=1 organization authorized");
|
||||
});
|
||||
|
||||
test("external auth web", async ({ page }) => {
|
||||
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
// This endpoint doesn't have the installations URL set intentionally!
|
||||
await page.waitForSelector("text=You've authenticated with GitHub!");
|
||||
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
// This endpoint doesn't have the installations URL set intentionally!
|
||||
await page.waitForSelector("text=You've authenticated with GitHub!");
|
||||
});
|
||||
|
||||
test("successful external auth from workspace", async ({ page }) => {
|
||||
const templateName = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithExternalAuth([
|
||||
{ id: gitAuth.webProvider, optional: false },
|
||||
]),
|
||||
);
|
||||
const templateName = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithExternalAuth([
|
||||
{ id: gitAuth.webProvider, optional: false },
|
||||
]),
|
||||
);
|
||||
|
||||
await createWorkspace(page, templateName, [], [], gitAuth.webProvider);
|
||||
await createWorkspace(page, templateName, [], [], gitAuth.webProvider);
|
||||
});
|
||||
|
||||
const ghUser: Endpoints["GET /user"]["response"]["data"] = {
|
||||
login: "kylecarbs",
|
||||
id: 7122116,
|
||||
node_id: "MDQ6VXNlcjcxMjIxMTY=",
|
||||
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||
gravatar_id: "",
|
||||
url: "https://api.github.com/users/kylecarbs",
|
||||
html_url: "https://github.com/kylecarbs",
|
||||
followers_url: "https://api.github.com/users/kylecarbs/followers",
|
||||
following_url:
|
||||
"https://api.github.com/users/kylecarbs/following{/other_user}",
|
||||
gists_url: "https://api.github.com/users/kylecarbs/gists{/gist_id}",
|
||||
starred_url: "https://api.github.com/users/kylecarbs/starred{/owner}{/repo}",
|
||||
subscriptions_url: "https://api.github.com/users/kylecarbs/subscriptions",
|
||||
organizations_url: "https://api.github.com/users/kylecarbs/orgs",
|
||||
repos_url: "https://api.github.com/users/kylecarbs/repos",
|
||||
events_url: "https://api.github.com/users/kylecarbs/events{/privacy}",
|
||||
received_events_url: "https://api.github.com/users/kylecarbs/received_events",
|
||||
type: "User",
|
||||
site_admin: false,
|
||||
name: "Kyle Carberry",
|
||||
company: "@coder",
|
||||
blog: "https://carberry.com",
|
||||
location: "Austin, TX",
|
||||
email: "kyle@carberry.com",
|
||||
hireable: null,
|
||||
bio: "hey there",
|
||||
twitter_username: "kylecarbs",
|
||||
public_repos: 52,
|
||||
public_gists: 9,
|
||||
followers: 208,
|
||||
following: 31,
|
||||
created_at: "2014-04-01T02:24:41Z",
|
||||
updated_at: "2023-06-26T13:03:09Z",
|
||||
login: "kylecarbs",
|
||||
id: 7122116,
|
||||
node_id: "MDQ6VXNlcjcxMjIxMTY=",
|
||||
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||
gravatar_id: "",
|
||||
url: "https://api.github.com/users/kylecarbs",
|
||||
html_url: "https://github.com/kylecarbs",
|
||||
followers_url: "https://api.github.com/users/kylecarbs/followers",
|
||||
following_url:
|
||||
"https://api.github.com/users/kylecarbs/following{/other_user}",
|
||||
gists_url: "https://api.github.com/users/kylecarbs/gists{/gist_id}",
|
||||
starred_url: "https://api.github.com/users/kylecarbs/starred{/owner}{/repo}",
|
||||
subscriptions_url: "https://api.github.com/users/kylecarbs/subscriptions",
|
||||
organizations_url: "https://api.github.com/users/kylecarbs/orgs",
|
||||
repos_url: "https://api.github.com/users/kylecarbs/repos",
|
||||
events_url: "https://api.github.com/users/kylecarbs/events{/privacy}",
|
||||
received_events_url: "https://api.github.com/users/kylecarbs/received_events",
|
||||
type: "User",
|
||||
site_admin: false,
|
||||
name: "Kyle Carberry",
|
||||
company: "@coder",
|
||||
blog: "https://carberry.com",
|
||||
location: "Austin, TX",
|
||||
email: "kyle@carberry.com",
|
||||
hireable: null,
|
||||
bio: "hey there",
|
||||
twitter_username: "kylecarbs",
|
||||
public_repos: 52,
|
||||
public_gists: 9,
|
||||
followers: 208,
|
||||
following: 31,
|
||||
created_at: "2014-04-01T02:24:41Z",
|
||||
updated_at: "2023-06-26T13:03:09Z",
|
||||
};
|
||||
|
||||
const ghInstall: Endpoints["GET /user/installations"]["response"]["data"] = {
|
||||
installations: [
|
||||
{
|
||||
id: 1,
|
||||
access_tokens_url: "",
|
||||
account: ghUser,
|
||||
app_id: 1,
|
||||
app_slug: "coder",
|
||||
created_at: "2014-04-01T02:24:41Z",
|
||||
events: [],
|
||||
html_url: "",
|
||||
permissions: {},
|
||||
repositories_url: "",
|
||||
repository_selection: "all",
|
||||
single_file_name: "",
|
||||
suspended_at: null,
|
||||
suspended_by: null,
|
||||
target_id: 1,
|
||||
target_type: "",
|
||||
updated_at: "2023-06-26T13:03:09Z",
|
||||
},
|
||||
],
|
||||
total_count: 1,
|
||||
installations: [
|
||||
{
|
||||
id: 1,
|
||||
access_tokens_url: "",
|
||||
account: ghUser,
|
||||
app_id: 1,
|
||||
app_slug: "coder",
|
||||
created_at: "2014-04-01T02:24:41Z",
|
||||
events: [],
|
||||
html_url: "",
|
||||
permissions: {},
|
||||
repositories_url: "",
|
||||
repository_selection: "all",
|
||||
single_file_name: "",
|
||||
suspended_at: null,
|
||||
suspended_by: null,
|
||||
target_id: 1,
|
||||
target_type: "",
|
||||
updated_at: "2023-06-26T13:03:09Z",
|
||||
},
|
||||
],
|
||||
total_count: 1,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
createGroup,
|
||||
createUser,
|
||||
getCurrentOrgId,
|
||||
setupApiCalls,
|
||||
createGroup,
|
||||
createUser,
|
||||
getCurrentOrgId,
|
||||
setupApiCalls,
|
||||
} from "../../api";
|
||||
import { requiresEnterpriseLicense } from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
@@ -11,24 +11,24 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("add members", async ({ page, baseURL }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
const numberOfMembers = 3;
|
||||
const users = await Promise.all(
|
||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||
);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
const numberOfMembers = 3;
|
||||
const users = await Promise.all(
|
||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||
);
|
||||
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
|
||||
for (const user of users) {
|
||||
await page.getByPlaceholder("User email or username").fill(user.username);
|
||||
await page.getByRole("option", { name: user.email }).click();
|
||||
await page.getByRole("button", { name: "Add user" }).click();
|
||||
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
|
||||
}
|
||||
for (const user of users) {
|
||||
await page.getByPlaceholder("User email or username").fill(user.username);
|
||||
await page.getByRole("option", { name: user.email }).click();
|
||||
await page.getByRole("button", { name: "Add user" }).click();
|
||||
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,25 +8,25 @@ test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
const DEFAULT_GROUP_NAME = "Everyone";
|
||||
|
||||
test(`Every user should be automatically added to the default '${DEFAULT_GROUP_NAME}' group upon creation`, async ({
|
||||
page,
|
||||
baseURL,
|
||||
page,
|
||||
baseURL,
|
||||
}) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const numberOfMembers = 3;
|
||||
const users = await Promise.all(
|
||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||
);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const numberOfMembers = 3;
|
||||
const users = await Promise.all(
|
||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||
);
|
||||
|
||||
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
|
||||
const groupRow = page.getByRole("row", { name: DEFAULT_GROUP_NAME });
|
||||
await groupRow.click();
|
||||
await expect(page).toHaveTitle(`${DEFAULT_GROUP_NAME} - Coder`);
|
||||
const groupRow = page.getByRole("row", { name: DEFAULT_GROUP_NAME });
|
||||
await groupRow.click();
|
||||
await expect(page).toHaveTitle(`${DEFAULT_GROUP_NAME} - Coder`);
|
||||
|
||||
for (const user of users) {
|
||||
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
|
||||
}
|
||||
for (const user of users) {
|
||||
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,26 +5,26 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("create group", async ({ page, baseURL }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
requiresEnterpriseLicense();
|
||||
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
|
||||
await page.getByText("Create group").click();
|
||||
await expect(page).toHaveTitle("Create Group - Coder");
|
||||
await page.getByText("Create group").click();
|
||||
await expect(page).toHaveTitle("Create Group - Coder");
|
||||
|
||||
const name = randomName();
|
||||
const groupValues = {
|
||||
name: name,
|
||||
displayName: `Display Name for ${name}`,
|
||||
avatarURL: "/emojis/1f60d.png",
|
||||
};
|
||||
const name = randomName();
|
||||
const groupValues = {
|
||||
name: name,
|
||||
displayName: `Display Name for ${name}`,
|
||||
avatarURL: "/emojis/1f60d.png",
|
||||
};
|
||||
|
||||
await page.getByLabel("Name", { exact: true }).fill(groupValues.name);
|
||||
await page.getByLabel("Display Name").fill(groupValues.displayName);
|
||||
await page.getByLabel("Avatar URL").fill(groupValues.avatarURL);
|
||||
await page.getByRole("button", { name: "Submit" }).click();
|
||||
await page.getByLabel("Name", { exact: true }).fill(groupValues.name);
|
||||
await page.getByLabel("Display Name").fill(groupValues.displayName);
|
||||
await page.getByLabel("Avatar URL").fill(groupValues.avatarURL);
|
||||
await page.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`);
|
||||
await expect(page.getByText(groupValues.displayName)).toBeVisible();
|
||||
await expect(page.getByText("No members yet")).toBeVisible();
|
||||
await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`);
|
||||
await expect(page.getByText(groupValues.displayName)).toBeVisible();
|
||||
await expect(page.getByText("No members yet")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -6,18 +6,18 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("navigate to group page", async ({ page, baseURL }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
|
||||
await page.getByRole("link", { name: "Groups" }).click();
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
await page.getByRole("link", { name: "Groups" }).click();
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
|
||||
const groupRow = page.getByRole("row", { name: group.display_name });
|
||||
await groupRow.click();
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
const groupRow = page.getByRole("row", { name: group.display_name });
|
||||
await groupRow.click();
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
});
|
||||
|
||||
@@ -6,21 +6,21 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("remove group", async ({ page, baseURL }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const group = await createGroup(orgId);
|
||||
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
|
||||
await page.getByRole("button", { name: "Delete" }).click();
|
||||
const dialog = page.getByTestId("dialog");
|
||||
await dialog.getByLabel("Name of the group to delete").fill(group.name);
|
||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||
await expect(page.getByText("Group deleted successfully.")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Delete" }).click();
|
||||
const dialog = page.getByTestId("dialog");
|
||||
await dialog.getByLabel("Name of the group to delete").fill(group.name);
|
||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||
await expect(page.getByText("Group deleted successfully.")).toBeVisible();
|
||||
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
await expect(page).toHaveTitle("Groups - Coder");
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { API } from "api/api";
|
||||
import {
|
||||
createGroup,
|
||||
createUser,
|
||||
getCurrentOrgId,
|
||||
setupApiCalls,
|
||||
createGroup,
|
||||
createUser,
|
||||
getCurrentOrgId,
|
||||
setupApiCalls,
|
||||
} from "../../api";
|
||||
import { requiresEnterpriseLicense } from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
@@ -12,25 +12,25 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("remove member", async ({ page, baseURL }) => {
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const [group, member] = await Promise.all([
|
||||
createGroup(orgId),
|
||||
createUser(orgId),
|
||||
]);
|
||||
await API.addMember(group.id, member.id);
|
||||
requiresEnterpriseLicense();
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const [group, member] = await Promise.all([
|
||||
createGroup(orgId),
|
||||
createUser(orgId),
|
||||
]);
|
||||
await API.addMember(group.id, member.id);
|
||||
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||
|
||||
const userRow = page.getByRole("row", { name: member.username });
|
||||
await userRow.getByRole("button", { name: "More options" }).click();
|
||||
const userRow = page.getByRole("row", { name: member.username });
|
||||
await userRow.getByRole("button", { name: "More options" }).click();
|
||||
|
||||
const menu = page.locator("#more-options");
|
||||
await menu.getByText("Remove").click({ timeout: 1_000 });
|
||||
const menu = page.locator("#more-options");
|
||||
await menu.getByText("Remove").click({ timeout: 1_000 });
|
||||
|
||||
await expect(page.getByText("Member removed successfully.")).toBeVisible();
|
||||
await expect(page.getByText("Member removed successfully.")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -5,32 +5,32 @@ import { requiresEnterpriseLicense } from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await beforeCoderTest(page);
|
||||
await setupApiCalls(page);
|
||||
await beforeCoderTest(page);
|
||||
await setupApiCalls(page);
|
||||
});
|
||||
|
||||
test("create and delete organization", async ({ page, baseURL }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
// Create an organization
|
||||
await page.goto(`${baseURL}/organizations/new`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
// Create an organization
|
||||
await page.goto(`${baseURL}/organizations/new`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
|
||||
await page.getByLabel("Name", { exact: true }).fill("floop");
|
||||
await page.getByLabel("Display name").fill("Floop");
|
||||
await page.getByLabel("Description").fill("Org description floop");
|
||||
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
|
||||
await page.getByLabel("Name", { exact: true }).fill("floop");
|
||||
await page.getByLabel("Display name").fill("Floop");
|
||||
await page.getByLabel("Description").fill("Org description floop");
|
||||
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
|
||||
|
||||
await page.getByRole("button", { name: "Submit" }).click();
|
||||
await page.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
// Expect to be redirected to the new organization
|
||||
await expectUrl(page).toHavePathName("/organizations/floop");
|
||||
await expect(page.getByText("Organization created.")).toBeVisible();
|
||||
// Expect to be redirected to the new organization
|
||||
await expectUrl(page).toHavePathName("/organizations/floop");
|
||||
await expect(page.getByText("Organization created.")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Delete this organization" }).click();
|
||||
const dialog = page.getByTestId("dialog");
|
||||
await dialog.getByLabel("Name").fill("floop");
|
||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||
await expect(page.getByText("Organization deleted.")).toBeVisible();
|
||||
await page.getByRole("button", { name: "Delete this organization" }).click();
|
||||
const dialog = page.getByTestId("dialog");
|
||||
await dialog.getByLabel("Name").fill("floop");
|
||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||
await expect(page.getByText("Organization deleted.")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
downloadCoderVersion,
|
||||
sshIntoWorkspace,
|
||||
startAgentWithCommand,
|
||||
stopAgent,
|
||||
stopWorkspace,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
downloadCoderVersion,
|
||||
sshIntoWorkspace,
|
||||
startAgentWithCommand,
|
||||
stopAgent,
|
||||
stopWorkspace,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
@@ -17,48 +17,48 @@ const agentVersion = "v2.12.1";
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test(`ssh with agent ${agentVersion}`, async ({ page }) => {
|
||||
test.setTimeout(40_000); // This is a slow test, 20s may not be enough on Mac.
|
||||
test.setTimeout(40_000); // This is a slow test, 20s may not be enough on Mac.
|
||||
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const binaryPath = await downloadCoderVersion(agentVersion);
|
||||
const agent = await startAgentWithCommand(page, token, binaryPath);
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const binaryPath = await downloadCoderVersion(agentVersion);
|
||||
const agent = await startAgentWithCommand(page, token, binaryPath);
|
||||
|
||||
const client = await sshIntoWorkspace(page, workspaceName);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// We just exec a command to be certain the agent is running!
|
||||
client.exec("exit 0", (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
stream.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
client.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
const client = await sshIntoWorkspace(page, workspaceName);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// We just exec a command to be certain the agent is running!
|
||||
client.exec("exit 0", (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
stream.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
client.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await stopWorkspace(page, workspaceName);
|
||||
await stopAgent(agent, false);
|
||||
await stopWorkspace(page, workspaceName);
|
||||
await stopAgent(agent, false);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
downloadCoderVersion,
|
||||
sshIntoWorkspace,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
stopWorkspace,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
downloadCoderVersion,
|
||||
sshIntoWorkspace,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
stopWorkspace,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
@@ -17,46 +17,46 @@ const clientVersion = "v0.27.0";
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test(`ssh with client ${clientVersion}`, async ({ page }) => {
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const agent = await startAgent(page, token);
|
||||
const binaryPath = await downloadCoderVersion(clientVersion);
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const agent = await startAgent(page, token);
|
||||
const binaryPath = await downloadCoderVersion(clientVersion);
|
||||
|
||||
const client = await sshIntoWorkspace(page, workspaceName, binaryPath);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// We just exec a command to be certain the agent is running!
|
||||
client.exec("exit 0", (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
stream.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
client.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
const client = await sshIntoWorkspace(page, workspaceName, binaryPath);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// We just exec a command to be certain the agent is running!
|
||||
client.exec("exit 0", (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
stream.on("exit", (code) => {
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`Command exited with code ${code}`));
|
||||
}
|
||||
client.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await stopWorkspace(page, workspaceName);
|
||||
await stopAgent(agent);
|
||||
await stopWorkspace(page, workspaceName);
|
||||
await stopAgent(agent);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,6 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("list templates", async ({ page, baseURL }) => {
|
||||
await page.goto(`${baseURL}/templates`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Templates - Coder");
|
||||
await page.goto(`${baseURL}/templates`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Templates - Coder");
|
||||
});
|
||||
|
||||
@@ -6,40 +6,40 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("update template schedule settings without override other settings", async ({
|
||||
page,
|
||||
baseURL,
|
||||
page,
|
||||
baseURL,
|
||||
}) => {
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const templateVersion = await API.createTemplateVersion(orgId, {
|
||||
storage_method: "file" as const,
|
||||
provisioner: "echo",
|
||||
user_variable_values: [],
|
||||
example_id: "docker",
|
||||
tags: {},
|
||||
});
|
||||
const template = await API.createTemplate(orgId, {
|
||||
name: "test-template",
|
||||
display_name: "Test Template",
|
||||
template_version_id: templateVersion.id,
|
||||
disable_everyone_group_access: false,
|
||||
require_active_version: true,
|
||||
});
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const templateVersion = await API.createTemplateVersion(orgId, {
|
||||
storage_method: "file" as const,
|
||||
provisioner: "echo",
|
||||
user_variable_values: [],
|
||||
example_id: "docker",
|
||||
tags: {},
|
||||
});
|
||||
const template = await API.createTemplate(orgId, {
|
||||
name: "test-template",
|
||||
display_name: "Test Template",
|
||||
template_version_id: templateVersion.id,
|
||||
disable_everyone_group_access: false,
|
||||
require_active_version: true,
|
||||
});
|
||||
|
||||
await page.goto(`${baseURL}/templates/${template.name}/settings/schedule`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.getByLabel("Default autostop (hours)").fill("48");
|
||||
await page.getByRole("button", { name: "Submit" }).click();
|
||||
await expect(page.getByText("Template updated successfully")).toBeVisible();
|
||||
await page.goto(`${baseURL}/templates/${template.name}/settings/schedule`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await page.getByLabel("Default autostop (hours)").fill("48");
|
||||
await page.getByRole("button", { name: "Submit" }).click();
|
||||
await expect(page.getByText("Template updated successfully")).toBeVisible();
|
||||
|
||||
const updatedTemplate = await API.getTemplate(template.id);
|
||||
// Validate that the template data remains consistent, with the exception of
|
||||
// the 'default_ttl_ms' field (updated during the test) and the 'updated at'
|
||||
// field (automatically updated by the backend).
|
||||
expect({
|
||||
...template,
|
||||
default_ttl_ms: 48 * 60 * 60 * 1000,
|
||||
updated_at: updatedTemplate.updated_at,
|
||||
}).toStrictEqual(updatedTemplate);
|
||||
const updatedTemplate = await API.getTemplate(template.id);
|
||||
// Validate that the template data remains consistent, with the exception of
|
||||
// the 'default_ttl_ms' field (updated during the test) and the 'updated at'
|
||||
// field (automatically updated by the backend).
|
||||
expect({
|
||||
...template,
|
||||
default_ttl_ms: 48 * 60 * 60 * 1000,
|
||||
updated_at: updatedTemplate.updated_at,
|
||||
}).toStrictEqual(updatedTemplate);
|
||||
});
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { expectUrl } from "../expectUrl";
|
||||
import {
|
||||
createGroup,
|
||||
createTemplate,
|
||||
requiresEnterpriseLicense,
|
||||
updateTemplateSettings,
|
||||
createGroup,
|
||||
createTemplate,
|
||||
requiresEnterpriseLicense,
|
||||
updateTemplateSettings,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("template update with new name redirects on successful submit", async ({
|
||||
page,
|
||||
page,
|
||||
}) => {
|
||||
const templateName = await createTemplate(page);
|
||||
const templateName = await createTemplate(page);
|
||||
|
||||
await updateTemplateSettings(page, templateName, {
|
||||
name: "new-name",
|
||||
});
|
||||
await updateTemplateSettings(page, templateName, {
|
||||
name: "new-name",
|
||||
});
|
||||
});
|
||||
|
||||
test("add and remove a group", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
const templateName = await createTemplate(page);
|
||||
const groupName = await createGroup(page);
|
||||
const templateName = await createTemplate(page);
|
||||
const groupName = await createGroup(page);
|
||||
|
||||
await page.goto(`/templates/${templateName}/settings/permissions`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expectUrl(page).toHavePathName(
|
||||
`/templates/${templateName}/settings/permissions`,
|
||||
);
|
||||
await page.goto(`/templates/${templateName}/settings/permissions`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expectUrl(page).toHavePathName(
|
||||
`/templates/${templateName}/settings/permissions`,
|
||||
);
|
||||
|
||||
// Type the first half of the group name
|
||||
await page
|
||||
.getByPlaceholder("Search for user or group", { exact: true })
|
||||
.fill(groupName.slice(0, 4));
|
||||
// Type the first half of the group name
|
||||
await page
|
||||
.getByPlaceholder("Search for user or group", { exact: true })
|
||||
.fill(groupName.slice(0, 4));
|
||||
|
||||
// Select the group from the list and add it
|
||||
await page.getByText(groupName).click();
|
||||
await page.getByText("Add member").click();
|
||||
const row = page.locator(".MuiTableRow-root", { hasText: groupName });
|
||||
await expect(row).toBeVisible();
|
||||
// Select the group from the list and add it
|
||||
await page.getByText(groupName).click();
|
||||
await page.getByText("Add member").click();
|
||||
const row = page.locator(".MuiTableRow-root", { hasText: groupName });
|
||||
await expect(row).toBeVisible();
|
||||
|
||||
// Now remove the group
|
||||
await row.getByLabel("More options").click();
|
||||
await page.getByText("Remove").click();
|
||||
await expect(page.getByText("Group removed successfully!")).toBeVisible();
|
||||
await expect(row).not.toBeVisible();
|
||||
// Now remove the group
|
||||
await row.getByLabel("More options").click();
|
||||
await page.getByText("Remove").click();
|
||||
await expect(page.getByText("Group removed successfully!")).toBeVisible();
|
||||
await expect(row).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("require latest version", async ({ page }) => {
|
||||
requiresEnterpriseLicense();
|
||||
requiresEnterpriseLicense();
|
||||
|
||||
const templateName = await createTemplate(page);
|
||||
const templateName = await createTemplate(page);
|
||||
|
||||
await page.goto(`/templates/${templateName}/settings`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`);
|
||||
let checkbox = await page.waitForSelector("#require_active_version");
|
||||
await checkbox.click();
|
||||
await page.getByTestId("form-submit").click();
|
||||
await page.goto(`/templates/${templateName}/settings`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`);
|
||||
let checkbox = await page.waitForSelector("#require_active_version");
|
||||
await checkbox.click();
|
||||
await page.getByTestId("form-submit").click();
|
||||
|
||||
await page.goto(`/templates/${templateName}/settings`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
checkbox = await page.waitForSelector("#require_active_version");
|
||||
await checkbox.scrollIntoViewIfNeeded();
|
||||
expect(await checkbox.isChecked()).toBe(true);
|
||||
await page.goto(`/templates/${templateName}/settings`, {
|
||||
waitUntil: "domcontentloaded",
|
||||
});
|
||||
checkbox = await page.waitForSelector("#require_active_version");
|
||||
await checkbox.scrollIntoViewIfNeeded();
|
||||
expect(await checkbox.isChecked()).toBe(true);
|
||||
});
|
||||
|
||||
@@ -5,63 +5,63 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("create user with password", async ({ page, baseURL }) => {
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page).toHaveTitle("Create User - Coder");
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page).toHaveTitle("Create User - Coder");
|
||||
|
||||
const name = randomName();
|
||||
const userValues = {
|
||||
username: name,
|
||||
name: name,
|
||||
email: `${name}@coder.com`,
|
||||
loginType: "password",
|
||||
password: "s3cure&password!",
|
||||
};
|
||||
const name = randomName();
|
||||
const userValues = {
|
||||
username: name,
|
||||
name: name,
|
||||
email: `${name}@coder.com`,
|
||||
loginType: "password",
|
||||
password: "s3cure&password!",
|
||||
};
|
||||
|
||||
await page.getByLabel("Username").fill(userValues.username);
|
||||
await page.getByLabel("Full name").fill(userValues.username);
|
||||
await page.getByLabel("Email").fill(userValues.email);
|
||||
await page.getByLabel("Login Type").click();
|
||||
await page.getByRole("option", { name: "Password", exact: false }).click();
|
||||
// Using input[name=password] due to the select element utilizing 'password'
|
||||
// as the label for the currently active option.
|
||||
const passwordField = page.locator("input[name=password]");
|
||||
await passwordField.fill(userValues.password);
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
||||
await page.getByLabel("Username").fill(userValues.username);
|
||||
await page.getByLabel("Full name").fill(userValues.username);
|
||||
await page.getByLabel("Email").fill(userValues.email);
|
||||
await page.getByLabel("Login Type").click();
|
||||
await page.getByRole("option", { name: "Password", exact: false }).click();
|
||||
// Using input[name=password] due to the select element utilizing 'password'
|
||||
// as the label for the currently active option.
|
||||
const passwordField = page.locator("input[name=password]");
|
||||
await passwordField.fill(userValues.password);
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
||||
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
||||
});
|
||||
|
||||
test("create user without full name is optional", async ({ page, baseURL }) => {
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page).toHaveTitle("Create User - Coder");
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page).toHaveTitle("Create User - Coder");
|
||||
|
||||
const name = randomName();
|
||||
const userValues = {
|
||||
username: name,
|
||||
email: `${name}@coder.com`,
|
||||
loginType: "password",
|
||||
password: "s3cure&password!",
|
||||
};
|
||||
const name = randomName();
|
||||
const userValues = {
|
||||
username: name,
|
||||
email: `${name}@coder.com`,
|
||||
loginType: "password",
|
||||
password: "s3cure&password!",
|
||||
};
|
||||
|
||||
await page.getByLabel("Username").fill(userValues.username);
|
||||
await page.getByLabel("Email").fill(userValues.email);
|
||||
await page.getByLabel("Login Type").click();
|
||||
await page.getByRole("option", { name: "Password", exact: false }).click();
|
||||
// Using input[name=password] due to the select element utilizing 'password'
|
||||
// as the label for the currently active option.
|
||||
const passwordField = page.locator("input[name=password]");
|
||||
await passwordField.fill(userValues.password);
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
||||
await page.getByLabel("Username").fill(userValues.username);
|
||||
await page.getByLabel("Email").fill(userValues.email);
|
||||
await page.getByLabel("Login Type").click();
|
||||
await page.getByRole("option", { name: "Password", exact: false }).click();
|
||||
// Using input[name=password] due to the select element utilizing 'password'
|
||||
// as the label for the currently active option.
|
||||
const passwordField = page.locator("input[name=password]");
|
||||
await passwordField.fill(userValues.password);
|
||||
await page.getByRole("button", { name: "Create user" }).click();
|
||||
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
||||
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -5,21 +5,21 @@ import { beforeCoderTest } from "../../hooks";
|
||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||
|
||||
test("remove user", async ({ page, baseURL }) => {
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const user = await createUser(orgId);
|
||||
await setupApiCalls(page);
|
||||
const orgId = await getCurrentOrgId();
|
||||
const user = await createUser(orgId);
|
||||
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveTitle("Users - Coder");
|
||||
|
||||
const userRow = page.getByRole("row", { name: user.email });
|
||||
await userRow.getByRole("button", { name: "More options" }).click();
|
||||
const menu = page.locator("#more-options");
|
||||
await menu.getByText("Delete").click();
|
||||
const userRow = page.getByRole("row", { name: user.email });
|
||||
await userRow.getByRole("button", { name: "More options" }).click();
|
||||
const menu = page.locator("#more-options");
|
||||
await menu.getByText("Delete").click();
|
||||
|
||||
const dialog = page.getByTestId("dialog");
|
||||
await dialog.getByLabel("Name of the user to delete").fill(user.username);
|
||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||
const dialog = page.getByTestId("dialog");
|
||||
await dialog.getByLabel("Name of the user to delete").fill(user.username);
|
||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||
|
||||
await expect(page.getByText("Successfully deleted the user.")).toBeVisible();
|
||||
await expect(page.getByText("Successfully deleted the user.")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
openTerminalWindow,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
openTerminalWindow,
|
||||
startAgent,
|
||||
stopAgent,
|
||||
} from "../helpers";
|
||||
import { beforeCoderTest } from "../hooks";
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("web terminal", async ({ context, page }) => {
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
displayApps: {
|
||||
webTerminal: true,
|
||||
},
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const agent = await startAgent(page, token);
|
||||
const terminal = await openTerminalWindow(page, context, workspaceName);
|
||||
const token = randomUUID();
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
agents: [
|
||||
{
|
||||
token,
|
||||
displayApps: {
|
||||
webTerminal: true,
|
||||
},
|
||||
order: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const agent = await startAgent(page, token);
|
||||
const terminal = await openTerminalWindow(page, context, workspaceName);
|
||||
|
||||
await terminal.waitForSelector("div.xterm-rows", {
|
||||
state: "visible",
|
||||
});
|
||||
await terminal.waitForSelector("div.xterm-rows", {
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
// Workaround: delay next steps as "div.xterm-rows" can be recreated/reattached
|
||||
// after a couple of milliseconds.
|
||||
await terminal.waitForTimeout(2000);
|
||||
// Workaround: delay next steps as "div.xterm-rows" can be recreated/reattached
|
||||
// after a couple of milliseconds.
|
||||
await terminal.waitForTimeout(2000);
|
||||
|
||||
// Ensure that we can type in it
|
||||
await terminal.keyboard.type("echo he${justabreak}llo123456");
|
||||
await terminal.keyboard.press("Enter");
|
||||
// Ensure that we can type in it
|
||||
await terminal.keyboard.type("echo he${justabreak}llo123456");
|
||||
await terminal.keyboard.press("Enter");
|
||||
|
||||
// Check if "echo" command was executed
|
||||
// try-catch is used temporarily to find the root cause: https://github.com/coder/coder/actions/runs/6176958762/job/16767089943
|
||||
try {
|
||||
await terminal.waitForSelector(
|
||||
'div.xterm-rows span:text-matches("hello123456")',
|
||||
{
|
||||
state: "visible",
|
||||
timeout: 10 * 1000,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
const pageContent = await terminal.content();
|
||||
// eslint-disable-next-line no-console -- Let's see what is inside of xterm-rows
|
||||
console.log("Unable to find echoed text:", pageContent);
|
||||
throw error;
|
||||
}
|
||||
// Check if "echo" command was executed
|
||||
// try-catch is used temporarily to find the root cause: https://github.com/coder/coder/actions/runs/6176958762/job/16767089943
|
||||
try {
|
||||
await terminal.waitForSelector(
|
||||
'div.xterm-rows span:text-matches("hello123456")',
|
||||
{
|
||||
state: "visible",
|
||||
timeout: 10 * 1000,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
const pageContent = await terminal.content();
|
||||
// eslint-disable-next-line no-console -- Let's see what is inside of xterm-rows
|
||||
console.log("Unable to find echoed text:", pageContent);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await stopAgent(agent);
|
||||
await stopAgent(agent);
|
||||
});
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { username } from "../../constants";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
} from "../../helpers";
|
||||
import { emptyParameter } from "../../parameters";
|
||||
import type { RichParameter } from "../../provisionerGenerated";
|
||||
|
||||
test("create workspace in auto mode", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
{ ...emptyParameter, name: "repo", type: "string" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const name = "test-workspace";
|
||||
await page.goto(
|
||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=${name}`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await expect(page).toHaveTitle(`${username}/${name} - Coder`);
|
||||
const richParameters: RichParameter[] = [
|
||||
{ ...emptyParameter, name: "repo", type: "string" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const name = "test-workspace";
|
||||
await page.goto(
|
||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=${name}`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await expect(page).toHaveTitle(`${username}/${name} - Coder`);
|
||||
});
|
||||
|
||||
test("use an existing workspace that matches the `match` parameter instead of creating a new one", async ({
|
||||
page,
|
||||
page,
|
||||
}) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
{ ...emptyParameter, name: "repo", type: "string" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const prevWorkspace = await createWorkspace(page, template);
|
||||
await page.goto(
|
||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await expect(page).toHaveTitle(`${username}/${prevWorkspace} - Coder`);
|
||||
const richParameters: RichParameter[] = [
|
||||
{ ...emptyParameter, name: "repo", type: "string" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const prevWorkspace = await createWorkspace(page, template);
|
||||
await page.goto(
|
||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await expect(page).toHaveTitle(`${username}/${prevWorkspace} - Coder`);
|
||||
});
|
||||
|
||||
test("show error if `match` parameter is invalid", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
{ ...emptyParameter, name: "repo", type: "string" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const prevWorkspace = await createWorkspace(page, template);
|
||||
await page.goto(
|
||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await expect(page.getByText("Invalid match value")).toBeVisible();
|
||||
const richParameters: RichParameter[] = [
|
||||
{ ...emptyParameter, name: "repo", type: "string" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const prevWorkspace = await createWorkspace(page, template);
|
||||
await page.goto(
|
||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await expect(page.getByText("Invalid match value")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,191 +1,191 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
StarterTemplates,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
openTerminalWindow,
|
||||
requireTerraformProvisioner,
|
||||
verifyParameters,
|
||||
StarterTemplates,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
openTerminalWindow,
|
||||
requireTerraformProvisioner,
|
||||
verifyParameters,
|
||||
} from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
import {
|
||||
fifthParameter,
|
||||
firstParameter,
|
||||
fourthParameter,
|
||||
randParamName,
|
||||
secondParameter,
|
||||
seventhParameter,
|
||||
sixthParameter,
|
||||
thirdParameter,
|
||||
fifthParameter,
|
||||
firstParameter,
|
||||
fourthParameter,
|
||||
randParamName,
|
||||
secondParameter,
|
||||
seventhParameter,
|
||||
sixthParameter,
|
||||
thirdParameter,
|
||||
} from "../../parameters";
|
||||
import type { RichParameter } from "../../provisionerGenerated";
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("create workspace", async ({ page }) => {
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
name: "example",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await createWorkspace(page, template);
|
||||
const template = await createTemplate(page, {
|
||||
apply: [
|
||||
{
|
||||
apply: {
|
||||
resources: [
|
||||
{
|
||||
name: "example",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
await createWorkspace(page, template);
|
||||
});
|
||||
|
||||
test("create workspace with default immutable parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
fifthParameter,
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||
{ name: fifthParameter.name, value: fifthParameter.defaultValue },
|
||||
]);
|
||||
const richParameters: RichParameter[] = [
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
fifthParameter,
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||
{ name: fifthParameter.name, value: fifthParameter.defaultValue },
|
||||
]);
|
||||
});
|
||||
|
||||
test("create workspace with default mutable parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [firstParameter, thirdParameter];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: thirdParameter.name, value: thirdParameter.defaultValue },
|
||||
]);
|
||||
const richParameters: RichParameter[] = [firstParameter, thirdParameter];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: thirdParameter.name, value: thirdParameter.defaultValue },
|
||||
]);
|
||||
});
|
||||
|
||||
test("create workspace with default and required parameters", async ({
|
||||
page,
|
||||
page,
|
||||
}) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
sixthParameter,
|
||||
seventhParameter,
|
||||
];
|
||||
const buildParameters = [
|
||||
{ name: sixthParameter.name, value: "12345" },
|
||||
{ name: seventhParameter.name, value: "abcdef" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(
|
||||
page,
|
||||
template,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
// user values:
|
||||
...buildParameters,
|
||||
// default values:
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||
]);
|
||||
const richParameters: RichParameter[] = [
|
||||
secondParameter,
|
||||
fourthParameter,
|
||||
sixthParameter,
|
||||
seventhParameter,
|
||||
];
|
||||
const buildParameters = [
|
||||
{ name: sixthParameter.name, value: "12345" },
|
||||
{ name: seventhParameter.name, value: "abcdef" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(
|
||||
page,
|
||||
template,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
// user values:
|
||||
...buildParameters,
|
||||
// default values:
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||
]);
|
||||
});
|
||||
|
||||
test("create workspace and overwrite default parameters", async ({ page }) => {
|
||||
// We use randParamName to prevent the new values from corrupting user_history
|
||||
// and thus affecting other tests.
|
||||
const richParameters: RichParameter[] = [
|
||||
randParamName(secondParameter),
|
||||
randParamName(fourthParameter),
|
||||
];
|
||||
// We use randParamName to prevent the new values from corrupting user_history
|
||||
// and thus affecting other tests.
|
||||
const richParameters: RichParameter[] = [
|
||||
randParamName(secondParameter),
|
||||
randParamName(fourthParameter),
|
||||
];
|
||||
|
||||
const buildParameters = [
|
||||
{ name: richParameters[0].name, value: "AAAAA" },
|
||||
{ name: richParameters[1].name, value: "false" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const buildParameters = [
|
||||
{ name: richParameters[0].name, value: "AAAAA" },
|
||||
{ name: richParameters[1].name, value: "false" },
|
||||
];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
|
||||
const workspaceName = await createWorkspace(
|
||||
page,
|
||||
template,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
await verifyParameters(page, workspaceName, richParameters, buildParameters);
|
||||
const workspaceName = await createWorkspace(
|
||||
page,
|
||||
template,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
await verifyParameters(page, workspaceName, richParameters, buildParameters);
|
||||
});
|
||||
|
||||
test("create workspace with disable_param search params", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [
|
||||
firstParameter, // mutable
|
||||
secondParameter, //immutable
|
||||
];
|
||||
const richParameters: RichParameter[] = [
|
||||
firstParameter, // mutable
|
||||
secondParameter, //immutable
|
||||
];
|
||||
|
||||
const templateName = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const templateName = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
|
||||
await page.goto(
|
||||
`/templates/${templateName}/workspace?disable_params=first_parameter,second_parameter`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
await page.goto(
|
||||
`/templates/${templateName}/workspace?disable_params=first_parameter,second_parameter`,
|
||||
{
|
||||
waitUntil: "domcontentloaded",
|
||||
},
|
||||
);
|
||||
|
||||
await expect(page.getByLabel(/First parameter/i)).toBeDisabled();
|
||||
await expect(page.getByLabel(/Second parameter/i)).toBeDisabled();
|
||||
await expect(page.getByLabel(/First parameter/i)).toBeDisabled();
|
||||
await expect(page.getByLabel(/Second parameter/i)).toBeDisabled();
|
||||
});
|
||||
|
||||
test("create docker workspace", async ({ context, page }) => {
|
||||
test.skip(
|
||||
true,
|
||||
"creating docker containers is currently leaky. They are not cleaned up when the tests are over.",
|
||||
);
|
||||
requireTerraformProvisioner();
|
||||
const template = await createTemplate(page, StarterTemplates.STARTER_DOCKER);
|
||||
test.skip(
|
||||
true,
|
||||
"creating docker containers is currently leaky. They are not cleaned up when the tests are over.",
|
||||
);
|
||||
requireTerraformProvisioner();
|
||||
const template = await createTemplate(page, StarterTemplates.STARTER_DOCKER);
|
||||
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
|
||||
// The workspace agents must be ready before we try to interact with the workspace.
|
||||
await page.waitForSelector(
|
||||
`//div[@role="status"][@data-testid="agent-status-ready"]`,
|
||||
{
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
// The workspace agents must be ready before we try to interact with the workspace.
|
||||
await page.waitForSelector(
|
||||
`//div[@role="status"][@data-testid="agent-status-ready"]`,
|
||||
{
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
|
||||
// Wait for the terminal button to be visible, and click it.
|
||||
const terminalButton =
|
||||
"//a[@data-testid='terminal'][normalize-space()='Terminal']";
|
||||
await page.waitForSelector(terminalButton, {
|
||||
state: "visible",
|
||||
});
|
||||
// Wait for the terminal button to be visible, and click it.
|
||||
const terminalButton =
|
||||
"//a[@data-testid='terminal'][normalize-space()='Terminal']";
|
||||
await page.waitForSelector(terminalButton, {
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
const terminal = await openTerminalWindow(
|
||||
page,
|
||||
context,
|
||||
workspaceName,
|
||||
"main",
|
||||
);
|
||||
await terminal.waitForSelector(
|
||||
`//textarea[contains(@class,"xterm-helper-textarea")]`,
|
||||
{
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
const terminal = await openTerminalWindow(
|
||||
page,
|
||||
context,
|
||||
workspaceName,
|
||||
"main",
|
||||
);
|
||||
await terminal.waitForSelector(
|
||||
`//textarea[contains(@class,"xterm-helper-textarea")]`,
|
||||
{
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
buildWorkspaceWithParameters,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
verifyParameters,
|
||||
buildWorkspaceWithParameters,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
verifyParameters,
|
||||
} from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
import { firstBuildOption, secondBuildOption } from "../../parameters";
|
||||
@@ -13,35 +13,35 @@ import type { RichParameter } from "../../provisionerGenerated";
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("restart workspace with ephemeral parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
|
||||
// Now, restart the workspace with ephemeral parameters selected.
|
||||
const buildParameters = [
|
||||
{ name: richParameters[0].name, value: "AAAAA" },
|
||||
{ name: richParameters[1].name, value: "true" },
|
||||
];
|
||||
await buildWorkspaceWithParameters(
|
||||
page,
|
||||
workspaceName,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
true,
|
||||
);
|
||||
// Now, restart the workspace with ephemeral parameters selected.
|
||||
const buildParameters = [
|
||||
{ name: richParameters[0].name, value: "AAAAA" },
|
||||
{ name: richParameters[1].name, value: "true" },
|
||||
];
|
||||
await buildWorkspaceWithParameters(
|
||||
page,
|
||||
workspaceName,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
true,
|
||||
);
|
||||
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
buildWorkspaceWithParameters,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
stopWorkspace,
|
||||
verifyParameters,
|
||||
buildWorkspaceWithParameters,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
stopWorkspace,
|
||||
verifyParameters,
|
||||
} from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
import { firstBuildOption, secondBuildOption } from "../../parameters";
|
||||
@@ -14,38 +14,38 @@ import type { RichParameter } from "../../provisionerGenerated";
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("start workspace with ephemeral parameters", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
|
||||
// Stop the workspace
|
||||
await stopWorkspace(page, workspaceName);
|
||||
// Stop the workspace
|
||||
await stopWorkspace(page, workspaceName);
|
||||
|
||||
// Now, start the workspace with ephemeral parameters selected.
|
||||
const buildParameters = [
|
||||
{ name: richParameters[0].name, value: "AAAAA" },
|
||||
{ name: richParameters[1].name, value: "true" },
|
||||
];
|
||||
// Now, start the workspace with ephemeral parameters selected.
|
||||
const buildParameters = [
|
||||
{ name: richParameters[0].name, value: "AAAAA" },
|
||||
{ name: richParameters[1].name, value: "true" },
|
||||
];
|
||||
|
||||
await buildWorkspaceWithParameters(
|
||||
page,
|
||||
workspaceName,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
await buildWorkspaceWithParameters(
|
||||
page,
|
||||
workspaceName,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
// Verify that build options are default (not selected).
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1,132 +1,132 @@
|
||||
import { test } from "@playwright/test";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
updateTemplate,
|
||||
updateWorkspace,
|
||||
updateWorkspaceParameters,
|
||||
verifyParameters,
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
echoResponsesWithParameters,
|
||||
updateTemplate,
|
||||
updateWorkspace,
|
||||
updateWorkspaceParameters,
|
||||
verifyParameters,
|
||||
} from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
import {
|
||||
fifthParameter,
|
||||
firstParameter,
|
||||
secondBuildOption,
|
||||
secondParameter,
|
||||
sixthParameter,
|
||||
fifthParameter,
|
||||
firstParameter,
|
||||
secondBuildOption,
|
||||
secondParameter,
|
||||
sixthParameter,
|
||||
} from "../../parameters";
|
||||
import type { RichParameter } from "../../provisionerGenerated";
|
||||
|
||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||
|
||||
test("update workspace, new optional, immutable parameter added", async ({
|
||||
page,
|
||||
page,
|
||||
}) => {
|
||||
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
]);
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
]);
|
||||
|
||||
// Push updated template.
|
||||
const updatedRichParameters = [...richParameters, fifthParameter];
|
||||
await updateTemplate(
|
||||
page,
|
||||
template,
|
||||
echoResponsesWithParameters(updatedRichParameters),
|
||||
);
|
||||
// Push updated template.
|
||||
const updatedRichParameters = [...richParameters, fifthParameter];
|
||||
await updateTemplate(
|
||||
page,
|
||||
template,
|
||||
echoResponsesWithParameters(updatedRichParameters),
|
||||
);
|
||||
|
||||
// Now, update the workspace, and select the value for immutable parameter.
|
||||
await updateWorkspace(page, workspaceName, updatedRichParameters, [
|
||||
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
||||
]);
|
||||
// Now, update the workspace, and select the value for immutable parameter.
|
||||
await updateWorkspace(page, workspaceName, updatedRichParameters, [
|
||||
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
||||
]);
|
||||
|
||||
// Verify parameter values.
|
||||
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
||||
]);
|
||||
// Verify parameter values.
|
||||
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
||||
]);
|
||||
});
|
||||
|
||||
test("update workspace, new required, mutable parameter added", async ({
|
||||
page,
|
||||
page,
|
||||
}) => {
|
||||
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
]);
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
]);
|
||||
|
||||
// Push updated template.
|
||||
const updatedRichParameters = [...richParameters, sixthParameter];
|
||||
await updateTemplate(
|
||||
page,
|
||||
template,
|
||||
echoResponsesWithParameters(updatedRichParameters),
|
||||
);
|
||||
// Push updated template.
|
||||
const updatedRichParameters = [...richParameters, sixthParameter];
|
||||
await updateTemplate(
|
||||
page,
|
||||
template,
|
||||
echoResponsesWithParameters(updatedRichParameters),
|
||||
);
|
||||
|
||||
// Now, update the workspace, and provide the parameter value.
|
||||
const buildParameters = [{ name: sixthParameter.name, value: "99" }];
|
||||
await updateWorkspace(
|
||||
page,
|
||||
workspaceName,
|
||||
updatedRichParameters,
|
||||
buildParameters,
|
||||
);
|
||||
// Now, update the workspace, and provide the parameter value.
|
||||
const buildParameters = [{ name: sixthParameter.name, value: "99" }];
|
||||
await updateWorkspace(
|
||||
page,
|
||||
workspaceName,
|
||||
updatedRichParameters,
|
||||
buildParameters,
|
||||
);
|
||||
|
||||
// Verify parameter values.
|
||||
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
...buildParameters,
|
||||
]);
|
||||
// Verify parameter values.
|
||||
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||
...buildParameters,
|
||||
]);
|
||||
});
|
||||
|
||||
test("update workspace with ephemeral parameter enabled", async ({ page }) => {
|
||||
const richParameters: RichParameter[] = [firstParameter, secondBuildOption];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
const richParameters: RichParameter[] = [firstParameter, secondBuildOption];
|
||||
const template = await createTemplate(
|
||||
page,
|
||||
echoResponsesWithParameters(richParameters),
|
||||
);
|
||||
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
const workspaceName = await createWorkspace(page, template);
|
||||
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondBuildOption.name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondBuildOption.name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
|
||||
// Now, update the workspace, and select the value for ephemeral parameter.
|
||||
const buildParameters = [{ name: secondBuildOption.name, value: "true" }];
|
||||
await updateWorkspaceParameters(
|
||||
page,
|
||||
workspaceName,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
// Now, update the workspace, and select the value for ephemeral parameter.
|
||||
const buildParameters = [{ name: secondBuildOption.name, value: "true" }];
|
||||
await updateWorkspaceParameters(
|
||||
page,
|
||||
workspaceName,
|
||||
richParameters,
|
||||
buildParameters,
|
||||
);
|
||||
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondBuildOption.name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
// Verify that parameter values are default.
|
||||
await verifyParameters(page, workspaceName, richParameters, [
|
||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||
{ name: secondBuildOption.name, value: secondBuildOption.defaultValue },
|
||||
]);
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user