Skip to content

Commit 9df9ad4

Browse files
authored
feat: embed common client requests into the template html (#8076)
This should reduce the number of API requests a client makes when loading the dashboard dramatically!
1 parent 2a10c91 commit 9df9ad4

File tree

15 files changed

+406
-181
lines changed

15 files changed

+406
-181
lines changed

coderd/audit.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"cdr.dev/slog"
1818
"github.com/coder/coder/coderd/audit"
1919
"github.com/coder/coder/coderd/database"
20+
"github.com/coder/coder/coderd/database/db2sdk"
2021
"github.com/coder/coder/coderd/httpapi"
2122
"github.com/coder/coder/coderd/httpmw"
2223
"github.com/coder/coder/coderd/rbac"
@@ -193,7 +194,7 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
193194

194195
for _, roleName := range dblog.UserRoles {
195196
rbacRole, _ := rbac.RoleByName(roleName)
196-
user.Roles = append(user.Roles, convertRole(rbacRole))
197+
user.Roles = append(user.Roles, db2sdk.Role(rbacRole))
197198
}
198199
}
199200

coderd/coderd.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,13 @@ func New(options *Options) *API {
293293
},
294294
)
295295

296-
staticHandler := site.Handler(site.FS(), binFS, binHashes)
297-
// Static file handler must be wrapped with HSTS handler if the
298-
// StrictTransportSecurityAge is set. We only need to set this header on
299-
// static files since it only affects browsers.
300-
staticHandler = httpmw.HSTS(staticHandler, options.StrictTransportSecurityCfg)
296+
staticHandler := site.New(&site.Options{
297+
BinFS: binFS,
298+
BinHashes: binHashes,
299+
Database: options.Database,
300+
SiteFS: site.FS(),
301+
})
302+
staticHandler.Experiments.Store(&experiments)
301303

302304
oauthConfigs := &httpmw.OAuth2Configs{
303305
Github: options.GithubOAuth2Config,
@@ -313,7 +315,7 @@ func New(options *Options) *API {
313315
ID: uuid.New(),
314316
Options: options,
315317
RootHandler: r,
316-
siteHandler: staticHandler,
318+
SiteHandler: staticHandler,
317319
HTTPAuth: &HTTPAuthorizer{
318320
Authorizer: options.Authorizer,
319321
Logger: options.Logger,
@@ -813,7 +815,11 @@ func New(options *Options) *API {
813815
// By default we do not add extra websocket connections to the CSP
814816
return []string{}
815817
})
816-
r.NotFound(cspMW(compressHandler(http.HandlerFunc(api.siteHandler.ServeHTTP))).ServeHTTP)
818+
819+
// Static file handler must be wrapped with HSTS handler if the
820+
// StrictTransportSecurityAge is set. We only need to set this header on
821+
// static files since it only affects browsers.
822+
r.NotFound(cspMW(compressHandler(httpmw.HSTS(api.SiteHandler, options.StrictTransportSecurityCfg))).ServeHTTP)
817823

818824
// This must be before all middleware to improve the response time.
819825
// So make a new router, and mount the old one as the root.
@@ -858,7 +864,8 @@ type API struct {
858864
// RootHandler serves "/"
859865
RootHandler chi.Router
860866

861-
siteHandler http.Handler
867+
// SiteHandler serves static files for the dashboard.
868+
SiteHandler *site.Handler
862869

863870
WebsocketWaitMutex sync.Mutex
864871
WebsocketWaitGroup sync.WaitGroup

coderd/database/db2sdk/db2sdk.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import (
55
"encoding/json"
66
"time"
77

8+
"github.com/google/uuid"
9+
810
"github.com/coder/coder/coderd/database"
911
"github.com/coder/coder/coderd/parameter"
12+
"github.com/coder/coder/coderd/rbac"
1013
"github.com/coder/coder/codersdk"
1114
"github.com/coder/coder/provisionersdk/proto"
1215
)
@@ -100,3 +103,31 @@ func ProvisionerJobStatus(provisionerJob database.ProvisionerJob) codersdk.Provi
100103
return codersdk.ProvisionerJobRunning
101104
}
102105
}
106+
107+
func User(user database.User, organizationIDs []uuid.UUID) codersdk.User {
108+
convertedUser := codersdk.User{
109+
ID: user.ID,
110+
Email: user.Email,
111+
CreatedAt: user.CreatedAt,
112+
LastSeenAt: user.LastSeenAt,
113+
Username: user.Username,
114+
Status: codersdk.UserStatus(user.Status),
115+
OrganizationIDs: organizationIDs,
116+
Roles: make([]codersdk.Role, 0, len(user.RBACRoles)),
117+
AvatarURL: user.AvatarURL.String,
118+
}
119+
120+
for _, roleName := range user.RBACRoles {
121+
rbacRole, _ := rbac.RoleByName(roleName)
122+
convertedUser.Roles = append(convertedUser.Roles, Role(rbacRole))
123+
}
124+
125+
return convertedUser
126+
}
127+
128+
func Role(role rbac.Role) codersdk.Role {
129+
return codersdk.Role{
130+
DisplayName: role.DisplayName,
131+
Name: role.Name,
132+
}
133+
}

coderd/members.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"golang.org/x/xerrors"
1010

11+
"github.com/coder/coder/coderd/database/db2sdk"
1112
"github.com/coder/coder/coderd/rbac"
1213

1314
"github.com/coder/coder/coderd/database"
@@ -104,7 +105,7 @@ func convertOrganizationMember(mem database.OrganizationMember) codersdk.Organiz
104105

105106
for _, roleName := range mem.Roles {
106107
rbacRole, _ := rbac.RoleByName(roleName)
107-
convertedMember.Roles = append(convertedMember.Roles, convertRole(rbacRole))
108+
convertedMember.Roles = append(convertedMember.Roles, db2sdk.Role(rbacRole))
108109
}
109110
return convertedMember
110111
}

