From 1398c7ff4a5f9fed530f5c9b05f686415a513f24 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 13 Nov 2019 20:15:50 +0100 Subject: [PATCH] Client - add application configuration in Application Admin - #15 --- .../fittrackee_api/application/utils.py | 4 +- fittrackee_client/src/actions/activities.js | 4 + fittrackee_client/src/actions/application.js | 38 ++++ fittrackee_client/src/actions/stats.js | 16 -- .../Activity/ActivityForms/FormWithGpx.jsx | 23 +- .../src/components/Admin/AdminDashboard.jsx | 7 +- .../src/components/Admin/AdminStats.jsx | 4 +- .../components/Admin/Application/Config.jsx | 89 ++++++++ .../Admin/Application/ConfigForm.jsx | 198 ++++++++++++++++++ .../components/Admin/Application/index.jsx | 54 +++++ .../src/components/Admin/index.jsx | 82 ++++---- fittrackee_client/src/components/App.css | 4 + fittrackee_client/src/components/App.jsx | 22 +- .../src/components/Others/AccessDenied.jsx | 20 +- .../src/components/Others/NotFound.jsx | 4 +- .../src/components/User/Form.jsx | 3 +- .../src/components/User/Profile.jsx | 13 +- .../src/components/User/UserForm.jsx | 3 + fittrackee_client/src/fitTrackeeApi/index.js | 2 +- .../src/locales/en/administration.json | 9 + fittrackee_client/src/locales/en/common.json | 7 +- .../src/locales/en/messages.json | 1 + .../src/locales/fr/administration.json | 9 + fittrackee_client/src/locales/fr/common.json | 7 +- .../src/locales/fr/messages.json | 1 + fittrackee_client/src/reducers/index.js | 6 + fittrackee_client/src/reducers/initial.js | 8 + fittrackee_client/src/utils/index.js | 22 +- 28 files changed, 563 insertions(+), 97 deletions(-) create mode 100644 fittrackee_client/src/actions/application.js create mode 100644 fittrackee_client/src/components/Admin/Application/Config.jsx create mode 100644 fittrackee_client/src/components/Admin/Application/ConfigForm.jsx create mode 100644 fittrackee_client/src/components/Admin/Application/index.jsx diff --git a/fittrackee_api/fittrackee_api/application/utils.py b/fittrackee_api/fittrackee_api/application/utils.py index 032c102b..b56ba562 100644 --- a/fittrackee_api/fittrackee_api/application/utils.py +++ b/fittrackee_api/fittrackee_api/application/utils.py @@ -11,8 +11,8 @@ def init_config(): """ init application configuration if not existing in database - Note: get some configuration values from env variables (for FitTrackee versions - prior to v0.3.0) + Note: get some configuration values from env variables + (for FitTrackee versions prior to v0.3.0) """ existing_config = AppConfig.query.one_or_none() if not existing_config: diff --git a/fittrackee_client/src/actions/activities.js b/fittrackee_client/src/actions/activities.js index 997f1084..ceb25a31 100644 --- a/fittrackee_client/src/actions/activities.js +++ b/fittrackee_client/src/actions/activities.js @@ -43,6 +43,10 @@ export const addActivity = form => dispatch => dispatch(loadProfile()) history.push('/') } + } else if (ret.status === 413) { + dispatch( + setError('activities|File size is greater than the allowed size') + ) } else { dispatch(setError(`activities|${ret.message}`)) } diff --git a/fittrackee_client/src/actions/application.js b/fittrackee_client/src/actions/application.js new file mode 100644 index 00000000..bab6e8ae --- /dev/null +++ b/fittrackee_client/src/actions/application.js @@ -0,0 +1,38 @@ +import FitTrackeeGenericApi from '../fitTrackeeApi' +import { setError } from './index' + +export const setAppConfig = data => ({ + type: 'SET_APP_CONFIG', + data, +}) + +export const setAppStats = data => ({ + type: 'SET_APP_STATS', + data, +}) + +export const getAppData = target => dispatch => + FitTrackeeGenericApi.getData(target) + .then(ret => { + if (ret.status === 'success') { + if (target === 'config') { + dispatch(setAppConfig(ret.data)) + } else if (target === 'stats/all') { + dispatch(setAppStats(ret.data)) + } + } else { + dispatch(setError(`application|${ret.message}`)) + } + }) + .catch(error => dispatch(setError(`application|${error}`))) + +export const updateAppConfig = formData => dispatch => + FitTrackeeGenericApi.updateData('config', formData) + .then(ret => { + if (ret.status === 'success') { + dispatch(setAppConfig(ret.data)) + } else { + dispatch(setError(`application|${ret.message}`)) + } + }) + .catch(error => dispatch(setError(`application|${error}`))) diff --git a/fittrackee_client/src/actions/stats.js b/fittrackee_client/src/actions/stats.js index 844378e0..834e65d4 100644 --- a/fittrackee_client/src/actions/stats.js +++ b/fittrackee_client/src/actions/stats.js @@ -1,22 +1,6 @@ import FitTrackeeGenericApi from '../fitTrackeeApi' import { setData, setError } from './index' -export const setAppStats = data => ({ - type: 'SET_APP_STATS', - data, -}) - -export const getAppStats = () => dispatch => - FitTrackeeGenericApi.getData('stats/all') - .then(ret => { - if (ret.status === 'success') { - dispatch(setAppStats(ret.data)) - } else { - dispatch(setError(`application|${ret.message}`)) - } - }) - .catch(error => dispatch(setError(`application|${error}`))) - export const getStats = (userId, type, data) => dispatch => FitTrackeeGenericApi.getData(`stats/${userId}/${type}`, data) .then(ret => { diff --git a/fittrackee_client/src/components/Activity/ActivityForms/FormWithGpx.jsx b/fittrackee_client/src/components/Activity/ActivityForms/FormWithGpx.jsx index eda7addd..3f1a9c0d 100644 --- a/fittrackee_client/src/components/Activity/ActivityForms/FormWithGpx.jsx +++ b/fittrackee_client/src/components/Activity/ActivityForms/FormWithGpx.jsx @@ -5,17 +5,27 @@ import { connect } from 'react-redux' import { setLoading } from '../../../actions/index' import { addActivity, editActivity } from '../../../actions/activities' import { history } from '../../../index' -import { fileSizeLimit, gpxLimit, zipSizeLimit } from '../../../utils' +import { getFileSize } from '../../../utils' import { translateSports } from '../../../utils/activities' function FormWithGpx(props) { - const { activity, loading, onAddActivity, onEditActivity, sports, t } = props + const { + activity, + appConfig, + loading, + onAddActivity, + onEditActivity, + sports, + t, + } = props const sportId = activity ? activity.sport_id : '' const translatedSports = translateSports(sports, t, true) - // prettier-ignore - const zipTooltip = - `${t('activities:no folder inside')}, ${gpxLimit} ${ - t('activities:files max')}, ${t('activities:max size')}: ${zipSizeLimit}` + const zipTooltip = `${t('activities:no folder inside')}, ${ + appConfig.gpx_limit_import + } ${t('activities:files max')}, ${t('activities:max size')}: ${getFileSize( + appConfig.max_zip_file_size + )}` + const fileSizeLimit = getFileSize(appConfig.max_single_file_size) return (
({ + appConfig: state.application.config, loading: state.loading, }), dispatch => ({ diff --git a/fittrackee_client/src/components/Admin/AdminDashboard.jsx b/fittrackee_client/src/components/Admin/AdminDashboard.jsx index bead8429..cbae1f7d 100644 --- a/fittrackee_client/src/components/Admin/AdminDashboard.jsx +++ b/fittrackee_client/src/components/Admin/AdminDashboard.jsx @@ -3,15 +3,16 @@ import { Helmet } from 'react-helmet' import AdminStats from './AdminStats' -export default function AdminDashboard() { +export default function AdminDashboard(props) { + const { t } = props return (
- FitTrackee - Administration + {t('administration:FitTrackee administration')}
- FitTrackee administration + {t('administration:FitTrackee administration')}
diff --git a/fittrackee_client/src/components/Admin/AdminStats.jsx b/fittrackee_client/src/components/Admin/AdminStats.jsx index 13ace5c2..fa8735f6 100644 --- a/fittrackee_client/src/components/Admin/AdminStats.jsx +++ b/fittrackee_client/src/components/Admin/AdminStats.jsx @@ -2,7 +2,7 @@ import React from 'react' import { withTranslation } from 'react-i18next' import { connect } from 'react-redux' -import { getAppStats } from '../../actions/stats' +import { getAppData } from '../../actions/application' class AdminStats extends React.Component { componentDidMount() { @@ -80,7 +80,7 @@ export default withTranslation()( }), dispatch => ({ loadAppStats: () => { - dispatch(getAppStats()) + dispatch(getAppData('stats/all')) }, }) )(AdminStats) diff --git a/fittrackee_client/src/components/Admin/Application/Config.jsx b/fittrackee_client/src/components/Admin/Application/Config.jsx new file mode 100644 index 00000000..cdaf667a --- /dev/null +++ b/fittrackee_client/src/components/Admin/Application/Config.jsx @@ -0,0 +1,89 @@ +import React from 'react' +import { Helmet } from 'react-helmet' + +import Message from '../../Common/Message' +import { history } from '../../../index' +import { getFileSize } from '../../../utils' + +export default function Config({ appConfig, message, t, updateIsInEdition }) { + return ( +
+ + + FitTrackee - {t('administration:Application configuration')} + + + +
+
+
+
+
+ {t('administration:Application configuration')} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ {t('administration:Enable registration')}: + + {appConfig.registration + ? t('common:yes') + : t('common:no')} +
+ {t('administration:Max. number of active users')}: + {appConfig.max_users}
+ {t( + 'administration:Max. size of ' + 'uploaded files' + )} + : + {getFileSize(appConfig.max_single_file_size)}
+ {t('administration:Max. size of zip archive')}: + {getFileSize(appConfig.max_zip_file_size)}
+ {t('administration:Max. files of zip archive')}: + {appConfig.gpx_limit_import}
+ updateIsInEdition()} + value={t('common:Edit')} + /> + history.push('/admin')} + value={t('common:Back')} + /> +
+
+
+
+
+
+
+
+ ) +} diff --git a/fittrackee_client/src/components/Admin/Application/ConfigForm.jsx b/fittrackee_client/src/components/Admin/Application/ConfigForm.jsx new file mode 100644 index 00000000..37c4a2c2 --- /dev/null +++ b/fittrackee_client/src/components/Admin/Application/ConfigForm.jsx @@ -0,0 +1,198 @@ +import React from 'react' +import { connect } from 'react-redux' +import { Helmet } from 'react-helmet' + +import Message from '../../Common/Message' +import { updateAppConfig } from '../../../actions/application' +import { getFileSizeInMB } from '../../../utils' + +class AdminApplication extends React.Component { + constructor(props, context) { + super(props, context) + this.state = { + formData: {}, + } + } + componentDidMount() { + this.initForm() + } + + initForm() { + const { appConfig } = this.props + const formData = {} + Object.keys(appConfig).map(k => + appConfig[k] === null + ? (formData[k] = '') + : ['max_single_file_size', 'max_zip_file_size'].includes(k) + ? (formData[k] = getFileSizeInMB(appConfig[k])) + : (formData[k] = appConfig[k]) + ) + this.setState({ formData }) + } + + handleFormChange(e) { + const { formData } = this.state + if (e.target.name === 'registration') { + formData[e.target.name] = e.target.checked + } else { + formData[e.target.name] = +e.target.value + } + this.setState(formData) + } + + render() { + const { + message, + onHandleConfigFormSubmit, + t, + updateIsInEdition, + } = this.props + const { formData } = this.state + return ( +
+ + + FitTrackee - {t('administration:Application configuration')} + + + {message && } + {Object.keys(formData).length > 0 && ( +
+
+
+
+
+ {t('administration:Application configuration')} +
+
+ { + event.preventDefault() + onHandleConfigFormSubmit(formData) + updateIsInEdition() + }} + > +
+ + this.handleFormChange(e)} + /> +
+
+ + this.handleFormChange(e)} + /> +
+
+ + this.handleFormChange(e)} + /> +
+
+ + this.handleFormChange(e)} + /> +
+
+ + this.handleFormChange(e)} + /> +
+ + updateIsInEdition()} + value={t('common:Cancel')} + /> + +
+
+
+
+
+ )} +
+ ) + } +} + +export default connect( + () => ({}), + dispatch => ({ + onHandleConfigFormSubmit: formData => { + formData.max_single_file_size *= 1048576 + formData.max_zip_file_size *= 1048576 + dispatch(updateAppConfig(formData)) + }, + }) +)(AdminApplication) diff --git a/fittrackee_client/src/components/Admin/Application/index.jsx b/fittrackee_client/src/components/Admin/Application/index.jsx new file mode 100644 index 00000000..db55b454 --- /dev/null +++ b/fittrackee_client/src/components/Admin/Application/index.jsx @@ -0,0 +1,54 @@ +import React from 'react' +import { connect } from 'react-redux' +import { Helmet } from 'react-helmet' + +import Config from './Config' +import ConfigForm from './ConfigForm' +import Message from '../../Common/Message' + +class AdminApplication extends React.Component { + constructor(props, context) { + super(props, context) + this.state = { + isInEdition: false, + } + } + + render() { + const { appConfig, message, t } = this.props + const { isInEdition } = this.state + return ( +
+ + FitTrackee - {t('administration:Administration')} + + {message && } + {isInEdition ? ( + { + this.setState({ isInEdition: false }) + }} + t={t} + /> + ) : ( + { + this.setState({ isInEdition: true }) + }} + /> + )} +
+ ) + } +} + +export default connect(state => ({ + appConfig: state.application.config, + message: state.message, + user: state.user, +}))(AdminApplication) diff --git a/fittrackee_client/src/components/Admin/index.jsx b/fittrackee_client/src/components/Admin/index.jsx index 0e6c4191..7d5e1f65 100644 --- a/fittrackee_client/src/components/Admin/index.jsx +++ b/fittrackee_client/src/components/Admin/index.jsx @@ -2,14 +2,13 @@ import React from 'react' import { Helmet } from 'react-helmet' import { withTranslation } from 'react-i18next' import { connect } from 'react-redux' -import { Link, Redirect, Route, Switch } from 'react-router-dom' +import { Link, Route, Switch } from 'react-router-dom' +import AdminApplication from './Application' import AdminDashboard from './AdminDashboard' import AdminMenu from './AdminMenu' import AdminSports from './Sports' -import AccessDenied from './../Others/AccessDenied' import NotFound from './../Others/NotFound' -import { isLoggedIn } from '../../utils' function Admin(props) { const { t, user } = props @@ -19,47 +18,48 @@ function Admin(props) { FitTrackee - {t('administration:Administration')}
-
-
-
-
- - {t('administration:Administration')} - -
-
- + {user.admin ? ( +
+
+
+
+ + {t('administration:Administration')} + +
+
+ +
+
+ + } + /> + } + /> + } + /> + + +
-
- {isLoggedIn() ? ( - user.admin ? ( - - } - /> - } - /> - - - ) : ( - - ) - ) : ( - - )} -
-
+ ) : ( + + )}
) diff --git a/fittrackee_client/src/components/App.css b/fittrackee_client/src/components/App.css index b65f0290..1ccb9f5b 100644 --- a/fittrackee_client/src/components/App.css +++ b/fittrackee_client/src/components/App.css @@ -177,6 +177,10 @@ label { margin-left: 10px; } +.app-config-form label { + font-weight: bold; +} + .card { text-align: left; } diff --git a/fittrackee_client/src/components/App.jsx b/fittrackee_client/src/components/App.jsx index 2a240599..f7483fe3 100644 --- a/fittrackee_client/src/components/App.jsx +++ b/fittrackee_client/src/components/App.jsx @@ -1,4 +1,5 @@ import React from 'react' +import { connect } from 'react-redux' import { Redirect, Route, Switch } from 'react-router-dom' import './App.css' @@ -14,13 +15,17 @@ import Profile from './User/Profile' import ProfileEdit from './User/ProfileEdit' import Statistics from './Statistics' import UserForm from './User/UserForm' +import { getAppData } from '../actions/application' import { isLoggedIn } from '../utils' -export default class App extends React.Component { +class App extends React.Component { constructor(props) { super(props) this.props = props } + componentDidMount() { + this.props.loadAppConfig() + } render() { return ( @@ -74,7 +79,12 @@ export default class App extends React.Component { - + + isLoggedIn() ? : + } + />