Admin: Combine org and admin user pages (#59365)

* Admin: Add unified users page

* Admin: Combine admin and org components

* Admin: Add combined route

* Admin: Show combined page in nav

* Admin: Update translation

* Admin: Update description

* Admin: Update description on backend

* Admin: Update translations

* Admin: Use dynamic imports
This commit is contained in:
Alex Khomenko
2022-11-30 15:24:53 +02:00
committed by GitHub
parent 32a498e04f
commit c3d13a0e2f
12 changed files with 207 additions and 128 deletions

View File

@@ -77,98 +77,105 @@ const UserListAdminPageUnConnected = ({
const showLicensedRole = useMemo(() => users.some((user) => user.licensedRole), [users]);
return (
<Page navId="global-users">
<Page.Contents>
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<FilterInput
placeholder="Search user by login, email, or name."
autoFocus={true}
value={query}
onChange={changeQuery}
/>
<RadioButtonGroup
options={[
{ label: 'All users', value: false },
{ label: 'Active last 30 days', value: true },
]}
onChange={(value) => changeFilter({ name: 'activeLast30Days', value })}
value={filters.find((f) => f.name === 'activeLast30Days')?.value}
className={styles.filter}
/>
{extraFilters.map((FilterComponent, index) => (
<FilterComponent key={index} filters={filters} onChange={changeFilter} className={styles.filter} />
))}
</div>
{contextSrv.hasPermission(AccessControlAction.UsersCreate) && (
<LinkButton href="admin/users/create" variant="primary">
New user
</LinkButton>
)}
<Page.Contents>
<div className="page-action-bar">
<div className="gf-form gf-form--grow">
<FilterInput
placeholder="Search user by login, email, or name."
autoFocus={true}
value={query}
onChange={changeQuery}
/>
<RadioButtonGroup
options={[
{ label: 'All users', value: false },
{ label: 'Active last 30 days', value: true },
]}
onChange={(value) => changeFilter({ name: 'activeLast30Days', value })}
value={filters.find((f) => f.name === 'activeLast30Days')?.value}
className={styles.filter}
/>
{extraFilters.map((FilterComponent, index) => (
<FilterComponent key={index} filters={filters} onChange={changeFilter} className={styles.filter} />
))}
</div>
{isLoading ? (
<PageLoader />
) : (
<>
<div className={cx(styles.table, 'admin-list-table')}>
<table className="filter-table form-inline filter-table--hover">
<thead>
<tr>
<th></th>
<th>Login</th>
<th>Email</th>
<th>Name</th>
<th>Belongs to</th>
{showLicensedRole && (
<th>
Licensed role{' '}
<Tooltip
placement="top"
content={
<>
Licensed role is based on a user&apos;s Org role (i.e. Viewer, Editor, Admin) and their
dashboard/folder permissions.{' '}
<a
className={styles.link}
target="_blank"
rel="noreferrer noopener"
href={
'https://grafana.com/docs/grafana/next/enterprise/license/license-restrictions/#active-users-limit'
}
>
Learn more
</a>
</>
}
>
<Icon name="question-circle" />
</Tooltip>
</th>
)}
{contextSrv.hasPermission(AccessControlAction.UsersCreate) && (
<LinkButton href="admin/users/create" variant="primary">
New user
</LinkButton>
)}
</div>
{isLoading ? (
<PageLoader />
) : (
<>
<div className={cx(styles.table, 'admin-list-table')}>
<table className="filter-table form-inline filter-table--hover">
<thead>
<tr>
<th></th>
<th>Login</th>
<th>Email</th>
<th>Name</th>
<th>Belongs to</th>
{showLicensedRole && (
<th>
Last active&nbsp;
<Tooltip placement="top" content="Time since user was seen using Grafana">
Licensed role{' '}
<Tooltip
placement="top"
content={
<>
Licensed role is based on a user&apos;s Org role (i.e. Viewer, Editor, Admin) and their
dashboard/folder permissions.{' '}
<a
className={styles.link}
target="_blank"
rel="noreferrer noopener"
href={
'https://grafana.com/docs/grafana/next/enterprise/license/license-restrictions/#active-users-limit'
}
>
Learn more
</a>
</>
}
>
<Icon name="question-circle" />
</Tooltip>
</th>
<th style={{ width: '1%' }}></th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<UserListItem user={user} showLicensedRole={showLicensedRole} key={user.id} />
))}
</tbody>
</table>
</div>
{showPaging && <Pagination numberOfPages={totalPages} currentPage={page} onNavigate={changePage} />}
</>
)}
</Page.Contents>
</Page>
)}
<th>
Last active&nbsp;
<Tooltip placement="top" content="Time since user was seen using Grafana">
<Icon name="question-circle" />
</Tooltip>
</th>
<th style={{ width: '1%' }}></th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<UserListItem user={user} showLicensedRole={showLicensedRole} key={user.id} />
))}
</tbody>
</table>
</div>
{showPaging && <Pagination numberOfPages={totalPages} currentPage={page} onNavigate={changePage} />}
</>
)}
</Page.Contents>
);
};
export const UserListAdminPageContent = connector(UserListAdminPageUnConnected);
export function UserListAdminPage() {
return (
<Page navId="global-users">
<UserListAdminPageContent />
</Page>
);
}
const getUsersAriaLabel = (name: string) => {
return `Edit user's ${name} details`;
};
@@ -349,4 +356,4 @@ const getStyles = (theme: GrafanaTheme2) => {
};
};
export default connector(UserListAdminPageUnConnected);
export default UserListAdminPage;

View File

@@ -0,0 +1,52 @@
import { css } from '@emotion/css';
import React, { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { RadioButtonGroup, Field, useStyles2 } from '@grafana/ui';
import { contextSrv } from 'app/core/services/context_srv';
import { Page } from '../../core/components/Page/Page';
import { AccessControlAction } from '../../types';
import { UsersListPageContent } from '../users/UsersListPage';
import { UserListAdminPageContent } from './UserListAdminPage';
const views = [
{ value: 'admin', label: 'All organisations' },
{ value: 'org', label: 'This organisation' },
];
export default function UserListPage() {
const hasAccessToAdminUsers = contextSrv.hasAccess(AccessControlAction.UsersRead, contextSrv.isGrafanaAdmin);
const hasAccessToOrgUsers = contextSrv.hasPermission(AccessControlAction.OrgUsersRead);
const styles = useStyles2(getStyles);
const [view, setView] = useState(() => {
if (hasAccessToAdminUsers) {
return 'admin';
} else if (hasAccessToOrgUsers) {
return 'org';
}
return null;
});
const showToggle = hasAccessToOrgUsers && hasAccessToAdminUsers;
return (
<Page navId={'global-users'}>
{showToggle && (
<Field label={'Display list of users for'} className={styles.container}>
<RadioButtonGroup options={views} onChange={setView} value={view} />
</Field>
)}
{view === 'admin' ? <UserListAdminPageContent /> : <UsersListPageContent />}
</Page>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css`
margin: ${theme.spacing(2, 0)};
`,
};
};