diff --git a/addons/web_settings_dashboard/__init__.py b/addons/web_settings_dashboard/__init__.py new file mode 100644 index 00000000000..bde83af3aea --- /dev/null +++ b/addons/web_settings_dashboard/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +import controllers +import models diff --git a/addons/web_settings_dashboard/__openerp__.py b/addons/web_settings_dashboard/__openerp__.py new file mode 100644 index 00000000000..7de71e122e7 --- /dev/null +++ b/addons/web_settings_dashboard/__openerp__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Odoo Settings Dashboard', + 'version': '1.0', + 'summary': 'Quick actions for installing new app, adding users, completing planners, etc.', + 'category': 'Tools', + 'description': + """ +Odoo dashboard +============== +* Quick access to install apps +* Quick users add +* Access all planners at one place +* Quick access to the `App Store` and `Theme Store` + + """, + 'data': [ + 'views/dashboard_views.xml', + 'views/dashboard_templates.xml', + ], + 'depends': ['web_planner'], + 'qweb': ['static/src/xml/dashboard.xml'], + 'auto_install': True, +} diff --git a/addons/web_settings_dashboard/controllers/__init__.py b/addons/web_settings_dashboard/controllers/__init__.py new file mode 100644 index 00000000000..a84d81a791b --- /dev/null +++ b/addons/web_settings_dashboard/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import main diff --git a/addons/web_settings_dashboard/controllers/main.py b/addons/web_settings_dashboard/controllers/main.py new file mode 100644 index 00000000000..35db119ae14 --- /dev/null +++ b/addons/web_settings_dashboard/controllers/main.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from openerp import http +from openerp.http import request + + +class WebSettingsDashboard(http.Controller): + + @http.route('/web_settings_dashboard/data', type='json', auth='user') + def web_settings_dashboard_data(self, **kw): + + installed_apps = request.env['ir.module.module'].search_count([ + ('application', '=', True), + ('state', 'in', ['installed', 'to upgrade', 'to remove']) + ]) + active_users = request.env['res.users'].search_count([('active', '=', True), ('login_date', '!=', False)]) + pending_users = request.env['res.users'].search([('login_date', '=', False)], order="create_date desc") + + return { + 'apps': { + 'installed_apps': installed_apps + }, + 'users_info': { + 'active_users': active_users, + 'pending_users': zip(pending_users.mapped('id'), pending_users.mapped('login')), + 'user_form_view_id': request.env['ir.model.data'].xmlid_to_res_id("base.view_users_form"), + }, + } diff --git a/addons/web_settings_dashboard/models/__init__.py b/addons/web_settings_dashboard/models/__init__.py new file mode 100644 index 00000000000..b0b7a73232a --- /dev/null +++ b/addons/web_settings_dashboard/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import res_users diff --git a/addons/web_settings_dashboard/models/res_users.py b/addons/web_settings_dashboard/models/res_users.py new file mode 100644 index 00000000000..f42aceba94c --- /dev/null +++ b/addons/web_settings_dashboard/models/res_users.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from datetime import datetime, timedelta + +from openerp import models, api, _ +from openerp.exceptions import RedirectWarning +from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT + + +class Users(models.Model): + _inherit = 'res.users' + + @api.model + def web_dashboard_create_users(self, emails): + + # Reactivate already existing users if needed + deactivated_users = self.with_context(active_test=False).search([('active', '=', False), '|', ('login', 'in', emails), ('email', 'in', emails)]) + for user in deactivated_users: + user.active = True + + new_emails = set(emails) - set(deactivated_users.mapped('email')) + + # Process new email addresses : create new users + for email in new_emails: + default_values = {'login': email, 'name': email.split('@')[0], 'email': email, 'active': True} + user = self.create(default_values) + + return True diff --git a/addons/web_settings_dashboard/static/src/js/dashboard.js b/addons/web_settings_dashboard/static/src/js/dashboard.js new file mode 100644 index 00000000000..48f98c52417 --- /dev/null +++ b/addons/web_settings_dashboard/static/src/js/dashboard.js @@ -0,0 +1,266 @@ +odoo.define('web_settings_dashboard', function (require) { +"use strict"; + +var core = require('web.core'); +var Widget = require('web.Widget'); +var Model = require('web.Model'); +var session = require('web.session'); +var PlannerCommon = require('web.planner.common'); +var framework = require('web.framework'); +var webclient = require('web.web_client'); +var PlannerDialog = PlannerCommon.PlannerDialog; + +var QWeb = core.qweb; +var _t = core._t; + +var Dashboard = Widget.extend({ + template: 'DashboardMain', + + events: { + 'click .o_browse_apps': 'on_new_apps', + }, + + start: function(){ + return this.load(['apps', 'invitations', 'planner', 'share']) + }, + + load: function(dashboards){ + var self = this; + var loading_done = new $.Deferred(); + session.rpc("/web_settings_dashboard/data", {}).then(function (data) { + // Load each dashboard + var all_dashboards_defs = [] + _.each(dashboards, function(dashboard) { + var dashboard_def = self['load_' + dashboard](data); + if (dashboard_def) { + all_dashboards_defs.push(dashboard_def); + } + }); + + // Resolve loading_done when all dashboards defs are resolved + $.when.apply($, all_dashboards_defs).then(function() { + loading_done.resolve(); + }); + }); + return loading_done; + }, + + load_apps: function(data){ + this.$('.o_web_settings_dashboard_apps').append(QWeb.render("DashboardApps", data['apps'])); + }, + + load_share: function(data){ + return new DashboardShare(this, {}).replace(this.$('.o_web_settings_dashboard_share')); + }, + + load_invitations: function(data){ + return new DashboardInvitations(this, data['users_info']).replace(this.$('.o_web_settings_dashboard_invitations')); + }, + + load_planner: function(data){ + return new DashboardPlanner(this, data['planner']).replace(this.$('.o_web_settings_dashboard_planner')); + }, + + on_new_apps: function(){ + this.do_action('base.open_module_tree'); + } +}); + +var DashboardInvitations = Widget.extend({ + template: 'DashboardInvitations', + events: { + 'click .o_web_settings_dashboard_invitations': 'send_invitations', + 'click .o_web_settings_dashboard_access_rights': 'on_access_rights_clicked', + 'click .o_web_settings_dashboard_user': 'on_user_clicked', + }, + init: function(parent, data){ + this.data = data; + this.parent = parent; + return this._super.apply(this, arguments); + }, + send_invitations: function(e){ + var self = this; + var $target = $(e.currentTarget); + var user_emails = _.filter(this.$('#user_emails').val().split(/[\n, ]/), function(email){ + return email != ""; + }); + var re = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i; + var is_valid_emails = _.every(user_emails, function(email) { + return re.test(email); + }); + if (is_valid_emails) { + // Disable button + $target.prop('disabled', true); + $target.find('i.fa-cog').removeClass('hidden'); + // Try to create user accountst + new Model("res.users") + .call("web_dashboard_create_users", [user_emails]) + .then(function() { + self.reload(); + }) + .always(function() { + // Re-enable button + self.$('.o_web_settings_dashboard_invitations').prop('disabled', false); + self.$('i.fa-cog').addClass('hidden'); + }); + + } + else { + this.do_warn(_t("Please provide valid email addresses"), ""); + } + }, + on_access_rights_clicked: function (e) { + var self = this; + e.preventDefault(); + this.do_action('base.action_res_users', { + on_reverse_breadcrumb: function(){ return self.reload();} + }); + }, + on_user_clicked: function (e) { + var self = this; + e.preventDefault(); + var user_id = $(e.currentTarget).data('user-id'); + var action = { + type: 'ir.actions.act_window', + view_type: 'form', + view_mode: 'form', + res_model: 'res.users', + views: [[this.data.user_form_view_id, 'form']], + res_id: user_id, + } + this.do_action(action,{ + on_reverse_breadcrumb: function(){ return self.reload();} + }); + }, + reload:function(){ + return this.parent.load(['invitations']); + }, +}); + +var DashboardPlanner = Widget.extend({ + + template: 'DashboardPlanner', + + events: { + 'click .o_web_settings_dashboard_progress_title,.progress': 'on_planner_clicked', + }, + + init: function(parent, data){ + this.data = data; + this.parent = parent; + this.planner_by_menu = {}; + this._super.apply(this, arguments); + }, + + willStart:function(){ + var self = this; + return new Model('web.planner').query().all().then(function(res) { + self.planners = res; + _.each(self.planners, function(planner) { + self.planner_by_menu[planner.menu_id[0]] = planner; + self.planner_by_menu[planner.menu_id[0]].data = $.parseJSON(planner.data) || {}; + }); + self.set_overall_progress(); + }); + }, + + update_planner_progress: function(){ + this.set_overall_progress(); + this.$('.o_web_settings_dashboard_planners_list').replaceWith( + QWeb.render("DashboardPlanner.PlannersList", {'planners': this.planners}) + ); + }, + + set_overall_progress: function(){ + var self = this; + this.sort_planners_list(); + var average = _.reduce(self.planners, function(memo, planner) { + return planner.progress + memo; + }, 0) / (self.planners.length || 1); + self.overall_progress = Math.floor(average); + self.$('.o_web_settings_dashboard_planner_overall_progress').text(self.overall_progress); + }, + + sort_planners_list: function(){ + // sort planners alphabetically but with fully completed planners at the end: + this.planners = _.sortBy(this.planners, function(planner){return (planner.progress == 100) + planner.name}); + }, + + on_planner_clicked: function(e){ + + var menu_id = $(e.currentTarget).attr('data-menu-id'); + // Setup the planner if we didn't do it yet + if (this.planner && this.planner.menu_id[0] == menu_id) { + this.dialog.$el.modal('show'); + } + else { + this.setup_planner(menu_id); + } + }, + + setup_planner: function(menu_id){ + var self = this; + this.planner = self.planner_by_menu[menu_id]; + if (this.dialog) { + this.dialog.destroy() + } + this.dialog = new PlannerDialog(this, this.planner); + this.dialog.on("planner_progress_changed", this, function(percent) { + self.planner.progress = percent; + self.update_planner_progress(); + }); + this.dialog.appendTo(webclient.$el).then(function() { + self.dialog.$el.modal('show'); + }); + } +}); + +var DashboardShare = Widget.extend({ + template: 'DashboardShare', + + events: { + 'click .tw_share': 'share_twitter', + 'click .fb_share': 'share_facebook', + 'click .li_share': 'share_linkedin', + }, + + init: function(parent, data){ + this.data = data; + this.parent = parent; + this.share_url = 'http://www.odoo.com/'; + this.share_text = encodeURIComponent("#IamUsingOdoo, an Open-Source Web App that manages my Sales, Projects, Accounting, Website, Warehouse, Shop and more"); + }, + + share_twitter: function(){ + var popup_url = _.str.sprintf( 'https://twitter.com/intent/tweet?tw_p=tweetbutton&text=%s %s',this.share_text,this.share_url); + this.sharer(popup_url); + }, + + share_facebook: function(){ + var popup_url = _.str.sprintf('https://www.facebook.com/sharer/sharer.php?u=%s', encodeURIComponent(this.share_url)); + this.sharer(popup_url); + }, + + share_linkedin: function(){ + var popup_url = _.str.sprintf('http://www.linkedin.com/shareArticle?mini=true&url=%s&title=I am using odoo&summary=%s&source=www.odoo.com', encodeURIComponent(this.share_url), this.share_text); + this.sharer(popup_url); + }, + + sharer: function(popup_url){ + window.open( + popup_url, + 'Share Dialog', + 'width=600,height=400'); // We have to add a size otherwise the window pops in a new tab + } +}); + +core.action_registry.add('web_settings_dashboard.main', Dashboard); + +return { + Dashboard: Dashboard, + DashboardInvitations: DashboardInvitations, + DashboardPlanner: DashboardPlanner, + DashboardShare: DashboardShare, +}; + +}); diff --git a/addons/web_settings_dashboard/static/src/less/dashboard.less b/addons/web_settings_dashboard/static/src/less/dashboard.less new file mode 100644 index 00000000000..78016febbf5 --- /dev/null +++ b/addons/web_settings_dashboard/static/src/less/dashboard.less @@ -0,0 +1,97 @@ +.o_web_settings_dashboard { + + @bg-dashboard-col: #FFF; + + .o_web_settings_dashboard_card() { + box-shadow: 0 1px 2px 0 rgba(0,0,0,.14); + background: @bg-dashboard-col; + border-radius: 2px; + } + + background: #F7F7F7; + height: 100%; + + .o_web_settings_dashboard_col { + padding: 10px 8px; + + > div > hr { + margin: 12px -18px 15px -18px; + border-color: #CBCBCB; + } + + > div:first-child { + .o_web_settings_dashboard_card(); + padding: 25px 18px 18px 18px; + .o_web_settings_dashboard_header { + font-size: 22px; + margin-bottom: 8px; + } + } + .o_web_settings_dashboard_pills { + margin-top: 10px; + + > li > a { + .o_web_settings_dashboard_card(); + padding: 10px; + + > i.fa { + vertical-align: middle; + font-size: 1.5em; + } + } + @media (max-width: 1200px) { + > li { + width: 100%; + margin-bottom: 5px; + } + } + } + .btn.o_browse_apps, .btn.o_web_settings_dashboard_invitations { + margin-top: 12px; + } + .o_web_settings_dashboard_compact_subtitle { + line-height: 12px; + display: block; + } + .o_web_settings_dashboard_invitations { + .o_web_settings_dashboard_invitation_form { + text-align: left; + > textarea { + resize: vertical; + width: 100%; + } + } + a.o_web_settings_dashboard_user { + line-height: 24px; + } + } + .o_web_settings_dashboard_planner { + .progress { + height: 10px; + background-color: darken(@bg-dashboard-col, 15%); + cursor: pointer; + margin-bottom: 30px; + } + .o_web_settings_dashboard_planner_progress_text { + font-size: 18px; + padding-right: 0px; + padding-top: 3px; + } + .o_web_settings_dashboard_planner_progress_bar { + padding-left: 10px; + } + .o_web_settings_dashboard_progress_title { + font-weight: bold; + cursor: pointer; + color: @odoo-brand-optional; + } + .o_web_settings_dashboard_progress_100 { + -webkit-filter: grayscale(100%); + filter: grayscale(100%); + &.o_web_settings_dashboard_planner_progress_text { + font-size: 15px + } + } + } + } +} diff --git a/addons/web_settings_dashboard/static/src/xml/dashboard.xml b/addons/web_settings_dashboard/static/src/xml/dashboard.xml new file mode 100644 index 00000000000..ebec855e3c1 --- /dev/null +++ b/addons/web_settings_dashboard/static/src/xml/dashboard.xml @@ -0,0 +1,132 @@ + + + + + + + + + App store + Theme store + + + + + + + + + + + + + + + + + + + + + Installed App + Installed Apps + + + No app installed + + + + Browse Apps + + + + + + + + + + Active Users + + + + Manage access rights + + + + Add new users (with default rights) + + Add Users + + + + + + Pending users: + + + + + + + + + + + + + % + Implementation + + + + Follow these Implementation Guides to get the maximum value for your business. + + + + + + + + + + + + You need to install some apps first. + + + + % + + + + + + + + + + + + + + + + + + Social Share + + + Help us spread the word: Share Odoo's awesomeness with your friends! + + + + + + + + + + + + diff --git a/addons/web_settings_dashboard/views/dashboard_templates.xml b/addons/web_settings_dashboard/views/dashboard_templates.xml new file mode 100644 index 00000000000..ff4f74b1063 --- /dev/null +++ b/addons/web_settings_dashboard/views/dashboard_templates.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/addons/web_settings_dashboard/views/dashboard_views.xml b/addons/web_settings_dashboard/views/dashboard_views.xml new file mode 100644 index 00000000000..3bf85772f4b --- /dev/null +++ b/addons/web_settings_dashboard/views/dashboard_views.xml @@ -0,0 +1,8 @@ + + + + Dashboard + web_settings_dashboard.main + + +