mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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'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
|
||||
<Tooltip placement="top" content="Time since user was seen using Grafana">
|
||||
Licensed role{' '}
|
||||
<Tooltip
|
||||
placement="top"
|
||||
content={
|
||||
<>
|
||||
Licensed role is based on a user'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
|
||||
<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;
|
||||
|
||||
52
public/app/features/admin/UserListPage.tsx
Normal file
52
public/app/features/admin/UserListPage.tsx
Normal 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)};
|
||||
`,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user