mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
first crude display
This commit is contained in:
parent
da856187d8
commit
8f99276606
@ -3,15 +3,16 @@ import LayoutSelector, { LayoutMode } from '../LayoutSelector/LayoutSelector';
|
||||
|
||||
export interface Props {
|
||||
searchQuery: string;
|
||||
layoutMode: LayoutMode;
|
||||
setLayoutMode: (mode: LayoutMode) => {};
|
||||
layoutMode?: LayoutMode;
|
||||
showLayoutMode: boolean;
|
||||
setLayoutMode?: (mode: LayoutMode) => {};
|
||||
setSearchQuery: (value: string) => {};
|
||||
linkButton: { href: string; title: string };
|
||||
}
|
||||
|
||||
export default class OrgActionBar extends PureComponent<Props> {
|
||||
render() {
|
||||
const { searchQuery, layoutMode, setLayoutMode, linkButton, setSearchQuery } = this.props;
|
||||
const { searchQuery, layoutMode, setLayoutMode, linkButton, setSearchQuery, showLayoutMode } = this.props;
|
||||
|
||||
return (
|
||||
<div className="page-action-bar">
|
||||
@ -26,7 +27,9 @@ export default class OrgActionBar extends PureComponent<Props> {
|
||||
/>
|
||||
<i className="gf-form-input-icon fa fa-search" />
|
||||
</label>
|
||||
<LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => setLayoutMode(mode)} />
|
||||
{showLayoutMode && (
|
||||
<LayoutSelector mode={layoutMode} onLayoutModeChanged={(mode: LayoutMode) => setLayoutMode(mode)} />
|
||||
)}
|
||||
</div>
|
||||
<div className="page-action-bar__spacer" />
|
||||
<a className="btn btn-success" href={linkButton.href} target="_blank">
|
||||
|
@ -10,7 +10,7 @@ const setup = (propOverrides?: object) => {
|
||||
plugins: [] as Plugin[],
|
||||
searchQuery: '',
|
||||
setPluginsSearchQuery: jest.fn(),
|
||||
setPluginsLayoutMoode: jest.fn(),
|
||||
setPluginsLayoutMode: jest.fn(),
|
||||
layoutMode: LayoutModes.Grid,
|
||||
loadPlugins: jest.fn(),
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ export interface Props {
|
||||
layoutMode: LayoutMode;
|
||||
searchQuery: string;
|
||||
loadPlugins: typeof loadPlugins;
|
||||
setPluginsLayoutMoode: typeof setPluginsLayoutMode;
|
||||
setPluginsLayoutMode: typeof setPluginsLayoutMode;
|
||||
setPluginsSearchQuery: typeof setPluginsSearchQuery;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export class PluginListPage extends PureComponent<Props> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { navModel, plugins, layoutMode, setPluginsLayoutMoode, setPluginsSearchQuery, searchQuery } = this.props;
|
||||
const { navModel, plugins, layoutMode, setPluginsLayoutMode, setPluginsSearchQuery, searchQuery } = this.props;
|
||||
|
||||
const linkButton = {
|
||||
href: 'https://grafana.com/plugins?utm_source=grafana_plugin_list',
|
||||
@ -42,8 +42,9 @@ export class PluginListPage extends PureComponent<Props> {
|
||||
<div className="page-container page-body">
|
||||
<OrgActionBar
|
||||
searchQuery={searchQuery}
|
||||
showLayoutMode={true}
|
||||
layoutMode={layoutMode}
|
||||
setLayoutMode={mode => setPluginsLayoutMoode(mode)}
|
||||
setLayoutMode={mode => setPluginsLayoutMode(mode)}
|
||||
setSearchQuery={query => setPluginsSearchQuery(query)}
|
||||
linkButton={linkButton}
|
||||
/>
|
||||
|
66
public/app/features/users/UsersListPage.tsx
Normal file
66
public/app/features/users/UsersListPage.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
|
||||
import PageHeader from 'app/core/components/PageHeader/PageHeader';
|
||||
import UsersTable from 'app/features/users/UsersTable';
|
||||
import { NavModel, User } from 'app/types';
|
||||
import { loadUsers, setUsersSearchQuery } from './state/actions';
|
||||
import { getNavModel } from '../../core/selectors/navModel';
|
||||
import { getUsers, getUsersSearchQuery } from './state/selectors';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
users: User[];
|
||||
searchQuery: string;
|
||||
loadUsers: typeof loadUsers;
|
||||
setUsersSearchQuery: typeof setUsersSearchQuery;
|
||||
}
|
||||
|
||||
export class UsersListPage extends PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
this.fetchUsers();
|
||||
}
|
||||
|
||||
async fetchUsers() {
|
||||
return await this.props.loadUsers();
|
||||
}
|
||||
render() {
|
||||
const { navModel, searchQuery, setUsersSearchQuery, users } = this.props;
|
||||
|
||||
const linkButton = {
|
||||
href: '/org/users/add',
|
||||
title: 'Add user',
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader model={navModel} />
|
||||
<div className="page-container page-body">
|
||||
<OrgActionBar
|
||||
searchQuery={searchQuery}
|
||||
showLayoutMode={false}
|
||||
setSearchQuery={setUsersSearchQuery}
|
||||
linkButton={linkButton}
|
||||
/>
|
||||
<UsersTable users={users} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, 'users'),
|
||||
users: getUsers(state.users),
|
||||
searchQuery: getUsersSearchQuery(state.users),
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
loadUsers,
|
||||
setUsersSearchQuery,
|
||||
};
|
||||
|
||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UsersListPage));
|
67
public/app/features/users/UsersTable.tsx
Normal file
67
public/app/features/users/UsersTable.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React, { SFC } from 'react';
|
||||
import { User } from 'app/types';
|
||||
|
||||
export interface Props {
|
||||
users: User[];
|
||||
onRoleChange: (value: string) => {};
|
||||
}
|
||||
|
||||
const UsersTable: SFC<Props> = props => {
|
||||
const { users } = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
Le Table
|
||||
<table className="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Login</th>
|
||||
<th>Email</th>
|
||||
<th>Seen</th>
|
||||
<th>Role</th>
|
||||
<th style={{ width: '34px' }} />
|
||||
</tr>
|
||||
</thead>
|
||||
{users.map((user, index) => {
|
||||
return (
|
||||
<tr key={`${user.userId}-${index}`}>
|
||||
<td className="width-4 text-center">
|
||||
<img className="filter-table__avatar" src={user.avatarUrl} />
|
||||
</td>
|
||||
<td>{user.login}</td>
|
||||
<td>
|
||||
<span className="ellipsis">{user.email}</span>
|
||||
</td>
|
||||
<td>{user.lastSeenAtAge}</td>
|
||||
<td>
|
||||
<div className="gf-form-select-wrapper width-12">
|
||||
<select
|
||||
value={user.role}
|
||||
className="gf-form-input"
|
||||
onChange={event => props.onRoleChange(event.target.value)}
|
||||
>
|
||||
{['Viewer', 'Editor', 'Admin'].map((option, index) => {
|
||||
return (
|
||||
<option value={option} key={`${option}-${index}`}>
|
||||
{option}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div onClick={() => props.removeUser(user)} className="btn btn-danger btn-mini">
|
||||
<i className="fa fa-remove" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UsersTable;
|
40
public/app/features/users/state/actions.ts
Normal file
40
public/app/features/users/state/actions.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { StoreState } from '../../../types';
|
||||
import { getBackendSrv } from '../../../core/services/backend_srv';
|
||||
import { User } from 'app/types';
|
||||
|
||||
export enum ActionTypes {
|
||||
LoadUsers = 'LOAD_USERS',
|
||||
SetUsersSearchQuery = 'SET_USERS_SEARCH_QUERY',
|
||||
}
|
||||
|
||||
export interface LoadUsersAction {
|
||||
type: ActionTypes.LoadUsers;
|
||||
payload: User[];
|
||||
}
|
||||
|
||||
export interface SetUsersSearchQueryAction {
|
||||
type: ActionTypes.SetUsersSearchQuery;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
const usersLoaded = (users: User[]): LoadUsersAction => ({
|
||||
type: ActionTypes.LoadUsers,
|
||||
payload: users,
|
||||
});
|
||||
|
||||
export const setUsersSearchQuery = (query: string): SetUsersSearchQueryAction => ({
|
||||
type: ActionTypes.SetUsersSearchQuery,
|
||||
payload: query,
|
||||
});
|
||||
|
||||
export type Action = LoadUsersAction | SetUsersSearchQueryAction;
|
||||
|
||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
||||
|
||||
export function loadUsers(): ThunkResult<void> {
|
||||
return async dispatch => {
|
||||
const users = await getBackendSrv().get('/api/org/users');
|
||||
dispatch(usersLoaded(users));
|
||||
};
|
||||
}
|
20
public/app/features/users/state/reducers.ts
Normal file
20
public/app/features/users/state/reducers.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { User, UsersState } from 'app/types';
|
||||
import { Action, ActionTypes } from './actions';
|
||||
|
||||
export const initialState: UsersState = { users: [] as User[], searchQuery: '' };
|
||||
|
||||
export const usersReducer = (state = initialState, action: Action): UsersState => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.LoadUsers:
|
||||
return { ...state, users: action.payload };
|
||||
|
||||
case ActionTypes.SetUsersSearchQuery:
|
||||
return { ...state, searchQuery: action.payload };
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default {
|
||||
users: usersReducer,
|
||||
};
|
2
public/app/features/users/state/selectors.ts
Normal file
2
public/app/features/users/state/selectors.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const getUsers = state => state.users;
|
||||
export const getUsersSearchQuery = state => state.searchQuery;
|
@ -9,6 +9,7 @@ import PluginListPage from 'app/features/plugins/PluginListPage';
|
||||
import FolderSettingsPage from 'app/features/folders/FolderSettingsPage';
|
||||
import FolderPermissions from 'app/features/folders/FolderPermissions';
|
||||
import DataSourcesListPage from 'app/features/datasources/DataSourcesListPage';
|
||||
import UsersListPage from 'app/features/users/UsersListPage';
|
||||
|
||||
/** @ngInject */
|
||||
export function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
@ -131,9 +132,10 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
|
||||
controller: 'NewOrgCtrl',
|
||||
})
|
||||
.when('/org/users', {
|
||||
templateUrl: 'public/app/features/org/partials/orgUsers.html',
|
||||
controller: 'OrgUsersCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => UsersListPage,
|
||||
},
|
||||
})
|
||||
.when('/org/users/invite', {
|
||||
templateUrl: 'public/app/features/org/partials/invite.html',
|
||||
|
@ -8,6 +8,7 @@ import foldersReducers from 'app/features/folders/state/reducers';
|
||||
import dashboardReducers from 'app/features/dashboard/state/reducers';
|
||||
import pluginReducers from 'app/features/plugins/state/reducers';
|
||||
import dataSourcesReducers from 'app/features/datasources/state/reducers';
|
||||
import usersReducers from 'app/features/users/state/reducers';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
...sharedReducers,
|
||||
@ -17,6 +18,7 @@ const rootReducer = combineReducers({
|
||||
...dashboardReducers,
|
||||
...pluginReducers,
|
||||
...dataSourcesReducers,
|
||||
...usersReducers,
|
||||
});
|
||||
|
||||
export let store;
|
||||
|
@ -7,6 +7,7 @@ import { DashboardState } from './dashboard';
|
||||
import { DashboardAcl, OrgRole, PermissionLevel } from './acl';
|
||||
import { DataSource, DataSourcesState } from './datasources';
|
||||
import { PluginMeta, Plugin, PluginsState } from './plugins';
|
||||
import { User, UsersState } from './users';
|
||||
|
||||
export {
|
||||
Team,
|
||||
@ -36,6 +37,8 @@ export {
|
||||
Plugin,
|
||||
PluginsState,
|
||||
DataSourcesState,
|
||||
User,
|
||||
UsersState,
|
||||
};
|
||||
|
||||
export interface StoreState {
|
||||
@ -46,4 +49,6 @@ export interface StoreState {
|
||||
team: TeamState;
|
||||
folder: FolderState;
|
||||
dashboard: DashboardState;
|
||||
dataSources: DataSourcesState;
|
||||
users: UsersState;
|
||||
}
|
||||
|
15
public/app/types/users.ts
Normal file
15
public/app/types/users.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export interface User {
|
||||
avatarUrl: string;
|
||||
email: string;
|
||||
lastSeenAt: string;
|
||||
lastSeenAtAge: string;
|
||||
login: string;
|
||||
orgId: number;
|
||||
role: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
export interface UsersState {
|
||||
users: User[];
|
||||
searchQuery: string;
|
||||
}
|
Loading…
Reference in New Issue
Block a user