mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: migrate admin/users from angular to react + redux (#22759)
* Start adding admin users list page to redux/react. * removed unused code. * added pagination. * changed so we use the new form styles. * added tooltip. * using tagbadge for authlabels. * remove unused code. * removed old code. * Fixed the last feedback on PR.
This commit is contained in:
parent
f78501f3b5
commit
be192b8191
@ -0,0 +1,8 @@
|
|||||||
|
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
|
||||||
|
import { Pagination } from './Pagination';
|
||||||
|
|
||||||
|
# Pagination
|
||||||
|
|
||||||
|
<Meta title="MDX|Pagination" component={Pagination} />
|
||||||
|
|
||||||
|
<Props of={Pagination}/>
|
@ -0,0 +1,22 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { number } from '@storybook/addon-knobs';
|
||||||
|
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||||
|
import { Pagination } from './Pagination';
|
||||||
|
import mdx from './Pagination.mdx';
|
||||||
|
|
||||||
|
export const WithPages = () => {
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const numberOfPages = number('Number of pages', 5);
|
||||||
|
return <Pagination numberOfPages={numberOfPages} currentPage={page} onNavigate={setPage} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'General/Pagination',
|
||||||
|
component: WithPages,
|
||||||
|
decorators: [withCenteredStory],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
page: mdx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
47
packages/grafana-ui/src/components/Pagination/Pagination.tsx
Normal file
47
packages/grafana-ui/src/components/Pagination/Pagination.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { stylesFactory } from '../../themes';
|
||||||
|
import { Button, ButtonVariant } from '../Forms/Button';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentPage: number;
|
||||||
|
numberOfPages: number;
|
||||||
|
onNavigate: (toPage: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavigate }) => {
|
||||||
|
const styles = getStyles();
|
||||||
|
const pages = [...new Array(numberOfPages).keys()];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<ol>
|
||||||
|
{pages.map(pageIndex => {
|
||||||
|
const page = pageIndex + 1;
|
||||||
|
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li key={page} className={styles.item}>
|
||||||
|
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory(() => {
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
float: right;
|
||||||
|
`,
|
||||||
|
item: css`
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -40,6 +40,7 @@ export { TimePicker } from './TimePicker/TimePicker';
|
|||||||
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
|
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
|
||||||
export { List } from './List/List';
|
export { List } from './List/List';
|
||||||
export { TagsInput } from './TagsInput/TagsInput';
|
export { TagsInput } from './TagsInput/TagsInput';
|
||||||
|
export { Pagination } from './Pagination/Pagination';
|
||||||
|
|
||||||
export { ConfirmModal } from './ConfirmModal/ConfirmModal';
|
export { ConfirmModal } from './ConfirmModal/ConfirmModal';
|
||||||
export { QueryField } from './QueryField/QueryField';
|
export { QueryField } from './QueryField/QueryField';
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
import { getTagColorsFromName } from '@grafana/ui';
|
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
|
||||||
import { NavModelSrv } from 'app/core/core';
|
|
||||||
import { Scope } from 'app/types/angular';
|
|
||||||
import { promiseToDigest } from 'app/core/utils/promiseToDigest';
|
|
||||||
|
|
||||||
export default class AdminListUsersCtrl {
|
|
||||||
users: any;
|
|
||||||
pages: any[] = [];
|
|
||||||
perPage = 50;
|
|
||||||
page = 1;
|
|
||||||
totalPages: number;
|
|
||||||
showPaging = false;
|
|
||||||
query: any;
|
|
||||||
navModel: any;
|
|
||||||
|
|
||||||
/** @ngInject */
|
|
||||||
constructor(private $scope: Scope, navModelSrv: NavModelSrv) {
|
|
||||||
this.navModel = navModelSrv.getNav('admin', 'global-users', 0);
|
|
||||||
this.query = '';
|
|
||||||
this.getUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
getUsers() {
|
|
||||||
promiseToDigest(this.$scope)(
|
|
||||||
getBackendSrv()
|
|
||||||
.get(`/api/users/search?perpage=${this.perPage}&page=${this.page}&query=${this.query}`)
|
|
||||||
.then((result: any) => {
|
|
||||||
this.users = result.users;
|
|
||||||
this.page = result.page;
|
|
||||||
this.perPage = result.perPage;
|
|
||||||
this.totalPages = Math.ceil(result.totalCount / result.perPage);
|
|
||||||
this.showPaging = this.totalPages > 1;
|
|
||||||
this.pages = [];
|
|
||||||
|
|
||||||
for (let i = 1; i < this.totalPages + 1; i++) {
|
|
||||||
this.pages.push({ page: i, current: i === this.page });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addUsersAuthLabels();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigateToPage(page: any) {
|
|
||||||
this.page = page.page;
|
|
||||||
this.getUsers();
|
|
||||||
}
|
|
||||||
|
|
||||||
addUsersAuthLabels() {
|
|
||||||
for (const user of this.users) {
|
|
||||||
user.authLabel = getAuthLabel(user);
|
|
||||||
user.authLabelStyle = getAuthLabelStyle(user.authLabel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthLabel(user: any) {
|
|
||||||
if (user.authLabels && user.authLabels.length) {
|
|
||||||
return user.authLabels[0];
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthLabelStyle(label: string) {
|
|
||||||
if (label === 'LDAP' || !label) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const { color, borderColor } = getTagColorsFromName(label);
|
|
||||||
return {
|
|
||||||
'background-color': color,
|
|
||||||
'border-color': borderColor,
|
|
||||||
};
|
|
||||||
}
|
|
152
public/app/features/admin/UserListAdminPage.tsx
Normal file
152
public/app/features/admin/UserListAdminPage.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { css, cx } from 'emotion';
|
||||||
|
import { hot } from 'react-hot-loader';
|
||||||
|
import { connect, MapDispatchToProps, MapStateToProps } from 'react-redux';
|
||||||
|
import { NavModel } from '@grafana/data';
|
||||||
|
import { Pagination, Forms, Tooltip, HorizontalGroup, stylesFactory } from '@grafana/ui';
|
||||||
|
import { StoreState, UserDTO } from '../../types';
|
||||||
|
import Page from 'app/core/components/Page/Page';
|
||||||
|
import { getNavModel } from '../../core/selectors/navModel';
|
||||||
|
import { fetchUsers, changeQuery, changePage } from './state/actions';
|
||||||
|
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||||
|
|
||||||
|
interface OwnProps {}
|
||||||
|
|
||||||
|
interface ConnectedProps {
|
||||||
|
navModel: NavModel;
|
||||||
|
users: UserDTO[];
|
||||||
|
query: string;
|
||||||
|
showPaging: boolean;
|
||||||
|
totalPages: number;
|
||||||
|
page: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
fetchUsers: typeof fetchUsers;
|
||||||
|
changeQuery: typeof changeQuery;
|
||||||
|
changePage: typeof changePage;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = OwnProps & ConnectedProps & DispatchProps;
|
||||||
|
|
||||||
|
const UserListAdminPageUnConnected: React.FC<Props> = props => {
|
||||||
|
const styles = getStyles();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
props.fetchUsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page navModel={props.navModel}>
|
||||||
|
<Page.Contents>
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<HorizontalGroup justify="space-between">
|
||||||
|
<Forms.Input
|
||||||
|
size="md"
|
||||||
|
type="text"
|
||||||
|
placeholder="Find user by name/login/email"
|
||||||
|
tabIndex={1}
|
||||||
|
autoFocus={true}
|
||||||
|
value={props.query}
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={event => props.changeQuery(event.currentTarget.value)}
|
||||||
|
prefix={<i className="fa fa-search" />}
|
||||||
|
/>
|
||||||
|
<Forms.LinkButton href="admin/users/create" variant="primary">
|
||||||
|
New user
|
||||||
|
</Forms.LinkButton>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
Seen
|
||||||
|
<Tooltip placement="top" content="Time since user was seen using Grafana">
|
||||||
|
<i className="fa fa-question-circle" />
|
||||||
|
</Tooltip>
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
<th style={{ width: '1%' }}></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{props.users.map(renderUser)}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{props.showPaging && (
|
||||||
|
<Pagination numberOfPages={props.totalPages} currentPage={props.page} onNavigate={props.changePage} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</Page.Contents>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderUser = (user: UserDTO) => {
|
||||||
|
const editUrl = `admin/users/edit/${user.id}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={user.id}>
|
||||||
|
<td className="width-4 text-center link-td">
|
||||||
|
<a href={editUrl}>
|
||||||
|
<img className="filter-table__avatar" src={user.avatarUrl} />
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td className="link-td">
|
||||||
|
<a href={editUrl}>{user.login}</a>
|
||||||
|
</td>
|
||||||
|
<td className="link-td">
|
||||||
|
<a href={editUrl}>{user.email}</a>
|
||||||
|
</td>
|
||||||
|
<td className="link-td">{user.lastSeenAtAge && <a href={editUrl}>{user.lastSeenAtAge}</a>}</td>
|
||||||
|
<td className="link-td">
|
||||||
|
{user.isAdmin && (
|
||||||
|
<a href={editUrl}>
|
||||||
|
<Tooltip placement="top" content="Grafana Admin">
|
||||||
|
<i className="fa fa-shield" />
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="text-right">
|
||||||
|
{Array.isArray(user.authLabels) && user.authLabels.length > 0 && (
|
||||||
|
<TagBadge label={user.authLabels[0]} removeIcon={false} count={0} />
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td className="text-right">
|
||||||
|
{user.isDisabled && <span className="label label-tag label-tag--gray">Disabled</span>}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStyles = stylesFactory(() => {
|
||||||
|
return {
|
||||||
|
table: css`
|
||||||
|
margin-top: 28px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps: MapDispatchToProps<DispatchProps, OwnProps> = {
|
||||||
|
fetchUsers,
|
||||||
|
changeQuery,
|
||||||
|
changePage,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps: MapStateToProps<ConnectedProps, OwnProps, StoreState> = state => ({
|
||||||
|
navModel: getNavModel(state.navIndex, 'global-users'),
|
||||||
|
users: state.userListAdmin.users,
|
||||||
|
query: state.userListAdmin.query,
|
||||||
|
showPaging: state.userListAdmin.showPaging,
|
||||||
|
totalPages: state.userListAdmin.totalPages,
|
||||||
|
page: state.userListAdmin.page,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UserListAdminPageUnConnected));
|
@ -1,4 +1,3 @@
|
|||||||
import AdminListUsersCtrl from './AdminListUsersCtrl';
|
|
||||||
import AdminEditUserCtrl from './AdminEditUserCtrl';
|
import AdminEditUserCtrl from './AdminEditUserCtrl';
|
||||||
import AdminListOrgsCtrl from './AdminListOrgsCtrl';
|
import AdminListOrgsCtrl from './AdminListOrgsCtrl';
|
||||||
import AdminEditOrgCtrl from './AdminEditOrgCtrl';
|
import AdminEditOrgCtrl from './AdminEditOrgCtrl';
|
||||||
@ -15,7 +14,6 @@ class AdminHomeCtrl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coreModule.controller('AdminListUsersCtrl', AdminListUsersCtrl);
|
|
||||||
coreModule.controller('AdminEditUserCtrl', AdminEditUserCtrl);
|
coreModule.controller('AdminEditUserCtrl', AdminEditUserCtrl);
|
||||||
coreModule.controller('AdminListOrgsCtrl', AdminListOrgsCtrl);
|
coreModule.controller('AdminListOrgsCtrl', AdminListOrgsCtrl);
|
||||||
coreModule.controller('AdminEditOrgCtrl', AdminEditOrgCtrl);
|
coreModule.controller('AdminEditOrgCtrl', AdminEditOrgCtrl);
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
<page-header model="ctrl.navModel"></page-header>
|
|
||||||
|
|
||||||
<div class="page-container page-body">
|
|
||||||
<div class="page-action-bar">
|
|
||||||
<label class="gf-form gf-form--grow gf-form--has-input-icon">
|
|
||||||
<input type="text" class="gf-form-input max-width-30" placeholder="Find user by name/login/email" tabindex="1" give-focus="true" ng-model="ctrl.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.getUsers()" />
|
|
||||||
<i class="gf-form-input-icon fa fa-search"></i>
|
|
||||||
</label>
|
|
||||||
<div class="page-action-bar__spacer"></div>
|
|
||||||
<a class="btn btn-primary" href="admin/users/create">
|
|
||||||
New user
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-list-table">
|
|
||||||
<table class="filter-table form-inline filter-table--hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Login</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>
|
|
||||||
Seen
|
|
||||||
<tip>Time since user was seen using Grafana</tip>
|
|
||||||
</th>
|
|
||||||
<th></th>
|
|
||||||
<th style="width: 1%"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="user in ctrl.users">
|
|
||||||
<td class="width-4 text-center link-td">
|
|
||||||
<a href="admin/users/edit/{{user.id}}">
|
|
||||||
<img class="filter-table__avatar" ng-src="{{user.avatarUrl}}"></img>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="link-td">
|
|
||||||
<a href="admin/users/edit/{{user.id}}">
|
|
||||||
{{user.login}}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="link-td">
|
|
||||||
<a href="admin/users/edit/{{user.id}}">
|
|
||||||
{{user.email}}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="link-td">
|
|
||||||
<a href="admin/users/edit/{{user.id}}">
|
|
||||||
{{user.lastSeenAtAge}}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="link-td">
|
|
||||||
<a href="admin/users/edit/{{user.id}}">
|
|
||||||
<i class="fa fa-shield" ng-show="user.isAdmin" bs-tooltip="'Grafana Admin'"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span class="label label-tag" ng-style="user.authLabelStyle" ng-if="user.authLabel">
|
|
||||||
{{user.authLabel}}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-right">
|
|
||||||
<span class="label label-tag label-tag--gray" ng-if="user.isDisabled">Disabled</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-list-paging" ng-if="ctrl.showPaging">
|
|
||||||
<ol>
|
|
||||||
<li ng-repeat="page in ctrl.pages">
|
|
||||||
<button class="btn btn-small" ng-class="{'btn-secondary': page.current, 'btn-inverse': !page.current}" ng-click="ctrl.navigateToPage(page)">{{page.page}}</button>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer />
|
|
@ -17,7 +17,11 @@ import {
|
|||||||
clearUserMappingInfoAction,
|
clearUserMappingInfoAction,
|
||||||
clearUserErrorAction,
|
clearUserErrorAction,
|
||||||
ldapFailedAction,
|
ldapFailedAction,
|
||||||
|
usersFetched,
|
||||||
|
queryChanged,
|
||||||
|
pageChanged,
|
||||||
} from './reducers';
|
} from './reducers';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
// UserAdminPage
|
// UserAdminPage
|
||||||
|
|
||||||
@ -239,3 +243,33 @@ export function clearUserMappingInfo(): ThunkResult<void> {
|
|||||||
dispatch(clearUserMappingInfoAction());
|
dispatch(clearUserMappingInfoAction());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserListAdminPage
|
||||||
|
|
||||||
|
export function fetchUsers(): ThunkResult<void> {
|
||||||
|
return async (dispatch, getState) => {
|
||||||
|
try {
|
||||||
|
const { perPage, page, query } = getState().userListAdmin;
|
||||||
|
const result = await getBackendSrv().get(`/api/users/search?perpage=${perPage}&page=${page}&query=${query}`);
|
||||||
|
dispatch(usersFetched(result));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchUsersWithDebounce = debounce(dispatch => dispatch(fetchUsers()), 500);
|
||||||
|
|
||||||
|
export function changeQuery(query: string): ThunkResult<void> {
|
||||||
|
return async dispatch => {
|
||||||
|
dispatch(queryChanged(query));
|
||||||
|
fetchUsersWithDebounce(dispatch);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changePage(page: number): ThunkResult<void> {
|
||||||
|
return async dispatch => {
|
||||||
|
dispatch(pageChanged(page));
|
||||||
|
dispatch(fetchUsers());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
UserDTO,
|
UserDTO,
|
||||||
UserOrg,
|
UserOrg,
|
||||||
UserSession,
|
UserSession,
|
||||||
|
UserListAdminState,
|
||||||
} from 'app/types';
|
} from 'app/types';
|
||||||
|
|
||||||
const initialLdapState: LdapState = {
|
const initialLdapState: LdapState = {
|
||||||
@ -118,7 +119,56 @@ export const {
|
|||||||
|
|
||||||
export const userAdminReducer = userAdminSlice.reducer;
|
export const userAdminReducer = userAdminSlice.reducer;
|
||||||
|
|
||||||
|
// UserListAdminPage
|
||||||
|
|
||||||
|
const initialUserListAdminState: UserListAdminState = {
|
||||||
|
users: [],
|
||||||
|
query: '',
|
||||||
|
page: 0,
|
||||||
|
perPage: 50,
|
||||||
|
totalPages: 1,
|
||||||
|
showPaging: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UsersFetched {
|
||||||
|
users: UserDTO[];
|
||||||
|
perPage: number;
|
||||||
|
page: number;
|
||||||
|
totalCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const userListAdminSlice = createSlice({
|
||||||
|
name: 'userListAdmin',
|
||||||
|
initialState: initialUserListAdminState,
|
||||||
|
reducers: {
|
||||||
|
usersFetched: (state, action: PayloadAction<UsersFetched>) => {
|
||||||
|
const { totalCount, perPage, ...rest } = action.payload;
|
||||||
|
const totalPages = Math.ceil(totalCount / perPage);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...rest,
|
||||||
|
totalPages,
|
||||||
|
perPage,
|
||||||
|
showPaging: totalPages > 1,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
queryChanged: (state, action: PayloadAction<string>) => ({
|
||||||
|
...state,
|
||||||
|
query: action.payload,
|
||||||
|
}),
|
||||||
|
pageChanged: (state, action: PayloadAction<number>) => ({
|
||||||
|
...state,
|
||||||
|
page: action.payload,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { usersFetched, queryChanged, pageChanged } = userListAdminSlice.actions;
|
||||||
|
export const userListAdminReducer = userListAdminSlice.reducer;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ldap: ldapReducer,
|
ldap: ldapReducer,
|
||||||
userAdmin: userAdminReducer,
|
userAdmin: userAdminReducer,
|
||||||
|
userListAdmin: userListAdminReducer,
|
||||||
};
|
};
|
||||||
|
@ -7,12 +7,12 @@ import DashboardImportCtrl from 'app/features/manage-dashboards/DashboardImportC
|
|||||||
import LdapPage from 'app/features/admin/ldap/LdapPage';
|
import LdapPage from 'app/features/admin/ldap/LdapPage';
|
||||||
import UserAdminPage from 'app/features/admin/UserAdminPage';
|
import UserAdminPage from 'app/features/admin/UserAdminPage';
|
||||||
import SignupPage from 'app/features/profile/SignupPage';
|
import SignupPage from 'app/features/profile/SignupPage';
|
||||||
|
import { LoginPage } from 'app/core/components/Login/LoginPage';
|
||||||
|
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { ILocationProvider, route } from 'angular';
|
import { ILocationProvider, route } from 'angular';
|
||||||
// Types
|
// Types
|
||||||
import { DashboardRouteInfo } from 'app/types';
|
import { DashboardRouteInfo } from 'app/types';
|
||||||
import { LoginPage } from 'app/core/components/Login/LoginPage';
|
|
||||||
import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamicImport';
|
import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamicImport';
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
@ -304,9 +304,11 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.when('/admin/users', {
|
.when('/admin/users', {
|
||||||
templateUrl: 'public/app/features/admin/partials/users.html',
|
template: '<react-container />',
|
||||||
controller: 'AdminListUsersCtrl',
|
resolve: {
|
||||||
controllerAs: 'ctrl',
|
component: () =>
|
||||||
|
SafeDynamicImport(import(/* webpackChunkName: "UserListAdminPage" */ 'app/features/admin/UserListAdminPage')),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.when('/admin/users/create', {
|
.when('/admin/users/create', {
|
||||||
template: '<react-container />',
|
template: '<react-container />',
|
||||||
|
@ -9,7 +9,7 @@ import { FolderState } from './folders';
|
|||||||
import { DashboardState } from './dashboard';
|
import { DashboardState } from './dashboard';
|
||||||
import { DataSourceSettingsState, DataSourcesState } from './datasources';
|
import { DataSourceSettingsState, DataSourcesState } from './datasources';
|
||||||
import { ExploreState } from './explore';
|
import { ExploreState } from './explore';
|
||||||
import { UserAdminState, UsersState, UserState } from './user';
|
import { UserAdminState, UserListAdminState, UsersState, UserState } from './user';
|
||||||
import { OrganizationState } from './organization';
|
import { OrganizationState } from './organization';
|
||||||
import { AppNotificationsState } from './appNotifications';
|
import { AppNotificationsState } from './appNotifications';
|
||||||
import { PluginsState } from './plugins';
|
import { PluginsState } from './plugins';
|
||||||
@ -42,6 +42,7 @@ export interface StoreState {
|
|||||||
ldap: LdapState;
|
ldap: LdapState;
|
||||||
apiKeys: ApiKeysState;
|
apiKeys: ApiKeysState;
|
||||||
userAdmin: UserAdminState;
|
userAdmin: UserAdminState;
|
||||||
|
userListAdmin: UserListAdminState;
|
||||||
templating: TemplatingState;
|
templating: TemplatingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,12 +29,14 @@ export interface UserDTO {
|
|||||||
name: string;
|
name: string;
|
||||||
isGrafanaAdmin: boolean;
|
isGrafanaAdmin: boolean;
|
||||||
isDisabled: boolean;
|
isDisabled: boolean;
|
||||||
|
isAdmin?: boolean;
|
||||||
isExternal?: boolean;
|
isExternal?: boolean;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
authLabels?: string[];
|
authLabels?: string[];
|
||||||
theme?: string;
|
theme?: string;
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
orgId?: number;
|
orgId?: number;
|
||||||
|
lastSeenAtAge?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Invitee {
|
export interface Invitee {
|
||||||
@ -101,3 +103,12 @@ export interface UserAdminError {
|
|||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserListAdminState {
|
||||||
|
users: UserDTO[];
|
||||||
|
query: string;
|
||||||
|
perPage: number;
|
||||||
|
page: number;
|
||||||
|
totalPages: number;
|
||||||
|
showPaging: boolean;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user