Admin: add UserIndex to view a list of registered users
This commit is contained in:
parent
e14df4139b
commit
80a682f120
8 changed files with 162 additions and 6 deletions
|
@ -130,7 +130,9 @@ export function fetchAccount(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchRelationships([id]));
|
dispatch(fetchRelationships([id]));
|
||||||
|
|
||||||
if (getState().getIn(['accounts', id], null) !== null) {
|
const account = getState().getIn(['accounts', id]);
|
||||||
|
|
||||||
|
if (account && !account.get('dirty')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +158,15 @@ export function fetchAccount(id) {
|
||||||
|
|
||||||
export function fetchAccountByUsername(username) {
|
export function fetchAccountByUsername(username) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
const account = getState().get('accounts').find(account => account.get('acct') === username);
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
dispatch(fetchAccount(account.get('id')));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${username}`).then(response => {
|
api(getState).get(`/api/v1/accounts/${username}`).then(response => {
|
||||||
|
dispatch(fetchRelationships([response.data.id]));
|
||||||
dispatch(importFetchedAccount(response.data));
|
dispatch(importFetchedAccount(response.data));
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
dispatch(fetchAccountSuccess());
|
dispatch(fetchAccountSuccess());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer';
|
import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer';
|
||||||
|
import { fetchRelationships } from 'soapbox/actions/accounts';
|
||||||
|
|
||||||
export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
|
export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
|
||||||
export const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
|
export const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
|
||||||
|
@ -129,8 +130,9 @@ export function fetchUsers(params) {
|
||||||
dispatch({ type: ADMIN_USERS_FETCH_REQUEST, params });
|
dispatch({ type: ADMIN_USERS_FETCH_REQUEST, params });
|
||||||
return api(getState)
|
return api(getState)
|
||||||
.get('/api/pleroma/admin/users', { params })
|
.get('/api/pleroma/admin/users', { params })
|
||||||
.then(({ data }) => {
|
.then(({ data: { users, count, page_size: pageSize } }) => {
|
||||||
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, data, params });
|
dispatch(fetchRelationships(users.map(user => user.id)));
|
||||||
|
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users, count, pageSize, params });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, params });
|
dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, params });
|
||||||
});
|
});
|
||||||
|
|
|
@ -291,7 +291,8 @@ class Header extends ImmutablePureComponent {
|
||||||
const info = this.makeInfo();
|
const info = this.makeInfo();
|
||||||
const menu = this.makeMenu();
|
const menu = this.makeMenu();
|
||||||
|
|
||||||
const headerMissing = (account.get('header').indexOf('/headers/original/missing.png') > -1);
|
const header = account.get('header', '');
|
||||||
|
const headerMissing = !header || ['/images/banner.png', '/headers/original/missing.png'].some(path => header.endsWith(path));
|
||||||
const avatarSize = isSmallScreen ? 90 : 200;
|
const avatarSize = isSmallScreen ? 90 : 200;
|
||||||
const deactivated = !account.getIn(['pleroma', 'is_active'], true);
|
const deactivated = !account.getIn(['pleroma', 'is_active'], true);
|
||||||
|
|
||||||
|
@ -306,7 +307,7 @@ class Header extends ImmutablePureComponent {
|
||||||
<SubscriptionButton account={account} />
|
<SubscriptionButton account={account} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StillImage src={account.get('header')} alt='' className='parallax' />
|
{header && <StillImage src={account.get('header')} alt='' className='parallax' />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='account__header__bar'>
|
<div className='account__header__bar'>
|
||||||
|
|
71
app/soapbox/features/admin/user_index.js
Normal file
71
app/soapbox/features/admin/user_index.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
||||||
|
import { fetchUsers } from 'soapbox/actions/admin';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import AccountContainer from 'soapbox/containers/account_container';
|
||||||
|
import Column from 'soapbox/features/ui/components/column';
|
||||||
|
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
accountIds: state.getIn(['admin', 'usersList']),
|
||||||
|
hasMore: false,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class UserIndex extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
params: PropTypes.object.isRequired,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
accountIds: ImmutablePropTypes.orderedSet,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
|
diffCount: PropTypes.number,
|
||||||
|
isAccount: PropTypes.bool,
|
||||||
|
unavailable: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(fetchUsers({ filters: 'local,active' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoadMore = debounce(() => {
|
||||||
|
// if (this.props.accountId && this.props.accountId !== -1) {
|
||||||
|
// this.props.dispatch(expandFollowers(this.props.accountId));
|
||||||
|
// }
|
||||||
|
}, 300, { leading: true });
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { accountIds, hasMore } = this.props;
|
||||||
|
|
||||||
|
if (!accountIds) {
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<LoadingIndicator />
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<ScrollableList
|
||||||
|
scrollKey='user-index'
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={this.handleLoadMore}
|
||||||
|
emptyMessage={<FormattedMessage id='admin.user_index.empty' defaultMessage='No users found.' />}
|
||||||
|
>
|
||||||
|
{accountIds.map(id =>
|
||||||
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
|
)}
|
||||||
|
</ScrollableList>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -95,6 +95,7 @@ import {
|
||||||
ModerationLog,
|
ModerationLog,
|
||||||
CryptoDonate,
|
CryptoDonate,
|
||||||
ScheduledStatuses,
|
ScheduledStatuses,
|
||||||
|
UserIndex,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
|
|
||||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||||
|
@ -265,6 +266,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<WrappedRoute path='/admin/approval' page={AdminPage} component={AwaitingApproval} content={children} exact />
|
<WrappedRoute path='/admin/approval' page={AdminPage} component={AwaitingApproval} content={children} exact />
|
||||||
<WrappedRoute path='/admin/reports' page={AdminPage} component={Reports} content={children} exact />
|
<WrappedRoute path='/admin/reports' page={AdminPage} component={Reports} content={children} exact />
|
||||||
<WrappedRoute path='/admin/log' page={AdminPage} component={ModerationLog} content={children} exact />
|
<WrappedRoute path='/admin/log' page={AdminPage} component={ModerationLog} content={children} exact />
|
||||||
|
<WrappedRoute path='/admin/users' page={AdminPage} component={UserIndex} content={children} exact />
|
||||||
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
|
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />
|
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />
|
||||||
|
|
|
@ -241,3 +241,7 @@ export function CryptoDonate() {
|
||||||
export function ScheduledStatuses() {
|
export function ScheduledStatuses() {
|
||||||
return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses');
|
return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function UserIndex() {
|
||||||
|
return import(/* webpackChunkName: "features/admin/user_index" */'../../admin/user_index');
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from 'immutable';
|
} from 'immutable';
|
||||||
import { normalizePleromaUserFields } from 'soapbox/utils/pleroma';
|
import { normalizePleromaUserFields } from 'soapbox/utils/pleroma';
|
||||||
import {
|
import {
|
||||||
|
ADMIN_USERS_FETCH_SUCCESS,
|
||||||
ADMIN_USERS_TAG_REQUEST,
|
ADMIN_USERS_TAG_REQUEST,
|
||||||
ADMIN_USERS_TAG_FAIL,
|
ADMIN_USERS_TAG_FAIL,
|
||||||
ADMIN_USERS_UNTAG_REQUEST,
|
ADMIN_USERS_UNTAG_REQUEST,
|
||||||
|
@ -121,6 +122,66 @@ const removePermission = (state, accountIds, permissionGroup) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildAccount = adminUser => fromJS({
|
||||||
|
id: adminUser.get('id'),
|
||||||
|
username: adminUser.get('nickname').split('@')[0],
|
||||||
|
acct: adminUser.get('nickname'),
|
||||||
|
display_name: adminUser.get('display_name'),
|
||||||
|
display_name_html: adminUser.get('display_name'),
|
||||||
|
note: '',
|
||||||
|
url: adminUser.get('url'),
|
||||||
|
avatar: adminUser.get('avatar'),
|
||||||
|
avatar_static: adminUser.get('avatar'),
|
||||||
|
header: '',
|
||||||
|
header_static: '',
|
||||||
|
emojis: [],
|
||||||
|
fields: [],
|
||||||
|
pleroma: {
|
||||||
|
is_active: adminUser.get('is_active'),
|
||||||
|
is_confirmed: adminUser.get('is_confirmed'),
|
||||||
|
is_admin: adminUser.getIn(['roles', 'admin']),
|
||||||
|
is_moderator: adminUser.getIn(['roles', 'moderator']),
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
pleroma: {
|
||||||
|
actor_type: adminUser.get('actor_type'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dirty: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mergeAdminUser = (account, adminUser) => {
|
||||||
|
return account.withMutations(account => {
|
||||||
|
account.set('display_name', adminUser.get('display_name'));
|
||||||
|
account.set('avatar', adminUser.get('avatar'));
|
||||||
|
account.set('avatar_static', adminUser.get('avatar'));
|
||||||
|
account.setIn(['pleroma', 'is_active'], adminUser.get('is_active'));
|
||||||
|
account.setIn(['pleroma', 'is_admin'], adminUser.getIn(['roles', 'admin']));
|
||||||
|
account.setIn(['pleroma', 'is_moderator'], adminUser.getIn(['roles', 'moderator']));
|
||||||
|
account.setIn(['pleroma', 'is_confirmed'], adminUser.get('is_confirmed'));
|
||||||
|
account.set('dirty', true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const importAdminUser = (state, adminUser) => {
|
||||||
|
const id = adminUser.get('id');
|
||||||
|
const account = state.get(id);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return state.set(id, buildAccount(adminUser));
|
||||||
|
} else {
|
||||||
|
return state.set(id, mergeAdminUser(account, adminUser));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const importAdminUsers = (state, adminUsers) => {
|
||||||
|
return state.withMutations(state => {
|
||||||
|
fromJS(adminUsers).forEach(adminUser => {
|
||||||
|
importAdminUser(state, adminUser);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function accounts(state = initialState, action) {
|
export default function accounts(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case ACCOUNT_IMPORT:
|
case ACCOUNT_IMPORT:
|
||||||
|
@ -150,6 +211,8 @@ export default function accounts(state = initialState, action) {
|
||||||
return removePermission(state, action.accountIds, action.permissionGroup);
|
return removePermission(state, action.accountIds, action.permissionGroup);
|
||||||
case ADMIN_USERS_DELETE_REQUEST:
|
case ADMIN_USERS_DELETE_REQUEST:
|
||||||
return setDeactivated(state, action.nicknames);
|
return setDeactivated(state, action.nicknames);
|
||||||
|
case ADMIN_USERS_FETCH_SUCCESS:
|
||||||
|
return importAdminUsers(state, action.users);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ const initialState = ImmutableMap({
|
||||||
reports: ImmutableMap(),
|
reports: ImmutableMap(),
|
||||||
openReports: ImmutableOrderedSet(),
|
openReports: ImmutableOrderedSet(),
|
||||||
users: ImmutableMap(),
|
users: ImmutableMap(),
|
||||||
|
usersList: ImmutableOrderedSet(),
|
||||||
awaitingApproval: ImmutableOrderedSet(),
|
awaitingApproval: ImmutableOrderedSet(),
|
||||||
configs: ImmutableList(),
|
configs: ImmutableList(),
|
||||||
needsReboot: false,
|
needsReboot: false,
|
||||||
|
@ -28,6 +29,8 @@ const initialState = ImmutableMap({
|
||||||
|
|
||||||
function importUsers(state, users) {
|
function importUsers(state, users) {
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
|
const ids = users.map(user => user.id);
|
||||||
|
state.update('usersList', ImmutableOrderedSet(), items => items.union(ids));
|
||||||
users.forEach(user => {
|
users.forEach(user => {
|
||||||
user = normalizePleromaUserFields(user);
|
user = normalizePleromaUserFields(user);
|
||||||
if (!user.is_approved) {
|
if (!user.is_approved) {
|
||||||
|
@ -94,7 +97,7 @@ export default function admin(state = initialState, action) {
|
||||||
case ADMIN_REPORTS_PATCH_SUCCESS:
|
case ADMIN_REPORTS_PATCH_SUCCESS:
|
||||||
return handleReportDiffs(state, action.reports);
|
return handleReportDiffs(state, action.reports);
|
||||||
case ADMIN_USERS_FETCH_SUCCESS:
|
case ADMIN_USERS_FETCH_SUCCESS:
|
||||||
return importUsers(state, action.data.users);
|
return importUsers(state, action.users);
|
||||||
case ADMIN_USERS_DELETE_REQUEST:
|
case ADMIN_USERS_DELETE_REQUEST:
|
||||||
case ADMIN_USERS_DELETE_SUCCESS:
|
case ADMIN_USERS_DELETE_SUCCESS:
|
||||||
return deleteUsers(state, action.nicknames);
|
return deleteUsers(state, action.nicknames);
|
||||||
|
|
Loading…
Reference in a new issue