This commit is contained in:
McKayla Washburn 2024-04-24 17:19:39 +00:00
parent 215dd7b152
commit f432099664
21 changed files with 188 additions and 5 deletions

6
coderd/apidoc/docs.go generated
View File

@ -8280,6 +8280,9 @@ const docTemplate = `{
"items": { "items": {
"$ref": "#/definitions/codersdk.LinkConfig" "$ref": "#/definitions/codersdk.LinkConfig"
} }
},
"terms_of_service": {
"type": "string"
} }
} }
}, },
@ -11896,6 +11899,9 @@ const docTemplate = `{
}, },
"service_banner": { "service_banner": {
"$ref": "#/definitions/codersdk.ServiceBannerConfig" "$ref": "#/definitions/codersdk.ServiceBannerConfig"
},
"terms_of_service": {
"type": "string"
} }
} }
}, },

View File

@ -7349,6 +7349,9 @@
"items": { "items": {
"$ref": "#/definitions/codersdk.LinkConfig" "$ref": "#/definitions/codersdk.LinkConfig"
} }
},
"terms_of_service": {
"type": "string"
} }
} }
}, },
@ -10755,6 +10758,9 @@
}, },
"service_banner": { "service_banner": {
"$ref": "#/definitions/codersdk.ServiceBannerConfig" "$ref": "#/definitions/codersdk.ServiceBannerConfig"
},
"terms_of_service": {
"type": "string"
} }
} }
}, },

View File

