Skip to content

Commit d6767c7

Browse files
author
Simon Emms
committed
[dashboard]: create a settings tab inside admin
1 parent bcfbe27 commit d6767c7

File tree

12 files changed

+142
-23
lines changed

12 files changed

+142
-23
lines changed

components/dashboard/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Login } from './Login';
1212
import { UserContext } from './user-context';
1313
import { TeamsContext } from './teams/teams-context';
1414
import { ThemeContext } from './theme-context';
15+
import { AdminContext } from './admin-context';
1516
import { getGitpodService } from './service/service';
1617
import { shouldSeeWhatsNew, WhatsNew } from './whatsnew/WhatsNew';
1718
import gitpodIcon from './icons/gitpod.svg';
@@ -52,6 +53,7 @@ const InstallGitHubApp = React.lazy(() => import(/* webpackPrefetch: true */ './
5253
const FromReferrer = React.lazy(() => import(/* webpackPrefetch: true */ './FromReferrer'));
5354
const UserSearch = React.lazy(() => import(/* webpackPrefetch: true */ './admin/UserSearch'));
5455
const WorkspacesSearch = React.lazy(() => import(/* webpackPrefetch: true */ './admin/WorkspacesSearch'));
56+
const AdminSettings = React.lazy(() => import(/* webpackPrefetch: true */ './admin/Settings'));
5557
const OAuthClientApproval = React.lazy(() => import(/* webpackPrefetch: true */ './OauthClientApproval'));
5658

5759
function Loading() {
@@ -98,6 +100,7 @@ export function getURLHash() {
98100
function App() {
99101
const { user, setUser } = useContext(UserContext);
100102
const { teams, setTeams } = useContext(TeamsContext);
103+
const { setSendTelemetry } = useContext(AdminContext);
101104
const { setIsDark } = useContext(ThemeContext);
102105

103106
const [ loading, setLoading ] = useState<boolean>(true);
@@ -132,6 +135,11 @@ function App() {
132135
}
133136
}
134137
setTeams(teams);
138+
139+
if (user?.rolesOrPermissions?.includes('admin')) {
140+
const sendTelemetry = await getGitpodService().server.adminSettingsGetTelemetry();
141+
setSendTelemetry(sendTelemetry);
142+
}
135143
} catch (error) {
136144
console.error(error);
137145
if (error && "code" in error) {
@@ -279,6 +287,7 @@ function App() {
279287

280288
<Route path="/admin/users" component={UserSearch} />
281289
<Route path="/admin/workspaces" component={WorkspacesSearch} />
290+
<Route path="/admin/settings" component={AdminSettings} />
282291

283292
<Route path={["/", "/login"]} exact>
284293
<Redirect to={workspacesPathMain} />
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import React, { createContext, useState } from 'react';
8+
9+
const AdminContext = createContext<{
10+
sendTelemetry?: boolean,
11+
setSendTelemetry: React.Dispatch<boolean>,
12+
}>({
13+
setSendTelemetry: () => null,
14+
});
15+
16+
const AdminContextProvider: React.FC = ({ children }) => {
17+
const [sendTelemetry, setSendTelemetry] = useState<boolean>();
18+
return (
19+
<AdminContext.Provider value={{ sendTelemetry, setSendTelemetry }}>
20+
{children}
21+
</AdminContext.Provider>
22+
);
23+
};
24+
25+
export { AdminContext, AdminContextProvider }
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { useContext } from "react";
8+
import { AdminContext } from "../admin-context";
9+
import CheckBox from "../components/CheckBox";
10+
import { PageWithSubMenu } from "../components/PageWithSubMenu";
11+
import { getGitpodService } from "../service/service";
12+
import { adminMenu } from "./admin-menu";
13+
14+
export default function Settings() {
15+
const { sendTelemetry, setSendTelemetry } = useContext(AdminContext);
16+
17+
const actuallySetTelemetryPrefs = async (value: boolean) => {
18+
await getGitpodService().server.adminSettingsSetTelemetry(value);
19+
setSendTelemetry(value);
20+
}
21+
22+
return (
23+
<div>
24+
<PageWithSubMenu subMenu={adminMenu} title="Settings" subtitle="Configure settings for your Gitpod cluster.">
25+
<CheckBox
26+
title="Send anonymous service ping to Gitpod"
27+
desc="This is used to provide insights on how you use your cluster so we can provide a better overall experience."
28+
checked={sendTelemetry ?? false}
29+
onChange={(evt) => actuallySetTelemetryPrefs(evt.target.checked)} />
30+
</PageWithSubMenu>
31+
</div >
32+
)
33+
}

components/dashboard/src/admin/admin-menu.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ export const adminMenu = [{
1010
}, {
1111
title: 'Workspaces',
1212
link: ['/admin/workspaces']
13+
}, {
14+
title: 'Settings',
15+
link: ['/admin/settings']
1316
},];

components/dashboard/src/index.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import React from 'react';
88
import ReactDOM from 'react-dom';
99
import App from './App';
1010
import { UserContextProvider } from './user-context';
11+
import { AdminContextProvider } from './admin-context';
1112
import { TeamsContextProvider } from './teams/teams-context';
1213
import { ProjectContextProvider } from './projects/project-context';
1314
import { ThemeContextProvider } from './theme-context';
@@ -18,15 +19,17 @@ import "./index.css"
1819
ReactDOM.render(
1920
<React.StrictMode>
2021
<UserContextProvider>
21-
<TeamsContextProvider>
22-
<ProjectContextProvider>
23-
<ThemeContextProvider>
24-
<BrowserRouter>
25-
<App />
26-
</BrowserRouter>
27-
</ThemeContextProvider>
28-
</ProjectContextProvider>
29-
</TeamsContextProvider>
22+
<AdminContextProvider>
23+
<TeamsContextProvider>
24+
<ProjectContextProvider>
25+
<ThemeContextProvider>
26+
<BrowserRouter>
27+
<App />
28+
</BrowserRouter>
29+
</ThemeContextProvider>
30+
</ProjectContextProvider>
31+
</TeamsContextProvider>
32+
</AdminContextProvider>
3033
</UserContextProvider>
3134
</React.StrictMode>,
3235
document.getElementById('root')

components/gitpod-db/src/installation-admin-db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ import { InstallationAdmin } from "@gitpod/gitpod-protocol";
99
export const InstallationAdminDB = Symbol('InstallationAdminDB');
1010
export interface InstallationAdminDB {
1111
getTelemetryData(): Promise<InstallationAdmin>;
12+
setSendTelemetry(value: boolean): Promise<void>;
1213
}

components/gitpod-db/src/typeorm/installation-admin-db-impl.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
2424
const record: InstallationAdmin = {
2525
id: uuidv4(),
2626
settings: {
27-
sendTelemetry: false,
27+
sendTelemetry: true,
2828
},
2929
};
3030

@@ -55,4 +55,12 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
5555
/* Record not found - create one */
5656
return this.createDefaultRecord();
5757
}
58+
59+
async setSendTelemetry(value: boolean): Promise<void> {
60+
const record = await this.getTelemetryData();
61+
record.settings.sendTelemetry = value;
62+
63+
const repo = await this.getInstallationAdminRepo();
64+
repo.save(record);
65+
}
5866
}

components/gitpod-protocol/src/admin-protocol.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export interface AdminServer {
2929
adminIsStudent(userId: string): Promise<boolean>;
3030
adminAddStudentEmailDomain(userId: string, domain: string): Promise<void>;
3131
adminGrantExtraHours(userId: string, extraHours: number): Promise<void>;
32+
33+
adminSettingsGetTelemetry(): Promise<boolean>
34+
adminSettingsSetTelemetry(send: boolean): Promise<void>
3235
}
3336

3437
export interface AdminGetListRequest<T> {
@@ -65,7 +68,7 @@ export interface AdminModifyPermanentWorkspaceFeatureFlagRequest {
6568
}[]
6669
}
6770

68-
export interface WorkspaceAndInstance extends Omit<Workspace, "id"|"creationTime">, Omit<WorkspaceInstance, "id"|"creationTime"> {
71+
export interface WorkspaceAndInstance extends Omit<Workspace, "id" | "creationTime">, Omit<WorkspaceInstance, "id" | "creationTime"> {
6972
workspaceId: string;
7073
workspaceCreationTime: string;
7174
instanceId: string;
@@ -78,7 +81,7 @@ export namespace WorkspaceAndInstance {
7881
return {
7982
id: wai.workspaceId,
8083
creationTime: wai.workspaceCreationTime,
81-
... wai
84+
...wai
8285
};
8386
}
8487

@@ -89,7 +92,7 @@ export namespace WorkspaceAndInstance {
8992
return {
9093
id: wai.instanceId,
9194
creationTime: wai.instanceCreationTime,
92-
... wai
95+
...wai
9396
};
9497
}
9598
}

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
125125
resetGenericInvite(inviteId: string): Promise<TeamMembershipInvite>;
126126
deleteTeam(teamId: string, userId: string): Promise<void>;
127127

128+
// Admin Settings
129+
adminSettingsGetTelemetry(): Promise<boolean>;
130+
adminSettingsSetTelemetry(send: boolean): Promise<void>;
131+
128132
// Projects
129133
getProviderRepositoriesForUser(params: GetProviderRepositoriesParams): Promise<ProviderRepository[]>;
130134
createProject(params: CreateProjectParams): Promise<Project>;

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
318318
if (!instance) {
319319
throw new ResponseError(ErrorCodes.NOT_FOUND, `Workspace ${workspaceId} has no running instance`);
320320
}
321-
await this.guardAccess({ kind: "workspaceInstance", subject: instance, workspace}, "get");
321+
await this.guardAccess({ kind: "workspaceInstance", subject: instance, workspace }, "get");
322322

323323
const client = await this.workspaceManagerClientProvider.get(instance.region);
324324
const request = new TakeSnapshotRequest();
@@ -337,13 +337,13 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
337337
await this.internalDoWaitForWorkspace(waitOpts);
338338
} else {
339339
// start driving the snapshot immediately
340-
SafePromise.catchAndLog(this.internalDoWaitForWorkspace(waitOpts), { userId: user.id, workspaceId: workspaceId})
340+
SafePromise.catchAndLog(this.internalDoWaitForWorkspace(waitOpts), { userId: user.id, workspaceId: workspaceId })
341341
}
342342

343343
return snapshot.id;
344344
}
345345

346-
protected async guardSnaphotAccess(ctx: TraceContext, userId: string, workspaceId: string) : Promise<Workspace> {
346+
protected async guardSnaphotAccess(ctx: TraceContext, userId: string, workspaceId: string): Promise<Workspace> {
347347
traceAPIParams(ctx, { userId, workspaceId });
348348

349349
const workspace = await this.workspaceDb.trace(ctx).findById(workspaceId);
@@ -563,6 +563,24 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
563563
return result;
564564
}
565565

566+
async adminSettingsGetTelemetry(ctx: TraceContext): Promise<boolean> {
567+
traceAPIParams(ctx, {});
568+
569+
await this.guardAdminAccess("adminSettingsGetTelemetry", {}, Permission.ADMIN_API);
570+
571+
const settings = await this.installationAdminDb.getTelemetryData();
572+
573+
return settings.settings.sendTelemetry;
574+
}
575+
576+
async adminSettingsSetTelemetry(ctx: TraceContext, send: boolean): Promise<void> {
577+
traceAPIParams(ctx, {});
578+
579+
await this.guardAdminAccess("adminSettingsGetTelemetry", {}, Permission.ADMIN_API);
580+
581+
await this.installationAdminDb.setSendTelemetry(send);
582+
}
583+
566584
async adminForceStopWorkspace(ctx: TraceContext, workspaceId: string): Promise<void> {
567585
traceAPIParams(ctx, { workspaceId });
568586

@@ -715,7 +733,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
715733
}
716734

717735
async adminSetLicense(ctx: TraceContext, key: string): Promise<void> {
718-
traceAPIParams(ctx, { }); // don't trace the actual key
736+
traceAPIParams(ctx, {}); // don't trace the actual key
719737

720738
await this.guardAdminAccess("adminGetWorkspaces", { key }, Permission.ADMIN_API);
721739

@@ -1406,7 +1424,7 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
14061424

14071425
// various
14081426
async sendFeedback(ctx: TraceContext, feedback: string): Promise<string | undefined> {
1409-
traceAPIParams(ctx, { }); // feedback is not interesting here, any may contain names
1427+
traceAPIParams(ctx, {}); // feedback is not interesting here, any may contain names
14101428

14111429
const user = this.checkUser("sendFeedback");
14121430
const now = new Date().toISOString();

components/server/src/auth/rate-limiter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
135135
"adminForceStopWorkspace": { group: "default", points: 1 },
136136
"adminRestoreSoftDeletedWorkspace": { group: "default", points: 1 },
137137
"adminSetLicense": { group: "default", points: 1 },
138+
"adminSettingsGetTelemetry": { group: "default", points: 1 },
139+
"adminSettingsSetTelemetry": { group: "default", points: 1 },
138140

139141
"validateLicense": { group: "default", points: 1 },
140142
"getLicenseInfo": { group: "default", points: 1 },

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { DownloadUrlRequest, DownloadUrlResponse, UploadUrlRequest, UploadUrlResponse } from '@gitpod/content-service/lib/blobs_pb';
8-
import { AppInstallationDB, UserDB, UserMessageViewsDB, WorkspaceDB, DBWithTracing, TracedWorkspaceDB, DBGitpodToken, DBUser, UserStorageResourcesDB, TeamDB } from '@gitpod/gitpod-db/lib';
8+
import { AppInstallationDB, UserDB, UserMessageViewsDB, WorkspaceDB, DBWithTracing, TracedWorkspaceDB, DBGitpodToken, DBUser, UserStorageResourcesDB, TeamDB, InstallationAdminDB } from '@gitpod/gitpod-db/lib';
99
import { AuthProviderEntry, AuthProviderInfo, CommitContext, Configuration, CreateWorkspaceMode, DisposableCollection, GetWorkspaceTimeoutResult, GitpodClient as GitpodApiClient, GitpodServer, GitpodToken, GitpodTokenType, InstallPluginsParams, PermissionName, PortVisibility, PrebuiltWorkspace, PrebuiltWorkspaceContext, PreparePluginUploadParams, ResolvedPlugins, ResolvePluginsParams, SetWorkspaceTimeoutResult, StartPrebuildContext, StartWorkspaceResult, Terms, Token, UninstallPluginParams, User, UserEnvVar, UserEnvVarValue, UserInfo, WhitelistedRepository, Workspace, WorkspaceContext, WorkspaceCreationResult, WorkspaceImageBuild, WorkspaceInfo, WorkspaceInstance, WorkspaceInstancePort, WorkspaceInstanceUser, WorkspaceTimeoutDuration, GuessGitTokenScopesParams, GuessedGitTokenScopes, Team, TeamMemberInfo, TeamMembershipInvite, CreateProjectParams, Project, ProviderRepository, TeamMemberRole, WithDefaultConfig, FindPrebuildsParams, PrebuildWithStatus, StartPrebuildResult, ClientHeaderFields } from '@gitpod/gitpod-protocol';
1010
import { AccountStatement } from "@gitpod/gitpod-protocol/lib/accounting-protocol";
1111
import { AdminBlockUserRequest, AdminGetListRequest, AdminGetListResult, AdminGetWorkspacesRequest, AdminModifyPermanentWorkspaceFeatureFlagRequest, AdminModifyRoleOrPermissionRequest, WorkspaceAndInstance } from '@gitpod/gitpod-protocol/lib/admin-protocol';
@@ -77,6 +77,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
7777
@inject(ContextParser) protected contextParser: ContextParser;
7878
@inject(HostContextProvider) protected readonly hostContextProvider: HostContextProvider;
7979
@inject(GitpodFileParser) protected readonly gitpodParser: GitpodFileParser;
80+
@inject(InstallationAdminDB) protected readonly installationAdminDb: InstallationAdminDB;
8081

8182
@inject(WorkspaceStarter) protected readonly workspaceStarter: WorkspaceStarter;
8283
@inject(WorkspaceManagerClientProvider) protected readonly workspaceManagerClientProvider: WorkspaceManagerClientProvider;
@@ -621,7 +622,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
621622
includeHeadless: false,
622623
});
623624
await Promise.all(res.map(ws => this.guardAccess({ kind: "workspace", subject: ws.workspace }, "get")));
624-
await Promise.all(res.map(ws => this.guardAccess({ kind: "workspaceInstance", subject: ws.latestInstance, workspace: ws.workspace}, "get")));
625+
await Promise.all(res.map(ws => this.guardAccess({ kind: "workspaceInstance", subject: ws.latestInstance, workspace: ws.workspace }, "get")));
625626
return res;
626627
}
627628

@@ -1853,7 +1854,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
18531854
}
18541855

18551856
public async getGitpodTokenScopes(ctx: TraceContext, tokenHash: string): Promise<string[]> {
1856-
traceAPIParams(ctx, { }); // do not trace tokenHash
1857+
traceAPIParams(ctx, {}); // do not trace tokenHash
18571858

18581859
const user = this.checkAndBlockUser("getGitpodTokenScopes");
18591860
let token: GitpodToken | undefined;
@@ -1871,7 +1872,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
18711872
}
18721873

18731874
public async deleteGitpodToken(ctx: TraceContext, tokenHash: string): Promise<void> {
1874-
traceAPIParams(ctx, { }); // do not trace tokenHash
1875+
traceAPIParams(ctx, {}); // do not trace tokenHash
18751876

18761877
const user = this.checkAndBlockUser("deleteGitpodToken");
18771878
const existingTokens = await this.getGitpodTokens(ctx); // all tokens for logged in user
@@ -1984,6 +1985,15 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
19841985
throw new ResponseError(ErrorCodes.EE_FEATURE, `Admin support is implemented in Gitpod's Enterprise Edition`);
19851986
}
19861987

1988+
async adminSettingsGetTelemetry(ctx: TraceContext): Promise<boolean> {
1989+
console.log(2222)
1990+
return false;
1991+
}
1992+
1993+
async adminSettingsSetTelemetry(ctx: TraceContext, send: boolean): Promise<void> {
1994+
console.log(3333);
1995+
}
1996+
19871997
async getLicenseInfo(): Promise<GetLicenseInfoResult> {
19881998
throw new ResponseError(ErrorCodes.EE_FEATURE, `Licensing is implemented in Gitpod's Enterprise Edition`);
19891999
}
@@ -2040,7 +2050,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
20402050
protected validHostNameRegexp = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(\/([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))?$/;
20412051

20422052
async updateOwnAuthProvider(ctx: TraceContext, { entry }: GitpodServer.UpdateOwnAuthProviderParams): Promise<AuthProviderEntry> {
2043-
traceAPIParams(ctx, { }); // entry contains PII
2053+
traceAPIParams(ctx, {}); // entry contains PII
20442054

20452055
let userId: string;
20462056
try {

0 commit comments

Comments
 (0)