2022-11-16 08:55:10 -06:00
|
|
|
import React, { useEffect, useState } from 'react';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2022-11-16 08:55:10 -06:00
|
|
|
import { LinkButton, FilterInput, VerticalGroup, HorizontalGroup, Pagination } from '@grafana/ui';
|
2018-08-10 04:31:35 -05:00
|
|
|
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
2022-07-06 10:00:56 -05:00
|
|
|
import { Page } from 'app/core/components/Page/Page';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { fetchRoleOptions } from 'app/core/components/RolePicker/api';
|
2019-03-14 02:21:53 -05:00
|
|
|
import { config } from 'app/core/config';
|
|
|
|
import { contextSrv, User } from 'app/core/services/context_srv';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { AccessControlAction, Role, StoreState, Team } from 'app/types';
|
|
|
|
|
2019-10-08 23:37:07 -05:00
|
|
|
import { connectWithCleanUp } from '../../core/components/connectWithCleanUp';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2022-11-16 08:55:10 -06:00
|
|
|
import { TeamListRow } from './TeamListRow';
|
|
|
|
import { deleteTeam, loadTeams, changePage, changeQuery } from './state/actions';
|
|
|
|
import { initialTeamsState } from './state/reducers';
|
|
|
|
import { isPermissionTeamAdmin } from './state/selectors';
|
2018-09-11 07:14:03 -05:00
|
|
|
|
|
|
|
export interface Props {
|
|
|
|
teams: Team[];
|
2022-11-16 08:55:10 -06:00
|
|
|
page: number;
|
|
|
|
query: string;
|
|
|
|
noTeams: boolean;
|
|
|
|
totalPages: number;
|
2018-10-11 04:49:34 -05:00
|
|
|
hasFetched: boolean;
|
2018-09-11 07:14:03 -05:00
|
|
|
loadTeams: typeof loadTeams;
|
|
|
|
deleteTeam: typeof deleteTeam;
|
2022-11-16 08:55:10 -06:00
|
|
|
changePage: typeof changePage;
|
|
|
|
changeQuery: typeof changeQuery;
|
2020-07-08 04:05:20 -05:00
|
|
|
editorsCanAdmin: boolean;
|
|
|
|
signedInUser: User;
|
2018-07-11 13:23:07 -05:00
|
|
|
}
|
|
|
|
|
2022-01-17 09:04:54 -06:00
|
|
|
export interface State {
|
|
|
|
roleOptions: Role[];
|
|
|
|
}
|
|
|
|
|
2022-11-16 08:55:10 -06:00
|
|
|
export const TeamList = ({
|
|
|
|
teams,
|
|
|
|
page,
|
|
|
|
query,
|
|
|
|
noTeams,
|
|
|
|
totalPages,
|
|
|
|
hasFetched,
|
|
|
|
loadTeams,
|
|
|
|
deleteTeam,
|
|
|
|
changeQuery,
|
|
|
|
changePage,
|
|
|
|
signedInUser,
|
|
|
|
editorsCanAdmin,
|
|
|
|
}: Props) => {
|
|
|
|
const [roleOptions, setRoleOptions] = useState<Role[]>([]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
loadTeams(true);
|
|
|
|
}, [loadTeams]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2022-02-04 07:54:42 -06:00
|
|
|
if (contextSrv.licensedAccessControlEnabled() && contextSrv.hasPermission(AccessControlAction.ActionRolesList)) {
|
2022-11-16 08:55:10 -06:00
|
|
|
fetchRoleOptions().then((roles) => setRoleOptions(roles));
|
2022-01-17 09:04:54 -06:00
|
|
|
}
|
2022-11-16 08:55:10 -06:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
const canCreate = canCreateTeam(editorsCanAdmin);
|
|
|
|
const displayRolePicker = shouldDisplayRolePicker();
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Page navId="teams">
|
|
|
|
<Page.Contents isLoading={!hasFetched}>
|
|
|
|
{noTeams ? (
|
|
|
|
<EmptyListCTA
|
|
|
|
title="You haven't created any teams yet."
|
|
|
|
buttonIcon="users-alt"
|
|
|
|
buttonLink="org/teams/new"
|
|
|
|
buttonTitle=" New team"
|
|
|
|
buttonDisabled={!contextSrv.hasPermission(AccessControlAction.ActionTeamsCreate)}
|
|
|
|
proTip="Assign folder and dashboard permissions to teams instead of users to ease administration."
|
|
|
|
proTipLink=""
|
|
|
|
proTipLinkTitle=""
|
|
|
|
proTipTarget="_blank"
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<div className="page-action-bar">
|
|
|
|
<div className="gf-form gf-form--grow">
|
|
|
|
<FilterInput placeholder="Search teams" value={query} onChange={changeQuery} />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<LinkButton href={canCreate ? 'org/teams/new' : '#'} disabled={!canCreate}>
|
|
|
|
New Team
|
|
|
|
</LinkButton>
|
|
|
|
</div>
|
2018-07-11 13:23:07 -05:00
|
|
|
|
2022-11-16 08:55:10 -06:00
|
|
|
<div className="admin-list-table">
|
|
|
|
<VerticalGroup spacing="md">
|
|
|
|
<table className="filter-table filter-table--hover form-inline">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th />
|
|
|
|
<th>Name</th>
|
|
|
|
<th>Email</th>
|
|
|
|
<th>Members</th>
|
|
|
|
{displayRolePicker && <th>Roles</th>}
|
|
|
|
<th style={{ width: '1%' }} />
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
{teams.map((team) => (
|
|
|
|
<TeamListRow
|
|
|
|
key={team.id}
|
|
|
|
team={team}
|
|
|
|
roleOptions={roleOptions}
|
|
|
|
displayRolePicker={displayRolePicker}
|
|
|
|
isTeamAdmin={isPermissionTeamAdmin({
|
|
|
|
permission: team.permission,
|
|
|
|
editorsCanAdmin,
|
|
|
|
signedInUser,
|
|
|
|
})}
|
|
|
|
onDelete={deleteTeam}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
<HorizontalGroup justify="flex-end">
|
|
|
|
<Pagination
|
|
|
|
hideWhenSinglePage
|
|
|
|
currentPage={page}
|
|
|
|
numberOfPages={totalPages}
|
|
|
|
onNavigate={changePage}
|
|
|
|
/>
|
|
|
|
</HorizontalGroup>
|
|
|
|
</VerticalGroup>
|
2022-02-15 11:09:03 -06:00
|
|
|
</div>
|
2022-11-16 08:55:10 -06:00
|
|
|
</>
|
2022-01-17 09:04:54 -06:00
|
|
|
)}
|
2022-11-16 08:55:10 -06:00
|
|
|
</Page.Contents>
|
|
|
|
</Page>
|
|
|
|
);
|
|
|
|
};
|
2018-10-11 04:49:34 -05:00
|
|
|
|
2022-11-16 08:55:10 -06:00
|
|
|
function canCreateTeam(editorsCanAdmin: boolean): boolean {
|
|
|
|
const teamAdmin = contextSrv.hasRole('Admin') || (editorsCanAdmin && contextSrv.hasRole('Editor'));
|
|
|
|
return contextSrv.hasAccess(AccessControlAction.ActionTeamsCreate, teamAdmin);
|
|
|
|
}
|
2018-08-09 04:05:20 -05:00
|
|
|
|
2022-11-16 08:55:10 -06:00
|
|
|
function shouldDisplayRolePicker(): boolean {
|
|
|
|
return (
|
|
|
|
contextSrv.licensedAccessControlEnabled() &&
|
|
|
|
contextSrv.hasPermission(AccessControlAction.ActionTeamsRolesList) &&
|
|
|
|
contextSrv.hasPermission(AccessControlAction.ActionRolesList)
|
|
|
|
);
|
2018-07-11 13:23:07 -05:00
|
|
|
}
|
|
|
|
|
2019-10-08 23:37:07 -05:00
|
|
|
function mapStateToProps(state: StoreState) {
|
2018-09-11 07:14:03 -05:00
|
|
|
return {
|
2022-11-16 08:55:10 -06:00
|
|
|
teams: state.teams.teams,
|
|
|
|
page: state.teams.page,
|
|
|
|
query: state.teams.query,
|
|
|
|
perPage: state.teams.perPage,
|
|
|
|
noTeams: state.teams.noTeams,
|
|
|
|
totalPages: state.teams.totalPages,
|
2018-10-11 04:49:34 -05:00
|
|
|
hasFetched: state.teams.hasFetched,
|
2019-03-14 02:21:53 -05:00
|
|
|
editorsCanAdmin: config.editorsCanAdmin, // this makes the feature toggle mockable/controllable from tests,
|
|
|
|
signedInUser: contextSrv.user, // this makes the feature toggle mockable/controllable from tests,
|
2018-09-11 07:14:03 -05:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const mapDispatchToProps = {
|
|
|
|
loadTeams,
|
|
|
|
deleteTeam,
|
2022-11-16 08:55:10 -06:00
|
|
|
changePage,
|
|
|
|
changeQuery,
|
2018-09-11 07:14:03 -05:00
|
|
|
};
|
|
|
|
|
2022-09-05 07:56:08 -05:00
|
|
|
export default connectWithCleanUp(
|
|
|
|
mapStateToProps,
|
|
|
|
mapDispatchToProps,
|
|
|
|
(state) => (state.teams = initialTeamsState)
|
|
|
|
)(TeamList);
|