Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin user list #623

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion apps/gitness/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import { RepoSidebar } from './pages-v2/repo/repo-sidebar'
import RepoSummaryPage from './pages-v2/repo/repo-summary'
import { SignIn as SignInV2 } from './pages-v2/signin'
import { SignUp as SignUpV2 } from './pages-v2/signup'
// import { UserManagementPageContainer } from './pages/user-management/user-management-container'
import { UserManagementPageContainer } from './pages-v2/user-management/user-management-container'
import { CreateWebhookContainer } from './pages-v2/webhooks/create-webhook-container'
import WebhookListPage from './pages-v2/webhooks/webhook-list'
import CreateProjectV1 from './pages/create-project'
Expand Down Expand Up @@ -98,7 +100,6 @@ import RepoSummaryPageV1 from './pages/repo/repo-summary'
import { SignIn } from './pages/signin'
import { SignUp } from './pages/signup'
import { CreateNewUserContainer } from './pages/user-management/create-new-user-container'
import { UserManagementPageContainer } from './pages/user-management/user-management-container'
import RepoWebhooksListPage from './pages/webhooks/repo-webhook-list'

const BASE_URL_PREFIX = `${window.apiUrl || ''}/api/v1`
Expand Down Expand Up @@ -387,6 +388,10 @@ export default function App() {
}
]
},
{
path: 'admin/default-settings',
element: <UserManagementPageContainer />
},
{
path: 'theme',
element: <ProfileSettingsThemePage />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { create } from 'zustand'

import { IAdminListUsersStore, UsersProps } from '@harnessio/ui/views'

import { PageResponseHeader } from '../../../types'

export const useAdminListUsersStore = create<IAdminListUsersStore>(set => ({
users: [],
totalPages: 1,
page: 1,
setPage: page =>
set({
page
}),
setUsers: (data: UsersProps[]) => {
set({
users: data
})
},
setTotalPages: (headers: Headers) => {
const totalPages = parseInt(headers?.get(PageResponseHeader.xTotalPages) || '0')

set({
totalPages
})
}
}))
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { useEffect } from 'react'

// import { useQueryClient } from '@tanstack/react-query'
import { parseAsInteger, useQueryState } from 'nuqs'

import {
// useAdminDeleteUserMutation,
useAdminListUsersQuery
// useAdminUpdateUserMutation,
// useUpdateUserAdminMutation
} from '@harnessio/code-service-client'
import { UserManagementPage } from '@harnessio/ui/views'

import { useTranslationStore } from '../../i18n/stores/i18n-store'
import { useAdminListUsersStore } from './stores/admin-list-store'

export const UserManagementPageContainer = () => {
const [queryPage, setQueryPage] = useQueryState('page', parseAsInteger.withDefault(1))
const { setUsers, setTotalPages, setPage, page } = useAdminListUsersStore()
// const queryClient = useQueryClient()
const { data: { body: userData, headers } = {} } = useAdminListUsersQuery({
queryParams: {
page: queryPage
}
})

useEffect(() => {
if (userData) {
setUsers(userData)
}
if (headers) {
setTotalPages(headers)
}
}, [userData, setUsers, setTotalPages, headers])

useEffect(() => {
setQueryPage(page)
}, [queryPage, page, setPage])

// @TODO: add following mutations and callbacks back once functionality is added in the views page

// const { mutateAsync: updateUser } = useAdminUpdateUserMutation(
// {},
// {
// onError: error => {
// console.error(error)
// }
// }
// )

// const { mutateAsync: deleteUser } = useAdminDeleteUserMutation(
// {},
// {
// onError: error => {
// console.error(error)
// }
// }
// )

// const { mutateAsync: updateUserAdmin } = useUpdateUserAdminMutation(
// {},
// {
// onError: error => {
// console.error(error)
// }
// }
// )

// const handleUpdateUser = async (data: { email: string; displayName: string; userID: string }) => {
// await updateUser({
// user_uid: data.userID,
// body: {
// email: data.email,
// display_name: data.displayName
// }
// })
// queryClient.invalidateQueries({ queryKey: ['adminListUsers'] })
// }

// const handleDeleteUser = async (userUid: string) => {
// await deleteUser({
// user_uid: userUid
// })
// queryClient.invalidateQueries({ queryKey: ['adminListUsers'] })
// }

// const handleUpdateUserAdmin = async (userUid: string, isAdmin: boolean) => {
// await updateUserAdmin({
// user_uid: userUid,
// body: {
// admin: isAdmin
// }
// })
// queryClient.invalidateQueries({ queryKey: ['adminListUsers'] })
// }

// const handleUpdatePassword = async (userId: string, password: string) => {
// await updateUser({
// user_uid: userId,
// body: {
// password: password
// }
// })
// queryClient.invalidateQueries({ queryKey: ['adminListUsers'] })
// }
return (
<UserManagementPage
useAdminListUsersStore={useAdminListUsersStore}
useTranslationStore={useTranslationStore}
// handleUpdateUser={handleUpdateUser}
// handleDeleteUser={handleDeleteUser}
// handleUpdatePassword={handleUpdatePassword}
// updateUserAdmin={handleUpdateUserAdmin}
/>
)
}
3 changes: 3 additions & 0 deletions packages/ui/src/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ export * from './profile-settings'

// pipelines
export * from './pipelines'

// user-management
export * from './user-management'
137 changes: 137 additions & 0 deletions packages/ui/src/views/user-management/components/users-list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import {
Avatar,
AvatarFallback,
AvatarImage,
Badge,
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
Icon,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Text
} from '@/components'
import { getInitials } from '@/utils/utils'

import { UsersProps } from '../types'

interface PageProps {
users: UsersProps[]
}

// fix the edit form dialog and mock data and coressponding props
export const UsersList = ({ users }: PageProps) => {
const moreActionsTooltip = ({ user }: { user: UsersProps }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="xs">
<Icon name="vertical-ellipsis" size={14} className="text-tertiary-background" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[180px] rounded-[10px] border border-gray-800 bg-primary-background py-2 shadow-sm"
onCloseAutoFocus={event => event.preventDefault()}
>
<DropdownMenuGroup>
<DropdownMenuItem className="cursor-pointer">
<DropdownMenuShortcut className="ml-0">
<Icon name="trash" className="mr-2" />
</DropdownMenuShortcut>
{user.admin ? 'Remove Admin' : 'Set as Admin'}
</DropdownMenuItem>
<DropdownMenuItem className="cursor-pointer">
<DropdownMenuShortcut className="ml-0">
<Icon name="cog-6" className="mr-2" />
</DropdownMenuShortcut>
Reset Password
</DropdownMenuItem>
<DropdownMenuItem className="cursor-pointer">
<DropdownMenuShortcut className="ml-0">
<Icon name="edit-pen" className="mr-2" />
</DropdownMenuShortcut>
Edit User
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="cursor-pointer text-red-400 hover:text-red-400 focus:text-red-400">
<DropdownMenuShortcut className="ml-0">
<Icon name="trash" className="mr-2 text-red-400" />
</DropdownMenuShortcut>
Delete User
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
)
}

return (
<Table variant="asStackedList">
<TableHeader>
<TableRow>
<TableHead>User</TableHead>
<TableHead>Email</TableHead>
<TableHead>Role Binding</TableHead>
{/* <TableHead className="text-right text-primary">Date added</TableHead> */}
<TableHead>
<></>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users &&
users.map(user => {
return (
<TableRow key={user.uid}>
{/* NAME */}
<TableCell className="my-6 content-center">
<div className="flex items-center gap-2">
<Avatar className="size-6 rounded-full p-0">
{user.avatarUrl && <AvatarImage src={user.avatarUrl} />}
<AvatarFallback>{getInitials(user.uid!, 2)}</AvatarFallback>
</Avatar>
<Text size={2} weight="medium" wrap="nowrap" truncate className="text-primary">
{user.display_name}
{user.admin && (
<Badge
variant="outline"
size="xs"
className="m-auto ml-2 h-5 rounded-full bg-tertiary-background/10 p-2 text-center text-xs font-normal text-tertiary-background"
>
Admin
</Badge>
)}
</Text>
</div>
</TableCell>
{/* EMAIL */}
<TableCell className="my-6 content-center">
<div className="flex gap-1.5">
<Text wrap="nowrap" size={1} truncate className="text-tertiary-background">
{user.email}
</Text>
</div>
</TableCell>

{/* @TODO: add roll binding data when available */}
<TableCell className="my-6 content-center"></TableCell>

<TableCell className="my-6 content-center">
<div className="flex items-center justify-end">{moreActionsTooltip({ user })}</div>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
)
}
2 changes: 2 additions & 0 deletions packages/ui/src/views/user-management/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './user-management-page'
export * from './types'
25 changes: 25 additions & 0 deletions packages/ui/src/views/user-management/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TranslationStore } from '@/views'

export interface UsersProps {
admin?: boolean
uid?: string
display_name?: string | undefined
email?: string
created?: number
updated?: number
avatarUrl?: string
blocked?: boolean
}

export interface IUserManagementPageProps {
useAdminListUsersStore: () => IAdminListUsersStore
useTranslationStore: () => TranslationStore
}
export interface IAdminListUsersStore {
users: UsersProps[]
totalPages: number
page: number
setPage: (data: number) => void
setUsers: (data: UsersProps[]) => void
setTotalPages: (data: Headers) => void
}
51 changes: 51 additions & 0 deletions packages/ui/src/views/user-management/user-management-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Button, ListActions, PaginationComponent, SearchBox, Spacer, Text } from '@/components'
import { SandboxLayout } from '@/views'

import { UsersList } from './components/users-list'
import { IUserManagementPageProps, UsersProps } from './types'

export const UserManagementPage: React.FC<IUserManagementPageProps> = ({
useAdminListUsersStore,
useTranslationStore
}) => {
const { users: userData, totalPages, page: currentPage, setPage } = useAdminListUsersStore()
const { t } = useTranslationStore()

const renderUserListContent = () => {
return (
<>
<UsersList users={userData as UsersProps[]} />
</>
)
}

return (
<SandboxLayout.Main>
<SandboxLayout.Content maxWidth="3xl">
<Spacer size={10} />
<Text size={5} weight={'medium'}>
Users
</Text>
<Spacer size={6} />
<ListActions.Root>
<ListActions.Left>
<SearchBox.Root width="full" className="max-w-96" placeholder="search" />
</ListActions.Left>
<ListActions.Right>
<Button variant="default">New user</Button>
</ListActions.Right>
</ListActions.Root>
<Spacer size={5} />
<Spacer size={5} />
{renderUserListContent()}
<Spacer size={8} />
<PaginationComponent
totalPages={totalPages}
currentPage={currentPage}
goToPage={(pageNum: number) => setPage(pageNum)}
t={t}
/>
</SandboxLayout.Content>
</SandboxLayout.Main>
)
}