Admin: add UserIndex to view a list of registered users

merge-requests/613/head
Alex Gleason 2021-07-13 15:16:31 -05:00
rodzic e14df4139b
commit 80a682f120
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
8 zmienionych plików z 162 dodań i 6 usunięć

Wyświetl plik

@ -130,7 +130,9 @@ export function fetchAccount(id) {
return (dispatch, getState) => {
dispatch(fetchRelationships([id]));
if (getState().getIn(['accounts', id], null) !== null) {
const account = getState().getIn(['accounts', id]);
if (account && !account.get('dirty')) {
return;
}
@ -156,7 +158,15 @@ export function fetchAccount(id) {
export function fetchAccountByUsername(username) {
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 => {
dispatch(fetchRelationships([response.data.id]));
dispatch(importFetchedAccount(response.data));
}).then(() => {
dispatch(fetchAccountSuccess());

Wyświetl plik

@ -1,5 +1,6 @@
import api from '../api';
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_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS';
@ -129,8 +130,9 @@ export function fetchUsers(params) {
dispatch({ type: ADMIN_USERS_FETCH_REQUEST, params });
return api(getState)
.get('/api/pleroma/admin/users', { params })
.then(({ data }) => {
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, data, params });
.then(({ data: { users, count, page_size: pageSize } }) => {
dispatch(fetchRelationships(users.map(user => user.id)));
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users, count, pageSize, params });
}).catch(error => {
dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, params });
});

Wyświetl plik

@ -291,7 +291,8 @@ class Header extends ImmutablePureComponent {
const info = this.makeInfo();
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 deactivated = !account.getIn(['pleroma', 'is_active'], true);
@ -306,7 +307,7 @@ class Header extends ImmutablePureComponent {
<SubscriptionButton account={account} />
</div>
<StillImage src={account.get('header')} alt='' className='parallax' />
{header && <StillImage src={account.get('header')} alt='' className='parallax' />}
</div>
<div className='account__header__bar'>

Wyświetl plik

@ -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>
);
}
}

Wyświetl plik

@ -95,6 +95,7 @@ import {
ModerationLog,
CryptoDonate,
ScheduledStatuses,
UserIndex,
} from './util/async-components';
// 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/reports' page={AdminPage} component={Reports} 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='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />

Wyświetl plik

@ -241,3 +241,7 @@ export function CryptoDonate() {
export function ScheduledStatuses() {
return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses');
}
export function UserIndex() {
return import(/* webpackChunkName: "features/admin/user_index" */'../../admin/user_index');
}

Wyświetl plik

@ -13,6 +13,7 @@ import {
} from 'immutable';
import { normalizePleromaUserFields } from 'soapbox/utils/pleroma';
import {
ADMIN_USERS_FETCH_SUCCESS,
ADMIN_USERS_TAG_REQUEST,
ADMIN_USERS_TAG_FAIL,
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) {
switch(action.type) {
case ACCOUNT_IMPORT:
@ -150,6 +211,8 @@ export default function accounts(state = initialState, action) {
return removePermission(state, action.accountIds, action.permissionGroup);
case ADMIN_USERS_DELETE_REQUEST:
return setDeactivated(state, action.nicknames);
case ADMIN_USERS_FETCH_SUCCESS:
return importAdminUsers(state, action.users);
default:
return state;
}

Wyświetl plik

@ -21,6 +21,7 @@ const initialState = ImmutableMap({
reports: ImmutableMap(),
openReports: ImmutableOrderedSet(),
users: ImmutableMap(),
usersList: ImmutableOrderedSet(),
awaitingApproval: ImmutableOrderedSet(),
configs: ImmutableList(),
needsReboot: false,
@ -28,6 +29,8 @@ const initialState = ImmutableMap({
function importUsers(state, users) {
return state.withMutations(state => {
const ids = users.map(user => user.id);
state.update('usersList', ImmutableOrderedSet(), items => items.union(ids));
users.forEach(user => {
user = normalizePleromaUserFields(user);
if (!user.is_approved) {
@ -94,7 +97,7 @@ export default function admin(state = initialState, action) {
case ADMIN_REPORTS_PATCH_SUCCESS:
return handleReportDiffs(state, action.reports);
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_SUCCESS:
return deleteUsers(state, action.nicknames);