mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: Enable custom support links (#6313)
* backend: support links * frontend: Support links * fmt * test: CODER_SUPPORT_LINKS_0_NAME * Go tests * Use UpdateAppearanceConfig * ui: UpdateAppearanceConfig * fix: fmt * Fix: site * Fix: site tests * fix: fmt * fix * test: check default support links
This commit is contained in:
@@ -570,6 +570,15 @@ func newConfig() *codersdk.DeploymentConfig {
|
||||
Flag: "disable-password-auth",
|
||||
Default: false,
|
||||
},
|
||||
Support: &codersdk.SupportConfig{
|
||||
Links: &codersdk.DeploymentConfigField[[]codersdk.LinkConfig]{
|
||||
Name: "Support links",
|
||||
Usage: "Use custom support links",
|
||||
Flag: "support-links",
|
||||
Default: []codersdk.LinkConfig{},
|
||||
Enterprise: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +658,10 @@ func setConfig(prefix string, vip *viper.Viper, target interface{}) {
|
||||
// Do not bind to CODER_GITAUTH, instead bind to CODER_GITAUTH_0_*, etc.
|
||||
values := readSliceFromViper[codersdk.GitAuthConfig](vip, prefix, value)
|
||||
val.FieldByName("Value").Set(reflect.ValueOf(values))
|
||||
case []codersdk.LinkConfig:
|
||||
// Do not bind to CODER_SUPPORT_LINKS, instead bind to CODER_SUPPORT_LINKS_0_*, etc.
|
||||
values := readSliceFromViper[codersdk.LinkConfig](vip, prefix, value)
|
||||
val.FieldByName("Value").Set(reflect.ValueOf(values))
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported type %T", value))
|
||||
}
|
||||
@@ -824,6 +837,8 @@ func setFlags(prefix string, flagset *pflag.FlagSet, vip *viper.Viper, target in
|
||||
_ = flagset.DurationP(flg, shorthand, vip.GetDuration(prefix), usage)
|
||||
case []string:
|
||||
_ = flagset.StringSliceP(flg, shorthand, vip.GetStringSlice(prefix), usage)
|
||||
case []codersdk.LinkConfig:
|
||||
// Ignore this one!
|
||||
case []codersdk.GitAuthConfig:
|
||||
// Ignore this one!
|
||||
default:
|
||||
|
||||
@@ -222,6 +222,29 @@ func TestConfig(t *testing.T) {
|
||||
Regex: "gitlab.com",
|
||||
}}, config.GitAuth.Value)
|
||||
},
|
||||
}, {
|
||||
Name: "Support links",
|
||||
Env: map[string]string{
|
||||
"CODER_SUPPORT_LINKS_0_NAME": "First link",
|
||||
"CODER_SUPPORT_LINKS_0_TARGET": "http://target-link-1",
|
||||
"CODER_SUPPORT_LINKS_0_ICON": "bug",
|
||||
|
||||
"CODER_SUPPORT_LINKS_1_NAME": "Second link",
|
||||
"CODER_SUPPORT_LINKS_1_TARGET": "http://target-link-2",
|
||||
"CODER_SUPPORT_LINKS_1_ICON": "chat",
|
||||
},
|
||||
Valid: func(config *codersdk.DeploymentConfig) {
|
||||
require.Len(t, config.Support.Links.Value, 2)
|
||||
require.Equal(t, []codersdk.LinkConfig{{
|
||||
Name: "First link",
|
||||
Target: "http://target-link-1",
|
||||
Icon: "bug",
|
||||
}, {
|
||||
Name: "Second link",
|
||||
Target: "http://target-link-2",
|
||||
Icon: "chat",
|
||||
}}, config.Support.Links.Value)
|
||||
},
|
||||
}, {
|
||||
Name: "Wrong env must not break default values",
|
||||
Env: map[string]string{
|
||||
|
||||
Generated
+82
-2
@@ -93,7 +93,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.AppearanceConfig"
|
||||
"$ref": "#/definitions/codersdk.UpdateAppearanceConfig"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -101,7 +101,7 @@ const docTemplate = `{
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.AppearanceConfig"
|
||||
"$ref": "#/definitions/codersdk.UpdateAppearanceConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5379,6 +5379,12 @@ const docTemplate = `{
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
},
|
||||
"support_links": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LinkConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6196,6 +6202,9 @@ const docTemplate = `{
|
||||
"strict_transport_security_options": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-array_string"
|
||||
},
|
||||
"support": {
|
||||
"$ref": "#/definitions/codersdk.SupportConfig"
|
||||
},
|
||||
"swagger": {
|
||||
"$ref": "#/definitions/codersdk.SwaggerConfig"
|
||||
},
|
||||
@@ -6254,6 +6263,44 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.DeploymentConfigField-array_codersdk_LinkConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LinkConfig"
|
||||
}
|
||||
},
|
||||
"enterprise": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"flag": {
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"secret": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shorthand": {
|
||||
"type": "string"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LinkConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.DeploymentConfigField-array_string": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6651,6 +6698,20 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.LinkConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.LogLevel": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -7362,6 +7423,14 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.SupportConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"links": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-array_codersdk_LinkConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.SwaggerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7795,6 +7864,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateAppearanceConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"logo_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateCheckResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Generated
+82
-2
@@ -71,7 +71,7 @@
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.AppearanceConfig"
|
||||
"$ref": "#/definitions/codersdk.UpdateAppearanceConfig"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -79,7 +79,7 @@
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/codersdk.AppearanceConfig"
|
||||
"$ref": "#/definitions/codersdk.UpdateAppearanceConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4760,6 +4760,12 @@
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
},
|
||||
"support_links": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LinkConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5516,6 +5522,9 @@
|
||||
"strict_transport_security_options": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-array_string"
|
||||
},
|
||||
"support": {
|
||||
"$ref": "#/definitions/codersdk.SupportConfig"
|
||||
},
|
||||
"swagger": {
|
||||
"$ref": "#/definitions/codersdk.SwaggerConfig"
|
||||
},
|
||||
@@ -5574,6 +5583,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.DeploymentConfigField-array_codersdk_LinkConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LinkConfig"
|
||||
}
|
||||
},
|
||||
"enterprise": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"flag": {
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"secret": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"shorthand": {
|
||||
"type": "string"
|
||||
},
|
||||
"usage": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.LinkConfig"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.DeploymentConfigField-array_string": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5961,6 +6008,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.LinkConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.LogLevel": {
|
||||
"type": "string",
|
||||
"enum": ["trace", "debug", "info", "warn", "error"],
|
||||
@@ -6608,6 +6669,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.SupportConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"links": {
|
||||
"$ref": "#/definitions/codersdk.DeploymentConfigField-array_codersdk_LinkConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.SwaggerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7011,6 +7080,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateAppearanceConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"logo_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.UpdateCheckResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
+20
-2
@@ -153,6 +153,8 @@ type DeploymentConfig struct {
|
||||
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
|
||||
// DEPRECATED: Use Experiments instead.
|
||||
Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"`
|
||||
|
||||
Support *SupportConfig `json:"support" typescript:",notnull"`
|
||||
}
|
||||
|
||||
type DERP struct {
|
||||
@@ -276,8 +278,18 @@ type DangerousConfig struct {
|
||||
AllowPathAppSiteOwnerAccess *DeploymentConfigField[bool] `json:"allow_path_app_site_owner_access" typescript:",notnull"`
|
||||
}
|
||||
|
||||
type SupportConfig struct {
|
||||
Links *DeploymentConfigField[[]LinkConfig] `json:"links" typescript:",notnull"`
|
||||
}
|
||||
|
||||
type LinkConfig struct {
|
||||
Name string `json:"name"`
|
||||
Target string `json:"target"`
|
||||
Icon string `json:"icon"`
|
||||
}
|
||||
|
||||
type Flaggable interface {
|
||||
string | time.Duration | bool | int | []string | []GitAuthConfig
|
||||
string | time.Duration | bool | int | []string | []GitAuthConfig | []LinkConfig
|
||||
}
|
||||
|
||||
type DeploymentConfigField[T Flaggable] struct {
|
||||
@@ -348,6 +360,12 @@ func (c *Client) DeploymentConfig(ctx context.Context) (DeploymentConfig, error)
|
||||
type AppearanceConfig struct {
|
||||
LogoURL string `json:"logo_url"`
|
||||
ServiceBanner ServiceBannerConfig `json:"service_banner"`
|
||||
SupportLinks []LinkConfig `json:"support_links,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateAppearanceConfig struct {
|
||||
LogoURL string `json:"logo_url"`
|
||||
ServiceBanner ServiceBannerConfig `json:"service_banner"`
|
||||
}
|
||||
|
||||
type ServiceBannerConfig struct {
|
||||
@@ -371,7 +389,7 @@ func (c *Client) Appearance(ctx context.Context) (AppearanceConfig, error) {
|
||||
return cfg, json.NewDecoder(res.Body).Decode(&cfg)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateAppearance(ctx context.Context, appearance AppearanceConfig) error {
|
||||
func (c *Client) UpdateAppearance(ctx context.Context, appearance UpdateAppearanceConfig) error {
|
||||
res, err := c.Request(ctx, http.MethodPut, "/api/v2/appearance", appearance)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+14
-7
@@ -24,7 +24,14 @@ curl -X GET http://coder-server:8080/api/v2/appearance \
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
},
|
||||
"support_links": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -65,9 +72,9 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ---------------------------------------------------------------- | -------- | ------------------------- |
|
||||
| `body` | body | [codersdk.AppearanceConfig](schemas.md#codersdkappearanceconfig) | true | Update appearance request |
|
||||
| Name | In | Type | Required | Description |
|
||||
| ------ | ---- | ---------------------------------------------------------------------------- | -------- | ------------------------- |
|
||||
| `body` | body | [codersdk.UpdateAppearanceConfig](schemas.md#codersdkupdateappearanceconfig) | true | Update appearance request |
|
||||
|
||||
### Example responses
|
||||
|
||||
@@ -86,9 +93,9 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
|
||||
|
||||
### Responses
|
||||
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------- |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.AppearanceConfig](schemas.md#codersdkappearanceconfig) |
|
||||
| Status | Meaning | Description | Schema |
|
||||
| ------ | ------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------- |
|
||||
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.UpdateAppearanceConfig](schemas.md#codersdkupdateappearanceconfig) |
|
||||
|
||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||
|
||||
|
||||
@@ -879,6 +879,31 @@ curl -X GET http://coder-server:8080/api/v2/config/deployment \
|
||||
"usage": "string",
|
||||
"value": ["string"]
|
||||
},
|
||||
"support": {
|
||||
"links": {
|
||||
"default": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
],
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"swagger": {
|
||||
"enable": {
|
||||
"default": true,
|
||||
|
||||
+151
-1
@@ -440,7 +440,14 @@
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
},
|
||||
"support_links": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -450,6 +457,7 @@
|
||||
| ---------------- | ------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||
| `logo_url` | string | false | | |
|
||||
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
|
||||
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
||||
|
||||
## codersdk.AssignableRoles
|
||||
|
||||
@@ -2285,6 +2293,31 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
||||
"usage": "string",
|
||||
"value": ["string"]
|
||||
},
|
||||
"support": {
|
||||
"links": {
|
||||
"default": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
],
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"swagger": {
|
||||
"enable": {
|
||||
"default": true,
|
||||
@@ -2546,6 +2579,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
||||
| `ssh_keygen_algorithm` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | |
|
||||
| `strict_transport_security` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | |
|
||||
| `strict_transport_security_options` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | |
|
||||
| `support` | [codersdk.SupportConfig](#codersdksupportconfig) | false | | |
|
||||
| `swagger` | [codersdk.SwaggerConfig](#codersdkswaggerconfig) | false | | |
|
||||
| `telemetry` | [codersdk.TelemetryConfig](#codersdktelemetryconfig) | false | | |
|
||||
| `tls` | [codersdk.TLSConfig](#codersdktlsconfig) | false | | |
|
||||
@@ -2607,6 +2641,48 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
||||
| `usage` | string | false | | |
|
||||
| `value` | array of [codersdk.GitAuthConfig](#codersdkgitauthconfig) | false | | |
|
||||
|
||||
## codersdk.DeploymentConfigField-array_codersdk_LinkConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"default": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
],
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------ | --------------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `default` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
||||
| `enterprise` | boolean | false | | |
|
||||
| `flag` | string | false | | |
|
||||
| `hidden` | boolean | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `secret` | boolean | false | | |
|
||||
| `shorthand` | string | false | | |
|
||||
| `usage` | string | false | | |
|
||||
| `value` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
||||
|
||||
## codersdk.DeploymentConfigField-array_string
|
||||
|
||||
```json
|
||||
@@ -3043,6 +3119,24 @@ CreateParameterRequest is a structure used to create a new parameter value for a
|
||||
| `uploaded_at` | string | false | | |
|
||||
| `uuid` | string | false | | |
|
||||
|
||||
## codersdk.LinkConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------- | ------ | -------- | ------------ | ----------- |
|
||||
| `icon` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `target` | string | false | | |
|
||||
|
||||
## codersdk.LogLevel
|
||||
|
||||
```json
|
||||
@@ -4120,6 +4214,42 @@ Parameter represents a set value for the scope.
|
||||
| `enabled` | boolean | false | | |
|
||||
| `message` | string | false | | |
|
||||
|
||||
## codersdk.SupportConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"links": {
|
||||
"default": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
],
|
||||
"enterprise": true,
|
||||
"flag": "string",
|
||||
"hidden": true,
|
||||
"name": "string",
|
||||
"secret": true,
|
||||
"shorthand": "string",
|
||||
"usage": "string",
|
||||
"value": [
|
||||
{
|
||||
"icon": "string",
|
||||
"name": "string",
|
||||
"target": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------- | -------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- |
|
||||
| `links` | [codersdk.DeploymentConfigField-array_codersdk_LinkConfig](#codersdkdeploymentconfigfield-array_codersdk_linkconfig) | false | | |
|
||||
|
||||
## codersdk.SwaggerConfig
|
||||
|
||||
```json
|
||||
@@ -4757,6 +4887,26 @@ Parameter represents a set value for the scope.
|
||||
| ---- | ------ | -------- | ------------ | ----------- |
|
||||
| `id` | string | true | | |
|
||||
|
||||
## codersdk.UpdateAppearanceConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"logo_url": "string",
|
||||
"service_banner": {
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ---------------- | ------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||
| `logo_url` | string | false | | |
|
||||
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
|
||||
|
||||
## codersdk.UpdateCheckResponse
|
||||
|
||||
```json
|
||||
|
||||
@@ -15,6 +15,24 @@ import (
|
||||
"github.com/coder/coder/codersdk"
|
||||
)
|
||||
|
||||
var DefaultSupportLinks = []codersdk.LinkConfig{
|
||||
{
|
||||
Name: "Documentation",
|
||||
Target: "https://coder.com/docs/coder-oss",
|
||||
Icon: "docs",
|
||||
},
|
||||
{
|
||||
Name: "Report a bug",
|
||||
Target: "https://github.com/coder/coder/issues/new?labels=needs+grooming&body={CODER_BUILD_INFO}",
|
||||
Icon: "bug",
|
||||
},
|
||||
{
|
||||
Name: "Join the Coder Discord",
|
||||
Target: "https://coder.com/chat?utm_source=coder&utm_medium=coder&utm_campaign=server-footer",
|
||||
Icon: "chat",
|
||||
},
|
||||
}
|
||||
|
||||
// @Summary Get appearance
|
||||
// @ID get-appearance
|
||||
// @Security CoderSessionToken
|
||||
@@ -30,7 +48,9 @@ func (api *API) appearance(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
if !isEntitled {
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.AppearanceConfig{})
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.AppearanceConfig{
|
||||
SupportLinks: DefaultSupportLinks,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,6 +87,12 @@ func (api *API) appearance(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(api.DeploymentConfig.Support.Links.Value) == 0 {
|
||||
cfg.SupportLinks = DefaultSupportLinks
|
||||
} else {
|
||||
cfg.SupportLinks = api.DeploymentConfig.Support.Links.Value
|
||||
}
|
||||
|
||||
httpapi.Write(r.Context(), rw, http.StatusOK, cfg)
|
||||
}
|
||||
|
||||
@@ -87,8 +113,8 @@ func validateHexColor(color string) error {
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Enterprise
|
||||
// @Param request body codersdk.AppearanceConfig true "Update appearance request"
|
||||
// @Success 200 {object} codersdk.AppearanceConfig
|
||||
// @Param request body codersdk.UpdateAppearanceConfig true "Update appearance request"
|
||||
// @Success 200 {object} codersdk.UpdateAppearanceConfig
|
||||
// @Router /appearance [put]
|
||||
func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
@@ -100,7 +126,7 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
var appearance codersdk.AppearanceConfig
|
||||
var appearance codersdk.UpdateAppearanceConfig
|
||||
if !httpapi.Read(ctx, rw, r, &appearance) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/enterprise/coderd"
|
||||
"github.com/coder/coder/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/enterprise/coderd/license"
|
||||
"github.com/coder/coder/testutil"
|
||||
@@ -43,16 +44,20 @@ func TestServiceBanners(t *testing.T) {
|
||||
|
||||
basicUserClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
|
||||
|
||||
uac := codersdk.UpdateAppearanceConfig{
|
||||
ServiceBanner: sb.ServiceBanner,
|
||||
}
|
||||
// Regular user should be unable to set the banner
|
||||
sb.ServiceBanner.Enabled = true
|
||||
err = basicUserClient.UpdateAppearance(ctx, sb)
|
||||
uac.ServiceBanner.Enabled = true
|
||||
|
||||
err = basicUserClient.UpdateAppearance(ctx, uac)
|
||||
require.Error(t, err)
|
||||
var sdkError *codersdk.Error
|
||||
require.True(t, errors.As(err, &sdkError))
|
||||
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
|
||||
|
||||
// But an admin can
|
||||
wantBanner := sb
|
||||
wantBanner := uac
|
||||
wantBanner.ServiceBanner.Enabled = true
|
||||
wantBanner.ServiceBanner.Message = "Hey"
|
||||
wantBanner.ServiceBanner.BackgroundColor = "#00FF00"
|
||||
@@ -60,10 +65,67 @@ func TestServiceBanners(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
gotBanner, err := adminClient.Appearance(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantBanner, gotBanner)
|
||||
gotBanner.SupportLinks = nil // clean "support links" before comparison
|
||||
require.Equal(t, wantBanner.ServiceBanner, gotBanner.ServiceBanner)
|
||||
|
||||
// But even an admin can't give a bad color
|
||||
wantBanner.ServiceBanner.BackgroundColor = "#bad color"
|
||||
err = adminClient.UpdateAppearance(ctx, wantBanner)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCustomSupportLinks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
supportLinks := []codersdk.LinkConfig{
|
||||
{
|
||||
Name: "First link",
|
||||
Target: "http://first-link-1",
|
||||
Icon: "chat",
|
||||
},
|
||||
{
|
||||
Name: "Second link",
|
||||
Target: "http://second-link-2",
|
||||
Icon: "bug",
|
||||
},
|
||||
}
|
||||
cfg := coderdtest.DeploymentConfig(t)
|
||||
cfg.Support = new(codersdk.SupportConfig)
|
||||
cfg.Support.Links = &codersdk.DeploymentConfigField[[]codersdk.LinkConfig]{
|
||||
Value: supportLinks,
|
||||
}
|
||||
|
||||
client := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
DeploymentConfig: cfg,
|
||||
},
|
||||
})
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
coderdenttest.AddLicense(t, client, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAppearance: 1,
|
||||
},
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||
defer cancel()
|
||||
|
||||
appearance, err := client.Appearance(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, supportLinks, appearance.SupportLinks)
|
||||
}
|
||||
|
||||
func TestDefaultSupportLinks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := coderdenttest.New(t, nil)
|
||||
coderdtest.CreateFirstUser(t, client)
|
||||
// Don't need to set the license, as default links are passed without it.
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||
defer cancel()
|
||||
|
||||
appearance, err := client.Appearance(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, coderd.DefaultSupportLinks, appearance.SupportLinks)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface AppHostResponse {
|
||||
export interface AppearanceConfig {
|
||||
readonly logo_url: string
|
||||
readonly service_banner: ServiceBannerConfig
|
||||
readonly support_links?: LinkConfig[]
|
||||
}
|
||||
|
||||
// From codersdk/roles.go
|
||||
@@ -334,6 +335,7 @@ export interface DeploymentConfig {
|
||||
readonly disable_password_auth: DeploymentConfigField<boolean>
|
||||
readonly address: DeploymentConfigField<string>
|
||||
readonly experimental: DeploymentConfigField<boolean>
|
||||
readonly support: SupportConfig
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
@@ -434,6 +436,13 @@ export interface License {
|
||||
readonly claims: Record<string, any>
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface LinkConfig {
|
||||
readonly name: string
|
||||
readonly target: string
|
||||
readonly icon: string
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface LoggingConfig {
|
||||
readonly human: DeploymentConfigField<string>
|
||||
@@ -657,6 +666,11 @@ export interface ServiceBannerConfig {
|
||||
readonly background_color?: string
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface SupportConfig {
|
||||
readonly links: DeploymentConfigField<LinkConfig[]>
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface SwaggerConfig {
|
||||
readonly enable: DeploymentConfigField<boolean>
|
||||
@@ -818,6 +832,12 @@ export interface UpdateActiveTemplateVersion {
|
||||
readonly id: string
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface UpdateAppearanceConfig {
|
||||
readonly logo_url: string
|
||||
readonly service_banner: ServiceBannerConfig
|
||||
}
|
||||
|
||||
// From codersdk/updatecheck.go
|
||||
export interface UpdateCheckResponse {
|
||||
readonly current: boolean
|
||||
@@ -1331,4 +1351,10 @@ export const WorkspaceTransitions: WorkspaceTransition[] = [
|
||||
]
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export type Flaggable = string | number | boolean | string[] | GitAuthConfig[]
|
||||
export type Flaggable =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| string[]
|
||||
| GitAuthConfig[]
|
||||
| LinkConfig[]
|
||||
|
||||
@@ -22,6 +22,7 @@ export const Navbar: FC = () => {
|
||||
user={me}
|
||||
logo_url={appearance.config.logo_url}
|
||||
buildInfo={buildInfo}
|
||||
supportLinks={appearance.config.support_links}
|
||||
onSignOut={onSignOut}
|
||||
canViewAuditLog={canViewAuditLog}
|
||||
canViewDeployment={canViewDeployment}
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface NavbarViewProps {
|
||||
logo_url?: string
|
||||
user?: TypesGen.User
|
||||
buildInfo?: TypesGen.BuildInfoResponse
|
||||
supportLinks?: TypesGen.LinkConfig[]
|
||||
onSignOut: () => void
|
||||
canViewAuditLog: boolean
|
||||
canViewDeployment: boolean
|
||||
@@ -86,6 +87,7 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
|
||||
user,
|
||||
logo_url,
|
||||
buildInfo,
|
||||
supportLinks,
|
||||
onSignOut,
|
||||
canViewAuditLog,
|
||||
canViewDeployment,
|
||||
@@ -147,6 +149,7 @@ export const NavbarView: React.FC<React.PropsWithChildren<NavbarViewProps>> = ({
|
||||
<UserDropdown
|
||||
user={user}
|
||||
buildInfo={buildInfo}
|
||||
supportLinks={supportLinks}
|
||||
onSignOut={onSignOut}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { fireEvent, screen } from "@testing-library/react"
|
||||
import { MockUser } from "../../testHelpers/entities"
|
||||
import { MockSupportLinks, MockUser } from "../../testHelpers/entities"
|
||||
import { render } from "../../testHelpers/renderHelpers"
|
||||
import { Language } from "../UserDropdownContent/UserDropdownContent"
|
||||
import { UserDropdown, UserDropdownProps } from "./UsersDropdown"
|
||||
@@ -8,6 +8,7 @@ const renderAndClick = async (props: Partial<UserDropdownProps> = {}) => {
|
||||
render(
|
||||
<UserDropdown
|
||||
user={props.user ?? MockUser}
|
||||
supportLinks={MockSupportLinks}
|
||||
onSignOut={props.onSignOut ?? jest.fn()}
|
||||
/>,
|
||||
)
|
||||
@@ -20,7 +21,9 @@ describe("UserDropdown", () => {
|
||||
it("opens the menu", async () => {
|
||||
await renderAndClick()
|
||||
expect(screen.getByText(Language.accountLabel)).toBeDefined()
|
||||
expect(screen.getByText(Language.docsLabel)).toBeDefined()
|
||||
expect(screen.getByText(MockSupportLinks[0].name)).toBeDefined()
|
||||
expect(screen.getByText(MockSupportLinks[1].name)).toBeDefined()
|
||||
expect(screen.getByText(MockSupportLinks[2].name)).toBeDefined()
|
||||
expect(screen.getByText(Language.signOutLabel)).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,12 +13,14 @@ import { UserDropdownContent } from "../UserDropdownContent/UserDropdownContent"
|
||||
export interface UserDropdownProps {
|
||||
user: TypesGen.User
|
||||
buildInfo?: TypesGen.BuildInfoResponse
|
||||
supportLinks?: TypesGen.LinkConfig[]
|
||||
onSignOut: () => void
|
||||
}
|
||||
|
||||
export const UserDropdown: FC<PropsWithChildren<UserDropdownProps>> = ({
|
||||
buildInfo,
|
||||
user,
|
||||
supportLinks,
|
||||
onSignOut,
|
||||
}: UserDropdownProps) => {
|
||||
const styles = useStyles()
|
||||
@@ -69,6 +71,7 @@ export const UserDropdown: FC<PropsWithChildren<UserDropdownProps>> = ({
|
||||
<UserDropdownContent
|
||||
user={user}
|
||||
buildInfo={buildInfo}
|
||||
supportLinks={supportLinks}
|
||||
onPopoverClose={onPopoverClose}
|
||||
onSignOut={onSignOut}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { screen } from "@testing-library/react"
|
||||
import { MockBuildInfo, MockUser } from "../../testHelpers/entities"
|
||||
import {
|
||||
MockBuildInfo,
|
||||
MockSupportLinks,
|
||||
MockUser,
|
||||
} from "../../testHelpers/entities"
|
||||
import { render } from "../../testHelpers/renderHelpers"
|
||||
import { Language, UserDropdownContent } from "./UserDropdownContent"
|
||||
|
||||
@@ -21,16 +25,23 @@ describe("UserDropdownContent", () => {
|
||||
<UserDropdownContent
|
||||
user={MockUser}
|
||||
buildInfo={MockBuildInfo}
|
||||
supportLinks={MockSupportLinks}
|
||||
onSignOut={jest.fn()}
|
||||
onPopoverClose={jest.fn()}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByText(Language.accountLabel)).toBeDefined()
|
||||
expect(screen.getByText(Language.docsLabel)).toBeDefined()
|
||||
expect(screen.getByText(Language.signOutLabel)).toBeDefined()
|
||||
expect(screen.getByText(Language.bugLabel)).toBeDefined()
|
||||
expect(screen.getByText(Language.discordLabel)).toBeDefined()
|
||||
expect(screen.getByText(Language.copyrightText)).toBeDefined()
|
||||
expect(screen.getByText(MockSupportLinks[0].name)).toBeDefined()
|
||||
expect(screen.getByText(MockSupportLinks[1].name)).toBeDefined()
|
||||
expect(screen.getByText(MockSupportLinks[2].name)).toBeDefined()
|
||||
expect(
|
||||
screen.getByText(MockSupportLinks[2].name).closest("a"),
|
||||
).toHaveAttribute(
|
||||
"href",
|
||||
"https://github.com/coder/coder/issues/new?labels=needs+grooming&body=Version%3A%20%5B%60v99.999.9999%2Bc9cdf14%60%5D(file%3A%2F%2F%2Fmock-url)",
|
||||
)
|
||||
expect(screen.getByText(MockBuildInfo.version)).toBeDefined()
|
||||
})
|
||||
|
||||
|
||||
@@ -15,16 +15,14 @@ import { combineClasses } from "util/combineClasses"
|
||||
|
||||
export const Language = {
|
||||
accountLabel: "Account",
|
||||
docsLabel: "Documentation",
|
||||
signOutLabel: "Sign Out",
|
||||
bugLabel: "Report a Bug",
|
||||
discordLabel: "Join the Coder Discord",
|
||||
copyrightText: `\u00a9 ${new Date().getFullYear()} Coder Technologies, Inc.`,
|
||||
}
|
||||
|
||||
export interface UserDropdownContentProps {
|
||||
user: TypesGen.User
|
||||
buildInfo?: TypesGen.BuildInfoResponse
|
||||
supportLinks?: TypesGen.LinkConfig[]
|
||||
onPopoverClose: () => void
|
||||
onSignOut: () => void
|
||||
}
|
||||
@@ -32,14 +30,11 @@ export interface UserDropdownContentProps {
|
||||
export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
buildInfo,
|
||||
user,
|
||||
supportLinks,
|
||||
onPopoverClose,
|
||||
onSignOut,
|
||||
}) => {
|
||||
const styles = useStyles()
|
||||
const githubUrl = `https://github.com/coder/coder/issues/new?labels=needs+grooming&body=${encodeURIComponent(`Version: [\`${buildInfo?.version}\`](${buildInfo?.external_url})
|
||||
|
||||
<!--- Ask a question or leave feedback! -->`)}`
|
||||
const discordUrl = `https://coder.com/chat?utm_source=coder&utm_medium=coder&utm_campaign=server-footer`
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -64,43 +59,33 @@ export const UserDropdownContent: FC<UserDropdownContentProps> = ({
|
||||
|
||||
<Divider className={styles.divider} />
|
||||
|
||||
<a
|
||||
href="https://coder.com/docs/coder-oss"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
<MenuItem className={styles.menuItem} onClick={onPopoverClose}>
|
||||
<DocsIcon className={styles.menuItemIcon} />
|
||||
<span className={styles.menuItemText}>{Language.docsLabel}</span>
|
||||
</MenuItem>
|
||||
</a>
|
||||
<>
|
||||
{supportLinks &&
|
||||
supportLinks.map((link) => (
|
||||
<a
|
||||
href={includeBuildInfo(link.target, buildInfo)}
|
||||
key={link.name}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
<MenuItem className={styles.menuItem} onClick={onPopoverClose}>
|
||||
{link.icon === "bug" && (
|
||||
<BugIcon className={styles.menuItemIcon} />
|
||||
)}
|
||||
{link.icon === "chat" && (
|
||||
<ChatIcon className={styles.menuItemIcon} />
|
||||
)}
|
||||
{link.icon === "docs" && (
|
||||
<DocsIcon className={styles.menuItemIcon} />
|
||||
)}
|
||||
<span className={styles.menuItemText}>{link.name}</span>
|
||||
</MenuItem>
|
||||
</a>
|
||||
))}
|
||||
</>
|
||||
|
||||
<a
|
||||
href={githubUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
<MenuItem className={styles.menuItem} onClick={onPopoverClose}>
|
||||
<BugIcon className={styles.menuItemIcon} />
|
||||
<span className={styles.menuItemText}>{Language.bugLabel}</span>
|
||||
</MenuItem>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href={discordUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
<MenuItem className={styles.menuItem} onClick={onPopoverClose}>
|
||||
<ChatIcon className={styles.menuItemIcon} />
|
||||
<span className={styles.menuItemText}>{Language.discordLabel}</span>
|
||||
</MenuItem>
|
||||
</a>
|
||||
|
||||
<Divider className={styles.divider} />
|
||||
{supportLinks && <Divider className={styles.divider} />}
|
||||
|
||||
<Stack className={styles.info} spacing={0}>
|
||||
<a
|
||||
@@ -181,3 +166,15 @@ const useStyles = makeStyles((theme) => ({
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}))
|
||||
|
||||
const includeBuildInfo = (
|
||||
href: string,
|
||||
buildInfo?: TypesGen.BuildInfoResponse,
|
||||
): string => {
|
||||
return href.replace(
|
||||
"{CODER_BUILD_INFO}",
|
||||
`${encodeURIComponent(
|
||||
`Version: [\`${buildInfo?.version}\`](${buildInfo?.external_url})`,
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppearanceConfig } from "api/typesGenerated"
|
||||
import { UpdateAppearanceConfig } from "api/typesGenerated"
|
||||
import { useDashboard } from "components/Dashboard/DashboardProvider"
|
||||
import { FC } from "react"
|
||||
import { Helmet } from "react-helmet-async"
|
||||
@@ -15,7 +15,7 @@ const AppearanceSettingsPage: FC = () => {
|
||||
entitlements.features["appearance"].entitlement !== "not_entitled"
|
||||
|
||||
const updateAppearance = (
|
||||
newConfig: Partial<AppearanceConfig>,
|
||||
newConfig: Partial<UpdateAppearanceConfig>,
|
||||
preview: boolean,
|
||||
) => {
|
||||
const newAppearance = {
|
||||
|
||||
+18
-16
@@ -17,16 +17,16 @@ import { useTranslation } from "react-i18next"
|
||||
import makeStyles from "@material-ui/core/styles/makeStyles"
|
||||
import Switch from "@material-ui/core/Switch"
|
||||
import TextField from "@material-ui/core/TextField"
|
||||
import { AppearanceConfig } from "api/typesGenerated"
|
||||
import { UpdateAppearanceConfig } from "api/typesGenerated"
|
||||
import { Stack } from "components/Stack/Stack"
|
||||
import { useFormik } from "formik"
|
||||
import { useTheme } from "@material-ui/core/styles"
|
||||
|
||||
export type AppearanceSettingsPageViewProps = {
|
||||
appearance: AppearanceConfig
|
||||
appearance: UpdateAppearanceConfig
|
||||
isEntitled: boolean
|
||||
updateAppearance: (
|
||||
newConfig: Partial<AppearanceConfig>,
|
||||
newConfig: Partial<UpdateAppearanceConfig>,
|
||||
preview: boolean,
|
||||
) => void
|
||||
}
|
||||
@@ -48,20 +48,22 @@ export const AppearanceSettingsPageView = ({
|
||||
})
|
||||
const logoFieldHelpers = getFormHelpers(logoForm)
|
||||
|
||||
const serviceBannerForm = useFormik<AppearanceConfig["service_banner"]>({
|
||||
initialValues: {
|
||||
message: appearance.service_banner.message,
|
||||
enabled: appearance.service_banner.enabled,
|
||||
background_color: appearance.service_banner.background_color,
|
||||
const serviceBannerForm = useFormik<UpdateAppearanceConfig["service_banner"]>(
|
||||
{
|
||||
initialValues: {
|
||||
message: appearance.service_banner.message,
|
||||
enabled: appearance.service_banner.enabled,
|
||||
background_color: appearance.service_banner.background_color,
|
||||
},
|
||||
onSubmit: (values) =>
|
||||
updateAppearance(
|
||||
{
|
||||
service_banner: values,
|
||||
},
|
||||
false,
|
||||
),
|
||||
},
|
||||
onSubmit: (values) =>
|
||||
updateAppearance(
|
||||
{
|
||||
service_banner: values,
|
||||
},
|
||||
false,
|
||||
),
|
||||
})
|
||||
)
|
||||
const serviceBannerFieldHelpers = getFormHelpers(serviceBannerForm)
|
||||
const [backgroundColor, setBackgroundColor] = useState(
|
||||
serviceBannerForm.values.background_color,
|
||||
|
||||
@@ -60,6 +60,25 @@ export const MockBuildInfo: TypesGen.BuildInfoResponse = {
|
||||
version: "v99.999.9999+c9cdf14",
|
||||
}
|
||||
|
||||
export const MockSupportLinks: TypesGen.LinkConfig[] = [
|
||||
{
|
||||
name: "First link",
|
||||
target: "http://first-link",
|
||||
icon: "chat",
|
||||
},
|
||||
{
|
||||
name: "Second link",
|
||||
target: "http://second-link",
|
||||
icon: "docs",
|
||||
},
|
||||
{
|
||||
name: "Third link",
|
||||
target:
|
||||
"https://github.com/coder/coder/issues/new?labels=needs+grooming&body={CODER_BUILD_INFO}",
|
||||
icon: "",
|
||||
},
|
||||
]
|
||||
|
||||
export const MockUpdateCheck: TypesGen.UpdateCheckResponse = {
|
||||
current: true,
|
||||
url: "file:///mock-url",
|
||||
|
||||
Reference in New Issue
Block a user