diff --git a/fittrackee_client/src/App.vue b/fittrackee_client/src/App.vue index 2ff5b5d8..ec10b2a1 100644 --- a/fittrackee_client/src/App.vue +++ b/fittrackee_client/src/App.vue @@ -36,7 +36,7 @@ import NavBar from '@/components/NavBar.vue' import NoConfig from '@/components/NoConfig.vue' import { ROOT_STORE } from '@/store/constants' - import { IAppConfig } from '@/types/application' + import { TAppConfig } from '@/types/application' import { useStore } from '@/use/useStore' export default defineComponent({ @@ -49,7 +49,7 @@ setup() { const store = useStore() - const appConfig: ComputedRef = computed( + const appConfig: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG] ) const appLoading: ComputedRef = computed( diff --git a/fittrackee_client/src/components/Administration/AdminApplication.vue b/fittrackee_client/src/components/Administration/AdminApplication.vue new file mode 100644 index 00000000..451ad17d --- /dev/null +++ b/fittrackee_client/src/components/Administration/AdminApplication.vue @@ -0,0 +1,153 @@ + + + diff --git a/fittrackee_client/src/components/Administration/AdminMenu.vue b/fittrackee_client/src/components/Administration/AdminMenu.vue index d01d0392..dcce7611 100644 --- a/fittrackee_client/src/components/Administration/AdminMenu.vue +++ b/fittrackee_client/src/components/Administration/AdminMenu.vue @@ -6,7 +6,11 @@
-
{{ $t('admin.APPLICATION') }}
+
+ + {{ $t('admin.APPLICATION') }} + +
{{ $t('admin.UPDATE_APPLICATION_DESCRIPTION') }}
diff --git a/fittrackee_client/src/components/User/ProfileEdition/UserPictureEdition.vue b/fittrackee_client/src/components/User/ProfileEdition/UserPictureEdition.vue index 93ebe953..688a7f4f 100644 --- a/fittrackee_client/src/components/User/ProfileEdition/UserPictureEdition.vue +++ b/fittrackee_client/src/components/User/ProfileEdition/UserPictureEdition.vue @@ -39,7 +39,7 @@ import UserPicture from '@/components/User/UserPicture.vue' import { ROOT_STORE, USER_STORE } from '@/store/constants' - import { IAppConfig } from '@/types/application' + import { TAppConfig } from '@/types/application' import { IAuthUserProfile } from '@/types/user' import { useStore } from '@/use/useStore' import { getReadableFileSize } from '@/utils/files' @@ -60,7 +60,7 @@ const errorMessages: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES] ) - const appConfig: ComputedRef = computed( + const appConfig: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG] ) const fileSizeLimit = appConfig.value.max_single_file_size diff --git a/fittrackee_client/src/components/User/UserAuthForm.vue b/fittrackee_client/src/components/User/UserAuthForm.vue index d5d6e514..32b0895f 100644 --- a/fittrackee_client/src/components/User/UserAuthForm.vue +++ b/fittrackee_client/src/components/User/UserAuthForm.vue @@ -81,7 +81,7 @@ import { useRoute } from 'vue-router' import { ROOT_STORE, USER_STORE } from '@/store/constants' - import { IAppConfig } from '@/types/application' + import { TAppConfig } from '@/types/application' import { ILoginRegisterFormData } from '@/types/user' import { useStore } from '@/use/useStore' @@ -113,7 +113,7 @@ const errorMessages: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.ERROR_MESSAGES] ) - const appConfig: ComputedRef = computed( + const appConfig: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG] ) const registration_disabled: ComputedRef = computed( diff --git a/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutMap.vue b/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutMap.vue index 3a256928..c97430bf 100644 --- a/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutMap.vue +++ b/fittrackee_client/src/components/Workout/WorkoutDetail/WorkoutMap.vue @@ -34,7 +34,7 @@ import { ComputedRef, PropType, computed, defineComponent, ref } from 'vue' import { ROOT_STORE } from '@/store/constants' - import { IAppConfig } from '@/types/application' + import { TAppConfig } from '@/types/application' import { GeoJSONData } from '@/types/geojson' import { IWorkoutData, TCoordinates } from '@/types/workouts' import { useStore } from '@/use/useStore' @@ -103,7 +103,7 @@ ] : [] ) - const appConfig: ComputedRef = computed( + const appConfig: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG] ) const center = computed(() => getCenter(bounds)) diff --git a/fittrackee_client/src/components/Workout/WorkoutEdition.vue b/fittrackee_client/src/components/Workout/WorkoutEdition.vue index 9e6c6c1a..9226a874 100644 --- a/fittrackee_client/src/components/Workout/WorkoutEdition.vue +++ b/fittrackee_client/src/components/Workout/WorkoutEdition.vue @@ -221,7 +221,7 @@ import { useRouter } from 'vue-router' import { ROOT_STORE, WORKOUTS_STORE } from '@/store/constants' - import { IAppConfig } from '@/types/application' + import { TAppConfig } from '@/types/application' import { ISport } from '@/types/sports' import { IAuthUserProfile } from '@/types/user' import { IWorkout, IWorkoutForm } from '@/types/workouts' @@ -268,7 +268,7 @@ const translatedSports: ComputedRef = computed(() => translateSports(props.sports, t) ) - const appConfig: ComputedRef = computed( + const appConfig: ComputedRef = computed( () => store.getters[ROOT_STORE.GETTERS.APP_CONFIG] ) const fileSizeLimit = appConfig.value.max_single_file_size diff --git a/fittrackee_client/src/locales/en/administration.json b/fittrackee_client/src/locales/en/administration.json index 193762a7..87d7a75b 100644 --- a/fittrackee_client/src/locales/en/administration.json +++ b/fittrackee_client/src/locales/en/administration.json @@ -3,6 +3,14 @@ "ADMIN": "Admin", "ADMINISTRATION": "Administration", "APPLICATION": "Application", + "APP_CONFIG": { + "MAX_USERS_LABEL": "Max. number of active users", + "MAX_FILES_IN_ZIP_LABEL": "Max. files of zip archive", + "SINGLE_UPLOAD_MAX_SIZE_LABEL": "Max. size of uploaded files (in Mb)", + "TITLE": "Application configuration", + "ZIP_UPLOAD_MAX_SIZE_LABEL": "Max. size of zip archive (in Mb)" + }, + "BACK_TO_ADMIN": "Back to admin", "ENABLE_DISABLE_SPORTS": "Enable/disable sports.", "REGISTRATION_DISABLED": "Registration is currently disabled.", "REGISTRATION_ENABLED": "Registration is currently enabled.", diff --git a/fittrackee_client/src/locales/en/buttons.json b/fittrackee_client/src/locales/en/buttons.json index 95f1bf70..1fe7f3e3 100644 --- a/fittrackee_client/src/locales/en/buttons.json +++ b/fittrackee_client/src/locales/en/buttons.json @@ -1,6 +1,7 @@ { "CANCEL": "Cancel", "DELETE_MY_ACCOUNT": "Delete my account", + "EDIT": "Edit", "FILTER": "Filter", "LOGIN": "Log in", "NO": "No", diff --git a/fittrackee_client/src/locales/fr/administration.json b/fittrackee_client/src/locales/fr/administration.json index 7cc7a5c3..859843eb 100644 --- a/fittrackee_client/src/locales/fr/administration.json +++ b/fittrackee_client/src/locales/fr/administration.json @@ -3,6 +3,14 @@ "ADMIN": "Admin", "ADMINISTRATION": "Administration", "APPLICATION": "Application", + "APP_CONFIG": { + "MAX_USERS_LABEL": "Nombre maximum d'utilisateurs actifs ", + "MAX_FILES_IN_ZIP_LABEL": "Taille max. des archives zip (en Mo) ", + "SINGLE_UPLOAD_MAX_SIZE_LABEL": "Taille max. des fichiers (en Mo) ", + "TITLE": "Configuration de l'application", + "ZIP_UPLOAD_MAX_SIZE_LABEL": "Nombre max. de fichiers dans une archive zip " + }, + "BACK_TO_ADMIN": "Revenir à l'admin", "ENABLE_DISABLE_SPORTS": "Activer/désactiver des sports.", "REGISTRATION_DISABLED": "Les inscriptions sont actuellement désactivées.", "REGISTRATION_ENABLED": "Les inscriptions sont actuellement activées.", diff --git a/fittrackee_client/src/locales/fr/buttons.json b/fittrackee_client/src/locales/fr/buttons.json index 99c73b42..3c26a851 100644 --- a/fittrackee_client/src/locales/fr/buttons.json +++ b/fittrackee_client/src/locales/fr/buttons.json @@ -1,6 +1,7 @@ { "CANCEL": "Annuler", "DELETE_MY_ACCOUNT": "Supprimer mon compte", + "EDIT": "Modifier", "FILTER": "Filtrer", "LOGIN": "Se connecter", "NO": "Non", diff --git a/fittrackee_client/src/router/index.ts b/fittrackee_client/src/router/index.ts index b2b12f0e..7cc21652 100644 --- a/fittrackee_client/src/router/index.ts +++ b/fittrackee_client/src/router/index.ts @@ -1,5 +1,7 @@ import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' +import AdminApplication from '@/components/Administration/AdminApplication.vue' +import AdminMenu from '@/components/Administration/AdminMenu.vue' import Profile from '@/components/User/ProfileDisplay/index.vue' import UserInfos from '@/components/User/ProfileDisplay/UserInfos.vue' import UserPreferences from '@/components/User/ProfileDisplay/UserPreferences.vue' @@ -167,6 +169,24 @@ const routes: Array = [ name: 'Administration', component: () => import(/* webpackChunkName: 'admin' */ '@/views/AdminView.vue'), + children: [ + { + path: '', + name: 'AdministrationMenu', + component: AdminMenu, + }, + { + path: 'application', + name: 'ApplicationAdministration', + component: AdminApplication, + }, + { + path: 'application/edit', + name: 'ApplicationAdministrationEdition', + component: AdminApplication, + props: { edition: true }, + }, + ], }, { path: '/:pathMatch(.*)*', diff --git a/fittrackee_client/src/store/modules/root/actions.ts b/fittrackee_client/src/store/modules/root/actions.ts index 4f9b5f27..d80852d4 100644 --- a/fittrackee_client/src/store/modules/root/actions.ts +++ b/fittrackee_client/src/store/modules/root/actions.ts @@ -1,8 +1,10 @@ import { ActionContext, ActionTree } from 'vuex' import authApi from '@/api/authApi' +import router from '@/router' import { ROOT_STORE } from '@/store/constants' import { IRootActions, IRootState } from '@/store/modules/root/types' +import { TAppConfigForm } from '@/types/application' import { handleError } from '@/utils' export const actions: ActionTree & IRootActions = { @@ -46,4 +48,24 @@ export const actions: ActionTree & IRootActions = { }) .catch((error) => handleError(context, error)) }, + [ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG]( + context: ActionContext, + payload: TAppConfigForm + ): void { + context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES) + authApi + .patch('config', payload) + .then((res) => { + if (res.data.status === 'success') { + context.commit( + ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_CONFIG, + res.data.data + ) + router.push('/admin/application') + } else { + handleError(context, null) + } + }) + .catch((error) => handleError(context, error)) + }, } diff --git a/fittrackee_client/src/store/modules/root/enums.ts b/fittrackee_client/src/store/modules/root/enums.ts index 944ca3db..42c765ed 100644 --- a/fittrackee_client/src/store/modules/root/enums.ts +++ b/fittrackee_client/src/store/modules/root/enums.ts @@ -1,6 +1,7 @@ export enum RootActions { GET_APPLICATION_CONFIG = 'GET_APPLICATION_CONFIG', GET_APPLICATION_STATS = 'GET_APPLICATION_STATS', + UPDATE_APPLICATION_CONFIG = 'UPDATE_APPLICATION_CONFIG', } export enum RootGetters { diff --git a/fittrackee_client/src/store/modules/root/mutations.ts b/fittrackee_client/src/store/modules/root/mutations.ts index 228f398f..2cd76d38 100644 --- a/fittrackee_client/src/store/modules/root/mutations.ts +++ b/fittrackee_client/src/store/modules/root/mutations.ts @@ -2,7 +2,7 @@ import { MutationTree } from 'vuex' import { ROOT_STORE } from '@/store/constants' import { IRootState, TRootMutations } from '@/store/modules/root/types' -import { IAppConfig, IAppStatistics } from '@/types/application' +import { TAppConfig, IAppStatistics } from '@/types/application' import { localeFromLanguage } from '@/utils/locales' export const mutations: MutationTree & TRootMutations = { @@ -17,7 +17,7 @@ export const mutations: MutationTree & TRootMutations = { }, [ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_CONFIG]( state: IRootState, - config: IAppConfig + config: TAppConfig ) { state.application.config = config }, diff --git a/fittrackee_client/src/store/modules/root/types.ts b/fittrackee_client/src/store/modules/root/types.ts index abd43ebd..c0e9f6e8 100644 --- a/fittrackee_client/src/store/modules/root/types.ts +++ b/fittrackee_client/src/store/modules/root/types.ts @@ -7,7 +7,12 @@ import { } from 'vuex' import { ROOT_STORE } from '@/store/constants' -import { IAppConfig, IApplication, IAppStatistics } from '@/types/application' +import { + TAppConfig, + IApplication, + IAppStatistics, + TAppConfigForm, +} from '@/types/application' export interface IRootState { root: boolean @@ -25,10 +30,14 @@ export interface IRootActions { [ROOT_STORE.ACTIONS.GET_APPLICATION_STATS]( context: ActionContext ): void + [ROOT_STORE.ACTIONS.UPDATE_APPLICATION_CONFIG]( + context: ActionContext, + payload: TAppConfigForm + ): void } export interface IRootGetters { - [ROOT_STORE.GETTERS.APP_CONFIG](state: IRootState): IAppConfig + [ROOT_STORE.GETTERS.APP_CONFIG](state: IRootState): TAppConfig [ROOT_STORE.GETTERS.APP_LOADING](state: IRootState): boolean @@ -51,7 +60,7 @@ export type TRootMutations = { ): void [ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_CONFIG]( state: S, - config: IAppConfig + config: TAppConfig ): void [ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_LOADING]( state: S, diff --git a/fittrackee_client/src/types/application.ts b/fittrackee_client/src/types/application.ts index 99f79de9..fb8e787a 100644 --- a/fittrackee_client/src/types/application.ts +++ b/fittrackee_client/src/types/application.ts @@ -5,17 +5,26 @@ export interface IAppStatistics { workouts: number } -export interface IAppConfig { +export type TAppConfig = { + [key: string]: number | boolean | string federation_enabled: boolean - gpx_limit_import: number | null + gpx_limit_import: number is_registration_enabled: boolean map_attribution: string - max_single_file_size: number | null - max_users: number | null - max_zip_file_size: number | null + max_single_file_size: number + max_users: number + max_zip_file_size: number } export interface IApplication { statistics: IAppStatistics - config: IAppConfig + config: TAppConfig +} + +export type TAppConfigForm = { + [key: string]: number + gpx_limit_import: number + max_single_file_size: number + max_users: number + max_zip_file_size: number } diff --git a/fittrackee_client/src/utils/files.ts b/fittrackee_client/src/utils/files.ts index 5292b0d8..814ee0de 100644 --- a/fittrackee_client/src/utils/files.ts +++ b/fittrackee_client/src/utils/files.ts @@ -12,3 +12,8 @@ export const getReadableFileSize = ( const suffix = suffixes[i] return asText ? `${size}${suffix}` : { size, suffix } } + +export const getFileSizeInMB = (fileSize: number): number => { + const value = fileSize / 1048576 + return (!fileSize && 0) || +value.toFixed(2) +} diff --git a/fittrackee_client/src/views/AdminView.vue b/fittrackee_client/src/views/AdminView.vue index f7c13698..6a881042 100644 --- a/fittrackee_client/src/views/AdminView.vue +++ b/fittrackee_client/src/views/AdminView.vue @@ -1,8 +1,9 @@