@ -1793,6 +1793,11 @@ func (q *querier) GetTemplatesWithFilter(ctx context.Context, arg database.GetTe
return q.db.GetAuthorizedTemplates(ctx, arg, prep) return q.db.GetAuthorizedTemplates(ctx, arg, prep)
} }
func (q *querier) GetTermsOfService(ctx context.Context) (string, error) {
// No authz checks
return q.db.GetTermsOfService(ctx)
}
func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, error) { func (q *querier) GetUnexpiredLicenses(ctx context.Context) ([]database.License, error) {
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil { if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil {
return nil, err return nil, err
@ -3438,6 +3443,13 @@ func (q *querier) UpsertTemplateUsageStats(ctx context.Context) error {
return q.db.UpsertTemplateUsageStats(ctx) return q.db.UpsertTemplateUsageStats(ctx)
} }
func (q *querier) UpsertTermsOfService(ctx context.Context, value string) error {
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil {
return err
}
return q.db.UpsertTermsOfService(ctx, value)
}
func (q *querier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) { func (q *querier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID) workspace, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID)
if err != nil { if err != nil {

View File

@ -4213,6 +4213,10 @@ func (q *FakeQuerier) GetTemplatesWithFilter(ctx context.Context, arg database.G
return q.GetAuthorizedTemplates(ctx, arg, nil) return q.GetAuthorizedTemplates(ctx, arg, nil)
} }
func (q *FakeQuerier) GetTermsOfService(ctx context.Context) (string, error) {
panic("not implemented")
}
func (q *FakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.License, error) { func (q *FakeQuerier) GetUnexpiredLicenses(_ context.Context) ([]database.License, error) {
q.mutex.RLock() q.mutex.RLock()
defer q.mutex.RUnlock() defer q.mutex.RUnlock()
@ -8932,6 +8936,10 @@ TemplateUsageStatsInsertLoop:
return nil return nil
} }
func (q *FakeQuerier) UpsertTermsOfService(ctx context.Context, value string) error {
panic("not implemented")
}
func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) { func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
err := validateDatabaseType(arg) err := validateDatabaseType(arg)
if err != nil { if err != nil {

View File

@ -1038,6 +1038,13 @@ func (m metricsStore) GetTemplatesWithFilter(ctx context.Context, arg database.G
return templates, err return templates, err
} }
func (m metricsStore) GetTermsOfService(ctx context.Context) (string, error) {
start := time.Now()
r0, r1 := m.s.GetTermsOfService(ctx)
m.queryLatencies.WithLabelValues("GetTermsOfService").Observe(time.Since(start).Seconds())
return r0, r1
}
func (m metricsStore) GetUnexpiredLicenses(ctx context.Context) ([]database.License, error) { func (m metricsStore) GetUnexpiredLicenses(ctx context.Context) ([]database.License, error) {
start := time.Now() start := time.Now()
licenses, err := m.s.GetUnexpiredLicenses(ctx) licenses, err := m.s.GetUnexpiredLicenses(ctx)
@ -2256,6 +2263,13 @@ func (m metricsStore) UpsertTemplateUsageStats(ctx context.Context) error {
return r0 return r0
} }
func (m metricsStore) UpsertTermsOfService(ctx context.Context, value string) error {
start := time.Now()
r0 := m.s.UpsertTermsOfService(ctx, value)
m.queryLatencies.WithLabelValues("UpsertTermsOfService").Observe(time.Since(start).Seconds())
return r0
}
func (m metricsStore) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) { func (m metricsStore) UpsertWorkspaceAgentPortShare(ctx context.Context, arg database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
start := time.Now() start := time.Now()
r0, r1 := m.s.UpsertWorkspaceAgentPortShare(ctx, arg) r0, r1 := m.s.UpsertWorkspaceAgentPortShare(ctx, arg)

View File

@ -2145,6 +2145,21 @@ func (mr *MockStoreMockRecorder) GetTemplatesWithFilter(arg0, arg1 any) *gomock.
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplatesWithFilter", reflect.TypeOf((*MockStore)(nil).GetTemplatesWithFilter), arg0, arg1) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplatesWithFilter", reflect.TypeOf((*MockStore)(nil).GetTemplatesWithFilter), arg0, arg1)
} }
// GetTermsOfService mocks base method.
func (m *MockStore) GetTermsOfService(arg0 context.Context) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetTermsOfService", arg0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetTermsOfService indicates an expected call of GetTermsOfService.
func (mr *MockStoreMockRecorder) GetTermsOfService(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTermsOfService", reflect.TypeOf((*MockStore)(nil).GetTermsOfService), arg0)
}
// GetUnexpiredLicenses mocks base method. // GetUnexpiredLicenses mocks base method.
func (m *MockStore) GetUnexpiredLicenses(arg0 context.Context) ([]database.License, error) { func (m *MockStore) GetUnexpiredLicenses(arg0 context.Context) ([]database.License, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -4723,6 +4738,20 @@ func (mr *MockStoreMockRecorder) UpsertTemplateUsageStats(arg0 any) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTemplateUsageStats", reflect.TypeOf((*MockStore)(nil).UpsertTemplateUsageStats), arg0) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTemplateUsageStats", reflect.TypeOf((*MockStore)(nil).UpsertTemplateUsageStats), arg0)
} }
// UpsertTermsOfService mocks base method.
func (m *MockStore) UpsertTermsOfService(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertTermsOfService", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// UpsertTermsOfService indicates an expected call of UpsertTermsOfService.
func (mr *MockStoreMockRecorder) UpsertTermsOfService(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertTermsOfService", reflect.TypeOf((*MockStore)(nil).UpsertTermsOfService), arg0, arg1)
}
// UpsertWorkspaceAgentPortShare mocks base method. // UpsertWorkspaceAgentPortShare mocks base method.
func (m *MockStore) UpsertWorkspaceAgentPortShare(arg0 context.Context, arg1 database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) { func (m *MockStore) UpsertWorkspaceAgentPortShare(arg0 context.Context, arg1 database.UpsertWorkspaceAgentPortShareParams) (database.WorkspaceAgentPortShare, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -215,6 +215,7 @@ type sqlcQuerier interface {
GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error)
GetTemplates(ctx context.Context) ([]Template, error) GetTemplates(ctx context.Context) ([]Template, error)
GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error)
GetTermsOfService(ctx context.Context) (string, error)
GetUnexpiredLicenses(ctx context.Context) ([]License, error) GetUnexpiredLicenses(ctx context.Context) ([]License, error)
// GetUserActivityInsights returns the ranking with top active users. // GetUserActivityInsights returns the ranking with top active users.
// The result can be filtered on template_ids, meaning only user data // The result can be filtered on template_ids, meaning only user data
@ -435,6 +436,7 @@ type sqlcQuerier interface {
// used to store the data, and the minutes are summed for each user and template // used to store the data, and the minutes are summed for each user and template
// combination. The result is stored in the template_usage_stats table. // combination. The result is stored in the template_usage_stats table.
UpsertTemplateUsageStats(ctx context.Context) error UpsertTemplateUsageStats(ctx context.Context) error
UpsertTermsOfService(ctx context.Context, value string) error
UpsertWorkspaceAgentPortShare(ctx context.Context, arg UpsertWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error) UpsertWorkspaceAgentPortShare(ctx context.Context, arg UpsertWorkspaceAgentPortShareParams) (WorkspaceAgentPortShare, error)
} }

View File

@ -5637,6 +5637,17 @@ func (q *sqlQuerier) GetServiceBanner(ctx context.Context) (string, error) {
return value, err return value, err
} }
const getTermsOfService = `-- name: GetTermsOfService :one
SELECT value FROM site_configs WHERE key = 'terms_of_service'
`
func (q *sqlQuerier) GetTermsOfService(ctx context.Context) (string, error) {
row := q.db.QueryRowContext(ctx, getTermsOfService)
var value string
err := row.Scan(&value)
return value, err
}
const insertDERPMeshKey = `-- name: InsertDERPMeshKey :exec const insertDERPMeshKey = `-- name: InsertDERPMeshKey :exec
INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1) INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1)
` `
@ -5748,6 +5759,16 @@ func (q *sqlQuerier) UpsertServiceBanner(ctx context.Context, value string) erro
return err return err
} }
const upsertTermsOfService = `-- name: UpsertTermsOfService :exec
INSERT INTO site_configs (key, value) VALUES ('terms_of_service', $1)
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'terms_of_service'
`
func (q *sqlQuerier) UpsertTermsOfService(ctx context.Context, value string) error {
_, err := q.db.ExecContext(ctx, upsertTermsOfService, value)
return err
}
const cleanTailnetCoordinators = `-- name: CleanTailnetCoordinators :exec const cleanTailnetCoordinators = `-- name: CleanTailnetCoordinators :exec
DELETE DELETE
FROM tailnet_coordinators FROM tailnet_coordinators

