summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/backend
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website/static/src/js/backend
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website/static/src/js/backend')
-rw-r--r--addons/website/static/src/js/backend/button.js116
-rw-r--r--addons/website/static/src/js/backend/dashboard.js721
-rw-r--r--addons/website/static/src/js/backend/res_config_settings.js81
3 files changed, 918 insertions, 0 deletions
diff --git a/addons/website/static/src/js/backend/button.js b/addons/website/static/src/js/backend/button.js
new file mode 100644
index 00000000..bb3c5bd1
--- /dev/null
+++ b/addons/website/static/src/js/backend/button.js
@@ -0,0 +1,116 @@
+odoo.define('website.backend.button', function (require) {
+'use strict';
+
+var AbstractField = require('web.AbstractField');
+var core = require('web.core');
+var field_registry = require('web.field_registry');
+
+var _t = core._t;
+
+var WebsitePublishButton = AbstractField.extend({
+ className: 'o_stat_info',
+ supportedFieldTypes: ['boolean'],
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * A boolean field is always set since false is a valid value.
+ *
+ * @override
+ */
+ isSet: function () {
+ return true;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * This widget is supposed to be used inside a stat button and, as such, is
+ * rendered the same way in edit and readonly mode.
+ *
+ * @override
+ * @private
+ */
+ _render: function () {
+ this.$el.empty();
+ var text = this.value ? _t("Published") : _t("Unpublished");
+ var hover = this.value ? _t("Unpublish") : _t("Publish");
+ var valColor = this.value ? 'text-success' : 'text-danger';
+ var hoverColor = this.value ? 'text-danger' : 'text-success';
+ var $val = $('<span>').addClass('o_stat_text o_not_hover ' + valColor).text(text);
+ var $hover = $('<span>').addClass('o_stat_text o_hover ' + hoverColor).text(hover);
+ this.$el.append($val).append($hover);
+ },
+});
+
+var WidgetWebsiteButtonIcon = AbstractField.extend({
+ template: 'WidgetWebsiteButtonIcon',
+ events: {
+ 'click': '_onClick',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ this.$icon = this.$('.o_button_icon');
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ isSet: function () {
+ return true;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _render: function () {
+ this._super.apply(this, arguments);
+
+ var published = this.value;
+ var info = published ? _t("Published") : _t("Unpublished");
+ this.$el.attr('aria-label', info)
+ .prop('title', info);
+ this.$icon.toggleClass('text-danger', !published)
+ .toggleClass('text-success', published);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handler
+ //--------------------------------------------------------------------------
+
+ /**
+ * Redirects to the website page of the record.
+ *
+ * @private
+ */
+ _onClick: function () {
+ this.trigger_up('button_clicked', {
+ attrs: {
+ type: 'object',
+ name: 'open_website_url',
+ },
+ record: this.record,
+ });
+ },
+});
+
+field_registry
+ .add('website_redirect_button', WidgetWebsiteButtonIcon)
+ .add('website_publish_button', WebsitePublishButton);
+});
diff --git a/addons/website/static/src/js/backend/dashboard.js b/addons/website/static/src/js/backend/dashboard.js
new file mode 100644
index 00000000..b7050675
--- /dev/null
+++ b/addons/website/static/src/js/backend/dashboard.js
@@ -0,0 +1,721 @@
+odoo.define('website.backend.dashboard', function (require) {
+'use strict';
+
+var AbstractAction = require('web.AbstractAction');
+var ajax = require('web.ajax');
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var field_utils = require('web.field_utils');
+var pyUtils = require('web.py_utils');
+var session = require('web.session');
+var time = require('web.time');
+var web_client = require('web.web_client');
+
+var _t = core._t;
+var QWeb = core.qweb;
+
+var COLORS = ["#1f77b4", "#aec7e8"];
+var FORMAT_OPTIONS = {
+ // allow to decide if utils.human_number should be used
+ humanReadable: function (value) {
+ return Math.abs(value) >= 1000;
+ },
+ // with the choices below, 1236 is represented by 1.24k
+ minDigits: 1,
+ decimals: 2,
+ // avoid comma separators for thousands in numbers when human_number is used
+ formatterCallback: function (str) {
+ return str;
+ },
+};
+
+var Dashboard = AbstractAction.extend({
+ hasControlPanel: true,
+ contentTemplate: 'website.WebsiteDashboardMain',
+ jsLibs: [
+ '/web/static/lib/Chart/Chart.js',
+ ],
+ events: {
+ 'click .js_link_analytics_settings': 'on_link_analytics_settings',
+ 'click .o_dashboard_action': 'on_dashboard_action',
+ 'click .o_dashboard_action_form': 'on_dashboard_action_form',
+ },
+
+ init: function(parent, context) {
+ this._super(parent, context);
+
+ this.DATE_FORMAT = time.getLangDateFormat();
+ this.date_range = 'week'; // possible values : 'week', 'month', year'
+ this.date_from = moment.utc().subtract(1, 'week');
+ this.date_to = moment.utc();
+
+ this.dashboards_templates = ['website.dashboard_header', 'website.dashboard_content'];
+ this.graphs = [];
+ this.chartIds = {};
+ },
+
+ willStart: function() {
+ var self = this;
+ return Promise.all([ajax.loadLibs(this), this._super()]).then(function() {
+ return self.fetch_data();
+ }).then(function(){
+ var website = _.findWhere(self.websites, {selected: true});
+ self.website_id = website ? website.id : false;
+ });
+ },
+
+ start: function() {
+ var self = this;
+ this._computeControlPanelProps();
+ return this._super().then(function() {
+ self.render_graphs();
+ });
+ },
+
+ on_attach_callback: function () {
+ this._isInDom = true;
+ this.render_graphs();
+ this._super.apply(this, arguments);
+ },
+ on_detach_callback: function () {
+ this._isInDom = false;
+ this._super.apply(this, arguments);
+ },
+ /**
+ * Fetches dashboard data
+ */
+ fetch_data: function() {
+ var self = this;
+ var prom = this._rpc({
+ route: '/website/fetch_dashboard_data',
+ params: {
+ website_id: this.website_id || false,
+ date_from: this.date_from.year()+'-'+(this.date_from.month()+1)+'-'+this.date_from.date(),
+ date_to: this.date_to.year()+'-'+(this.date_to.month()+1)+'-'+this.date_to.date(),
+ },
+ });
+ prom.then(function (result) {
+ self.data = result;
+ self.dashboards_data = result.dashboards;
+ self.currency_id = result.currency_id;
+ self.groups = result.groups;
+ self.websites = result.websites;
+ });
+ return prom;
+ },
+
+ on_link_analytics_settings: function(ev) {
+ ev.preventDefault();
+
+ var self = this;
+ var dialog = new Dialog(this, {
+ size: 'medium',
+ title: _t('Connect Google Analytics'),
+ $content: QWeb.render('website.ga_dialog_content', {
+ ga_key: this.dashboards_data.visits.ga_client_id,
+ ga_analytics_key: this.dashboards_data.visits.ga_analytics_key,
+ }),
+ buttons: [
+ {
+ text: _t("Save"),
+ classes: 'btn-primary',
+ close: true,
+ click: function() {
+ var ga_client_id = dialog.$el.find('input[name="ga_client_id"]').val();
+ var ga_analytics_key = dialog.$el.find('input[name="ga_analytics_key"]').val();
+ self.on_save_ga_client_id(ga_client_id, ga_analytics_key);
+ },
+ },
+ {
+ text: _t("Cancel"),
+ close: true,
+ },
+ ],
+ }).open();
+ },
+
+ on_go_to_website: function (ev) {
+ ev.preventDefault();
+ var website = _.findWhere(this.websites, {selected: true});
+ window.location.href = `/website/force/${website.id}`;
+ },
+
+ on_save_ga_client_id: function(ga_client_id, ga_analytics_key) {
+ var self = this;
+ return this._rpc({
+ route: '/website/dashboard/set_ga_data',
+ params: {
+ 'website_id': self.website_id,
+ 'ga_client_id': ga_client_id,
+ 'ga_analytics_key': ga_analytics_key,
+ },
+ }).then(function (result) {
+ if (result.error) {
+ self.do_warn(result.error.title, result.error.message);
+ return;
+ }
+ self.on_date_range_button('week');
+ });
+ },
+
+ render_dashboards: function() {
+ var self = this;
+ _.each(this.dashboards_templates, function(template) {
+ self.$('.o_website_dashboard').append(QWeb.render(template, {widget: self}));
+ });
+ },
+
+ render_graph: function(div_to_display, chart_values, chart_id) {
+ var self = this;
+
+ this.$(div_to_display).empty();
+ var $canvasContainer = $('<div/>', {class: 'o_graph_canvas_container'});
+ this.$canvas = $('<canvas/>').attr('id', chart_id);
+ $canvasContainer.append(this.$canvas);
+ this.$(div_to_display).append($canvasContainer);
+
+ var labels = chart_values[0].values.map(function (date) {
+ return moment(date[0], "YYYY-MM-DD", 'en');
+ });
+
+ var datasets = chart_values.map(function (group, index) {
+ return {
+ label: group.key,
+ data: group.values.map(function (value) {
+ return value[1];
+ }),
+ dates: group.values.map(function (value) {
+ return value[0];
+ }),
+ fill: false,
+ borderColor: COLORS[index],
+ };
+ });
+
+ var ctx = this.$canvas[0];
+ this.chart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: labels,
+ datasets: datasets,
+ },
+ options: {
+ legend: {
+ display: false,
+ },
+ maintainAspectRatio: false,
+ scales: {
+ yAxes: [{
+ type: 'linear',
+ ticks: {
+ beginAtZero: true,
+ callback: this.formatValue.bind(this),
+ },
+ }],
+ xAxes: [{
+ ticks: {
+ callback: function (moment) {
+ return moment.format(self.DATE_FORMAT);
+ },
+ }
+ }],
+ },
+ tooltips: {
+ mode: 'index',
+ intersect: false,
+ bodyFontColor: 'rgba(0,0,0,1)',
+ titleFontSize: 13,
+ titleFontColor: 'rgba(0,0,0,1)',
+ backgroundColor: 'rgba(255,255,255,0.6)',
+ borderColor: 'rgba(0,0,0,0.2)',
+ borderWidth: 2,
+ callbacks: {
+ title: function (tooltipItems, data) {
+ return data.datasets[0].label;
+ },
+ label: function (tooltipItem, data) {
+ var moment = data.labels[tooltipItem.index];
+ var date = tooltipItem.datasetIndex === 0 ?
+ moment :
+ moment.subtract(1, self.date_range);
+ return date.format(self.DATE_FORMAT) + ': ' + self.formatValue(tooltipItem.yLabel);
+ },
+ labelColor: function (tooltipItem, chart) {
+ var dataset = chart.data.datasets[tooltipItem.datasetIndex];
+ return {
+ borderColor: dataset.borderColor,
+ backgroundColor: dataset.borderColor,
+ };
+ },
+ }
+ }
+ }
+ });
+ },
+
+ render_graphs: function() {
+ var self = this;
+ if (this._isInDom) {
+ _.each(this.graphs, function(e) {
+ var renderGraph = self.groups[e.group] &&
+ self.dashboards_data[e.name].summary.order_count;
+ if (!self.chartIds[e.name]) {
+ self.chartIds[e.name] = _.uniqueId('chart_' + e.name);
+ }
+ var chart_id = self.chartIds[e.name];
+ if (renderGraph) {
+ self.render_graph('.o_graph_' + e.name, self.dashboards_data[e.name].graph, chart_id);
+ }
+ });
+ this.render_graph_analytics(this.dashboards_data.visits.ga_client_id);
+ }
+ },
+
+ render_graph_analytics: function(client_id) {
+ if (!this.dashboards_data.visits || !this.dashboards_data.visits.ga_client_id) {
+ return;
+ }
+
+ this.load_analytics_api();
+
+ var $analytics_components = this.$('.js_analytics_components');
+ this.addLoader($analytics_components);
+
+ var self = this;
+ gapi.analytics.ready(function() {
+
+ $analytics_components.empty();
+ // 1. Authorize component
+ var $analytics_auth = $('<div>').addClass('col-lg-12');
+ window.onOriginError = function () {
+ $analytics_components.find('.js_unauthorized_message').remove();
+ self.display_unauthorized_message($analytics_components, 'not_initialized');
+ };
+ gapi.analytics.auth.authorize({
+ container: $analytics_auth[0],
+ clientid: client_id
+ });
+
+ $analytics_auth.appendTo($analytics_components);
+
+ self.handle_analytics_auth($analytics_components);
+ gapi.analytics.auth.on('signIn', function() {
+ delete window.onOriginError;
+ self.handle_analytics_auth($analytics_components);
+ });
+
+ });
+ },
+
+ on_date_range_button: function(date_range) {
+ if (date_range === 'week') {
+ this.date_range = 'week';
+ this.date_from = moment.utc().subtract(1, 'weeks');
+ } else if (date_range === 'month') {
+ this.date_range = 'month';
+ this.date_from = moment.utc().subtract(1, 'months');
+ } else if (date_range === 'year') {
+ this.date_range = 'year';
+ this.date_from = moment.utc().subtract(1, 'years');
+ } else {
+ console.log('Unknown date range. Choose between [week, month, year]');
+ return;
+ }
+
+ var self = this;
+ Promise.resolve(this.fetch_data()).then(function () {
+ self.$('.o_website_dashboard').empty();
+ self.render_dashboards();
+ self.render_graphs();
+ });
+
+ },
+
+ on_website_button: function(website_id) {
+ var self = this;
+ this.website_id = website_id;
+ Promise.resolve(this.fetch_data()).then(function () {
+ self.$('.o_website_dashboard').empty();
+ self.render_dashboards();
+ self.render_graphs();
+ });
+ },
+
+ on_reverse_breadcrumb: function() {
+ var self = this;
+ web_client.do_push_state({});
+ this.fetch_data().then(function() {
+ self.$('.o_website_dashboard').empty();
+ self.render_dashboards();
+ self.render_graphs();
+ });
+ },
+
+ on_dashboard_action: function (ev) {
+ ev.preventDefault();
+ var self = this
+ var $action = $(ev.currentTarget);
+ var additional_context = {};
+ if (this.date_range === 'week') {
+ additional_context = {search_default_week: true};
+ } else if (this.date_range === 'month') {
+ additional_context = {search_default_month: true};
+ } else if (this.date_range === 'year') {
+ additional_context = {search_default_year: true};
+ }
+ this._rpc({
+ route: '/web/action/load',
+ params: {
+ 'action_id': $action.attr('name'),
+ },
+ })
+ .then(function (action) {
+ action.domain = pyUtils.assembleDomains([action.domain, `[('website_id', '=', ${self.website_id})]`]);
+ return self.do_action(action, {
+ 'additional_context': additional_context,
+ 'on_reverse_breadcrumb': self.on_reverse_breadcrumb
+ });
+ });
+ },
+
+ on_dashboard_action_form: function (ev) {
+ ev.preventDefault();
+ var $action = $(ev.currentTarget);
+ this.do_action({
+ name: $action.attr('name'),
+ res_model: $action.data('res_model'),
+ res_id: $action.data('res_id'),
+ views: [[false, 'form']],
+ type: 'ir.actions.act_window',
+ }, {
+ on_reverse_breadcrumb: this.on_reverse_breadcrumb
+ });
+ },
+
+ /**
+ * @private
+ */
+ _computeControlPanelProps() {
+ const $searchview = $(QWeb.render("website.DateRangeButtons", {
+ widget: this,
+ }));
+ $searchview.find('button.js_date_range').click((ev) => {
+ $searchview.find('button.js_date_range.active').removeClass('active');
+ $(ev.target).addClass('active');
+ this.on_date_range_button($(ev.target).data('date'));
+ });
+ $searchview.find('button.js_website').click((ev) => {
+ $searchview.find('button.js_website.active').removeClass('active');
+ $(ev.target).addClass('active');
+ this.on_website_button($(ev.target).data('website-id'));
+ });
+
+ const $buttons = $(QWeb.render("website.GoToButtons"));
+ $buttons.on('click', this.on_go_to_website.bind(this));
+
+ this.controlPanelProps.cp_content = { $searchview, $buttons };
+ },
+
+ // Loads Analytics API
+ load_analytics_api: function() {
+ var self = this;
+ if (!("gapi" in window)) {
+ (function(w,d,s,g,js,fjs){
+ g=w.gapi||(w.gapi={});g.analytics={q:[],ready:function(cb){this.q.push(cb);}};
+ js=d.createElement(s);fjs=d.getElementsByTagName(s)[0];
+ js.src='https://apis.google.com/js/platform.js';
+ fjs.parentNode.insertBefore(js,fjs);js.onload=function(){g.load('analytics');};
+ }(window,document,'script'));
+ gapi.analytics.ready(function() {
+ self.analytics_create_components();
+ });
+ }
+ },
+
+ handle_analytics_auth: function($analytics_components) {
+ $analytics_components.find('.js_unauthorized_message').remove();
+
+ // Check if the user is authenticated and has the right to make API calls
+ if (!gapi.analytics.auth.getAuthResponse()) {
+ this.display_unauthorized_message($analytics_components, 'not_connected');
+ } else if (gapi.analytics.auth.getAuthResponse() && gapi.analytics.auth.getAuthResponse().scope.indexOf('https://www.googleapis.com/auth/analytics') === -1) {
+ this.display_unauthorized_message($analytics_components, 'no_right');
+ } else {
+ this.make_analytics_calls($analytics_components);
+ }
+ },
+
+ display_unauthorized_message: function($analytics_components, reason) {
+ $analytics_components.prepend($(QWeb.render('website.unauthorized_analytics', {reason: reason})));
+ },
+
+ make_analytics_calls: function($analytics_components) {
+ // 2. ActiveUsers component
+ var $analytics_users = $('<div>');
+ var activeUsers = new gapi.analytics.ext.ActiveUsers({
+ container: $analytics_users[0],
+ pollingInterval: 10,
+ });
+ $analytics_users.appendTo($analytics_components);
+
+ // 3. View Selector
+ var $analytics_view_selector = $('<div>').addClass('col-lg-12 o_properties_selection');
+ var viewSelector = new gapi.analytics.ViewSelector({
+ container: $analytics_view_selector[0],
+ });
+ viewSelector.execute();
+ $analytics_view_selector.appendTo($analytics_components);
+
+ // 4. Chart graph
+ var start_date = '7daysAgo';
+ if (this.date_range === 'month') {
+ start_date = '30daysAgo';
+ } else if (this.date_range === 'year') {
+ start_date = '365daysAgo';
+ }
+ var $analytics_chart_2 = $('<div>').addClass('col-lg-6 col-12');
+ var breakdownChart = new gapi.analytics.googleCharts.DataChart({
+ query: {
+ 'dimensions': 'ga:date',
+ 'metrics': 'ga:sessions',
+ 'start-date': start_date,
+ 'end-date': 'yesterday'
+ },
+ chart: {
+ type: 'LINE',
+ container: $analytics_chart_2[0],
+ options: {
+ title: 'All',
+ width: '100%',
+ tooltip: {isHtml: true},
+ }
+ }
+ });
+ $analytics_chart_2.appendTo($analytics_components);
+
+ // 5. Chart table
+ var $analytics_chart_1 = $('<div>').addClass('col-lg-6 col-12');
+ var mainChart = new gapi.analytics.googleCharts.DataChart({
+ query: {
+ 'dimensions': 'ga:medium',
+ 'metrics': 'ga:sessions',
+ 'sort': '-ga:sessions',
+ 'max-results': '6'
+ },
+ chart: {
+ type: 'TABLE',
+ container: $analytics_chart_1[0],
+ options: {
+ width: '100%'
+ }
+ }
+ });
+ $analytics_chart_1.appendTo($analytics_components);
+
+ // Events handling & animations
+
+ var table_row_listener;
+
+ viewSelector.on('change', function(ids) {
+ var options = {query: {ids: ids}};
+ activeUsers.set({ids: ids}).execute();
+ mainChart.set(options).execute();
+ breakdownChart.set(options).execute();
+
+ if (table_row_listener) { google.visualization.events.removeListener(table_row_listener); }
+ });
+
+ mainChart.on('success', function(response) {
+ var chart = response.chart;
+ var dataTable = response.dataTable;
+
+ table_row_listener = google.visualization.events.addListener(chart, 'select', function() {
+ var options;
+ if (chart.getSelection().length) {
+ var row = chart.getSelection()[0].row;
+ var medium = dataTable.getValue(row, 0);
+ options = {
+ query: {
+ filters: 'ga:medium==' + medium,
+ },
+ chart: {
+ options: {
+ title: medium,
+ }
+ }
+ };
+ } else {
+ options = {
+ chart: {
+ options: {
+ title: 'All',
+ }
+ }
+ };
+ delete breakdownChart.get().query.filters;
+ }
+ breakdownChart.set(options).execute();
+ });
+ });
+
+ // Add CSS animation to visually show the when users come and go.
+ activeUsers.once('success', function() {
+ var element = this.container.firstChild;
+ var timeout;
+
+ this.on('change', function(data) {
+ element = this.container.firstChild;
+ var animationClass = data.delta > 0 ? 'is-increasing' : 'is-decreasing';
+ element.className += (' ' + animationClass);
+
+ clearTimeout(timeout);
+ timeout = setTimeout(function() {
+ element.className = element.className.replace(/ is-(increasing|decreasing)/g, '');
+ }, 3000);
+ });
+ });
+ },
+
+ /*
+ * Credits to https://github.com/googleanalytics/ga-dev-tools
+ * This is the Active Users component that polls
+ * the number of active users on Analytics each 5 secs
+ */
+ analytics_create_components: function() {
+
+ gapi.analytics.createComponent('ActiveUsers', {
+
+ initialize: function() {
+ this.activeUsers = 0;
+ gapi.analytics.auth.once('signOut', this.handleSignOut_.bind(this));
+ },
+
+ execute: function() {
+ // Stop any polling currently going on.
+ if (this.polling_) {
+ this.stop();
+ }
+
+ this.render_();
+
+ // Wait until the user is authorized.
+ if (gapi.analytics.auth.isAuthorized()) {
+ this.pollActiveUsers_();
+ } else {
+ gapi.analytics.auth.once('signIn', this.pollActiveUsers_.bind(this));
+ }
+ },
+
+ stop: function() {
+ clearTimeout(this.timeout_);
+ this.polling_ = false;
+ this.emit('stop', {activeUsers: this.activeUsers});
+ },
+
+ render_: function() {
+ var opts = this.get();
+
+ // Render the component inside the container.
+ this.container = typeof opts.container === 'string' ?
+ document.getElementById(opts.container) : opts.container;
+
+ this.container.innerHTML = opts.template || this.template;
+ this.container.querySelector('b').innerHTML = this.activeUsers;
+ },
+
+ pollActiveUsers_: function() {
+ var options = this.get();
+ var pollingInterval = (options.pollingInterval || 5) * 1000;
+
+ if (isNaN(pollingInterval) || pollingInterval < 5000) {
+ throw new Error('Frequency must be 5 seconds or more.');
+ }
+
+ this.polling_ = true;
+ gapi.client.analytics.data.realtime
+ .get({ids:options.ids, metrics:'rt:activeUsers'})
+ .then(function(response) {
+ var result = response.result;
+ var newValue = result.totalResults ? +result.rows[0][0] : 0;
+ var oldValue = this.activeUsers;
+
+ this.emit('success', {activeUsers: this.activeUsers});
+
+ if (newValue !== oldValue) {
+ this.activeUsers = newValue;
+ this.onChange_(newValue - oldValue);
+ }
+
+ if (this.polling_) {
+ this.timeout_ = setTimeout(this.pollActiveUsers_.bind(this), pollingInterval);
+ }
+ }.bind(this));
+ },
+
+ onChange_: function(delta) {
+ var valueContainer = this.container.querySelector('b');
+ if (valueContainer) { valueContainer.innerHTML = this.activeUsers; }
+
+ this.emit('change', {activeUsers: this.activeUsers, delta: delta});
+ if (delta > 0) {
+ this.emit('increase', {activeUsers: this.activeUsers, delta: delta});
+ } else {
+ this.emit('decrease', {activeUsers: this.activeUsers, delta: delta});
+ }
+ },
+
+ handleSignOut_: function() {
+ this.stop();
+ gapi.analytics.auth.once('signIn', this.handleSignIn_.bind(this));
+ },
+
+ handleSignIn_: function() {
+ this.pollActiveUsers_();
+ gapi.analytics.auth.once('signOut', this.handleSignOut_.bind(this));
+ },
+
+ template:
+ '<div class="ActiveUsers">' +
+ 'Active Users: <b class="ActiveUsers-value"></b>' +
+ '</div>'
+
+ });
+ },
+
+ // Utility functions
+ addLoader: function(selector) {
+ var loader = '<span class="fa fa-3x fa-spin fa-spinner fa-pulse"/>';
+ selector.html("<div class='o_loader'>" + loader + "</div>");
+ },
+ getValue: function(d) { return d[1]; },
+ format_number: function(value, type, digits, symbol) {
+ if (type === 'currency') {
+ return this.render_monetary_field(value, this.currency_id);
+ } else {
+ return field_utils.format[type](value || 0, {digits: digits}) + ' ' + symbol;
+ }
+ },
+ formatValue: function (value) {
+ var formatter = field_utils.format.float;
+ var formatedValue = formatter(value, undefined, FORMAT_OPTIONS);
+ return formatedValue;
+ },
+ render_monetary_field: function(value, currency_id) {
+ var currency = session.get_currency(currency_id);
+ var formatted_value = field_utils.format.float(value || 0, {digits: currency && currency.digits});
+ if (currency) {
+ if (currency.position === "after") {
+ formatted_value += currency.symbol;
+ } else {
+ formatted_value = currency.symbol + formatted_value;
+ }
+ }
+ return formatted_value;
+ },
+
+});
+
+core.action_registry.add('backend_dashboard', Dashboard);
+
+return Dashboard;
+});
diff --git a/addons/website/static/src/js/backend/res_config_settings.js b/addons/website/static/src/js/backend/res_config_settings.js
new file mode 100644
index 00000000..6dd014c0
--- /dev/null
+++ b/addons/website/static/src/js/backend/res_config_settings.js
@@ -0,0 +1,81 @@
+odoo.define('website.settings', function (require) {
+
+const BaseSettingController = require('base.settings').Controller;
+const core = require('web.core');
+const Dialog = require('web.Dialog');
+const FieldBoolean = require('web.basic_fields').FieldBoolean;
+const fieldRegistry = require('web.field_registry');
+const FormController = require('web.FormController');
+
+const QWeb = core.qweb;
+const _t = core._t;
+
+BaseSettingController.include({
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Bypasses the discard confirmation dialog when going to a website because
+ * the target website will be the one selected and when selecting a theme
+ * because the theme will be installed on the selected website.
+ *
+ * Without this override, it is impossible to go to a website other than the
+ * first because discarding will revert it back to the default value.
+ *
+ * Without this override, it is impossible to edit robots.txt website other than the
+ * first because discarding will revert it back to the default value.
+ *
+ * Without this override, it is impossible to submit sitemap to google other than for the
+ * first website because discarding will revert it back to the default value.
+ *
+ * Without this override, it is impossible to install a theme on a website
+ * other than the first because discarding will revert it back to the
+ * default value.
+ *
+ * @override
+ */
+ _onButtonClicked: function (ev) {
+ if (ev.data.attrs.name === 'website_go_to'
+ || ev.data.attrs.name === 'action_open_robots'
+ || ev.data.attrs.name === 'action_ping_sitemap'
+ || ev.data.attrs.name === 'install_theme_on_current_website') {
+ FormController.prototype._onButtonClicked.apply(this, arguments);
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+});
+
+const WebsiteCookiesbarField = FieldBoolean.extend({
+ xmlDependencies: ['/website/static/src/xml/website.res_config_settings.xml'],
+
+ _onChange: function () {
+ const checked = this.$input[0].checked;
+ if (!checked) {
+ return this._setValue(checked);
+ }
+
+ const cancelCallback = () => this.$input[0].checked = !checked;
+ Dialog.confirm(this, null, {
+ title: _t("Please confirm"),
+ $content: QWeb.render('website.res_config_settings.cookies_modal_main'),
+ buttons: [{
+ text: _t('Do not activate'),
+ classes: 'btn-primary',
+ close: true,
+ click: cancelCallback,
+ },
+ {
+ text: _t('Activate anyway'),
+ close: true,
+ click: () => this._setValue(checked),
+ }],
+ cancel_callback: cancelCallback,
+ });
+ },
+});
+
+fieldRegistry.add('website_cookiesbar_field', WebsiteCookiesbarField);
+});