Skip to content

Commit 24aea53

Browse files
author
Simon Emms
committed
[dashboard]: create a settings tab inside admin
1 parent 4f2e71e commit 24aea53

File tree

14 files changed

+191
-48
lines changed

14 files changed

+191
-48
lines changed

components/dashboard/src/App.tsx

Lines changed: 12 additions & 3 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,11 +100,12 @@ export function getURLHash() {
98100
function App() {
99101
const { user, setUser } = useContext(UserContext);
100102
const { teams, setTeams } = useContext(TeamsContext);
103+
const { setAdminSettings } = useContext(AdminContext);
101104
const { setIsDark } = useContext(ThemeContext);
102105

103-
const [ loading, setLoading ] = useState<boolean>(true);
104-
const [ isWhatsNewShown, setWhatsNewShown ] = useState(false);
105-
const [ isSetupRequired, setSetupRequired ] = useState(false);
106+
const [loading, setLoading] = useState<boolean>(true);
107+
const [isWhatsNewShown, setWhatsNewShown] = useState(false);
108+
const [isSetupRequired, setSetupRequired] = useState(false);
106109
const history = useHistory();
107110

108111
useEffect(() => {
@@ -132,6 +135,11 @@ function App() {
132135
}
133136
}
134137
setTeams(teams);
138+
139+
if (user?.rolesOrPermissions?.includes('admin')) {
140+
const adminSettings = await getGitpodService().server.adminGetSettings();
141+
setAdminSettings(adminSettings);
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
import { InstallationAdminSettings } from "@gitpod/gitpod-protocol";
9+
10+
const AdminContext = createContext<{
11+
adminSettings?: InstallationAdminSettings,
12+
setAdminSettings: React.Dispatch<InstallationAdminSettings>,
13+
}>({
14+
setAdminSettings: () => null,
15+
});
16+
17+
const AdminContextProvider: React.FC = ({ children }) => {
18+
const [adminSettings, setAdminSettings] = useState<InstallationAdminSettings>();
19+
return (
20+
<AdminContext.Provider value={{ adminSettings, setAdminSettings }}>
21+
{children}
22+
</AdminContext.Provider>
23+
);
24+
};
25+
26+
export { AdminContext, AdminContextProvider };
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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 { InstallationAdminSettings } from "@gitpod/gitpod-protocol";
9+
import { AdminContext } from "../admin-context";
10+
import CheckBox from "../components/CheckBox";
11+
import { PageWithSubMenu } from "../components/PageWithSubMenu";
12+
import { getGitpodService } from "../service/service";
13+
import { adminMenu } from "./admin-menu";
14+
15+
export default function Settings() {
16+
const { adminSettings, setAdminSettings } = useContext(AdminContext);
17+
18+
const actuallySetTelemetryPrefs = async (value: InstallationAdminSettings) => {
19+
await getGitpodService().server.adminUpdateSettings(value);
20+
setAdminSettings(value);
21+
}
22+
23+
return (
24+
<div>
25+
<PageWithSubMenu subMenu={adminMenu} title="Settings" subtitle="Configure settings for your Gitpod cluster.">
26+
<h3>Usage Statistics</h3>
27+
<CheckBox
28+
title="Enable Service Ping"
29+
desc={<span>This is used to provide insights on how you use your cluster so we can provide a better overall experience. <a className="gp-link" href="https://www.gitpod.io/privacy">Read our Privacy Policy</a></span>}
30+
checked={adminSettings?.sendTelemetry ?? false}
31+
onChange={(evt) => actuallySetTelemetryPrefs({
32+
sendTelemetry: evt.target.checked,
33+
})} />
34+
</PageWithSubMenu>
35+
</div >
36+
)
37+
}

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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import { InstallationAdmin } from "@gitpod/gitpod-protocol";
7+
import { InstallationAdmin, InstallationAdminSettings } from "@gitpod/gitpod-protocol";
88

99
export const InstallationAdminDB = Symbol('InstallationAdminDB');
1010
export interface InstallationAdminDB {
11-
getTelemetryData(): Promise<InstallationAdmin>;
11+
getData(): Promise<InstallationAdmin>;
12+
setSettings(settings: InstallationAdminSettings): Promise<void>;
1213
}

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

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

77
import { inject, injectable, } from 'inversify';
8-
import { InstallationAdmin } from '@gitpod/gitpod-protocol';
9-
import { v4 as uuidv4 } from 'uuid';
8+
import { InstallationAdmin, InstallationAdminSettings } from '@gitpod/gitpod-protocol';
109
import { Repository } from 'typeorm';
1110
import { TypeORM } from './typeorm';
1211
import { InstallationAdminDB } from '../installation-admin-db';
@@ -21,12 +20,7 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
2120
}
2221

2322
protected async createDefaultRecord(): Promise<InstallationAdmin> {
24-
const record: InstallationAdmin = {
25-
id: uuidv4(),
26-
settings: {
27-
sendTelemetry: false,
28-
},
29-
};
23+
const record = InstallationAdmin.createDefault();
3024

3125
const repo = await this.getInstallationAdminRepo();
3226
return repo.save(record);
@@ -37,14 +31,14 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
3731
}
3832

3933
/**
40-
* Get Telemetry Data
34+
* Get Data
4135
*
4236
* Returns the first record found or creates a
4337
* new record.
4438
*
4539
* @returns Promise<InstallationAdmin>
4640
*/
47-
async getTelemetryData(): Promise<InstallationAdmin> {
41+
async getData(): Promise<InstallationAdmin> {
4842
const repo = await this.getInstallationAdminRepo();
4943
const [record] = await repo.find();
5044

@@ -55,4 +49,14 @@ export class TypeORMInstallationAdminImpl implements InstallationAdminDB {
5549
/* Record not found - create one */
5650
return this.createDefaultRecord();
5751
}
52+
53+
async setSettings(settings: InstallationAdminSettings): Promise<void> {
54+
const record = await this.getData();
55+
record.settings = {
56+
...settings,
57+
}
58+
59+
const repo = await this.getInstallationAdminRepo();
60+
await repo.save(record);
61+
}
5862
}

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { User, Workspace, NamedWorkspaceFeatureFlag } from "./protocol";
88
import { WorkspaceInstance, WorkspaceInstancePhase } from "./workspace-instance";
99
import { RoleOrPermission } from "./permission";
1010
import { AccountStatement } from "./accounting-protocol";
11+
import { InstallationAdminSettings } from "./installation-admin-protocol";
1112

1213
export interface AdminServer {
1314
adminGetUsers(req: AdminGetListRequest<User>): Promise<AdminGetListResult<User>>;
@@ -29,6 +30,9 @@ export interface AdminServer {
2930
adminIsStudent(userId: string): Promise<boolean>;
3031
adminAddStudentEmailDomain(userId: string, domain: string): Promise<void>;
3132
adminGrantExtraHours(userId: string, extraHours: number): Promise<void>;
33+
34+
adminGetSettings(): Promise<InstallationAdminSettings>
35+
adminUpdateSettings(settings: InstallationAdminSettings): Promise<void>
3236
}
3337

3438
export interface AdminGetListRequest<T> {
@@ -65,7 +69,7 @@ export interface AdminModifyPermanentWorkspaceFeatureFlagRequest {
6569
}[]
6670
}
6771

68-
export interface WorkspaceAndInstance extends Omit<Workspace, "id"|"creationTime">, Omit<WorkspaceInstance, "id"|"creationTime"> {
72+
export interface WorkspaceAndInstance extends Omit<Workspace, "id" | "creationTime">, Omit<WorkspaceInstance, "id" | "creationTime"> {
6973
workspaceId: string;
7074
workspaceCreationTime: string;
7175
instanceId: string;
@@ -78,7 +82,7 @@ export namespace WorkspaceAndInstance {
7882
return {
7983
id: wai.workspaceId,
8084
creationTime: wai.workspaceCreationTime,
81-
... wai
85+
...wai
8286
};
8387
}
8488

@@ -89,7 +93,7 @@ export namespace WorkspaceAndInstance {
8993
return {
9094
id: wai.instanceId,
9195
creationTime: wai.instanceCreationTime,
92-
... wai
96+
...wai
9397
};
9498
}
9599
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { GithubUpgradeURL, PlanCoupon } from './payment-protocol';
3030
import { TeamSubscription, TeamSubscriptionSlot, TeamSubscriptionSlotResolved } from './team-subscription-protocol';
3131
import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from './analytics';
3232
import { IDEServer } from './ide-protocol';
33+
import { InstallationAdminSettings } from './installation-admin-protocol';
3334

3435
export interface GitpodClient {
3536
onInstanceUpdate(instance: WorkspaceInstance): void;
@@ -131,6 +132,10 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
131132
resetGenericInvite(inviteId: string): Promise<TeamMembershipInvite>;
132133
deleteTeam(teamId: string, userId: string): Promise<void>;
133134

135+
// Admin Settings
136+
adminGetSettings(): Promise<InstallationAdminSettings>;
137+
adminUpdateSettings(settings: InstallationAdminSettings): Promise<void>;
138+
134139
// Projects
135140
getProviderRepositoriesForUser(params: GetProviderRepositoriesParams): Promise<ProviderRepository[]>;
136141
createProject(params: CreateProjectParams): Promise<Project>;

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,32 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
export interface InstallationAdminSettings {
8-
sendTelemetry: boolean;
7+
import { v4 as uuidv4 } from 'uuid';
8+
9+
const InstallationAdminSettingsPrototype = {
10+
sendTelemetry: true
11+
}
12+
13+
export type InstallationAdminSettings = typeof InstallationAdminSettingsPrototype;
14+
15+
export namespace InstallationAdminSettings {
16+
export function fields(): (keyof InstallationAdminSettings)[] {
17+
return Object.keys(InstallationAdminSettingsPrototype) as (keyof InstallationAdminSettings)[];
18+
}
919
}
1020

1121
export interface InstallationAdmin {
1222
id: string;
1323
settings: InstallationAdminSettings;
14-
}
24+
}
25+
26+
export namespace InstallationAdmin {
27+
export function createDefault(): InstallationAdmin {
28+
return {
29+
id: uuidv4(),
30+
settings: {
31+
...InstallationAdminSettingsPrototype,
32+
}
33+
};
34+
}
35+
}

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { injectable, inject } from "inversify";
88
import { GitpodServerImpl, traceAPIParams, traceWI, censor } from "../../../src/workspace/gitpod-server-impl";
99
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
10-
import { GitpodServer, GitpodClient, AdminGetListRequest, User, AdminGetListResult, Permission, AdminBlockUserRequest, AdminModifyRoleOrPermissionRequest, RoleOrPermission, AdminModifyPermanentWorkspaceFeatureFlagRequest, UserFeatureSettings, AdminGetWorkspacesRequest, WorkspaceAndInstance, GetWorkspaceTimeoutResult, WorkspaceTimeoutDuration, WorkspaceTimeoutValues, SetWorkspaceTimeoutResult, WorkspaceContext, CreateWorkspaceMode, WorkspaceCreationResult, PrebuiltWorkspaceContext, CommitContext, PrebuiltWorkspace, PermissionName, WorkspaceInstance, EduEmailDomain, ProviderRepository, Queue, PrebuildWithStatus, CreateProjectParams, Project, StartPrebuildResult, ClientHeaderFields, Workspace } from "@gitpod/gitpod-protocol";
10+
import { GitpodServer, GitpodClient, AdminGetListRequest, User, AdminGetListResult, Permission, AdminBlockUserRequest, AdminModifyRoleOrPermissionRequest, RoleOrPermission, AdminModifyPermanentWorkspaceFeatureFlagRequest, UserFeatureSettings, AdminGetWorkspacesRequest, WorkspaceAndInstance, GetWorkspaceTimeoutResult, WorkspaceTimeoutDuration, WorkspaceTimeoutValues, SetWorkspaceTimeoutResult, WorkspaceContext, CreateWorkspaceMode, WorkspaceCreationResult, PrebuiltWorkspaceContext, CommitContext, PrebuiltWorkspace, WorkspaceInstance, EduEmailDomain, ProviderRepository, Queue, PrebuildWithStatus, CreateProjectParams, Project, StartPrebuildResult, ClientHeaderFields, Workspace } from "@gitpod/gitpod-protocol";
1111
import { ResponseError } from "vscode-jsonrpc";
1212
import { TakeSnapshotRequest, AdmissionLevel, ControlAdmissionRequest, StopWorkspacePolicy, DescribeWorkspaceRequest, SetTimeoutRequest } from "@gitpod/ws-manager/lib";
1313
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
@@ -602,15 +602,6 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
602602
});
603603
}
604604

605-
protected async guardAdminAccess(method: string, params: any, requiredPermission: PermissionName) {
606-
const user = this.checkAndBlockUser(method);
607-
if (!this.authorizationService.hasPermission(user, requiredPermission)) {
608-
log.warn({ userId: this.user?.id }, "unauthorised admin access", { authorised: false, method, params });
609-
throw new ResponseError(ErrorCodes.PERMISSION_DENIED, "not allowed");
610-
}
611-
log.info({ userId: this.user?.id }, "admin access", { authorised: true, method, params });
612-
}
613-
614605
protected async findPrebuiltWorkspace(parentCtx: TraceContext, user: User, context: WorkspaceContext, mode: CreateWorkspaceMode): Promise<WorkspaceCreationResult | PrebuiltWorkspaceContext | undefined> {
615606
const ctx = TraceContext.childContext("findPrebuiltWorkspace", parentCtx);
616607

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig {
136136
"adminForceStopWorkspace": { group: "default", points: 1 },
137137
"adminRestoreSoftDeletedWorkspace": { group: "default", points: 1 },
138138
"adminSetLicense": { group: "default", points: 1 },
139+
"adminGetSettings": { group: "default", points: 1 },
140+
"adminUpdateSettings": { group: "default", points: 1 },
139141

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

components/server/src/installation-admin/installation-admin-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class InstallationAdminController {
1616
const app = express();
1717

1818
app.get('/data', async (req: express.Request, res: express.Response) => {
19-
const data = await this.installationAdminDb.getTelemetryData();
19+
const data = await this.installationAdminDb.getData();
2020

2121
res.send(data);
2222
});

0 commit comments

Comments
 (0)