From 859815650e70a9a7f46c110c80616acb6f8badab Mon Sep 17 00:00:00 2001 From: minwoox Date: Fri, 27 Dec 2024 13:09:37 +0900 Subject: [PATCH] WIP --- .../internal/api/CredentialServiceV1.java | 17 +- .../repository/DefaultMetaRepository.java | 24 +-- .../storage/repository/MetaRepository.java | 1 - .../internal/api/CredentialServiceV1Test.java | 2 +- webapp/src/dogma/features/api/apiSlice.ts | 57 ++++++- .../src/dogma/features/auth/ProjectRole.tsx | 2 +- .../dogma/features/auth/RepositoryRole.tsx | 32 ++++ .../src/dogma/features/project/ProjectDto.ts | 5 +- .../project/settings/AppEntityList.tsx | 87 ++++++++++ .../DeleteMember.tsx => DeleteAppEntity.tsx} | 31 ++-- .../settings/DeleteConfirmationModal.tsx | 4 +- .../project/settings/ProjectSettingsView.tsx | 5 +- .../settings/credentials/CredentialForm.tsx | 25 ++- .../settings/credentials/CredentialList.tsx | 30 ++-- .../settings/credentials/CredentialView.tsx | 29 +++- .../settings/credentials/DeleteCredential.tsx | 7 +- .../settings/members/AppMemberList.tsx | 62 ------- .../project/settings/mirrors/MirrorForm.tsx | 1 + .../settings/repositories/RepoMetaList.tsx | 2 + .../project/settings/tokens/AppTokenList.tsx | 58 ------- .../project/settings/tokens/DeleteToken.tsx | 48 ------ webapp/src/dogma/features/repo/DeleteRepo.tsx | 8 +- .../src/dogma/features/repo/RepoRoleList.tsx | 2 +- .../features/repo/RepositoriesMetadataDto.ts | 2 +- .../repo/roles/AddUserRepositoryRoleDto.ts | 10 -- .../features/repo/roles/ProjectRolesForm.tsx | 5 +- .../AddUserOrTokenRepositoryRoleDto.ts | 10 ++ .../ConfirmAddUserOrTokenRepositoryRole.tsx} | 22 ++- .../DeleteUserOrTokenRepositoryRoleDto.ts} | 2 +- .../repo/settings/RepositorySettingsView.tsx | 61 +++---- .../UserOrTokenRepositoryRoleList.tsx} | 37 +++-- .../tokens}/NewTokenRepositoryRole.tsx | 9 +- .../users}/NewUserRepositoryRole.tsx | 9 +- .../api/v1/projects/[projectName]/index.ts | 2 +- .../repos/[repoName]/roles/index.tsx | 155 ------------------ .../settings/credentials/[id]/edit/index.tsx | 78 +++++++++ .../settings/credentials/[id]/index.tsx | 48 ++++++ .../[repoName]/settings/credentials/index.tsx | 66 ++++++++ .../settings/credentials/new/index.tsx | 70 ++++++++ .../[repoName]/settings/danger-zone/index.tsx | 27 +++ .../repos/[repoName]/settings/index.tsx | 16 +- .../[repoName]/settings/tokens/index.tsx | 68 ++++++++ .../repos/[repoName]/settings/users/index.tsx | 67 ++++++++ .../tree/[revision]/[[...path]]/index.tsx | 2 +- .../projects/[projectName]/repos/index.tsx | 12 ++ .../settings/credentials/index.tsx | 11 +- .../[projectName]/settings/members/index.tsx | 20 ++- .../[projectName]/settings/tokens/index.tsx | 13 +- .../feature/repo/RepoPermissionList.test.tsx | 5 +- .../xds/k8s/v1/XdsKubernetesServiceTest.java | 4 +- 50 files changed, 876 insertions(+), 494 deletions(-) create mode 100644 webapp/src/dogma/features/auth/RepositoryRole.tsx create mode 100644 webapp/src/dogma/features/project/settings/AppEntityList.tsx rename webapp/src/dogma/features/project/settings/{members/DeleteMember.tsx => DeleteAppEntity.tsx} (68%) delete mode 100644 webapp/src/dogma/features/project/settings/members/AppMemberList.tsx delete mode 100644 webapp/src/dogma/features/project/settings/tokens/AppTokenList.tsx delete mode 100644 webapp/src/dogma/features/project/settings/tokens/DeleteToken.tsx delete mode 100644 webapp/src/dogma/features/repo/roles/AddUserRepositoryRoleDto.ts create mode 100644 webapp/src/dogma/features/repo/settings/AddUserOrTokenRepositoryRoleDto.ts rename webapp/src/dogma/features/repo/{roles/ConfirmAddUserRepositoryRole.tsx => settings/ConfirmAddUserOrTokenRepositoryRole.tsx} (78%) rename webapp/src/dogma/features/repo/{roles/DeleteUserRepositoryRoleDto.ts => settings/DeleteUserOrTokenRepositoryRoleDto.ts} (52%) rename webapp/src/dogma/features/repo/{roles/UserRepositoryRole.tsx => settings/UserOrTokenRepositoryRoleList.tsx} (66%) rename webapp/src/dogma/features/repo/{roles => settings/tokens}/NewTokenRepositoryRole.tsx (92%) rename webapp/src/dogma/features/repo/{roles => settings/users}/NewUserRepositoryRole.tsx (92%) delete mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/roles/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/[id]/edit/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/[id]/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/new/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/danger-zone/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/tokens/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/users/index.tsx create mode 100644 webapp/src/pages/app/projects/[projectName]/repos/index.tsx diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java index e634eae20f..63c623a44c 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1.java @@ -79,9 +79,12 @@ private static CompletableFuture> credentials(User loginUser, if (loginUser.isSystemAdmin()) { return future; } - return future.thenApply(credentials -> credentials.stream() - .map(Credential::withoutSecret) - .collect(toImmutableList())); + return future.thenApply(credentials -> { + return credentials + .stream() + .map(Credential::withoutSecret) + .collect(toImmutableList()); + }); } /** @@ -169,7 +172,7 @@ private MetaRepository metaRepo(String projectName, User user) { // Repository level credential management APIs. /** - * GET /projects/{projectName}/repos/{repoName}credentials + * GET /projects/{projectName}/repos/{repoName}/credentials * *

Returns the list of the credentials in the repository. */ @@ -207,10 +210,10 @@ public CompletableFuture getRepoCredentialById(User loginUser, * *

Creates a new credential. */ - @RequiresRepositoryRole(RepositoryRole.ADMIN) - @Post("/projects/{projectName}/repos/{repoName}/credentials") @ConsumesJson @StatusCode(201) + @RequiresRepositoryRole(RepositoryRole.ADMIN) + @Post("/projects/{projectName}/repos/{repoName}/credentials") public CompletableFuture createRepoCredential(@Param String projectName, Repository repository, Credential credential, Author author, @@ -223,9 +226,9 @@ public CompletableFuture createRepoCredential(@Param String proje * *

Update the existing credential. */ + @ConsumesJson @RequiresRepositoryRole(RepositoryRole.ADMIN) @Put("/projects/{projectName}/repos/{repoName}/credentials/{id}") - @ConsumesJson public CompletableFuture updateRepoCredential(@Param String projectName, Repository repository, @Param String id, diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java index b71db9066a..de4d213135 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/storage/repository/DefaultMetaRepository.java @@ -64,13 +64,11 @@ public final class DefaultMetaRepository extends RepositoryWrapper implements Me public static boolean isMetaFile(String path) { return "/mirrors.json".equals(path) || "/credentials.json".equals(path) || - (path.endsWith(".json") && - (path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS))); + (path.endsWith(".json") && (path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS))); } public static boolean isMirrorFile(String path) { - return path.endsWith(".json") && - (path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS)); + return path.endsWith(".json") && (path.startsWith(PATH_CREDENTIALS) || path.startsWith(PATH_MIRRORS)); } public static String credentialFile(String credentialId) { @@ -208,26 +206,22 @@ private List credentials(Map> entries, @Nullable St @Override public CompletableFuture credential(String credentialId) { final String credentialFile = credentialFile(credentialId); - return credential(null, credentialId, credentialFile); + return credential0(credentialFile); } @Override public CompletableFuture credential(String repoName, String id) { final String credentialFile = credentialFile(repoName, id); - return credential(repoName, id, credentialFile); + return credential0(credentialFile); } - private CompletableFuture credential(@Nullable String repoName, String id, - String credentialFile) { + private CompletableFuture credential0(String credentialFile) { return find(credentialFile).thenApply(entries -> { @SuppressWarnings("unchecked") final Entry entry = (Entry) entries.get(credentialFile); if (entry == null) { - String path = parent().name() + '/' + name(); - if (repoName != null) { - path += "/repos/" + repoName; - } - throw new EntryNotFoundException("failed to find credential '" + id + "' in " + path); + throw new EntryNotFoundException("failed to find credential file '" + credentialFile + "' in " + + parent().name() + '/' + name()); } try { @@ -323,9 +317,9 @@ public CompletableFuture> createCredentialPushCommand(Stri return credential(repoName, credential.id()).thenApply(c -> { final String summary = "Update the mirror credential '" + repoName + '/' + credential.id() + '\''; - return newCredentialCommand(credentialFile(repoName, credential.id()), credential, author, summary); + return newCredentialCommand( + credentialFile(repoName, credential.id()), credential, author, summary); }); - } final String summary = "Create a new mirror credential for " + repoName + '/' + credential.id(); return UnmodifiableFuture.completedFuture( diff --git a/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java b/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java index 794100fd71..b2c0fdc2dd 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/storage/repository/MetaRepository.java @@ -90,5 +90,4 @@ CompletableFuture> createCredentialPushCommand(Credential */ CompletableFuture> createCredentialPushCommand(String repoName, Credential credential, Author author, boolean update); - } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1Test.java b/server/src/test/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1Test.java index fa72f444d2..128fb91db6 100644 --- a/server/src/test/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1Test.java +++ b/server/src/test/java/com/linecorp/centraldogma/server/internal/api/CredentialServiceV1Test.java @@ -60,7 +60,7 @@ class CredentialServiceV1Test { @Override protected void configure(CentralDogmaBuilder builder) { builder.authProviderFactory(new TestAuthProviderFactory()); - builder.administrators(USERNAME); + builder.systemAdministrators(USERNAME); } @Override diff --git a/webapp/src/dogma/features/api/apiSlice.ts b/webapp/src/dogma/features/api/apiSlice.ts index 7b85ca7ddb..8abcc10106 100644 --- a/webapp/src/dogma/features/api/apiSlice.ts +++ b/webapp/src/dogma/features/api/apiSlice.ts @@ -25,8 +25,8 @@ import { FileContentDto } from 'dogma/features/file/FileContentDto'; import { RevisionDto } from 'dogma/features/history/RevisionDto'; import { TokenDto } from 'dogma/features/token/TokenDto'; import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; -import { DeleteUserRepositoryRoleDto } from 'dogma/features/repo/roles/DeleteUserRepositoryRoleDto'; -import { AddUserRepositoryRoleDto } from 'dogma/features/repo/roles/AddUserRepositoryRoleDto'; +import { DeleteUserOrTokenRepositoryRoleDto } from 'dogma/features/repo/settings/DeleteUserOrTokenRepositoryRoleDto'; +import { AddUserOrTokenRepositoryRoleDto } from 'dogma/features/repo/settings/AddUserOrTokenRepositoryRoleDto'; import { DeleteMemberDto } from 'dogma/features/project/settings/members/DeleteMemberDto'; import { MirrorDto } from 'dogma/features/project/settings/mirrors/MirrorDto'; import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto'; @@ -186,7 +186,7 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Metadata'], }), - addUserRepositoryRole: builder.mutation({ + addUserRepositoryRole: builder.mutation({ query: ({ projectName, repoName, data }) => ({ url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/users`, method: 'POST', @@ -194,14 +194,14 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Metadata'], }), - deleteUserRepositoryRole: builder.mutation({ + deleteUserRepositoryRole: builder.mutation({ query: ({ projectName, repoName, id }) => ({ url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/users/${id}`, method: 'DELETE', }), invalidatesTags: ['Metadata'], }), - addTokenRepositoryRole: builder.mutation({ + addTokenRepositoryRole: builder.mutation({ query: ({ projectName, repoName, data }) => ({ url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/tokens`, method: 'POST', @@ -209,7 +209,7 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Metadata'], }), - deleteTokenRepositoryRole: builder.mutation({ + deleteTokenRepositoryRole: builder.mutation({ query: ({ projectName, repoName, id }) => ({ url: `/api/v1/metadata/${projectName}/repos/${repoName}/roles/tokens/${id}`, method: 'DELETE', @@ -410,6 +410,46 @@ export const apiSlice = createApi({ }), invalidatesTags: ['Metadata'], }), + getRepoCredentials: builder.query({ + query: ({ projectName, repoName }) => `/api/v1/projects/${projectName}/repos/${repoName}/credentials`, + providesTags: ['Metadata'], + }), + getRepoCredential: builder.query({ + query: ({ projectName, id, repoName }) => + `/api/v1/projects/${projectName}/repos/${repoName}/credentials/${id}`, + providesTags: ['Metadata'], + }), + addNewRepoCredential: builder.mutation< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + any, + { projectName: string; credential: CredentialDto; repoName: string } + >({ + query: ({ projectName, credential, repoName }) => ({ + url: `/api/v1/projects/${projectName}/repos/${repoName}/credentials`, + method: 'POST', + body: credential, + }), + invalidatesTags: ['Metadata'], + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + updateRepoCredential: builder.mutation< + any, + { projectName: string; id: string; credential: CredentialDto; repoName: string } + >({ + query: ({ projectName, id, credential, repoName }) => ({ + url: `/api/v1/projects/${projectName}/repos/${repoName}/credentials/${id}`, + method: 'PUT', + body: credential, + }), + invalidatesTags: ['Metadata'], + }), + deleteRepoCredential: builder.mutation({ + query: ({ projectName, id, repoName }) => ({ + url: `/api/v1/projects/${projectName}/repos/${repoName}/credentials/${id}`, + method: 'DELETE', + }), + invalidatesTags: ['Metadata'], + }), getTitle: builder.query({ query: () => ({ url: `/title`, @@ -468,6 +508,11 @@ export const { useAddNewCredentialMutation, useUpdateCredentialMutation, useDeleteCredentialMutation, + useGetRepoCredentialsQuery, + useGetRepoCredentialQuery, + useAddNewRepoCredentialMutation, + useUpdateRepoCredentialMutation, + useDeleteRepoCredentialMutation, // Title useGetTitleQuery, } = apiSlice; diff --git a/webapp/src/dogma/features/auth/ProjectRole.tsx b/webapp/src/dogma/features/auth/ProjectRole.tsx index 7ab919f4af..b9a8d94a79 100644 --- a/webapp/src/dogma/features/auth/ProjectRole.tsx +++ b/webapp/src/dogma/features/auth/ProjectRole.tsx @@ -4,7 +4,7 @@ import { ReactNode } from 'react'; import { UserDto } from './UserDto'; import { ProjectMetadataDto } from '../project/ProjectMetadataDto'; -type ProjectRole = 'OWNER' | 'MEMBER' | 'GUEST'; +export type ProjectRole = 'OWNER' | 'MEMBER' | 'GUEST'; type WithProjectRoleProps = { projectName: string; diff --git a/webapp/src/dogma/features/auth/RepositoryRole.tsx b/webapp/src/dogma/features/auth/RepositoryRole.tsx new file mode 100644 index 0000000000..2ee154d730 --- /dev/null +++ b/webapp/src/dogma/features/auth/RepositoryRole.tsx @@ -0,0 +1,32 @@ +import { UserDto } from './UserDto'; +import { ProjectMetadataDto } from '../project/ProjectMetadataDto'; +import { ProjectRole } from './ProjectRole'; + +export type RepositoryRole = 'READ' | 'WRITE' | 'ADMIN'; + +export function findUserRepositoryRole(repoName: string, user: UserDto, metadata: ProjectMetadataDto) { + if (user && user.systemAdmin) { + return 'ADMIN'; + } + const projectRole = metadata.members[user.email]?.role as ProjectRole; + if (projectRole === 'OWNER') { + return 'ADMIN'; + } + + const roles = metadata.repos[repoName]?.roles; + const memberOrGuestRole = projectRole === 'MEMBER' ? roles?.projects.member : roles?.projects.guest; + const userRepositoryRole = roles?.users[user.email]; + + if (userRepositoryRole === 'ADMIN' || memberOrGuestRole === 'ADMIN') { + return 'ADMIN'; + } + + if (userRepositoryRole === 'WRITE' || memberOrGuestRole === 'WRITE') { + return 'WRITE'; + } + + if (userRepositoryRole === 'READ' || memberOrGuestRole === 'READ') { + return 'READ'; + } + return null; +} diff --git a/webapp/src/dogma/features/project/ProjectDto.ts b/webapp/src/dogma/features/project/ProjectDto.ts index b272546bd6..62474580d6 100644 --- a/webapp/src/dogma/features/project/ProjectDto.ts +++ b/webapp/src/dogma/features/project/ProjectDto.ts @@ -14,14 +14,13 @@ * under the License. */ +import { ProjectRole } from 'dogma/features/auth/ProjectRole'; import { CreatorDto } from 'dogma/features/repo/RepoDto'; -export type ProjectUserRole = 'OWNER' | 'MEMBER' | 'GUEST'; - export interface ProjectDto { name: string; url?: string; creator?: CreatorDto; createdAt?: string; - userRole?: ProjectUserRole; + userRole?: ProjectRole; } diff --git a/webapp/src/dogma/features/project/settings/AppEntityList.tsx b/webapp/src/dogma/features/project/settings/AppEntityList.tsx new file mode 100644 index 0000000000..8555359a53 --- /dev/null +++ b/webapp/src/dogma/features/project/settings/AppEntityList.tsx @@ -0,0 +1,87 @@ +import { Text, VStack } from '@chakra-ui/react'; +import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; +import { useMemo } from 'react'; +import { DateWithTooltip } from 'dogma/common/components/DateWithTooltip'; +import { UserRole } from 'dogma/common/components/UserRole'; +import { DataTableClientPagination } from 'dogma/common/components/table/DataTableClientPagination'; +import { DeleteAppEntity } from 'dogma/features/project/settings/DeleteAppEntity'; + +export type AppEntityListProps = { + data: Data[]; + projectName: string; + entityType: 'member' | 'token'; + getId: (row: Data) => string; + getRole: (row: Data) => string; + getAddedBy: (row: Data) => string; + getTimestamp: (row: Data) => string; + showDeleteButton?: (row: Data) => boolean; + deleteMutation: (projectName: string, id: string) => Promise; + isLoading: boolean; +}; + +const AppEntityList = ({ + data, + projectName, + entityType, + getId, + getRole, + getAddedBy, + getTimestamp, + showDeleteButton = () => true, + deleteMutation, + isLoading, +}: AppEntityListProps): JSX.Element => { + const columnHelper = createColumnHelper(); + const columns = useMemo( + () => [ + columnHelper.accessor((row: Data) => getId(row), { + cell: (info) => ( + + {info.getValue()} + + + + + ), + header: entityType === 'member' ? 'Login ID' : 'App ID', + }), + columnHelper.accessor((row: Data) => getAddedBy(row), { + cell: (info) => info.getValue(), + header: 'Added By', + }), + columnHelper.accessor((row: Data) => getTimestamp(row), { + cell: (info) => , + header: 'Added At', + }), + columnHelper.accessor((row: Data) => getId(row), { + cell: (info) => + showDeleteButton(info.row.original) ? ( + + ) : null, + header: 'Actions', + enableSorting: false, + }), + ], + [ + columnHelper, + projectName, + entityType, + getId, + getRole, + getAddedBy, + getTimestamp, + showDeleteButton, + deleteMutation, + isLoading, + ], + ); + return []} data={data} />; +}; + +export default AppEntityList; diff --git a/webapp/src/dogma/features/project/settings/members/DeleteMember.tsx b/webapp/src/dogma/features/project/settings/DeleteAppEntity.tsx similarity index 68% rename from webapp/src/dogma/features/project/settings/members/DeleteMember.tsx rename to webapp/src/dogma/features/project/settings/DeleteAppEntity.tsx index 484350a47f..fc8fa9ee20 100644 --- a/webapp/src/dogma/features/project/settings/members/DeleteMember.tsx +++ b/webapp/src/dogma/features/project/settings/DeleteAppEntity.tsx @@ -3,27 +3,31 @@ import { newNotification } from 'dogma/features/notification/notificationSlice'; import ErrorMessageParser from 'dogma/features/services/ErrorMessageParser'; import { useAppDispatch } from 'dogma/hooks'; import { MdDelete } from 'react-icons/md'; -import { DeleteConfirmationModal } from '../DeleteConfirmationModal'; +import { DeleteConfirmationModal } from './DeleteConfirmationModal'; -export const DeleteMember = ({ - projectName, - repoName, - id, - deleteMember, - isLoading, -}: { +type DeleteEntityProps = { projectName: string; repoName?: string; id: string; - deleteMember: (projectName: string, id: string, repoName?: string) => Promise; + entityType: 'member' | 'token' | 'user'; + deleteEntity: (projectName: string, id: string, repoName?: string) => Promise; isLoading: boolean; -}): JSX.Element => { +}; + +export const DeleteAppEntity = ({ + projectName, + repoName, + id, + entityType, + deleteEntity, + isLoading, +}: DeleteEntityProps): JSX.Element => { const { isOpen, onToggle, onClose } = useDisclosure(); const dispatch = useAppDispatch(); const handleDelete = async () => { try { - await deleteMember(projectName, id, repoName); - dispatch(newNotification('Member deleted.', `Successfully deleted ${id}`, 'success')); + await deleteEntity(projectName, id, repoName); + dispatch(newNotification(`${entityType} deleted.`, `Successfully deleted ${id}`, 'success')); onClose(); } catch (error) { dispatch(newNotification(`Failed to delete ${id}`, ErrorMessageParser.parse(error), 'error')); @@ -38,8 +42,9 @@ export const DeleteMember = ({ isOpen={isOpen} onClose={onClose} id={id} - type={'member'} + type={entityType} projectName={projectName} + repoName={repoName} handleDelete={handleDelete} isLoading={isLoading} /> diff --git a/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx b/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx index 32e25605d0..e1962588d4 100644 --- a/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx +++ b/webapp/src/dogma/features/project/settings/DeleteConfirmationModal.tsx @@ -16,6 +16,7 @@ interface DeleteConfirmationModalProps { type: string; id: string; projectName: string; + repoName?: string; handleDelete: () => void; isLoading: boolean; } @@ -26,6 +27,7 @@ export const DeleteConfirmationModal = ({ id, type, projectName, + repoName, handleDelete, isLoading, }: DeleteConfirmationModalProps): JSX.Element => { @@ -36,7 +38,7 @@ export const DeleteConfirmationModal = ({ Are you sure? - Delete {type} '{id}' from {projectName}? + Delete {type} '{id}' from {repoName ? repoName : projectName}? diff --git a/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx b/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx index 5fdf757fdb..55b6995098 100644 --- a/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx +++ b/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx @@ -26,7 +26,7 @@ import { useAppSelector } from 'dogma/hooks'; import { FiBox } from 'react-icons/fi'; import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; import { HttpStatusCode } from 'dogma/features/api/HttpStatusCode'; -import { findUserRole } from '../../auth/ProjectRole'; +import { findUserRole, ProjectRole } from 'dogma/features/auth/ProjectRole'; interface ProjectSettingsViewProps { projectName: string; @@ -35,12 +35,11 @@ interface ProjectSettingsViewProps { } type TabName = 'repositories' | 'roles' | 'members' | 'tokens' | 'mirrors' | 'credentials' | 'danger zone'; -type UserRole = 'OWNER' | 'MEMBER' | 'GUEST'; export interface TapInfo { name: TabName; path: string; - accessRole: UserRole; + accessRole: ProjectRole; allowAnonymous: boolean; } diff --git a/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx b/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx index 94598acf31..e7b12b39a6 100644 --- a/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx +++ b/webapp/src/dogma/features/project/settings/credentials/CredentialForm.tsx @@ -42,9 +42,11 @@ import { LabelledIcon } from 'dogma/common/components/LabelledIcon'; import FieldErrorMessage from 'dogma/common/components/form/FieldErrorMessage'; import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto'; import { FiBox } from 'react-icons/fi'; +import { GoRepo } from 'react-icons/go'; interface CredentialFormProps { projectName: string; + repoName?: string; defaultValue: CredentialDto; onSubmit: (credential: CredentialDto, onSuccess: () => void) => Promise; isWaitingResponse: boolean; @@ -62,7 +64,13 @@ const CREDENTIAL_TYPES: CredentialType[] = [ { type: 'none', description: 'No authentication' }, ]; -const CredentialForm = ({ projectName, defaultValue, onSubmit, isWaitingResponse }: CredentialFormProps) => { +const CredentialForm = ({ + projectName, + repoName, + defaultValue, + onSubmit, + isWaitingResponse, +}: CredentialFormProps) => { const [credentialType, setCredentialType] = useState(defaultValue.type); const isNew = defaultValue.id === ''; @@ -104,10 +112,17 @@ const CredentialForm = ({ projectName, defaultValue, onSubmit, isWaitingResponse {isNew ? 'New Credential' : 'Edit Credential'} - - - {projectName} - + {repoName ? ( + + + {repoName} + + ) : ( + + + {projectName} + + )} diff --git a/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx b/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx index 5cc188a761..43f6185783 100644 --- a/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx +++ b/webapp/src/dogma/features/project/settings/credentials/CredentialList.tsx @@ -1,7 +1,6 @@ import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; import React, { useMemo } from 'react'; import { DataTableClientPagination } from 'dogma/common/components/table/DataTableClientPagination'; -import { useGetCredentialsQuery, useDeleteCredentialMutation } from 'dogma/features/api/apiSlice'; import { Badge } from '@chakra-ui/react'; import { ChakraLink } from 'dogma/common/components/ChakraLink'; import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto'; @@ -10,22 +9,30 @@ import { DeleteCredential } from 'dogma/features/project/settings/credentials/De // eslint-disable-next-line @typescript-eslint/no-unused-vars export type CredentialListProps = { projectName: string; + repoName?: string; + credentials: CredentialDto[]; + deleteCredential: (projectName: string, id: string, repoName?: string) => Promise; + isLoading: boolean; }; -const CredentialList = ({ projectName }: CredentialListProps) => { - const { data } = useGetCredentialsQuery(projectName); - const [deleteCredential, { isLoading }] = useDeleteCredentialMutation(); +const CredentialList = ({ + projectName, + repoName, + credentials, + deleteCredential, + isLoading, +}: CredentialListProps) => { const columnHelper = createColumnHelper(); const columns = useMemo( () => [ columnHelper.accessor((row: CredentialDto) => row.id, { cell: (info) => { const id = info.getValue() || 'undefined'; + const credentialLink = repoName + ? `/app/projects/${projectName}/repos/${repoName}/settings/credentials/${info.row.original.id}` + : `/app/projects/${projectName}/settings/credentials/${info.row.original.id}`; return ( - + {id} ); @@ -52,8 +59,9 @@ const CredentialList = ({ projectName }: CredentialListProp cell: (info) => ( deleteCredential({ projectName, id }).unwrap()} + deleteCredential={deleteCredential} isLoading={isLoading} /> ), @@ -61,9 +69,9 @@ const CredentialList = ({ projectName }: CredentialListProp enableSorting: false, }), ], - [columnHelper, deleteCredential, isLoading, projectName], + [columnHelper, deleteCredential, isLoading, projectName, repoName], ); - return []} data={data || []} />; + return []} data={credentials || []} />; }; export default CredentialList; diff --git a/webapp/src/dogma/features/project/settings/credentials/CredentialView.tsx b/webapp/src/dogma/features/project/settings/credentials/CredentialView.tsx index 0fa04825c0..abdf0fcc9e 100644 --- a/webapp/src/dogma/features/project/settings/credentials/CredentialView.tsx +++ b/webapp/src/dogma/features/project/settings/credentials/CredentialView.tsx @@ -47,6 +47,7 @@ import { RiGitRepositoryPrivateLine } from 'react-icons/ri'; import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto'; import { CiLock } from 'react-icons/ci'; import { FiBox } from 'react-icons/fi'; +import { GoRepo } from 'react-icons/go'; const HeadRow = ({ children }: { children: ReactNode }) => ( @@ -103,12 +104,13 @@ const SecretViewer = ({ dispatch, secretProvider }: SecretViewerProps) => { interface CredentialViewProps { projectName: string; + repoName?: string; credential: CredentialDto; } const AlignedIcon = ({ as }: { as: IconType }) => ; -const CredentialView = ({ projectName, credential }: CredentialViewProps) => { +const CredentialView = ({ projectName, repoName, credential }: CredentialViewProps) => { const dispatch = useAppDispatch(); return ( @@ -127,12 +129,21 @@ const CredentialView = ({ projectName, credential }: CredentialViewProps) => { - - - Project - - - + {repoName ? ( + + + Repository + + + + ) : ( + + + Project + + + + )} Credential ID @@ -219,7 +230,9 @@ const CredentialView = ({ projectName, credential }: CredentialViewProps) => {
- + - - - ); -}; diff --git a/webapp/src/dogma/features/repo/DeleteRepo.tsx b/webapp/src/dogma/features/repo/DeleteRepo.tsx index b65e061fae..406ba69628 100644 --- a/webapp/src/dogma/features/repo/DeleteRepo.tsx +++ b/webapp/src/dogma/features/repo/DeleteRepo.tsx @@ -20,10 +20,14 @@ export const DeleteRepo = ({ projectName, repoName, hidden, + buttonVariant, + buttonSize, }: { projectName: string; repoName: string; hidden: boolean; + buttonVariant: 'solid' | 'outline'; + buttonSize: 'sm' | 'lg'; }) => { const { isOpen, onToggle, onClose } = useDisclosure(); const dispatch = useAppDispatch(); @@ -42,8 +46,8 @@ export const DeleteRepo = ({ + + + deleteCredentialMutation({ projectName, id, repoName }).unwrap() + } + isLoading={isLoading} + /> + + )} + + ); +}; + +export default RepositoryCredentialPage; diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/new/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/new/index.tsx new file mode 100644 index 0000000000..0563f0c26b --- /dev/null +++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/credentials/new/index.tsx @@ -0,0 +1,70 @@ +/* + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import Router, { useRouter } from 'next/router'; +import { useAppDispatch } from 'dogma/hooks'; +import { FetchBaseQueryError } from '@reduxjs/toolkit/query'; +import { SerializedError } from '@reduxjs/toolkit'; +import { newNotification } from 'dogma/features/notification/notificationSlice'; +import ErrorMessageParser from 'dogma/features/services/ErrorMessageParser'; +import { useAddNewRepoCredentialMutation } from 'dogma/features/api/apiSlice'; +import { Breadcrumbs } from 'dogma/common/components/Breadcrumbs'; +import React from 'react'; +import { CredentialDto } from 'dogma/features/project/settings/credentials/CredentialDto'; +import CredentialForm from 'dogma/features/project/settings/credentials/CredentialForm'; + +const EMPTY_CREDENTIAL: CredentialDto = { + id: '', + type: 'public_key', + enabled: true, +}; +const NewRepoCredentialPage = () => { + const router = useRouter(); + const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const repoName = router.query.repoName ? (router.query.repoName as string) : ''; + + const [addNewCredential, { isLoading }] = useAddNewRepoCredentialMutation(); + const dispatch = useAppDispatch(); + + const onSubmit = async (credential: CredentialDto, onSuccess: () => void) => { + try { + const response = await addNewCredential({ projectName, credential, repoName }).unwrap(); + if ((response as { error: FetchBaseQueryError | SerializedError }).error) { + throw (response as { error: FetchBaseQueryError | SerializedError }).error; + } + dispatch(newNotification('New credential is created', `Successfully created`, 'success')); + onSuccess(); + Router.push(`/app/projects/${projectName}/repos/${repoName}/settings/credentials`); + } catch (error) { + dispatch(newNotification(`Failed to create a new credential`, ErrorMessageParser.parse(error), 'error')); + } + }; + + return ( + <> + + + + ); +}; + +export default NewRepoCredentialPage; diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/danger-zone/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/danger-zone/index.tsx new file mode 100644 index 0000000000..38faf87873 --- /dev/null +++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/danger-zone/index.tsx @@ -0,0 +1,27 @@ +import { useRouter } from 'next/router'; +import { Box } from '@chakra-ui/react'; +import RepositorySettingsView from 'dogma/features/repo/settings/RepositorySettingsView'; +import { DeleteRepo } from 'dogma/features/repo/DeleteRepo'; + +const DangerZonePage = () => { + const router = useRouter(); + const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const repoName = router.query.repoName ? (router.query.repoName as string) : ''; + return ( + + {() => ( + + + )} + + ); +}; + +export default DangerZonePage; diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/index.tsx index 787b4a0440..c0d5990edf 100644 --- a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/index.tsx +++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/index.tsx @@ -15,10 +15,8 @@ */ import { useRouter } from 'next/router'; -import { Flex, Spacer } from '@chakra-ui/react'; -import { NewRepo } from 'dogma/features/repo/NewRepo'; -import RepoMetaList from 'dogma/features/project/settings/repositories/RepoMetaList'; import RepositorySettingsView from 'dogma/features/repo/settings/RepositorySettingsView'; +import { ProjectRolesForm } from 'dogma/features/repo/roles/ProjectRolesForm'; const RepositorySettingsPage = () => { const router = useRouter(); @@ -26,14 +24,14 @@ const RepositorySettingsPage = () => { const repoName = router.query.repoName ? (router.query.repoName as string) : ''; return ( <> - + {(metadata) => ( <> - - - - - + )} diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/tokens/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/tokens/index.tsx new file mode 100644 index 0000000000..68e507c867 --- /dev/null +++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/tokens/index.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { useRouter } from 'next/router'; +import { Flex, Spacer } from '@chakra-ui/react'; +import RepositorySettingsView from 'dogma/features/repo/settings/RepositorySettingsView'; +import { NewTokenRepositoryRole } from 'dogma/features/repo/settings/tokens/NewTokenRepositoryRole'; +import { UserOrTokenRepositoryRoleDto } from 'dogma/features/repo/RepositoriesMetadataDto'; +import { UserOrTokenRepositoryRoleList } from 'dogma/features/repo/settings/UserOrTokenRepositoryRoleList'; +import { + useAddTokenRepositoryRoleMutation, + useDeleteTokenRepositoryRoleMutation, +} from 'dogma/features/api/apiSlice'; + +const ProjectTokenPage = () => { + const router = useRouter(); + const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const repoName = router.query.repoName ? (router.query.repoName as string) : ''; + const [addTokenRepositoryRole, { isLoading: isAddTokenLoading }] = useAddTokenRepositoryRoleMutation(); + const [deleteTokenRepositoryRole, { isLoading: isDeleteTokenLoading }] = + useDeleteTokenRepositoryRoleMutation(); + return ( + + {(metadata) => ( + <> + + + + + + + )} + + ); +}; + +export default ProjectTokenPage; diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/users/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/users/index.tsx new file mode 100644 index 0000000000..cf38f54486 --- /dev/null +++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/settings/users/index.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2024 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +import { useRouter } from 'next/router'; +import { Flex, Spacer } from '@chakra-ui/react'; +import RepositorySettingsView from 'dogma/features/repo/settings/RepositorySettingsView'; +import { NewUserRepositoryRole } from 'dogma/features/repo/settings/users/NewUserRepositoryRole'; +import { UserOrTokenRepositoryRoleDto } from 'dogma/features/repo/RepositoriesMetadataDto'; +import { UserOrTokenRepositoryRoleList } from 'dogma/features/repo/settings/UserOrTokenRepositoryRoleList'; +import { + useAddUserRepositoryRoleMutation, + useDeleteUserRepositoryRoleMutation, +} from 'dogma/features/api/apiSlice'; + +const RepositoryUserPage = () => { + const router = useRouter(); + const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const repoName = router.query.repoName ? (router.query.repoName as string) : ''; + const [addUserRepositoryRole, { isLoading: isAddUserLoading }] = useAddUserRepositoryRoleMutation(); + const [deleteUserRepositoryRole, { isLoading: isDeleteUserLoading }] = useDeleteUserRepositoryRoleMutation(); + return ( + + {(metadata) => ( + <> + + + + + + + )} + + ); +}; + +export default RepositoryUserPage; diff --git a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx index 96c129b1a8..9ce6255102 100644 --- a/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx +++ b/webapp/src/pages/app/projects/[projectName]/repos/[repoName]/tree/[revision]/[[...path]]/index.tsx @@ -146,7 +146,7 @@ cat ${project}/${repo}${path}`; {() => ( diff --git a/webapp/src/pages/app/projects/[projectName]/repos/index.tsx b/webapp/src/pages/app/projects/[projectName]/repos/index.tsx new file mode 100644 index 0000000000..fbb2c876be --- /dev/null +++ b/webapp/src/pages/app/projects/[projectName]/repos/index.tsx @@ -0,0 +1,12 @@ +import { useRouter } from 'next/router'; + +const ReposPage = () => { + const router = useRouter(); + + const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + router.replace(`/app/projects/${projectName}`); + + return <>; +}; + +export default ReposPage; diff --git a/webapp/src/pages/app/projects/[projectName]/settings/credentials/index.tsx b/webapp/src/pages/app/projects/[projectName]/settings/credentials/index.tsx index bf21441e81..f38db3e3c5 100644 --- a/webapp/src/pages/app/projects/[projectName]/settings/credentials/index.tsx +++ b/webapp/src/pages/app/projects/[projectName]/settings/credentials/index.tsx @@ -19,12 +19,16 @@ import { Button, Flex, Spacer } from '@chakra-ui/react'; import Link from 'next/link'; import { AiOutlinePlus } from 'react-icons/ai'; import React from 'react'; +import { useGetCredentialsQuery, useDeleteCredentialMutation } from 'dogma/features/api/apiSlice'; import ProjectSettingsView from 'dogma/features/project/settings/ProjectSettingsView'; import CredentialList from 'dogma/features/project/settings/credentials/CredentialList'; const ProjectCredentialPage = () => { const router = useRouter(); const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const { data: credentialsData } = useGetCredentialsQuery(projectName); + const [deleteCredentialMutation, { isLoading }] = useDeleteCredentialMutation(); + return ( {() => ( @@ -41,7 +45,12 @@ const ProjectCredentialPage = () => { New Credential - + deleteCredentialMutation({ projectName, id }).unwrap()} + isLoading={isLoading} + /> )} diff --git a/webapp/src/pages/app/projects/[projectName]/settings/members/index.tsx b/webapp/src/pages/app/projects/[projectName]/settings/members/index.tsx index fd882099f1..ef29cb2246 100644 --- a/webapp/src/pages/app/projects/[projectName]/settings/members/index.tsx +++ b/webapp/src/pages/app/projects/[projectName]/settings/members/index.tsx @@ -16,13 +16,18 @@ import { useRouter } from 'next/router'; import { Flex, Spacer } from '@chakra-ui/react'; +import { useAppSelector } from 'dogma/hooks'; +import { useDeleteMemberMutation } from 'dogma/features/api/apiSlice'; import ProjectSettingsView from 'dogma/features/project/settings/ProjectSettingsView'; import { AddMember } from 'dogma/features/project/settings/members/AddMember'; -import AppMemberList from 'dogma/features/project/settings/members/AppMemberList'; +import AppEntityList from 'dogma/features/project/settings/AppEntityList'; const ProjectMemberPage = () => { const router = useRouter(); const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const [deleteMember, { isLoading }] = useDeleteMemberMutation(); + const auth = useAppSelector((state) => state.auth); + return ( {(metadata) => ( @@ -31,7 +36,18 @@ const ProjectMemberPage = () => { - + row.login} + getRole={(row) => row.role} + getAddedBy={(row) => row.creation.user} + getTimestamp={(row) => row.creation.timestamp} + deleteMutation={(projectName, id) => deleteMember({ projectName, id }).unwrap()} + showDeleteButton={(row) => row.login !== auth.user?.email} + isLoading={isLoading} + /> )} diff --git a/webapp/src/pages/app/projects/[projectName]/settings/tokens/index.tsx b/webapp/src/pages/app/projects/[projectName]/settings/tokens/index.tsx index c4688e8de6..cdf1af9e6d 100644 --- a/webapp/src/pages/app/projects/[projectName]/settings/tokens/index.tsx +++ b/webapp/src/pages/app/projects/[projectName]/settings/tokens/index.tsx @@ -16,13 +16,15 @@ import { useRouter } from 'next/router'; import { Flex, Spacer } from '@chakra-ui/react'; +import { useDeleteTokenMemberMutation } from 'dogma/features/api/apiSlice'; import ProjectSettingsView from 'dogma/features/project/settings/ProjectSettingsView'; import { AddAppToken } from 'dogma/features/project/settings/tokens/AddAppToken'; -import AppTokenList from 'dogma/features/project/settings/tokens/AppTokenList'; +import AppEntityList from 'dogma/features/project/settings/AppEntityList'; const ProjectTokenPage = () => { const router = useRouter(); const projectName = router.query.projectName ? (router.query.projectName as string) : ''; + const [deleteToken, { isLoading }] = useDeleteTokenMemberMutation(); return ( {(metadata) => ( @@ -31,9 +33,16 @@ const ProjectTokenPage = () => { - row.appId} + getRole={(row) => row.role} + getAddedBy={(row) => row.creation.user} + getTimestamp={(row) => row.creation.timestamp} + deleteMutation={(projectName, id) => deleteToken({ projectName, id }).unwrap()} + isLoading={isLoading} /> )} diff --git a/webapp/tests/dogma/feature/repo/RepoPermissionList.test.tsx b/webapp/tests/dogma/feature/repo/RepoPermissionList.test.tsx index 70a64b53e5..23595353f9 100644 --- a/webapp/tests/dogma/feature/repo/RepoPermissionList.test.tsx +++ b/webapp/tests/dogma/feature/repo/RepoPermissionList.test.tsx @@ -1,6 +1,7 @@ import { act, fireEvent, render } from '@testing-library/react'; +import { RepositoryRole } from 'dogma/features/auth/RepositoryRole'; import RepoRoleList, { RepoRoleListProps } from 'dogma/features/repo/RepoRoleList'; -import { RepositoryMetadataDto, RepositoryRole } from 'dogma/features/repo/RepositoriesMetadataDto'; +import { RepositoryMetadataDto } from 'dogma/features/repo/RepositoriesMetadataDto'; import '@testing-library/jest-dom'; describe('RepoRoleList', () => { @@ -90,7 +91,7 @@ describe('RepoRoleList', () => { const firstRepoName = 'meta'; expect(firstCell).toHaveAttribute( 'href', - `/app/projects/${expectedProps.projectName}/repos/${firstRepoName}/roles`, + `/app/projects/${expectedProps.projectName}/repos/${firstRepoName}/settings`, ); }); }); diff --git a/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesServiceTest.java b/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesServiceTest.java index 0a5ba26b09..bfeaf0f6dd 100644 --- a/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesServiceTest.java +++ b/xds/src/test/java/com/linecorp/centraldogma/xds/k8s/v1/XdsKubernetesServiceTest.java @@ -184,7 +184,9 @@ void invalidProperty() throws IOException { assertThat(response.status()).isSameAs(HttpStatus.BAD_REQUEST); assertThatJson(response.contentUtf8()) .node("grpc-code").isEqualTo("INVALID_ARGUMENT") - .node("message").isEqualTo("failed to find credential 'invalid-credential-id' in @xds/meta"); + .node("message").isEqualTo( + "failed to find credential file " + + "'/credentials/invalid-credential-id.json' in @xds/meta"); } @Test
{projectName}
{repoName}
{projectName}