diff --git a/CHANGELOG.md b/CHANGELOG.md index 02eaa9eb0..b7ed3d5f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ ## Next release - [TODO DEPLOY] `rails fablab:stripe:set_gateway` +## Next release (v4.7.6) +- Ability to disable the trainings module +- Prevent showing error message when testing for old versions during upgrade +- In the email notification, sent to admins on account creation, show the group of the user +- More explanations in the setup script +- Send pre-compressed assets to the browsers instead of the regular ones +- Fix a bug: subscriptions tab is selected by default in statistics, even if the module is disabled +- Fix a security issue: updated elliptic to 6.5.4 to fix [CVE-2020-28498](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28498) +- [TODO DEPLOY] `\curl -sSL https://raw.githubusercontent.com/sleede/fab-manager/master/scripts/nginx-packs-directive.sh | bash` +- [TODO DEPLOY] `rails db:seed` + +## v4.7.5 2021 March 08 +- Fix a bug: unable to compile the assets during the upgrade, if the env file has some whitespaces around the equal sign + ## v4.7.4 2021 March 08 - Show remaining training credits in the dashboard - Allow writing short rich descriptions for each subscription plan diff --git a/app/frontend/src/javascript/controllers/admin/graphs.js b/app/frontend/src/javascript/controllers/admin/graphs.js index 3883405e0..c7320006b 100644 --- a/app/frontend/src/javascript/controllers/admin/graphs.js +++ b/app/frontend/src/javascript/controllers/admin/graphs.js @@ -51,6 +51,9 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro // active tab will be set here $scope.selectedIndex = null; + // ui-bootstrap active tab index + $scope.selectedTab = 0; + // for palmares graphs, filters values are stored here $scope.ranking = { sortCriterion: 'ca', @@ -101,9 +104,11 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro * Callback called when the active tab is changed. * Recover the current tab and store its value in $scope.selectedIndex * @param tab {Object} elasticsearch statistic structure + * @param index {number} index of the tab in the $scope.statistics array */ - $scope.setActiveTab = function (tab) { + $scope.setActiveTab = function (tab, index) { $scope.selectedIndex = tab; + $scope.selectedTab = index; $scope.ranking.groupCriterion = 'subType'; if (tab.ca) { $scope.ranking.sortCriterion = 'ca'; @@ -113,6 +118,18 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro return refreshChart(); }; + /** + * Returns true if the provided tab must be hidden due to some global or local configuration + * @param tab {Object} elasticsearch statistic structure (from statistic_indices table) + */ + $scope.hiddenTab = function (tab) { + if (tab.graph) { + return !((tab.es_type_key === 'subscription' && !$rootScope.modules.plans) || + (tab.es_type_key === 'training' && !$rootScope.modules.trainings)); + } + return false; + }; + /** * Callback to close the date-picking popup and refresh the results */ @@ -137,11 +154,20 @@ Application.Controllers.controller('GraphsController', ['$scope', '$state', '$ro $scope.$watch(scope => scope.ranking.groupCriterion , (newValue, oldValue) => refreshChart()); return refreshChart(); + + // set the default tab to "machines" if "subscriptions" are disabled + if (!$rootScope.modules.plans) { + const idx = $scope.statistics.findIndex(s => s.es_type_key === 'machine'); + $scope.setActiveTab($scope.statistics[idx], idx); + } else { + const idx = $scope.statistics.findIndex(s => s.es_type_key === 'subscription'); + $scope.setActiveTab($scope.statistics[idx], idx); + } }); // workaround for angular-bootstrap::tabs behavior: on tab deletion, another tab will be selected // which will cause every tabs to reload, one by one, when the view is closed - return $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { + $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { if ((fromState.name === 'app.admin.stats_graphs') && (Object.keys(fromParams).length === 0)) { return $scope.preventRefresh = true; } diff --git a/app/frontend/src/javascript/controllers/admin/pricing.js b/app/frontend/src/javascript/controllers/admin/pricing.js index f3c08ab5b..da31903eb 100644 --- a/app/frontend/src/javascript/controllers/admin/pricing.js +++ b/app/frontend/src/javascript/controllers/admin/pricing.js @@ -111,7 +111,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @returns {float} */ $scope.findTrainingsPricing = function (trainingsPricings, trainingId, groupId) { - for (let trainingsPricing of Array.from(trainingsPricings)) { + for (const trainingsPricing of Array.from(trainingsPricings)) { if ((trainingsPricing.training_id === trainingId) && (trainingsPricing.group_id === groupId)) { return trainingsPricing; } @@ -138,7 +138,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @returns {Object} Plan, inherits from $resource */ $scope.getPlanFromId = function (id) { - for (let plan of Array.from($scope.plans)) { + for (const plan of Array.from($scope.plans)) { if (plan.id === parseInt(id)) { return plan; } @@ -151,7 +151,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @returns {Object} Group, inherits from $resource */ $scope.getGroupFromId = function (groups, id) { - for (let group of Array.from(groups)) { + for (const group of Array.from(groups)) { if (group.id === parseInt(id)) { return group; } @@ -313,7 +313,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @param [id] {number} credit id for edition, create a new credit object if not provided */ $scope.saveMachineCredit = function (data, id) { - for (let mc of Array.from($scope.machineCredits)) { + for (const mc of Array.from($scope.machineCredits)) { if ((mc.plan_id === data.plan_id) && (mc.creditable_id === data.creditable_id) && ((id === null) || (mc.id !== id))) { growl.error(_t('app.admin.pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists')); if (!id) { @@ -383,7 +383,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @param [id] {number} credit id for edition, create a new credit object if not provided */ $scope.saveSpaceCredit = function (data, id) { - for (let sc of Array.from($scope.spaceCredits)) { + for (const sc of Array.from($scope.spaceCredits)) { if ((sc.plan_id === data.plan_id) && (sc.creditable_id === data.creditable_id) && ((id === null) || (sc.id !== id))) { growl.error(_t('app.admin.pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists')); if (!id) { @@ -459,7 +459,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * Retrieve a price from prices array by a machineId and a groupId */ $scope.findPriceBy = function (prices, machineId, groupId) { - for (let price of Array.from(prices)) { + for (const price of Array.from(prices)) { if ((price.priceable_id === machineId) && (price.group_id === groupId)) { return price; } @@ -603,7 +603,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', /** * Load the next 10 coupons */ - $scope.loadMore = function() { + $scope.loadMore = function () { $scope.couponsPage++; Coupon.query({ page: $scope.couponsPage, filter: $scope.filter.coupon }, function (data) { $scope.coupons = $scope.coupons.concat(data); @@ -613,19 +613,19 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', /** * Reset the list of coupons according to the newly selected filter */ - $scope.updateCouponFilter = function() { + $scope.updateCouponFilter = function () { $scope.couponsPage = 1; Coupon.query({ page: $scope.couponsPage, filter: $scope.filter.coupon }, function (data) { $scope.coupons = data; }); - } + }; /** * Return the exemple price based on the configuration of the default slot duration. * @param type {string} 'hourly_rate' | * * @returns {number} price for "SLOT_DURATION" minutes. */ - $scope.examplePrice = function(type) { + $scope.examplePrice = function (type) { const hourlyRate = 10; if (type === 'hourly_rate') { @@ -634,7 +634,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', const price = (hourlyRate / 60) * $scope.slotDuration; return $filter('currency')(price); - } + }; /** * Setup the feature-tour for the admin/pricing page. @@ -660,14 +660,16 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', content: _t('app.admin.tour.pricing.new_plan.content'), placement: 'bottom' }); - uitour.createStep({ - selector: '.plans-pricing .trainings-tab', - stepId: 'trainings', - order: 2, - title: _t('app.admin.tour.pricing.trainings.title'), - content: _t('app.admin.tour.pricing.trainings.content'), - placement: 'bottom' - }); + if ($scope.$root.modules.trainings) { + uitour.createStep({ + selector: '.plans-pricing .trainings-tab', + stepId: 'trainings', + order: 2, + title: _t('app.admin.tour.pricing.trainings.title'), + content: _t('app.admin.tour.pricing.trainings.content'), + placement: 'bottom' + }); + } uitour.createStep({ selector: '.plans-pricing .machines-tab', stepId: 'machines', @@ -733,7 +735,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('pricing') < 0) { uitour.start(); } - } + }; /* PRIVATE SCOPE */ @@ -746,7 +748,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', // adds empty array for plan which hasn't any credits yet return (function () { const result = []; - for (let plan of Array.from($scope.plans)) { + for (const plan of Array.from($scope.plans)) { if ($scope.trainingCreditsGroups[plan.id] == null) { result.push($scope.trainingCreditsGroups[plan.id] = []); } else { @@ -763,7 +765,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @param id {number} * @returns {number} item index in the provided array */ - var findItemIdxById = function (items, id) { + const findItemIdxById = function (items, id) { return (items.map(function (item) { return item.id; })).indexOf(id); }; @@ -771,7 +773,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * Group the given credits array into a map associating the plan ID with its associated trainings/machines * @return {Object} the association map */ - var groupCreditsByPlan = function (credits) { + const groupCreditsByPlan = function (credits) { const creditsMap = {}; angular.forEach(credits, function (c) { if (!creditsMap[c.plan_id]) { @@ -787,11 +789,11 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @param trainingId {number|string} training ID * @param planId {number|string} plan ID */ - var findTrainingCredit = function (trainingId, planId) { + const findTrainingCredit = function (trainingId, planId) { trainingId = parseInt(trainingId); planId = parseInt(planId); - for (let credit of Array.from($scope.trainingCredits)) { + for (const credit of Array.from($scope.trainingCredits)) { if ((credit.plan_id === planId) && (credit.creditable_id === trainingId)) { return credit; } @@ -803,8 +805,8 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', * @param id {number} training ID * @returns {Object} Training inherited from $resource */ - var getTrainingFromId = function (id) { - for (let training of Array.from($scope.trainings)) { + const getTrainingFromId = function (id) { + for (const training of Array.from($scope.trainings)) { if (training.id === parseInt(id)) { return training; } diff --git a/app/frontend/src/javascript/controllers/admin/statistics.js b/app/frontend/src/javascript/controllers/admin/statistics.js index 997ee4b68..a08a5c2cb 100644 --- a/app/frontend/src/javascript/controllers/admin/statistics.js +++ b/app/frontend/src/javascript/controllers/admin/statistics.js @@ -76,6 +76,9 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', // active tab will be set here $scope.selectedIndex = null; + // ui-bootstrap active tab index + $scope.selectedTab = 0; + // type filter binding $scope.type = { selected: null, @@ -135,7 +138,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', */ $scope.customFieldName = function (field) { return _t(`app.admin.statistics.${field}`); - } + }; /** * Callback to open the datepicker (interval start) @@ -159,9 +162,11 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', * Callback called when the active tab is changed. * recover the current tab and store its value in $scope.selectedIndex * @param tab {Object} elasticsearch statistic structure (from statistic_indices table) + * @param index {number} index of the tab in the $scope.statistics array */ - $scope.setActiveTab = function (tab) { + $scope.setActiveTab = function (tab, index) { $scope.selectedIndex = tab; + $scope.selectedTab = index; $scope.type.selected = tab.types[0]; $scope.type.active = $scope.type.selected; $scope.customFilter.criterion = {}; @@ -179,9 +184,10 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', */ $scope.hiddenTab = function (tab) { if (tab.table) { - if ((tab.es_type_key === 'subscription') && !$rootScope.modules.plans) { - return true; - } else return (tab.es_type_key === 'space') && !$rootScope.modules.spaces; + return ((tab.es_type_key === 'subscription' && !$rootScope.modules.plans) || + (tab.es_type_key === 'training' && !$rootScope.modules.trainings) || + (tab.es_type_key === 'space' && !$rootScope.modules.spaces) + ); } else { return true; } @@ -292,8 +298,8 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', return refreshStats(); } else { return es.scroll({ - 'scroll': ES_SCROLL_TIME + 'm', - 'body': { scrollId: $scope.scrollId } + scroll: ES_SCROLL_TIME + 'm', + body: { scrollId: $scope.scrollId } } , function (error, response) { if (error) { @@ -335,7 +341,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', }; return $uibModal.open(options) - .result['finally'](null).then(function (info) { console.log(info); }); + .result.finally(null).then(function (info) { console.log(info); }); }; /** @@ -391,7 +397,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', if (settingsPromise.feature_tour_display !== 'manual' && $scope.currentUser.profile.tours.indexOf('statistics') < 0) { uitour.start(); } - } + }; /* PRIVATE SCOPE */ @@ -406,6 +412,15 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', return $scope.preventRefresh = true; } }); + + // set the default tab to "machines" if "subscriptions" are disabled + if (!$rootScope.modules.plans) { + const idx = $scope.statistics.findIndex(s => s.es_type_key === 'machine'); + $scope.setActiveTab($scope.statistics[idx], idx); + } else { + const idx = $scope.statistics.findIndex(s => s.es_type_key === 'subscription'); + $scope.setActiveTab($scope.statistics[idx], idx); + } }; /** @@ -471,15 +486,15 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', // run query return es.search({ - 'index': 'stats', - 'type': index, - 'size': RESULTS_PER_PAGE, - 'scroll': ES_SCROLL_TIME + 'm', + index: 'stats', + type: index, + size: RESULTS_PER_PAGE, + scroll: ES_SCROLL_TIME + 'm', 'stat-type': type, 'custom-query': custom ? JSON.stringify(Object.assign({ exclude: custom.exclude }, buildElasticCustomCriterion(custom))) : '', 'start-date': moment($scope.datePickerStart.selected).format(), 'end-date': moment($scope.datePickerEnd.selected).format(), - 'body': buildElasticDataQuery(type, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) + body: buildElasticDataQuery(type, custom, $scope.agePicker.start, $scope.agePicker.end, moment($scope.datePickerStart.selected), moment($scope.datePickerEnd.selected), $scope.sorting) } , function (error, response) { if (error) { @@ -503,19 +518,19 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', */ const buildElasticDataQuery = function (type, custom, ageMin, ageMax, intervalBegin, intervalEnd, sortings) { const q = { - 'query': { - 'bool': { - 'must': [ + query: { + bool: { + must: [ { - 'term': { - 'type': type + term: { + type: type } }, { - 'range': { - 'date': { - 'gte': intervalBegin.format(), - 'lte': intervalEnd.format() + range: { + date: { + gte: intervalBegin.format(), + lte: intervalEnd.format() } } } @@ -526,10 +541,10 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', // optional date range if ((typeof ageMin === 'number') && (typeof ageMax === 'number')) { q.query.bool.must.push({ - 'range': { - 'age': { - 'gte': ageMin, - 'lte': ageMax + range: { + age: { + gte: ageMin, + lte: ageMax } } }); @@ -539,7 +554,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', const criterion = buildElasticCustomCriterion(custom); if (custom.exclude) { q.query.bool.must_not = [ - { 'term': criterion.match } + { term: criterion.match } ]; } else { q.query.bool.must.push(criterion); @@ -547,24 +562,24 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', } if (sortings) { - q['sort'] = buildElasticSortCriteria(sortings); + q.sort = buildElasticSortCriteria(sortings); } // aggregations (avg age & CA sum) - q['aggs'] = { - 'total_ca': { - 'sum': { - 'field': 'ca' + q.aggs = { + total_ca: { + sum: { + field: 'ca' } }, - 'average_age': { - 'avg': { - 'field': 'age' + average_age: { + avg: { + field: 'age' } }, - 'total_stat': { - 'sum': { - 'field': 'stat' + total_stat: { + sum: { + field: 'stat' } } }; @@ -579,7 +594,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', const buildElasticCustomCriterion = function (custom) { if (custom) { const criterion = { - 'match': {} + match: {} }; switch ($scope.getCustomValueInputType($scope.customFilter.criterion)) { case 'input_date': criterion.match[custom.key] = moment(custom.value).format('YYYY-MM-DD'); break; @@ -602,7 +617,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', angular.forEach(criteria, function (value, key) { if ((typeof value !== 'undefined') && (value !== null) && (value !== 'none')) { const c = {}; - c[key] = { 'order': value }; + c[key] = { order: value }; return crits.push(c); } }); @@ -624,8 +639,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', // if no plans were created, there's no types for statisticIndex=subscriptions if ($scope.type.active) { - $scope.filters.splice(4, 0, { key: 'subType', label: _t('app.admin.statistics.type'), values: $scope.type.active.subtypes }) - + $scope.filters.splice(4, 0, { key: 'subType', label: _t('app.admin.statistics.type'), values: $scope.type.active.subtypes }); if (!$scope.type.active.simple) { const f = { key: 'stat', label: $scope.type.active.label, values: ['input_number'] }; @@ -670,7 +684,7 @@ Application.Controllers.controller('StatisticsController', ['$scope', '$state', ]); -Application.Controllers.controller('ExportStatisticsController', [ '$scope', '$uibModalInstance', 'Export', 'dates', 'query', 'index', 'type', 'CSRF', 'growl', '_t', +Application.Controllers.controller('ExportStatisticsController', ['$scope', '$uibModalInstance', 'Export', 'dates', 'query', 'index', 'type', 'CSRF', 'growl', '_t', function ($scope, $uibModalInstance, Export, dates, query, index, type, CSRF, growl, _t) { // Retrieve Anti-CSRF tokens from cookies CSRF.setMetaTags(); @@ -739,14 +753,14 @@ Application.Controllers.controller('ExportStatisticsController', [ '$scope', '$u if ($scope.export.type === 'global') { $scope.actionUrl = '/stats/global/export'; return $scope.query = JSON.stringify({ - 'query': { - 'bool': { - 'must': [ + query: { + bool: { + must: [ { - 'range': { - 'date': { - 'gte': moment($scope.dates.start).format(), - 'lte': moment($scope.dates.end).format() + range: { + date: { + gte: moment($scope.dates.start).format(), + lte: moment($scope.dates.end).format() } } } @@ -766,8 +780,8 @@ Application.Controllers.controller('ExportStatisticsController', [ '$scope', '$u $scope.exportData = function () { const statusQry = { category: 'statistics', type: $scope.export.type, query: $scope.query }; if ($scope.export.type !== 'global') { - statusQry['type'] = index.key; - statusQry['key'] = type.key; + statusQry.type = index.key; + statusQry.key = type.key; } Export.status(statusQry).then(function (res) { diff --git a/app/frontend/src/javascript/controllers/main_nav.js b/app/frontend/src/javascript/controllers/main_nav.js index dfce96ad5..3479d4223 100644 --- a/app/frontend/src/javascript/controllers/main_nav.js +++ b/app/frontend/src/javascript/controllers/main_nav.js @@ -35,12 +35,6 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc linkIcon: 'cogs', class: 'reserve-machine-link' }, - { - state: 'app.public.trainings_list', - linkText: 'app.public.common.trainings_registrations', - linkIcon: 'graduation-cap', - class: 'reserve-training-link' - }, { state: 'app.public.events_list', linkText: 'app.public.common.events_registrations', @@ -67,6 +61,15 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc }); } + if ($scope.$root.modules.trainings) { + $scope.navLinks.splice(4, 0, { + state: 'app.public.trainings_list', + linkText: 'app.public.common.trainings_registrations', + linkIcon: 'graduation-cap', + class: 'reserve-training-link' + }); + } + if ($scope.$root.modules.spaces) { $scope.navLinks.splice(4, 0, { state: 'app.public.spaces_list', @@ -90,12 +93,6 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc linkIcon: 'cogs', authorizedRoles: ['admin', 'manager'] }, - { - state: 'app.admin.trainings', - linkText: 'app.public.common.trainings_monitoring', - linkIcon: 'graduation-cap', - authorizedRoles: ['admin', 'manager'] - }, { state: 'app.admin.events', linkText: 'app.public.common.manage_the_events', @@ -147,6 +144,15 @@ Application.Controllers.controller('MainNavController', ['$scope', function ($sc $scope.adminNavLinks = adminNavLinks; + if ($scope.$root.modules.trainings) { + $scope.adminNavLinks.splice(3, 0, { + state: 'app.admin.trainings', + linkText: 'app.public.common.trainings_monitoring', + linkIcon: 'graduation-cap', + authorizedRoles: ['admin', 'manager'] + }); + } + if ($scope.$root.modules.spaces) { $scope.adminNavLinks.splice(3, 0, { state: 'app.public.spaces_list', diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js index c6dfb5702..5b2b67dbd 100644 --- a/app/frontend/src/javascript/router.js +++ b/app/frontend/src/javascript/router.js @@ -37,7 +37,7 @@ angular.module('application.router', ['ui.router']) logoFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-file' }).$promise; }], logoBlackFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-black-file' }).$promise; }], sharedTranslations: ['Translations', function (Translations) { return Translations.query(['app.shared', 'app.public.common']).$promise; }], - modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['spaces_module', 'plans_module', 'invoicing_module', 'wallet_module', 'statistics_module']" }).$promise; }] + modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['spaces_module', 'plans_module', 'invoicing_module', 'wallet_module', 'statistics_module', 'trainings_module']" }).$promise; }] }, onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, CSRF) { // Retrieve Anti-CSRF tokens from cookies @@ -48,6 +48,7 @@ angular.module('application.router', ['ui.router']) $rootScope.modules = { spaces: (modulesPromise.spaces_module === 'true'), plans: (modulesPromise.plans_module === 'true'), + trainings: (modulesPromise.trainings_module === 'true'), invoicing: (modulesPromise.invoicing_module === 'true'), wallet: (modulesPromise.wallet_module === 'true'), statistics: (modulesPromise.statistics_module === 'true') @@ -458,6 +459,7 @@ angular.module('application.router', ['ui.router']) // trainings .state('app.public.trainings_list', { url: '/trainings', + abstract: !Fablab.trainingsModule, views: { 'main@': { templateUrl: '/trainings/index.html', @@ -470,6 +472,7 @@ angular.module('application.router', ['ui.router']) }) .state('app.public.training_show', { url: '/trainings/:id', + abstract: !Fablab.trainingsModule, views: { 'main@': { templateUrl: '/trainings/show.html', @@ -482,6 +485,7 @@ angular.module('application.router', ['ui.router']) }) .state('app.logged.trainings_reserve', { url: '/trainings/:id/reserve', + abstract: !Fablab.trainingsModule, views: { 'main@': { templateUrl: '/trainings/reserve.html', @@ -652,6 +656,7 @@ angular.module('application.router', ['ui.router']) // trainings .state('app.admin.trainings', { url: '/admin/trainings', + abstract: !Fablab.trainingsModule, views: { 'main@': { templateUrl: '/admin/trainings/index.html', @@ -666,6 +671,7 @@ angular.module('application.router', ['ui.router']) }) .state('app.admin.trainings_new', { url: '/admin/trainings/new', + abstract: !Fablab.trainingsModule, views: { 'main@': { templateUrl: '/admin/trainings/new.html', @@ -678,6 +684,7 @@ angular.module('application.router', ['ui.router']) }) .state('app.admin.trainings_edit', { url: '/admin/trainings/:id/edit', + abstract: !Fablab.trainingsModule, views: { 'main@': { templateUrl: '/admin/trainings/edit.html', @@ -1054,7 +1061,7 @@ angular.module('application.router', ['ui.router']) "'booking_move_enable', 'booking_move_delay', 'booking_cancel_enable', 'feature_tour_display', " + "'booking_cancel_delay', 'main_color', 'secondary_color', 'spaces_module', 'twitter_analytics', " + "'fablab_name', 'name_genre', 'reminder_enable', 'plans_module', 'confirmation_required', " + - "'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', " + + "'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', 'trainings_module', " + "'display_name_enable', 'machines_sort_by', 'fab_analytics', 'statistics_module', " + "'link_name', 'home_content', 'home_css', 'phone_required', 'upcoming_events_shown']" }).$promise; diff --git a/app/frontend/templates/admin/calendar/eventModal.html b/app/frontend/templates/admin/calendar/eventModal.html index a5bfc133b..8ed8f6184 100644 --- a/app/frontend/templates/admin/calendar/eventModal.html +++ b/app/frontend/templates/admin/calendar/eventModal.html @@ -6,7 +6,7 @@