View File

@ -57,6 +57,13 @@ ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'application
-- name: GetApplicationName :one -- name: GetApplicationName :one
SELECT value FROM site_configs WHERE key = 'application_name'; SELECT value FROM site_configs WHERE key = 'application_name';
-- name: UpsertTermsOfService :exec
INSERT INTO site_configs (key, value) VALUES ('terms_of_service', $1)
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'terms_of_service';
-- name: GetTermsOfService :one
SELECT value FROM site_configs WHERE key = 'terms_of_service';
-- name: GetAppSecurityKey :one -- name: GetAppSecurityKey :one
SELECT value FROM site_configs WHERE key = 'app_signing_key'; SELECT value FROM site_configs WHERE key = 'app_signing_key';

View File

@ -2078,6 +2078,7 @@ type AppearanceConfig struct {
ApplicationName string `json:"application_name"` ApplicationName string `json:"application_name"`
LogoURL string `json:"logo_url"` LogoURL string `json:"logo_url"`
ServiceBanner ServiceBannerConfig `json:"service_banner"` ServiceBanner ServiceBannerConfig `json:"service_banner"`
TermsOfService string `json:"terms_of_service"`
SupportLinks []LinkConfig `json:"support_links,omitempty"` SupportLinks []LinkConfig `json:"support_links,omitempty"`
} }
@ -2085,6 +2086,7 @@ type UpdateAppearanceConfig struct {
ApplicationName string `json:"application_name"` ApplicationName string `json:"application_name"`
LogoURL string `json:"logo_url"` LogoURL string `json:"logo_url"`
ServiceBanner ServiceBannerConfig `json:"service_banner"` ServiceBanner ServiceBannerConfig `json:"service_banner"`
TermsOfService string `json:"terms_of_service"`
} }
type ServiceBannerConfig struct { type ServiceBannerConfig struct {

View File

@ -32,7 +32,8 @@ curl -X GET http://coder-server:8080/api/v2/appearance \
"name": "string", "name": "string",
"target": "string" "target": "string"
} }
] ],
"terms_of_service": "string"
} }
``` ```
@ -68,7 +69,8 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
"background_color": "string", "background_color": "string",
"enabled": true, "enabled": true,
"message": "string" "message": "string"
} },
"terms_of_service": "string"
} }
``` ```
@ -90,7 +92,8 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
"background_color": "string", "background_color": "string",
"enabled": true, "enabled": true,
"message": "string" "message": "string"
} },
"terms_of_service": "string"
} }
``` ```

8
docs/api/schemas.md generated
View File

