mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(site): embed users page in management settings (#14006)
This commit is contained in:
@@ -32,10 +32,10 @@ export const CreateUserPage: FC = () => {
|
||||
onSubmit={async (user) => {
|
||||
await createUserMutation.mutateAsync(user);
|
||||
displaySuccess("Successfully created user.");
|
||||
navigate("/users");
|
||||
navigate("..", { relative: "path" });
|
||||
}}
|
||||
onCancel={() => {
|
||||
navigate("/users");
|
||||
navigate("..", { relative: "path" });
|
||||
}}
|
||||
isLoading={createUserMutation.isLoading}
|
||||
organizationId={organizationId}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Sidebar as BaseSidebar } from "components/Sidebar/Sidebar";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { UserAvatar } from "components/UserAvatar/UserAvatar";
|
||||
import { type ClassName, useClassName } from "hooks/useClassName";
|
||||
import { USERS_LINK } from "modules/navigation";
|
||||
import { useOrganizationSettings } from "./ManagementSettingsLayout";
|
||||
|
||||
export const Sidebar: FC = () => {
|
||||
@@ -77,7 +78,9 @@ const DeploymentSettingsNavigation: FC = () => {
|
||||
<SidebarNavSubItem href="observability">
|
||||
Observability
|
||||
</SidebarNavSubItem>
|
||||
<SidebarNavSubItem href="/users">Users</SidebarNavSubItem>
|
||||
<SidebarNavSubItem href={USERS_LINK.slice(1)}>
|
||||
Users
|
||||
</SidebarNavSubItem>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -27,6 +27,8 @@ export const UsersLayout: FC = () => {
|
||||
const location = useLocation();
|
||||
const activeTab = location.pathname.endsWith("groups") ? "groups" : "users";
|
||||
|
||||
const isMultiOrg = experiments.includes("multi-organization");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Margins>
|
||||
@@ -59,7 +61,7 @@ export const UsersLayout: FC = () => {
|
||||
</PageHeader>
|
||||
</Margins>
|
||||
|
||||
{!experiments.includes("multi-organization") && (
|
||||
{!isMultiOrg && (
|
||||
<Tabs
|
||||
css={{ marginBottom: 40, marginTop: -TAB_PADDING_Y }}
|
||||
active={activeTab}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { type FC, useState } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
useSearchParams,
|
||||
useNavigate,
|
||||
Navigate,
|
||||
useLocation,
|
||||
} from "react-router-dom";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import { deploymentConfig } from "api/queries/deployment";
|
||||
import { groupsByUserId } from "api/queries/groups";
|
||||
@@ -33,16 +38,21 @@ import { UsersPageView } from "./UsersPageView";
|
||||
const UsersPage: FC = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const location = useLocation();
|
||||
const searchParamsResult = useSearchParams();
|
||||
const { entitlements, organizationId } = useDashboard();
|
||||
const { entitlements, experiments, organizationId } = useDashboard();
|
||||
const [searchParams] = searchParamsResult;
|
||||
const isMultiOrg = experiments.includes("multi-organization");
|
||||
|
||||
const groupsByUserIdQuery = useQuery(groupsByUserId(organizationId));
|
||||
const authMethodsQuery = useQuery(authMethods());
|
||||
|
||||
const { permissions, user: me } = useAuthenticated();
|
||||
const { updateUsers: canEditUsers, viewDeploymentValues } = permissions;
|
||||
const {
|
||||
createUser: canCreateUser,
|
||||
updateUsers: canEditUsers,
|
||||
viewDeploymentValues,
|
||||
} = permissions;
|
||||
const rolesQuery = useQuery(roles());
|
||||
const { data: deploymentValues } = useQuery({
|
||||
...deploymentConfig(),
|
||||
@@ -93,6 +103,13 @@ const UsersPage: FC = () => {
|
||||
authMethodsQuery.isLoading ||
|
||||
groupsByUserIdQuery.isLoading;
|
||||
|
||||
if (
|
||||
experiments.includes("multi-organization") &&
|
||||
location.pathname !== "/deployment/users"
|
||||
) {
|
||||
return <Navigate to={`/deployment/users${location.search}`} replace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
@@ -147,6 +164,8 @@ const UsersPage: FC = () => {
|
||||
menus: { status: statusMenu },
|
||||
}}
|
||||
usersQuery={usersQuery}
|
||||
isMultiOrg={isMultiOrg}
|
||||
canCreateUser={canCreateUser}
|
||||
/>
|
||||
|
||||
<DeleteDialog
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import PersonAdd from "@mui/icons-material/PersonAdd";
|
||||
import Button from "@mui/material/Button";
|
||||
import type { ComponentProps, FC } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import type { GroupsByUserId } from "api/queries/groups";
|
||||
import type * as TypesGen from "api/typesGenerated";
|
||||
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
|
||||
import {
|
||||
PaginationContainer,
|
||||
type PaginationResult,
|
||||
@@ -32,6 +36,10 @@ export interface UsersPageViewProps {
|
||||
actorID: string;
|
||||
groupsByUserId: GroupsByUserId | undefined;
|
||||
usersQuery: PaginationResult;
|
||||
|
||||
// TODO: Refactor these out once we remove the multi-organization experiment.
|
||||
isMultiOrg?: boolean;
|
||||
canCreateUser?: boolean;
|
||||
}
|
||||
|
||||
export const UsersPageView: FC<UsersPageViewProps> = ({
|
||||
@@ -55,9 +63,33 @@ export const UsersPageView: FC<UsersPageViewProps> = ({
|
||||
authMethods,
|
||||
groupsByUserId,
|
||||
usersQuery,
|
||||
isMultiOrg,
|
||||
canCreateUser,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMultiOrg && (
|
||||
<PageHeader
|
||||
css={{ paddingTop: 0 }}
|
||||
actions={
|
||||
<>
|
||||
{canCreateUser && (
|
||||
<Button
|
||||
onClick={() => navigate("create")}
|
||||
startIcon={<PersonAdd />}
|
||||
>
|
||||
Create user
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<PageHeaderTitle>Users</PageHeaderTitle>
|
||||
</PageHeader>
|
||||
)}
|
||||
|
||||
<UsersFilter {...filterProps} />
|
||||
|
||||
<PaginationContainer query={usersQuery} paginationUnitLabel="users">
|
||||
|
||||
@@ -69,7 +69,7 @@ export const UsersTable: FC<UsersTableProps> = ({
|
||||
<Table data-testid="users-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="29%">{Language.usernameLabel}</TableCell>
|
||||
<TableCell width="32%">{Language.usernameLabel}</TableCell>
|
||||
|
||||
<TableCell width="29%">
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
@@ -78,15 +78,15 @@ export const UsersTable: FC<UsersTableProps> = ({
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell width="14%">
|
||||
<TableCell width="13%">
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<span>{Language.groupsLabel}</span>
|
||||
<TableColumnHelpTooltip variant="groups" />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
|
||||
<TableCell width="14%">{Language.loginTypeLabel}</TableCell>
|
||||
<TableCell width="14%">{Language.statusLabel}</TableCell>
|
||||
<TableCell width="13%">{Language.loginTypeLabel}</TableCell>
|
||||
<TableCell width="13%">{Language.statusLabel}</TableCell>
|
||||
|
||||
{/* 1% is a trick to make the table cell width fit the content */}
|
||||
{canEditUsers && <TableCell width="1%" />}
|
||||
|
||||
@@ -407,6 +407,8 @@ export const router = createBrowserRouter(
|
||||
</Route>
|
||||
|
||||
<Route path="workspace-proxies" element={<WorkspaceProxyPage />} />
|
||||
<Route path="users" element={<UsersPage />} />
|
||||
<Route path="users/create" element={<CreateUserPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/settings" element={<UserSettingsLayout />}>
|
||||
|
||||
Reference in New Issue
Block a user