diff --git a/app/soapbox/actions/admin.js b/app/soapbox/actions/admin.js index 4329aa670..ecf70923a 100644 --- a/app/soapbox/actions/admin.js +++ b/app/soapbox/actions/admin.js @@ -1,5 +1,9 @@ import api from '../api'; +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_FAIL = 'ADMIN_CONFIG_FETCH_FAIL'; + export const ADMIN_CONFIG_UPDATE_REQUEST = 'ADMIN_CONFIG_UPDATE_REQUEST'; export const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS'; export const ADMIN_CONFIG_UPDATE_FAIL = 'ADMIN_CONFIG_UPDATE_FAIL'; @@ -20,13 +24,26 @@ export const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST'; export const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS'; export const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL'; -export function updateAdminConfig(params) { +export function fetchConfig() { return (dispatch, getState) => { - dispatch({ type: ADMIN_CONFIG_UPDATE_REQUEST }); + dispatch({ type: ADMIN_CONFIG_FETCH_REQUEST }); return api(getState) - .post('/api/pleroma/admin/config', params) - .then(response => { - dispatch({ type: ADMIN_CONFIG_UPDATE_SUCCESS, config: response.data }); + .get('/api/pleroma/admin/config') + .then(({ data }) => { + dispatch({ type: ADMIN_CONFIG_FETCH_SUCCESS, configs: data.configs, needsReboot: data.need_reboot }); + }).catch(error => { + dispatch({ type: ADMIN_CONFIG_FETCH_FAIL, error }); + }); + }; +} + +export function updateConfig(configs) { + return (dispatch, getState) => { + dispatch({ type: ADMIN_CONFIG_UPDATE_REQUEST, configs }); + return api(getState) + .post('/api/pleroma/admin/config', { configs }) + .then(({ data: { configs } }) => { + dispatch({ type: ADMIN_CONFIG_UPDATE_SUCCESS, configs }); }).catch(error => { dispatch({ type: ADMIN_CONFIG_UPDATE_FAIL, error }); }); diff --git a/app/soapbox/features/admin/components/admin_nav.js b/app/soapbox/features/admin/components/admin_nav.js index 74cefde8b..1d0a9a47f 100644 --- a/app/soapbox/features/admin/components/admin_nav.js +++ b/app/soapbox/features/admin/components/admin_nav.js @@ -37,18 +37,18 @@ class AdminNav extends React.PureComponent { - {(instance.get('approval_required') || approvalCount > 0) && ( + {((instance.get('registrations') && instance.get('approval_required')) || approvalCount > 0) && ( )} - {!instance.get('registrations') && ( - {/* + {/* !instance.get('registrations') && ( + - */} - )} + + ) */} {/* diff --git a/app/soapbox/features/admin/components/registration_mode_picker.js b/app/soapbox/features/admin/components/registration_mode_picker.js new file mode 100644 index 000000000..9b60055f0 --- /dev/null +++ b/app/soapbox/features/admin/components/registration_mode_picker.js @@ -0,0 +1,80 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { + SimpleForm, + FieldsGroup, + RadioGroup, + RadioItem, +} from 'soapbox/features/forms'; +import { updateConfig } from 'soapbox/actions/admin'; + +const mapStateToProps = (state, props) => ({ + mode: modeFromInstance(state.get('instance')), + openReportCount: state.getIn(['admin', 'open_report_count']), +}); + +const generateConfig = mode => { + const configMap = { + open: [{ tuple: [':registrations_open', true] }, { tuple: [':account_approval_required', false] }], + approval: [{ tuple: [':registrations_open', true] }, { tuple: [':account_approval_required', true] }], + closed: [{ tuple: [':registrations_open', false] }], + }; + + return [{ + group: ':pleroma', + key: ':instance', + value: configMap[mode], + }]; +}; + +const modeFromInstance = instance => { + if (instance.get('approval_required') && instance.get('registrations')) return 'approval'; + return instance.get('registrations') ? 'open' : 'closed'; +}; + +export default @connect(mapStateToProps) +class RegistrationModePicker extends ImmutablePureComponent { + + onChange = e => { + const { dispatch } = this.props; + const config = generateConfig(e.target.value); + dispatch(updateConfig(config)); + } + + render() { + const { mode } = this.props; + + return ( + + + } + onChange={this.onChange} + > + } + hint={} + checked={mode === 'open'} + value='open' + /> + } + hint={} + checked={mode === 'approval'} + value='approval' + /> + } + hint={} + checked={mode === 'closed'} + value='closed' + /> + + + + ); + }; + +} diff --git a/app/soapbox/features/admin/index.js b/app/soapbox/features/admin/index.js index eb91ff2a5..506416540 100644 --- a/app/soapbox/features/admin/index.js +++ b/app/soapbox/features/admin/index.js @@ -5,6 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; +import RegistrationModePicker from './components/registration_mode_picker'; import { parseVersion } from 'soapbox/utils/features'; const messages = defineMessages({ @@ -63,14 +64,14 @@ class Dashboard extends ImmutablePureComponent { - {/* TODO: Awaiting approval users count */} +
-
+

    -
  • Soapbox FE 1.1.0
  • -
  • {v.software} {v.version}
  • +
  • Soapbox FE 1.1.0
  • +
  • {v.software} {v.version}
diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js index 37b3dd1fb..7856a8027 100644 --- a/app/soapbox/features/soapbox_config/index.js +++ b/app/soapbox/features/soapbox_config/index.js @@ -14,7 +14,7 @@ import { FormPropTypes, } from 'soapbox/features/forms'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; -import { updateAdminConfig } from 'soapbox/actions/admin'; +import { updateConfig } from 'soapbox/actions/admin'; import Icon from 'soapbox/components/icon'; import { defaultConfig } from 'soapbox/actions/soapbox'; import { uploadMedia } from 'soapbox/actions/media'; @@ -82,20 +82,18 @@ class SoapboxConfig extends ImmutablePureComponent { getParams = () => { const { soapbox } = this.state; - return { - configs: [{ - group: ':pleroma', - key: ':frontend_configurations', - value: [{ - tuple: [':soapbox_fe', soapbox.toJS()], - }], + return [{ + group: ':pleroma', + key: ':frontend_configurations', + value: [{ + tuple: [':soapbox_fe', soapbox.toJS()], }], - }; + }]; } handleSubmit = (event) => { const { dispatch } = this.props; - dispatch(updateAdminConfig(this.getParams())).then(() => { + dispatch(updateConfig(this.getParams())).then(() => { this.setState({ isLoading: false }); }).catch((error) => { this.setState({ isLoading: false }); diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 313f60607..370400dd1 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -16,7 +16,7 @@ import { debounce } from 'lodash'; import { uploadCompose, resetCompose } from '../../actions/compose'; import { expandHomeTimeline } from '../../actions/timelines'; import { expandNotifications } from '../../actions/notifications'; -import { fetchReports, fetchUsers } from '../../actions/admin'; +import { fetchReports, fetchUsers, fetchConfig } from '../../actions/admin'; import { fetchFilters } from '../../actions/filters'; import { fetchChats } from 'soapbox/actions/chats'; import { clearHeight } from '../../actions/height_cache'; @@ -465,6 +465,7 @@ class UI extends React.PureComponent { if (isStaff(account)) { this.props.dispatch(fetchReports({ state: 'open' })); this.props.dispatch(fetchUsers({ page: 1, filters: 'local,need_approval' })); + this.props.dispatch(fetchConfig()); } setTimeout(() => this.props.dispatch(fetchFilters()), 500); diff --git a/app/soapbox/reducers/__tests__/admin-test.js b/app/soapbox/reducers/__tests__/admin-test.js index 891b8cabe..9f983af0a 100644 --- a/app/soapbox/reducers/__tests__/admin-test.js +++ b/app/soapbox/reducers/__tests__/admin-test.js @@ -1,5 +1,10 @@ import reducer from '../admin'; -import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { + Map as ImmutableMap, + List as ImmutableList, + OrderedSet as ImmutableOrderedSet, + fromJS, +} from 'immutable'; describe('admin reducer', () => { it('should return the initial state', () => { @@ -8,6 +13,8 @@ describe('admin reducer', () => { open_report_count: 0, users: ImmutableMap(), awaitingApproval: ImmutableOrderedSet(), + configs: ImmutableList(), + needsReboot: false, })); }); }); diff --git a/app/soapbox/reducers/__tests__/soapbox-test.js b/app/soapbox/reducers/__tests__/soapbox-test.js index 25106faac..ff06e7c69 100644 --- a/app/soapbox/reducers/__tests__/soapbox-test.js +++ b/app/soapbox/reducers/__tests__/soapbox-test.js @@ -37,7 +37,7 @@ describe('soapbox reducer', () => { const state = ImmutableMap({ brandColor: '#354e91' }); const action = { type: ADMIN_CONFIG_UPDATE_SUCCESS, - config: soapboxConfig, + configs: soapboxConfig.configs, }; expect(reducer(state, action).toJS()).toMatchObject({ brandColor: '#254f92', diff --git a/app/soapbox/reducers/admin.js b/app/soapbox/reducers/admin.js index c7c6be507..eaacf7323 100644 --- a/app/soapbox/reducers/admin.js +++ b/app/soapbox/reducers/admin.js @@ -1,4 +1,5 @@ import { + ADMIN_CONFIG_FETCH_SUCCESS, ADMIN_REPORTS_FETCH_SUCCESS, ADMIN_USERS_FETCH_SUCCESS, ADMIN_USERS_DELETE_REQUEST, @@ -18,6 +19,8 @@ const initialState = ImmutableMap({ users: ImmutableMap(), open_report_count: 0, awaitingApproval: ImmutableOrderedSet(), + configs: ImmutableList(), + needsReboot: false, }); function importUsers(state, users) { @@ -51,6 +54,8 @@ function approveUsers(state, users) { export default function admin(state = initialState, action) { switch(action.type) { + case ADMIN_CONFIG_FETCH_SUCCESS: + return state.set('configs', fromJS(action.configs)); case ADMIN_REPORTS_FETCH_SUCCESS: if (action.params && action.params.state === 'open') { return state diff --git a/app/soapbox/reducers/instance.js b/app/soapbox/reducers/instance.js index 3bcd1c4ba..72105279e 100644 --- a/app/soapbox/reducers/instance.js +++ b/app/soapbox/reducers/instance.js @@ -3,7 +3,9 @@ import { NODEINFO_FETCH_SUCCESS, } from '../actions/instance'; import { PRELOAD_IMPORT } from 'soapbox/actions/preload'; -import { Map as ImmutableMap, fromJS } from 'immutable'; +import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin'; +import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { ConfigDB } from 'soapbox/utils/config_db'; const nodeinfoToInstance = nodeinfo => { // Match Pleroma's develop branch @@ -37,6 +39,28 @@ const preloadImport = (state, action, path) => { return data ? initialState.mergeDeep(fromJS(data)) : state; }; +const getConfigValue = (instanceConfig, key) => { + const v = instanceConfig + .find(value => value.getIn(['tuple', 0]) === key); + + return v ? v.getIn(['tuple', 1]) : undefined; +}; + +const importConfigs = (state, configs) => { + // FIXME: This is pretty hacked together. Need to make a cleaner map. + const config = ConfigDB.find(configs, ':pleroma', ':instance'); + if (!config) return state; + const value = config.get('value', ImmutableList()); + + return state.withMutations(state => { + const registrationsOpen = getConfigValue(value, ':registrations_open'); + const approvalRequired = getConfigValue(value, ':account_approval_required'); + + state.update('registrations', c => typeof registrationsOpen === 'boolean' ? registrationsOpen : c); + state.update('approval_required', c => typeof approvalRequired === 'boolean' ? approvalRequired : c); + }); +}; + export default function instance(state = initialState, action) { switch(action.type) { case PRELOAD_IMPORT: @@ -45,6 +69,9 @@ export default function instance(state = initialState, action) { return initialState.mergeDeep(fromJS(action.instance)); case NODEINFO_FETCH_SUCCESS: return nodeinfoToInstance(fromJS(action.nodeinfo)).mergeDeep(state); + case ADMIN_CONFIG_UPDATE_REQUEST: + case ADMIN_CONFIG_UPDATE_SUCCESS: + return importConfigs(state, fromJS(action.configs)); default: return state; } diff --git a/app/soapbox/reducers/soapbox.js b/app/soapbox/reducers/soapbox.js index 40e65c608..5b7db02a8 100644 --- a/app/soapbox/reducers/soapbox.js +++ b/app/soapbox/reducers/soapbox.js @@ -4,7 +4,7 @@ import { SOAPBOX_CONFIG_REQUEST_FAIL, } from '../actions/soapbox'; import { PRELOAD_IMPORT } from 'soapbox/actions/preload'; -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { Map as ImmutableMap, fromJS } from 'immutable'; import { ConfigDB } from 'soapbox/utils/config_db'; const initialState = ImmutableMap(); @@ -13,9 +13,7 @@ const fallbackState = ImmutableMap({ brandColor: '#0482d8', // Azure }); -const updateFromAdmin = (state, config) => { - const configs = config.get('configs', ImmutableList()); - +const updateFromAdmin = (state, configs) => { try { return ConfigDB.find(configs, ':pleroma', ':frontend_configurations') .get('value') @@ -47,7 +45,7 @@ export default function soapbox(state = initialState, action) { case SOAPBOX_CONFIG_REQUEST_FAIL: return fallbackState.mergeDeep(state); case ADMIN_CONFIG_UPDATE_SUCCESS: - return updateFromAdmin(state, fromJS(action.config)); + return updateFromAdmin(state, fromJS(action.configs)); default: return state; }