coderd/roles.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,6 @@ func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
5555
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Actor.Roles, roles))
5656
}
5757

58-
func convertRole(role rbac.Role) codersdk.Role {
59-
return codersdk.Role{
60-
DisplayName: role.DisplayName,
61-
Name: role.Name,
62-
}
63-
}
64-
6558
func assignableRoles(actorRoles rbac.ExpandableRoles, roles []rbac.Role) []codersdk.AssignableRoles {
6659
assignable := make([]codersdk.AssignableRoles, 0)
6760
for _, role := range roles {

coderd/users.go

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/coder/coder/coderd/audit"
1616
"github.com/coder/coder/coderd/database"
17+
"github.com/coder/coder/coderd/database/db2sdk"
1718
"github.com/coder/coder/coderd/database/dbauthz"
1819
"github.com/coder/coder/coderd/gitsshkey"
1920
"github.com/coder/coder/coderd/httpapi"
@@ -401,7 +402,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
401402
Users: []telemetry.User{telemetry.ConvertUser(user)},
402403
})
403404

404-
httpapi.Write(ctx, rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID}))
405+
httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.User(user, []uuid.UUID{req.OrganizationID}))
405406
}
406407

407408
// @Summary Delete user
@@ -495,7 +496,7 @@ func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
495496
return
496497
}
497498

498-
httpapi.Write(ctx, rw, http.StatusOK, convertUser(user, organizationIDs))
499+
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.User(user, organizationIDs))
499500
}
500501

501502
// @Summary Update user profile
@@ -580,7 +581,7 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
580581
return
581582
}
582583

583-
httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
584+
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.User(updatedUserProfile, organizationIDs))
584585
}
585586

586587
// @Summary Suspend user account
@@ -667,7 +668,7 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
667668
return
668669
}
669670

670-
httpapi.Write(ctx, rw, http.StatusOK, convertUser(suspendedUser, organizations))
671+
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.User(suspendedUser, organizations))
671672
}
672673
}
673674

@@ -892,7 +893,7 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
892893
return
893894
}
894895

895-
httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUser, organizationIDs))
896+
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.User(updatedUser, organizationIDs))
896897
}
897898

898899
// updateSiteUserRoles will ensure only site wide roles are passed in as arguments.
@@ -1087,32 +1088,11 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
10871088
}, nil)
10881089
}
10891090

1090-
func convertUser(user database.User, organizationIDs []uuid.UUID) codersdk.User {
1091-
convertedUser := codersdk.User{
1092-
ID: user.ID,
1093-
Email: user.Email,
1094-
CreatedAt: user.CreatedAt,
1095-
LastSeenAt: user.LastSeenAt,
1096-
Username: user.Username,
1097-
Status: codersdk.UserStatus(user.Status),
1098-
OrganizationIDs: organizationIDs,
1099-
Roles: make([]codersdk.Role, 0, len(user.RBACRoles)),
1100-
AvatarURL: user.AvatarURL.String,
1101-
}
1102-
1103-
for _, roleName := range user.RBACRoles {
1104-
rbacRole, _ := rbac.RoleByName(roleName)
1105-
convertedUser.Roles = append(convertedUser.Roles, convertRole(rbacRole))
1106-
}
1107-
1108-
return convertedUser
1109-
}
1110-
11111091
func convertUsers(users []database.User, organizationIDsByUserID map[uuid.UUID][]uuid.UUID) []codersdk.User {
11121092
converted := make([]codersdk.User, 0, len(users))
11131093
for _, u := range users {
11141094
userOrganizationIDs := organizationIDsByUserID[u.ID]
1115-
converted = append(converted, convertUser(u, userOrganizationIDs))
1095+
converted = append(converted, db2sdk.User(u, userOrganizationIDs))
11161096
}
11171097
return converted
11181098
}

enterprise/coderd/appearance.go

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package coderd
22

