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",
|
"name": "Development environments on your infrastructure",
|
||||||
"image": "codercom/oss-dogfood:latest",
|
"image": "codercom/oss-dogfood:latest",
|
||||||
|
|
||||||
"features": {
|
"features": {
|
||||||
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
|
// See all possible options here https://github.com/devcontainers/features/tree/main/src/docker-in-docker
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||||
"moby": "false"
|
"moby": "false"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SYS_PTRACE to enable go debugging
|
// SYS_PTRACE to enable go debugging
|
||||||
"runArgs": ["--cap-add=SYS_PTRACE"]
|
"runArgs": ["--cap-add=SYS_PTRACE"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,10 +95,6 @@ updates:
|
|||||||
- "@emotion*"
|
- "@emotion*"
|
||||||
exclude-patterns:
|
exclude-patterns:
|
||||||
- "jest-runner-eslint"
|
- "jest-runner-eslint"
|
||||||
eslint:
|
|
||||||
patterns:
|
|
||||||
- "eslint*"
|
|
||||||
- "@typescript-eslint*"
|
|
||||||
jest:
|
jest:
|
||||||
patterns:
|
patterns:
|
||||||
- "jest"
|
- "jest"
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"ignorePatterns": [
|
"ignorePatterns": [
|
||||||
{
|
{
|
||||||
"pattern": "://localhost"
|
"pattern": "://localhost"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "://.*.?example\\.com"
|
"pattern": "://.*.?example\\.com"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "developer.github.com"
|
"pattern": "developer.github.com"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "docs.github.com"
|
"pattern": "docs.github.com"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "support.google.com"
|
"pattern": "support.google.com"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "tailscale.com"
|
"pattern": "tailscale.com"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pattern": "wireguard.com"
|
"pattern": "wireguard.com"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliveStatusCodes": [200, 0]
|
"aliveStatusCodes": [200, 0]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
printWidth: 80
|
printWidth: 80
|
||||||
proseWrap: always
|
proseWrap: always
|
||||||
trailingComma: all
|
trailingComma: all
|
||||||
useTabs: false
|
useTabs: true
|
||||||
tabWidth: 2
|
tabWidth: 2
|
||||||
overrides:
|
overrides:
|
||||||
- files:
|
- files:
|
||||||
|
|||||||
Vendored
+13
-13
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"github.vscode-codeql",
|
"github.vscode-codeql",
|
||||||
"golang.go",
|
"golang.go",
|
||||||
"hashicorp.terraform",
|
"hashicorp.terraform",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"foxundermoon.shell-format",
|
"foxundermoon.shell-format",
|
||||||
"emeraldwalk.runonsave",
|
"emeraldwalk.runonsave",
|
||||||
"zxh404.vscode-proto3",
|
"zxh404.vscode-proto3",
|
||||||
"redhat.vscode-yaml",
|
"redhat.vscode-yaml",
|
||||||
"streetsidesoftware.code-spell-checker",
|
"streetsidesoftware.code-spell-checker",
|
||||||
"EditorConfig.EditorConfig",
|
"EditorConfig.EditorConfig",
|
||||||
"biomejs.biome"
|
"biomejs.biome"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+234
-234
@@ -1,238 +1,238 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"afero",
|
"afero",
|
||||||
"agentsdk",
|
"agentsdk",
|
||||||
"apps",
|
"apps",
|
||||||
"ASKPASS",
|
"ASKPASS",
|
||||||
"authcheck",
|
"authcheck",
|
||||||
"autostop",
|
"autostop",
|
||||||
"awsidentity",
|
"awsidentity",
|
||||||
"bodyclose",
|
"bodyclose",
|
||||||
"buildinfo",
|
"buildinfo",
|
||||||
"buildname",
|
"buildname",
|
||||||
"circbuf",
|
"circbuf",
|
||||||
"cliflag",
|
"cliflag",
|
||||||
"cliui",
|
"cliui",
|
||||||
"codecov",
|
"codecov",
|
||||||
"coderd",
|
"coderd",
|
||||||
"coderdenttest",
|
"coderdenttest",
|
||||||
"coderdtest",
|
"coderdtest",
|
||||||
"codersdk",
|
"codersdk",
|
||||||
"contravariance",
|
"contravariance",
|
||||||
"cronstrue",
|
"cronstrue",
|
||||||
"databasefake",
|
"databasefake",
|
||||||
"dbgen",
|
"dbgen",
|
||||||
"dbmem",
|
"dbmem",
|
||||||
"dbtype",
|
"dbtype",
|
||||||
"DERP",
|
"DERP",
|
||||||
"derphttp",
|
"derphttp",
|
||||||
"derpmap",
|
"derpmap",
|
||||||
"devel",
|
"devel",
|
||||||
"devtunnel",
|
"devtunnel",
|
||||||
"dflags",
|
"dflags",
|
||||||
"drpc",
|
"drpc",
|
||||||
"drpcconn",
|
"drpcconn",
|
||||||
"drpcmux",
|
"drpcmux",
|
||||||
"drpcserver",
|
"drpcserver",
|
||||||
"Dsts",
|
"Dsts",
|
||||||
"embeddedpostgres",
|
"embeddedpostgres",
|
||||||
"enablements",
|
"enablements",
|
||||||
"enterprisemeta",
|
"enterprisemeta",
|
||||||
"errgroup",
|
"errgroup",
|
||||||
"eventsourcemock",
|
"eventsourcemock",
|
||||||
"externalauth",
|
"externalauth",
|
||||||
"Failf",
|
"Failf",
|
||||||
"fatih",
|
"fatih",
|
||||||
"Formik",
|
"Formik",
|
||||||
"gitauth",
|
"gitauth",
|
||||||
"gitsshkey",
|
"gitsshkey",
|
||||||
"goarch",
|
"goarch",
|
||||||
"gographviz",
|
"gographviz",
|
||||||
"goleak",
|
"goleak",
|
||||||
"gonet",
|
"gonet",
|
||||||
"gossh",
|
"gossh",
|
||||||
"gsyslog",
|
"gsyslog",
|
||||||
"GTTY",
|
"GTTY",
|
||||||
"hashicorp",
|
"hashicorp",
|
||||||
"hclsyntax",
|
"hclsyntax",
|
||||||
"httpapi",
|
"httpapi",
|
||||||
"httpmw",
|
"httpmw",
|
||||||
"idtoken",
|
"idtoken",
|
||||||
"Iflag",
|
"Iflag",
|
||||||
"incpatch",
|
"incpatch",
|
||||||
"initialisms",
|
"initialisms",
|
||||||
"ipnstate",
|
"ipnstate",
|
||||||
"isatty",
|
"isatty",
|
||||||
"Jobf",
|
"Jobf",
|
||||||
"Keygen",
|
"Keygen",
|
||||||
"kirsle",
|
"kirsle",
|
||||||
"Kubernetes",
|
"Kubernetes",
|
||||||
"ldflags",
|
"ldflags",
|
||||||
"magicsock",
|
"magicsock",
|
||||||
"manifoldco",
|
"manifoldco",
|
||||||
"mapstructure",
|
"mapstructure",
|
||||||
"mattn",
|
"mattn",
|
||||||
"mitchellh",
|
"mitchellh",
|
||||||
"moby",
|
"moby",
|
||||||
"namesgenerator",
|
"namesgenerator",
|
||||||
"namespacing",
|
"namespacing",
|
||||||
"netaddr",
|
"netaddr",
|
||||||
"netip",
|
"netip",
|
||||||
"netmap",
|
"netmap",
|
||||||
"netns",
|
"netns",
|
||||||
"netstack",
|
"netstack",
|
||||||
"nettype",
|
"nettype",
|
||||||
"nfpms",
|
"nfpms",
|
||||||
"nhooyr",
|
"nhooyr",
|
||||||
"nmcfg",
|
"nmcfg",
|
||||||
"nolint",
|
"nolint",
|
||||||
"nosec",
|
"nosec",
|
||||||
"ntqry",
|
"ntqry",
|
||||||
"OIDC",
|
"OIDC",
|
||||||
"oneof",
|
"oneof",
|
||||||
"opty",
|
"opty",
|
||||||
"paralleltest",
|
"paralleltest",
|
||||||
"parameterscopeid",
|
"parameterscopeid",
|
||||||
"pqtype",
|
"pqtype",
|
||||||
"prometheusmetrics",
|
"prometheusmetrics",
|
||||||
"promhttp",
|
"promhttp",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"provisionerd",
|
"provisionerd",
|
||||||
"provisionerdserver",
|
"provisionerdserver",
|
||||||
"provisionersdk",
|
"provisionersdk",
|
||||||
"ptty",
|
"ptty",
|
||||||
"ptys",
|
"ptys",
|
||||||
"ptytest",
|
"ptytest",
|
||||||
"quickstart",
|
"quickstart",
|
||||||
"reconfig",
|
"reconfig",
|
||||||
"replicasync",
|
"replicasync",
|
||||||
"retrier",
|
"retrier",
|
||||||
"rpty",
|
"rpty",
|
||||||
"SCIM",
|
"SCIM",
|
||||||
"sdkproto",
|
"sdkproto",
|
||||||
"sdktrace",
|
"sdktrace",
|
||||||
"Signup",
|
"Signup",
|
||||||
"slogtest",
|
"slogtest",
|
||||||
"sourcemapped",
|
"sourcemapped",
|
||||||
"spinbutton",
|
"spinbutton",
|
||||||
"Srcs",
|
"Srcs",
|
||||||
"stdbuf",
|
"stdbuf",
|
||||||
"stretchr",
|
"stretchr",
|
||||||
"STTY",
|
"STTY",
|
||||||
"stuntest",
|
"stuntest",
|
||||||
"tailbroker",
|
"tailbroker",
|
||||||
"tailcfg",
|
"tailcfg",
|
||||||
"tailexchange",
|
"tailexchange",
|
||||||
"tailnet",
|
"tailnet",
|
||||||
"tailnettest",
|
"tailnettest",
|
||||||
"Tailscale",
|
"Tailscale",
|
||||||
"tanstack",
|
"tanstack",
|
||||||
"tbody",
|
"tbody",
|
||||||
"TCGETS",
|
"TCGETS",
|
||||||
"tcpip",
|
"tcpip",
|
||||||
"TCSETS",
|
"TCSETS",
|
||||||
"templateversions",
|
"templateversions",
|
||||||
"testdata",
|
"testdata",
|
||||||
"testid",
|
"testid",
|
||||||
"testutil",
|
"testutil",
|
||||||
"tfexec",
|
"tfexec",
|
||||||
"tfjson",
|
"tfjson",
|
||||||
"tfplan",
|
"tfplan",
|
||||||
"tfstate",
|
"tfstate",
|
||||||
"thead",
|
"thead",
|
||||||
"tios",
|
"tios",
|
||||||
"tmpdir",
|
"tmpdir",
|
||||||
"tokenconfig",
|
"tokenconfig",
|
||||||
"Topbar",
|
"Topbar",
|
||||||
"tparallel",
|
"tparallel",
|
||||||
"trialer",
|
"trialer",
|
||||||
"trimprefix",
|
"trimprefix",
|
||||||
"tsdial",
|
"tsdial",
|
||||||
"tslogger",
|
"tslogger",
|
||||||
"tstun",
|
"tstun",
|
||||||
"turnconn",
|
"turnconn",
|
||||||
"typegen",
|
"typegen",
|
||||||
"typesafe",
|
"typesafe",
|
||||||
"unconvert",
|
"unconvert",
|
||||||
"Untar",
|
"Untar",
|
||||||
"Userspace",
|
"Userspace",
|
||||||
"VMID",
|
"VMID",
|
||||||
"walkthrough",
|
"walkthrough",
|
||||||
"weblinks",
|
"weblinks",
|
||||||
"webrtc",
|
"webrtc",
|
||||||
"wgcfg",
|
"wgcfg",
|
||||||
"wgconfig",
|
"wgconfig",
|
||||||
"wgengine",
|
"wgengine",
|
||||||
"wgmonitor",
|
"wgmonitor",
|
||||||
"wgnet",
|
"wgnet",
|
||||||
"workspaceagent",
|
"workspaceagent",
|
||||||
"workspaceagents",
|
"workspaceagents",
|
||||||
"workspaceapp",
|
"workspaceapp",
|
||||||
"workspaceapps",
|
"workspaceapps",
|
||||||
"workspacebuilds",
|
"workspacebuilds",
|
||||||
"workspacename",
|
"workspacename",
|
||||||
"wsjson",
|
"wsjson",
|
||||||
"xerrors",
|
"xerrors",
|
||||||
"xlarge",
|
"xlarge",
|
||||||
"xsmall",
|
"xsmall",
|
||||||
"yamux"
|
"yamux"
|
||||||
],
|
],
|
||||||
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],
|
||||||
"emeraldwalk.runonsave": {
|
"emeraldwalk.runonsave": {
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"match": "database/queries/*.sql",
|
"match": "database/queries/*.sql",
|
||||||
"cmd": "make gen"
|
"cmd": "make gen"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"match": "provisionerd/proto/provisionerd.proto",
|
"match": "provisionerd/proto/provisionerd.proto",
|
||||||
"cmd": "make provisionerd/proto/provisionerd.pb.go"
|
"cmd": "make provisionerd/proto/provisionerd.pb.go"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**.pb.go": true,
|
"**.pb.go": true,
|
||||||
"**/*.gen.json": true,
|
"**/*.gen.json": true,
|
||||||
"**/testdata/*": true,
|
"**/testdata/*": true,
|
||||||
"coderd/apidoc/**": true,
|
"coderd/apidoc/**": true,
|
||||||
"docs/reference/api/*.md": true,
|
"docs/reference/api/*.md": true,
|
||||||
"docs/reference/cli/*.md": true,
|
"docs/reference/cli/*.md": true,
|
||||||
"docs/templates/*.md": true,
|
"docs/templates/*.md": true,
|
||||||
"LICENSE": true,
|
"LICENSE": true,
|
||||||
"scripts/metricsdocgen/metrics": true,
|
"scripts/metricsdocgen/metrics": true,
|
||||||
"site/out/**": true,
|
"site/out/**": true,
|
||||||
"site/storybook-static/**": true,
|
"site/storybook-static/**": true,
|
||||||
"**.map": true,
|
"**.map": true,
|
||||||
"pnpm-lock.yaml": true
|
"pnpm-lock.yaml": true
|
||||||
},
|
},
|
||||||
// Ensure files always have a newline.
|
// Ensure files always have a newline.
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"go.lintTool": "golangci-lint",
|
"go.lintTool": "golangci-lint",
|
||||||
"go.lintFlags": ["--fast"],
|
"go.lintFlags": ["--fast"],
|
||||||
"go.coverageDecorator": {
|
"go.coverageDecorator": {
|
||||||
"type": "gutter",
|
"type": "gutter",
|
||||||
"coveredGutterStyle": "blockgreen",
|
"coveredGutterStyle": "blockgreen",
|
||||||
"uncoveredGutterStyle": "blockred"
|
"uncoveredGutterStyle": "blockred"
|
||||||
},
|
},
|
||||||
// The codersdk is used by coderd another other packages extensively.
|
// The codersdk is used by coderd another other packages extensively.
|
||||||
// To reduce redundancy in tests, it's covered by other packages.
|
// To reduce redundancy in tests, it's covered by other packages.
|
||||||
// Since package coverage pairing can't be defined, all packages cover
|
// Since package coverage pairing can't be defined, all packages cover
|
||||||
// all other packages.
|
// all other packages.
|
||||||
"go.testFlags": ["-short", "-coverpkg=./..."],
|
"go.testFlags": ["-short", "-coverpkg=./..."],
|
||||||
// We often use a version of TypeScript that's ahead of the version shipped
|
// We often use a version of TypeScript that's ahead of the version shipped
|
||||||
// with VS Code.
|
// with VS Code.
|
||||||
"typescript.tsdk": "./site/node_modules/typescript/lib",
|
"typescript.tsdk": "./site/node_modules/typescript/lib",
|
||||||
// Playwright tests in VSCode will open a browser to live "view" the test.
|
// Playwright tests in VSCode will open a browser to live "view" the test.
|
||||||
"playwright.reuseBrowser": true,
|
"playwright.reuseBrowser": true,
|
||||||
|
|
||||||
"[javascript][javascriptreact][json][jsonc][typescript][typescriptreact]": {
|
"[javascript][javascriptreact][json][jsonc][typescript][typescriptreact]": {
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
"editor.defaultFormatter": "biomejs.biome"
|
||||||
// "editor.codeActionsOnSave": {
|
// "editor.codeActionsOnSave": {
|
||||||
// "source.organizeImports.biome": "explicit"
|
// "source.organizeImports.biome": "explicit"
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
|
|
||||||
"[css][html][markdown][yaml]": {
|
"[css][html][markdown][yaml]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -491,7 +491,7 @@ gen: \
|
|||||||
site/src/api/typesGenerated.ts \
|
site/src/api/typesGenerated.ts \
|
||||||
coderd/rbac/object_gen.go \
|
coderd/rbac/object_gen.go \
|
||||||
codersdk/rbacresources_gen.go \
|
codersdk/rbacresources_gen.go \
|
||||||
site/src/api/rbacresources_gen.ts \
|
site/src/api/rbacresourcesGenerated.ts \
|
||||||
docs/admin/prometheus.md \
|
docs/admin/prometheus.md \
|
||||||
docs/reference/cli/README.md \
|
docs/reference/cli/README.md \
|
||||||
docs/admin/audit-logs.md \
|
docs/admin/audit-logs.md \
|
||||||
@@ -520,7 +520,7 @@ gen/mark-fresh:
|
|||||||
site/src/api/typesGenerated.ts \
|
site/src/api/typesGenerated.ts \
|
||||||
coderd/rbac/object_gen.go \
|
coderd/rbac/object_gen.go \
|
||||||
codersdk/rbacresources_gen.go \
|
codersdk/rbacresources_gen.go \
|
||||||
site/src/api/rbacresources_gen.ts \
|
site/src/api/rbacresourcesGenerated.ts \
|
||||||
docs/admin/prometheus.md \
|
docs/admin/prometheus.md \
|
||||||
docs/reference/cli/README.md \
|
docs/reference/cli/README.md \
|
||||||
docs/admin/audit-logs.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
|
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
|
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
|
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 > site/src/api/rbacresources_gen.ts
|
go run scripts/rbacgen/main.go typescript > "$@"
|
||||||
|
|
||||||
|
|
||||||
docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/metrics
|
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",
|
"action": "never-match-action",
|
||||||
"object": {
|
"object": {
|
||||||
"id": "9046b041-58ed-47a3-9c3a-de302577875a",
|
"id": "9046b041-58ed-47a3-9c3a-de302577875a",
|
||||||
"owner": "00000000-0000-0000-0000-000000000000",
|
"owner": "00000000-0000-0000-0000-000000000000",
|
||||||
"org_owner": "bf7b72bd-a2b1-4ef2-962c-1d698e0483f6",
|
"org_owner": "bf7b72bd-a2b1-4ef2-962c-1d698e0483f6",
|
||||||
"type": "workspace",
|
"type": "workspace",
|
||||||
"acl_user_list": {
|
"acl_user_list": {
|
||||||
"f041847d-711b-40da-a89a-ede39f70dc7f": ["create"]
|
"f041847d-711b-40da-a89a-ede39f70dc7f": ["create"]
|
||||||
},
|
},
|
||||||
"acl_group_list": {}
|
"acl_group_list": {}
|
||||||
},
|
},
|
||||||
"subject": {
|
"subject": {
|
||||||
"id": "10d03e62-7703-4df5-a358-4f76577d4e2f",
|
"id": "10d03e62-7703-4df5-a358-4f76577d4e2f",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"name": "owner",
|
"name": "owner",
|
||||||
"display_name": "Owner",
|
"display_name": "Owner",
|
||||||
"site": [
|
"site": [
|
||||||
{
|
{
|
||||||
"negate": false,
|
"negate": false,
|
||||||
"resource_type": "*",
|
"resource_type": "*",
|
||||||
"action": "*"
|
"action": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"org": {},
|
"org": {},
|
||||||
"user": []
|
"user": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"groups": ["b617a647-b5d0-4cbe-9e40-26f89710bf18"],
|
"groups": ["b617a647-b5d0-4cbe-9e40-26f89710bf18"],
|
||||||
"scope": {
|
"scope": {
|
||||||
"name": "Scope_all",
|
"name": "Scope_all",
|
||||||
"display_name": "All operations",
|
"display_name": "All operations",
|
||||||
"site": [
|
"site": [
|
||||||
{
|
{
|
||||||
"negate": false,
|
"negate": false,
|
||||||
"resource_type": "*",
|
"resource_type": "*",
|
||||||
"action": "*"
|
"action": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"org": {},
|
"org": {},
|
||||||
"user": [],
|
"user": [],
|
||||||
"allow_list": ["*"]
|
"allow_list": ["*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-28
@@ -82,34 +82,34 @@ entry:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ts": "2023-06-13T03:45:37.294730279Z",
|
"ts": "2023-06-13T03:45:37.294730279Z",
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"msg": "audit_log",
|
"msg": "audit_log",
|
||||||
"caller": "/home/runner/work/coder/coder/enterprise/audit/backends/slog.go:36",
|
"caller": "/home/runner/work/coder/coder/enterprise/audit/backends/slog.go:36",
|
||||||
"func": "github.com/coder/coder/enterprise/audit/backends.slogBackend.Export",
|
"func": "github.com/coder/coder/enterprise/audit/backends.slogBackend.Export",
|
||||||
"logger_names": ["coderd"],
|
"logger_names": ["coderd"],
|
||||||
"fields": {
|
"fields": {
|
||||||
"ID": "033a9ffa-b54d-4c10-8ec3-2aaf9e6d741a",
|
"ID": "033a9ffa-b54d-4c10-8ec3-2aaf9e6d741a",
|
||||||
"Time": "2023-06-13T03:45:37.288506Z",
|
"Time": "2023-06-13T03:45:37.288506Z",
|
||||||
"UserID": "6c405053-27e3-484a-9ad7-bcb64e7bfde6",
|
"UserID": "6c405053-27e3-484a-9ad7-bcb64e7bfde6",
|
||||||
"OrganizationID": "00000000-0000-0000-0000-000000000000",
|
"OrganizationID": "00000000-0000-0000-0000-000000000000",
|
||||||
"Ip": "{IPNet:{IP:\u003cnil\u003e Mask:\u003cnil\u003e} Valid:false}",
|
"Ip": "{IPNet:{IP:\u003cnil\u003e Mask:\u003cnil\u003e} Valid:false}",
|
||||||
"UserAgent": "{String: Valid:false}",
|
"UserAgent": "{String: Valid:false}",
|
||||||
"ResourceType": "workspace_build",
|
"ResourceType": "workspace_build",
|
||||||
"ResourceID": "ca5647e0-ef50-4202-a246-717e04447380",
|
"ResourceID": "ca5647e0-ef50-4202-a246-717e04447380",
|
||||||
"ResourceTarget": "",
|
"ResourceTarget": "",
|
||||||
"Action": "start",
|
"Action": "start",
|
||||||
"Diff": {},
|
"Diff": {},
|
||||||
"StatusCode": 200,
|
"StatusCode": 200,
|
||||||
"AdditionalFields": {
|
"AdditionalFields": {
|
||||||
"workspace_name": "linux-container",
|
"workspace_name": "linux-container",
|
||||||
"build_number": "9",
|
"build_number": "9",
|
||||||
"build_reason": "initiator",
|
"build_reason": "initiator",
|
||||||
"workspace_owner": ""
|
"workspace_owner": ""
|
||||||
},
|
},
|
||||||
"RequestID": "bb791ac3-f6ee-4da8-8ec2-f54e87013e93",
|
"RequestID": "bb791ac3-f6ee-4da8-8ec2-f54e87013e93",
|
||||||
"ResourceIcon": ""
|
"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.
|
> **Note:** Groups are only updated on login.
|
||||||
|
|
||||||
[azure-gids]:
|
[azure-gids]:
|
||||||
https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
|
https://github.com/MicrosoftDocs/azure-docs/issues/59766#issuecomment-664387195
|
||||||
|
|
||||||
### Group allowlist
|
### Group allowlist
|
||||||
|
|
||||||
|
|||||||
@@ -125,17 +125,17 @@ within the component's story.
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
export const WithQuota: Story = {
|
export const WithQuota: Story = {
|
||||||
parameters: {
|
parameters: {
|
||||||
queries: [
|
queries: [
|
||||||
{
|
{
|
||||||
key: getWorkspaceQuotaQueryKey(MockUser.username),
|
key: getWorkspaceQuotaQueryKey(MockUser.username),
|
||||||
data: {
|
data: {
|
||||||
credits_consumed: 2,
|
credits_consumed: 2,
|
||||||
budget: 40,
|
budget: 40,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -150,12 +150,12 @@ example below:
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const getAgentListeningPorts = async (
|
export const getAgentListeningPorts = async (
|
||||||
agentID: string,
|
agentID: string,
|
||||||
): Promise<TypesGen.ListeningPortsResponse> => {
|
): Promise<TypesGen.ListeningPortsResponse> => {
|
||||||
const response = await axiosInstance.get(
|
const response = await axiosInstance.get(
|
||||||
`/api/v2/workspaceagents/${agentID}/listening-ports`,
|
`/api/v2/workspaceagents/${agentID}/listening-ports`,
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -164,10 +164,10 @@ wrap it as a single function.
|
|||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const updateWorkspaceVersion = async (
|
export const updateWorkspaceVersion = async (
|
||||||
workspace: TypesGen.Workspace,
|
workspace: TypesGen.Workspace,
|
||||||
): Promise<TypesGen.WorkspaceBuild> => {
|
): Promise<TypesGen.WorkspaceBuild> => {
|
||||||
const template = await getTemplate(workspace.template_id);
|
const template = await getTemplate(workspace.template_id);
|
||||||
return startWorkspace(workspace.id, template.active_version_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";
|
import { visuallyHidden } from "@mui/utils";
|
||||||
|
|
||||||
<Button>
|
<Button>
|
||||||
<GearIcon />
|
<GearIcon />
|
||||||
<Box component="span" sx={visuallyHidden}>
|
<Box component="span" sx={visuallyHidden}>
|
||||||
Settings
|
Settings
|
||||||
</Box>
|
</Box>
|
||||||
</Button>;
|
</Button>;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+59
-59
@@ -39,21 +39,21 @@ following:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Principal": {
|
"Principal": {
|
||||||
"Federated": "accounts.google.com"
|
"Federated": "accounts.google.com"
|
||||||
},
|
},
|
||||||
"Action": "sts:AssumeRoleWithWebIdentity",
|
"Action": "sts:AssumeRoleWithWebIdentity",
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"StringEquals": {
|
"StringEquals": {
|
||||||
"accounts.google.com:aud": "<enter-OAuth-client-ID-here"
|
"accounts.google.com:aud": "<enter-OAuth-client-ID-here"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -64,50 +64,50 @@ following policy to the role:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Sid": "VisualEditor0",
|
"Sid": "VisualEditor0",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:GetDefaultCreditSpecification",
|
"ec2:GetDefaultCreditSpecification",
|
||||||
"ec2:DescribeIamInstanceProfileAssociations",
|
"ec2:DescribeIamInstanceProfileAssociations",
|
||||||
"ec2:DescribeTags",
|
"ec2:DescribeTags",
|
||||||
"ec2:DescribeInstances",
|
"ec2:DescribeInstances",
|
||||||
"ec2:DescribeInstanceTypes",
|
"ec2:DescribeInstanceTypes",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:DescribeInstanceCreditSpecifications",
|
"ec2:DescribeInstanceCreditSpecifications",
|
||||||
"ec2:DescribeImages",
|
"ec2:DescribeImages",
|
||||||
"ec2:ModifyDefaultCreditSpecification",
|
"ec2:ModifyDefaultCreditSpecification",
|
||||||
"ec2:DescribeVolumes"
|
"ec2:DescribeVolumes"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Sid": "CoderResources",
|
"Sid": "CoderResources",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:DescribeInstanceAttribute",
|
"ec2:DescribeInstanceAttribute",
|
||||||
"ec2:UnmonitorInstances",
|
"ec2:UnmonitorInstances",
|
||||||
"ec2:TerminateInstances",
|
"ec2:TerminateInstances",
|
||||||
"ec2:StartInstances",
|
"ec2:StartInstances",
|
||||||
"ec2:StopInstances",
|
"ec2:StopInstances",
|
||||||
"ec2:DeleteTags",
|
"ec2:DeleteTags",
|
||||||
"ec2:MonitorInstances",
|
"ec2:MonitorInstances",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:ModifyInstanceAttribute",
|
"ec2:ModifyInstanceAttribute",
|
||||||
"ec2:ModifyInstanceCreditSpecification"
|
"ec2:ModifyInstanceCreditSpecification"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"StringEquals": {
|
"StringEquals": {
|
||||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ actual Docker registry URL, username, and password.
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"auths": {
|
"auths": {
|
||||||
"<your-registry>": {
|
"<your-registry>": {
|
||||||
"username": "<your-username>",
|
"username": "<your-username>",
|
||||||
"password": "<your-password>"
|
"password": "<your-password>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -54,13 +54,13 @@ The output should look similar to this:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"auths": {
|
"auths": {
|
||||||
"your.private.registry.com": {
|
"your.private.registry.com": {
|
||||||
"username": "ericpaulsen",
|
"username": "ericpaulsen",
|
||||||
"password": "xxxx",
|
"password": "xxxx",
|
||||||
"auth": "c3R...zE2"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"document": "string",
|
"document": "string",
|
||||||
"signature": "string"
|
"signature": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/aws-instance-identi
|
|||||||
|
|
||||||
```json
|
```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
|
```json
|
||||||
{
|
{
|
||||||
"encoding": "string",
|
"encoding": "string",
|
||||||
"signature": "string"
|
"signature": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/azure-instance-iden
|
|||||||
|
|
||||||
```json
|
```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
|
||||||
{
|
{
|
||||||
"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
|
```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
|
```json
|
||||||
{
|
{
|
||||||
"access_token": "string",
|
"access_token": "string",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"token_extra": {},
|
"token_extra": {},
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"url": "string",
|
"url": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -231,12 +231,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitauth?match=str
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"access_token": "string",
|
"access_token": "string",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"token_extra": {},
|
"token_extra": {},
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"url": "string",
|
"url": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -267,8 +267,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitsshkey \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"private_key": "string",
|
"private_key": "string",
|
||||||
"public_key": "string"
|
"public_key": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -298,9 +298,9 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/log-source \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "string"
|
"id": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -316,11 +316,11 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/log-source \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"log_source_id": "string",
|
"log_source_id": "string",
|
||||||
"logs": [
|
"logs": [
|
||||||
{
|
{
|
||||||
"created_at": "string",
|
"created_at": "string",
|
||||||
"level": "trace",
|
"level": "trace",
|
||||||
"output": "string"
|
"output": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -373,14 +373,14 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/logs \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"message": "string",
|
"message": "string",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"field": "string"
|
"field": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -417,91 +417,91 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"api_version": "string",
|
"api_version": "string",
|
||||||
"apps": [
|
"apps": [
|
||||||
{
|
{
|
||||||
"command": "string",
|
"command": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"external": true,
|
"external": true,
|
||||||
"health": "disabled",
|
"health": "disabled",
|
||||||
"healthcheck": {
|
"healthcheck": {
|
||||||
"interval": 0,
|
"interval": 0,
|
||||||
"threshold": 0,
|
"threshold": 0,
|
||||||
"url": "string"
|
"url": "string"
|
||||||
},
|
},
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"sharing_level": "owner",
|
"sharing_level": "owner",
|
||||||
"slug": "string",
|
"slug": "string",
|
||||||
"subdomain": true,
|
"subdomain": true,
|
||||||
"subdomain_name": "string",
|
"subdomain_name": "string",
|
||||||
"url": "string"
|
"url": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"architecture": "string",
|
"architecture": "string",
|
||||||
"connection_timeout_seconds": 0,
|
"connection_timeout_seconds": 0,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"directory": "string",
|
"directory": "string",
|
||||||
"disconnected_at": "2019-08-24T14:15:22Z",
|
"disconnected_at": "2019-08-24T14:15:22Z",
|
||||||
"display_apps": ["vscode"],
|
"display_apps": ["vscode"],
|
||||||
"environment_variables": {
|
"environment_variables": {
|
||||||
"property1": "string",
|
"property1": "string",
|
||||||
"property2": "string"
|
"property2": "string"
|
||||||
},
|
},
|
||||||
"expanded_directory": "string",
|
"expanded_directory": "string",
|
||||||
"first_connected_at": "2019-08-24T14:15:22Z",
|
"first_connected_at": "2019-08-24T14:15:22Z",
|
||||||
"health": {
|
"health": {
|
||||||
"healthy": false,
|
"healthy": false,
|
||||||
"reason": "agent has lost connection"
|
"reason": "agent has lost connection"
|
||||||
},
|
},
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"instance_id": "string",
|
"instance_id": "string",
|
||||||
"last_connected_at": "2019-08-24T14:15:22Z",
|
"last_connected_at": "2019-08-24T14:15:22Z",
|
||||||
"latency": {
|
"latency": {
|
||||||
"property1": {
|
"property1": {
|
||||||
"latency_ms": 0,
|
"latency_ms": 0,
|
||||||
"preferred": true
|
"preferred": true
|
||||||
},
|
},
|
||||||
"property2": {
|
"property2": {
|
||||||
"latency_ms": 0,
|
"latency_ms": 0,
|
||||||
"preferred": true
|
"preferred": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lifecycle_state": "created",
|
"lifecycle_state": "created",
|
||||||
"log_sources": [
|
"log_sources": [
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"logs_length": 0,
|
"logs_length": 0,
|
||||||
"logs_overflowed": true,
|
"logs_overflowed": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"operating_system": "string",
|
"operating_system": "string",
|
||||||
"ready_at": "2019-08-24T14:15:22Z",
|
"ready_at": "2019-08-24T14:15:22Z",
|
||||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||||
"scripts": [
|
"scripts": [
|
||||||
{
|
{
|
||||||
"cron": "string",
|
"cron": "string",
|
||||||
"log_path": "string",
|
"log_path": "string",
|
||||||
"log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a",
|
"log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a",
|
||||||
"run_on_start": true,
|
"run_on_start": true,
|
||||||
"run_on_stop": true,
|
"run_on_stop": true,
|
||||||
"script": "string",
|
"script": "string",
|
||||||
"start_blocks_login": true,
|
"start_blocks_login": true,
|
||||||
"timeout": 0
|
"timeout": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"started_at": "2019-08-24T14:15:22Z",
|
"started_at": "2019-08-24T14:15:22Z",
|
||||||
"startup_script_behavior": "blocking",
|
"startup_script_behavior": "blocking",
|
||||||
"status": "connecting",
|
"status": "connecting",
|
||||||
"subsystems": ["envbox"],
|
"subsystems": ["envbox"],
|
||||||
"troubleshooting_url": "string",
|
"troubleshooting_url": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"version": "string"
|
"version": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -538,67 +538,67 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/con
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"derp_force_websockets": true,
|
"derp_force_websockets": true,
|
||||||
"derp_map": {
|
"derp_map": {
|
||||||
"homeParams": {
|
"homeParams": {
|
||||||
"regionScore": {
|
"regionScore": {
|
||||||
"property1": 0,
|
"property1": 0,
|
||||||
"property2": 0
|
"property2": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"omitDefaultRegions": true,
|
"omitDefaultRegions": true,
|
||||||
"regions": {
|
"regions": {
|
||||||
"property1": {
|
"property1": {
|
||||||
"avoid": true,
|
"avoid": true,
|
||||||
"embeddedRelay": true,
|
"embeddedRelay": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"canPort80": true,
|
"canPort80": true,
|
||||||
"certName": "string",
|
"certName": "string",
|
||||||
"derpport": 0,
|
"derpport": 0,
|
||||||
"forceHTTP": true,
|
"forceHTTP": true,
|
||||||
"hostName": "string",
|
"hostName": "string",
|
||||||
"insecureForTests": true,
|
"insecureForTests": true,
|
||||||
"ipv4": "string",
|
"ipv4": "string",
|
||||||
"ipv6": "string",
|
"ipv6": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"stunonly": true,
|
"stunonly": true,
|
||||||
"stunport": 0,
|
"stunport": 0,
|
||||||
"stuntestIP": "string"
|
"stuntestIP": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"regionCode": "string",
|
"regionCode": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"regionName": "string"
|
"regionName": "string"
|
||||||
},
|
},
|
||||||
"property2": {
|
"property2": {
|
||||||
"avoid": true,
|
"avoid": true,
|
||||||
"embeddedRelay": true,
|
"embeddedRelay": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"canPort80": true,
|
"canPort80": true,
|
||||||
"certName": "string",
|
"certName": "string",
|
||||||
"derpport": 0,
|
"derpport": 0,
|
||||||
"forceHTTP": true,
|
"forceHTTP": true,
|
||||||
"hostName": "string",
|
"hostName": "string",
|
||||||
"insecureForTests": true,
|
"insecureForTests": true,
|
||||||
"ipv4": "string",
|
"ipv4": "string",
|
||||||
"ipv6": "string",
|
"ipv6": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"stunonly": true,
|
"stunonly": true,
|
||||||
"stunport": 0,
|
"stunport": 0,
|
||||||
"stuntestIP": "string"
|
"stuntestIP": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"regionCode": "string",
|
"regionCode": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"regionName": "string"
|
"regionName": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disable_direct_connections": true
|
"disable_direct_connections": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -661,13 +661,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/lis
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ports": [
|
"ports": [
|
||||||
{
|
{
|
||||||
"network": "string",
|
"network": "string",
|
||||||
"port": 0,
|
"port": 0,
|
||||||
"process_name": "string"
|
"process_name": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -708,13 +708,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/log
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"level": "trace",
|
"level": "trace",
|
||||||
"output": "string",
|
"output": "string",
|
||||||
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -804,13 +804,13 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"level": "trace",
|
"level": "trace",
|
||||||
"output": "string",
|
"output": "string",
|
||||||
"source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81"
|
"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
|
```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
|
```json
|
||||||
{
|
{
|
||||||
"audit_logs": [
|
"audit_logs": [
|
||||||
{
|
{
|
||||||
"action": "create",
|
"action": "create",
|
||||||
"additional_fields": [0],
|
"additional_fields": [0],
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"diff": {
|
"diff": {
|
||||||
"property1": {
|
"property1": {
|
||||||
"new": null,
|
"new": null,
|
||||||
"old": null,
|
"old": null,
|
||||||
"secret": true
|
"secret": true
|
||||||
},
|
},
|
||||||
"property2": {
|
"property2": {
|
||||||
"new": null,
|
"new": null,
|
||||||
"old": null,
|
"old": null,
|
||||||
"secret": true
|
"secret": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"ip": "string",
|
"ip": "string",
|
||||||
"is_deleted": true,
|
"is_deleted": true,
|
||||||
"organization": {
|
"organization": {
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"name": "string"
|
"name": "string"
|
||||||
},
|
},
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||||
"resource_icon": "string",
|
"resource_icon": "string",
|
||||||
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||||
"resource_link": "string",
|
"resource_link": "string",
|
||||||
"resource_target": "string",
|
"resource_target": "string",
|
||||||
"resource_type": "template",
|
"resource_type": "template",
|
||||||
"status_code": 0,
|
"status_code": 0,
|
||||||
"time": "2019-08-24T14:15:22Z",
|
"time": "2019-08-24T14:15:22Z",
|
||||||
"user": {
|
"user": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
},
|
},
|
||||||
"user_agent": "string"
|
"user_agent": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"count": 0
|
"count": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Generated
+33
-33
@@ -18,28 +18,28 @@ curl -X POST http://coder-server:8080/api/v2/authcheck \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"checks": {
|
"checks": {
|
||||||
"property1": {
|
"property1": {
|
||||||
"action": "create",
|
"action": "create",
|
||||||
"object": {
|
"object": {
|
||||||
"any_org": true,
|
"any_org": true,
|
||||||
"organization_id": "string",
|
"organization_id": "string",
|
||||||
"owner_id": "string",
|
"owner_id": "string",
|
||||||
"resource_id": "string",
|
"resource_id": "string",
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"property2": {
|
"property2": {
|
||||||
"action": "create",
|
"action": "create",
|
||||||
"object": {
|
"object": {
|
||||||
"any_org": true,
|
"any_org": true,
|
||||||
"organization_id": "string",
|
"organization_id": "string",
|
||||||
"owner_id": "string",
|
"owner_id": "string",
|
||||||
"resource_id": "string",
|
"resource_id": "string",
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ curl -X POST http://coder-server:8080/api/v2/authcheck \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"property1": true,
|
"property1": true,
|
||||||
"property2": true
|
"property2": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -85,8 +85,8 @@ curl -X POST http://coder-server:8080/api/v2/users/login \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"password": "string"
|
"password": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ curl -X POST http://coder-server:8080/api/v2/users/login \
|
|||||||
|
|
||||||
```json
|
```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
|
```json
|
||||||
{
|
{
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"to_type": ""
|
"to_type": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -148,10 +148,10 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/convert-login \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"expires_at": "2019-08-24T14:15:22Z",
|
"expires_at": "2019-08-24T14:15:22Z",
|
||||||
"state_string": "string",
|
"state_string": "string",
|
||||||
"to_type": "",
|
"to_type": "",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"access_url": {
|
"access_url": {
|
||||||
"access_url": "string",
|
"access_url": "string",
|
||||||
"dismissed": true,
|
"dismissed": true,
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"healthz_response": "string",
|
"healthz_response": "string",
|
||||||
"reachable": true,
|
"reachable": true,
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"status_code": 0,
|
"status_code": 0,
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"coder_version": "string",
|
"coder_version": "string",
|
||||||
"database": {
|
"database": {
|
||||||
"dismissed": true,
|
"dismissed": true,
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"latency": "string",
|
"latency": "string",
|
||||||
"latency_ms": 0,
|
"latency_ms": 0,
|
||||||
"reachable": true,
|
"reachable": true,
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"threshold_ms": 0,
|
"threshold_ms": 0,
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"derp": {
|
"derp": {
|
||||||
"dismissed": true,
|
"dismissed": true,
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"netcheck": {
|
"netcheck": {
|
||||||
"captivePortal": "string",
|
"captivePortal": "string",
|
||||||
"globalV4": "string",
|
"globalV4": "string",
|
||||||
"globalV6": "string",
|
"globalV6": "string",
|
||||||
"hairPinning": "string",
|
"hairPinning": "string",
|
||||||
"icmpv4": true,
|
"icmpv4": true,
|
||||||
"ipv4": true,
|
"ipv4": true,
|
||||||
"ipv4CanSend": true,
|
"ipv4CanSend": true,
|
||||||
"ipv6": true,
|
"ipv6": true,
|
||||||
"ipv6CanSend": true,
|
"ipv6CanSend": true,
|
||||||
"mappingVariesByDestIP": "string",
|
"mappingVariesByDestIP": "string",
|
||||||
"oshasIPv6": true,
|
"oshasIPv6": true,
|
||||||
"pcp": "string",
|
"pcp": "string",
|
||||||
"pmp": "string",
|
"pmp": "string",
|
||||||
"preferredDERP": 0,
|
"preferredDERP": 0,
|
||||||
"regionLatency": {
|
"regionLatency": {
|
||||||
"property1": 0,
|
"property1": 0,
|
||||||
"property2": 0
|
"property2": 0
|
||||||
},
|
},
|
||||||
"regionV4Latency": {
|
"regionV4Latency": {
|
||||||
"property1": 0,
|
"property1": 0,
|
||||||
"property2": 0
|
"property2": 0
|
||||||
},
|
},
|
||||||
"regionV6Latency": {
|
"regionV6Latency": {
|
||||||
"property1": 0,
|
"property1": 0,
|
||||||
"property2": 0
|
"property2": 0
|
||||||
},
|
},
|
||||||
"udp": true,
|
"udp": true,
|
||||||
"upnP": "string"
|
"upnP": "string"
|
||||||
},
|
},
|
||||||
"netcheck_err": "string",
|
"netcheck_err": "string",
|
||||||
"netcheck_logs": ["string"],
|
"netcheck_logs": ["string"],
|
||||||
"regions": {
|
"regions": {
|
||||||
"property1": {
|
"property1": {
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"node_reports": [
|
"node_reports": [
|
||||||
{
|
{
|
||||||
"can_exchange_messages": true,
|
"can_exchange_messages": true,
|
||||||
"client_errs": [["string"]],
|
"client_errs": [["string"]],
|
||||||
"client_logs": [["string"]],
|
"client_logs": [["string"]],
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"node": {
|
"node": {
|
||||||
"canPort80": true,
|
"canPort80": true,
|
||||||
"certName": "string",
|
"certName": "string",
|
||||||
"derpport": 0,
|
"derpport": 0,
|
||||||
"forceHTTP": true,
|
"forceHTTP": true,
|
||||||
"hostName": "string",
|
"hostName": "string",
|
||||||
"insecureForTests": true,
|
"insecureForTests": true,
|
||||||
"ipv4": "string",
|
"ipv4": "string",
|
||||||
"ipv6": "string",
|
"ipv6": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"stunonly": true,
|
"stunonly": true,
|
||||||
"stunport": 0,
|
"stunport": 0,
|
||||||
"stuntestIP": "string"
|
"stuntestIP": "string"
|
||||||
},
|
},
|
||||||
"node_info": {
|
"node_info": {
|
||||||
"tokenBucketBytesBurst": 0,
|
"tokenBucketBytesBurst": 0,
|
||||||
"tokenBucketBytesPerSecond": 0
|
"tokenBucketBytesPerSecond": 0
|
||||||
},
|
},
|
||||||
"round_trip_ping": "string",
|
"round_trip_ping": "string",
|
||||||
"round_trip_ping_ms": 0,
|
"round_trip_ping_ms": 0,
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"stun": {
|
"stun": {
|
||||||
"canSTUN": true,
|
"canSTUN": true,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"error": "string"
|
"error": "string"
|
||||||
},
|
},
|
||||||
"uses_websocket": true,
|
"uses_websocket": true,
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"region": {
|
"region": {
|
||||||
"avoid": true,
|
"avoid": true,
|
||||||
"embeddedRelay": true,
|
"embeddedRelay": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"canPort80": true,
|
"canPort80": true,
|
||||||
"certName": "string",
|
"certName": "string",
|
||||||
"derpport": 0,
|
"derpport": 0,
|
||||||
"forceHTTP": true,
|
"forceHTTP": true,
|
||||||
"hostName": "string",
|
"hostName": "string",
|
||||||
"insecureForTests": true,
|
"insecureForTests": true,
|
||||||
"ipv4": "string",
|
"ipv4": "string",
|
||||||
"ipv6": "string",
|
"ipv6": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"stunonly": true,
|
"stunonly": true,
|
||||||
"stunport": 0,
|
"stunport": 0,
|
||||||
"stuntestIP": "string"
|
"stuntestIP": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"regionCode": "string",
|
"regionCode": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"regionName": "string"
|
"regionName": "string"
|
||||||
},
|
},
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"property2": {
|
"property2": {
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"node_reports": [
|
"node_reports": [
|
||||||
{
|
{
|
||||||
"can_exchange_messages": true,
|
"can_exchange_messages": true,
|
||||||
"client_errs": [["string"]],
|
"client_errs": [["string"]],
|
||||||
"client_logs": [["string"]],
|
"client_logs": [["string"]],
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"node": {
|
"node": {
|
||||||
"canPort80": true,
|
"canPort80": true,
|
||||||
"certName": "string",
|
"certName": "string",
|
||||||
"derpport": 0,
|
"derpport": 0,
|
||||||
"forceHTTP": true,
|
"forceHTTP": true,
|
||||||
"hostName": "string",
|
"hostName": "string",
|
||||||
"insecureForTests": true,
|
"insecureForTests": true,
|
||||||
"ipv4": "string",
|
"ipv4": "string",
|
||||||
"ipv6": "string",
|
"ipv6": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"stunonly": true,
|
"stunonly": true,
|
||||||
"stunport": 0,
|
"stunport": 0,
|
||||||
"stuntestIP": "string"
|
"stuntestIP": "string"
|
||||||
},
|
},
|
||||||
"node_info": {
|
"node_info": {
|
||||||
"tokenBucketBytesBurst": 0,
|
"tokenBucketBytesBurst": 0,
|
||||||
"tokenBucketBytesPerSecond": 0
|
"tokenBucketBytesPerSecond": 0
|
||||||
},
|
},
|
||||||
"round_trip_ping": "string",
|
"round_trip_ping": "string",
|
||||||
"round_trip_ping_ms": 0,
|
"round_trip_ping_ms": 0,
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"stun": {
|
"stun": {
|
||||||
"canSTUN": true,
|
"canSTUN": true,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"error": "string"
|
"error": "string"
|
||||||
},
|
},
|
||||||
"uses_websocket": true,
|
"uses_websocket": true,
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"region": {
|
"region": {
|
||||||
"avoid": true,
|
"avoid": true,
|
||||||
"embeddedRelay": true,
|
"embeddedRelay": true,
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"canPort80": true,
|
"canPort80": true,
|
||||||
"certName": "string",
|
"certName": "string",
|
||||||
"derpport": 0,
|
"derpport": 0,
|
||||||
"forceHTTP": true,
|
"forceHTTP": true,
|
||||||
"hostName": "string",
|
"hostName": "string",
|
||||||
"insecureForTests": true,
|
"insecureForTests": true,
|
||||||
"ipv4": "string",
|
"ipv4": "string",
|
||||||
"ipv6": "string",
|
"ipv6": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"stunonly": true,
|
"stunonly": true,
|
||||||
"stunport": 0,
|
"stunport": 0,
|
||||||
"stuntestIP": "string"
|
"stuntestIP": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"regionCode": "string",
|
"regionCode": "string",
|
||||||
"regionID": 0,
|
"regionID": 0,
|
||||||
"regionName": "string"
|
"regionName": "string"
|
||||||
},
|
},
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"provisioner_daemons": {
|
"provisioner_daemons": {
|
||||||
"dismissed": true,
|
"dismissed": true,
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"provisioner_daemon": {
|
"provisioner_daemon": {
|
||||||
"api_version": "string",
|
"api_version": "string",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"provisioners": ["string"],
|
"provisioners": ["string"],
|
||||||
"tags": {
|
"tags": {
|
||||||
"property1": "string",
|
"property1": "string",
|
||||||
"property2": "string"
|
"property2": "string"
|
||||||
},
|
},
|
||||||
"version": "string"
|
"version": "string"
|
||||||
},
|
},
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"time": "2019-08-24T14:15:22Z",
|
"time": "2019-08-24T14:15:22Z",
|
||||||
"websocket": {
|
"websocket": {
|
||||||
"body": "string",
|
"body": "string",
|
||||||
"code": 0,
|
"code": 0,
|
||||||
"dismissed": true,
|
"dismissed": true,
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"workspace_proxy": {
|
"workspace_proxy": {
|
||||||
"dismissed": true,
|
"dismissed": true,
|
||||||
"error": "string",
|
"error": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"severity": "ok",
|
"severity": "ok",
|
||||||
"warnings": [
|
"warnings": [
|
||||||
{
|
{
|
||||||
"code": "EUNKNOWN",
|
"code": "EUNKNOWN",
|
||||||
"message": "string"
|
"message": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"workspace_proxies": {
|
"workspace_proxies": {
|
||||||
"regions": [
|
"regions": [
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"deleted": true,
|
"deleted": true,
|
||||||
"derp_enabled": true,
|
"derp_enabled": true,
|
||||||
"derp_only": true,
|
"derp_only": true,
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"icon_url": "string",
|
"icon_url": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"path_app_url": "string",
|
"path_app_url": "string",
|
||||||
"status": {
|
"status": {
|
||||||
"checked_at": "2019-08-24T14:15:22Z",
|
"checked_at": "2019-08-24T14:15:22Z",
|
||||||
"report": {
|
"report": {
|
||||||
"errors": ["string"],
|
"errors": ["string"],
|
||||||
"warnings": ["string"]
|
"warnings": ["string"]
|
||||||
},
|
},
|
||||||
"status": "ok"
|
"status": "ok"
|
||||||
},
|
},
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"version": "string",
|
"version": "string",
|
||||||
"wildcard_hostname": "string"
|
"wildcard_hostname": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -401,7 +401,7 @@ curl -X GET http://coder-server:8080/api/v2/debug/health/settings \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dismissed_healthchecks": ["DERP"]
|
"dismissed_healthchecks": ["DERP"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -431,7 +431,7 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dismissed_healthchecks": ["DERP"]
|
"dismissed_healthchecks": ["DERP"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -447,7 +447,7 @@ curl -X PUT http://coder-server:8080/api/v2/debug/health/settings \
|
|||||||
|
|
||||||
```json
|
```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
|
```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
|
```json
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"message": "string",
|
"message": "string",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"field": "string"
|
"field": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -53,14 +53,14 @@ curl -X GET http://coder-server:8080/api/v2/buildinfo \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"agent_api_version": "string",
|
"agent_api_version": "string",
|
||||||
"dashboard_url": "string",
|
"dashboard_url": "string",
|
||||||
"deployment_id": "string",
|
"deployment_id": "string",
|
||||||
"external_url": "string",
|
"external_url": "string",
|
||||||
"telemetry": true,
|
"telemetry": true,
|
||||||
"upgrade_message": "string",
|
"upgrade_message": "string",
|
||||||
"version": "string",
|
"version": "string",
|
||||||
"workspace_proxy": true
|
"workspace_proxy": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ curl -X POST http://coder-server:8080/api/v2/csp/reports \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"csp-report": {}
|
"csp-report": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -124,377 +124,377 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"access_url": {
|
"access_url": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"port": "string"
|
"port": "string"
|
||||||
},
|
},
|
||||||
"agent_fallback_troubleshooting_url": {
|
"agent_fallback_troubleshooting_url": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
},
|
},
|
||||||
"agent_stat_refresh_interval": 0,
|
"agent_stat_refresh_interval": 0,
|
||||||
"allow_workspace_renames": true,
|
"allow_workspace_renames": true,
|
||||||
"autobuild_poll_interval": 0,
|
"autobuild_poll_interval": 0,
|
||||||
"browser_only": true,
|
"browser_only": true,
|
||||||
"cache_directory": "string",
|
"cache_directory": "string",
|
||||||
"cli_upgrade_message": "string",
|
"cli_upgrade_message": "string",
|
||||||
"config": "string",
|
"config": "string",
|
||||||
"config_ssh": {
|
"config_ssh": {
|
||||||
"deploymentName": "string",
|
"deploymentName": "string",
|
||||||
"sshconfigOptions": ["string"]
|
"sshconfigOptions": ["string"]
|
||||||
},
|
},
|
||||||
"dangerous": {
|
"dangerous": {
|
||||||
"allow_all_cors": true,
|
"allow_all_cors": true,
|
||||||
"allow_path_app_sharing": true,
|
"allow_path_app_sharing": true,
|
||||||
"allow_path_app_site_owner_access": true
|
"allow_path_app_site_owner_access": true
|
||||||
},
|
},
|
||||||
"derp": {
|
"derp": {
|
||||||
"config": {
|
"config": {
|
||||||
"block_direct": true,
|
"block_direct": true,
|
||||||
"force_websockets": true,
|
"force_websockets": true,
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"url": "string"
|
"url": "string"
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"region_code": "string",
|
"region_code": "string",
|
||||||
"region_id": 0,
|
"region_id": 0,
|
||||||
"region_name": "string",
|
"region_name": "string",
|
||||||
"relay_url": {
|
"relay_url": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
},
|
},
|
||||||
"stun_addresses": ["string"]
|
"stun_addresses": ["string"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"disable_owner_workspace_exec": true,
|
"disable_owner_workspace_exec": true,
|
||||||
"disable_password_auth": true,
|
"disable_password_auth": true,
|
||||||
"disable_path_apps": true,
|
"disable_path_apps": true,
|
||||||
"docs_url": {
|
"docs_url": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
},
|
},
|
||||||
"enable_terraform_debug_mode": true,
|
"enable_terraform_debug_mode": true,
|
||||||
"experiments": ["string"],
|
"experiments": ["string"],
|
||||||
"external_auth": {
|
"external_auth": {
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
"app_install_url": "string",
|
"app_install_url": "string",
|
||||||
"app_installations_url": "string",
|
"app_installations_url": "string",
|
||||||
"auth_url": "string",
|
"auth_url": "string",
|
||||||
"client_id": "string",
|
"client_id": "string",
|
||||||
"device_code_url": "string",
|
"device_code_url": "string",
|
||||||
"device_flow": true,
|
"device_flow": true,
|
||||||
"display_icon": "string",
|
"display_icon": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"no_refresh": true,
|
"no_refresh": true,
|
||||||
"regex": "string",
|
"regex": "string",
|
||||||
"scopes": ["string"],
|
"scopes": ["string"],
|
||||||
"token_url": "string",
|
"token_url": "string",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"validate_url": "string"
|
"validate_url": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"external_token_encryption_keys": ["string"],
|
"external_token_encryption_keys": ["string"],
|
||||||
"healthcheck": {
|
"healthcheck": {
|
||||||
"refresh": 0,
|
"refresh": 0,
|
||||||
"threshold_database": 0
|
"threshold_database": 0
|
||||||
},
|
},
|
||||||
"http_address": "string",
|
"http_address": "string",
|
||||||
"in_memory_database": true,
|
"in_memory_database": true,
|
||||||
"job_hang_detector_interval": 0,
|
"job_hang_detector_interval": 0,
|
||||||
"logging": {
|
"logging": {
|
||||||
"human": "string",
|
"human": "string",
|
||||||
"json": "string",
|
"json": "string",
|
||||||
"log_filter": ["string"],
|
"log_filter": ["string"],
|
||||||
"stackdriver": "string"
|
"stackdriver": "string"
|
||||||
},
|
},
|
||||||
"metrics_cache_refresh_interval": 0,
|
"metrics_cache_refresh_interval": 0,
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"dispatch_timeout": 0,
|
"dispatch_timeout": 0,
|
||||||
"email": {
|
"email": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"identity": "string",
|
"identity": "string",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"password_file": "string",
|
"password_file": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
},
|
},
|
||||||
"force_tls": true,
|
"force_tls": true,
|
||||||
"from": "string",
|
"from": "string",
|
||||||
"hello": "string",
|
"hello": "string",
|
||||||
"smarthost": {
|
"smarthost": {
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"port": "string"
|
"port": "string"
|
||||||
},
|
},
|
||||||
"tls": {
|
"tls": {
|
||||||
"ca_file": "string",
|
"ca_file": "string",
|
||||||
"cert_file": "string",
|
"cert_file": "string",
|
||||||
"insecure_skip_verify": true,
|
"insecure_skip_verify": true,
|
||||||
"key_file": "string",
|
"key_file": "string",
|
||||||
"server_name": "string",
|
"server_name": "string",
|
||||||
"start_tls": true
|
"start_tls": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fetch_interval": 0,
|
"fetch_interval": 0,
|
||||||
"lease_count": 0,
|
"lease_count": 0,
|
||||||
"lease_period": 0,
|
"lease_period": 0,
|
||||||
"max_send_attempts": 0,
|
"max_send_attempts": 0,
|
||||||
"method": "string",
|
"method": "string",
|
||||||
"retry_interval": 0,
|
"retry_interval": 0,
|
||||||
"sync_buffer_size": 0,
|
"sync_buffer_size": 0,
|
||||||
"sync_interval": 0,
|
"sync_interval": 0,
|
||||||
"webhook": {
|
"webhook": {
|
||||||
"endpoint": {
|
"endpoint": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth2": {
|
"oauth2": {
|
||||||
"github": {
|
"github": {
|
||||||
"allow_everyone": true,
|
"allow_everyone": true,
|
||||||
"allow_signups": true,
|
"allow_signups": true,
|
||||||
"allowed_orgs": ["string"],
|
"allowed_orgs": ["string"],
|
||||||
"allowed_teams": ["string"],
|
"allowed_teams": ["string"],
|
||||||
"client_id": "string",
|
"client_id": "string",
|
||||||
"client_secret": "string",
|
"client_secret": "string",
|
||||||
"enterprise_base_url": "string"
|
"enterprise_base_url": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oidc": {
|
"oidc": {
|
||||||
"allow_signups": true,
|
"allow_signups": true,
|
||||||
"auth_url_params": {},
|
"auth_url_params": {},
|
||||||
"client_cert_file": "string",
|
"client_cert_file": "string",
|
||||||
"client_id": "string",
|
"client_id": "string",
|
||||||
"client_key_file": "string",
|
"client_key_file": "string",
|
||||||
"client_secret": "string",
|
"client_secret": "string",
|
||||||
"email_domain": ["string"],
|
"email_domain": ["string"],
|
||||||
"email_field": "string",
|
"email_field": "string",
|
||||||
"group_allow_list": ["string"],
|
"group_allow_list": ["string"],
|
||||||
"group_auto_create": true,
|
"group_auto_create": true,
|
||||||
"group_mapping": {},
|
"group_mapping": {},
|
||||||
"group_regex_filter": {},
|
"group_regex_filter": {},
|
||||||
"groups_field": "string",
|
"groups_field": "string",
|
||||||
"icon_url": {
|
"icon_url": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
},
|
},
|
||||||
"ignore_email_verified": true,
|
"ignore_email_verified": true,
|
||||||
"ignore_user_info": true,
|
"ignore_user_info": true,
|
||||||
"issuer_url": "string",
|
"issuer_url": "string",
|
||||||
"name_field": "string",
|
"name_field": "string",
|
||||||
"scopes": ["string"],
|
"scopes": ["string"],
|
||||||
"sign_in_text": "string",
|
"sign_in_text": "string",
|
||||||
"signups_disabled_text": "string",
|
"signups_disabled_text": "string",
|
||||||
"skip_issuer_checks": true,
|
"skip_issuer_checks": true,
|
||||||
"user_role_field": "string",
|
"user_role_field": "string",
|
||||||
"user_role_mapping": {},
|
"user_role_mapping": {},
|
||||||
"user_roles_default": ["string"],
|
"user_roles_default": ["string"],
|
||||||
"username_field": "string"
|
"username_field": "string"
|
||||||
},
|
},
|
||||||
"pg_auth": "string",
|
"pg_auth": "string",
|
||||||
"pg_connection_url": "string",
|
"pg_connection_url": "string",
|
||||||
"pprof": {
|
"pprof": {
|
||||||
"address": {
|
"address": {
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"port": "string"
|
"port": "string"
|
||||||
},
|
},
|
||||||
"enable": true
|
"enable": true
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
"address": {
|
"address": {
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"port": "string"
|
"port": "string"
|
||||||
},
|
},
|
||||||
"aggregate_agent_stats_by": ["string"],
|
"aggregate_agent_stats_by": ["string"],
|
||||||
"collect_agent_stats": true,
|
"collect_agent_stats": true,
|
||||||
"collect_db_metrics": true,
|
"collect_db_metrics": true,
|
||||||
"enable": true
|
"enable": true
|
||||||
},
|
},
|
||||||
"provisioner": {
|
"provisioner": {
|
||||||
"daemon_poll_interval": 0,
|
"daemon_poll_interval": 0,
|
||||||
"daemon_poll_jitter": 0,
|
"daemon_poll_jitter": 0,
|
||||||
"daemon_psk": "string",
|
"daemon_psk": "string",
|
||||||
"daemon_types": ["string"],
|
"daemon_types": ["string"],
|
||||||
"daemons": 0,
|
"daemons": 0,
|
||||||
"force_cancel_interval": 0
|
"force_cancel_interval": 0
|
||||||
},
|
},
|
||||||
"proxy_health_status_interval": 0,
|
"proxy_health_status_interval": 0,
|
||||||
"proxy_trusted_headers": ["string"],
|
"proxy_trusted_headers": ["string"],
|
||||||
"proxy_trusted_origins": ["string"],
|
"proxy_trusted_origins": ["string"],
|
||||||
"rate_limit": {
|
"rate_limit": {
|
||||||
"api": 0,
|
"api": 0,
|
||||||
"disable_all": true
|
"disable_all": true
|
||||||
},
|
},
|
||||||
"redirect_to_access_url": true,
|
"redirect_to_access_url": true,
|
||||||
"scim_api_key": "string",
|
"scim_api_key": "string",
|
||||||
"secure_auth_cookie": true,
|
"secure_auth_cookie": true,
|
||||||
"session_lifetime": {
|
"session_lifetime": {
|
||||||
"default_duration": 0,
|
"default_duration": 0,
|
||||||
"disable_expiry_refresh": true,
|
"disable_expiry_refresh": true,
|
||||||
"max_token_lifetime": 0
|
"max_token_lifetime": 0
|
||||||
},
|
},
|
||||||
"ssh_keygen_algorithm": "string",
|
"ssh_keygen_algorithm": "string",
|
||||||
"strict_transport_security": 0,
|
"strict_transport_security": 0,
|
||||||
"strict_transport_security_options": ["string"],
|
"strict_transport_security_options": ["string"],
|
||||||
"support": {
|
"support": {
|
||||||
"links": {
|
"links": {
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
"icon": "bug",
|
"icon": "bug",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"target": "string"
|
"target": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"swagger": {
|
"swagger": {
|
||||||
"enable": true
|
"enable": true
|
||||||
},
|
},
|
||||||
"telemetry": {
|
"telemetry": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"trace": true,
|
"trace": true,
|
||||||
"url": {
|
"url": {
|
||||||
"forceQuery": true,
|
"forceQuery": true,
|
||||||
"fragment": "string",
|
"fragment": "string",
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"omitHost": true,
|
"omitHost": true,
|
||||||
"opaque": "string",
|
"opaque": "string",
|
||||||
"path": "string",
|
"path": "string",
|
||||||
"rawFragment": "string",
|
"rawFragment": "string",
|
||||||
"rawPath": "string",
|
"rawPath": "string",
|
||||||
"rawQuery": "string",
|
"rawQuery": "string",
|
||||||
"scheme": "string",
|
"scheme": "string",
|
||||||
"user": {}
|
"user": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"terms_of_service_url": "string",
|
"terms_of_service_url": "string",
|
||||||
"tls": {
|
"tls": {
|
||||||
"address": {
|
"address": {
|
||||||
"host": "string",
|
"host": "string",
|
||||||
"port": "string"
|
"port": "string"
|
||||||
},
|
},
|
||||||
"allow_insecure_ciphers": true,
|
"allow_insecure_ciphers": true,
|
||||||
"cert_file": ["string"],
|
"cert_file": ["string"],
|
||||||
"client_auth": "string",
|
"client_auth": "string",
|
||||||
"client_ca_file": "string",
|
"client_ca_file": "string",
|
||||||
"client_cert_file": "string",
|
"client_cert_file": "string",
|
||||||
"client_key_file": "string",
|
"client_key_file": "string",
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"key_file": ["string"],
|
"key_file": ["string"],
|
||||||
"min_version": "string",
|
"min_version": "string",
|
||||||
"redirect_http": true,
|
"redirect_http": true,
|
||||||
"supported_ciphers": ["string"]
|
"supported_ciphers": ["string"]
|
||||||
},
|
},
|
||||||
"trace": {
|
"trace": {
|
||||||
"capture_logs": true,
|
"capture_logs": true,
|
||||||
"data_dog": true,
|
"data_dog": true,
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"honeycomb_api_key": "string"
|
"honeycomb_api_key": "string"
|
||||||
},
|
},
|
||||||
"update_check": true,
|
"update_check": true,
|
||||||
"user_quiet_hours_schedule": {
|
"user_quiet_hours_schedule": {
|
||||||
"allow_user_custom": true,
|
"allow_user_custom": true,
|
||||||
"default_schedule": "string"
|
"default_schedule": "string"
|
||||||
},
|
},
|
||||||
"verbose": true,
|
"verbose": true,
|
||||||
"web_terminal_renderer": "string",
|
"web_terminal_renderer": "string",
|
||||||
"wgtunnel_host": "string",
|
"wgtunnel_host": "string",
|
||||||
"wildcard_access_url": "string",
|
"wildcard_access_url": "string",
|
||||||
"write_config": true
|
"write_config": true
|
||||||
},
|
},
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"property1": "string",
|
"property1": "string",
|
||||||
"property2": "string"
|
"property2": "string"
|
||||||
},
|
},
|
||||||
"default": "string",
|
"default": "string",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"env": "string",
|
"env": "string",
|
||||||
"flag": "string",
|
"flag": "string",
|
||||||
"flag_shorthand": "string",
|
"flag_shorthand": "string",
|
||||||
"group": {
|
"group": {
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"parent": {
|
"parent": {
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"parent": {},
|
"parent": {},
|
||||||
"yaml": "string"
|
"yaml": "string"
|
||||||
},
|
},
|
||||||
"yaml": "string"
|
"yaml": "string"
|
||||||
},
|
},
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"required": true,
|
"required": true,
|
||||||
"use_instead": [{}],
|
"use_instead": [{}],
|
||||||
"value": null,
|
"value": null,
|
||||||
"value_source": "",
|
"value_source": "",
|
||||||
"yaml": "string"
|
"yaml": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -525,11 +525,11 @@ curl -X GET http://coder-server:8080/api/v2/deployment/ssh \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"hostname_prefix": "string",
|
"hostname_prefix": "string",
|
||||||
"ssh_config_options": {
|
"ssh_config_options": {
|
||||||
"property1": "string",
|
"property1": "string",
|
||||||
"property2": "string"
|
"property2": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -560,28 +560,28 @@ curl -X GET http://coder-server:8080/api/v2/deployment/stats \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"aggregated_from": "2019-08-24T14:15:22Z",
|
"aggregated_from": "2019-08-24T14:15:22Z",
|
||||||
"collected_at": "2019-08-24T14:15:22Z",
|
"collected_at": "2019-08-24T14:15:22Z",
|
||||||
"next_update_at": "2019-08-24T14:15:22Z",
|
"next_update_at": "2019-08-24T14:15:22Z",
|
||||||
"session_count": {
|
"session_count": {
|
||||||
"jetbrains": 0,
|
"jetbrains": 0,
|
||||||
"reconnecting_pty": 0,
|
"reconnecting_pty": 0,
|
||||||
"ssh": 0,
|
"ssh": 0,
|
||||||
"vscode": 0
|
"vscode": 0
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"building": 0,
|
"building": 0,
|
||||||
"connection_latency_ms": {
|
"connection_latency_ms": {
|
||||||
"p50": 0,
|
"p50": 0,
|
||||||
"p95": 0
|
"p95": 0
|
||||||
},
|
},
|
||||||
"failed": 0,
|
"failed": 0,
|
||||||
"pending": 0,
|
"pending": 0,
|
||||||
"running": 0,
|
"running": 0,
|
||||||
"rx_bytes": 0,
|
"rx_bytes": 0,
|
||||||
"stopped": 0,
|
"stopped": 0,
|
||||||
"tx_bytes": 0
|
"tx_bytes": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -685,9 +685,9 @@ curl -X GET http://coder-server:8080/api/v2/updatecheck \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"current": true,
|
"current": true,
|
||||||
"url": "string",
|
"url": "string",
|
||||||
"version": "string"
|
"version": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -722,7 +722,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens/tokenconfig
|
|||||||
|
|
||||||
```json
|
```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
|
```json
|
||||||
{
|
{
|
||||||
"authenticated": true,
|
"authenticated": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"expires": "2019-08-24T14:15:22Z",
|
"expires": "2019-08-24T14:15:22Z",
|
||||||
"has_refresh_token": true,
|
"has_refresh_token": true,
|
||||||
"provider_id": "string",
|
"provider_id": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"validate_error": "string"
|
"validate_error": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -62,31 +62,31 @@ curl -X GET http://coder-server:8080/api/v2/external-auth/{externalauth} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"app_install_url": "string",
|
"app_install_url": "string",
|
||||||
"app_installable": true,
|
"app_installable": true,
|
||||||
"authenticated": true,
|
"authenticated": true,
|
||||||
"device": true,
|
"device": true,
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"installations": [
|
"installations": [
|
||||||
{
|
{
|
||||||
"account": {
|
"account": {
|
||||||
"avatar_url": "string",
|
"avatar_url": "string",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"login": "string",
|
"login": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"profile_url": "string"
|
"profile_url": "string"
|
||||||
},
|
},
|
||||||
"configure_url": "string",
|
"configure_url": "string",
|
||||||
"id": 0
|
"id": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user": {
|
"user": {
|
||||||
"avatar_url": "string",
|
"avatar_url": "string",
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"login": "string",
|
"login": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"profile_url": "string"
|
"profile_url": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -149,11 +149,11 @@ curl -X GET http://coder-server:8080/api/v2/external-auth/{externalauth}/device
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"device_code": "string",
|
"device_code": "string",
|
||||||
"expires_in": 0,
|
"expires_in": 0,
|
||||||
"interval": 0,
|
"interval": 0,
|
||||||
"user_code": "string",
|
"user_code": "string",
|
||||||
"verification_uri": "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
|
```json
|
||||||
{
|
{
|
||||||
"entries": [
|
"entries": [
|
||||||
{
|
{
|
||||||
"amount": 0,
|
"amount": 0,
|
||||||
"date": "string"
|
"date": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tz_hour_offset": 0
|
"tz_hour_offset": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -78,55 +78,55 @@ curl -X GET http://coder-server:8080/api/v2/insights/templates?start_time=2019-0
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"interval_reports": [
|
"interval_reports": [
|
||||||
{
|
{
|
||||||
"active_users": 14,
|
"active_users": 14,
|
||||||
"end_time": "2019-08-24T14:15:22Z",
|
"end_time": "2019-08-24T14:15:22Z",
|
||||||
"interval": "week",
|
"interval": "week",
|
||||||
"start_time": "2019-08-24T14:15:22Z",
|
"start_time": "2019-08-24T14:15:22Z",
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"report": {
|
"report": {
|
||||||
"active_users": 22,
|
"active_users": 22,
|
||||||
"apps_usage": [
|
"apps_usage": [
|
||||||
{
|
{
|
||||||
"display_name": "Visual Studio Code",
|
"display_name": "Visual Studio Code",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"seconds": 80500,
|
"seconds": 80500,
|
||||||
"slug": "vscode",
|
"slug": "vscode",
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"times_used": 2,
|
"times_used": 2,
|
||||||
"type": "builtin"
|
"type": "builtin"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"end_time": "2019-08-24T14:15:22Z",
|
"end_time": "2019-08-24T14:15:22Z",
|
||||||
"parameters_usage": [
|
"parameters_usage": [
|
||||||
{
|
{
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"value": "string"
|
"value": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"values": [
|
"values": [
|
||||||
{
|
{
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"value": "string"
|
"value": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_time": "2019-08-24T14:15:22Z",
|
"start_time": "2019-08-24T14:15:22Z",
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"]
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"report": {
|
"report": {
|
||||||
"end_time": "2019-08-24T14:15:22Z",
|
"end_time": "2019-08-24T14:15:22Z",
|
||||||
"start_time": "2019-08-24T14:15:22Z",
|
"start_time": "2019-08-24T14:15:22Z",
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"seconds": 80500,
|
"seconds": 80500,
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -217,23 +217,23 @@ curl -X GET http://coder-server:8080/api/v2/insights/user-latency?start_time=201
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"report": {
|
"report": {
|
||||||
"end_time": "2019-08-24T14:15:22Z",
|
"end_time": "2019-08-24T14:15:22Z",
|
||||||
"start_time": "2019-08-24T14:15:22Z",
|
"start_time": "2019-08-24T14:15:22Z",
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"latency_ms": {
|
"latency_ms": {
|
||||||
"p50": 31.312,
|
"p50": 31.312,
|
||||||
"p95": 119.832
|
"p95": 119.832
|
||||||
},
|
},
|
||||||
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"template_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Generated
+227
-227
@@ -25,30 +25,30 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"avatar_url": "string",
|
"avatar_url": "string",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "string",
|
"email": "string",
|
||||||
"global_roles": [
|
"global_roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,34 +106,34 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/members
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"assignable": true,
|
"assignable": true,
|
||||||
"built_in": true,
|
"built_in": true,
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -229,29 +229,29 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -268,32 +268,32 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -387,29 +387,29 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -426,32 +426,32 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -553,32 +553,32 @@ curl -X DELETE http://coder-server:8080/api/v2/organizations/{organization}/memb
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -680,17 +680,17 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -747,7 +747,7 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"roles": ["string"]
|
"roles": ["string"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -765,17 +765,17 @@ curl -X PUT http://coder-server:8080/api/v2/organizations/{organization}/members
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -806,34 +806,34 @@ curl -X GET http://coder-server:8080/api/v2/users/roles \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"assignable": true,
|
"assignable": true,
|
||||||
"built_in": true,
|
"built_in": true,
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"organization_permissions": [
|
"organization_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"site_permissions": [
|
"site_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_permissions": [
|
"user_permissions": [
|
||||||
{
|
{
|
||||||
"action": "application_connect",
|
"action": "application_connect",
|
||||||
"negate": true,
|
"negate": true,
|
||||||
"resource_type": "*"
|
"resource_type": "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Generated
+31
-31
@@ -19,10 +19,10 @@ curl -X GET http://coder-server:8080/api/v2/notifications/dispatch-methods \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"available": ["string"],
|
"available": ["string"],
|
||||||
"default": "string"
|
"default": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ curl -X GET http://coder-server:8080/api/v2/notifications/settings \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"notifier_paused": true
|
"notifier_paused": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ curl -X PUT http://coder-server:8080/api/v2/notifications/settings \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"notifier_paused": true
|
"notifier_paused": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ curl -X PUT http://coder-server:8080/api/v2/notifications/settings \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"notifier_paused": true
|
"notifier_paused": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -141,16 +141,16 @@ curl -X GET http://coder-server:8080/api/v2/notifications/templates/system \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"actions": "string",
|
"actions": "string",
|
||||||
"body_template": "string",
|
"body_template": "string",
|
||||||
"group": "string",
|
"group": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"kind": "string",
|
"kind": "string",
|
||||||
"method": "string",
|
"method": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"title_template": "string"
|
"title_template": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -203,11 +203,11 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/notifications/preferenc
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"template_disabled_map": {
|
"template_disabled_map": {
|
||||||
"property1": true,
|
"property1": true,
|
||||||
"property2": true
|
"property2": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -268,11 +268,11 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/notifications/preferenc
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"license": "string"
|
"license": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -34,10 +34,10 @@ curl -X POST http://coder-server:8080/api/v2/licenses \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"claims": {},
|
"claims": {},
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"uploaded_at": "2019-08-24T14:15:22Z",
|
"uploaded_at": "2019-08-24T14:15:22Z",
|
||||||
"uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f"
|
"uuid": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -68,14 +68,14 @@ curl -X POST http://coder-server:8080/api/v2/licenses/refresh-entitlements \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"message": "string",
|
"message": "string",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"field": "string"
|
"field": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,16 +106,16 @@ curl -X GET http://coder-server:8080/api/v2/organizations \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"updated_at": "2019-08-24T14:15:22Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -161,10 +161,10 @@ curl -X POST http://coder-server:8080/api/v2/organizations \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"name": "string"
|
"name": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -180,14 +180,14 @@ curl -X POST http://coder-server:8080/api/v2/organizations \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"updated_at": "2019-08-24T14:15:22Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -224,14 +224,14 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"updated_at": "2019-08-24T14:15:22Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -268,14 +268,14 @@ curl -X DELETE http://coder-server:8080/api/v2/organizations/{organization} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"message": "string",
|
"message": "string",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"field": "string"
|
"field": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -305,10 +305,10 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"name": "string"
|
"name": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -325,14 +325,14 @@ curl -X PATCH http://coder-server:8080/api/v2/organizations/{organization} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"agent_name": "string",
|
"agent_name": "string",
|
||||||
"port": 0
|
"port": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -55,10 +55,10 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/port-share \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"agent_name": "string",
|
"agent_name": "string",
|
||||||
"port": 0,
|
"port": 0,
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
"share_level": "owner"
|
"share_level": "owner"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -75,11 +75,11 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/port-share \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"agent_name": "string",
|
"agent_name": "string",
|
||||||
"port": 0,
|
"port": 0,
|
||||||
"protocol": "http",
|
"protocol": "http",
|
||||||
"share_level": "owner",
|
"share_level": "owner",
|
||||||
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -81,13 +81,13 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"disable_login": true,
|
"disable_login": true,
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -103,25 +103,25 @@ curl -X POST http://coder-server:8080/api/v2/users \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -152,18 +152,18 @@ curl -X GET http://coder-server:8080/api/v2/users/authmethods \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"github": {
|
"github": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"oidc": {
|
"oidc": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"iconUrl": "string",
|
"iconUrl": "string",
|
||||||
"signInText": "string"
|
"signInText": "string"
|
||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"terms_of_service_url": "string"
|
"terms_of_service_url": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -194,14 +194,14 @@ curl -X GET http://coder-server:8080/api/v2/users/first \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"message": "string",
|
"message": "string",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"field": "string"
|
"field": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -231,20 +231,20 @@ curl -X POST http://coder-server:8080/api/v2/users/first \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"email": "string",
|
"email": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"password": "string",
|
"password": "string",
|
||||||
"trial": true,
|
"trial": true,
|
||||||
"trial_info": {
|
"trial_info": {
|
||||||
"company_name": "string",
|
"company_name": "string",
|
||||||
"country": "string",
|
"country": "string",
|
||||||
"developers": "string",
|
"developers": "string",
|
||||||
"first_name": "string",
|
"first_name": "string",
|
||||||
"job_title": "string",
|
"job_title": "string",
|
||||||
"last_name": "string",
|
"last_name": "string",
|
||||||
"phone_number": "string"
|
"phone_number": "string"
|
||||||
},
|
},
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -260,8 +260,8 @@ curl -X POST http://coder-server:8080/api/v2/users/first \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -292,14 +292,14 @@ curl -X POST http://coder-server:8080/api/v2/users/logout \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"message": "string",
|
"message": "string",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"detail": "string",
|
"detail": "string",
|
||||||
"field": "string"
|
"field": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -376,25 +376,25 @@ curl -X GET http://coder-server:8080/api/v2/users/{user} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/appearance \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"theme_preference": "string"
|
"theme_preference": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -467,25 +467,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/appearance \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -523,10 +523,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/autofill-parameters?tem
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"value": "string"
|
"value": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -573,10 +573,10 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/gitsshkey \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"public_key": "string",
|
"public_key": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -613,10 +613,10 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/gitsshkey \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"public_key": "string",
|
"public_key": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -653,7 +653,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/keys \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"key": "string"
|
"key": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -690,18 +690,18 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"expires_at": "2019-08-24T14:15:22Z",
|
"expires_at": "2019-08-24T14:15:22Z",
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"last_used": "2019-08-24T14:15:22Z",
|
"last_used": "2019-08-24T14:15:22Z",
|
||||||
"lifetime_seconds": 0,
|
"lifetime_seconds": 0,
|
||||||
"login_type": "password",
|
"login_type": "password",
|
||||||
"scope": "all",
|
"scope": "all",
|
||||||
"token_name": "string",
|
"token_name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"lifetime": 0,
|
"lifetime": 0,
|
||||||
"scope": "all",
|
"scope": "all",
|
||||||
"token_name": "string"
|
"token_name": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -779,7 +779,7 @@ curl -X POST http://coder-server:8080/api/v2/users/{user}/keys/tokens \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"key": "string"
|
"key": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -817,16 +817,16 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/keys/tokens/{keyname} \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"expires_at": "2019-08-24T14:15:22Z",
|
"expires_at": "2019-08-24T14:15:22Z",
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"last_used": "2019-08-24T14:15:22Z",
|
"last_used": "2019-08-24T14:15:22Z",
|
||||||
"lifetime_seconds": 0,
|
"lifetime_seconds": 0,
|
||||||
"login_type": "password",
|
"login_type": "password",
|
||||||
"scope": "all",
|
"scope": "all",
|
||||||
"token_name": "string",
|
"token_name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"expires_at": "2019-08-24T14:15:22Z",
|
"expires_at": "2019-08-24T14:15:22Z",
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"last_used": "2019-08-24T14:15:22Z",
|
"last_used": "2019-08-24T14:15:22Z",
|
||||||
"lifetime_seconds": 0,
|
"lifetime_seconds": 0,
|
||||||
"login_type": "password",
|
"login_type": "password",
|
||||||
"scope": "all",
|
"scope": "all",
|
||||||
"token_name": "string",
|
"token_name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"user_id": "a169451c-8525-4352-b8ca-070dd449a1a5"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"login_type": ""
|
"login_type": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -974,16 +974,16 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/organizations \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"is_default": true,
|
"is_default": true,
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z"
|
"updated_at": "2019-08-24T14:15:22Z"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1073,8 +1073,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/password \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"old_password": "string",
|
"old_password": "string",
|
||||||
"password": "string"
|
"password": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1111,8 +1111,8 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1129,25 +1129,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/profile \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1184,25 +1184,25 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/roles \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1232,7 +1232,7 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"roles": ["string"]
|
"roles": ["string"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1249,25 +1249,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/roles \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1304,25 +1304,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/activate \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1359,25 +1359,25 @@ curl -X PUT http://coder-server:8080/api/v2/users/{user}/status/suspend \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"email": "user@example.com",
|
"email": "user@example.com",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"last_seen_at": "2019-08-24T14:15:22Z",
|
"last_seen_at": "2019-08-24T14:15:22Z",
|
||||||
"login_type": "",
|
"login_type": "",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
"organization_ids": ["497f6eca-6276-4993-bfeb-53cbbbba6f08"],
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"organization_id": "string"
|
"organization_id": "string"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"theme_preference": "string",
|
"theme_preference": "string",
|
||||||
"updated_at": "2019-08-24T14:15:22Z",
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
"username": "string"
|
"username": "string"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Generated
+11
-11
@@ -19,17 +19,17 @@ curl -X GET http://coder-server:8080/api/v2/regions \
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"regions": [
|
"regions": [
|
||||||
{
|
{
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"healthy": true,
|
"healthy": true,
|
||||||
"icon_url": "string",
|
"icon_url": "string",
|
||||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
"name": "string",
|
"name": "string",
|
||||||
"path_app_url": "string",
|
"path_app_url": "string",
|
||||||
"wildcard_hostname": "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
|
```json
|
||||||
{
|
{
|
||||||
"ts": "2022-02-28T20:29:38.038452202Z",
|
"ts": "2022-02-28T20:29:38.038452202Z",
|
||||||
"level": "INFO",
|
"level": "INFO",
|
||||||
"msg": "exec",
|
"msg": "exec",
|
||||||
"fields": {
|
"fields": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"user_email": "jessie@coder.com",
|
"user_email": "jessie@coder.com",
|
||||||
"user_id": "5e876e9a-121663f01ebd1522060d5270",
|
"user_id": "5e876e9a-121663f01ebd1522060d5270",
|
||||||
"username": "jessie",
|
"username": "jessie",
|
||||||
"workspace_id": "621d2e52-a6987ef6c56210058ee2593c",
|
"workspace_id": "621d2e52-a6987ef6c56210058ee2593c",
|
||||||
"workspace_name": "main"
|
"workspace_name": "main"
|
||||||
},
|
},
|
||||||
"cmdline": "uname -a",
|
"cmdline": "uname -a",
|
||||||
"event": {
|
"event": {
|
||||||
"filename": "/usr/bin/uname",
|
"filename": "/usr/bin/uname",
|
||||||
"argv": ["uname", "-a"],
|
"argv": ["uname", "-a"],
|
||||||
"truncated": false,
|
"truncated": false,
|
||||||
"pid": 920684,
|
"pid": 920684,
|
||||||
"uid": 101000,
|
"uid": 101000,
|
||||||
"gid": 101000,
|
"gid": 101000,
|
||||||
"comm": "bash"
|
"comm": "bash"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "Develop Coder on Coder using Envbuilder",
|
"name": "Develop Coder on Coder using Envbuilder",
|
||||||
"build": {
|
"build": {
|
||||||
"dockerfile": "Dockerfile"
|
"dockerfile": "Dockerfile"
|
||||||
},
|
},
|
||||||
|
|
||||||
"features": {},
|
"features": {},
|
||||||
"runArgs": ["--cap-add=SYS_PTRACE"]
|
"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.
|
// Code generated by examplegen. DO NOT EDIT.
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "aws-devcontainer",
|
"id": "aws-devcontainer",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "AWS EC2 (Devcontainer)",
|
"name": "AWS EC2 (Devcontainer)",
|
||||||
"description": "Provision AWS EC2 VMs with a devcontainer as Coder workspaces",
|
"description": "Provision AWS EC2 VMs with a devcontainer as Coder workspaces",
|
||||||
"icon": "/icon/aws.svg",
|
"icon": "/icon/aws.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"linux",
|
"linux",
|
||||||
"aws",
|
"aws",
|
||||||
"persistent",
|
"persistent",
|
||||||
"devcontainer"
|
"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"
|
"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",
|
"id": "aws-linux",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "AWS EC2 (Linux)",
|
"name": "AWS EC2 (Linux)",
|
||||||
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
||||||
"icon": "/icon/aws.svg",
|
"icon": "/icon/aws.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"linux",
|
"linux",
|
||||||
"aws",
|
"aws",
|
||||||
"persistent-vm"
|
"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"
|
"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",
|
"id": "aws-windows",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "AWS EC2 (Windows)",
|
"name": "AWS EC2 (Windows)",
|
||||||
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
"description": "Provision AWS EC2 VMs as Coder workspaces",
|
||||||
"icon": "/icon/aws.svg",
|
"icon": "/icon/aws.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"windows",
|
"windows",
|
||||||
"aws"
|
"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"
|
"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",
|
"id": "azure-linux",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Azure VM (Linux)",
|
"name": "Azure VM (Linux)",
|
||||||
"description": "Provision Azure VMs as Coder workspaces",
|
"description": "Provision Azure VMs as Coder workspaces",
|
||||||
"icon": "/icon/azure.png",
|
"icon": "/icon/azure.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"linux",
|
"linux",
|
||||||
"azure"
|
"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"
|
"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",
|
"id": "do-linux",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "DigitalOcean Droplet (Linux)",
|
"name": "DigitalOcean Droplet (Linux)",
|
||||||
"description": "Provision DigitalOcean Droplets as Coder workspaces",
|
"description": "Provision DigitalOcean Droplets as Coder workspaces",
|
||||||
"icon": "/icon/do.png",
|
"icon": "/icon/do.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"linux",
|
"linux",
|
||||||
"digitalocean"
|
"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"
|
"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",
|
"id": "docker",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Docker Containers",
|
"name": "Docker Containers",
|
||||||
"description": "Provision Docker containers as Coder workspaces",
|
"description": "Provision Docker containers as Coder workspaces",
|
||||||
"icon": "/icon/docker.png",
|
"icon": "/icon/docker.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"docker",
|
"docker",
|
||||||
"container"
|
"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"
|
"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",
|
"id": "gcp-devcontainer",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Google Compute Engine (Devcontainer)",
|
"name": "Google Compute Engine (Devcontainer)",
|
||||||
"description": "Provision a Devcontainer on Google Compute Engine instances as Coder workspaces",
|
"description": "Provision a Devcontainer on Google Compute Engine instances as Coder workspaces",
|
||||||
"icon": "/icon/gcp.png",
|
"icon": "/icon/gcp.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"linux",
|
"linux",
|
||||||
"gcp",
|
"gcp",
|
||||||
"devcontainer"
|
"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"
|
"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",
|
"id": "gcp-linux",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Google Compute Engine (Linux)",
|
"name": "Google Compute Engine (Linux)",
|
||||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||||
"icon": "/icon/gcp.png",
|
"icon": "/icon/gcp.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"linux",
|
"linux",
|
||||||
"gcp"
|
"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"
|
"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",
|
"id": "gcp-vm-container",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Google Compute Engine (VM Container)",
|
"name": "Google Compute Engine (VM Container)",
|
||||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||||
"icon": "/icon/gcp.png",
|
"icon": "/icon/gcp.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm-container",
|
"vm-container",
|
||||||
"linux",
|
"linux",
|
||||||
"gcp"
|
"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"
|
"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",
|
"id": "gcp-windows",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Google Compute Engine (Windows)",
|
"name": "Google Compute Engine (Windows)",
|
||||||
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
"description": "Provision Google Compute Engine instances as Coder workspaces",
|
||||||
"icon": "/icon/gcp.png",
|
"icon": "/icon/gcp.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"vm",
|
"vm",
|
||||||
"windows",
|
"windows",
|
||||||
"gcp"
|
"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"
|
"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",
|
"id": "kubernetes",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Kubernetes (Deployment)",
|
"name": "Kubernetes (Deployment)",
|
||||||
"description": "Provision Kubernetes Deployments as Coder workspaces",
|
"description": "Provision Kubernetes Deployments as Coder workspaces",
|
||||||
"icon": "/icon/k8s.png",
|
"icon": "/icon/k8s.png",
|
||||||
"tags": [
|
"tags": [
|
||||||
"kubernetes",
|
"kubernetes",
|
||||||
"container"
|
"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"
|
"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",
|
"id": "nomad-docker",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Nomad",
|
"name": "Nomad",
|
||||||
"description": "Provision Nomad Jobs as Coder workspaces",
|
"description": "Provision Nomad Jobs as Coder workspaces",
|
||||||
"icon": "/icon/nomad.svg",
|
"icon": "/icon/nomad.svg",
|
||||||
"tags": [
|
"tags": [
|
||||||
"nomad",
|
"nomad",
|
||||||
"container"
|
"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"
|
"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",
|
"id": "scratch",
|
||||||
"url": "",
|
"url": "",
|
||||||
"name": "Scratch",
|
"name": "Scratch",
|
||||||
"description": "A minimal starter template for Coder",
|
"description": "A minimal starter template for Coder",
|
||||||
"icon": "/emojis/1f4e6.png",
|
"icon": "/emojis/1f4e6.png",
|
||||||
"tags": [],
|
"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"
|
"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
|
```json
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Sid": "VisualEditor0",
|
"Sid": "VisualEditor0",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:GetDefaultCreditSpecification",
|
"ec2:GetDefaultCreditSpecification",
|
||||||
"ec2:DescribeIamInstanceProfileAssociations",
|
"ec2:DescribeIamInstanceProfileAssociations",
|
||||||
"ec2:DescribeTags",
|
"ec2:DescribeTags",
|
||||||
"ec2:DescribeInstances",
|
"ec2:DescribeInstances",
|
||||||
"ec2:DescribeInstanceTypes",
|
"ec2:DescribeInstanceTypes",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:DescribeInstanceCreditSpecifications",
|
"ec2:DescribeInstanceCreditSpecifications",
|
||||||
"ec2:DescribeImages",
|
"ec2:DescribeImages",
|
||||||
"ec2:ModifyDefaultCreditSpecification",
|
"ec2:ModifyDefaultCreditSpecification",
|
||||||
"ec2:DescribeVolumes"
|
"ec2:DescribeVolumes"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Sid": "CoderResources",
|
"Sid": "CoderResources",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:DescribeInstanceAttribute",
|
"ec2:DescribeInstanceAttribute",
|
||||||
"ec2:UnmonitorInstances",
|
"ec2:UnmonitorInstances",
|
||||||
"ec2:TerminateInstances",
|
"ec2:TerminateInstances",
|
||||||
"ec2:StartInstances",
|
"ec2:StartInstances",
|
||||||
"ec2:StopInstances",
|
"ec2:StopInstances",
|
||||||
"ec2:DeleteTags",
|
"ec2:DeleteTags",
|
||||||
"ec2:MonitorInstances",
|
"ec2:MonitorInstances",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:ModifyInstanceAttribute",
|
"ec2:ModifyInstanceAttribute",
|
||||||
"ec2:ModifyInstanceCreditSpecification"
|
"ec2:ModifyInstanceCreditSpecification"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"StringEquals": {
|
"StringEquals": {
|
||||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -30,50 +30,50 @@ instances provisioned by Coder:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Sid": "VisualEditor0",
|
"Sid": "VisualEditor0",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:GetDefaultCreditSpecification",
|
"ec2:GetDefaultCreditSpecification",
|
||||||
"ec2:DescribeIamInstanceProfileAssociations",
|
"ec2:DescribeIamInstanceProfileAssociations",
|
||||||
"ec2:DescribeTags",
|
"ec2:DescribeTags",
|
||||||
"ec2:DescribeInstances",
|
"ec2:DescribeInstances",
|
||||||
"ec2:DescribeInstanceTypes",
|
"ec2:DescribeInstanceTypes",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:DescribeInstanceCreditSpecifications",
|
"ec2:DescribeInstanceCreditSpecifications",
|
||||||
"ec2:DescribeImages",
|
"ec2:DescribeImages",
|
||||||
"ec2:ModifyDefaultCreditSpecification",
|
"ec2:ModifyDefaultCreditSpecification",
|
||||||
"ec2:DescribeVolumes"
|
"ec2:DescribeVolumes"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Sid": "CoderResources",
|
"Sid": "CoderResources",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:DescribeInstanceAttribute",
|
"ec2:DescribeInstanceAttribute",
|
||||||
"ec2:UnmonitorInstances",
|
"ec2:UnmonitorInstances",
|
||||||
"ec2:TerminateInstances",
|
"ec2:TerminateInstances",
|
||||||
"ec2:StartInstances",
|
"ec2:StartInstances",
|
||||||
"ec2:StopInstances",
|
"ec2:StopInstances",
|
||||||
"ec2:DeleteTags",
|
"ec2:DeleteTags",
|
||||||
"ec2:MonitorInstances",
|
"ec2:MonitorInstances",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:ModifyInstanceAttribute",
|
"ec2:ModifyInstanceAttribute",
|
||||||
"ec2:ModifyInstanceCreditSpecification"
|
"ec2:ModifyInstanceCreditSpecification"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"StringEquals": {
|
"StringEquals": {
|
||||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
"aws:ResourceTag/Coder_Provisioned": "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -30,50 +30,50 @@ instances provisioned by Coder:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"Version": "2012-10-17",
|
"Version": "2012-10-17",
|
||||||
"Statement": [
|
"Statement": [
|
||||||
{
|
{
|
||||||
"Sid": "VisualEditor0",
|
"Sid": "VisualEditor0",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:GetDefaultCreditSpecification",
|
"ec2:GetDefaultCreditSpecification",
|
||||||
"ec2:DescribeIamInstanceProfileAssociations",
|
"ec2:DescribeIamInstanceProfileAssociations",
|
||||||
"ec2:DescribeTags",
|
"ec2:DescribeTags",
|
||||||
"ec2:DescribeInstances",
|
"ec2:DescribeInstances",
|
||||||
"ec2:DescribeInstanceTypes",
|
"ec2:DescribeInstanceTypes",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:DescribeInstanceCreditSpecifications",
|
"ec2:DescribeInstanceCreditSpecifications",
|
||||||
"ec2:DescribeImages",
|
"ec2:DescribeImages",
|
||||||
"ec2:ModifyDefaultCreditSpecification",
|
"ec2:ModifyDefaultCreditSpecification",
|
||||||
"ec2:DescribeVolumes"
|
"ec2:DescribeVolumes"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Sid": "CoderResources",
|
"Sid": "CoderResources",
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Action": [
|
"Action": [
|
||||||
"ec2:DescribeInstanceAttribute",
|
"ec2:DescribeInstanceAttribute",
|
||||||
"ec2:UnmonitorInstances",
|
"ec2:UnmonitorInstances",
|
||||||
"ec2:TerminateInstances",
|
"ec2:TerminateInstances",
|
||||||
"ec2:StartInstances",
|
"ec2:StartInstances",
|
||||||
"ec2:StopInstances",
|
"ec2:StopInstances",
|
||||||
"ec2:DeleteTags",
|
"ec2:DeleteTags",
|
||||||
"ec2:MonitorInstances",
|
"ec2:MonitorInstances",
|
||||||
"ec2:CreateTags",
|
"ec2:CreateTags",
|
||||||
"ec2:RunInstances",
|
"ec2:RunInstances",
|
||||||
"ec2:ModifyInstanceAttribute",
|
"ec2:ModifyInstanceAttribute",
|
||||||
"ec2:ModifyInstanceCreditSpecification"
|
"ec2:ModifyInstanceCreditSpecification"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:ec2:*:*:instance/*",
|
"Resource": "arn:aws:ec2:*:*:instance/*",
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"StringEquals": {
|
"StringEquals": {
|
||||||
"aws:ResourceTag/Coder_Provisioned": "true"
|
"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} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: "export",
|
output: "export",
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
trailingSlash: true,
|
trailingSlash: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|||||||
+43
-43
@@ -1,45 +1,45 @@
|
|||||||
{
|
{
|
||||||
"name": "coder-docs-generator",
|
"name": "coder-docs-generator",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm copy-images && next dev",
|
"dev": "pnpm copy-images && next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"export": "pnpm copy-images && next build",
|
"export": "pnpm copy-images && next build",
|
||||||
"copy-images": "sh ./scripts/copyImages.sh",
|
"copy-images": "sh ./scripts/copyImages.sh",
|
||||||
"lint": "pnpm run lint:types",
|
"lint": "pnpm run lint:types",
|
||||||
"lint:types": "tsc --noEmit",
|
"lint:types": "tsc --noEmit",
|
||||||
"format": "prettier --cache --write './**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'",
|
"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}'"
|
"format:check": "prettier --cache --check './**/*.{css,html,js,json,jsx,md,ts,tsx,yaml,yml}'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chakra-ui/react": "2.8.2",
|
"@chakra-ui/react": "2.8.2",
|
||||||
"@emotion/react": "11.11.4",
|
"@emotion/react": "11.11.4",
|
||||||
"@emotion/styled": "11.11.5",
|
"@emotion/styled": "11.11.5",
|
||||||
"archiver": "6.0.2",
|
"archiver": "6.0.2",
|
||||||
"framer-motion": "^10.17.6",
|
"framer-motion": "^10.17.6",
|
||||||
"front-matter": "4.0.2",
|
"front-matter": "4.0.2",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"next": "14.2.4",
|
"next": "14.2.4",
|
||||||
"react": "18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-icons": "4.12.0",
|
"react-icons": "4.12.0",
|
||||||
"react-markdown": "9.0.1",
|
"react-markdown": "9.0.1",
|
||||||
"rehype-raw": "7.0.0",
|
"rehype-raw": "7.0.0",
|
||||||
"remark-gfm": "4.0.0"
|
"remark-gfm": "4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "4.14.196",
|
"@types/lodash": "4.14.196",
|
||||||
"@types/node": "18.19.0",
|
"@types/node": "18.19.0",
|
||||||
"@types/react": "18.3.3",
|
"@types/react": "18.3.3",
|
||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "18.3.0",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-config-next": "14.0.1",
|
"eslint-config-next": "14.0.1",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"typescript": "5.3.2"
|
"typescript": "5.3.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">=9.0.0 <10.0.0",
|
"npm": ">=9.0.0 <10.0.0",
|
||||||
"node": ">=18.0.0 <21.0.0"
|
"node": ">=18.0.0 <21.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+419
-419
@@ -1,29 +1,29 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Code,
|
Code,
|
||||||
Drawer,
|
Drawer,
|
||||||
DrawerBody,
|
DrawerBody,
|
||||||
DrawerCloseButton,
|
DrawerCloseButton,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
Flex,
|
Flex,
|
||||||
Grid,
|
Grid,
|
||||||
GridProps,
|
GridProps,
|
||||||
Heading,
|
Heading,
|
||||||
Icon,
|
Icon,
|
||||||
Img,
|
Img,
|
||||||
Link,
|
Link,
|
||||||
OrderedList,
|
OrderedList,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
Td,
|
Td,
|
||||||
Text,
|
Text,
|
||||||
Th,
|
Th,
|
||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
UnorderedList,
|
UnorderedList,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import fm from "front-matter";
|
import fm from "front-matter";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
@@ -42,19 +42,19 @@ import remarkGfm from "remark-gfm";
|
|||||||
type FilePath = string;
|
type FilePath = string;
|
||||||
type UrlPath = string;
|
type UrlPath = string;
|
||||||
type Route = {
|
type Route = {
|
||||||
path: FilePath;
|
path: FilePath;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
children?: Route[];
|
children?: Route[];
|
||||||
};
|
};
|
||||||
type Manifest = { versions: string[]; routes: Route[] };
|
type Manifest = { versions: string[]; routes: Route[] };
|
||||||
type NavItem = { title: string; path: UrlPath; children?: NavItem[] };
|
type NavItem = { title: string; path: UrlPath; children?: NavItem[] };
|
||||||
type Nav = NavItem[];
|
type Nav = NavItem[];
|
||||||
|
|
||||||
const readContentFile = (filePath: string) => {
|
const readContentFile = (filePath: string) => {
|
||||||
const baseDir = process.cwd();
|
const baseDir = process.cwd();
|
||||||
const docsPath = path.join(baseDir, "..", "docs");
|
const docsPath = path.join(baseDir, "..", "docs");
|
||||||
return readFileSync(path.join(docsPath, filePath), { encoding: "utf-8" });
|
return readFileSync(path.join(docsPath, filePath), { encoding: "utf-8" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTrailingSlash = (path: string) => path.replace(/\/+$/, "");
|
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 removeMkdExtension = (path: string) => path.replace(/\.md/g, "");
|
||||||
|
|
||||||
const removeIndexFilename = (path: string) => {
|
const removeIndexFilename = (path: string) => {
|
||||||
if (path.endsWith("index")) {
|
if (path.endsWith("index")) {
|
||||||
path = path.replace("index", "");
|
path = path.replace("index", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeREADMEName = (path: string) => {
|
const removeREADMEName = (path: string) => {
|
||||||
if (path.startsWith("README")) {
|
if (path.startsWith("README")) {
|
||||||
path = path.replace("README", "");
|
path = path.replace("README", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
// transformLinkUri converts the links in the markdown file to
|
// 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 -> ./subdir/file = ../subdir/file
|
||||||
// file.md -> ../file-next-to-file = ../file-next-to-file
|
// file.md -> ../file-next-to-file = ../file-next-to-file
|
||||||
const transformLinkUriSource = (sourceFile: string) => {
|
const transformLinkUriSource = (sourceFile: string) => {
|
||||||
return (href = "") => {
|
return (href = "") => {
|
||||||
const isExternal = href.startsWith("http") || href.startsWith("https");
|
const isExternal = href.startsWith("http") || href.startsWith("https");
|
||||||
if (!isExternal) {
|
if (!isExternal) {
|
||||||
// Remove .md form the path
|
// Remove .md form the path
|
||||||
href = removeMkdExtension(href);
|
href = removeMkdExtension(href);
|
||||||
|
|
||||||
// Add the extra '..' if not an index file.
|
// Add the extra '..' if not an index file.
|
||||||
sourceFile = removeMkdExtension(sourceFile);
|
sourceFile = removeMkdExtension(sourceFile);
|
||||||
if (!sourceFile.endsWith("index")) {
|
if (!sourceFile.endsWith("index")) {
|
||||||
href = "../" + href;
|
href = "../" + href;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the index path
|
// Remove the index path
|
||||||
href = removeIndexFilename(href);
|
href = removeIndexFilename(href);
|
||||||
href = removeREADMEName(href);
|
href = removeREADMEName(href);
|
||||||
}
|
}
|
||||||
return href;
|
return href;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformFilePathToUrlPath = (filePath: string) => {
|
const transformFilePathToUrlPath = (filePath: string) => {
|
||||||
// Remove markdown extension
|
// Remove markdown extension
|
||||||
let urlPath = removeMkdExtension(filePath);
|
let urlPath = removeMkdExtension(filePath);
|
||||||
|
|
||||||
// Remove relative path
|
// Remove relative path
|
||||||
if (urlPath.startsWith("./")) {
|
if (urlPath.startsWith("./")) {
|
||||||
urlPath = urlPath.replace("./", "");
|
urlPath = urlPath.replace("./", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove index from the root file
|
// Remove index from the root file
|
||||||
urlPath = removeIndexFilename(urlPath);
|
urlPath = removeIndexFilename(urlPath);
|
||||||
urlPath = removeREADMEName(urlPath);
|
urlPath = removeREADMEName(urlPath);
|
||||||
|
|
||||||
// Remove trailing slash
|
// Remove trailing slash
|
||||||
if (urlPath.endsWith("/")) {
|
if (urlPath.endsWith("/")) {
|
||||||
urlPath = removeTrailingSlash(urlPath);
|
urlPath = removeTrailingSlash(urlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlPath;
|
return urlPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapRoutes = (manifest: Manifest): Record<UrlPath, Route> => {
|
const mapRoutes = (manifest: Manifest): Record<UrlPath, Route> => {
|
||||||
const paths: Record<UrlPath, Route> = {};
|
const paths: Record<UrlPath, Route> = {};
|
||||||
|
|
||||||
const addPaths = (routes: Route[]) => {
|
const addPaths = (routes: Route[]) => {
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
paths[transformFilePathToUrlPath(route.path)] = route;
|
paths[transformFilePathToUrlPath(route.path)] = route;
|
||||||
|
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
addPaths(route.children);
|
addPaths(route.children);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addPaths(manifest.routes);
|
addPaths(manifest.routes);
|
||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
};
|
};
|
||||||
|
|
||||||
let manifest: Manifest | undefined;
|
let manifest: Manifest | undefined;
|
||||||
|
|
||||||
const getManifest = () => {
|
const getManifest = () => {
|
||||||
if (manifest) {
|
if (manifest) {
|
||||||
return manifest;
|
return manifest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const manifestContent = readContentFile("manifest.json");
|
const manifestContent = readContentFile("manifest.json");
|
||||||
manifest = JSON.parse(manifestContent) as Manifest;
|
manifest = JSON.parse(manifestContent) as Manifest;
|
||||||
return manifest;
|
return manifest;
|
||||||
};
|
};
|
||||||
|
|
||||||
let navigation: Nav | undefined;
|
let navigation: Nav | undefined;
|
||||||
|
|
||||||
const getNavigation = (manifest: Manifest): Nav => {
|
const getNavigation = (manifest: Manifest): Nav => {
|
||||||
if (navigation) {
|
if (navigation) {
|
||||||
return navigation;
|
return navigation;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNavItem = (route: Route, parentPath?: UrlPath): NavItem => {
|
const getNavItem = (route: Route, parentPath?: UrlPath): NavItem => {
|
||||||
const path = parentPath
|
const path = parentPath
|
||||||
? `${parentPath}/${transformFilePathToUrlPath(route.path)}`
|
? `${parentPath}/${transformFilePathToUrlPath(route.path)}`
|
||||||
: transformFilePathToUrlPath(route.path);
|
: transformFilePathToUrlPath(route.path);
|
||||||
const navItem: NavItem = {
|
const navItem: NavItem = {
|
||||||
title: route.title,
|
title: route.title,
|
||||||
path,
|
path,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (route.children) {
|
if (route.children) {
|
||||||
navItem.children = [];
|
navItem.children = [];
|
||||||
|
|
||||||
for (const childRoute of route.children) {
|
for (const childRoute of route.children) {
|
||||||
navItem.children.push(getNavItem(childRoute));
|
navItem.children.push(getNavItem(childRoute));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return navItem;
|
return navItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
navigation = [];
|
navigation = [];
|
||||||
|
|
||||||
for (const route of manifest.routes) {
|
for (const route of manifest.routes) {
|
||||||
navigation.push(getNavItem(route));
|
navigation.push(getNavItem(route));
|
||||||
}
|
}
|
||||||
|
|
||||||
return navigation;
|
return navigation;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeHtmlComments = (string: string) => {
|
const removeHtmlComments = (string: string) => {
|
||||||
return string.replace(/<!--[\s\S]*?-->/g, "");
|
return string.replace(/<!--[\s\S]*?-->/g, "");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = () => {
|
export const getStaticPaths: GetStaticPaths = () => {
|
||||||
const manifest = getManifest();
|
const manifest = getManifest();
|
||||||
const routes = mapRoutes(manifest);
|
const routes = mapRoutes(manifest);
|
||||||
const paths = Object.keys(routes).map((urlPath) => ({
|
const paths = Object.keys(routes).map((urlPath) => ({
|
||||||
params: { slug: urlPath.split("/") },
|
params: { slug: urlPath.split("/") },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
fallback: false,
|
fallback: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = (context) => {
|
export const getStaticProps: GetStaticProps = (context) => {
|
||||||
// When it is home page, the slug is undefined because there is no url path
|
// 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
|
// so we make it an empty string to work good with the mapRoutes
|
||||||
const { slug = [""] } = context.params as { slug: string[] };
|
const { slug = [""] } = context.params as { slug: string[] };
|
||||||
const manifest = getManifest();
|
const manifest = getManifest();
|
||||||
const routes = mapRoutes(manifest);
|
const routes = mapRoutes(manifest);
|
||||||
const urlPath = slug.join("/");
|
const urlPath = slug.join("/");
|
||||||
const route = routes[urlPath];
|
const route = routes[urlPath];
|
||||||
const { body } = fm(readContentFile(route.path));
|
const { body } = fm(readContentFile(route.path));
|
||||||
// Serialize MDX to support custom components
|
// Serialize MDX to support custom components
|
||||||
const content = removeHtmlComments(body);
|
const content = removeHtmlComments(body);
|
||||||
const navigation = getNavigation(manifest);
|
const navigation = getNavigation(manifest);
|
||||||
const version = manifest.versions[0];
|
const version = manifest.versions[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
content,
|
content,
|
||||||
navigation,
|
navigation,
|
||||||
route,
|
route,
|
||||||
version,
|
version,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarNavItem: React.FC<{ item: NavItem; nav: Nav }> = ({
|
const SidebarNavItem: React.FC<{ item: NavItem; nav: Nav }> = ({
|
||||||
item,
|
item,
|
||||||
nav,
|
nav,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
let isActive = router.asPath.startsWith(`/${item.path}`);
|
let isActive = router.asPath.startsWith(`/${item.path}`);
|
||||||
|
|
||||||
// Special case to handle the home path
|
// Special case to handle the home path
|
||||||
if (item.path === "") {
|
if (item.path === "") {
|
||||||
isActive = router.asPath === "/";
|
isActive = router.asPath === "/";
|
||||||
|
|
||||||
// Special case to handle the home path children
|
// Special case to handle the home path children
|
||||||
const homeNav = nav.find((navItem) => navItem.path === "") as NavItem;
|
const homeNav = nav.find((navItem) => navItem.path === "") as NavItem;
|
||||||
const homeNavPaths =
|
const homeNavPaths =
|
||||||
homeNav.children?.map((item) => `/${item.path}/`) ?? [];
|
homeNav.children?.map((item) => `/${item.path}/`) ?? [];
|
||||||
if (homeNavPaths.includes(router.asPath)) {
|
if (homeNavPaths.includes(router.asPath)) {
|
||||||
isActive = true;
|
isActive = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<NextLink href={"/" + item.path} passHref legacyBehavior>
|
<NextLink href={"/" + item.path} passHref legacyBehavior>
|
||||||
<Link
|
<Link
|
||||||
fontWeight={isActive ? 600 : 400}
|
fontWeight={isActive ? 600 : 400}
|
||||||
color={isActive ? "gray.900" : "gray.700"}
|
color={isActive ? "gray.900" : "gray.700"}
|
||||||
>
|
>
|
||||||
{item.title}
|
{item.title}
|
||||||
</Link>
|
</Link>
|
||||||
</NextLink>
|
</NextLink>
|
||||||
|
|
||||||
{isActive && item.children && (
|
{isActive && item.children && (
|
||||||
<Grid
|
<Grid
|
||||||
as="nav"
|
as="nav"
|
||||||
pt={2}
|
pt={2}
|
||||||
pl={3}
|
pl={3}
|
||||||
maxW="sm"
|
maxW="sm"
|
||||||
autoFlow="row"
|
autoFlow="row"
|
||||||
gap={2}
|
gap={2}
|
||||||
autoRows="min-content"
|
autoRows="min-content"
|
||||||
>
|
>
|
||||||
{item.children.map((subItem) => (
|
{item.children.map((subItem) => (
|
||||||
<SidebarNavItem key={subItem.path} item={subItem} nav={nav} />
|
<SidebarNavItem key={subItem.path} item={subItem} nav={nav} />
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarNav: React.FC<{ nav: Nav; version: string } & GridProps> = ({
|
const SidebarNav: React.FC<{ nav: Nav; version: string } & GridProps> = ({
|
||||||
nav,
|
nav,
|
||||||
version,
|
version,
|
||||||
...gridProps
|
...gridProps
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
h="100vh"
|
h="100vh"
|
||||||
overflowY="scroll"
|
overflowY="scroll"
|
||||||
as="nav"
|
as="nav"
|
||||||
p={8}
|
p={8}
|
||||||
w="300px"
|
w="300px"
|
||||||
autoFlow="row"
|
autoFlow="row"
|
||||||
gap={2}
|
gap={2}
|
||||||
autoRows="min-content"
|
autoRows="min-content"
|
||||||
bgColor="white"
|
bgColor="white"
|
||||||
borderRightWidth={1}
|
borderRightWidth={1}
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
borderStyle="solid"
|
borderStyle="solid"
|
||||||
{...gridProps}
|
{...gridProps}
|
||||||
>
|
>
|
||||||
<Box mb={6}>
|
<Box mb={6}>
|
||||||
<Img src="/logo.svg" alt="Coder logo" />
|
<Img src="/logo.svg" alt="Coder logo" />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{nav.map((navItem) => (
|
{nav.map((navItem) => (
|
||||||
<SidebarNavItem key={navItem.path} item={navItem} nav={nav} />
|
<SidebarNavItem key={navItem.path} item={navItem} nav={nav} />
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const MobileNavbar: React.FC<{ nav: Nav; version: string }> = ({
|
const MobileNavbar: React.FC<{ nav: Nav; version: string }> = ({
|
||||||
nav,
|
nav,
|
||||||
version,
|
version,
|
||||||
}) => {
|
}) => {
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex
|
<Flex
|
||||||
bgColor="white"
|
bgColor="white"
|
||||||
px={6}
|
px={6}
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
h={16}
|
h={16}
|
||||||
borderBottomWidth={1}
|
borderBottomWidth={1}
|
||||||
>
|
>
|
||||||
<Img src="/logo.svg" alt="Coder logo" w={28} />
|
<Img src="/logo.svg" alt="Coder logo" w={28} />
|
||||||
|
|
||||||
<Button variant="ghost" ml="auto" onClick={onOpen}>
|
<Button variant="ghost" ml="auto" onClick={onOpen}>
|
||||||
<Icon as={MdMenu} fontSize="2xl" />
|
<Icon as={MdMenu} fontSize="2xl" />
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Drawer onClose={onClose} isOpen={isOpen}>
|
<Drawer onClose={onClose} isOpen={isOpen}>
|
||||||
<DrawerOverlay />
|
<DrawerOverlay />
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<DrawerCloseButton />
|
<DrawerCloseButton />
|
||||||
<DrawerBody p={0}>
|
<DrawerBody p={0}>
|
||||||
<SidebarNav nav={nav} version={version} border={0} />
|
<SidebarNav nav={nav} version={version} border={0} />
|
||||||
</DrawerBody>
|
</DrawerBody>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const slugifyTitle = (titleSource: ReactNode) => {
|
const slugifyTitle = (titleSource: ReactNode) => {
|
||||||
if (Array.isArray(titleSource) && typeof titleSource[0] === "string") {
|
if (Array.isArray(titleSource) && typeof titleSource[0] === "string") {
|
||||||
return _.kebabCase(titleSource[0].toLowerCase());
|
return _.kebabCase(titleSource[0].toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImageUrl = (src: string | undefined) => {
|
const getImageUrl = (src: string | undefined) => {
|
||||||
if (src === undefined) {
|
if (src === undefined) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const assetPath = src.split("images/")[1];
|
const assetPath = src.split("images/")[1];
|
||||||
return `/images/${assetPath}`;
|
return `/images/${assetPath}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DocsPage: NextPage<{
|
const DocsPage: NextPage<{
|
||||||
content: string;
|
content: string;
|
||||||
navigation: Nav;
|
navigation: Nav;
|
||||||
route: Route;
|
route: Route;
|
||||||
version: string;
|
version: string;
|
||||||
}> = ({ content, navigation, route, version }) => {
|
}> = ({ content, navigation, route, version }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{route.title}</title>
|
<title>{route.title}</title>
|
||||||
<meta name="source" content={route.path} />
|
<meta name="source" content={route.path} />
|
||||||
</Head>
|
</Head>
|
||||||
<Box
|
<Box
|
||||||
display={{ md: "grid" }}
|
display={{ md: "grid" }}
|
||||||
gridTemplateColumns="max-content 1fr"
|
gridTemplateColumns="max-content 1fr"
|
||||||
fontSize="md"
|
fontSize="md"
|
||||||
color="gray.700"
|
color="gray.700"
|
||||||
>
|
>
|
||||||
<Box display={{ base: "none", md: "block" }}>
|
<Box display={{ base: "none", md: "block" }}>
|
||||||
<SidebarNav nav={navigation} version={version} />
|
<SidebarNav nav={navigation} version={version} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box display={{ base: "block", md: "none" }}>
|
<Box display={{ base: "block", md: "none" }}>
|
||||||
<MobileNavbar nav={navigation} version={version} />
|
<MobileNavbar nav={navigation} version={version} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
as="main"
|
as="main"
|
||||||
w="full"
|
w="full"
|
||||||
pb={20}
|
pb={20}
|
||||||
px={{ base: 6, md: 10 }}
|
px={{ base: 6, md: 10 }}
|
||||||
pl={{ base: 6, md: 20 }}
|
pl={{ base: 6, md: 20 }}
|
||||||
h="100vh"
|
h="100vh"
|
||||||
overflowY="auto"
|
overflowY="auto"
|
||||||
>
|
>
|
||||||
<Box maxW="872">
|
<Box maxW="872">
|
||||||
<Box lineHeight="tall">
|
<Box lineHeight="tall">
|
||||||
{/* Some docs don't have the title */}
|
{/* Some docs don't have the title */}
|
||||||
<Heading
|
<Heading
|
||||||
as="h1"
|
as="h1"
|
||||||
fontSize="4xl"
|
fontSize="4xl"
|
||||||
pt={10}
|
pt={10}
|
||||||
pb={2}
|
pb={2}
|
||||||
// Hide this title if the doc has the title already
|
// Hide this title if the doc has the title already
|
||||||
sx={{ "& + h1": { display: "none" } }}
|
sx={{ "& + h1": { display: "none" } }}
|
||||||
>
|
>
|
||||||
{route.title}
|
{route.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
rehypePlugins={[rehypeRaw]}
|
rehypePlugins={[rehypeRaw]}
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
urlTransform={transformLinkUriSource(route.path)}
|
urlTransform={transformLinkUriSource(route.path)}
|
||||||
components={{
|
components={{
|
||||||
h1: ({ children }) => (
|
h1: ({ children }) => (
|
||||||
<Heading
|
<Heading
|
||||||
as="h1"
|
as="h1"
|
||||||
fontSize="4xl"
|
fontSize="4xl"
|
||||||
pt={10}
|
pt={10}
|
||||||
pb={2}
|
pb={2}
|
||||||
id={slugifyTitle(children)}
|
id={slugifyTitle(children)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Heading>
|
</Heading>
|
||||||
),
|
),
|
||||||
|
|
||||||
h2: ({ children }) => (
|
h2: ({ children }) => (
|
||||||
<Heading
|
<Heading
|
||||||
as="h2"
|
as="h2"
|
||||||
fontSize="3xl"
|
fontSize="3xl"
|
||||||
pt={10}
|
pt={10}
|
||||||
pb={2}
|
pb={2}
|
||||||
id={slugifyTitle(children)}
|
id={slugifyTitle(children)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Heading>
|
</Heading>
|
||||||
),
|
),
|
||||||
h3: ({ children }) => (
|
h3: ({ children }) => (
|
||||||
<Heading
|
<Heading
|
||||||
as="h3"
|
as="h3"
|
||||||
fontSize="2xl"
|
fontSize="2xl"
|
||||||
pt={10}
|
pt={10}
|
||||||
pb={2}
|
pb={2}
|
||||||
id={slugifyTitle(children)}
|
id={slugifyTitle(children)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Heading>
|
</Heading>
|
||||||
),
|
),
|
||||||
img: ({ src }) => (
|
img: ({ src }) => (
|
||||||
<Img
|
<Img
|
||||||
src={getImageUrl(src)}
|
src={getImageUrl(src)}
|
||||||
mb={2}
|
mb={2}
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
borderColor="gray.200"
|
borderColor="gray.200"
|
||||||
borderStyle="solid"
|
borderStyle="solid"
|
||||||
rounded="md"
|
rounded="md"
|
||||||
height="auto"
|
height="auto"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
p: ({ children }) => (
|
p: ({ children }) => (
|
||||||
<Text pt={2} pb={2}>
|
<Text pt={2} pb={2}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
ul: ({ children }) => (
|
ul: ({ children }) => (
|
||||||
<UnorderedList
|
<UnorderedList
|
||||||
mb={4}
|
mb={4}
|
||||||
display="grid"
|
display="grid"
|
||||||
gridAutoFlow="row"
|
gridAutoFlow="row"
|
||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</UnorderedList>
|
</UnorderedList>
|
||||||
),
|
),
|
||||||
ol: ({ children }) => (
|
ol: ({ children }) => (
|
||||||
<OrderedList
|
<OrderedList
|
||||||
mb={4}
|
mb={4}
|
||||||
display="grid"
|
display="grid"
|
||||||
gridAutoFlow="row"
|
gridAutoFlow="row"
|
||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</OrderedList>
|
</OrderedList>
|
||||||
),
|
),
|
||||||
a: ({ children, href = "" }) => {
|
a: ({ children, href = "" }) => {
|
||||||
const isExternal =
|
const isExternal =
|
||||||
href.startsWith("http") || href.startsWith("https");
|
href.startsWith("http") || href.startsWith("https");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
target={isExternal ? "_blank" : undefined}
|
target={isExternal ? "_blank" : undefined}
|
||||||
fontWeight={500}
|
fontWeight={500}
|
||||||
color="blue.600"
|
color="blue.600"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
code: ({ node, ...props }) => (
|
code: ({ node, ...props }) => (
|
||||||
<Code {...props} bgColor="gray.100" />
|
<Code {...props} bgColor="gray.100" />
|
||||||
),
|
),
|
||||||
pre: ({ children }) => (
|
pre: ({ children }) => (
|
||||||
<Box
|
<Box
|
||||||
as="pre"
|
as="pre"
|
||||||
w="full"
|
w="full"
|
||||||
sx={{ "& > code": { w: "full", p: 4, rounded: "md" } }}
|
sx={{ "& > code": { w: "full", p: 4, rounded: "md" } }}
|
||||||
mb={2}
|
mb={2}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
),
|
),
|
||||||
table: ({ children }) => (
|
table: ({ children }) => (
|
||||||
<TableContainer
|
<TableContainer
|
||||||
mt={1}
|
mt={1}
|
||||||
mb={2}
|
mb={2}
|
||||||
bgColor="white"
|
bgColor="white"
|
||||||
rounded="md"
|
rounded="md"
|
||||||
borderWidth={1}
|
borderWidth={1}
|
||||||
borderColor="gray.100"
|
borderColor="gray.100"
|
||||||
borderStyle="solid"
|
borderStyle="solid"
|
||||||
>
|
>
|
||||||
<Table variant="simple">{children}</Table>
|
<Table variant="simple">{children}</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
),
|
),
|
||||||
thead: ({ children }) => <Thead>{children}</Thead>,
|
thead: ({ children }) => <Thead>{children}</Thead>,
|
||||||
th: ({ children }) => <Th>{children}</Th>,
|
th: ({ children }) => <Th>{children}</Th>,
|
||||||
td: ({ children }) => <Td>{children}</Td>,
|
td: ({ children }) => <Td>{children}</Td>,
|
||||||
tr: ({ children }) => <Tr>{children}</Tr>,
|
tr: ({ children }) => <Tr>{children}</Tr>,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DocsPage;
|
export default DocsPage;
|
||||||
|
|||||||
+18
-18
@@ -3,27 +3,27 @@ import type { AppProps } from "next/app";
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
|
||||||
const theme = extendTheme({
|
const theme = extendTheme({
|
||||||
styles: {
|
styles: {
|
||||||
global: {
|
global: {
|
||||||
body: {
|
body: {
|
||||||
bg: "gray.50",
|
bg: "gray.50",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
|
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<link rel="mask-icon" href="/favicon.svg" color="#000000" />
|
<link rel="mask-icon" href="/favicon.svg" color="#000000" />
|
||||||
<link rel="alternate icon" type="image/png" href="/favicon.png" />
|
<link rel="alternate icon" type="image/png" href="/favicon.png" />
|
||||||
</Head>
|
</Head>
|
||||||
<ChakraProvider theme={theme}>
|
<ChakraProvider theme={theme}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</ChakraProvider>
|
</ChakraProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MyApp;
|
export default MyApp;
|
||||||
|
|||||||
+18
-18
@@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules", "docs"]
|
"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.",
|
"_comment": "This version doesn't matter, it's just to allow importing from other repos.",
|
||||||
"name": "coder",
|
"name": "coder",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247",
|
"packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --cache --write '**/*.{css,html,json,md,yaml,yml}'",
|
"format": "prettier --cache --write '**/*.{css,html,json,md,yaml,yml}'",
|
||||||
"format:check": "prettier --cache --check '**/*.{css,html,json,md,yaml,yml}'",
|
"format:check": "prettier --cache --check '**/*.{css,html,json,md,yaml,yml}'",
|
||||||
"storybook": "pnpm run -C site/ storybook"
|
"storybook": "pnpm run -C site/ storybook"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.3.3"
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5057
-5057
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"widdershins": "^4.0.1"
|
"widdershins": "^4.0.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"semver": "7.5.3",
|
"semver": "7.5.3",
|
||||||
"jsonpointer": "5.0.1"
|
"jsonpointer": "5.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ var (
|
|||||||
// CLI option types:
|
// CLI option types:
|
||||||
"github.com/coder/serpent",
|
"github.com/coder/serpent",
|
||||||
}
|
}
|
||||||
indent = " "
|
indent = "\t"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
+4
-4
@@ -1,17 +1,17 @@
|
|||||||
// From codersdk/genericmap.go
|
// From codersdk/genericmap.go
|
||||||
export interface Buzz {
|
export interface Buzz {
|
||||||
readonly foo: Foo
|
readonly foo: Foo
|
||||||
readonly bazz: string
|
readonly bazz: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/genericmap.go
|
// From codersdk/genericmap.go
|
||||||
export interface Foo {
|
export interface Foo {
|
||||||
readonly bar: string
|
readonly bar: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/genericmap.go
|
// From codersdk/genericmap.go
|
||||||
export interface FooBuzz<R extends Custom> {
|
export interface FooBuzz<R extends Custom> {
|
||||||
readonly something: (readonly R[])
|
readonly something: (readonly R[])
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/genericmap.go
|
// From codersdk/genericmap.go
|
||||||
|
|||||||
+14
-14
@@ -1,35 +1,35 @@
|
|||||||
// From codersdk/generics.go
|
// From codersdk/generics.go
|
||||||
export interface Complex<C extends comparable, S extends Single, T extends Custom> {
|
export interface Complex<C extends comparable, S extends Single, T extends Custom> {
|
||||||
readonly dynamic: Fields<C, boolean, string, S>
|
readonly dynamic: Fields<C, boolean, string, S>
|
||||||
readonly order: FieldsDiffOrder<C, string, S, T>
|
readonly order: FieldsDiffOrder<C, string, S, T>
|
||||||
readonly comparable: C
|
readonly comparable: C
|
||||||
readonly single: S
|
readonly single: S
|
||||||
readonly static: Static
|
readonly static: Static
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/generics.go
|
// From codersdk/generics.go
|
||||||
export interface Dynamic<A extends any, S extends Single> {
|
export interface Dynamic<A extends any, S extends Single> {
|
||||||
readonly dynamic: Fields<boolean, A, string, S>
|
readonly dynamic: Fields<boolean, A, string, S>
|
||||||
readonly comparable: boolean
|
readonly comparable: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/generics.go
|
// From codersdk/generics.go
|
||||||
export interface Fields<C extends comparable, A extends any, T extends Custom, S extends Single> {
|
export interface Fields<C extends comparable, A extends any, T extends Custom, S extends Single> {
|
||||||
readonly comparable: C
|
readonly comparable: C
|
||||||
readonly any: A
|
readonly any: A
|
||||||
readonly custom: T
|
readonly custom: T
|
||||||
readonly again: T
|
readonly again: T
|
||||||
readonly single_constraint: S
|
readonly single_constraint: S
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/generics.go
|
// From codersdk/generics.go
|
||||||
export interface FieldsDiffOrder<A extends any, C extends comparable, S extends Single, T extends Custom> {
|
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
|
// From codersdk/generics.go
|
||||||
export interface Static {
|
export interface Static {
|
||||||
readonly static: Fields<string, number, number, string>
|
readonly static: Fields<string, number, number, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/generics.go
|
// From codersdk/generics.go
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// From codersdk/genericslice.go
|
// From codersdk/genericslice.go
|
||||||
export interface Bar {
|
export interface Bar {
|
||||||
readonly Bar: string
|
readonly Bar: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/genericslice.go
|
// From codersdk/genericslice.go
|
||||||
export interface Foo<R extends any> {
|
export interface Foo<R extends any> {
|
||||||
readonly Slice: (readonly R[])
|
readonly Slice: (readonly R[])
|
||||||
readonly TwoD: (readonly (readonly R[])[])
|
readonly TwoD: (readonly (readonly R[])[])
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ func run(lint bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", "\t")
|
||||||
return enc.Encode(examples)
|
return enc.Encode(examples)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+39
-43
@@ -1,45 +1,41 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"ignore": ["**/*Generated.ts"]
|
"ignore": ["**/*Generated.ts"]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"linter": {
|
||||||
"indentStyle": "space",
|
"rules": {
|
||||||
"indentWidth": 2
|
"a11y": {
|
||||||
},
|
"noSvgWithoutTitle": { "level": "off" },
|
||||||
"linter": {
|
"useButtonType": { "level": "off" }
|
||||||
"rules": {
|
},
|
||||||
"a11y": {
|
"style": {
|
||||||
"noSvgWithoutTitle": { "level": "off" },
|
"noNonNullAssertion": { "level": "off" },
|
||||||
"useButtonType": { "level": "off" }
|
"noParameterAssign": { "level": "off" },
|
||||||
},
|
"useDefaultParameterLast": { "level": "off" },
|
||||||
"style": {
|
"useSelfClosingElements": { "level": "off" }
|
||||||
"noNonNullAssertion": { "level": "off" },
|
},
|
||||||
"noParameterAssign": { "level": "off" },
|
"suspicious": {
|
||||||
"useDefaultParameterLast": { "level": "off" },
|
"noArrayIndexKey": { "level": "off" },
|
||||||
"useSelfClosingElements": { "level": "off" }
|
"noThenProperty": { "level": "off" }
|
||||||
},
|
},
|
||||||
"suspicious": {
|
"nursery": {
|
||||||
"noArrayIndexKey": { "level": "off" },
|
"noRestrictedImports": {
|
||||||
"noThenProperty": { "level": "off" }
|
"level": "error",
|
||||||
},
|
"options": {
|
||||||
"nursery": {
|
"paths": {
|
||||||
"noRestrictedImports": {
|
"@mui/material": "Use @mui/material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
||||||
"level": "error",
|
"@mui/icons-material": "Use @mui/icons-material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
||||||
"options": {
|
"@mui/material/Avatar": "Use components/Avatar/Avatar instead.",
|
||||||
"paths": {
|
"@mui/material/Alert": "Use components/Alert/Alert instead.",
|
||||||
"@mui/material": "Use @mui/material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
"@mui/material/Popover": "Use components/Popover/Popover instead.",
|
||||||
"@mui/icons-material": "Use @mui/icons-material/<name> instead. See: https://material-ui.com/guides/minimizing-bundle-size/.",
|
"@mui/material/Typography": "Use native HTML elements instead. Eg: <span>, <p>, <h1>, etc.",
|
||||||
"@mui/material/Avatar": "Use components/Avatar/Avatar instead.",
|
"@mui/material/Box": "Use a <div> instead.",
|
||||||
"@mui/material/Alert": "Use components/Alert/Alert instead.",
|
"@mui/material/styles": "Import from @emotion/react instead.",
|
||||||
"@mui/material/Popover": "Use components/Popover/Popover instead.",
|
"lodash": "Use lodash/<name> 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;
|
let currentOrgId: string;
|
||||||
|
|
||||||
export const setupApiCalls = async (page: Page) => {
|
export const setupApiCalls = async (page: Page) => {
|
||||||
try {
|
try {
|
||||||
const token = await findSessionToken(page);
|
const token = await findSessionToken(page);
|
||||||
API.setSessionToken(token);
|
API.setSessionToken(token);
|
||||||
} catch {
|
} catch {
|
||||||
// If this fails, we have an unauthenticated client.
|
// 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> => {
|
export const getCurrentOrgId = async (): Promise<string> => {
|
||||||
if (currentOrgId) {
|
if (currentOrgId) {
|
||||||
return currentOrgId;
|
return currentOrgId;
|
||||||
}
|
}
|
||||||
const currentUser = await API.getAuthenticatedUser();
|
const currentUser = await API.getAuthenticatedUser();
|
||||||
currentOrgId = currentUser.organization_ids[0];
|
currentOrgId = currentUser.organization_ids[0];
|
||||||
return currentOrgId;
|
return currentOrgId;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createUser = async (orgId: string) => {
|
export const createUser = async (orgId: string) => {
|
||||||
const name = randomName();
|
const name = randomName();
|
||||||
const user = await API.createUser({
|
const user = await API.createUser({
|
||||||
email: `${name}@coder.com`,
|
email: `${name}@coder.com`,
|
||||||
username: name,
|
username: name,
|
||||||
name: name,
|
name: name,
|
||||||
password: "s3cure&password!",
|
password: "s3cure&password!",
|
||||||
login_type: "password",
|
login_type: "password",
|
||||||
disable_login: false,
|
disable_login: false,
|
||||||
organization_id: orgId,
|
organization_id: orgId,
|
||||||
});
|
});
|
||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createGroup = async (orgId: string) => {
|
export const createGroup = async (orgId: string) => {
|
||||||
const name = randomName();
|
const name = randomName();
|
||||||
const group = await API.createGroup(orgId, {
|
const group = await API.createGroup(orgId, {
|
||||||
name,
|
name,
|
||||||
display_name: `Display ${name}`,
|
display_name: `Display ${name}`,
|
||||||
avatar_url: "/emojis/1f60d.png",
|
avatar_url: "/emojis/1f60d.png",
|
||||||
quota_allowance: 0,
|
quota_allowance: 0,
|
||||||
});
|
});
|
||||||
return group;
|
return group;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createOrganization = async () => {
|
export const createOrganization = async () => {
|
||||||
const name = randomName();
|
const name = randomName();
|
||||||
const org = await API.createOrganization({
|
const org = await API.createOrganization({
|
||||||
name,
|
name,
|
||||||
display_name: `Org ${name}`,
|
display_name: `Org ${name}`,
|
||||||
description: `Org description ${name}`,
|
description: `Org description ${name}`,
|
||||||
icon: "/emojis/1f957.png",
|
icon: "/emojis/1f957.png",
|
||||||
});
|
});
|
||||||
return org;
|
return org;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function verifyConfigFlagBoolean(
|
export async function verifyConfigFlagBoolean(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
) {
|
) {
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
const type = opt.value ? "option-enabled" : "option-disabled";
|
const type = opt.value ? "option-enabled" : "option-disabled";
|
||||||
const value = opt.value ? "Enabled" : "Disabled";
|
const value = opt.value ? "Enabled" : "Disabled";
|
||||||
|
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .${type}`,
|
`div.options-table .option-${flag} .${type}`,
|
||||||
);
|
);
|
||||||
await expect(configOption).toHaveText(value);
|
await expect(configOption).toHaveText(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyConfigFlagNumber(
|
export async function verifyConfigFlagNumber(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
) {
|
) {
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-value-number`,
|
`div.options-table .option-${flag} .option-value-number`,
|
||||||
);
|
);
|
||||||
await expect(configOption).toHaveText(String(opt.value));
|
await expect(configOption).toHaveText(String(opt.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyConfigFlagString(
|
export async function verifyConfigFlagString(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
) {
|
) {
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
|
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-value-string`,
|
`div.options-table .option-${flag} .option-value-string`,
|
||||||
);
|
);
|
||||||
await expect(configOption).toHaveText(opt.value);
|
await expect(configOption).toHaveText(opt.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyConfigFlagEmpty(page: Page, flag: string) {
|
export async function verifyConfigFlagEmpty(page: Page, flag: string) {
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-value-empty`,
|
`div.options-table .option-${flag} .option-value-empty`,
|
||||||
);
|
);
|
||||||
await expect(configOption).toHaveText("Not set");
|
await expect(configOption).toHaveText("Not set");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyConfigFlagArray(
|
export async function verifyConfigFlagArray(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
) {
|
) {
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-array`,
|
`div.options-table .option-${flag} .option-array`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify array of options with simple dots
|
// Verify array of options with simple dots
|
||||||
for (const item of opt.value) {
|
for (const item of opt.value) {
|
||||||
await expect(configOption.locator("li", { hasText: item })).toBeVisible();
|
await expect(configOption.locator("li", { hasText: item })).toBeVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyConfigFlagEntries(
|
export async function verifyConfigFlagEntries(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
) {
|
) {
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-array`,
|
`div.options-table .option-${flag} .option-array`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify array of options with green marks.
|
// Verify array of options with green marks.
|
||||||
Object.entries(opt.value)
|
Object.entries(opt.value)
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
.map(async ([item]) => {
|
.map(async ([item]) => {
|
||||||
await expect(
|
await expect(
|
||||||
configOption.locator(`.option-array-item-${item}.option-enabled`, {
|
configOption.locator(`.option-array-item-${item}.option-enabled`, {
|
||||||
hasText: item,
|
hasText: item,
|
||||||
}),
|
}),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function verifyConfigFlagDuration(
|
export async function verifyConfigFlagDuration(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
) {
|
) {
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-value-string`,
|
`div.options-table .option-${flag} .option-value-string`,
|
||||||
);
|
);
|
||||||
await expect(configOption).toHaveText(
|
await expect(configOption).toHaveText(
|
||||||
formatDuration(
|
formatDuration(
|
||||||
// intervalToDuration takes ms, so convert nanoseconds to ms
|
// intervalToDuration takes ms, so convert nanoseconds to ms
|
||||||
intervalToDuration({
|
intervalToDuration({
|
||||||
start: 0,
|
start: 0,
|
||||||
end: (opt.value as number) / 1e6,
|
end: (opt.value as number) / 1e6,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findConfigOption(
|
export function findConfigOption(
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
flag: string,
|
flag: string,
|
||||||
): SerpentOption {
|
): SerpentOption {
|
||||||
const opt = config.options.find((option) => option.flag === flag);
|
const opt = config.options.find((option) => option.flag === flag);
|
||||||
if (opt === undefined) {
|
if (opt === undefined) {
|
||||||
// must be undefined as `false` is expected
|
// must be undefined as `false` is expected
|
||||||
throw new Error(`Option with env ${flag} has undefined value.`);
|
throw new Error(`Option with env ${flag} has undefined value.`);
|
||||||
}
|
}
|
||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -4,8 +4,8 @@ export const coderMain = path.join(__dirname, "../../enterprise/cmd/coder");
|
|||||||
|
|
||||||
// Default port from the server
|
// Default port from the server
|
||||||
export const coderPort = process.env.CODER_E2E_PORT
|
export const coderPort = process.env.CODER_E2E_PORT
|
||||||
? Number(process.env.CODER_E2E_PORT)
|
? Number(process.env.CODER_E2E_PORT)
|
||||||
: 3111;
|
: 3111;
|
||||||
export const prometheusPort = 2114;
|
export const prometheusPort = 2114;
|
||||||
export const workspaceProxyPort = 3112;
|
export const workspaceProxyPort = 3112;
|
||||||
|
|
||||||
@@ -19,23 +19,23 @@ export const password = "SomeSecurePassword!";
|
|||||||
export const email = "admin@coder.com";
|
export const email = "admin@coder.com";
|
||||||
|
|
||||||
export const gitAuth = {
|
export const gitAuth = {
|
||||||
deviceProvider: "device",
|
deviceProvider: "device",
|
||||||
webProvider: "web",
|
webProvider: "web",
|
||||||
// These ports need to be hardcoded so that they can be
|
// These ports need to be hardcoded so that they can be
|
||||||
// used in `playwright.config.ts` to set the environment
|
// used in `playwright.config.ts` to set the environment
|
||||||
// variables for the server.
|
// variables for the server.
|
||||||
devicePort: 50515,
|
devicePort: 50515,
|
||||||
webPort: 50516,
|
webPort: 50516,
|
||||||
|
|
||||||
authPath: "/auth",
|
authPath: "/auth",
|
||||||
tokenPath: "/token",
|
tokenPath: "/token",
|
||||||
codePath: "/code",
|
codePath: "/code",
|
||||||
validatePath: "/validate",
|
validatePath: "/validate",
|
||||||
installationsPath: "/installations",
|
installationsPath: "/installations",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const requireEnterpriseTests = Boolean(
|
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 ?? "";
|
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[] };
|
type PollingOptions = { timeout?: number; intervals?: number[] };
|
||||||
|
|
||||||
export const expectUrl = expect.extend({
|
export const expectUrl = expect.extend({
|
||||||
/**
|
/**
|
||||||
* toHavePathName is an alternative to `toHaveURL` that won't fail if the URL contains query parameters.
|
* toHavePathName is an alternative to `toHaveURL` that won't fail if the URL contains query parameters.
|
||||||
*/
|
*/
|
||||||
async toHavePathName(page: Page, expected: string, options?: PollingOptions) {
|
async toHavePathName(page: Page, expected: string, options?: PollingOptions) {
|
||||||
let actual: string = new URL(page.url()).pathname;
|
let actual: string = new URL(page.url()).pathname;
|
||||||
let pass: boolean;
|
let pass: boolean;
|
||||||
try {
|
try {
|
||||||
await expect
|
await expect
|
||||||
.poll(() => {
|
.poll(() => {
|
||||||
actual = new URL(page.url()).pathname;
|
actual = new URL(page.url()).pathname;
|
||||||
return actual;
|
return actual;
|
||||||
}, options)
|
}, options)
|
||||||
.toBe(expected);
|
.toBe(expected);
|
||||||
pass = true;
|
pass = true;
|
||||||
} catch {
|
} catch {
|
||||||
pass = false;
|
pass = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "toHavePathName",
|
name: "toHavePathName",
|
||||||
pass,
|
pass,
|
||||||
actual,
|
actual,
|
||||||
expected,
|
expected,
|
||||||
message: () =>
|
message: () =>
|
||||||
`The page does not have the expected URL pathname.\nExpected: ${
|
`The page does not have the expected URL pathname.\nExpected: ${
|
||||||
this.isNot ? "not" : ""
|
this.isNot ? "not" : ""
|
||||||
}${this.utils.printExpected(
|
}${this.utils.printExpected(
|
||||||
expected,
|
expected,
|
||||||
)}\nActual: ${this.utils.printReceived(actual)}`,
|
)}\nActual: ${this.utils.printReceived(actual)}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+30
-30
@@ -7,41 +7,41 @@ import { expectUrl } from "./expectUrl";
|
|||||||
import { storageState } from "./playwright.config";
|
import { storageState } from "./playwright.config";
|
||||||
|
|
||||||
test("setup deployment", async ({ page }) => {
|
test("setup deployment", async ({ page }) => {
|
||||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const exists = await API.hasFirstUser();
|
const exists = await API.hasFirstUser();
|
||||||
// First user already exists, abort early. All tests execute this as a dependency,
|
// 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 you run multiple tests in the UI, this will fail unless we check this.
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup first user
|
// Setup first user
|
||||||
await page.getByLabel(Language.usernameLabel).fill(constants.username);
|
await page.getByLabel(Language.usernameLabel).fill(constants.username);
|
||||||
await page.getByLabel(Language.emailLabel).fill(constants.email);
|
await page.getByLabel(Language.emailLabel).fill(constants.email);
|
||||||
await page.getByLabel(Language.passwordLabel).fill(constants.password);
|
await page.getByLabel(Language.passwordLabel).fill(constants.password);
|
||||||
await page.getByTestId("create").click();
|
await page.getByTestId("create").click();
|
||||||
|
|
||||||
await expectUrl(page).toHavePathName("/workspaces");
|
await expectUrl(page).toHavePathName("/workspaces");
|
||||||
await page.context().storageState({ path: storageState });
|
await page.context().storageState({ path: storageState });
|
||||||
|
|
||||||
await page.getByTestId("button-select-template").isVisible();
|
await page.getByTestId("button-select-template").isVisible();
|
||||||
|
|
||||||
// Setup license
|
// Setup license
|
||||||
if (constants.requireEnterpriseTests || constants.enterpriseLicense) {
|
if (constants.requireEnterpriseTests || constants.enterpriseLicense) {
|
||||||
// Make sure that we have something that looks like a real license
|
// Make sure that we have something that looks like a real license
|
||||||
expect(constants.enterpriseLicense).toBeTruthy();
|
expect(constants.enterpriseLicense).toBeTruthy();
|
||||||
expect(constants.enterpriseLicense.length).toBeGreaterThan(92); // the signature alone should be this long
|
expect(constants.enterpriseLicense.length).toBeGreaterThan(92); // the signature alone should be this long
|
||||||
expect(constants.enterpriseLicense.split(".").length).toBe(3); // otherwise it's invalid
|
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.getByText("Add a license").click();
|
||||||
await page.getByRole("textbox").fill(constants.enterpriseLicense);
|
await page.getByRole("textbox").fill(constants.enterpriseLicense);
|
||||||
await page.getByText("Upload License").click();
|
await page.getByText("Upload License").click();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText("You have successfully added a license"),
|
page.getByText("You have successfully added a license"),
|
||||||
).toBeVisible();
|
).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";
|
import { coderPort, gitAuth } from "./constants";
|
||||||
|
|
||||||
export const beforeCoderTest = async (page: Page) => {
|
export const beforeCoderTest = async (page: Page) => {
|
||||||
// eslint-disable-next-line no-console -- Show everything that was printed with console.log()
|
// 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("console", (msg) => console.log(`[onConsole] ${msg.text()}`));
|
||||||
|
|
||||||
page.on("request", (request) => {
|
page.on("request", (request) => {
|
||||||
if (!isApiCall(request.url())) {
|
if (!isApiCall(request.url())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
||||||
console.log(
|
console.log(
|
||||||
`[onRequest] method=${request.method()} url=${request.url()} postData=${
|
`[onRequest] method=${request.method()} url=${request.url()} postData=${
|
||||||
request.postData() ? request.postData() : ""
|
request.postData() ? request.postData() : ""
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
page.on("response", async (response) => {
|
page.on("response", async (response) => {
|
||||||
if (!isApiCall(response.url())) {
|
if (!isApiCall(response.url())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldLogResponse =
|
const shouldLogResponse =
|
||||||
!response.url().endsWith("/api/v2/deployment/config") &&
|
!response.url().endsWith("/api/v2/deployment/config") &&
|
||||||
!response.url().endsWith("/api/v2/debug/health?force=false");
|
!response.url().endsWith("/api/v2/debug/health?force=false");
|
||||||
|
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
try {
|
try {
|
||||||
if (shouldLogResponse) {
|
if (shouldLogResponse) {
|
||||||
const buffer = await response.body();
|
const buffer = await response.body();
|
||||||
responseText = buffer.toString("utf-8");
|
responseText = buffer.toString("utf-8");
|
||||||
responseText = responseText.replace(/\n$/g, "");
|
responseText = responseText.replace(/\n$/g, "");
|
||||||
} else {
|
} else {
|
||||||
responseText = "skipped...";
|
responseText = "skipped...";
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
responseText = "not_available";
|
responseText = "not_available";
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
// eslint-disable-next-line no-console -- Log HTTP requests for debugging purposes
|
||||||
console.log(
|
console.log(
|
||||||
`[onResponse] url=${response.url()} status=${response.status()} body=${responseText}`,
|
`[onResponse] url=${response.url()} status=${response.status()} body=${responseText}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resetExternalAuthKey = async (context: BrowserContext) => {
|
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.
|
// 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 cookies = await context.cookies();
|
||||||
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
const sessionCookie = cookies.find((c) => c.name === "coder_session_token");
|
||||||
const options = {
|
const options = {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
hostname: "127.0.0.1",
|
hostname: "127.0.0.1",
|
||||||
port: coderPort,
|
port: coderPort,
|
||||||
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
path: `/api/v2/external-auth/${gitAuth.webProvider}?coder_session_token=${sessionCookie?.value}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const req = http.request(options, (res) => {
|
const req = http.request(options, (res) => {
|
||||||
let data = "";
|
let data = "";
|
||||||
res.on("data", (chunk) => {
|
res.on("data", (chunk) => {
|
||||||
data += chunk;
|
data += chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on("end", () => {
|
res.on("end", () => {
|
||||||
// Both 200 (key deleted successfully) and 500 (key was not found) are valid responses.
|
// Both 200 (key deleted successfully) and 500 (key was not found) are valid responses.
|
||||||
if (res.statusCode !== 200 && res.statusCode !== 500) {
|
if (res.statusCode !== 200 && res.statusCode !== 500) {
|
||||||
console.error("failed to delete external auth link", data);
|
console.error("failed to delete external auth link", data);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
`failed to delete external auth link: HTTP response ${res.statusCode}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on("error", (err) => {
|
req.on("error", (err) => {
|
||||||
throw err.message;
|
throw err.message;
|
||||||
});
|
});
|
||||||
|
|
||||||
req.end();
|
req.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isApiCall = (urlString: string): boolean => {
|
const isApiCall = (urlString: string): boolean => {
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
const apiPath = "/api/v2";
|
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
|
// Rich parameters
|
||||||
|
|
||||||
export const emptyParameter: RichParameter = {
|
export const emptyParameter: RichParameter = {
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
type: "",
|
type: "",
|
||||||
mutable: false,
|
mutable: false,
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
icon: "",
|
icon: "",
|
||||||
options: [],
|
options: [],
|
||||||
validationRegex: "",
|
validationRegex: "",
|
||||||
validationError: "",
|
validationError: "",
|
||||||
validationMin: undefined,
|
validationMin: undefined,
|
||||||
validationMax: undefined,
|
validationMax: undefined,
|
||||||
validationMonotonic: "",
|
validationMonotonic: "",
|
||||||
required: false,
|
required: false,
|
||||||
displayName: "",
|
displayName: "",
|
||||||
order: 0,
|
order: 0,
|
||||||
ephemeral: false,
|
ephemeral: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// firstParameter is mutable string with a default value (parameter value not required).
|
// firstParameter is mutable string with a default value (parameter value not required).
|
||||||
export const firstParameter: RichParameter = {
|
export const firstParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "first_parameter",
|
name: "first_parameter",
|
||||||
displayName: "First parameter",
|
displayName: "First parameter",
|
||||||
type: "number",
|
type: "number",
|
||||||
description: "This is first parameter.",
|
description: "This is first parameter.",
|
||||||
icon: "/emojis/1f310.png",
|
icon: "/emojis/1f310.png",
|
||||||
defaultValue: "123",
|
defaultValue: "123",
|
||||||
mutable: true,
|
mutable: true,
|
||||||
order: 1,
|
order: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// secondParameter is immutable string with a default value (parameter value not required).
|
// secondParameter is immutable string with a default value (parameter value not required).
|
||||||
export const secondParameter: RichParameter = {
|
export const secondParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "second_parameter",
|
name: "second_parameter",
|
||||||
displayName: "Second parameter",
|
displayName: "Second parameter",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "This is second parameter.",
|
description: "This is second parameter.",
|
||||||
defaultValue: "abc",
|
defaultValue: "abc",
|
||||||
order: 2,
|
order: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// thirdParameter is mutable string with an empty default value (parameter value not required).
|
// thirdParameter is mutable string with an empty default value (parameter value not required).
|
||||||
export const thirdParameter: RichParameter = {
|
export const thirdParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "third_parameter",
|
name: "third_parameter",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "This is third parameter.",
|
description: "This is third parameter.",
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
mutable: true,
|
mutable: true,
|
||||||
order: 3,
|
order: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
// fourthParameter is immutable boolean with a default "true" value (parameter value not required).
|
// fourthParameter is immutable boolean with a default "true" value (parameter value not required).
|
||||||
export const fourthParameter: RichParameter = {
|
export const fourthParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "fourth_parameter",
|
name: "fourth_parameter",
|
||||||
type: "bool",
|
type: "bool",
|
||||||
description: "This is fourth parameter.",
|
description: "This is fourth parameter.",
|
||||||
defaultValue: "true",
|
defaultValue: "true",
|
||||||
order: 3,
|
order: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
// fifthParameter is immutable "string with options", with a default option selected (parameter value not required).
|
// fifthParameter is immutable "string with options", with a default option selected (parameter value not required).
|
||||||
export const fifthParameter: RichParameter = {
|
export const fifthParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "fifth_parameter",
|
name: "fifth_parameter",
|
||||||
displayName: "Fifth parameter",
|
displayName: "Fifth parameter",
|
||||||
type: "string",
|
type: "string",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "ABC",
|
name: "ABC",
|
||||||
description: "This is ABC",
|
description: "This is ABC",
|
||||||
value: "abc",
|
value: "abc",
|
||||||
icon: "",
|
icon: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "DEF",
|
name: "DEF",
|
||||||
description: "This is DEF",
|
description: "This is DEF",
|
||||||
value: "def",
|
value: "def",
|
||||||
icon: "",
|
icon: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GHI",
|
name: "GHI",
|
||||||
description: "This is GHI",
|
description: "This is GHI",
|
||||||
value: "ghi",
|
value: "ghi",
|
||||||
icon: "",
|
icon: "",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
description: "This is fifth parameter.",
|
description: "This is fifth parameter.",
|
||||||
defaultValue: "def",
|
defaultValue: "def",
|
||||||
order: 3,
|
order: 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
// sixthParameter is mutable string without a default value (parameter value is required).
|
// sixthParameter is mutable string without a default value (parameter value is required).
|
||||||
export const sixthParameter: RichParameter = {
|
export const sixthParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "sixth_parameter",
|
name: "sixth_parameter",
|
||||||
displayName: "Sixth parameter",
|
displayName: "Sixth parameter",
|
||||||
type: "number",
|
type: "number",
|
||||||
description: "This is sixth parameter.",
|
description: "This is sixth parameter.",
|
||||||
icon: "/emojis/1f310.png",
|
icon: "/emojis/1f310.png",
|
||||||
required: true,
|
required: true,
|
||||||
mutable: true,
|
mutable: true,
|
||||||
order: 1,
|
order: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// seventhParameter is immutable string without a default value (parameter value is required).
|
// seventhParameter is immutable string without a default value (parameter value is required).
|
||||||
export const seventhParameter: RichParameter = {
|
export const seventhParameter: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "seventh_parameter",
|
name: "seventh_parameter",
|
||||||
displayName: "Seventh parameter",
|
displayName: "Seventh parameter",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "This is seventh parameter.",
|
description: "This is seventh parameter.",
|
||||||
required: true,
|
required: true,
|
||||||
order: 1,
|
order: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
// randParamName returns a new parameter with a random name.
|
// randParamName returns a new parameter with a random name.
|
||||||
// It helps to avoid cross-test interference when user-auto-fill triggers on
|
// It helps to avoid cross-test interference when user-auto-fill triggers on
|
||||||
// the same parameter name.
|
// the same parameter name.
|
||||||
export const randParamName = (p: RichParameter): RichParameter => {
|
export const randParamName = (p: RichParameter): RichParameter => {
|
||||||
const name = `${p.name}_${Math.random().toString(36).substring(7)}`;
|
const name = `${p.name}_${Math.random().toString(36).substring(7)}`;
|
||||||
return { ...p, name: name };
|
return { ...p, name: name };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build options
|
// Build options
|
||||||
|
|
||||||
export const firstBuildOption: RichParameter = {
|
export const firstBuildOption: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "first_build_option",
|
name: "first_build_option",
|
||||||
displayName: "First build option",
|
displayName: "First build option",
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "This is first build option.",
|
description: "This is first build option.",
|
||||||
icon: "/emojis/1f310.png",
|
icon: "/emojis/1f310.png",
|
||||||
defaultValue: "ABCDEF",
|
defaultValue: "ABCDEF",
|
||||||
mutable: true,
|
mutable: true,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const secondBuildOption: RichParameter = {
|
export const secondBuildOption: RichParameter = {
|
||||||
...emptyParameter,
|
...emptyParameter,
|
||||||
|
|
||||||
name: "second_build_option",
|
name: "second_build_option",
|
||||||
displayName: "Second build option",
|
displayName: "Second build option",
|
||||||
type: "bool",
|
type: "bool",
|
||||||
description: "This is second build option.",
|
description: "This is second build option.",
|
||||||
defaultValue: "false",
|
defaultValue: "false",
|
||||||
mutable: true,
|
mutable: true,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
};
|
};
|
||||||
|
|||||||
+127
-127
@@ -2,13 +2,13 @@ import { execSync } from "node:child_process";
|
|||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import { defineConfig } from "@playwright/test";
|
import { defineConfig } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
coderMain,
|
coderMain,
|
||||||
coderPort,
|
coderPort,
|
||||||
coderdPProfPort,
|
coderdPProfPort,
|
||||||
e2eFakeExperiment1,
|
e2eFakeExperiment1,
|
||||||
e2eFakeExperiment2,
|
e2eFakeExperiment2,
|
||||||
gitAuth,
|
gitAuth,
|
||||||
requireTerraformTests,
|
requireTerraformTests,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
|
||||||
export const wsEndpoint = process.env.CODER_E2E_WS_ENDPOINT;
|
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 hasTerraform = false;
|
||||||
let hasDocker = false;
|
let hasDocker = false;
|
||||||
try {
|
try {
|
||||||
execSync("terraform --version");
|
execSync("terraform --version");
|
||||||
hasTerraform = true;
|
hasTerraform = true;
|
||||||
} catch {
|
} catch {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execSync("docker --version");
|
execSync("docker --version");
|
||||||
hasDocker = true;
|
hasDocker = true;
|
||||||
} catch {
|
} catch {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTerraform || !hasDocker) {
|
if (!hasTerraform || !hasDocker) {
|
||||||
const msg = `Terraform provisioners require docker & terraform binaries to function. \n${
|
const msg = `Terraform provisioners require docker & terraform binaries to function. \n${
|
||||||
hasTerraform
|
hasTerraform
|
||||||
? ""
|
? ""
|
||||||
: "\tThe `terraform` executable is not present in the runtime environment.\n"
|
: "\tThe `terraform` executable is not present in the runtime environment.\n"
|
||||||
}${
|
}${
|
||||||
hasDocker
|
hasDocker
|
||||||
? ""
|
? ""
|
||||||
: "\tThe `docker` executable is not present in the runtime environment.\n"
|
: "\tThe `docker` executable is not present in the runtime environment.\n"
|
||||||
}`;
|
}`;
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
const localURL = (port: number, path: string): string => {
|
const localURL = (port: number, path: string): string => {
|
||||||
return `http://localhost:${port}${path}`;
|
return `http://localhost:${port}${path}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: "testsSetup",
|
name: "testsSetup",
|
||||||
testMatch: /global.setup\.ts/,
|
testMatch: /global.setup\.ts/,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tests",
|
name: "tests",
|
||||||
testMatch: /.*\.spec\.ts/,
|
testMatch: /.*\.spec\.ts/,
|
||||||
dependencies: ["testsSetup"],
|
dependencies: ["testsSetup"],
|
||||||
use: { storageState },
|
use: { storageState },
|
||||||
timeout: 50_000,
|
timeout: 50_000,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
reporter: [["./reporter.ts"]],
|
reporter: [["./reporter.ts"]],
|
||||||
use: {
|
use: {
|
||||||
baseURL: `http://localhost:${coderPort}`,
|
baseURL: `http://localhost:${coderPort}`,
|
||||||
video: "retain-on-failure",
|
video: "retain-on-failure",
|
||||||
...(wsEndpoint
|
...(wsEndpoint
|
||||||
? {
|
? {
|
||||||
connectOptions: {
|
connectOptions: {
|
||||||
wsEndpoint: wsEndpoint,
|
wsEndpoint: wsEndpoint,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
launchOptions: {
|
launchOptions: {
|
||||||
args: ["--disable-webgl"],
|
args: ["--disable-webgl"],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
webServer: {
|
webServer: {
|
||||||
url: `http://localhost:${coderPort}/api/v2/deployment/config`,
|
url: `http://localhost:${coderPort}/api/v2/deployment/config`,
|
||||||
command: [
|
command: [
|
||||||
`go run -tags embed ${coderMain} server`,
|
`go run -tags embed ${coderMain} server`,
|
||||||
"--global-config $(mktemp -d -t e2e-XXXXXXXXXX)",
|
"--global-config $(mktemp -d -t e2e-XXXXXXXXXX)",
|
||||||
`--access-url=http://localhost:${coderPort}`,
|
`--access-url=http://localhost:${coderPort}`,
|
||||||
`--http-address=0.0.0.0:${coderPort}`,
|
`--http-address=0.0.0.0:${coderPort}`,
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--telemetry=false",
|
"--telemetry=false",
|
||||||
"--dangerous-disable-rate-limits",
|
"--dangerous-disable-rate-limits",
|
||||||
"--provisioner-daemons 10",
|
"--provisioner-daemons 10",
|
||||||
// TODO: Enable some terraform provisioners
|
// TODO: Enable some terraform provisioners
|
||||||
`--provisioner-types=echo${requireTerraformTests ? ",terraform" : ""}`,
|
`--provisioner-types=echo${requireTerraformTests ? ",terraform" : ""}`,
|
||||||
"--provisioner-daemons=10",
|
"--provisioner-daemons=10",
|
||||||
"--web-terminal-renderer=dom",
|
"--web-terminal-renderer=dom",
|
||||||
"--pprof-enable",
|
"--pprof-enable",
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" "),
|
.join(" "),
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
// Otherwise, the runner fails on Mac with: could not determine kind of name for C.uuid_string_t
|
// Otherwise, the runner fails on Mac with: could not determine kind of name for C.uuid_string_t
|
||||||
CGO_ENABLED: "0",
|
CGO_ENABLED: "0",
|
||||||
|
|
||||||
// This is the test provider for git auth with devices!
|
// This is the test provider for git auth with devices!
|
||||||
CODER_GITAUTH_0_ID: gitAuth.deviceProvider,
|
CODER_GITAUTH_0_ID: gitAuth.deviceProvider,
|
||||||
CODER_GITAUTH_0_TYPE: "github",
|
CODER_GITAUTH_0_TYPE: "github",
|
||||||
CODER_GITAUTH_0_CLIENT_ID: "client",
|
CODER_GITAUTH_0_CLIENT_ID: "client",
|
||||||
CODER_GITAUTH_0_CLIENT_SECRET: "secret",
|
CODER_GITAUTH_0_CLIENT_SECRET: "secret",
|
||||||
CODER_GITAUTH_0_DEVICE_FLOW: "true",
|
CODER_GITAUTH_0_DEVICE_FLOW: "true",
|
||||||
CODER_GITAUTH_0_APP_INSTALL_URL:
|
CODER_GITAUTH_0_APP_INSTALL_URL:
|
||||||
"https://github.com/apps/coder/installations/new",
|
"https://github.com/apps/coder/installations/new",
|
||||||
CODER_GITAUTH_0_APP_INSTALLATIONS_URL: localURL(
|
CODER_GITAUTH_0_APP_INSTALLATIONS_URL: localURL(
|
||||||
gitAuth.devicePort,
|
gitAuth.devicePort,
|
||||||
gitAuth.installationsPath,
|
gitAuth.installationsPath,
|
||||||
),
|
),
|
||||||
CODER_GITAUTH_0_TOKEN_URL: localURL(
|
CODER_GITAUTH_0_TOKEN_URL: localURL(
|
||||||
gitAuth.devicePort,
|
gitAuth.devicePort,
|
||||||
gitAuth.tokenPath,
|
gitAuth.tokenPath,
|
||||||
),
|
),
|
||||||
CODER_GITAUTH_0_DEVICE_CODE_URL: localURL(
|
CODER_GITAUTH_0_DEVICE_CODE_URL: localURL(
|
||||||
gitAuth.devicePort,
|
gitAuth.devicePort,
|
||||||
gitAuth.codePath,
|
gitAuth.codePath,
|
||||||
),
|
),
|
||||||
CODER_GITAUTH_0_VALIDATE_URL: localURL(
|
CODER_GITAUTH_0_VALIDATE_URL: localURL(
|
||||||
gitAuth.devicePort,
|
gitAuth.devicePort,
|
||||||
gitAuth.validatePath,
|
gitAuth.validatePath,
|
||||||
),
|
),
|
||||||
|
|
||||||
CODER_GITAUTH_1_ID: gitAuth.webProvider,
|
CODER_GITAUTH_1_ID: gitAuth.webProvider,
|
||||||
CODER_GITAUTH_1_TYPE: "github",
|
CODER_GITAUTH_1_TYPE: "github",
|
||||||
CODER_GITAUTH_1_CLIENT_ID: "client",
|
CODER_GITAUTH_1_CLIENT_ID: "client",
|
||||||
CODER_GITAUTH_1_CLIENT_SECRET: "secret",
|
CODER_GITAUTH_1_CLIENT_SECRET: "secret",
|
||||||
CODER_GITAUTH_1_AUTH_URL: localURL(gitAuth.webPort, gitAuth.authPath),
|
CODER_GITAUTH_1_AUTH_URL: localURL(gitAuth.webPort, gitAuth.authPath),
|
||||||
CODER_GITAUTH_1_TOKEN_URL: localURL(gitAuth.webPort, gitAuth.tokenPath),
|
CODER_GITAUTH_1_TOKEN_URL: localURL(gitAuth.webPort, gitAuth.tokenPath),
|
||||||
CODER_GITAUTH_1_DEVICE_CODE_URL: localURL(
|
CODER_GITAUTH_1_DEVICE_CODE_URL: localURL(
|
||||||
gitAuth.webPort,
|
gitAuth.webPort,
|
||||||
gitAuth.codePath,
|
gitAuth.codePath,
|
||||||
),
|
),
|
||||||
CODER_GITAUTH_1_VALIDATE_URL: localURL(
|
CODER_GITAUTH_1_VALIDATE_URL: localURL(
|
||||||
gitAuth.webPort,
|
gitAuth.webPort,
|
||||||
gitAuth.validatePath,
|
gitAuth.validatePath,
|
||||||
),
|
),
|
||||||
CODER_PPROF_ADDRESS: `127.0.0.1:${coderdPProfPort}`,
|
CODER_PPROF_ADDRESS: `127.0.0.1:${coderdPProfPort}`,
|
||||||
CODER_EXPERIMENTS: `multi-organization,${e2eFakeExperiment1},${e2eFakeExperiment2}`,
|
CODER_EXPERIMENTS: `multi-organization,${e2eFakeExperiment1},${e2eFakeExperiment2}`,
|
||||||
|
|
||||||
// Tests for Deployment / User Authentication / OIDC
|
// Tests for Deployment / User Authentication / OIDC
|
||||||
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
CODER_OIDC_ISSUER_URL: "https://accounts.google.com",
|
||||||
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
CODER_OIDC_EMAIL_DOMAIN: "coder.com",
|
||||||
CODER_OIDC_CLIENT_ID: "1234567890",
|
CODER_OIDC_CLIENT_ID: "1234567890",
|
||||||
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
CODER_OIDC_CLIENT_SECRET: "1234567890Secret",
|
||||||
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
CODER_OIDC_ALLOW_SIGNUPS: "false",
|
||||||
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
CODER_OIDC_SIGN_IN_TEXT: "Hello",
|
||||||
CODER_OIDC_ICON_URL: "/icon/google.svg",
|
CODER_OIDC_ICON_URL: "/icon/google.svg",
|
||||||
},
|
},
|
||||||
reuseExistingServer: false,
|
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";
|
import { waitUntilUrlIsNotResponding } from "./helpers";
|
||||||
|
|
||||||
export const startWorkspaceProxy = async (
|
export const startWorkspaceProxy = async (
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<ChildProcess> => {
|
): Promise<ChildProcess> => {
|
||||||
const cp = spawn("go", ["run", coderMain, "wsproxy", "server"], {
|
const cp = spawn("go", ["run", coderMain, "wsproxy", "server"], {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
CODER_PRIMARY_ACCESS_URL: `http://127.0.0.1:${coderPort}`,
|
CODER_PRIMARY_ACCESS_URL: `http://127.0.0.1:${coderPort}`,
|
||||||
CODER_PROXY_SESSION_TOKEN: token,
|
CODER_PROXY_SESSION_TOKEN: token,
|
||||||
CODER_HTTP_ADDRESS: `localhost:${workspaceProxyPort}`,
|
CODER_HTTP_ADDRESS: `localhost:${workspaceProxyPort}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
cp.stdout.on("data", (data: Buffer) => {
|
cp.stdout.on("data", (data: Buffer) => {
|
||||||
// eslint-disable-next-line no-console -- Log wsproxy activity
|
// eslint-disable-next-line no-console -- Log wsproxy activity
|
||||||
console.log(
|
console.log(
|
||||||
`[wsproxy] [stdout] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
`[wsproxy] [stdout] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
cp.stderr.on("data", (data: Buffer) => {
|
cp.stderr.on("data", (data: Buffer) => {
|
||||||
// eslint-disable-next-line no-console -- Log wsproxy activity
|
// eslint-disable-next-line no-console -- Log wsproxy activity
|
||||||
console.log(
|
console.log(
|
||||||
`[wsproxy] [stderr] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
`[wsproxy] [stderr] [onData] ${data.toString().replace(/\n$/g, "")}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return cp;
|
return cp;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stopWorkspaceProxy = async (cp: ChildProcess, goRun = true) => {
|
export const stopWorkspaceProxy = async (cp: ChildProcess, goRun = true) => {
|
||||||
exec(goRun ? `pkill -P ${cp.pid}` : `kill ${cp.pid}`, (error) => {
|
exec(goRun ? `pkill -P ${cp.pid}` : `kill ${cp.pid}`, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new Error(`exec error: ${JSON.stringify(error)}`);
|
throw new Error(`exec error: ${JSON.stringify(error)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await waitUntilUrlIsNotResponding(`http://127.0.0.1:${workspaceProxyPort}`);
|
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";
|
import type { Writable } from "node:stream";
|
||||||
/* eslint-disable no-console -- Logging is sort of the whole point here */
|
/* eslint-disable no-console -- Logging is sort of the whole point here */
|
||||||
import type {
|
import type {
|
||||||
FullConfig,
|
FullConfig,
|
||||||
FullResult,
|
FullResult,
|
||||||
Reporter,
|
Reporter,
|
||||||
Suite,
|
Suite,
|
||||||
TestCase,
|
TestCase,
|
||||||
TestError,
|
TestError,
|
||||||
TestResult,
|
TestResult,
|
||||||
} from "@playwright/test/reporter";
|
} from "@playwright/test/reporter";
|
||||||
import { API } from "api/api";
|
import { API } from "api/api";
|
||||||
import { coderdPProfPort, enterpriseLicense } from "./constants";
|
import { coderdPProfPort, enterpriseLicense } from "./constants";
|
||||||
|
|
||||||
class CoderReporter implements Reporter {
|
class CoderReporter implements Reporter {
|
||||||
config: FullConfig | null = null;
|
config: FullConfig | null = null;
|
||||||
testOutput = new Map<string, Array<[Writable, string]>>();
|
testOutput = new Map<string, Array<[Writable, string]>>();
|
||||||
passedCount = 0;
|
passedCount = 0;
|
||||||
skippedCount = 0;
|
skippedCount = 0;
|
||||||
failedTests: TestCase[] = [];
|
failedTests: TestCase[] = [];
|
||||||
timedOutTests: TestCase[] = [];
|
timedOutTests: TestCase[] = [];
|
||||||
|
|
||||||
onBegin(config: FullConfig, suite: Suite) {
|
onBegin(config: FullConfig, suite: Suite) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
console.log(`==> Running ${suite.allTests().length} tests`);
|
console.log(`==> Running ${suite.allTests().length} tests`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTestBegin(test: TestCase) {
|
onTestBegin(test: TestCase) {
|
||||||
this.testOutput.set(test.id, []);
|
this.testOutput.set(test.id, []);
|
||||||
console.log(`==> Starting test ${test.title}`);
|
console.log(`==> Starting test ${test.title}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdOut(chunk: string, test?: TestCase, _?: TestResult): void {
|
onStdOut(chunk: string, test?: TestCase, _?: TestResult): void {
|
||||||
// If there's no associated test, just print it now
|
// If there's no associated test, just print it now
|
||||||
if (!test) {
|
if (!test) {
|
||||||
for (const line of logLines(chunk)) {
|
for (const line of logLines(chunk)) {
|
||||||
console.log(`[stdout] ${line}`);
|
console.log(`[stdout] ${line}`);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Will be printed if the test fails
|
// Will be printed if the test fails
|
||||||
this.testOutput.get(test.id)!.push([process.stdout, chunk]);
|
this.testOutput.get(test.id)!.push([process.stdout, chunk]);
|
||||||
}
|
}
|
||||||
|
|
||||||
onStdErr(chunk: string, test?: TestCase, _?: TestResult): void {
|
onStdErr(chunk: string, test?: TestCase, _?: TestResult): void {
|
||||||
// If there's no associated test, just print it now
|
// If there's no associated test, just print it now
|
||||||
if (!test) {
|
if (!test) {
|
||||||
for (const line of logLines(chunk)) {
|
for (const line of logLines(chunk)) {
|
||||||
console.error(`[stderr] ${line}`);
|
console.error(`[stderr] ${line}`);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Will be printed if the test fails
|
// Will be printed if the test fails
|
||||||
this.testOutput.get(test.id)!.push([process.stderr, chunk]);
|
this.testOutput.get(test.id)!.push([process.stderr, chunk]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onTestEnd(test: TestCase, result: TestResult) {
|
async onTestEnd(test: TestCase, result: TestResult) {
|
||||||
try {
|
try {
|
||||||
if (test.expectedStatus === "skipped") {
|
if (test.expectedStatus === "skipped") {
|
||||||
console.log(`==> Skipping test ${test.title}`);
|
console.log(`==> Skipping test ${test.title}`);
|
||||||
this.skippedCount++;
|
this.skippedCount++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`==> Finished test ${test.title}: ${result.status}`);
|
console.log(`==> Finished test ${test.title}: ${result.status}`);
|
||||||
|
|
||||||
if (result.status === "passed") {
|
if (result.status === "passed") {
|
||||||
this.passedCount++;
|
this.passedCount++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.status === "failed") {
|
if (result.status === "failed") {
|
||||||
this.failedTests.push(test);
|
this.failedTests.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.status === "timedOut") {
|
if (result.status === "timedOut") {
|
||||||
this.timedOutTests.push(test);
|
this.timedOutTests.push(test);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fsTestTitle = test.title.replaceAll(" ", "-");
|
const fsTestTitle = test.title.replaceAll(" ", "-");
|
||||||
const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`;
|
const outputFile = `test-results/debug-pprof-goroutine-${fsTestTitle}.txt`;
|
||||||
await exportDebugPprof(outputFile);
|
await exportDebugPprof(outputFile);
|
||||||
|
|
||||||
console.log(`Data from pprof has been saved to ${outputFile}`);
|
console.log(`Data from pprof has been saved to ${outputFile}`);
|
||||||
console.log("==> Output");
|
console.log("==> Output");
|
||||||
const output = this.testOutput.get(test.id)!;
|
const output = this.testOutput.get(test.id)!;
|
||||||
for (const [target, chunk] of output) {
|
for (const [target, chunk] of output) {
|
||||||
target.write(`${chunk.replace(/\n$/g, "")}\n`);
|
target.write(`${chunk.replace(/\n$/g, "")}\n`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.errors.length > 0) {
|
if (result.errors.length > 0) {
|
||||||
console.log("==> Errors");
|
console.log("==> Errors");
|
||||||
for (const error of result.errors) {
|
for (const error of result.errors) {
|
||||||
reportError(error);
|
reportError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.attachments.length > 0) {
|
if (result.attachments.length > 0) {
|
||||||
console.log("==> Attachments");
|
console.log("==> Attachments");
|
||||||
for (const attachment of result.attachments) {
|
for (const attachment of result.attachments) {
|
||||||
console.log(attachment);
|
console.log(attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.testOutput.delete(test.id);
|
this.testOutput.delete(test.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnd(result: FullResult) {
|
onEnd(result: FullResult) {
|
||||||
console.log(`==> Tests ${result.status}`);
|
console.log(`==> Tests ${result.status}`);
|
||||||
if (!enterpriseLicense) {
|
if (!enterpriseLicense) {
|
||||||
console.log(
|
console.log(
|
||||||
"==> Enterprise tests were skipped, because no license was provided",
|
"==> Enterprise tests were skipped, because no license was provided",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(`${this.passedCount} passed`);
|
console.log(`${this.passedCount} passed`);
|
||||||
if (this.skippedCount > 0) {
|
if (this.skippedCount > 0) {
|
||||||
console.log(`${this.skippedCount} skipped`);
|
console.log(`${this.skippedCount} skipped`);
|
||||||
}
|
}
|
||||||
if (this.failedTests.length > 0) {
|
if (this.failedTests.length > 0) {
|
||||||
console.log(`${this.failedTests.length} failed`);
|
console.log(`${this.failedTests.length} failed`);
|
||||||
for (const test of this.failedTests) {
|
for (const test of this.failedTests) {
|
||||||
console.log(` ${test.location.file} › ${test.title}`);
|
console.log(` ${test.location.file} › ${test.title}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.timedOutTests.length > 0) {
|
if (this.timedOutTests.length > 0) {
|
||||||
console.log(`${this.timedOutTests.length} timed out`);
|
console.log(`${this.timedOutTests.length} timed out`);
|
||||||
for (const test of this.timedOutTests) {
|
for (const test of this.timedOutTests) {
|
||||||
console.log(` ${test.location.file} › ${test.title}`);
|
console.log(` ${test.location.file} › ${test.title}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const logLines = (chunk: string | Buffer): string[] => {
|
const logLines = (chunk: string | Buffer): string[] => {
|
||||||
if (chunk instanceof Buffer) {
|
if (chunk instanceof Buffer) {
|
||||||
// When running in a debugger, the input to this is a Buffer instead of a string.
|
// 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.
|
// Unsure why, but this prevents the `trimEnd` from throwing an error.
|
||||||
return [chunk.toString()];
|
return [chunk.toString()];
|
||||||
}
|
}
|
||||||
return chunk.trimEnd().split("\n");
|
return chunk.trimEnd().split("\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportDebugPprof = async (outputFile: string) => {
|
const exportDebugPprof = async (outputFile: string) => {
|
||||||
const axiosInstance = API.getAxiosInstance();
|
const axiosInstance = API.getAxiosInstance();
|
||||||
const response = await axiosInstance.get(
|
const response = await axiosInstance.get(
|
||||||
`http://127.0.0.1:${coderdPProfPort}/debug/pprof/goroutine?debug=1`,
|
`http://127.0.0.1:${coderdPProfPort}/debug/pprof/goroutine?debug=1`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(`Error: Received status code ${response.status}`);
|
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) => {
|
const reportError = (error: TestError) => {
|
||||||
if (error.location) {
|
if (error.location) {
|
||||||
console.log(`${error.location.file}:${error.location.line}:`);
|
console.log(`${error.location.file}:${error.location.line}:`);
|
||||||
}
|
}
|
||||||
if (error.snippet) {
|
if (error.snippet) {
|
||||||
console.log(error.snippet);
|
console.log(error.snippet);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.message) {
|
if (error.message) {
|
||||||
console.log(error.message);
|
console.log(error.message);
|
||||||
} else {
|
} else {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars -- Playwright config uses it
|
// 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 * as http from "node:http";
|
||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
startAgent,
|
startAgent,
|
||||||
stopAgent,
|
stopAgent,
|
||||||
stopWorkspace,
|
stopWorkspace,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest } from "../hooks";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("app", async ({ context, page }) => {
|
test("app", async ({ context, page }) => {
|
||||||
const appContent = "Hello World";
|
const appContent = "Hello World";
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
const srv = http
|
const srv = http
|
||||||
.createServer((req, res) => {
|
.createServer((req, res) => {
|
||||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||||
res.end(appContent);
|
res.end(appContent);
|
||||||
})
|
})
|
||||||
.listen(0);
|
.listen(0);
|
||||||
const addr = srv.address();
|
const addr = srv.address();
|
||||||
if (typeof addr !== "object" || !addr) {
|
if (typeof addr !== "object" || !addr) {
|
||||||
throw new Error("Expected addr to be an object");
|
throw new Error("Expected addr to be an object");
|
||||||
}
|
}
|
||||||
const appName = "test-app";
|
const appName = "test-app";
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
apply: [
|
||||||
{
|
{
|
||||||
apply: {
|
apply: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
{
|
{
|
||||||
token,
|
token,
|
||||||
apps: [
|
apps: [
|
||||||
{
|
{
|
||||||
url: `http://localhost:${addr.port}`,
|
url: `http://localhost:${addr.port}`,
|
||||||
displayName: appName,
|
displayName: appName,
|
||||||
order: 0,
|
order: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
order: 0,
|
order: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
const agent = await startAgent(page, token);
|
const agent = await startAgent(page, token);
|
||||||
|
|
||||||
// Wait for the web terminal to open in a new tab
|
// Wait for the web terminal to open in a new tab
|
||||||
const pagePromise = context.waitForEvent("page");
|
const pagePromise = context.waitForEvent("page");
|
||||||
await page.getByText(appName).click();
|
await page.getByText(appName).click();
|
||||||
const app = await pagePromise;
|
const app = await pagePromise;
|
||||||
await app.waitForLoadState("domcontentloaded");
|
await app.waitForLoadState("domcontentloaded");
|
||||||
await app.getByText(appContent).isVisible();
|
await app.getByText(appContent).isVisible();
|
||||||
|
|
||||||
await stopWorkspace(page, workspaceName);
|
await stopWorkspace(page, workspaceName);
|
||||||
await stopAgent(agent);
|
await stopAgent(agent);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,69 +1,69 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
requiresEnterpriseLicense,
|
requiresEnterpriseLicense,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest } from "../hooks";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("inspecting and filtering audit logs", async ({ page }) => {
|
test("inspecting and filtering audit logs", async ({ page }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
|
|
||||||
const userName = "admin";
|
const userName = "admin";
|
||||||
// Do some stuff that should show up in the audit logs
|
// Do some stuff that should show up in the audit logs
|
||||||
const templateName = await createTemplate(page);
|
const templateName = await createTemplate(page);
|
||||||
const workspaceName = await createWorkspace(page, templateName);
|
const workspaceName = await createWorkspace(page, templateName);
|
||||||
|
|
||||||
// Go to the audit history
|
// Go to the audit history
|
||||||
await page.goto("/audit");
|
await page.goto("/audit");
|
||||||
|
|
||||||
// Make sure those things we did all actually show up
|
// Make sure those things we did all actually show up
|
||||||
await expect(page.getByText(`${userName} logged in`)).toBeVisible();
|
await expect(page.getByText(`${userName} logged in`)).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(`${userName} created template ${templateName}`),
|
page.getByText(`${userName} created template ${templateName}`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(`${userName} created workspace ${workspaceName}`),
|
page.getByText(`${userName} created workspace ${workspaceName}`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(`${userName} started workspace ${workspaceName}`),
|
page.getByText(`${userName} started workspace ${workspaceName}`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
// Make sure we can inspect the details of the log item
|
// Make sure we can inspect the details of the log item
|
||||||
const createdWorkspace = page.locator(".MuiTableRow-root", {
|
const createdWorkspace = page.locator(".MuiTableRow-root", {
|
||||||
hasText: `${userName} created workspace ${workspaceName}`,
|
hasText: `${userName} created workspace ${workspaceName}`,
|
||||||
});
|
});
|
||||||
await createdWorkspace.getByLabel("open-dropdown").click();
|
await createdWorkspace.getByLabel("open-dropdown").click();
|
||||||
await expect(
|
await expect(
|
||||||
createdWorkspace.getByText(`automatic_updates: "never"`),
|
createdWorkspace.getByText(`automatic_updates: "never"`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
await expect(
|
await expect(
|
||||||
createdWorkspace.getByText(`name: "${workspaceName}"`),
|
createdWorkspace.getByText(`name: "${workspaceName}"`),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
const startedWorkspaceMessage = `${userName} started workspace ${workspaceName}`;
|
const startedWorkspaceMessage = `${userName} started workspace ${workspaceName}`;
|
||||||
const loginMessage = `${userName} logged in`;
|
const loginMessage = `${userName} logged in`;
|
||||||
|
|
||||||
// Filter by resource type
|
// Filter by resource type
|
||||||
await page.getByText("All resource types").click();
|
await page.getByText("All resource types").click();
|
||||||
await page.getByRole("menu").getByText("Workspace Build").click();
|
await page.getByRole("menu").getByText("Workspace Build").click();
|
||||||
// Our workspace build should be visible
|
// Our workspace build should be visible
|
||||||
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||||
// Logins should no longer be visible
|
// Logins should no longer be visible
|
||||||
await expect(page.getByText(loginMessage)).not.toBeVisible();
|
await expect(page.getByText(loginMessage)).not.toBeVisible();
|
||||||
|
|
||||||
// Clear filters, everything should be visible again
|
// Clear filters, everything should be visible again
|
||||||
await page.getByLabel("Clear filter").click();
|
await page.getByLabel("Clear filter").click();
|
||||||
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||||
await expect(page.getByText(loginMessage)).toBeVisible();
|
await expect(page.getByText(loginMessage)).toBeVisible();
|
||||||
|
|
||||||
// Filter by action type
|
// Filter by action type
|
||||||
await page.getByText("All actions").click();
|
await page.getByText("All actions").click();
|
||||||
await page.getByRole("menu").getByText("Login").click();
|
await page.getByRole("menu").getByText("Login").click();
|
||||||
// Logins should be visible
|
// Logins should be visible
|
||||||
await expect(page.getByText(loginMessage)).toBeVisible();
|
await expect(page.getByText(loginMessage)).toBeVisible();
|
||||||
// Our workspace build should no longer be visible
|
// Our workspace build should no longer be visible
|
||||||
await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
|
await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,80 +3,80 @@ import { expectUrl } from "../../expectUrl";
|
|||||||
import { randomName, requiresEnterpriseLicense } from "../../helpers";
|
import { randomName, requiresEnterpriseLicense } from "../../helpers";
|
||||||
|
|
||||||
test("set application name", async ({ page }) => {
|
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
|
// Fill out the form
|
||||||
const form = page.locator("form", { hasText: "Application name" });
|
const form = page.locator("form", { hasText: "Application name" });
|
||||||
await form
|
await form
|
||||||
.getByLabel("Application name", { exact: true })
|
.getByLabel("Application name", { exact: true })
|
||||||
.fill(applicationName);
|
.fill(applicationName);
|
||||||
await form.getByRole("button", { name: "Submit" }).click();
|
await form.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
// Open a new session without cookies to see the login page
|
// Open a new session without cookies to see the login page
|
||||||
const browser = await chromium.launch();
|
const browser = await chromium.launch();
|
||||||
const incognitoContext = await browser.newContext();
|
const incognitoContext = await browser.newContext();
|
||||||
await incognitoContext.clearCookies();
|
await incognitoContext.clearCookies();
|
||||||
const incognitoPage = await incognitoContext.newPage();
|
const incognitoPage = await incognitoContext.newPage();
|
||||||
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
||||||
|
|
||||||
// Verify the application name
|
// Verify the application name
|
||||||
const name = incognitoPage.locator("h1", { hasText: applicationName });
|
const name = incognitoPage.locator("h1", { hasText: applicationName });
|
||||||
await expect(name).toBeVisible();
|
await expect(name).toBeVisible();
|
||||||
|
|
||||||
// Shut down browser
|
// Shut down browser
|
||||||
await incognitoPage.close();
|
await incognitoPage.close();
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("set application logo", async ({ page }) => {
|
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
|
// Fill out the form
|
||||||
const form = page.locator("form", { hasText: "Logo URL" });
|
const form = page.locator("form", { hasText: "Logo URL" });
|
||||||
await form.getByLabel("Logo URL", { exact: true }).fill(imageLink);
|
await form.getByLabel("Logo URL", { exact: true }).fill(imageLink);
|
||||||
await form.getByRole("button", { name: "Submit" }).click();
|
await form.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
// Open a new session without cookies to see the login page
|
// Open a new session without cookies to see the login page
|
||||||
const browser = await chromium.launch();
|
const browser = await chromium.launch();
|
||||||
const incognitoContext = await browser.newContext();
|
const incognitoContext = await browser.newContext();
|
||||||
await incognitoContext.clearCookies();
|
await incognitoContext.clearCookies();
|
||||||
const incognitoPage = await incognitoContext.newPage();
|
const incognitoPage = await incognitoContext.newPage();
|
||||||
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
await incognitoPage.goto("/", { waitUntil: "domcontentloaded" });
|
||||||
|
|
||||||
// Verify banner
|
// Verify banner
|
||||||
const logo = incognitoPage.locator("img.application-logo");
|
const logo = incognitoPage.locator("img.application-logo");
|
||||||
await expect(logo).toHaveAttribute("src", imageLink);
|
await expect(logo).toHaveAttribute("src", imageLink);
|
||||||
|
|
||||||
// Shut down browser
|
// Shut down browser
|
||||||
await incognitoPage.close();
|
await incognitoPage.close();
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("set service banner", async ({ page }) => {
|
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
|
// Fill out the form
|
||||||
const form = page.locator("form", { hasText: "Service Banner" });
|
const form = page.locator("form", { hasText: "Service Banner" });
|
||||||
await form.getByLabel("Enabled", { exact: true }).check();
|
await form.getByLabel("Enabled", { exact: true }).check();
|
||||||
await form.getByLabel("Message", { exact: true }).fill(message);
|
await form.getByLabel("Message", { exact: true }).fill(message);
|
||||||
await form.getByRole("button", { name: "Submit" }).click();
|
await form.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
// Verify service banner
|
// Verify service banner
|
||||||
await page.goto("/workspaces", { waitUntil: "domcontentloaded" });
|
await page.goto("/workspaces", { waitUntil: "domcontentloaded" });
|
||||||
await expectUrl(page).toHavePathName("/workspaces");
|
await expectUrl(page).toHavePathName("/workspaces");
|
||||||
|
|
||||||
const bar = page.locator("div.service-banner", { hasText: message });
|
const bar = page.locator("div.service-banner", { hasText: message });
|
||||||
await expect(bar).toBeVisible();
|
await expect(bar).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,36 +4,36 @@ import { setupApiCalls } from "../../api";
|
|||||||
import { e2eFakeExperiment1, e2eFakeExperiment2 } from "../../constants";
|
import { e2eFakeExperiment1, e2eFakeExperiment2 } from "../../constants";
|
||||||
|
|
||||||
test("experiments", async ({ page }) => {
|
test("experiments", async ({ page }) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
|
|
||||||
// Load experiments from backend API
|
// Load experiments from backend API
|
||||||
const availableExperiments = await API.getAvailableExperiments();
|
const availableExperiments = await API.getAvailableExperiments();
|
||||||
|
|
||||||
// Verify if the site lists the same experiments
|
// Verify if the site lists the same experiments
|
||||||
await page.goto("/deployment/general", { waitUntil: "networkidle" });
|
await page.goto("/deployment/general", { waitUntil: "networkidle" });
|
||||||
|
|
||||||
const experimentsLocator = page.locator(
|
const experimentsLocator = page.locator(
|
||||||
"div.options-table tr.option-experiments ul.option-array",
|
"div.options-table tr.option-experiments ul.option-array",
|
||||||
);
|
);
|
||||||
await expect(experimentsLocator).toBeVisible();
|
await expect(experimentsLocator).toBeVisible();
|
||||||
|
|
||||||
// Firstly, check if all enabled experiments are listed
|
// Firstly, check if all enabled experiments are listed
|
||||||
expect(
|
expect(
|
||||||
experimentsLocator.locator(
|
experimentsLocator.locator(
|
||||||
`li.option-array-item-${e2eFakeExperiment1}.option-enabled`,
|
`li.option-array-item-${e2eFakeExperiment1}.option-enabled`,
|
||||||
),
|
),
|
||||||
).toBeVisible;
|
).toBeVisible;
|
||||||
expect(
|
expect(
|
||||||
experimentsLocator.locator(
|
experimentsLocator.locator(
|
||||||
`li.option-array-item-${e2eFakeExperiment2}.option-enabled`,
|
`li.option-array-item-${e2eFakeExperiment2}.option-enabled`,
|
||||||
),
|
),
|
||||||
).toBeVisible;
|
).toBeVisible;
|
||||||
|
|
||||||
// Secondly, check if available experiments are listed
|
// Secondly, check if available experiments are listed
|
||||||
for (const experiment of availableExperiments.safe) {
|
for (const experiment of availableExperiments.safe) {
|
||||||
const experimentLocator = experimentsLocator.locator(
|
const experimentLocator = experimentsLocator.locator(
|
||||||
`li.option-array-item-${experiment}`,
|
`li.option-array-item-${experiment}`,
|
||||||
);
|
);
|
||||||
await expect(experimentLocator).toBeVisible();
|
await expect(experimentLocator).toBeVisible();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,29 +2,29 @@ import { expect, test } from "@playwright/test";
|
|||||||
import { requiresEnterpriseLicense } from "../../helpers";
|
import { requiresEnterpriseLicense } from "../../helpers";
|
||||||
|
|
||||||
test("license was added successfully", async ({ page }) => {
|
test("license was added successfully", async ({ page }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
|
|
||||||
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
|
await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });
|
||||||
const firstLicense = page.locator(".licenses > .license-card", {
|
const firstLicense = page.locator(".licenses > .license-card", {
|
||||||
hasText: "#1",
|
hasText: "#1",
|
||||||
});
|
});
|
||||||
await expect(firstLicense).toBeVisible();
|
await expect(firstLicense).toBeVisible();
|
||||||
|
|
||||||
// Trial vs. Enterprise?
|
// Trial vs. Enterprise?
|
||||||
const accountType = firstLicense.locator(".account-type");
|
const accountType = firstLicense.locator(".account-type");
|
||||||
await expect(accountType).toHaveText("Enterprise");
|
await expect(accountType).toHaveText("Enterprise");
|
||||||
|
|
||||||
// User limit 1/1
|
// User limit 1/1
|
||||||
const userLimit = firstLicense.locator(".user-limit");
|
const userLimit = firstLicense.locator(".user-limit");
|
||||||
await expect(userLimit).toHaveText("1 / 1");
|
await expect(userLimit).toHaveText("1 / 1");
|
||||||
|
|
||||||
// License should not be expired yet
|
// License should not be expired yet
|
||||||
const licenseExpires = firstLicense.locator(".license-expires");
|
const licenseExpires = firstLicense.locator(".license-expires");
|
||||||
const licenseExpiresDate = new Date(await licenseExpires.innerText());
|
const licenseExpiresDate = new Date(await licenseExpires.innerText());
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
expect(licenseExpiresDate.getTime()).toBeGreaterThan(now.getTime());
|
expect(licenseExpiresDate.getTime()).toBeGreaterThan(now.getTime());
|
||||||
|
|
||||||
// "Remove" button should be visible
|
// "Remove" button should be visible
|
||||||
const removeButton = firstLicense.locator(".remove-button");
|
const removeButton = firstLicense.locator(".remove-button");
|
||||||
await expect(removeButton).toBeVisible();
|
await expect(removeButton).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import { API } from "api/api";
|
import { API } from "api/api";
|
||||||
import {
|
import {
|
||||||
setupApiCalls,
|
setupApiCalls,
|
||||||
verifyConfigFlagArray,
|
verifyConfigFlagArray,
|
||||||
verifyConfigFlagBoolean,
|
verifyConfigFlagBoolean,
|
||||||
verifyConfigFlagDuration,
|
verifyConfigFlagDuration,
|
||||||
verifyConfigFlagNumber,
|
verifyConfigFlagNumber,
|
||||||
verifyConfigFlagString,
|
verifyConfigFlagString,
|
||||||
} from "../../api";
|
} from "../../api";
|
||||||
|
|
||||||
test("enabled network settings", async ({ page }) => {
|
test("enabled network settings", async ({ page }) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const config = await API.getDeploymentConfig();
|
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 verifyConfigFlagString(page, config, "access-url");
|
||||||
await verifyConfigFlagBoolean(page, config, "block-direct-connections");
|
await verifyConfigFlagBoolean(page, config, "block-direct-connections");
|
||||||
await verifyConfigFlagBoolean(page, config, "browser-only");
|
await verifyConfigFlagBoolean(page, config, "browser-only");
|
||||||
await verifyConfigFlagBoolean(page, config, "derp-force-websockets");
|
await verifyConfigFlagBoolean(page, config, "derp-force-websockets");
|
||||||
await verifyConfigFlagBoolean(page, config, "derp-server-enable");
|
await verifyConfigFlagBoolean(page, config, "derp-server-enable");
|
||||||
await verifyConfigFlagString(page, config, "derp-server-region-code");
|
await verifyConfigFlagString(page, config, "derp-server-region-code");
|
||||||
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 verifyConfigFlagNumber(page, config, "derp-server-region-id");
|
||||||
await verifyConfigFlagString(page, config, "derp-server-region-name");
|
await verifyConfigFlagString(page, config, "derp-server-region-name");
|
||||||
await verifyConfigFlagArray(page, config, "derp-server-stun-addresses");
|
await verifyConfigFlagArray(page, config, "derp-server-stun-addresses");
|
||||||
await verifyConfigFlagBoolean(page, config, "disable-password-auth");
|
await verifyConfigFlagBoolean(page, config, "disable-password-auth");
|
||||||
await verifyConfigFlagBoolean(page, config, "disable-session-expiry-refresh");
|
await verifyConfigFlagBoolean(page, config, "disable-session-expiry-refresh");
|
||||||
await verifyConfigFlagDuration(page, config, "max-token-lifetime");
|
await verifyConfigFlagDuration(page, config, "max-token-lifetime");
|
||||||
await verifyConfigFlagDuration(page, config, "proxy-health-interval");
|
await verifyConfigFlagDuration(page, config, "proxy-health-interval");
|
||||||
await verifyConfigFlagBoolean(page, config, "redirect-to-access-url");
|
await verifyConfigFlagBoolean(page, config, "redirect-to-access-url");
|
||||||
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
||||||
await verifyConfigFlagDuration(page, config, "session-duration");
|
await verifyConfigFlagDuration(page, config, "session-duration");
|
||||||
await verifyConfigFlagString(page, config, "tls-address");
|
await verifyConfigFlagString(page, config, "tls-address");
|
||||||
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
||||||
await verifyConfigFlagString(page, config, "tls-client-auth");
|
await verifyConfigFlagString(page, config, "tls-client-auth");
|
||||||
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
||||||
await verifyConfigFlagString(page, config, "tls-min-version");
|
await verifyConfigFlagString(page, config, "tls-min-version");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import { API } from "api/api";
|
import { API } from "api/api";
|
||||||
import {
|
import {
|
||||||
setupApiCalls,
|
setupApiCalls,
|
||||||
verifyConfigFlagArray,
|
verifyConfigFlagArray,
|
||||||
verifyConfigFlagBoolean,
|
verifyConfigFlagBoolean,
|
||||||
verifyConfigFlagDuration,
|
verifyConfigFlagDuration,
|
||||||
verifyConfigFlagEmpty,
|
verifyConfigFlagEmpty,
|
||||||
verifyConfigFlagString,
|
verifyConfigFlagString,
|
||||||
} from "../../api";
|
} from "../../api";
|
||||||
|
|
||||||
test("enabled observability settings", async ({ page }) => {
|
test("enabled observability settings", async ({ page }) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const config = await API.getDeploymentConfig();
|
const config = await API.getDeploymentConfig();
|
||||||
|
|
||||||
await page.goto("/deployment/observability", {
|
await page.goto("/deployment/observability", {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
|
|
||||||
await verifyConfigFlagBoolean(page, config, "trace-logs");
|
await verifyConfigFlagBoolean(page, config, "trace-logs");
|
||||||
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
||||||
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
await verifyConfigFlagBoolean(page, config, "enable-terraform-debug-mode");
|
||||||
await verifyConfigFlagDuration(page, config, "health-check-refresh");
|
await verifyConfigFlagDuration(page, config, "health-check-refresh");
|
||||||
await verifyConfigFlagEmpty(page, "health-check-threshold-database");
|
await verifyConfigFlagEmpty(page, "health-check-threshold-database");
|
||||||
await verifyConfigFlagString(page, config, "log-human");
|
await verifyConfigFlagString(page, config, "log-human");
|
||||||
await verifyConfigFlagString(page, config, "prometheus-address");
|
await verifyConfigFlagString(page, config, "prometheus-address");
|
||||||
await verifyConfigFlagArray(
|
await verifyConfigFlagArray(
|
||||||
page,
|
page,
|
||||||
config,
|
config,
|
||||||
"prometheus-aggregate-agent-stats-by",
|
"prometheus-aggregate-agent-stats-by",
|
||||||
);
|
);
|
||||||
await verifyConfigFlagBoolean(page, config, "prometheus-collect-agent-stats");
|
await verifyConfigFlagBoolean(page, config, "prometheus-collect-agent-stats");
|
||||||
await verifyConfigFlagBoolean(page, config, "prometheus-collect-db-metrics");
|
await verifyConfigFlagBoolean(page, config, "prometheus-collect-db-metrics");
|
||||||
await verifyConfigFlagBoolean(page, config, "prometheus-enable");
|
await verifyConfigFlagBoolean(page, config, "prometheus-enable");
|
||||||
await verifyConfigFlagBoolean(page, config, "trace-datadog");
|
await verifyConfigFlagBoolean(page, config, "trace-datadog");
|
||||||
await verifyConfigFlagBoolean(page, config, "trace");
|
await verifyConfigFlagBoolean(page, config, "trace");
|
||||||
await verifyConfigFlagBoolean(page, config, "verbose");
|
await verifyConfigFlagBoolean(page, config, "verbose");
|
||||||
await verifyConfigFlagBoolean(page, config, "pprof-enable");
|
await verifyConfigFlagBoolean(page, config, "pprof-enable");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,45 +2,45 @@ import type { Page } from "@playwright/test";
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { API, type DeploymentConfig } from "api/api";
|
import { API, type DeploymentConfig } from "api/api";
|
||||||
import {
|
import {
|
||||||
findConfigOption,
|
findConfigOption,
|
||||||
setupApiCalls,
|
setupApiCalls,
|
||||||
verifyConfigFlagBoolean,
|
verifyConfigFlagBoolean,
|
||||||
verifyConfigFlagNumber,
|
verifyConfigFlagNumber,
|
||||||
verifyConfigFlagString,
|
verifyConfigFlagString,
|
||||||
} from "../../api";
|
} from "../../api";
|
||||||
|
|
||||||
test("enabled security settings", async ({ page }) => {
|
test("enabled security settings", async ({ page }) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const config = await API.getDeploymentConfig();
|
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 verifyConfigFlagString(page, config, "ssh-keygen-algorithm");
|
||||||
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
await verifyConfigFlagBoolean(page, config, "secure-auth-cookie");
|
||||||
await verifyConfigFlagBoolean(page, config, "disable-owner-workspace-access");
|
await verifyConfigFlagBoolean(page, config, "disable-owner-workspace-access");
|
||||||
|
|
||||||
await verifyConfigFlagBoolean(page, config, "tls-redirect-http-to-https");
|
await verifyConfigFlagBoolean(page, config, "tls-redirect-http-to-https");
|
||||||
await verifyStrictTransportSecurity(page, config);
|
await verifyStrictTransportSecurity(page, config);
|
||||||
await verifyConfigFlagString(page, config, "tls-address");
|
await verifyConfigFlagString(page, config, "tls-address");
|
||||||
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
await verifyConfigFlagBoolean(page, config, "tls-allow-insecure-ciphers");
|
||||||
await verifyConfigFlagString(page, config, "tls-client-auth");
|
await verifyConfigFlagString(page, config, "tls-client-auth");
|
||||||
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
await verifyConfigFlagBoolean(page, config, "tls-enable");
|
||||||
await verifyConfigFlagString(page, config, "tls-min-version");
|
await verifyConfigFlagString(page, config, "tls-min-version");
|
||||||
});
|
});
|
||||||
|
|
||||||
async function verifyStrictTransportSecurity(
|
async function verifyStrictTransportSecurity(
|
||||||
page: Page,
|
page: Page,
|
||||||
config: DeploymentConfig,
|
config: DeploymentConfig,
|
||||||
) {
|
) {
|
||||||
const flag = "strict-transport-security";
|
const flag = "strict-transport-security";
|
||||||
const opt = findConfigOption(config, flag);
|
const opt = findConfigOption(config, flag);
|
||||||
if (opt.value !== 0) {
|
if (opt.value !== 0) {
|
||||||
await verifyConfigFlagNumber(page, config, flag);
|
await verifyConfigFlagNumber(page, config, flag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const configOption = page.locator(
|
const configOption = page.locator(
|
||||||
`div.options-table .option-${flag} .option-value-string`,
|
`div.options-table .option-${flag} .option-value-string`,
|
||||||
);
|
);
|
||||||
await expect(configOption).toHaveText("Disabled");
|
await expect(configOption).toHaveText("Disabled");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import { API } from "api/api";
|
import { API } from "api/api";
|
||||||
import {
|
import {
|
||||||
setupApiCalls,
|
setupApiCalls,
|
||||||
verifyConfigFlagArray,
|
verifyConfigFlagArray,
|
||||||
verifyConfigFlagBoolean,
|
verifyConfigFlagBoolean,
|
||||||
verifyConfigFlagEntries,
|
verifyConfigFlagEntries,
|
||||||
verifyConfigFlagString,
|
verifyConfigFlagString,
|
||||||
} from "../../api";
|
} from "../../api";
|
||||||
|
|
||||||
test("login with OIDC", async ({ page }) => {
|
test("login with OIDC", async ({ page }) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const config = await API.getDeploymentConfig();
|
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-group-auto-create");
|
||||||
await verifyConfigFlagBoolean(page, config, "oidc-allow-signups");
|
await verifyConfigFlagBoolean(page, config, "oidc-allow-signups");
|
||||||
await verifyConfigFlagEntries(page, config, "oidc-auth-url-params");
|
await verifyConfigFlagEntries(page, config, "oidc-auth-url-params");
|
||||||
await verifyConfigFlagString(page, config, "oidc-client-id");
|
await verifyConfigFlagString(page, config, "oidc-client-id");
|
||||||
await verifyConfigFlagArray(page, config, "oidc-email-domain");
|
await verifyConfigFlagArray(page, config, "oidc-email-domain");
|
||||||
await verifyConfigFlagString(page, config, "oidc-email-field");
|
await verifyConfigFlagString(page, config, "oidc-email-field");
|
||||||
await verifyConfigFlagEntries(page, config, "oidc-group-mapping");
|
await verifyConfigFlagEntries(page, config, "oidc-group-mapping");
|
||||||
await verifyConfigFlagBoolean(page, config, "oidc-ignore-email-verified");
|
await verifyConfigFlagBoolean(page, config, "oidc-ignore-email-verified");
|
||||||
await verifyConfigFlagBoolean(page, config, "oidc-ignore-userinfo");
|
await verifyConfigFlagBoolean(page, config, "oidc-ignore-userinfo");
|
||||||
await verifyConfigFlagString(page, config, "oidc-issuer-url");
|
await verifyConfigFlagString(page, config, "oidc-issuer-url");
|
||||||
await verifyConfigFlagString(page, config, "oidc-group-regex-filter");
|
await verifyConfigFlagString(page, config, "oidc-group-regex-filter");
|
||||||
await verifyConfigFlagArray(page, config, "oidc-scopes");
|
await verifyConfigFlagArray(page, config, "oidc-scopes");
|
||||||
await verifyConfigFlagEntries(page, config, "oidc-user-role-mapping");
|
await verifyConfigFlagEntries(page, config, "oidc-user-role-mapping");
|
||||||
await verifyConfigFlagString(page, config, "oidc-username-field");
|
await verifyConfigFlagString(page, config, "oidc-username-field");
|
||||||
await verifyConfigFlagString(page, config, "oidc-sign-in-text");
|
await verifyConfigFlagString(page, config, "oidc-sign-in-text");
|
||||||
await verifyConfigFlagString(page, config, "oidc-icon-url");
|
await verifyConfigFlagString(page, config, "oidc-icon-url");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,100 +6,100 @@ import { randomName, requiresEnterpriseLicense } from "../../helpers";
|
|||||||
import { startWorkspaceProxy, stopWorkspaceProxy } from "../../proxy";
|
import { startWorkspaceProxy, stopWorkspaceProxy } from "../../proxy";
|
||||||
|
|
||||||
test("default proxy is online", async ({ page }) => {
|
test("default proxy is online", async ({ page }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
|
|
||||||
await page.goto("/deployment/workspace-proxies", {
|
await page.goto("/deployment/workspace-proxies", {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verify if the default proxy is healthy
|
// Verify if the default proxy is healthy
|
||||||
const workspaceProxyPrimary = page.locator(
|
const workspaceProxyPrimary = page.locator(
|
||||||
`table.MuiTable-root tr[data-testid="primary"]`,
|
`table.MuiTable-root tr[data-testid="primary"]`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceProxyName = workspaceProxyPrimary.locator("td.name span");
|
const workspaceProxyName = workspaceProxyPrimary.locator("td.name span");
|
||||||
const workspaceProxyURL = workspaceProxyPrimary.locator("td.url");
|
const workspaceProxyURL = workspaceProxyPrimary.locator("td.url");
|
||||||
const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span");
|
const workspaceProxyStatus = workspaceProxyPrimary.locator("td.status span");
|
||||||
|
|
||||||
await expect(workspaceProxyName).toHaveText("Default");
|
await expect(workspaceProxyName).toHaveText("Default");
|
||||||
await expect(workspaceProxyURL).toHaveText(`http://localhost:${coderPort}`);
|
await expect(workspaceProxyURL).toHaveText(`http://localhost:${coderPort}`);
|
||||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("custom proxy is online", async ({ page }) => {
|
test("custom proxy is online", async ({ page }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
|
|
||||||
const proxyName = randomName();
|
const proxyName = randomName();
|
||||||
|
|
||||||
// Register workspace proxy
|
// Register workspace proxy
|
||||||
const proxyResponse = await API.createWorkspaceProxy({
|
const proxyResponse = await API.createWorkspaceProxy({
|
||||||
name: proxyName,
|
name: proxyName,
|
||||||
display_name: "",
|
display_name: "",
|
||||||
icon: "/emojis/1f1e7-1f1f7.png",
|
icon: "/emojis/1f1e7-1f1f7.png",
|
||||||
});
|
});
|
||||||
expect(proxyResponse.proxy_token).toBeDefined();
|
expect(proxyResponse.proxy_token).toBeDefined();
|
||||||
|
|
||||||
// Start "wsproxy server"
|
// Start "wsproxy server"
|
||||||
const proxyServer = await startWorkspaceProxy(proxyResponse.proxy_token);
|
const proxyServer = await startWorkspaceProxy(proxyResponse.proxy_token);
|
||||||
await waitUntilWorkspaceProxyIsHealthy(page, proxyName);
|
await waitUntilWorkspaceProxyIsHealthy(page, proxyName);
|
||||||
|
|
||||||
// Verify if custom proxy is healthy
|
// Verify if custom proxy is healthy
|
||||||
await page.goto("/deployment/workspace-proxies", {
|
await page.goto("/deployment/workspace-proxies", {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||||
hasText: proxyName,
|
hasText: proxyName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspaceProxyName = workspaceProxy.locator("td.name span");
|
const workspaceProxyName = workspaceProxy.locator("td.name span");
|
||||||
const workspaceProxyURL = workspaceProxy.locator("td.url");
|
const workspaceProxyURL = workspaceProxy.locator("td.url");
|
||||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||||
|
|
||||||
await expect(workspaceProxyName).toHaveText(proxyName);
|
await expect(workspaceProxyName).toHaveText(proxyName);
|
||||||
await expect(workspaceProxyURL).toHaveText(
|
await expect(workspaceProxyURL).toHaveText(
|
||||||
`http://127.0.0.1:${workspaceProxyPort}`,
|
`http://127.0.0.1:${workspaceProxyPort}`,
|
||||||
);
|
);
|
||||||
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
await expect(workspaceProxyStatus).toHaveText("Healthy");
|
||||||
|
|
||||||
// Tear down the proxy
|
// Tear down the proxy
|
||||||
await stopWorkspaceProxy(proxyServer);
|
await stopWorkspaceProxy(proxyServer);
|
||||||
});
|
});
|
||||||
|
|
||||||
const waitUntilWorkspaceProxyIsHealthy = async (
|
const waitUntilWorkspaceProxyIsHealthy = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
proxyName: string,
|
proxyName: string,
|
||||||
) => {
|
) => {
|
||||||
await page.goto("/deployment/workspace-proxies", {
|
await page.goto("/deployment/workspace-proxies", {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
|
|
||||||
const maxRetries = 30;
|
const maxRetries = 30;
|
||||||
const retryIntervalMs = 1000;
|
const retryIntervalMs = 1000;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
while (retries < maxRetries) {
|
while (retries < maxRetries) {
|
||||||
await page.reload();
|
await page.reload();
|
||||||
|
|
||||||
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
const workspaceProxy = page.locator("table.MuiTable-root tr", {
|
||||||
hasText: proxyName,
|
hasText: proxyName,
|
||||||
});
|
});
|
||||||
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
const workspaceProxyStatus = workspaceProxy.locator("td.status span");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await expect(workspaceProxyStatus).toHaveText("Healthy", {
|
await expect(workspaceProxyStatus).toHaveText("Healthy", {
|
||||||
timeout: 1_000,
|
timeout: 1_000,
|
||||||
});
|
});
|
||||||
return; // healthy!
|
return; // healthy!
|
||||||
} catch {
|
} catch {
|
||||||
retries++;
|
retries++;
|
||||||
await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
|
await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Workspace proxy "${proxyName}" is unhealthy after ${
|
`Workspace proxy "${proxyName}" is unhealthy after ${
|
||||||
maxRetries * retryIntervalMs
|
maxRetries * retryIntervalMs
|
||||||
}ms`,
|
}ms`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+131
-131
@@ -3,32 +3,32 @@ import { test } from "@playwright/test";
|
|||||||
import type { ExternalAuthDevice } from "api/typesGenerated";
|
import type { ExternalAuthDevice } from "api/typesGenerated";
|
||||||
import { gitAuth } from "../constants";
|
import { gitAuth } from "../constants";
|
||||||
import {
|
import {
|
||||||
Awaiter,
|
Awaiter,
|
||||||
createServer,
|
createServer,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
echoResponsesWithExternalAuth,
|
echoResponsesWithExternalAuth,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest, resetExternalAuthKey } from "../hooks";
|
import { beforeCoderTest, resetExternalAuthKey } from "../hooks";
|
||||||
|
|
||||||
test.beforeAll(async ({ baseURL }) => {
|
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!
|
// The GitHub validate endpoint returns the currently authenticated user!
|
||||||
srv.use(gitAuth.validatePath, (req, res) => {
|
srv.use(gitAuth.validatePath, (req, res) => {
|
||||||
res.write(JSON.stringify(ghUser));
|
res.write(JSON.stringify(ghUser));
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||||
const r = (Math.random() + 1).toString(36).substring(7);
|
const r = (Math.random() + 1).toString(36).substring(7);
|
||||||
res.write(JSON.stringify({ access_token: r }));
|
res.write(JSON.stringify({ access_token: r }));
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
srv.use(gitAuth.authPath, (req, res) => {
|
srv.use(gitAuth.authPath, (req, res) => {
|
||||||
res.redirect(
|
res.redirect(
|
||||||
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=${req.query.state}`,
|
`${baseURL}/external-auth/${gitAuth.webProvider}/callback?code=1234&state=${req.query.state}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.beforeEach(async ({ context }) => resetExternalAuthKey(context));
|
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!
|
// Ensures that a Git auth provider with the device flow functions and completes!
|
||||||
test("external auth device", async ({ page }) => {
|
test("external auth device", async ({ page }) => {
|
||||||
const device: ExternalAuthDevice = {
|
const device: ExternalAuthDevice = {
|
||||||
device_code: "1234",
|
device_code: "1234",
|
||||||
user_code: "1234-5678",
|
user_code: "1234-5678",
|
||||||
expires_in: 900,
|
expires_in: 900,
|
||||||
interval: 1,
|
interval: 1,
|
||||||
verification_uri: "",
|
verification_uri: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start a server to mock the GitHub API.
|
// Start a server to mock the GitHub API.
|
||||||
const srv = await createServer(gitAuth.devicePort);
|
const srv = await createServer(gitAuth.devicePort);
|
||||||
srv.use(gitAuth.validatePath, (req, res) => {
|
srv.use(gitAuth.validatePath, (req, res) => {
|
||||||
res.write(JSON.stringify(ghUser));
|
res.write(JSON.stringify(ghUser));
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
srv.use(gitAuth.codePath, (req, res) => {
|
srv.use(gitAuth.codePath, (req, res) => {
|
||||||
res.write(JSON.stringify(device));
|
res.write(JSON.stringify(device));
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
srv.use(gitAuth.installationsPath, (req, res) => {
|
srv.use(gitAuth.installationsPath, (req, res) => {
|
||||||
res.write(JSON.stringify(ghInstall));
|
res.write(JSON.stringify(ghInstall));
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
const token = {
|
const token = {
|
||||||
access_token: "",
|
access_token: "",
|
||||||
error: "authorization_pending",
|
error: "authorization_pending",
|
||||||
error_description: "",
|
error_description: "",
|
||||||
};
|
};
|
||||||
// First we send a result from the API that the token hasn't been
|
// First we send a result from the API that the token hasn't been
|
||||||
// authorized yet to ensure the UI reacts properly.
|
// authorized yet to ensure the UI reacts properly.
|
||||||
const sentPending = new Awaiter();
|
const sentPending = new Awaiter();
|
||||||
srv.use(gitAuth.tokenPath, (req, res) => {
|
srv.use(gitAuth.tokenPath, (req, res) => {
|
||||||
res.write(JSON.stringify(token));
|
res.write(JSON.stringify(token));
|
||||||
res.end();
|
res.end();
|
||||||
sentPending.done();
|
sentPending.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(`/external-auth/${gitAuth.deviceProvider}`, {
|
await page.goto(`/external-auth/${gitAuth.deviceProvider}`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await page.getByText(device.user_code).isVisible();
|
await page.getByText(device.user_code).isVisible();
|
||||||
await sentPending.wait();
|
await sentPending.wait();
|
||||||
// Update the token to be valid and ensure the UI updates!
|
// Update the token to be valid and ensure the UI updates!
|
||||||
token.error = "";
|
token.error = "";
|
||||||
token.access_token = "hello-world";
|
token.access_token = "hello-world";
|
||||||
await page.waitForSelector("text=1 organization authorized");
|
await page.waitForSelector("text=1 organization authorized");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("external auth web", async ({ page }) => {
|
test("external auth web", async ({ page }) => {
|
||||||
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
await page.goto(`/external-auth/${gitAuth.webProvider}`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
// This endpoint doesn't have the installations URL set intentionally!
|
// This endpoint doesn't have the installations URL set intentionally!
|
||||||
await page.waitForSelector("text=You've authenticated with GitHub!");
|
await page.waitForSelector("text=You've authenticated with GitHub!");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("successful external auth from workspace", async ({ page }) => {
|
test("successful external auth from workspace", async ({ page }) => {
|
||||||
const templateName = await createTemplate(
|
const templateName = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithExternalAuth([
|
echoResponsesWithExternalAuth([
|
||||||
{ id: gitAuth.webProvider, optional: false },
|
{ id: gitAuth.webProvider, optional: false },
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
||||||
await createWorkspace(page, templateName, [], [], gitAuth.webProvider);
|
await createWorkspace(page, templateName, [], [], gitAuth.webProvider);
|
||||||
});
|
});
|
||||||
|
|
||||||
const ghUser: Endpoints["GET /user"]["response"]["data"] = {
|
const ghUser: Endpoints["GET /user"]["response"]["data"] = {
|
||||||
login: "kylecarbs",
|
login: "kylecarbs",
|
||||||
id: 7122116,
|
id: 7122116,
|
||||||
node_id: "MDQ6VXNlcjcxMjIxMTY=",
|
node_id: "MDQ6VXNlcjcxMjIxMTY=",
|
||||||
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
avatar_url: "https://avatars.githubusercontent.com/u/7122116?v=4",
|
||||||
gravatar_id: "",
|
gravatar_id: "",
|
||||||
url: "https://api.github.com/users/kylecarbs",
|
url: "https://api.github.com/users/kylecarbs",
|
||||||
html_url: "https://github.com/kylecarbs",
|
html_url: "https://github.com/kylecarbs",
|
||||||
followers_url: "https://api.github.com/users/kylecarbs/followers",
|
followers_url: "https://api.github.com/users/kylecarbs/followers",
|
||||||
following_url:
|
following_url:
|
||||||
"https://api.github.com/users/kylecarbs/following{/other_user}",
|
"https://api.github.com/users/kylecarbs/following{/other_user}",
|
||||||
gists_url: "https://api.github.com/users/kylecarbs/gists{/gist_id}",
|
gists_url: "https://api.github.com/users/kylecarbs/gists{/gist_id}",
|
||||||
starred_url: "https://api.github.com/users/kylecarbs/starred{/owner}{/repo}",
|
starred_url: "https://api.github.com/users/kylecarbs/starred{/owner}{/repo}",
|
||||||
subscriptions_url: "https://api.github.com/users/kylecarbs/subscriptions",
|
subscriptions_url: "https://api.github.com/users/kylecarbs/subscriptions",
|
||||||
organizations_url: "https://api.github.com/users/kylecarbs/orgs",
|
organizations_url: "https://api.github.com/users/kylecarbs/orgs",
|
||||||
repos_url: "https://api.github.com/users/kylecarbs/repos",
|
repos_url: "https://api.github.com/users/kylecarbs/repos",
|
||||||
events_url: "https://api.github.com/users/kylecarbs/events{/privacy}",
|
events_url: "https://api.github.com/users/kylecarbs/events{/privacy}",
|
||||||
received_events_url: "https://api.github.com/users/kylecarbs/received_events",
|
received_events_url: "https://api.github.com/users/kylecarbs/received_events",
|
||||||
type: "User",
|
type: "User",
|
||||||
site_admin: false,
|
site_admin: false,
|
||||||
name: "Kyle Carberry",
|
name: "Kyle Carberry",
|
||||||
company: "@coder",
|
company: "@coder",
|
||||||
blog: "https://carberry.com",
|
blog: "https://carberry.com",
|
||||||
location: "Austin, TX",
|
location: "Austin, TX",
|
||||||
email: "kyle@carberry.com",
|
email: "kyle@carberry.com",
|
||||||
hireable: null,
|
hireable: null,
|
||||||
bio: "hey there",
|
bio: "hey there",
|
||||||
twitter_username: "kylecarbs",
|
twitter_username: "kylecarbs",
|
||||||
public_repos: 52,
|
public_repos: 52,
|
||||||
public_gists: 9,
|
public_gists: 9,
|
||||||
followers: 208,
|
followers: 208,
|
||||||
following: 31,
|
following: 31,
|
||||||
created_at: "2014-04-01T02:24:41Z",
|
created_at: "2014-04-01T02:24:41Z",
|
||||||
updated_at: "2023-06-26T13:03:09Z",
|
updated_at: "2023-06-26T13:03:09Z",
|
||||||
};
|
};
|
||||||
|
|
||||||
const ghInstall: Endpoints["GET /user/installations"]["response"]["data"] = {
|
const ghInstall: Endpoints["GET /user/installations"]["response"]["data"] = {
|
||||||
installations: [
|
installations: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
access_tokens_url: "",
|
access_tokens_url: "",
|
||||||
account: ghUser,
|
account: ghUser,
|
||||||
app_id: 1,
|
app_id: 1,
|
||||||
app_slug: "coder",
|
app_slug: "coder",
|
||||||
created_at: "2014-04-01T02:24:41Z",
|
created_at: "2014-04-01T02:24:41Z",
|
||||||
events: [],
|
events: [],
|
||||||
html_url: "",
|
html_url: "",
|
||||||
permissions: {},
|
permissions: {},
|
||||||
repositories_url: "",
|
repositories_url: "",
|
||||||
repository_selection: "all",
|
repository_selection: "all",
|
||||||
single_file_name: "",
|
single_file_name: "",
|
||||||
suspended_at: null,
|
suspended_at: null,
|
||||||
suspended_by: null,
|
suspended_by: null,
|
||||||
target_id: 1,
|
target_id: 1,
|
||||||
target_type: "",
|
target_type: "",
|
||||||
updated_at: "2023-06-26T13:03:09Z",
|
updated_at: "2023-06-26T13:03:09Z",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
total_count: 1,
|
total_count: 1,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createGroup,
|
createGroup,
|
||||||
createUser,
|
createUser,
|
||||||
getCurrentOrgId,
|
getCurrentOrgId,
|
||||||
setupApiCalls,
|
setupApiCalls,
|
||||||
} from "../../api";
|
} from "../../api";
|
||||||
import { requiresEnterpriseLicense } from "../../helpers";
|
import { requiresEnterpriseLicense } from "../../helpers";
|
||||||
import { beforeCoderTest } from "../../hooks";
|
import { beforeCoderTest } from "../../hooks";
|
||||||
@@ -11,24 +11,24 @@ import { beforeCoderTest } from "../../hooks";
|
|||||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("add members", async ({ page, baseURL }) => {
|
test("add members", async ({ page, baseURL }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const group = await createGroup(orgId);
|
const group = await createGroup(orgId);
|
||||||
const numberOfMembers = 3;
|
const numberOfMembers = 3;
|
||||||
const users = await Promise.all(
|
const users = await Promise.all(
|
||||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
await page.getByPlaceholder("User email or username").fill(user.username);
|
await page.getByPlaceholder("User email or username").fill(user.username);
|
||||||
await page.getByRole("option", { name: user.email }).click();
|
await page.getByRole("option", { name: user.email }).click();
|
||||||
await page.getByRole("button", { name: "Add user" }).click();
|
await page.getByRole("button", { name: "Add user" }).click();
|
||||||
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
|
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";
|
const DEFAULT_GROUP_NAME = "Everyone";
|
||||||
|
|
||||||
test(`Every user should be automatically added to the default '${DEFAULT_GROUP_NAME}' group upon creation`, async ({
|
test(`Every user should be automatically added to the default '${DEFAULT_GROUP_NAME}' group upon creation`, async ({
|
||||||
page,
|
page,
|
||||||
baseURL,
|
baseURL,
|
||||||
}) => {
|
}) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const numberOfMembers = 3;
|
const numberOfMembers = 3;
|
||||||
const users = await Promise.all(
|
const users = await Promise.all(
|
||||||
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
Array.from({ length: numberOfMembers }, () => createUser(orgId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Groups - Coder");
|
await expect(page).toHaveTitle("Groups - Coder");
|
||||||
|
|
||||||
const groupRow = page.getByRole("row", { name: DEFAULT_GROUP_NAME });
|
const groupRow = page.getByRole("row", { name: DEFAULT_GROUP_NAME });
|
||||||
await groupRow.click();
|
await groupRow.click();
|
||||||
await expect(page).toHaveTitle(`${DEFAULT_GROUP_NAME} - Coder`);
|
await expect(page).toHaveTitle(`${DEFAULT_GROUP_NAME} - Coder`);
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
await expect(page.getByRole("row", { name: user.username })).toBeVisible();
|
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.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("create group", async ({ page, baseURL }) => {
|
test("create group", async ({ page, baseURL }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/groups`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Groups - Coder");
|
await expect(page).toHaveTitle("Groups - Coder");
|
||||||
|
|
||||||
await page.getByText("Create group").click();
|
await page.getByText("Create group").click();
|
||||||
await expect(page).toHaveTitle("Create Group - Coder");
|
await expect(page).toHaveTitle("Create Group - Coder");
|
||||||
|
|
||||||
const name = randomName();
|
const name = randomName();
|
||||||
const groupValues = {
|
const groupValues = {
|
||||||
name: name,
|
name: name,
|
||||||
displayName: `Display Name for ${name}`,
|
displayName: `Display Name for ${name}`,
|
||||||
avatarURL: "/emojis/1f60d.png",
|
avatarURL: "/emojis/1f60d.png",
|
||||||
};
|
};
|
||||||
|
|
||||||
await page.getByLabel("Name", { exact: true }).fill(groupValues.name);
|
await page.getByLabel("Name", { exact: true }).fill(groupValues.name);
|
||||||
await page.getByLabel("Display Name").fill(groupValues.displayName);
|
await page.getByLabel("Display Name").fill(groupValues.displayName);
|
||||||
await page.getByLabel("Avatar URL").fill(groupValues.avatarURL);
|
await page.getByLabel("Avatar URL").fill(groupValues.avatarURL);
|
||||||
await page.getByRole("button", { name: "Submit" }).click();
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`);
|
await expect(page).toHaveTitle(`${groupValues.displayName} - Coder`);
|
||||||
await expect(page.getByText(groupValues.displayName)).toBeVisible();
|
await expect(page.getByText(groupValues.displayName)).toBeVisible();
|
||||||
await expect(page.getByText("No members yet")).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.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("navigate to group page", async ({ page, baseURL }) => {
|
test("navigate to group page", async ({ page, baseURL }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const group = await createGroup(orgId);
|
const group = await createGroup(orgId);
|
||||||
|
|
||||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Users - Coder");
|
await expect(page).toHaveTitle("Users - Coder");
|
||||||
|
|
||||||
await page.getByRole("link", { name: "Groups" }).click();
|
await page.getByRole("link", { name: "Groups" }).click();
|
||||||
await expect(page).toHaveTitle("Groups - Coder");
|
await expect(page).toHaveTitle("Groups - Coder");
|
||||||
|
|
||||||
const groupRow = page.getByRole("row", { name: group.display_name });
|
const groupRow = page.getByRole("row", { name: group.display_name });
|
||||||
await groupRow.click();
|
await groupRow.click();
|
||||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ import { beforeCoderTest } from "../../hooks";
|
|||||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("remove group", async ({ page, baseURL }) => {
|
test("remove group", async ({ page, baseURL }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const group = await createGroup(orgId);
|
const group = await createGroup(orgId);
|
||||||
|
|
||||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Delete" }).click();
|
await page.getByRole("button", { name: "Delete" }).click();
|
||||||
const dialog = page.getByTestId("dialog");
|
const dialog = page.getByTestId("dialog");
|
||||||
await dialog.getByLabel("Name of the group to delete").fill(group.name);
|
await dialog.getByLabel("Name of the group to delete").fill(group.name);
|
||||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||||
await expect(page.getByText("Group deleted successfully.")).toBeVisible();
|
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 { expect, test } from "@playwright/test";
|
||||||
import { API } from "api/api";
|
import { API } from "api/api";
|
||||||
import {
|
import {
|
||||||
createGroup,
|
createGroup,
|
||||||
createUser,
|
createUser,
|
||||||
getCurrentOrgId,
|
getCurrentOrgId,
|
||||||
setupApiCalls,
|
setupApiCalls,
|
||||||
} from "../../api";
|
} from "../../api";
|
||||||
import { requiresEnterpriseLicense } from "../../helpers";
|
import { requiresEnterpriseLicense } from "../../helpers";
|
||||||
import { beforeCoderTest } from "../../hooks";
|
import { beforeCoderTest } from "../../hooks";
|
||||||
@@ -12,25 +12,25 @@ import { beforeCoderTest } from "../../hooks";
|
|||||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("remove member", async ({ page, baseURL }) => {
|
test("remove member", async ({ page, baseURL }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const [group, member] = await Promise.all([
|
const [group, member] = await Promise.all([
|
||||||
createGroup(orgId),
|
createGroup(orgId),
|
||||||
createUser(orgId),
|
createUser(orgId),
|
||||||
]);
|
]);
|
||||||
await API.addMember(group.id, member.id);
|
await API.addMember(group.id, member.id);
|
||||||
|
|
||||||
await page.goto(`${baseURL}/groups/${group.name}`, {
|
await page.goto(`${baseURL}/groups/${group.name}`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
await expect(page).toHaveTitle(`${group.display_name} - Coder`);
|
||||||
|
|
||||||
const userRow = page.getByRole("row", { name: member.username });
|
const userRow = page.getByRole("row", { name: member.username });
|
||||||
await userRow.getByRole("button", { name: "More options" }).click();
|
await userRow.getByRole("button", { name: "More options" }).click();
|
||||||
|
|
||||||
const menu = page.locator("#more-options");
|
const menu = page.locator("#more-options");
|
||||||
await menu.getByText("Remove").click({ timeout: 1_000 });
|
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";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
test.beforeEach(async ({ page }) => {
|
test.beforeEach(async ({ page }) => {
|
||||||
await beforeCoderTest(page);
|
await beforeCoderTest(page);
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create and delete organization", async ({ page, baseURL }) => {
|
test("create and delete organization", async ({ page, baseURL }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
|
|
||||||
// Create an organization
|
// Create an organization
|
||||||
await page.goto(`${baseURL}/organizations/new`, {
|
await page.goto(`${baseURL}/organizations/new`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.getByLabel("Name", { exact: true }).fill("floop");
|
await page.getByLabel("Name", { exact: true }).fill("floop");
|
||||||
await page.getByLabel("Display name").fill("Floop");
|
await page.getByLabel("Display name").fill("Floop");
|
||||||
await page.getByLabel("Description").fill("Org description floop");
|
await page.getByLabel("Description").fill("Org description floop");
|
||||||
await page.getByLabel("Icon", { exact: true }).fill("/emojis/1f957.png");
|
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
|
// Expect to be redirected to the new organization
|
||||||
await expectUrl(page).toHavePathName("/organizations/floop");
|
await expectUrl(page).toHavePathName("/organizations/floop");
|
||||||
await expect(page.getByText("Organization created.")).toBeVisible();
|
await expect(page.getByText("Organization created.")).toBeVisible();
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Delete this organization" }).click();
|
await page.getByRole("button", { name: "Delete this organization" }).click();
|
||||||
const dialog = page.getByTestId("dialog");
|
const dialog = page.getByTestId("dialog");
|
||||||
await dialog.getByLabel("Name").fill("floop");
|
await dialog.getByLabel("Name").fill("floop");
|
||||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
await dialog.getByRole("button", { name: "Delete" }).click();
|
||||||
await expect(page.getByText("Organization deleted.")).toBeVisible();
|
await expect(page.getByText("Organization deleted.")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
downloadCoderVersion,
|
downloadCoderVersion,
|
||||||
sshIntoWorkspace,
|
sshIntoWorkspace,
|
||||||
startAgentWithCommand,
|
startAgentWithCommand,
|
||||||
stopAgent,
|
stopAgent,
|
||||||
stopWorkspace,
|
stopWorkspace,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest } from "../hooks";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
@@ -17,48 +17,48 @@ const agentVersion = "v2.12.1";
|
|||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test(`ssh with agent ${agentVersion}`, async ({ 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 token = randomUUID();
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
apply: [
|
||||||
{
|
{
|
||||||
apply: {
|
apply: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
{
|
{
|
||||||
token,
|
token,
|
||||||
order: 0,
|
order: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
const binaryPath = await downloadCoderVersion(agentVersion);
|
const binaryPath = await downloadCoderVersion(agentVersion);
|
||||||
const agent = await startAgentWithCommand(page, token, binaryPath);
|
const agent = await startAgentWithCommand(page, token, binaryPath);
|
||||||
|
|
||||||
const client = await sshIntoWorkspace(page, workspaceName);
|
const client = await sshIntoWorkspace(page, workspaceName);
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
// We just exec a command to be certain the agent is running!
|
// We just exec a command to be certain the agent is running!
|
||||||
client.exec("exit 0", (err, stream) => {
|
client.exec("exit 0", (err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
stream.on("exit", (code) => {
|
stream.on("exit", (code) => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
return reject(new Error(`Command exited with code ${code}`));
|
return reject(new Error(`Command exited with code ${code}`));
|
||||||
}
|
}
|
||||||
client.end();
|
client.end();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await stopWorkspace(page, workspaceName);
|
await stopWorkspace(page, workspaceName);
|
||||||
await stopAgent(agent, false);
|
await stopAgent(agent, false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
downloadCoderVersion,
|
downloadCoderVersion,
|
||||||
sshIntoWorkspace,
|
sshIntoWorkspace,
|
||||||
startAgent,
|
startAgent,
|
||||||
stopAgent,
|
stopAgent,
|
||||||
stopWorkspace,
|
stopWorkspace,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest } from "../hooks";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
@@ -17,46 +17,46 @@ const clientVersion = "v0.27.0";
|
|||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test(`ssh with client ${clientVersion}`, async ({ page }) => {
|
test(`ssh with client ${clientVersion}`, async ({ page }) => {
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
apply: [
|
||||||
{
|
{
|
||||||
apply: {
|
apply: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
{
|
{
|
||||||
token,
|
token,
|
||||||
order: 0,
|
order: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
const agent = await startAgent(page, token);
|
const agent = await startAgent(page, token);
|
||||||
const binaryPath = await downloadCoderVersion(clientVersion);
|
const binaryPath = await downloadCoderVersion(clientVersion);
|
||||||
|
|
||||||
const client = await sshIntoWorkspace(page, workspaceName, binaryPath);
|
const client = await sshIntoWorkspace(page, workspaceName, binaryPath);
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
// We just exec a command to be certain the agent is running!
|
// We just exec a command to be certain the agent is running!
|
||||||
client.exec("exit 0", (err, stream) => {
|
client.exec("exit 0", (err, stream) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
stream.on("exit", (code) => {
|
stream.on("exit", (code) => {
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
return reject(new Error(`Command exited with code ${code}`));
|
return reject(new Error(`Command exited with code ${code}`));
|
||||||
}
|
}
|
||||||
client.end();
|
client.end();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await stopWorkspace(page, workspaceName);
|
await stopWorkspace(page, workspaceName);
|
||||||
await stopAgent(agent);
|
await stopAgent(agent);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ import { beforeCoderTest } from "../../hooks";
|
|||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("list templates", async ({ page, baseURL }) => {
|
test("list templates", async ({ page, baseURL }) => {
|
||||||
await page.goto(`${baseURL}/templates`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/templates`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Templates - Coder");
|
await expect(page).toHaveTitle("Templates - Coder");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,40 +6,40 @@ import { beforeCoderTest } from "../../hooks";
|
|||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("update template schedule settings without override other settings", async ({
|
test("update template schedule settings without override other settings", async ({
|
||||||
page,
|
page,
|
||||||
baseURL,
|
baseURL,
|
||||||
}) => {
|
}) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const templateVersion = await API.createTemplateVersion(orgId, {
|
const templateVersion = await API.createTemplateVersion(orgId, {
|
||||||
storage_method: "file" as const,
|
storage_method: "file" as const,
|
||||||
provisioner: "echo",
|
provisioner: "echo",
|
||||||
user_variable_values: [],
|
user_variable_values: [],
|
||||||
example_id: "docker",
|
example_id: "docker",
|
||||||
tags: {},
|
tags: {},
|
||||||
});
|
});
|
||||||
const template = await API.createTemplate(orgId, {
|
const template = await API.createTemplate(orgId, {
|
||||||
name: "test-template",
|
name: "test-template",
|
||||||
display_name: "Test Template",
|
display_name: "Test Template",
|
||||||
template_version_id: templateVersion.id,
|
template_version_id: templateVersion.id,
|
||||||
disable_everyone_group_access: false,
|
disable_everyone_group_access: false,
|
||||||
require_active_version: true,
|
require_active_version: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto(`${baseURL}/templates/${template.name}/settings/schedule`, {
|
await page.goto(`${baseURL}/templates/${template.name}/settings/schedule`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await page.getByLabel("Default autostop (hours)").fill("48");
|
await page.getByLabel("Default autostop (hours)").fill("48");
|
||||||
await page.getByRole("button", { name: "Submit" }).click();
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
await expect(page.getByText("Template updated successfully")).toBeVisible();
|
await expect(page.getByText("Template updated successfully")).toBeVisible();
|
||||||
|
|
||||||
const updatedTemplate = await API.getTemplate(template.id);
|
const updatedTemplate = await API.getTemplate(template.id);
|
||||||
// Validate that the template data remains consistent, with the exception of
|
// Validate that the template data remains consistent, with the exception of
|
||||||
// the 'default_ttl_ms' field (updated during the test) and the 'updated at'
|
// the 'default_ttl_ms' field (updated during the test) and the 'updated at'
|
||||||
// field (automatically updated by the backend).
|
// field (automatically updated by the backend).
|
||||||
expect({
|
expect({
|
||||||
...template,
|
...template,
|
||||||
default_ttl_ms: 48 * 60 * 60 * 1000,
|
default_ttl_ms: 48 * 60 * 60 * 1000,
|
||||||
updated_at: updatedTemplate.updated_at,
|
updated_at: updatedTemplate.updated_at,
|
||||||
}).toStrictEqual(updatedTemplate);
|
}).toStrictEqual(updatedTemplate);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,73 +1,73 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { expectUrl } from "../expectUrl";
|
import { expectUrl } from "../expectUrl";
|
||||||
import {
|
import {
|
||||||
createGroup,
|
createGroup,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
requiresEnterpriseLicense,
|
requiresEnterpriseLicense,
|
||||||
updateTemplateSettings,
|
updateTemplateSettings,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest } from "../hooks";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("template update with new name redirects on successful submit", async ({
|
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, {
|
await updateTemplateSettings(page, templateName, {
|
||||||
name: "new-name",
|
name: "new-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("add and remove a group", async ({ page }) => {
|
test("add and remove a group", async ({ page }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
|
|
||||||
const templateName = await createTemplate(page);
|
const templateName = await createTemplate(page);
|
||||||
const groupName = await createGroup(page);
|
const groupName = await createGroup(page);
|
||||||
|
|
||||||
await page.goto(`/templates/${templateName}/settings/permissions`, {
|
await page.goto(`/templates/${templateName}/settings/permissions`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await expectUrl(page).toHavePathName(
|
await expectUrl(page).toHavePathName(
|
||||||
`/templates/${templateName}/settings/permissions`,
|
`/templates/${templateName}/settings/permissions`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Type the first half of the group name
|
// Type the first half of the group name
|
||||||
await page
|
await page
|
||||||
.getByPlaceholder("Search for user or group", { exact: true })
|
.getByPlaceholder("Search for user or group", { exact: true })
|
||||||
.fill(groupName.slice(0, 4));
|
.fill(groupName.slice(0, 4));
|
||||||
|
|
||||||
// Select the group from the list and add it
|
// Select the group from the list and add it
|
||||||
await page.getByText(groupName).click();
|
await page.getByText(groupName).click();
|
||||||
await page.getByText("Add member").click();
|
await page.getByText("Add member").click();
|
||||||
const row = page.locator(".MuiTableRow-root", { hasText: groupName });
|
const row = page.locator(".MuiTableRow-root", { hasText: groupName });
|
||||||
await expect(row).toBeVisible();
|
await expect(row).toBeVisible();
|
||||||
|
|
||||||
// Now remove the group
|
// Now remove the group
|
||||||
await row.getByLabel("More options").click();
|
await row.getByLabel("More options").click();
|
||||||
await page.getByText("Remove").click();
|
await page.getByText("Remove").click();
|
||||||
await expect(page.getByText("Group removed successfully!")).toBeVisible();
|
await expect(page.getByText("Group removed successfully!")).toBeVisible();
|
||||||
await expect(row).not.toBeVisible();
|
await expect(row).not.toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("require latest version", async ({ page }) => {
|
test("require latest version", async ({ page }) => {
|
||||||
requiresEnterpriseLicense();
|
requiresEnterpriseLicense();
|
||||||
|
|
||||||
const templateName = await createTemplate(page);
|
const templateName = await createTemplate(page);
|
||||||
|
|
||||||
await page.goto(`/templates/${templateName}/settings`, {
|
await page.goto(`/templates/${templateName}/settings`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`);
|
await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`);
|
||||||
let checkbox = await page.waitForSelector("#require_active_version");
|
let checkbox = await page.waitForSelector("#require_active_version");
|
||||||
await checkbox.click();
|
await checkbox.click();
|
||||||
await page.getByTestId("form-submit").click();
|
await page.getByTestId("form-submit").click();
|
||||||
|
|
||||||
await page.goto(`/templates/${templateName}/settings`, {
|
await page.goto(`/templates/${templateName}/settings`, {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
});
|
});
|
||||||
checkbox = await page.waitForSelector("#require_active_version");
|
checkbox = await page.waitForSelector("#require_active_version");
|
||||||
await checkbox.scrollIntoViewIfNeeded();
|
await checkbox.scrollIntoViewIfNeeded();
|
||||||
expect(await checkbox.isChecked()).toBe(true);
|
expect(await checkbox.isChecked()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,63 +5,63 @@ import { beforeCoderTest } from "../../hooks";
|
|||||||
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
test.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("create user with password", async ({ page, baseURL }) => {
|
test("create user with password", async ({ page, baseURL }) => {
|
||||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Users - Coder");
|
await expect(page).toHaveTitle("Users - Coder");
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Create user" }).click();
|
await page.getByRole("button", { name: "Create user" }).click();
|
||||||
await expect(page).toHaveTitle("Create User - Coder");
|
await expect(page).toHaveTitle("Create User - Coder");
|
||||||
|
|
||||||
const name = randomName();
|
const name = randomName();
|
||||||
const userValues = {
|
const userValues = {
|
||||||
username: name,
|
username: name,
|
||||||
name: name,
|
name: name,
|
||||||
email: `${name}@coder.com`,
|
email: `${name}@coder.com`,
|
||||||
loginType: "password",
|
loginType: "password",
|
||||||
password: "s3cure&password!",
|
password: "s3cure&password!",
|
||||||
};
|
};
|
||||||
|
|
||||||
await page.getByLabel("Username").fill(userValues.username);
|
await page.getByLabel("Username").fill(userValues.username);
|
||||||
await page.getByLabel("Full name").fill(userValues.username);
|
await page.getByLabel("Full name").fill(userValues.username);
|
||||||
await page.getByLabel("Email").fill(userValues.email);
|
await page.getByLabel("Email").fill(userValues.email);
|
||||||
await page.getByLabel("Login Type").click();
|
await page.getByLabel("Login Type").click();
|
||||||
await page.getByRole("option", { name: "Password", exact: false }).click();
|
await page.getByRole("option", { name: "Password", exact: false }).click();
|
||||||
// Using input[name=password] due to the select element utilizing 'password'
|
// Using input[name=password] due to the select element utilizing 'password'
|
||||||
// as the label for the currently active option.
|
// as the label for the currently active option.
|
||||||
const passwordField = page.locator("input[name=password]");
|
const passwordField = page.locator("input[name=password]");
|
||||||
await passwordField.fill(userValues.password);
|
await passwordField.fill(userValues.password);
|
||||||
await page.getByRole("button", { name: "Create user" }).click();
|
await page.getByRole("button", { name: "Create user" }).click();
|
||||||
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
||||||
|
|
||||||
await expect(page).toHaveTitle("Users - Coder");
|
await expect(page).toHaveTitle("Users - Coder");
|
||||||
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create user without full name is optional", async ({ page, baseURL }) => {
|
test("create user without full name is optional", async ({ page, baseURL }) => {
|
||||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Users - Coder");
|
await expect(page).toHaveTitle("Users - Coder");
|
||||||
|
|
||||||
await page.getByRole("button", { name: "Create user" }).click();
|
await page.getByRole("button", { name: "Create user" }).click();
|
||||||
await expect(page).toHaveTitle("Create User - Coder");
|
await expect(page).toHaveTitle("Create User - Coder");
|
||||||
|
|
||||||
const name = randomName();
|
const name = randomName();
|
||||||
const userValues = {
|
const userValues = {
|
||||||
username: name,
|
username: name,
|
||||||
email: `${name}@coder.com`,
|
email: `${name}@coder.com`,
|
||||||
loginType: "password",
|
loginType: "password",
|
||||||
password: "s3cure&password!",
|
password: "s3cure&password!",
|
||||||
};
|
};
|
||||||
|
|
||||||
await page.getByLabel("Username").fill(userValues.username);
|
await page.getByLabel("Username").fill(userValues.username);
|
||||||
await page.getByLabel("Email").fill(userValues.email);
|
await page.getByLabel("Email").fill(userValues.email);
|
||||||
await page.getByLabel("Login Type").click();
|
await page.getByLabel("Login Type").click();
|
||||||
await page.getByRole("option", { name: "Password", exact: false }).click();
|
await page.getByRole("option", { name: "Password", exact: false }).click();
|
||||||
// Using input[name=password] due to the select element utilizing 'password'
|
// Using input[name=password] due to the select element utilizing 'password'
|
||||||
// as the label for the currently active option.
|
// as the label for the currently active option.
|
||||||
const passwordField = page.locator("input[name=password]");
|
const passwordField = page.locator("input[name=password]");
|
||||||
await passwordField.fill(userValues.password);
|
await passwordField.fill(userValues.password);
|
||||||
await page.getByRole("button", { name: "Create user" }).click();
|
await page.getByRole("button", { name: "Create user" }).click();
|
||||||
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
await expect(page.getByText("Successfully created user.")).toBeVisible();
|
||||||
|
|
||||||
await expect(page).toHaveTitle("Users - Coder");
|
await expect(page).toHaveTitle("Users - Coder");
|
||||||
await expect(page.locator("tr", { hasText: userValues.email })).toBeVisible();
|
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.beforeEach(async ({ page }) => await beforeCoderTest(page));
|
||||||
|
|
||||||
test("remove user", async ({ page, baseURL }) => {
|
test("remove user", async ({ page, baseURL }) => {
|
||||||
await setupApiCalls(page);
|
await setupApiCalls(page);
|
||||||
const orgId = await getCurrentOrgId();
|
const orgId = await getCurrentOrgId();
|
||||||
const user = await createUser(orgId);
|
const user = await createUser(orgId);
|
||||||
|
|
||||||
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
await page.goto(`${baseURL}/users`, { waitUntil: "domcontentloaded" });
|
||||||
await expect(page).toHaveTitle("Users - Coder");
|
await expect(page).toHaveTitle("Users - Coder");
|
||||||
|
|
||||||
const userRow = page.getByRole("row", { name: user.email });
|
const userRow = page.getByRole("row", { name: user.email });
|
||||||
await userRow.getByRole("button", { name: "More options" }).click();
|
await userRow.getByRole("button", { name: "More options" }).click();
|
||||||
const menu = page.locator("#more-options");
|
const menu = page.locator("#more-options");
|
||||||
await menu.getByText("Delete").click();
|
await menu.getByText("Delete").click();
|
||||||
|
|
||||||
const dialog = page.getByTestId("dialog");
|
const dialog = page.getByTestId("dialog");
|
||||||
await dialog.getByLabel("Name of the user to delete").fill(user.username);
|
await dialog.getByLabel("Name of the user to delete").fill(user.username);
|
||||||
await dialog.getByRole("button", { name: "Delete" }).click();
|
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 { randomUUID } from "node:crypto";
|
||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
openTerminalWindow,
|
openTerminalWindow,
|
||||||
startAgent,
|
startAgent,
|
||||||
stopAgent,
|
stopAgent,
|
||||||
} from "../helpers";
|
} from "../helpers";
|
||||||
import { beforeCoderTest } from "../hooks";
|
import { beforeCoderTest } from "../hooks";
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("web terminal", async ({ context, page }) => {
|
test("web terminal", async ({ context, page }) => {
|
||||||
const token = randomUUID();
|
const token = randomUUID();
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
apply: [
|
||||||
{
|
{
|
||||||
apply: {
|
apply: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
agents: [
|
agents: [
|
||||||
{
|
{
|
||||||
token,
|
token,
|
||||||
displayApps: {
|
displayApps: {
|
||||||
webTerminal: true,
|
webTerminal: true,
|
||||||
},
|
},
|
||||||
order: 0,
|
order: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
const agent = await startAgent(page, token);
|
const agent = await startAgent(page, token);
|
||||||
const terminal = await openTerminalWindow(page, context, workspaceName);
|
const terminal = await openTerminalWindow(page, context, workspaceName);
|
||||||
|
|
||||||
await terminal.waitForSelector("div.xterm-rows", {
|
await terminal.waitForSelector("div.xterm-rows", {
|
||||||
state: "visible",
|
state: "visible",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Workaround: delay next steps as "div.xterm-rows" can be recreated/reattached
|
// Workaround: delay next steps as "div.xterm-rows" can be recreated/reattached
|
||||||
// after a couple of milliseconds.
|
// after a couple of milliseconds.
|
||||||
await terminal.waitForTimeout(2000);
|
await terminal.waitForTimeout(2000);
|
||||||
|
|
||||||
// Ensure that we can type in it
|
// Ensure that we can type in it
|
||||||
await terminal.keyboard.type("echo he${justabreak}llo123456");
|
await terminal.keyboard.type("echo he${justabreak}llo123456");
|
||||||
await terminal.keyboard.press("Enter");
|
await terminal.keyboard.press("Enter");
|
||||||
|
|
||||||
// Check if "echo" command was executed
|
// 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-catch is used temporarily to find the root cause: https://github.com/coder/coder/actions/runs/6176958762/job/16767089943
|
||||||
try {
|
try {
|
||||||
await terminal.waitForSelector(
|
await terminal.waitForSelector(
|
||||||
'div.xterm-rows span:text-matches("hello123456")',
|
'div.xterm-rows span:text-matches("hello123456")',
|
||||||
{
|
{
|
||||||
state: "visible",
|
state: "visible",
|
||||||
timeout: 10 * 1000,
|
timeout: 10 * 1000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const pageContent = await terminal.content();
|
const pageContent = await terminal.content();
|
||||||
// eslint-disable-next-line no-console -- Let's see what is inside of xterm-rows
|
// eslint-disable-next-line no-console -- Let's see what is inside of xterm-rows
|
||||||
console.log("Unable to find echoed text:", pageContent);
|
console.log("Unable to find echoed text:", pageContent);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
await stopAgent(agent);
|
await stopAgent(agent);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,65 +1,65 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import { username } from "../../constants";
|
import { username } from "../../constants";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
echoResponsesWithParameters,
|
echoResponsesWithParameters,
|
||||||
} from "../../helpers";
|
} from "../../helpers";
|
||||||
import { emptyParameter } from "../../parameters";
|
import { emptyParameter } from "../../parameters";
|
||||||
import type { RichParameter } from "../../provisionerGenerated";
|
import type { RichParameter } from "../../provisionerGenerated";
|
||||||
|
|
||||||
test("create workspace in auto mode", async ({ page }) => {
|
test("create workspace in auto mode", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
{ ...emptyParameter, name: "repo", type: "string" },
|
{ ...emptyParameter, name: "repo", type: "string" },
|
||||||
];
|
];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const name = "test-workspace";
|
const name = "test-workspace";
|
||||||
await page.goto(
|
await page.goto(
|
||||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=${name}`,
|
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=${name}`,
|
||||||
{
|
{
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await expect(page).toHaveTitle(`${username}/${name} - Coder`);
|
await expect(page).toHaveTitle(`${username}/${name} - Coder`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("use an existing workspace that matches the `match` parameter instead of creating a new one", async ({
|
test("use an existing workspace that matches the `match` parameter instead of creating a new one", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
{ ...emptyParameter, name: "repo", type: "string" },
|
{ ...emptyParameter, name: "repo", type: "string" },
|
||||||
];
|
];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const prevWorkspace = await createWorkspace(page, template);
|
const prevWorkspace = await createWorkspace(page, template);
|
||||||
await page.goto(
|
await page.goto(
|
||||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`,
|
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=name:${prevWorkspace}`,
|
||||||
{
|
{
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await expect(page).toHaveTitle(`${username}/${prevWorkspace} - Coder`);
|
await expect(page).toHaveTitle(`${username}/${prevWorkspace} - Coder`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("show error if `match` parameter is invalid", async ({ page }) => {
|
test("show error if `match` parameter is invalid", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
{ ...emptyParameter, name: "repo", type: "string" },
|
{ ...emptyParameter, name: "repo", type: "string" },
|
||||||
];
|
];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const prevWorkspace = await createWorkspace(page, template);
|
const prevWorkspace = await createWorkspace(page, template);
|
||||||
await page.goto(
|
await page.goto(
|
||||||
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`,
|
`/templates/${template}/workspace?mode=auto¶m.repo=example&name=new-name&match=not-valid-query:${prevWorkspace}`,
|
||||||
{
|
{
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
await expect(page.getByText("Invalid match value")).toBeVisible();
|
await expect(page.getByText("Invalid match value")).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,191 +1,191 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
StarterTemplates,
|
StarterTemplates,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
echoResponsesWithParameters,
|
echoResponsesWithParameters,
|
||||||
openTerminalWindow,
|
openTerminalWindow,
|
||||||
requireTerraformProvisioner,
|
requireTerraformProvisioner,
|
||||||
verifyParameters,
|
verifyParameters,
|
||||||
} from "../../helpers";
|
} from "../../helpers";
|
||||||
import { beforeCoderTest } from "../../hooks";
|
import { beforeCoderTest } from "../../hooks";
|
||||||
import {
|
import {
|
||||||
fifthParameter,
|
fifthParameter,
|
||||||
firstParameter,
|
firstParameter,
|
||||||
fourthParameter,
|
fourthParameter,
|
||||||
randParamName,
|
randParamName,
|
||||||
secondParameter,
|
secondParameter,
|
||||||
seventhParameter,
|
seventhParameter,
|
||||||
sixthParameter,
|
sixthParameter,
|
||||||
thirdParameter,
|
thirdParameter,
|
||||||
} from "../../parameters";
|
} from "../../parameters";
|
||||||
import type { RichParameter } from "../../provisionerGenerated";
|
import type { RichParameter } from "../../provisionerGenerated";
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("create workspace", async ({ page }) => {
|
test("create workspace", async ({ page }) => {
|
||||||
const template = await createTemplate(page, {
|
const template = await createTemplate(page, {
|
||||||
apply: [
|
apply: [
|
||||||
{
|
{
|
||||||
apply: {
|
apply: {
|
||||||
resources: [
|
resources: [
|
||||||
{
|
{
|
||||||
name: "example",
|
name: "example",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
await createWorkspace(page, template);
|
await createWorkspace(page, template);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create workspace with default immutable parameters", async ({ page }) => {
|
test("create workspace with default immutable parameters", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
secondParameter,
|
secondParameter,
|
||||||
fourthParameter,
|
fourthParameter,
|
||||||
fifthParameter,
|
fifthParameter,
|
||||||
];
|
];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||||
{ name: fifthParameter.name, value: fifthParameter.defaultValue },
|
{ name: fifthParameter.name, value: fifthParameter.defaultValue },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create workspace with default mutable parameters", async ({ page }) => {
|
test("create workspace with default mutable parameters", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [firstParameter, thirdParameter];
|
const richParameters: RichParameter[] = [firstParameter, thirdParameter];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: thirdParameter.name, value: thirdParameter.defaultValue },
|
{ name: thirdParameter.name, value: thirdParameter.defaultValue },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create workspace with default and required parameters", async ({
|
test("create workspace with default and required parameters", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
secondParameter,
|
secondParameter,
|
||||||
fourthParameter,
|
fourthParameter,
|
||||||
sixthParameter,
|
sixthParameter,
|
||||||
seventhParameter,
|
seventhParameter,
|
||||||
];
|
];
|
||||||
const buildParameters = [
|
const buildParameters = [
|
||||||
{ name: sixthParameter.name, value: "12345" },
|
{ name: sixthParameter.name, value: "12345" },
|
||||||
{ name: seventhParameter.name, value: "abcdef" },
|
{ name: seventhParameter.name, value: "abcdef" },
|
||||||
];
|
];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const workspaceName = await createWorkspace(
|
const workspaceName = await createWorkspace(
|
||||||
page,
|
page,
|
||||||
template,
|
template,
|
||||||
richParameters,
|
richParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
);
|
);
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
// user values:
|
// user values:
|
||||||
...buildParameters,
|
...buildParameters,
|
||||||
// default values:
|
// default values:
|
||||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||||
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
{ name: fourthParameter.name, value: fourthParameter.defaultValue },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create workspace and overwrite default parameters", async ({ page }) => {
|
test("create workspace and overwrite default parameters", async ({ page }) => {
|
||||||
// We use randParamName to prevent the new values from corrupting user_history
|
// We use randParamName to prevent the new values from corrupting user_history
|
||||||
// and thus affecting other tests.
|
// and thus affecting other tests.
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
randParamName(secondParameter),
|
randParamName(secondParameter),
|
||||||
randParamName(fourthParameter),
|
randParamName(fourthParameter),
|
||||||
];
|
];
|
||||||
|
|
||||||
const buildParameters = [
|
const buildParameters = [
|
||||||
{ name: richParameters[0].name, value: "AAAAA" },
|
{ name: richParameters[0].name, value: "AAAAA" },
|
||||||
{ name: richParameters[1].name, value: "false" },
|
{ name: richParameters[1].name, value: "false" },
|
||||||
];
|
];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceName = await createWorkspace(
|
const workspaceName = await createWorkspace(
|
||||||
page,
|
page,
|
||||||
template,
|
template,
|
||||||
richParameters,
|
richParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
);
|
);
|
||||||
await verifyParameters(page, workspaceName, richParameters, buildParameters);
|
await verifyParameters(page, workspaceName, richParameters, buildParameters);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create workspace with disable_param search params", async ({ page }) => {
|
test("create workspace with disable_param search params", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [
|
const richParameters: RichParameter[] = [
|
||||||
firstParameter, // mutable
|
firstParameter, // mutable
|
||||||
secondParameter, //immutable
|
secondParameter, //immutable
|
||||||
];
|
];
|
||||||
|
|
||||||
const templateName = await createTemplate(
|
const templateName = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.goto(
|
await page.goto(
|
||||||
`/templates/${templateName}/workspace?disable_params=first_parameter,second_parameter`,
|
`/templates/${templateName}/workspace?disable_params=first_parameter,second_parameter`,
|
||||||
{
|
{
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(page.getByLabel(/First parameter/i)).toBeDisabled();
|
await expect(page.getByLabel(/First parameter/i)).toBeDisabled();
|
||||||
await expect(page.getByLabel(/Second parameter/i)).toBeDisabled();
|
await expect(page.getByLabel(/Second parameter/i)).toBeDisabled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("create docker workspace", async ({ context, page }) => {
|
test("create docker workspace", async ({ context, page }) => {
|
||||||
test.skip(
|
test.skip(
|
||||||
true,
|
true,
|
||||||
"creating docker containers is currently leaky. They are not cleaned up when the tests are over.",
|
"creating docker containers is currently leaky. They are not cleaned up when the tests are over.",
|
||||||
);
|
);
|
||||||
requireTerraformProvisioner();
|
requireTerraformProvisioner();
|
||||||
const template = await createTemplate(page, StarterTemplates.STARTER_DOCKER);
|
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.
|
// The workspace agents must be ready before we try to interact with the workspace.
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
`//div[@role="status"][@data-testid="agent-status-ready"]`,
|
`//div[@role="status"][@data-testid="agent-status-ready"]`,
|
||||||
{
|
{
|
||||||
state: "visible",
|
state: "visible",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wait for the terminal button to be visible, and click it.
|
// Wait for the terminal button to be visible, and click it.
|
||||||
const terminalButton =
|
const terminalButton =
|
||||||
"//a[@data-testid='terminal'][normalize-space()='Terminal']";
|
"//a[@data-testid='terminal'][normalize-space()='Terminal']";
|
||||||
await page.waitForSelector(terminalButton, {
|
await page.waitForSelector(terminalButton, {
|
||||||
state: "visible",
|
state: "visible",
|
||||||
});
|
});
|
||||||
|
|
||||||
const terminal = await openTerminalWindow(
|
const terminal = await openTerminalWindow(
|
||||||
page,
|
page,
|
||||||
context,
|
context,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
"main",
|
"main",
|
||||||
);
|
);
|
||||||
await terminal.waitForSelector(
|
await terminal.waitForSelector(
|
||||||
`//textarea[contains(@class,"xterm-helper-textarea")]`,
|
`//textarea[contains(@class,"xterm-helper-textarea")]`,
|
||||||
{
|
{
|
||||||
state: "visible",
|
state: "visible",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
buildWorkspaceWithParameters,
|
buildWorkspaceWithParameters,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
echoResponsesWithParameters,
|
echoResponsesWithParameters,
|
||||||
verifyParameters,
|
verifyParameters,
|
||||||
} from "../../helpers";
|
} from "../../helpers";
|
||||||
import { beforeCoderTest } from "../../hooks";
|
import { beforeCoderTest } from "../../hooks";
|
||||||
import { firstBuildOption, secondBuildOption } from "../../parameters";
|
import { firstBuildOption, secondBuildOption } from "../../parameters";
|
||||||
@@ -13,35 +13,35 @@ import type { RichParameter } from "../../provisionerGenerated";
|
|||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("restart workspace with ephemeral parameters", async ({ page }) => {
|
test("restart workspace with ephemeral parameters", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
|
|
||||||
// Verify that build options are default (not selected).
|
// Verify that build options are default (not selected).
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Now, restart the workspace with ephemeral parameters selected.
|
// Now, restart the workspace with ephemeral parameters selected.
|
||||||
const buildParameters = [
|
const buildParameters = [
|
||||||
{ name: richParameters[0].name, value: "AAAAA" },
|
{ name: richParameters[0].name, value: "AAAAA" },
|
||||||
{ name: richParameters[1].name, value: "true" },
|
{ name: richParameters[1].name, value: "true" },
|
||||||
];
|
];
|
||||||
await buildWorkspaceWithParameters(
|
await buildWorkspaceWithParameters(
|
||||||
page,
|
page,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
richParameters,
|
richParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that build options are default (not selected).
|
// Verify that build options are default (not selected).
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
buildWorkspaceWithParameters,
|
buildWorkspaceWithParameters,
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
echoResponsesWithParameters,
|
echoResponsesWithParameters,
|
||||||
stopWorkspace,
|
stopWorkspace,
|
||||||
verifyParameters,
|
verifyParameters,
|
||||||
} from "../../helpers";
|
} from "../../helpers";
|
||||||
import { beforeCoderTest } from "../../hooks";
|
import { beforeCoderTest } from "../../hooks";
|
||||||
import { firstBuildOption, secondBuildOption } from "../../parameters";
|
import { firstBuildOption, secondBuildOption } from "../../parameters";
|
||||||
@@ -14,38 +14,38 @@ import type { RichParameter } from "../../provisionerGenerated";
|
|||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("start workspace with ephemeral parameters", async ({ page }) => {
|
test("start workspace with ephemeral parameters", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
const richParameters: RichParameter[] = [firstBuildOption, secondBuildOption];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
|
|
||||||
// Verify that build options are default (not selected).
|
// Verify that build options are default (not selected).
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Stop the workspace
|
// Stop the workspace
|
||||||
await stopWorkspace(page, workspaceName);
|
await stopWorkspace(page, workspaceName);
|
||||||
|
|
||||||
// Now, start the workspace with ephemeral parameters selected.
|
// Now, start the workspace with ephemeral parameters selected.
|
||||||
const buildParameters = [
|
const buildParameters = [
|
||||||
{ name: richParameters[0].name, value: "AAAAA" },
|
{ name: richParameters[0].name, value: "AAAAA" },
|
||||||
{ name: richParameters[1].name, value: "true" },
|
{ name: richParameters[1].name, value: "true" },
|
||||||
];
|
];
|
||||||
|
|
||||||
await buildWorkspaceWithParameters(
|
await buildWorkspaceWithParameters(
|
||||||
page,
|
page,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
richParameters,
|
richParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that build options are default (not selected).
|
// Verify that build options are default (not selected).
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
{ name: richParameters[0].name, value: firstBuildOption.defaultValue },
|
||||||
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
{ name: richParameters[1].name, value: secondBuildOption.defaultValue },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,132 +1,132 @@
|
|||||||
import { test } from "@playwright/test";
|
import { test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
createTemplate,
|
createTemplate,
|
||||||
createWorkspace,
|
createWorkspace,
|
||||||
echoResponsesWithParameters,
|
echoResponsesWithParameters,
|
||||||
updateTemplate,
|
updateTemplate,
|
||||||
updateWorkspace,
|
updateWorkspace,
|
||||||
updateWorkspaceParameters,
|
updateWorkspaceParameters,
|
||||||
verifyParameters,
|
verifyParameters,
|
||||||
} from "../../helpers";
|
} from "../../helpers";
|
||||||
import { beforeCoderTest } from "../../hooks";
|
import { beforeCoderTest } from "../../hooks";
|
||||||
import {
|
import {
|
||||||
fifthParameter,
|
fifthParameter,
|
||||||
firstParameter,
|
firstParameter,
|
||||||
secondBuildOption,
|
secondBuildOption,
|
||||||
secondParameter,
|
secondParameter,
|
||||||
sixthParameter,
|
sixthParameter,
|
||||||
} from "../../parameters";
|
} from "../../parameters";
|
||||||
import type { RichParameter } from "../../provisionerGenerated";
|
import type { RichParameter } from "../../provisionerGenerated";
|
||||||
|
|
||||||
test.beforeEach(({ page }) => beforeCoderTest(page));
|
test.beforeEach(({ page }) => beforeCoderTest(page));
|
||||||
|
|
||||||
test("update workspace, new optional, immutable parameter added", async ({
|
test("update workspace, new optional, immutable parameter added", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
|
|
||||||
// Verify that parameter values are default.
|
// Verify that parameter values are default.
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Push updated template.
|
// Push updated template.
|
||||||
const updatedRichParameters = [...richParameters, fifthParameter];
|
const updatedRichParameters = [...richParameters, fifthParameter];
|
||||||
await updateTemplate(
|
await updateTemplate(
|
||||||
page,
|
page,
|
||||||
template,
|
template,
|
||||||
echoResponsesWithParameters(updatedRichParameters),
|
echoResponsesWithParameters(updatedRichParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now, update the workspace, and select the value for immutable parameter.
|
// Now, update the workspace, and select the value for immutable parameter.
|
||||||
await updateWorkspace(page, workspaceName, updatedRichParameters, [
|
await updateWorkspace(page, workspaceName, updatedRichParameters, [
|
||||||
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Verify parameter values.
|
// Verify parameter values.
|
||||||
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||||
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
{ name: fifthParameter.name, value: fifthParameter.options[0].value },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("update workspace, new required, mutable parameter added", async ({
|
test("update workspace, new required, mutable parameter added", async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
const richParameters: RichParameter[] = [firstParameter, secondParameter];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
|
|
||||||
// Verify that parameter values are default.
|
// Verify that parameter values are default.
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Push updated template.
|
// Push updated template.
|
||||||
const updatedRichParameters = [...richParameters, sixthParameter];
|
const updatedRichParameters = [...richParameters, sixthParameter];
|
||||||
await updateTemplate(
|
await updateTemplate(
|
||||||
page,
|
page,
|
||||||
template,
|
template,
|
||||||
echoResponsesWithParameters(updatedRichParameters),
|
echoResponsesWithParameters(updatedRichParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now, update the workspace, and provide the parameter value.
|
// Now, update the workspace, and provide the parameter value.
|
||||||
const buildParameters = [{ name: sixthParameter.name, value: "99" }];
|
const buildParameters = [{ name: sixthParameter.name, value: "99" }];
|
||||||
await updateWorkspace(
|
await updateWorkspace(
|
||||||
page,
|
page,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
updatedRichParameters,
|
updatedRichParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify parameter values.
|
// Verify parameter values.
|
||||||
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
await verifyParameters(page, workspaceName, updatedRichParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
{ name: secondParameter.name, value: secondParameter.defaultValue },
|
||||||
...buildParameters,
|
...buildParameters,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("update workspace with ephemeral parameter enabled", async ({ page }) => {
|
test("update workspace with ephemeral parameter enabled", async ({ page }) => {
|
||||||
const richParameters: RichParameter[] = [firstParameter, secondBuildOption];
|
const richParameters: RichParameter[] = [firstParameter, secondBuildOption];
|
||||||
const template = await createTemplate(
|
const template = await createTemplate(
|
||||||
page,
|
page,
|
||||||
echoResponsesWithParameters(richParameters),
|
echoResponsesWithParameters(richParameters),
|
||||||
);
|
);
|
||||||
|
|
||||||
const workspaceName = await createWorkspace(page, template);
|
const workspaceName = await createWorkspace(page, template);
|
||||||
|
|
||||||
// Verify that parameter values are default.
|
// Verify that parameter values are default.
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: secondBuildOption.name, value: secondBuildOption.defaultValue },
|
{ name: secondBuildOption.name, value: secondBuildOption.defaultValue },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Now, update the workspace, and select the value for ephemeral parameter.
|
// Now, update the workspace, and select the value for ephemeral parameter.
|
||||||
const buildParameters = [{ name: secondBuildOption.name, value: "true" }];
|
const buildParameters = [{ name: secondBuildOption.name, value: "true" }];
|
||||||
await updateWorkspaceParameters(
|
await updateWorkspaceParameters(
|
||||||
page,
|
page,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
richParameters,
|
richParameters,
|
||||||
buildParameters,
|
buildParameters,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify that parameter values are default.
|
// Verify that parameter values are default.
|
||||||
await verifyParameters(page, workspaceName, richParameters, [
|
await verifyParameters(page, workspaceName, richParameters, [
|
||||||
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
{ name: firstParameter.name, value: firstParameter.defaultValue },
|
||||||
{ name: secondBuildOption.name, value: secondBuildOption.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