Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions components/dashboard/src/admin/BlockedEmailDomains.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import { ItemFieldContextMenu } from "../components/ItemsList";
import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal";
import { CheckboxInputField } from "../components/forms/CheckboxInputField";
import searchIcon from "../icons/search.svg";
import { getGitpodService } from "../service/service";
import { AdminPageHeader } from "./AdminPageHeader";
import Pagination from "../Pagination/Pagination";
import { Button } from "@podkit/buttons/Button";
import { installationClient } from "../service/public-api";
import { ListBlockedEmailDomainsResponse } from "@gitpod/public-api/lib/gitpod/v1/installation_pb";

export function BlockedEmailDomains() {
return (
Expand All @@ -27,7 +28,7 @@ export function BlockedEmailDomains() {
}

function useBlockedEmailDomains() {
return useQuery(["blockedEmailDomains"], () => getGitpodService().server.adminGetBlockedEmailDomains(), {
return useQuery(["blockedEmailDomains"], () => installationClient.listBlockedEmailDomains({}), {
staleTime: 1000 * 60 * 5, // 5min
});
}
Expand All @@ -37,19 +38,21 @@ function useUpdateBlockedEmailDomainMutation() {
const blockedEmailDomains = useBlockedEmailDomains();
return useMutation(
async (blockedDomain: EmailDomainFilterEntry) => {
await getGitpodService().server.adminSaveBlockedEmailDomain(blockedDomain);
await installationClient.createBlockedEmailDomain({
domain: blockedDomain.domain,
negative: blockedDomain.negative ?? false,
});
},
{
onSuccess: (_, blockedDomain) => {
const updated = [];
for (const entry of blockedEmailDomains.data || []) {
const data = new ListBlockedEmailDomainsResponse(blockedEmailDomains.data);
data.blockedEmailDomains.map((entry) => {
if (entry.domain !== blockedDomain.domain) {
updated.push(entry);
} else {
updated.push(blockedDomain);
return entry;
}
}
queryClient.setQueryData(["blockedEmailDomains"], updated);
return blockedDomain;
});
queryClient.setQueryData(["blockedEmailDomains"], data);
blockedEmailDomains.refetch();
},
},
Expand All @@ -74,7 +77,7 @@ export function BlockedEmailDomainsList(props: Props) {
if (!blockedEmailDomains.data) {
return [];
}
return blockedEmailDomains.data.filter((entry) =>
return blockedEmailDomains.data.blockedEmailDomains.filter((entry) =>
entry.domain.toLowerCase().includes(searchTerm.toLowerCase()),
);
}, [blockedEmailDomains.data, searchTerm]);
Expand Down
69 changes: 43 additions & 26 deletions components/dashboard/src/admin/BlockedRepositories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
* See License.AGPL.txt in the project root for license information.
*/

import { AdminGetListResult } from "@gitpod/gitpod-protocol";
import { useCallback, useEffect, useRef, useState } from "react";
import { getGitpodService } from "../service/service";
import { AdminPageHeader } from "./AdminPageHeader";
import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositories-protocol";
import ConfirmationModal from "../components/ConfirmationModal";
import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal";
import { CheckboxInputField } from "../components/forms/CheckboxInputField";
Expand All @@ -18,6 +15,9 @@ import Alert from "../components/Alert";
import { SpinnerLoader } from "../components/Loader";
import searchIcon from "../icons/search.svg";
import { Button } from "@podkit/buttons/Button";
import { installationClient } from "../service/public-api";
import { Sort, SortOrder } from "@gitpod/public-api/lib/gitpod/v1/sorting_pb";
import { BlockedRepository, ListBlockedRepositoriesResponse } from "@gitpod/public-api/lib/gitpod/v1/installation_pb";

export function BlockedRepositories() {
return (
Expand All @@ -33,27 +33,40 @@ type ExistingBlockedRepository = Pick<BlockedRepository, "id" | "urlRegexp" | "b
interface Props {}

export function BlockedRepositoriesList(props: Props) {
const [searchResult, setSearchResult] = useState<AdminGetListResult<BlockedRepository>>({ rows: [], total: 0 });
const [searchResult, setSearchResult] = useState<ListBlockedRepositoriesResponse>(
new ListBlockedRepositoriesResponse({
blockedRepositories: [],
}),
);
const [queryTerm, setQueryTerm] = useState("");
const [searching, setSearching] = useState(false);

const [isAddModalVisible, setAddModalVisible] = useState(false);
const [isDeleteModalVisible, setDeleteModalVisible] = useState(false);

const [currentBlockedRepository, setCurrentBlockedRepository] = useState<ExistingBlockedRepository>({
id: 0,
urlRegexp: "",
blockUser: false,
});
const [currentBlockedRepository, setCurrentBlockedRepository] = useState<BlockedRepository>(
new BlockedRepository({
id: 0,
urlRegexp: "",
blockUser: false,
}),
);

const search = async () => {
setSearching(true);
try {
const result = await getGitpodService().server.adminGetBlockedRepositories({
limit: 100,
orderBy: "urlRegexp",
offset: 0,
orderDir: "asc",
const result = await installationClient.listBlockedRepositories({
// Don't need, added it in json-rpc implement to make life easier.
// pagination: new PaginationRequest({
// token: Buffer.from(JSON.stringify({ offset: 0 })).toString("base64"),
// pageSize: 100,
// }),
sort: [
new Sort({
field: "urlRegexp",
order: SortOrder.ASC,
}),
],
searchTerm: queryTerm,
});
setSearchResult(result);
Expand All @@ -67,19 +80,21 @@ export function BlockedRepositoriesList(props: Props) {
}, []);

const add = () => {
setCurrentBlockedRepository({
id: 0,
urlRegexp: "",
blockUser: false,
});
setCurrentBlockedRepository(
new BlockedRepository({
id: 0,
urlRegexp: "",
blockUser: false,
}),
);
setAddModalVisible(true);
};

const save = async (blockedRepository: NewBlockedRepository) => {
await getGitpodService().server.adminCreateBlockedRepository(
blockedRepository.urlRegexp,
blockedRepository.blockUser,
);
await installationClient.createBlockedRepository({
urlRegexp: blockedRepository.urlRegexp ?? "",
blockUser: blockedRepository.blockUser ?? false,
});
setAddModalVisible(false);
search();
};
Expand All @@ -91,11 +106,13 @@ export function BlockedRepositoriesList(props: Props) {
};

const deleteBlockedRepository = async (blockedRepository: ExistingBlockedRepository) => {
await getGitpodService().server.adminDeleteBlockedRepository(blockedRepository.id);
await installationClient.deleteBlockedRepository({
blockedRepositoryId: blockedRepository.id,
});
search();
};

const confirmDeleteBlockedRepository = (blockedRepository: ExistingBlockedRepository) => {
const confirmDeleteBlockedRepository = (blockedRepository: BlockedRepository) => {
setCurrentBlockedRepository(blockedRepository);
setAddModalVisible(false);
setDeleteModalVisible(true);
Expand Down Expand Up @@ -158,7 +175,7 @@ export function BlockedRepositoriesList(props: Props) {
<div className="w-1/12">Block Users</div>
<div className="w-1/12"></div>
</div>
{searchResult.rows.map((br) => (
{searchResult.blockedRepositories.map((br) => (
<BlockedRepositoryEntry br={br} confirmedDelete={confirmDeleteBlockedRepository} />
))}
</div>
Expand Down
4 changes: 3 additions & 1 deletion components/dashboard/src/data/setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ import * as AuthProviderClasses from "@gitpod/public-api/lib/gitpod/v1/authprovi
import * as EnvVarClasses from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";
import * as PrebuildClasses from "@gitpod/public-api/lib/gitpod/v1/prebuild_pb";
import * as VerificationClasses from "@gitpod/public-api/lib/gitpod/v1/verification_pb";
import * as InstallationClasses from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
import * as SCMClasses from "@gitpod/public-api/lib/gitpod/v1/scm_pb";
import * as SSHClasses from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";

// This is used to version the cache
// If data we cache changes in a non-backwards compatible way, increment this version
// That will bust any previous cache versions a client may have stored
const CACHE_VERSION = "11";
const CACHE_VERSION = "12";

export function noPersistence(queryKey: QueryKey): QueryKey {
return [...queryKey, "no-persistence"];
Expand Down Expand Up @@ -154,6 +155,7 @@ function initializeMessages() {
...Object.values(EnvVarClasses),
...Object.values(PrebuildClasses),
...Object.values(VerificationClasses),
...Object.values(InstallationClasses),
...Object.values(SCMClasses),
...Object.values(SSHClasses),
];
Expand Down
101 changes: 101 additions & 0 deletions components/dashboard/src/service/json-rpc-installation-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
* Licensed under the GNU Affero General Public License (AGPL).
* See License.AGPL.txt in the project root for license information.
*/

import { CallOptions, PromiseClient } from "@connectrpc/connect";
import { PartialMessage } from "@bufbuild/protobuf";
import { InstallationService } from "@gitpod/public-api/lib/gitpod/v1/installation_connect";
import {
ListBlockedRepositoriesRequest,
ListBlockedRepositoriesResponse,
CreateBlockedRepositoryRequest,
CreateBlockedRepositoryResponse,
DeleteBlockedRepositoryRequest,
DeleteBlockedRepositoryResponse,
ListBlockedEmailDomainsRequest,
ListBlockedEmailDomainsResponse,
CreateBlockedEmailDomainRequest,
CreateBlockedEmailDomainResponse,
} from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
import { getGitpodService } from "./service";
import { converter } from "./public-api";
import { PaginationResponse } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb";

export class JsonRpcInstallationClient implements PromiseClient<typeof InstallationService> {
async listBlockedRepositories(
request: PartialMessage<ListBlockedRepositoriesRequest>,
_options?: CallOptions | undefined,
): Promise<ListBlockedRepositoriesResponse> {
// dashboard params is constant, no need to implement
const info = await getGitpodService().server.adminGetBlockedRepositories({
limit: 100,
offset: 0,
orderBy: "urlRegexp",
orderDir: "asc",
searchTerm: request.searchTerm,
});
return new ListBlockedRepositoriesResponse({
blockedRepositories: info.rows.map((item) => converter.toBlockedRepository(item)),
pagination: new PaginationResponse(),
});
}

async createBlockedRepository(
request: PartialMessage<CreateBlockedRepositoryRequest>,
_options?: CallOptions | undefined,
): Promise<CreateBlockedRepositoryResponse> {
if (!request.urlRegexp) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "urlRegexp is required");
}
if (request.blockUser === undefined) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "blockUser is required");
}
const info = await getGitpodService().server.adminCreateBlockedRepository(request.urlRegexp, request.blockUser);
return new CreateBlockedRepositoryResponse({
blockedRepository: converter.toBlockedRepository(info),
});
}

async deleteBlockedRepository(
request: PartialMessage<DeleteBlockedRepositoryRequest>,
_options?: CallOptions | undefined,
): Promise<DeleteBlockedRepositoryResponse> {
if (!request.blockedRepositoryId) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "blockedRepositoryId is required");
}
await getGitpodService().server.adminDeleteBlockedRepository(request.blockedRepositoryId);
return new DeleteBlockedRepositoryResponse();
}

async listBlockedEmailDomains(
request: PartialMessage<ListBlockedEmailDomainsRequest>,
_options?: CallOptions | undefined,
): Promise<ListBlockedEmailDomainsResponse> {
const info = await getGitpodService().server.adminGetBlockedEmailDomains();
return new ListBlockedEmailDomainsResponse({
blockedEmailDomains: info.map((item) => converter.toBlockedEmailDomain(item)),
pagination: new PaginationResponse(),
});
}

async createBlockedEmailDomain(
request: PartialMessage<CreateBlockedEmailDomainRequest>,
_options?: CallOptions | undefined,
): Promise<CreateBlockedEmailDomainResponse> {
if (!request.domain) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "domain is required");
}
if (request.negative === undefined) {
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "negative is required");
}
await getGitpodService().server.adminSaveBlockedEmailDomain({
domain: request.domain,
negative: request.negative,
});
// There's no way to get blockedEmailDomain, just ignore it since dashboard don't care about the response data
return new CreateBlockedEmailDomainResponse({});
}
}
8 changes: 8 additions & 0 deletions components/dashboard/src/service/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { SSHService } from "@gitpod/public-api/lib/gitpod/v1/ssh_connect";
import { JsonRpcSSHClient } from "./json-rpc-ssh-client";
import { JsonRpcVerificationClient } from "./json-rpc-verification-client";
import { VerificationService } from "@gitpod/public-api/lib/gitpod/v1/verification_connect";
import { JsonRpcInstallationClient } from "./json-rpc-installation-client";
import { InstallationService } from "@gitpod/public-api/lib/gitpod/v1/installation_connect";

const transport = createConnectTransport({
baseUrl: `${window.location.protocol}//${window.location.host}/public-api`,
Expand Down Expand Up @@ -66,6 +68,7 @@ export const organizationClient = createServiceClient(OrganizationService, {
client: new JsonRpcOrganizationClient(),
featureFlagSuffix: "organization",
});

// No jsonrcp client for the configuration service as it's only used in new UI of the dashboard
export const configurationClient = createServiceClient(ConfigurationService);
export const prebuildClient = createServiceClient(PrebuildService, {
Expand Down Expand Up @@ -98,6 +101,11 @@ export const verificationClient = createServiceClient(VerificationService, {
featureFlagSuffix: "verification",
});

export const installationClient = createServiceClient(InstallationService, {
client: new JsonRpcInstallationClient(),
featureFlagSuffix: "installation",
});

export async function listAllProjects(opts: { orgId: string }): Promise<ProtocolProject[]> {
let pagination = {
page: 1,
Expand Down
2 changes: 1 addition & 1 deletion components/gitpod-db/src/email-domain-filter-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EmailDomainFilterEntry } from "@gitpod/gitpod-protocol";

export const EmailDomainFilterDB = Symbol("EmailDomainFilterDB");
export interface EmailDomainFilterDB {
storeFilterEntry(entry: EmailDomainFilterEntry): Promise<void>;
storeFilterEntry(entry: EmailDomainFilterEntry): Promise<EmailDomainFilterEntry>;
getFilterEntries(): Promise<EmailDomainFilterEntry[]>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export class EmailDomainFilterDBImpl implements EmailDomainFilterDB {
return (await this.getManager()).getRepository<DBEmailDomainFilterEntry>(DBEmailDomainFilterEntry);
}

async storeFilterEntry(entry: EmailDomainFilterEntry): Promise<void> {
async storeFilterEntry(entry: EmailDomainFilterEntry): Promise<EmailDomainFilterEntry> {
const repo = await this.getRepo();
await repo.save(entry);
return await repo.save(entry);
}

async getFilterEntries(): Promise<EmailDomainFilterEntry[]> {
Expand Down
Loading