33
import (
4+
"context"
45
"database/sql"
56
"encoding/hex"
67
"encoding/json"
78
"errors"
89
"fmt"
910
"net/http"
1011

12+
"golang.org/x/sync/errgroup"
1113
"golang.org/x/xerrors"
1214

1315
"github.com/coder/coder/coderd/httpapi"
@@ -41,35 +43,49 @@ var DefaultSupportLinks = []codersdk.LinkConfig{
4143
// @Success 200 {object} codersdk.AppearanceConfig
4244
// @Router /appearance [get]
4345
func (api *API) appearance(rw http.ResponseWriter, r *http.Request) {
46+
cfg, err := api.fetchAppearanceConfig(r.Context())
47+
if err != nil {
48+
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
49+
Message: "Failed to fetch appearance config.",
50+
Detail: err.Error(),
51+
})
52+
return
53+
}
54+
55+
httpapi.Write(r.Context(), rw, http.StatusOK, cfg)
56+
}
57+
58+
func (api *API) fetchAppearanceConfig(ctx context.Context) (codersdk.AppearanceConfig, error) {
4459
api.entitlementsMu.RLock()
4560
isEntitled := api.entitlements.Features[codersdk.FeatureAppearance].Entitlement == codersdk.EntitlementEntitled
4661
api.entitlementsMu.RUnlock()
4762

48-
ctx := r.Context()
49-
5063
if !isEntitled {
51-
httpapi.Write(ctx, rw, http.StatusOK, codersdk.AppearanceConfig{
64+
return codersdk.AppearanceConfig{
5265
SupportLinks: DefaultSupportLinks,
53-
})
54-
return
66+
}, nil
5567
}
5668

57-
logoURL, err := api.Database.GetLogoURL(ctx)
58-
if err != nil && !errors.Is(err, sql.ErrNoRows) {
59-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
60-
Message: "Failed to fetch logo URL.",
61-
Detail: err.Error(),
62-
})
63-
return
64-
}
65-
66-
serviceBannerJSON, err := api.Database.GetServiceBanner(r.Context())
67-
if err != nil && !errors.Is(err, sql.ErrNoRows) {
68-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
69-
Message: "Failed to fetch service banner.",
70-
Detail: err.Error(),
71-
})
72-
return
69+
var eg errgroup.Group
70+
var logoURL string
71+
var serviceBannerJSON string
72+
eg.Go(func() (err error) {
73+
logoURL, err = api.Database.GetLogoURL(ctx)
74+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
75+
return xerrors.Errorf("get logo url: %w", err)
76+
}
77+
return nil
78+
})
79+
eg.Go(func() (err error) {
80+
serviceBannerJSON, err = api.Database.GetServiceBanner(ctx)
81+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
82+
return xerrors.Errorf("get service banner: %w", err)
83+
}
84+
return nil
85+
})
86+
err := eg.Wait()
87+
if err != nil {
88+
return codersdk.AppearanceConfig{}, err
7389
}
7490

7591
cfg := codersdk.AppearanceConfig{
@@ -78,12 +94,9 @@ func (api *API) appearance(rw http.ResponseWriter, r *http.Request) {
7894
if serviceBannerJSON != "" {
7995
err = json.Unmarshal([]byte(serviceBannerJSON), &cfg.ServiceBanner)
8096
if err != nil {
81-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
82-
Message: fmt.Sprintf(
83-
"unmarshal json: %+v, raw: %s", err, serviceBannerJSON,
84-
),
85-
})
86-
return
97+
return codersdk.AppearanceConfig{}, xerrors.Errorf(
98+
"unmarshal json: %w, raw: %s", err, serviceBannerJSON,
99+
)
87100
}
88101
}
89102

@@ -93,7 +106,7 @@ func (api *API) appearance(rw http.ResponseWriter, r *http.Request) {
93106
cfg.SupportLinks = api.DeploymentValues.Support.Links.Value
94107
}
95108

96-
httpapi.Write(r.Context(), rw, http.StatusOK, cfg)
109+
return cfg, nil
97110
}
98111

99112
func validateHexColor(color string) error {

enterprise/coderd/coderd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
6666
}()
6767

6868
api.AGPL.Options.SetUserGroups = api.setUserGroups
69+
api.AGPL.SiteHandler.AppearanceFetcher = api.fetchAppearanceConfig
6970

7071
oauthConfigs := &httpmw.OAuth2Configs{
7172
Github: options.GithubOAuth2Config,
@@ -451,6 +452,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
451452
}
452453

453454
api.entitlements = entitlements
455+
api.AGPL.SiteHandler.Entitlements.Store(&entitlements)
454456

455457
return nil
456458
}

site/index.html

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
<meta charset="utf-8" />
1313
<meta name="viewport" content="width=device-width, initial-scale=1" />
1414
<meta name="theme-color" content="#17172E" />
15-
<meta name="application-name" content="Coder2" />
15+
<meta name="application-name" content="Coder" />
1616
<meta property="og:type" content="website" />
17-
<meta property="csp-nonce" content="{{ .CSP.Nonce }}" />
1817
<meta property="csrf-token" content="{{ .CSRF.Token }}" />
1918
<meta property="build-info" content="{{ .BuildInfo }}" />
19+
<meta property="user" content="{{ .User }}" />
20+
<meta property="entitlements" content="{{ .Entitlements }}" />
21+
<meta property="appearance" content="{{ .Appearance }}" />
22+
<meta property="experiments" content="{{ .Experiments }}" />
2023
<!-- We need to set data-react-helmet to be able to override it in the workspace page -->
2124
<link
2225
rel="alternate icon"

0 commit comments

Comments
 (0)