diff --git a/webapp/src/dogma/common/components/UserRole.tsx b/webapp/src/dogma/common/components/UserRole.tsx index 7cb599c6d0..ba6f5609b2 100644 --- a/webapp/src/dogma/common/components/UserRole.tsx +++ b/webapp/src/dogma/common/components/UserRole.tsx @@ -1,6 +1,10 @@ import { Badge } from '@chakra-ui/react'; function badgeColor(role: string) { + if (!role) { + return 'gray'; + } + switch (role.toLowerCase()) { case 'user': case 'member': diff --git a/webapp/src/dogma/common/components/table/DataTableClientPagination.tsx b/webapp/src/dogma/common/components/table/DataTableClientPagination.tsx index be6d468f12..6abcde10eb 100644 --- a/webapp/src/dogma/common/components/table/DataTableClientPagination.tsx +++ b/webapp/src/dogma/common/components/table/DataTableClientPagination.tsx @@ -45,7 +45,10 @@ export const DataTableClientPagination = ({ return ( <> Filter by {table.getHeaderGroups()[0].headers[0].id} - + diff --git a/webapp/src/dogma/common/components/table/DynamicDataTable.tsx b/webapp/src/dogma/common/components/table/DynamicDataTable.tsx index 5cd335a601..4a3328863f 100644 --- a/webapp/src/dogma/common/components/table/DynamicDataTable.tsx +++ b/webapp/src/dogma/common/components/table/DynamicDataTable.tsx @@ -54,7 +54,10 @@ export const DynamicDataTable = ({ return ( <> Filter by {table.getHeaderGroups()[0].headers[0].id} - + {pagination && } diff --git a/webapp/src/dogma/common/components/table/Filter.tsx b/webapp/src/dogma/common/components/table/Filter.tsx index 92cbc823da..62733c199a 100644 --- a/webapp/src/dogma/common/components/table/Filter.tsx +++ b/webapp/src/dogma/common/components/table/Filter.tsx @@ -1,19 +1,26 @@ -import { Column } from '@tanstack/react-table'; +import { Column, Table } from '@tanstack/react-table'; import { DebouncedInput } from 'dogma/common/components/table/DebouncedInput'; import { useCallback, useMemo } from 'react'; export type FilterProps = { + table: Table; column: Column; }; -export const Filter = ({ column }: FilterProps) => { +export const Filter = ({ table, column }: FilterProps) => { const columnFilterValue = column.getFilterValue(); const facetedUniqueValues = column.getFacetedUniqueValues(); const sortedUniqueValues = useMemo( () => Array.from(facetedUniqueValues.keys()).sort(), [facetedUniqueValues], ); - const handleChange = useCallback((value: string | number) => column.setFilterValue(value), [column]); + const handleChange = useCallback( + (value: string | number) => { + table.setPageIndex(0); + column.setFilterValue(value); + }, + [table, column], + ); return ( <> diff --git a/webapp/src/dogma/common/components/table/PaginationBar.tsx b/webapp/src/dogma/common/components/table/PaginationBar.tsx index 3d58a491ee..bb17b1a4a6 100644 --- a/webapp/src/dogma/common/components/table/PaginationBar.tsx +++ b/webapp/src/dogma/common/components/table/PaginationBar.tsx @@ -9,25 +9,25 @@ export const PaginationBar = ({ table }: { table: ReactTabl aria-label="First page" icon={} onClick={() => table.setPageIndex(0)} - disabled={!table.getCanPreviousPage()} + isDisabled={!table.getCanPreviousPage()} /> } onClick={() => table.previousPage()} - disabled={!table.getCanPreviousPage()} + isDisabled={!table.getCanPreviousPage()} /> } onClick={() => table.nextPage()} - disabled={!table.getCanNextPage()} + isDisabled={!table.getCanNextPage()} /> } onClick={() => table.setPageIndex(table.getPageCount() - 1)} - disabled={!table.getCanNextPage()} + isDisabled={!table.getCanNextPage()} /> Page diff --git a/webapp/src/dogma/features/auth/ProjectRole.tsx b/webapp/src/dogma/features/auth/ProjectRole.tsx index f4cbebaa6d..2878ee3e30 100644 --- a/webapp/src/dogma/features/auth/ProjectRole.tsx +++ b/webapp/src/dogma/features/auth/ProjectRole.tsx @@ -32,7 +32,11 @@ export const WithProjectRole = ({ projectName, roles, children }: WithProjectRol refetchOnFocus: true, }); - const { user } = useAppSelector((state) => state.auth); + const { user, isInAnonymousMode } = useAppSelector((state) => state.auth); + if (isInAnonymousMode) { + return <>{children()}; + } + const role = findUserRole(user, metadata); if (roles.find((r) => r === role)) { diff --git a/webapp/src/dogma/features/auth/authSlice.ts b/webapp/src/dogma/features/auth/authSlice.ts index 22450c25cb..16b203c965 100644 --- a/webapp/src/dogma/features/auth/authSlice.ts +++ b/webapp/src/dogma/features/auth/authSlice.ts @@ -128,6 +128,14 @@ const initialState: AuthState = { isLoading: true, }; +const anonymousUser: UserDto = { + login: 'anonymous', + name: 'Anonymous', + email: 'anonymous@localhost', + roles: [], + admin: false, +}; + export const authSlice = createSlice({ name: 'auth', initialState, @@ -138,6 +146,7 @@ export const authSlice = createSlice({ state.isInAnonymousMode = true; state.sessionId = ''; state.isLoading = false; + state.user = anonymousUser; }) .addCase(login.fulfilled, (state, { payload }) => { state.sessionId = payload.access_token; diff --git a/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx b/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx index 1b21916e34..185d079d06 100644 --- a/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx +++ b/webapp/src/dogma/features/project/settings/ProjectSettingsView.tsx @@ -35,24 +35,33 @@ interface ProjectSettingsViewProps { } type TabName = 'repositories' | 'permissions' | 'members' | 'tokens' | 'mirrors' | 'credentials'; +type UserRole = 'OWNER' | 'MEMBER' | 'GUEST' | 'ANONYMOUS'; export interface TapInfo { name: TabName; path: string; - accessRole: 'OWNER' | 'MEMBER' | 'GUEST'; + accessRole: UserRole; + allowAnonymous: boolean; } const TABS: TapInfo[] = [ // 'repositories' is the index tab - { name: 'repositories', path: '', accessRole: 'GUEST' }, - { name: 'permissions', path: 'permissions', accessRole: 'OWNER' }, - { name: 'members', path: 'members', accessRole: 'OWNER' }, - { name: 'tokens', path: 'tokens', accessRole: 'OWNER' }, - { name: 'mirrors', path: 'mirrors', accessRole: 'OWNER' }, - { name: 'credentials', path: 'credentials', accessRole: 'OWNER' }, + { name: 'repositories', path: '', accessRole: 'GUEST', allowAnonymous: true }, + { name: 'permissions', path: 'permissions', accessRole: 'OWNER', allowAnonymous: false }, + { name: 'members', path: 'members', accessRole: 'OWNER', allowAnonymous: false }, + { name: 'tokens', path: 'tokens', accessRole: 'OWNER', allowAnonymous: false }, + { name: 'mirrors', path: 'mirrors', accessRole: 'OWNER', allowAnonymous: true }, + { name: 'credentials', path: 'credentials', accessRole: 'OWNER', allowAnonymous: true }, ]; -function isAllowed(userRole: string, tabInfo: TapInfo): boolean { +function isAllowed(userRole: string, anonymous: boolean, tabInfo: TapInfo): boolean { + if (!tabInfo) { + return false; + } + if (anonymous && tabInfo.allowAnonymous) { + return true; + } + switch (tabInfo.accessRole) { case 'OWNER': return userRole === 'OWNER'; @@ -64,7 +73,7 @@ function isAllowed(userRole: string, tabInfo: TapInfo): boolean { } const ProjectSettingsView = ({ projectName, currentTab, children }: ProjectSettingsViewProps) => { - const { user } = useAppSelector((state) => state.auth); + const { user, isInAnonymousMode } = useAppSelector((state) => state.auth); const tabIndex = TABS.findIndex((tab) => tab.name === currentTab); const router = useRouter(); @@ -104,7 +113,7 @@ const ProjectSettingsView = ({ projectName, currentTab, children }: ProjectSetti {TABS.map((tab) => { - const allowed = isAllowed(accessRole, tab); + const allowed = isAllowed(accessRole, isInAnonymousMode, tab); let link = ''; if (allowed) { link = `/app/projects/${projectName}/settings`; @@ -124,7 +133,7 @@ const ProjectSettingsView = ({ projectName, currentTab, children }: ProjectSetti {TABS.map((tab) => { - const allowed = isAllowed(accessRole, tab); + const allowed = isAllowed(accessRole, isInAnonymousMode, tab); return ( {tab.name === currentTab && allowed && children(metadata)} );