diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index fb75b0cd..2e01d66e 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. +import tarfile import babel.messages.pofile import base64 @@ -45,6 +46,11 @@ from flectra.http import content_disposition, dispatch_rpc, request, \ from flectra.exceptions import AccessError, UserError from flectra.models import check_method_name from flectra.service import db +import requests + +from flectra.tools import config +from flectra import release +from flectra.http import root _logger = logging.getLogger(__name__) @@ -433,6 +439,12 @@ def binary_content(xmlid=None, model='ir.attachment', id=None, field='datas', un #---------------------------------------------------------- # Flectra Web web Controllers #---------------------------------------------------------- + +server_url = 'https://store.flectrahq.com' +tmp_dir_path = tempfile.gettempdir() +data_dir = os.path.join(config.options['data_dir'], 'addons', release.series) + + class Home(http.Controller): @http.route('/', type='http', auth="none") @@ -549,6 +561,104 @@ class Home(http.Controller): options=context) return True + @http.route(['/web/get_app_store_mode'], type='json', auth="user") + def get_app_store_mode(self, **kwargs): + if request.env.user.has_group('base.group_system'): + app_store = config.get('app_store') + return app_store if app_store in ['install', 'download', 'disable'] else 'download' + return False + + @http.route(['/web/app_action'], type='json', auth="user") + def app_action(self, action='', module_name='', **kwargs): + if request.env.user.has_group('base.group_system'): + if module_name and action: + module = request.env['ir.module.module'].search([('state', '=', 'installed'), ('name', '=', module_name)], limit=1) + if module: + module.button_immediate_uninstall() + return {"success": "Module is successfully uninstalled."} + return {"error": "Module not found or Module already uninstalled."} + return False + + @http.route(['/web/get_modules'], type='json', auth="user") + def get_modules(self, **kwargs): + if request.env.user.has_group('base.group_system'): + try: + modules = request.env['ir.module.module'].search_read([('state', '=', 'installed')], fields=['name']) + p = requests.post(server_url + '/flectrahq/get_modules', data=kwargs) + data = json.loads(p.content.decode('utf-8')) + data.update({ + 'installed_modules': [m['name'] for m in modules], + 'store_url': server_url + }) + return data + except: + return False + return False + + @http.route(['/web/module_download/'], type='http', auth="user", methods=['GET', 'POST']) + def app_download(self, id=None): + if request.env.user.has_group('base.group_system'): + dbuuid = request.env['ir.config_parameter'].get_param('database.uuid') + p = requests.get(server_url + '/flectrahq/get_module_zip/' + str(id) + '/1', params={'dbuuid': dbuuid}) + try: + data = json.loads(p.content.decode('utf-8')) + if data.get('error', False): + return json.dumps(data) + except: + pass + name = p.headers.get('Content-Disposition', False) + if name and name.startswith('filename='): + zip = p.content + headers = [('Content-Type', 'application/zip'), + ('Content-Length', len(zip)), + ('Content-Disposition', p.headers.get('Content-Disposition', 'dummy') + '.tar.gz')] + response = request.make_response(zip, headers) + return response + else: + return request.not_found() + return request.not_found() + + @http.route(['/web/app_download_install/'], type='json', auth="user") + def app_download_install(self, id=None): + if request.env.user.has_group('base.group_system'): + IrModule = request.env['ir.module.module'] + try: + res_get_details = requests.get(server_url + '/flectrahq/get_module_zip/' + str(id) + '/0') + module_file_details = json.loads(res_get_details.content.decode('utf-8')) + except: + return {"error": "Internal Server Error"} + finally: + res_download = requests.get(server_url + '/flectrahq/get_module_zip/' + str(id) + '/1') + downloaded_file_checksum = hashlib.sha1(res_download.content or b'').hexdigest() + if res_download.status_code == 200: + try: + data = json.loads(res_download.content.decode('utf-8')) + if data.get('error', False): + return data + except: + pass + + if module_file_details['checksum'] == downloaded_file_checksum: + path = os.path.join(tmp_dir_path, module_file_details['name']) + try: + with open(path, 'wb') as f: + f.write(res_download.content) + with tarfile.open(path) as tar: + tar.extractall(data_dir) + except: + return {"error": "Internal Server Error"} + finally: + IrModule.update_list() + root.load_addons() + modules = IrModule.search([('name', '=', module_file_details['module_name'])], limit=1) + modules.button_immediate_install() + os.remove(path) + return {"success": "Module is successfully installed."} + else: + return {"error": "File Crash."} + + return {"error": "Internal Server Error."} + return False class WebClient(http.Controller): diff --git a/addons/web/static/src/js/apps.js b/addons/web/static/src/js/apps.js index bde7d0ad..d7595cad 100644 --- a/addons/web/static/src/js/apps.js +++ b/addons/web/static/src/js/apps.js @@ -2,63 +2,77 @@ flectra.define('web.Apps', function (require) { "use strict"; var core = require('web.core'); -var framework = require('web.framework'); -var session = require('web.session'); var Widget = require('web.Widget'); +var Dialog = require('web.Dialog'); +var framework = require('web.framework'); var _t = core._t; - -var apps_client = null; +var QWeb = core.qweb; var Apps = Widget.extend({ - template: 'EmptyComponent', - remote_action_tag: 'loempia.embed', - failback_action_id: 'base.open_module_tree', - - init: function(parent, action) { + template: 'AppStore', + events: { + 'click [app-action="download"]': '_onDownload', + 'click [app-action="install"]': '_onInstall', + 'click [app-action="uninstall"]': '_onUninstall', + 'click [app-action="view-info"]': '_onClickViewDetails', + 'click .load-more': '_onLoadMore', + 'click #try-again': '_onTryAgain', + 'keypress #input-module-search': '_onEnterSearch', + 'click #btn-module-search': '_onClickSearch', + 'click .top': '_onClickTop', + }, + init: function (parent, action) { this._super(parent, action); var options = action.params || {}; - this.params = options; // NOTE forwarded to embedded client action + this.context = {}; + this.params = options; }, - - get_client: function() { - // return the client via a deferred, resolved or rejected depending if - // the remote host is available or not. - var check_client_available = function(client) { - var d = $.Deferred(); - var i = new Image(); - i.onerror = function() { - d.reject(client); - }; - i.onload = function() { - d.resolve(client); - }; - var ts = new Date().getTime(); - i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif?%s', client.origin, ts); - return d.promise(); - }; - if (apps_client) { - return check_client_available(apps_client); - } else { - return this._rpc({model: 'ir.module.module', method: 'get_apps_server'}) - .then(function(u) { - var link = $(_.str.sprintf('', u))[0]; - var host = _.str.sprintf('%s//%s', link.protocol, link.host); - var dbname = link.pathname; - if (dbname[0] === '/') { - dbname = dbname.substr(1); + willStart: function () { + var self = this; + var def = this._rpc({ + route: '/web/get_app_store_mode', + }); + return $.when(def, this._super.apply(this, arguments)).then(function (mode) { + self.mode = mode; + framework.blockUI(); + if (self.mode != 'disable') { + self._rpc({ + route: '/web/get_modules', + }).done(function (data) { + if (data) { + self.all_app = []; + _.each(data.modules, function (categ) { + self.all_app = self.all_app.concat(categ); + }); + self.active_categ = data.categ[0][0]; + self.store_url = data.store_url; + self.search_categ = ''; + self.$el.html(QWeb.render('AppStore.Content', { + modules: data.modules, + categ: data.categ, + installed_modules: data.installed_modules, + mode: self.mode, + store_url: data.store_url + })); + if (data.banner) { + self.$el.find('.banner').html(data.banner); + } else { + self.$el.find('.banner').html('

FlectraHQ Store

'); + } + self.$el.find('ul.nav.category a:first').tab('show'); + self.$el.find('a[data-toggle="tab"]').on('shown.bs.tab', self._onChangeTab.bind(self)); + self.context.categ = self.prepareContext(data.categ); + self.context.limit = data.limit; + } else { + self.$el.html(QWeb.render('AppStore.TryError', {})); } - var client = { - origin: host, - dbname: dbname - }; - apps_client = client; - return check_client_available(client); + framework.unblockUI(); }); - } + } + }); }, - - destroy: function() { + destroy: function () { $(window).off("message." + this.uniq); if (this.$ifr) { this.$ifr.remove(); @@ -66,97 +80,189 @@ var Apps = Widget.extend({ } return this._super(); }, - - _on_message: function($e) { - var self = this, client = this.client, e = $e.originalEvent; - - if (e.origin !== client.origin) { - return; - } - - var dispatcher = { - 'event': function(m) { self.trigger('message:' + m.event, m); }, - 'action': function(m) { - self.do_action(m.action).then(function(r) { - var w = self.$ifr[0].contentWindow; - w.postMessage({id: m.id, result: r}, client.origin); - }); - }, - 'rpc': function(m) { - return self._rpc({route: m.args[0], params: m.args[1]}).then(function(r) { - var w = self.$ifr[0].contentWindow; - w.postMessage({id: m.id, result: r}, client.origin); - }); - }, - 'Model': function(m) { - return self._rpc({model: m.model, method: m.args[0], args: m.args[1]}) - .then(function(r) { - var w = self.$ifr[0].contentWindow; - w.postMessage({id: m.id, result: r}, client.origin); - }); - }, - }; - // console.log(e.data); - if (!_.isObject(e.data)) { return; } - if (dispatcher[e.data.type]) { - dispatcher[e.data.type](e.data); + start: function () { + if (this.mode == 'disable') { + this.$el.html(QWeb.render('AppStore.Disable', {})); + framework.unblockUI(); } + return this._super.apply(this, arguments); }, - - start: function() { - var self = this; - var def = $.Deferred(); - self.get_client().then(function(client) { - self.client = client; - - var qs = {db: client.dbname}; - if (session.debug) { - qs.debug = session.debug; + prepareContext: function (data) { + var context = {}; + _.each(data, function (value) { + context[value[0]] = { + offset: 0, + search: '' } - var u = $.param.querystring(client.origin + "/apps/embed/client", qs); - var css = {width: '100%', height: '750px'}; - self.$ifr = $('