@ -762,7 +762,8 @@
"name": "string", "name": "string",
"target": "string" "target": "string"
} }
] ],
"terms_of_service": "string"
} }
``` ```
@ -774,6 +775,7 @@
| `logo_url` | string | false | | | | `logo_url` | string | false | | |
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | | `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | | | `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
| `terms_of_service` | string | false | | |
## codersdk.ArchiveTemplateVersionsRequest ## codersdk.ArchiveTemplateVersionsRequest
@ -5172,7 +5174,8 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"background_color": "string", "background_color": "string",
"enabled": true, "enabled": true,
"message": "string" "message": "string"
} },
"terms_of_service": "string"
} }
``` ```
@ -5183,6 +5186,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| `application_name` | string | false | | | | `application_name` | string | false | | |
| `logo_url` | string | false | | | | `logo_url` | string | false | | |
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | | | `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
| `terms_of_service` | string | false | | |
## codersdk.UpdateCheckResponse ## codersdk.UpdateCheckResponse

View File

@ -56,6 +56,7 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
var applicationName string var applicationName string
var logoURL string var logoURL string
var serviceBannerJSON string var serviceBannerJSON string
var termsOfService string
eg.Go(func() (err error) { eg.Go(func() (err error) {
applicationName, err = f.database.GetApplicationName(ctx) applicationName, err = f.database.GetApplicationName(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) { if err != nil && !errors.Is(err, sql.ErrNoRows) {
@ -77,6 +78,13 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
} }
return nil return nil
}) })
eg.Go(func() (err error) {
termsOfService, err = f.database.GetTermsOfService(ctx)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return xerrors.Errorf("get terms of service: %w", err)
}
return nil
})
err := eg.Wait() err := eg.Wait()
if err != nil { if err != nil {
return codersdk.AppearanceConfig{}, err return codersdk.AppearanceConfig{}, err
@ -85,6 +93,7 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
cfg := codersdk.AppearanceConfig{ cfg := codersdk.AppearanceConfig{
ApplicationName: applicationName, ApplicationName: applicationName,
LogoURL: logoURL, LogoURL: logoURL,
TermsOfService: termsOfService,
} }
if serviceBannerJSON != "" { if serviceBannerJSON != "" {
err = json.Unmarshal([]byte(serviceBannerJSON), &cfg.ServiceBanner) err = json.Unmarshal([]byte(serviceBannerJSON), &cfg.ServiceBanner)
@ -176,6 +185,15 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) {
return return
} }
err = api.Database.UpsertTermsOfService(ctx, appearance.TermsOfService)
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Unable to set terms of service",
Detail: err.Error(),
})
return
}
err = api.Database.UpsertLogoURL(ctx, appearance.LogoURL) err = api.Database.UpsertLogoURL(ctx, appearance.LogoURL)
if err != nil { if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{

View File

@ -1282,6 +1282,8 @@ export const getAppearance = async (): Promise<TypesGen.AppearanceConfig> => {
const response = await axios.get(`/api/v2/appearance`); const response = await axios.get(`/api/v2/appearance`);
return response.data || {}; return response.data || {};
} catch (ex) { } catch (ex) {
// This endpoint is only available on enterprise binaries. A 404 is expected
// from AGPL builds, and should be be treated as a "successful" response.
if (axios.isAxiosError(ex) && ex.response?.status === 404) { if (axios.isAxiosError(ex) && ex.response?.status === 404) {
return { return {
application_name: "", application_name: "",
@ -1289,6 +1291,7 @@ export const getAppearance = async (): Promise<TypesGen.AppearanceConfig> => {
service_banner: { service_banner: {
enabled: false, enabled: false,
}, },
terms_of_service: "",
}; };
} }
throw ex; throw ex;

View File

@ -49,6 +49,7 @@ export interface AppearanceConfig {
readonly application_name: string; readonly application_name: string;
readonly logo_url: string; readonly logo_url: string;
readonly service_banner: ServiceBannerConfig; readonly service_banner: ServiceBannerConfig;
readonly terms_of_service: string;
readonly support_links?: readonly LinkConfig[]; readonly support_links?: readonly LinkConfig[];
} }
@ -1279,6 +1280,7 @@ export interface UpdateAppearanceConfig {
readonly application_name: string; readonly application_name: string;
readonly logo_url: string; readonly logo_url: string;
readonly service_banner: ServiceBannerConfig; readonly service_banner: ServiceBannerConfig;
readonly terms_of_service: string;
} }
// From codersdk/updatecheck.go // From codersdk/updatecheck.go

View File

@ -22,6 +22,7 @@ const AppearanceSettingsPage: FC = () => {
newConfig: Partial<UpdateAppearanceConfig>, newConfig: Partial<UpdateAppearanceConfig>,
preview: boolean, preview: boolean,
) => { ) => {
console.log(newConfig);
const newAppearance = { ...appearance.config, ...newConfig }; const newAppearance = { ...appearance.config, ...newConfig };
if (preview) { if (preview) {
appearance.setPreview(newAppearance); appearance.setPreview(newAppearance);

View File

@ -13,6 +13,7 @@ const meta: Meta<typeof AppearanceSettingsPageView> = {
message: "hello world", message: "hello world",
background_color: "white", background_color: "white",
}, },
terms_of_service: "",
}, },
isEntitled: false, isEntitled: false,
}, },

View File

@ -80,6 +80,16 @@ export const AppearanceSettingsPageView: FC<
serviceBannerForm.values.background_color, serviceBannerForm.values.background_color,
); );
const termsOfServiceForm = useFormik<{
terms_of_service: string;
}>({
initialValues: {
terms_of_service: appearance.terms_of_service,
},
onSubmit: (values) => onSaveAppearance(values, false),
});
const termsOfServiceFieldHelpers = getFormHelpers(termsOfServiceForm);
return ( return (
<> <>
<Header <Header
@ -276,6 +286,30 @@ export const AppearanceSettingsPageView: FC<
</Stack> </Stack>
)} )}
</Fieldset> </Fieldset>
<Fieldset
title="Terms of Service"
subtitle="Add a custom Terms of Service that must be accepted before using Coder."
validation={!isEntitled ? "This is an Enterprise only feature." : ""}
onSubmit={termsOfServiceForm.handleSubmit}
button={!isEntitled && <Button disabled>Submit</Button>}
>
<TextField
{...termsOfServiceFieldHelpers("terms_of_service", {
helperText: "Markdown is supported.",
})}
defaultValue={appearance.terms_of_service}
fullWidth
multiline
label="Terms of Service"
placeholder="Leave empty to disable."
disabled={!isEntitled}
inputProps={{
"aria-label": "Terms of Service. Leave empty to disable.",
style: { height: 100, resize: "vertical" },
}}
/>
</Fieldset>
</> </>
); );
}; };

View File

@ -8,6 +8,7 @@ import { useAuthContext } from "contexts/auth/AuthProvider";
import { getApplicationName } from "utils/appearance"; import { getApplicationName } from "utils/appearance";
import { retrieveRedirect } from "utils/redirect"; import { retrieveRedirect } from "utils/redirect";
import { LoginPageView } from "./LoginPageView"; import { LoginPageView } from "./LoginPageView";
import { appearance } from "api/queries/appearance";
export const LoginPage: FC = () => { export const LoginPage: FC = () => {
const location = useLocation(); const location = useLocation();
@ -24,6 +25,7 @@ export const LoginPage: FC = () => {
const applicationName = getApplicationName(); const applicationName = getApplicationName();
const navigate = useNavigate(); const navigate = useNavigate();
const buildInfoQuery = useQuery(buildInfo()); const buildInfoQuery = useQuery(buildInfo());
const siteConfigQuery = useQuery(appearance());
if (isSignedIn) { if (isSignedIn) {
// If the redirect is going to a workspace application, and we // If the redirect is going to a workspace application, and we
@ -68,6 +70,7 @@ export const LoginPage: FC = () => {
error={signInError} error={signInError}
isLoading={isLoading || authMethodsQuery.isLoading} isLoading={isLoading || authMethodsQuery.isLoading}
buildInfo={buildInfoQuery.data} buildInfo={buildInfoQuery.data}
termsOfService={siteConfigQuery.data?.terms_of_service}
isSigningIn={isSigningIn} isSigningIn={isSigningIn}
onSignIn={async ({ email, password }) => { onSignIn={async ({ email, password }) => {
await signIn(email, password); await signIn(email, password);

View File

@ -13,6 +13,7 @@ export interface LoginPageViewProps {
error: unknown; error: unknown;
isLoading: boolean; isLoading: boolean;
buildInfo?: BuildInfoResponse; buildInfo?: BuildInfoResponse;
termsOfService?: string;
isSigningIn: boolean; isSigningIn: boolean;
onSignIn: (credentials: { email: string; password: string }) => void; onSignIn: (credentials: { email: string; password: string }) => void;
} }
@ -22,6 +23,7 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
error, error,
isLoading, isLoading,
buildInfo, buildInfo,
termsOfService,
isSigningIn, isSigningIn,
onSignIn, onSignIn,
}) => { }) => {
@ -49,6 +51,10 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
<CoderIcon fill="white" opacity={1} css={styles.icon} /> <CoderIcon fill="white" opacity={1} css={styles.icon} />
); );
if (termsOfService) {
return <>{termsOfService}</>;
}
return ( return (
<div css={styles.root}> <div css={styles.root}>
<div css={styles.container}> <div css={styles.container}>

View File

@ -2347,6 +2347,7 @@ export const MockAppearanceConfig: TypesGen.AppearanceConfig = {
service_banner: { service_banner: {
enabled: false, enabled: false,
}, },
terms_of_service: "",
}; };
export const MockWorkspaceBuildParameter1: TypesGen.WorkspaceBuildParameter = { export const MockWorkspaceBuildParameter1: TypesGen.WorkspaceBuildParameter = {