diff --git a/.gitignore b/.gitignore index 1e7cc6a..f799be8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ !bin/ !bin/config/ !packs +!packs/devs/ # Permitir el seguimiento de archivos específicos # !archivo1.txt @@ -20,3 +21,4 @@ # !carpeta1/*.txt !bin/* !bin/config/* +!packs/devs/** diff --git a/packs/devs/kit_code/README.rst b/packs/devs/kit_code/README.rst new file mode 100644 index 0000000..08ac80c --- /dev/null +++ b/packs/devs/kit_code/README.rst @@ -0,0 +1,41 @@ +Odoo Code🔨 +================== +* Odoo Code🔨 help you build Odoo application onlione, super speedup development process🚀 + +============ + +Features +========== +✍️ View & Write Odoo code through Web Code Editor in the same Odoo runtime environment; + +☕️ Hot reload, quickly and easily experiment, build UIs, add features, fix bugs & more; + +👉 Locate the Templates Script source code quickly, save time & speed up development process; + +⚡︎ Write Odoo code anytime anywhere, free mind & happy coding! + + +Warning +========== + +⚠️ Only use for development phase! Production Environment is not recommend❗️ + + +Team +--------- +ixkit.com +Artificer@ixkit.com + +Maintainer +--------- +This module was maintained by ixkit Team. + +For support and more information, please visit https://www.ixkit.com/odookit + +Further information +--------- +HTML Description: `` + +Github +--------- +https://www.github.com/ixkit/odookit diff --git a/packs/devs/kit_code/__config__.py b/packs/devs/kit_code/__config__.py new file mode 100644 index 0000000..f4246c4 --- /dev/null +++ b/packs/devs/kit_code/__config__.py @@ -0,0 +1,4 @@ + +{ + +} \ No newline at end of file diff --git a/packs/devs/kit_code/__init__.py b/packs/devs/kit_code/__init__.py new file mode 100644 index 0000000..3b25c6e --- /dev/null +++ b/packs/devs/kit_code/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import os +import sys + +from . import land +from . import controllers +from . import models + +from .__support__ import * + +from .land import config +from .land.trace import Tracer + + + +from .hooks import module_pre_init_hook, module_post_init_hook,module_uninstall_hook + + +def main_init(): + config_data = config.load(get_config_file()) + + +#-- main --- +main_init() \ No newline at end of file diff --git a/packs/devs/kit_code/__manifest__.py b/packs/devs/kit_code/__manifest__.py new file mode 100644 index 0000000..6fc6880 --- /dev/null +++ b/packs/devs/kit_code/__manifest__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +# ixkit - odoo code🔨 +# +#@team : ixkit +#@author : Artificer@ixkit.com +#@date : 2024-4-25 +#@version : 1.1.0 +# +#----------------------------------------------------------------------- + +{ + "name": "Odoo Code🔨", + "version": "18.0", + "module_version": "1.1.0", + "summary": """ + Help you build Odoo application online, super speed development process🚀 + """, + "description":"Odoo Code🔨 build application in new way!", + 'category': 'Extra Tools/ixkit', + 'author': 'ixkit', + 'company': 'ixkit', + 'maintainer': 'ixkit', + 'website': "http://www.ixkit.com/odookit", + "live_test_url": "http://www.ixkit.com/odookit", + "license": "OPL-1", + "support": "odoo@ixkit.com", + "depends": [ + 'base', 'web' + ], + "data": [ + 'security/ir.model.access.csv', + + 'views/code_server/forms.xml', + 'views/code_server/lists.xml', + 'views/code_server/actions.xml', + 'views/code_server/menus.xml', + + 'views/console/console.xml', + + 'views/page/web.xml', + 'views/page/effect.xml', + ], + + 'assets': { + 'web.assets_backend': [ + 'kit_code/static/src/xml/code_dashboard.xml', + 'kit_code/static/src/js/code_dashboard.js', + 'kit_code/static/src/css/code_dashboard.css', + 'kit_code/static/image/**/*', + + 'kit_code/static/jslib/termynal/**/*', + 'kit_code/static/jslib/jsontable/**/*', + + 'kit_code/static/src/js/effect.js', + ], + 'web.assets_frontend': [ + + ], + }, + 'images': [ + 'static/description/banner.png', + ], + + 'pre_init_hook': 'module_pre_init_hook', + 'post_init_hook': 'module_post_init_hook', + 'uninstall_hook': 'module_uninstall_hook', + 'installable': True, + 'application': True, + 'auto_install': False, +} diff --git a/packs/devs/kit_code/__support__.py b/packs/devs/kit_code/__support__.py new file mode 100644 index 0000000..ea850db --- /dev/null +++ b/packs/devs/kit_code/__support__.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +import os +import sys + +from .land import config +from .land.trace import Tracer + + +def get_config_file(): + my_dir = os.path.dirname(os.path.abspath(__file__)) + return my_dir + "/__config__.py" + +def get_module_path(): + my_dir = os.path.dirname(os.path.abspath(__file__)) + return my_dir + +def get_mainifset_file(): + my_dir = os.path.dirname(os.path.abspath(__file__)) + return my_dir + "/__manifest__.py" + +def get_mainifset(): + mainifset_data = config.load(get_mainifset_file()) + return mainifset_data + + +def get_mainifset_version(): + mainifset_data = get_mainifset() + version = mainifset_data.get('version') + first_dot_index = version.index(".") + big_ver = version[0:first_dot_index] + return big_ver + + #TODO +def get_support_major_version(): + return '17' + +def setup_tracer(level=None): + trace_level = level + while trace_level is None: + runtime_options = sys.modules['runtime_options'] + if runtime_options is None: + break + trace_level = runtime_options.config.get('trace_level') + if trace_level is None: + break + break + Tracer.set_trace_level(trace_level) + + +#-- +setup_tracer(False) +#setup_tracer('DEBUG') + + + + + \ No newline at end of file diff --git a/packs/devs/kit_code/controllers/__init__.py b/packs/devs/kit_code/controllers/__init__.py new file mode 100644 index 0000000..c0c2783 --- /dev/null +++ b/packs/devs/kit_code/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import main +from . import intent \ No newline at end of file diff --git a/packs/devs/kit_code/controllers/intent.py b/packs/devs/kit_code/controllers/intent.py new file mode 100644 index 0000000..23877db --- /dev/null +++ b/packs/devs/kit_code/controllers/intent.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" + "url":"/web/static/src/core/dropdown/dropdown.xml", + "file":"/odoo-17/odoo/addons/web/static/src/core/dropdown/dropdown.xml" + => + dir: /odoo-17/odoo/addons/web + file: static/src/core/dropdown/dropdown.xml +""" +def to_dir_file(url,full_file_name): + dir = full_file_name.replace(url,'') + list = url.split('/' ); + module_name = list[1]; + dir = dir + "/" + module_name + + r_file_name = None + for i in range(2,len(list)): + if r_file_name is None: + r_file_name = list[i] + else: + r_file_name = r_file_name + "/" + list[i] + + return dir, r_file_name + + +def _test(): + url = "/web/static/src/core/dropdown/dropdown.xml" + file ="/odoo-17/odoo/addons/web/static/src/core/dropdown/dropdown.xml" + + #dir: /odoo-17/odoo/addons/web + #file: static/src/core/dropdown/dropdown.xml + + dir, sfile = to_dir_file(url,file) + print("\r\n-----------\r\n") + print("dir:" +dir) + print("file:" + sfile) + +if __name__ == '__main__': + _test() diff --git a/packs/devs/kit_code/controllers/main.py b/packs/devs/kit_code/controllers/main.py new file mode 100644 index 0000000..c17f5a9 --- /dev/null +++ b/packs/devs/kit_code/controllers/main.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +import json +import logging + +from odoo import http +from odoo.tools import date_utils +from odoo.http import request +from odoo import api, SUPERUSER_ID +from werkzeug.wrappers import Request, Response +from urllib.parse import quote + +from ..land.web import rpc_result +from .intent import to_dir_file + +_logger = logging.getLogger(__name__) + + +def kws2Dict(**kwargs): + result = {} + # Iterating over the Python kwargs dictionary + for key, value in kwargs.items(): + if key.startswith('amp;'): + key = key.replace("amp;",'') + result [key]= value + return result + +def dictToQueryParamsStr(dict): + result = "" + for key, value in dict.items(): + result = result + "&{}={}".format(key,value) + return result + +ROOT = "/code🔨" + +def entry(val=None): + if val is None: + return ROOT + return "{}{}".format(ROOT,val) + +class Main(http.Controller): + + + @http.route(entry('/api/ping') , auth='user', type='json',website=True ) + def code_server_ping(self, **kwargs): + headers = {'Content-Type': 'application/json'} + body = rpc_result.success() + return body #Response(json.dumps(body), headers=headers) + + + """ + "name:"web.Dropdown", + "url":"/web/static/src/core/dropdown/dropdown.xml", + "file":"/odoo-17/odoo/addons/web/static/src/core/dropdown/dropdown.xml" + """ + @http.route(entry(), auth='user', website=True ) + def code_server(self, **kwargs): + + params = kws2Dict(**kwargs) + + _logger.debug('/code🔨, input params =%s',params) + action = params.get('action') + if action == 'start': + return self.start_then_reload_current_page(params) + #@case + next_url = params.get('nextUrl') + _logger.debug('/code🔨, next_url=%s',next_url) + if not next_url is None: + started = params.get('started') + module_name = params.get('moduleName') + return self.open_file(next_url,module_name,started) + #@case + url = params.get('url') + file_full_path = params.get('file') + name = params.get('name') + + try: + dir,file = to_dir_file(url,file_full_path) + except Exception as ex: + render_values = { + 'error': 'invalidate parameters' + } + return self.render_self_home_page(render_values) + + params ={ + 'dir' : dir, + 'file':file, + 'keywords': name + } + # params_str = urlencode(params) + # _logger.debug('🧐 try ->open_file, urlencode params_str:%s',params_str) + """ + http://localhost:3030/folder?dir={}&file={}&keywords={} + """ + # next_url = 'http://localhost:3030/folder?{}'.format(params_str) + # _logger.debug('🧐 try ->open_file, next_url:%s',next_url) + + next_url = "http://localhost:3030/folder?dir={}&file={}&keywords={}".format(dir,file,name) + _logger.debug('🧐 try ->open_file, next_url:%s',next_url) + next_url = quote(next_url) + module_name = name + return self.open_file(next_url,module_name) + + + def open_file(self,next_url,module_name,started=False): + + _logger.debug('entry->open_file, next_url:%s,',next_url) + + + code_server = self.get_code_server() + if not code_server.get('status') == 'running': + if started: + code_server['status'] = 'running' + code_server['loading'] = True + + render_values = { 'nextUrl':next_url, + 'codeServer': code_server, + 'moduleName': module_name, + } + + + _logger.debug('->open_file, render_values:%s',render_values) + + return self.render_self_home_page(render_values,True) + + def render_self_home_page(self,render_values,need_start=True): + if need_start: + next_url = render_values.get('nextUrl') + start_url = entry("?action=start&nextUrl={}".format(next_url)) + + render_values['startUrl'] = start_url + + return request.render('kit_code.home_page',render_values ) + + def get_code_server(self): + server_info = request.env['kit.code.server'].fetch_server_info() + """ + { + "success": + "data":{ + url: + } + } + """ + if server_info is None or server_info.get('success') is False : + return { + 'status':'ready' + } + #info = server_info.get('data') + result = { + 'status': 'running', + # 'url' : info.url, + 'detail': server_info + } + return result + + + def start_then_reload_current_page(self, params): + _logger.debug('->start_then_reload_current_page, params:%s',params) + + if not params.get("start") is None: + del params['start'] + + codeServer = request.env['kit.code.server'] + result = codeServer.ctrl_server('start') + if result and result.get('success'): + next_url = params.get('nextUrl') + file = params.get('file') + keywords = params.get('keywords') + if file and not file in next_url: + next_url = "{}&file={}&keywords={}".format(file,keywords) + next_url = quote(next_url) + url = entry("?started=true&nextUrl={}".format(next_url)) + return request.redirect(url) + else: + # failed start the reback to original state + paramsStr = dictToQueryParamsStr(params) + url = entry("?{}".format(paramsStr)) + return request.redirect(url) + + \ No newline at end of file diff --git a/packs/devs/kit_code/hooks.py b/packs/devs/kit_code/hooks.py new file mode 100644 index 0000000..d2e4c4a --- /dev/null +++ b/packs/devs/kit_code/hooks.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +import sys + + +def module_pre_init_hook(env): + + pass + + +def module_post_init_hook(env): + + pass + +def module_uninstall_hook(env): + + pass + + diff --git a/packs/devs/kit_code/land/__init__.py b/packs/devs/kit_code/land/__init__.py new file mode 100644 index 0000000..f092322 --- /dev/null +++ b/packs/devs/kit_code/land/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from . import lang +from . import trace +from . import jsons + +def smart_json(val): + jsons.SmartJson(val) \ No newline at end of file diff --git a/packs/devs/kit_code/land/config/__init__.py b/packs/devs/kit_code/land/config/__init__.py new file mode 100644 index 0000000..c8ba5d6 --- /dev/null +++ b/packs/devs/kit_code/land/config/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +import os +from ..jsons import load_py_json + +def load(path): + return load_py_json(path) + + diff --git a/packs/devs/kit_code/land/jsons/__init__.py b/packs/devs/kit_code/land/jsons/__init__.py new file mode 100644 index 0000000..9e6f160 --- /dev/null +++ b/packs/devs/kit_code/land/jsons/__init__.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +import json + +from .__smart_json__ import SmartJson + +# remove comment +def load_py_to_json_str(file_name): + buf = None + with open(file_name, 'r') as file: + Lines = file.readlines() + for line in Lines: + if line.startswith("#"): + continue + else: + if buf is None: + buf = line + else: + buf = buf + line + + return buf + + +def load_py_json(file_name): + buf = load_py_to_json_str(file_name) + if not buf is None: + data = json.loads(buf) + return data + return None + +def load_json(file_name): + with open(file_name, 'r') as fcc_file: + data = json.load(fcc_file) + return data + return None + +def to_json(obj): + data = json.load(obj) + return data + +def set_to_list(obj): + if isinstance(obj, set): + return list(obj) + raise TypeError(f"Object of type {type(obj)} is not JSON serializable") + + + +def json_dumps(obj): + obj + json_str = json.dumps(obj, default=set_to_list) + return json_str diff --git a/packs/devs/kit_code/land/jsons/__smart_json__.py b/packs/devs/kit_code/land/jsons/__smart_json__.py new file mode 100644 index 0000000..212ee6f --- /dev/null +++ b/packs/devs/kit_code/land/jsons/__smart_json__.py @@ -0,0 +1,368 @@ +""" +Author : J. Koffi ONIPOH +Version : 2.0.2 +email: jolli644@gmail.com +""" + +import datetime +import json +import os +from collections import OrderedDict +from copy import deepcopy + + +class SmartJson: + def __init__(self, cls=None): + """ + :param cls: + """ + + self.__copy = cls + self.__classe = deepcopy(cls) + self.___obj = None + if cls: + self.classname = cls.__class__.__name__ + + def serialize(self, pretty=True): + """ + :param pretty: + :return: + """ + + try: + if isinstance(self.__classe, dict): + return SmartJson._DictConversion(self.__classe).serialize(pretty) + elif isinstance(self.__classe, list): + return SmartJson._ListConversion(self.__classe).serialize(pretty) + elif isinstance(self.__classe, (int, float, bool, str, type(None))): + if pretty: + return json.dumps(self.__classe, indent=2, sort_keys=True) + else: + return json.dumps(self.__classe, sort_keys=True) + elif isinstance(self.__classe, (tuple, complex, datetime.date, datetime.datetime, OrderedDict)): + if pretty: + return json.dumps(self._JsonConvert().json_convert(self.__classe), indent=2, sort_keys=True) + else: + return json.dumps(self._JsonConvert().json_convert(self.__classe), sort_keys=True) + elif isinstance(self.__classe, object): + if SmartJson._JsonConvert().get_class_name(self.__classe) == "enum.EnumMeta": + return SmartJson._EnumConversion(self.__classe).serialize(pretty) + else: + self.___obj = SmartJson._DataTypeConversion(self.__classe).next() + if pretty: + return json.dumps({'' + self.classname: self._serialize(self.___obj).__dict__}, indent=2, + sort_keys=True) + else: + return json.dumps({'' + self.classname: self._serialize(self.___obj).__dict__}, sort_keys=True) + + + + except TypeError as e: + SmartJson._UnsupportedClass((type(self.___obj).__name__), e) + + def serializeToJsonFile(self, directory="output", filename="smart.json"): + """ + :param pretty: + :return: + """ + + try: + os.makedirs(directory) + except OSError: + if not os.path.isdir(directory): + raise + + try: + if isinstance(self.__classe, ( + int, float, bool, str, type(None), dict, tuple, complex, datetime.date, datetime.datetime, + OrderedDict)) or SmartJson._JsonConvert().get_class_name(self.__classe) == "enum.EnumMeta": + with open(directory + "/" + filename, 'w') as outfile: + json.dump(json.loads(self.serialize(pretty=True)), outfile, indent=2, sort_keys=True) + elif isinstance(self.__classe, object): + self.___obj = SmartJson._DataTypeConversion(self.__classe).next() + + if filename != "smart.json": + with open(directory + "/" + filename, 'w') as outfile: + json.dump(json.loads( + json.dumps({'' + self.classname: self._serialize(self.___obj).__dict__}, indent=2, + sort_keys=True)), outfile, indent=2, sort_keys=True) + else: + with open(directory + "/" + self.classname + ".json", 'w') as outfile: + json.dump(json.loads( + json.dumps({'' + self.classname: self._serialize(self.___obj).__dict__}, indent=2, + sort_keys=True)), outfile, indent=2, sort_keys=True) + + + + except TypeError as e: + SmartJson._UnsupportedClass((type(self.___obj).__name__), e) + + def toObjectFromFile(self, jsonFile): + """ + :param jsonFile: + :param obj_name: + :return: + """ + with open(jsonFile) as outfile: + dic = json.load(outfile) + + return SmartJson._KObject(dic) + + def toObject(self, _json): + """ + :param _json: + :param obj_name: + :return: + """ + dic = None + if isinstance(_json, str): + dic = json.loads(_json) + elif isinstance(_json, dict): + dic = _json + + return SmartJson._KObject(dic) + + def getClass(self): + return self.__copy + + def _serialize(self, obj): + for attr, value in vars(obj).items(): + if hasattr(value, "__class__"): + if isinstance(value, ( + int, float, bool, complex, list, tuple, str, OrderedDict, dict, + datetime.datetime, datetime.date, bytes, type(None))): + continue + elif SmartJson._JsonConvert().get_class_name(value) == "builtins.dict": + continue + else: + obj.__setattr__(attr, value.__dict__) + self._serialize(value) + + return obj + + class _DataTypeConversion: + def __init__(self, cls): + self.___cls = cls + self._json_cvt = SmartJson._JsonConvert() + + def next(self): + try: + return self.__next(self.___cls) + except TypeError as e: + SmartJson._UnsupportedClass((type(self.___cls).__name__), e) + + def __next(self, cls): + for attr, value in vars(cls).items(): + if hasattr(value, "__class__"): + if isinstance(value, (datetime.datetime, datetime.date, complex)): + cls.__setattr__(attr, self._json_cvt.json_convert(value)) + elif isinstance(value, (int, float, bool, str)): + continue + elif isinstance(value, (list, tuple)): + cls.__setattr__(attr, list((self._json_cvt.json_convert(v) for v in value))) + elif self._json_cvt.get_class_name(value) == "collections.deque": + cls.__setattr__(attr, self._json_cvt.json_convert(OrderedDict(value))) + elif self._json_cvt.get_class_name(value) == "enum.EnumMeta": + cls.__setattr__(attr, [SmartJson._EnumConversion(value).convert()]) + elif isinstance(value, type(None)): + cls.__setattr__(attr, "") + elif isinstance(value, bytes): + cls.__setattr__(attr, value.decode("utf-8")) + elif isinstance(value, dict): + obj = [SmartJson._DictConversion(value).convert()] + cls.__setattr__(attr, obj) + else: + self.__next(value) + + return cls + + class _UnsupportedClass(Exception): + def __init__(self, message, errors): + # Call the base class constructor with the parameters it needs + super().__init__(message) + + # Now for your custom code... + self.errors = errors + + print("Error : 228 , UnsupportedClass : %s " % (message), errors) + + class _KObject(object): + def __init__(self, d): + for a, b in d.items(): + if isinstance(b, (list, tuple)): + setattr(self, a, [self.__class__(x) if isinstance(x, dict) else x for x in b]) + elif isinstance(b, str): + try: + setattr(self, a, datetime.datetime.strptime(b, "%Y-%m-%d %H:%M:%S.%f")) + except ValueError: + setattr(self, a, b) + + else: + setattr(self, a, self.__class__(b) if isinstance(b, dict) else b) + + class _EnumConversion: + def __init__(self, myEnum): + self.__myEnum = deepcopy(myEnum) + self._json_cvt = SmartJson._JsonConvert() + + def serialize(self, pretty): + if pretty: + return json.dumps(self.convert(), indent=2, sort_keys=True) + else: + return json.dumps(self.convert()) + + def convert(self): + if self._json_cvt.get_class_name(self.__myEnum) == "enum.EnumMeta": + converts = {} + for attr, value in vars(self.__myEnum).items(): + if "_member_names_" == attr: + for member in value: + # var = self.__myEnum.__getattribute__(self.__myEnum, member).value + converts[member] = self.__myEnum[member].value + + return SmartJson._DictConversion(converts).convert() + else: + SmartJson._UnsupportedClass((type(self.__myEnum).__name__), "This type of enum not support") + + class _ListConversion: + def __init__(self, myList): + self.__myList = deepcopy(myList) + self._json_cvt = SmartJson._JsonConvert() + + def serialize(self, pretty): + if pretty: + return json.dumps(self.convert(), indent=2, sort_keys=True) + else: + return json.dumps(self.convert()) + + def convert(self): + convert_result = [] + for attr in self.__myList: + if isinstance(attr, (datetime.date, datetime.datetime)): + convert_result.append(str(attr)) + elif isinstance(attr, bytes): + convert_result.append(attr.decode("utf-8")) + elif isinstance(attr, (int, float, bool, str, type(None))): + convert_result.append(attr) + elif isinstance(attr, (list, tuple, set)): + convert_result.append([self._json_cvt.json_convert(item) for item in attr]) + elif isinstance(attr, OrderedDict): + convert_result.append(self._json_cvt.json_convert(attr)) + elif isinstance(attr, complex): + convert_result.append(self._json_cvt.json_convert(attr)) + elif isinstance(attr, dict): + obj = [SmartJson._DictConversion(attr).convert()] + convert_result.append(obj) + elif self._json_cvt.get_class_name(attr) == "collections.deque": + convert_result.append(self._json_cvt.json_convert(OrderedDict(attr))) + elif self._json_cvt.get_class_name(attr) == "enum.EnumMeta": + convert_result.append([SmartJson._EnumConversion(attr).convert()]) + elif hasattr(attr, "__class__"): + if isinstance(attr, (int, float, bool, str, type(None))): + convert_result.append(attr) + elif isinstance(attr, (list, tuple, set)): + convert_result.append({[self._json_cvt.json_convert(item) for item in attr]}) + else: + cls = attr + serialize = SmartJson._DataTypeConversion(cls).next().__dict__ + convert_result.append(serialize) + + return convert_result + + class _DictConversion: + def __init__(self, dict): + self.__dict = deepcopy(dict) + self._json_cvt = SmartJson._JsonConvert() + + def serialize(self, pretty): + if pretty: + return json.dumps(self.convert(), indent=2, sort_keys=True) + else: + return json.dumps(self.convert()) + + def convert(self): + for attr in self.__dict: + if isinstance(self.__dict[attr], (datetime.date, datetime.datetime)): + self.__dict[attr] = str(self.__dict[attr]) + elif isinstance(self.__dict[attr], bytes): + self.__dict[attr] = self.__dict[attr].decode("utf-8") + elif isinstance(self.__dict[attr], (int, float, bool, str, type(None))): + continue + elif isinstance(self.__dict[attr], (list, tuple, set)): + self.__dict[attr] = [self._json_cvt.json_convert(item) for item in self.__dict[attr]] + elif isinstance(self.__dict[attr], OrderedDict): + self.__dict[attr] = self._json_cvt.json_convert(self.__dict[attr]) + elif isinstance(self.__dict[attr], complex): + self.__dict[attr] = self._json_cvt.json_convert(self.__dict[attr]) + elif self._json_cvt.get_class_name(self.__dict[attr]) == "collections.deque": + self.__dict[attr] = self._json_cvt.json_convert(OrderedDict(self.__dict[attr])) + elif self._json_cvt.get_class_name(self.__dict[attr]) == "enum.EnumMeta": + self.__dict[attr] = [SmartJson._EnumConversion(self.__dict[attr]).convert()] + elif hasattr(self.__dict[attr], "__class__"): + if isinstance(self.__dict[attr], (int, float, bool, str, type(None))): + continue + elif isinstance(self.__dict[attr], (list, tuple, set)): + self.__dict[attr] = {[self._json_cvt.json_convert(item) for item in self.__dict[attr]]} + elif isinstance(self.__dict[attr], dict): + cls = self.__dict[attr] + serialize = SmartJson._DictConversion(cls).convert() + self.__dict[attr] = serialize + else: + cls = self.__dict[attr] + serialize = SmartJson._DataTypeConversion(cls).next().__dict__ + self.__dict[attr] = serialize + + return self.__dict + + class _JsonConvert: + + self_dumpers = dict() + self_loaders = dict() + + def self_dump(self, obj): + class_name = self.get_class_name(obj) + if class_name in self.self_dumpers: + return self.self_dumpers[class_name](self, obj) + raise TypeError("%r is not JSON serializable" % obj) + + def json_convert(self, obj): + + if isinstance(obj, OrderedDict): + try: + return self.self_dump(obj) + except TypeError: + return {k: self.json_convert(v) for k, v in self.iter_items(obj)} + + # nested dict + elif isinstance(obj, dict): + return {k: self.json_convert(v) for k, v in self.iter_items(obj)} + + # list or tuple + elif isinstance(obj, (list, tuple)): + return list((self.json_convert(v) for v in obj)) + + elif isinstance(obj, (datetime.datetime, datetime.date)): + return str(obj) + + elif isinstance(obj, complex): + return [{'expression': str(obj), 'real': obj.real, 'imag': obj.imag}] + + elif hasattr(obj, "__class__"): + if isinstance(obj, (int, float, bool, str, type(None))): + pass + elif isinstance(obj, complex): + return [{'expression': str(obj), 'real': obj.real, 'imag': obj.imag}] + else: + return SmartJson._DataTypeConversion(obj).next().__dict__ + + # single object + try: + return self.self_dump(obj) + except TypeError: + return obj + + def iter_items(self, d, **kw): + return iter(d.items(**kw)) + + def get_class_name(self, obj): + return obj.__class__.__module__ + "." + obj.__class__.__name__ diff --git a/packs/devs/kit_code/land/lang/__init__.py b/packs/devs/kit_code/land/lang/__init__.py new file mode 100644 index 0000000..d112201 --- /dev/null +++ b/packs/devs/kit_code/land/lang/__init__.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +from . import pattern + +from . import timeout + +import json +import html + +from ...land.trace import Tracer + +_logger = Tracer + + +def is_primitive(x): + if isinstance(x, (int, float, bool, str, type(None))): + return True + else: + return False + + +def model_to_print_data(obj): + fields_dict = {} + for key in obj.fields_get(): + val = obj[key] + if (is_primitive(val)): + val = str(val) + else: + try: + # val = model_to_print_data(val) + val = str(val) + except Exception as ex: + print(ex) + val = str(val) + finally: + pass + + fields_dict[key] = val + + return fields_dict + + +def to_str(val): + try: + return json.dumps(val) + except Exception as ex: + print(ex) + pass + finally: + return str(val) + + + +def _format_template_code(value): + _logger.debug("👀->👽 ._format_template_code,template_code:{},", value) + if not value: + return "" + if value: + buf = 'Filter' + buf = value + buf = buf.replace("\"", "&?quote") # avoid json dump issue in js + + buf = html.escape(buf, True) + + return buf + + +""" + UIView +""" + + +def view_as_dict(view,deep=True): + _logger.debug("👀->👽 try view_as_dict, view:{},", view) + if not view or not view.id: + return None + + result = { + "id": view.id, + "name": html.escape(view.name,True), + "model": view.model, + "key": view.key, + "type": view.type, + "mode": view.mode, + "arch_fs": view.arch_fs, + # "arch_db": view.arch_db, # should use this one ? + } + if not deep: + return result + + # if view.arch_prev : + # result["arch_prev_formatted"] = _format_template_code(view.arch_prev) + # if view.arch : + # result["arch"] = _format_template_code(view.arch) + + # step + parent_dict = None + if not view.inherit_id.id is None: + parent_dict = view_as_dict(view.inherit_id,deep=False) + if not parent_dict is None: + result["parent"] = parent_dict + # step + children_views = None + if not view.inherit_children_ids is None: + children_views = [] + for x in view.inherit_children_ids: + item = view_as_dict(x,deep=False) + children_views.append(item) + + + if not children_views is None: + # import time + # mock_item = { + # "id": "111-" + str(time.time()), + # "name": "xxx", + # } + # children_views.append(mock_item) + result["children_views"] = children_views + + _logger.debug("👀->👽 view_as_dict, result:{},", result) + return result diff --git a/packs/devs/kit_code/land/lang/pattern.py b/packs/devs/kit_code/land/lang/pattern.py new file mode 100644 index 0000000..1f80a93 --- /dev/null +++ b/packs/devs/kit_code/land/lang/pattern.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +def singleton(cls): + _instance = {} + + def inner(): + if cls not in _instance: + _instance[cls] = cls() + return _instance[cls] + + return inner diff --git a/packs/devs/kit_code/land/lang/timeout.py b/packs/devs/kit_code/land/lang/timeout.py new file mode 100644 index 0000000..d888d52 --- /dev/null +++ b/packs/devs/kit_code/land/lang/timeout.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +""" + clone from https://github.com/johejo/inputimeout +""" +import sys + +DEFAULT_TIMEOUT = 30.0 +INTERVAL = 0.05 + +SP = ' ' +CR = '\r' +LF = '\n' +CRLF = CR + LF + + +class TimeoutOccurred(Exception): + pass + + +def echo(string): + sys.stdout.write(string) + sys.stdout.flush() + + +def posix_inputimeout(prompt='', timeout=DEFAULT_TIMEOUT): + echo(prompt) + sel = selectors.DefaultSelector() + sel.register(sys.stdin, selectors.EVENT_READ) + events = sel.select(timeout) + + if events: + key, _ = events[0] + return key.fileobj.readline().rstrip(LF) + else: + echo(LF) + termios.tcflush(sys.stdin, termios.TCIFLUSH) + raise TimeoutOccurred + + +def win_inputimeout(prompt='', timeout=DEFAULT_TIMEOUT): + echo(prompt) + begin = time.monotonic() + end = begin + timeout + line = '' + + while time.monotonic() < end: + if msvcrt.kbhit(): + c = msvcrt.getwche() + if c in (CR, LF): + echo(CRLF) + return line + if c == '\003': + raise KeyboardInterrupt + if c == '\b': + line = line[:-1] + cover = SP * len(prompt + line + SP) + echo(''.join([CR, cover, CR, prompt, line])) + else: + line += c + time.sleep(INTERVAL) + + echo(CRLF) + raise TimeoutOccurred + + +inputimeout = None + +try: + import msvcrt + +except ImportError: + import selectors + import termios + + inputimeout = posix_inputimeout + +else: + import time + + inputimeout = win_inputimeout \ No newline at end of file diff --git a/packs/devs/kit_code/land/trace/__init__.py b/packs/devs/kit_code/land/trace/__init__.py new file mode 100644 index 0000000..6c18bc9 --- /dev/null +++ b/packs/devs/kit_code/land/trace/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from .tracer import Tracer diff --git a/packs/devs/kit_code/land/trace/tracer.py b/packs/devs/kit_code/land/trace/tracer.py new file mode 100644 index 0000000..6ced438 --- /dev/null +++ b/packs/devs/kit_code/land/trace/tracer.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# ixkit - odoo spy +# +#@purpose : logger as Tracer with step feature +#@author : Artificer@ixkit.com +#@date : 2024-2-15 +#@version : 1.0.0 +# +#----------------------------------------------------------------------- +import sys +import inspect + +from ...land import jsons + +from ..lang.timeout import inputimeout + +from string import Template +from string import Formatter as StrFormatter + +def _wait_input(prompt,timeout): + result = None + try: + result = inputimeout(prompt=prompt, timeout=timeout) + except Exception as ex: + pass + finally: + return result + + +def _smart_json(val): + return jsons.SmartJson(val) + +def to_dic(obj): + dic = {} + for fieldkey in dir(obj): + fieldvaule = getattr(obj, fieldkey) + if not fieldkey.startswith("__") and not callable(fieldvaule) and not fieldkey.startswith("_"): + dic[fieldkey] = fieldvaule + return dic + +#--------------------------------------------------------------------------- +# Tracer classes and functions +#--------------------------------------------------------------------------- + + +class Tracer(): + trace_level = True # True|False, 'None','DEBUG'|'INFO'... + + def __init__(self ): + pass + + @staticmethod + def set_trace_level(level=None): + Tracer.trace_level = level + + @staticmethod + def get_trace_level(): + if Tracer.trace_level is None: + return None + if Tracer.trace_level == "None": + return None + if Tracer.trace_level == False or Tracer.trace_level == "False": + return None + return Tracer.trace_level + + @staticmethod + def smart_json(val): + return to_dic(val) + """ + # named + msg = "xxxx {age} xxxx {name}".format(age=18, name="hangman") + print(msg) # xxxx 18 xxxx hangman + + # order + msg = "xxxx {1} xxx{0}".format(value1,value2) + print(msg) # xxxx [9, 0] xxx(7, 8) + + # mix + msg = "xxxx {} XXX {name} xxx {}".format(value2,value1,name="s4") + print(msg) # xxxx [9, 0] XXX s4 xxx (7, 8) + """ + @staticmethod + def debug( msg, *args, sender=None, step=None, wait_second=-1, **kwargs ): + trace_level = Tracer.get_trace_level() + if trace_level is None: + return + + line = "line?" + if sender is None: + stack_frame = inspect.stack()[1] + stack_module = inspect.getmodule(stack_frame[0]) + sender = stack_module.__name__ + line = inspect.getlineno(stack_frame[0]) + Tracer._log(sender,line, msg, *args, **kwargs) + + if step == True : + prompt = "💡" + msg +' 👉 press any key to continue...' + if wait_second > 0 : + # wait limit time for input using inputimeout() function + prompt = prompt + " wait second:" + str(wait_second) + "\r\n" + input_val = _wait_input(prompt=prompt, timeout=wait_second) + if not input_val is None: + print(input_val) + else: + prompt = prompt + "\r\n" + input_val = str(input(prompt)) + print(input_val) + + + @staticmethod + def _log(sender,line, msg, /, *args, **kwargs): + prefix = "{},{}:".format(sender,line) + buf = msg.format(*args, **kwargs) + buf = prefix + buf + print("🔍->" + buf) + + \ No newline at end of file diff --git a/packs/devs/kit_code/land/web/rpc_result.py b/packs/devs/kit_code/land/web/rpc_result.py new file mode 100644 index 0000000..4e4322e --- /dev/null +++ b/packs/devs/kit_code/land/web/rpc_result.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +import time + +def as_result(success, data=None): + result = { + "success": success, + "ts": int(time.time()) + } + if not data is None: + result['data'] = data + return result + +def success(data = None): + return as_result(True,data) + +def error(data = None): + return as_result(False,data) \ No newline at end of file diff --git a/packs/devs/kit_code/models/__init__.py b/packs/devs/kit_code/models/__init__.py new file mode 100644 index 0000000..e533970 --- /dev/null +++ b/packs/devs/kit_code/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import ir_http +from . import code_server +from . import notifyer \ No newline at end of file diff --git a/packs/devs/kit_code/models/code_server.py b/packs/devs/kit_code/models/code_server.py new file mode 100644 index 0000000..4e2ce9b --- /dev/null +++ b/packs/devs/kit_code/models/code_server.py @@ -0,0 +1,431 @@ +#-*- coding: utf-8 -*- +import logging +import threading +import time +import json +import os +import re +import sys +import html +from array import array +import requests +import odoo +from odoo import models, fields, api, tools +from odoo.modules.module import get_manifest +from os.path import join as opj + +from datetime import datetime + +from .server_master import ServerMaster +from ..land.jsons import json_dumps +from ..land.lang.pattern import singleton +from ..land.web import rpc_result +from ..shell.sheller import async_run +from ..__support__ import get_module_path + +__author__ = odoo.release.author +__version__ = odoo.release.version + +_logger = logging.getLogger(__name__) + +def simple_manifest(manifest): + result ={} + try: + + keys = ["name","version","description","summary","author","website","license","category","depends","application"] + #keys = ["name","version","category","description"] + for x in keys: + val = manifest.get(x) + if not val is None: + result[x] = val + # buf = json_dumps(result) + # buf = html.escape(buf) + # _logger.debug(" simple_manifest, buf:%s",buf) + # return buf + except Exception as ex: + _logger.error("Failed simple_manifest, ex:%s",ex) + pass + return result + +def concise_folder(folder): + result ={ + 'name': folder["name"], + "path": folder["path"], + } + data = folder.get('data') + if not data is None: + result["data"] = data + return result + +@singleton +class CodeServerCahce(): + + _cacheSet = {} + + def get(self, key): + return self._cacheSet.get(key) + + def set(self, key, val): + self._cacheSet[key] = val + +# ready to use +theCache = CodeServerCahce() + +# +class CodeServer(models.Model): + _name = 'kit.code.server' + _description = 'kit code server' + + name = fields.Char(default="",required=True) + + ts = fields.Datetime( string="Time",required=True) + message = fields.Text( string="Message") + message_type = fields.Char() + + _order = "ts desc" + + + def on_echo(self,**kw): + message = kw['message'] + if message is None or message == "": + return + + _logger.debug("on_echo:%s",message) + + value ={ + "message":json.dumps(message), + "ts": datetime.now(), + } + # if self.env.user._is_public(): + #self.ensure_one() + #self.env['kit.code.server'].sudo().create(value) + with self.pool.cursor() as new_cr: + self = self.with_env(self.env(cr=new_cr)) + self.env['kit.code.server'].sudo().create(value) + self.env["bus.bus"].push( value) + + + def write(self, vals): + write_res = super(CodeServer, self).write(vals) + return write_res + + def _echo_server_op(self,op): + if "start" == op: + op = "🚀 Start" + if "stop" == op: + op = "❌ Stop" + msg = {"stdout":[op + " Code Server..."]} + self.on_echo(message = msg) + + def _get_server_handler(self): + return theCache.get('server_handler') + + def _set_server_handler(self,val): + theCache.set('server_handler',val) + + # op = 'start'|'stop' + @api.model + def ctrl_server(self,op): + #raise Warning(('Test action!')) + args = { + "on_echo": self.on_echo + } + + result = rpc_result.error( {'msg':"Invalidate operation:{}".format(op)}) + + success = None + while True: + if "start" == op: + server_handler = ServerMaster.startServer(args) + success = not server_handler is None + if success: + self._set_server_handler(server_handler) + #self._start_submit_space_info() + result = rpc_result.success(server_handler) + break + if "stop" == op: + success = ServerMaster.stopServer(args) + self._set_server_handler(None) + result = rpc_result.success() + break + break + + if success is None: + result = rpc_result.error( {'msg':"Invalidate operation:{}".format(op)}) + return result + + self._echo_server_op(op); + return result + + + def _start_submit_space_info(self): + thread = threading.Thread(target=self._try_submit_odoo_space_info) + thread.start() + #thread.join(); + return thread + + def _try_submit_odoo_space_info(self, timeout=30): + time.sleep(timeout) # second + go = True + wait_count = 0 + wait_unit = 3 + while(go): + _logger.debug("_try_submit_odoo_space_info,loop:%s", wait_count) + server_running = self._server_is_running() + if server_running: + go = False + break + # not running yet then wait it + time.sleep(wait_unit) # second + wait_count = wait_count + 1 + if wait_count * wait_unit > timeout : + go = False + + server_running = self._server_is_running() + _logger.debug("_try_submit_odoo_space_info,server_running:%s", server_running) + if server_running : + self.push_space_to_code_server() + else: + _logger.warn("❌ server is NOT running ") + + @api.model + def fetch_server_info(self): + server_handler = self._get_server_handler() + if server_handler is None: + return rpc_result.error() + + servers = ServerMaster.getServers() + if len(servers)<1: + return rpc_result.error() + + return rpc_result.success(server_handler) + + + + def _server_is_running(self): + servers = ServerMaster.getServers() + if servers is None or len(servers)<1: + return False + + server_info = ServerMaster.get_server_info() + if server_info is None: + return False + if server_info.get('success'): + return True + return False + + """ + @copy: from server.py + @purpose: output odoo runtime environment information + """ + @api.model + def report_configuration(self): + """ Log the server version and some configuration values. + + This function assumes the configuration has been initialized. + """ + result = {} + result["version"] = __version__ + result["release"] = vars(odoo.release) + result["addons"] = self.get_addons_modules() #odoo.addons.__path__ + + config = tools.config + _logger.debug("Odoo version %s", __version__) + if os.path.isfile(config.rcfile): + _logger.debug("Using configuration file at " + config.rcfile) + _logger.info('addons paths: %s', odoo.addons.__path__) + if config.get('upgrade_path'): + _logger.debug('upgrade path: %s', config['upgrade_path']) + host = config['db_host'] or os.environ.get('PGHOST', 'default') + port = config['db_port'] or os.environ.get('PGPORT', 'default') + user = config['db_user'] or os.environ.get('PGUSER', 'default') + _logger.debug('database: %s@%s:%s', user, host, port) + + #json_config = json.dumps(config.options, indent = 4) + result["config"] = config.options + + + return result + + def get_addons_modules(self): + #app=odoo.http.root + mod2path = {} + for addons_path in odoo.addons.__path__: + addons_item = {} + for module in os.listdir(addons_path): + manifest = get_manifest(module) + module_path = opj(addons_path, module) + if (manifest + #and (manifest['installable']) + and os.path.isdir(module_path)): + addons_item[module] = module_path + + mod2path[addons_path] = addons_item + return mod2path + + + def addons_to_folders(self): + #app=odoo.http.root + folders = [] + for addons_path in odoo.addons.__path__: + folder = { + "name":addons_path, + "path": addons_path + } + items = [] + for module in os.listdir(addons_path): + manifest = get_manifest(module) + module_path = opj(addons_path, module) + if (manifest + and (manifest['installable']) + and os.path.isdir(module_path)): + + subFolder ={ + "name" : module, + "path" : module_path, + "data" : simple_manifest(manifest) + } + items.append(subFolder) + + if len(items)>0: + folder["items"] = items + + folders.append(folder) + return folders + + """ + let params = { + folders:[ + { + name:"group-name" + ts, + path: "group-path" + ts, + data: {}, + items:[ { + name:"item-name" + ts, + path: 'item-path' + ts, + data:{} + }] + } + ] + } + """ + @api.model + def push_space_to_code_server(self): + _logger.debug("try push_space_to_code_server") + folders = self.addons_to_folders() + for x in folders: + self.post_addons_folder(x) + + + # split one big post task to mullti-tasks avoid Big Data issue on Node server + def post_addons_folder(self,folder): + simple_folder = concise_folder(folder) + + folder_rep = self.call_post_folders([simple_folder]) + if not folder_rep.get('success'): + return False + one = folder_rep.get("data")[0] + folder_id = one.get("id") + + items = folder.get('items') + if items is None: + return + + + count = 0 + folders =[] + for item in items: + simple_folder = concise_folder(item) + simple_folder['parentId'] = folder_id + folders.append(simple_folder) + count = count + 1 + if count >30: + self.call_post_folders(folders) + # reset + count = 0 + folders =[] + if len(folders) > 0: + self.call_post_folders(folders) + + def call_post_folders(self,folders): + return ServerMaster.post_folders(folders) + + def _get_cached_addons(self): + addons_modules = theCache.get("addons_modules") + if addons_modules is None: + addons_modules = self.get_addons_modules() + theCache.set("addons_modules",addons_modules) + return addons_modules + + + + """ + const intent = { + "module" : "kit_code", + "file" : "__manifest__.py", + "lineNo": 1 + } + """ + @api.model + def get_home_link(self): + dir = get_module_path() + server = ServerMaster._server + end_point = server.get("url") + file = "__manifest__.py" + url = "{}/folder?dir={}&file={}".format(end_point,dir,file) + result = { + 'navigateTo': url + } + return result + """ + name:web.NavBar.DropdownItem + class:sub template + type:owl + url:/web/static/src/webclient/navbar/navbar.xml + file:/odoo-17/odoo/addons/web/static/src/webclient/navbar/navbar.xml + """ + @api.model + def get_editable_file(self,intent): + if (intent is None): + intent = { + "name":"CodeDashboard", + "url" : "", + "file" : "full path", + "lineNo": 99 + } + file_name = intent["file"] + addons_modules = self._get_cached_addons() + module_dir = None + module_name = None + + for addons in addons_modules: + if file_name.startswith(addons + os.sep): + modules = addons_modules[addons] + for module in modules: + module_path = modules[module] + if file_name.startswith(module_path + os.sep): + module_dir = module_path + module_name = module + break + if module_dir is None: + return {"status":False,"error":"No matched module!"} + + module ={ + "name":module_name, + "path":module_dir + } + + short_file_name = file_name.replace(module_dir + os.sep,"") + args ={ + "dir":module_dir, + "file": short_file_name + } + + navigate_to_url = ServerMaster.get_open_file_url(args) + result ={ + "module":module, + "file":short_file_name, + "navigateTo": navigate_to_url + } + + return result \ No newline at end of file diff --git a/packs/devs/kit_code/models/ir_http.py b/packs/devs/kit_code/models/ir_http.py new file mode 100644 index 0000000..a4be891 --- /dev/null +++ b/packs/devs/kit_code/models/ir_http.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from odoo import models +from odoo.http import request + + +class Http(models.AbstractModel): + _inherit = 'ir.http' + + @classmethod + def _pre_dispatch(cls, rule, args): + super()._pre_dispatch(rule, args) + url = request.httprequest.url + for key in args: + pass diff --git a/packs/devs/kit_code/models/notifyer.py b/packs/devs/kit_code/models/notifyer.py new file mode 100644 index 0000000..63a4d74 --- /dev/null +++ b/packs/devs/kit_code/models/notifyer.py @@ -0,0 +1,19 @@ +#-*- coding: utf-8 -*- +import logging +from odoo import models, fields, api +import threading +import time +import json + + +class Notifyer(models.AbstractModel): + _inherit = "bus.bus" + + @api.model + def push(self,data, channel="eventChannel"): + message = { + "data": data, + "channel": channel + } + self._sendone(channel, "notification", message) + \ No newline at end of file diff --git a/packs/devs/kit_code/models/server_master.py b/packs/devs/kit_code/models/server_master.py new file mode 100644 index 0000000..82c0c97 --- /dev/null +++ b/packs/devs/kit_code/models/server_master.py @@ -0,0 +1,242 @@ +# -*- coding: utf-8 -*- +# 1 : imports of python lib +from pathlib import Path +import json +import logging +import urllib +import os +import subprocess +import base64 +import sys +import requests +# 2 : imports of odoo +from odoo import _, api, exceptions, fields, http, models, tools + +from ..shell.sheller import async_run +from ..land.jsons import json_dumps + +# 4 : variable declarations +_logger = logging.getLogger(__name__) + +def log_it(msg): + _logger.debug(str(msg)) + + +def get_current_path(): + #dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_path = Path(__file__).resolve().parent + return dir_path + +def get_value(data, key,default=None): + result = data.get(key) + if result is None: + result = default + return result + +def contains_error(buf): + if "\"error\":" in buf: + return True + return False +def contains_success(buf): + if "'result': True" in buf: + return True + return False + +# duplicate +def _quote_(value): + #return "\"{}\"".format(value) + return "\\\"{}\\\"".format(value) + +def _key_str_value(key,value): + result = _quote_(key) +":" + _quote_(value) + return result + +def _key_obj_value(key,value): + + result = _quote_(key) +":" + "{ }" if value is None else value + return result + +# sys.platform => 'darwin', 'linux', 'win32' +def _get_server_exe_name(): + bin_app_fmt = "code-server-{}" + os = sys.platform + if 'win32' == os : + return bin_app_fmt.format(".exe") + if 'darwin' == os : + return bin_app_fmt.format("macos") + return bin_app_fmt.format("linux") + +def _get_server_path(): + path = get_current_path() + result = str(path.resolve().parent) + "/vendors/code-server/" + _get_server_exe_name() + return result + +def _get_module_path(): + path = get_current_path() + result = str(path.resolve().parent) + return result + +def on_shell_echo(process, msg): + log_it("------------- on_shell_echo -------------") + + log_it(msg) + ServerMaster.put_proc(str(process.pid), process) + + +# Class +class ServerMaster(): + + proc_set ={} + + args = None + + _server={} + + @staticmethod + def put_proc(key,proc): + cached_proc = ServerMaster.proc_set.get(key) + if cached_proc is None: + ServerMaster.proc_set[key] = proc + + @staticmethod + def get_proc(key): + cached_proc = ServerMaster.proc_set.get(key) + return cached_proc + + @staticmethod + def get_proc_set(): + return ServerMaster.proc_set + + @staticmethod + def getTargetServerPath(): + return _get_server_path() + + """ + """ + @staticmethod + def startServer(args): + ServerMaster.args = args; + port = get_value(args,"port",3030) + doc_path = _get_module_path() + server = ServerMaster.getTargetServerPath() + cmd = "{} start --port={} --space={} --slient=true --focus=README.rst,__manifest__.py ".format(server,port, doc_path) + log_it(cmd) + # return json string that print out by RPC server + async_run(cmd,ServerMaster.on_shell_echo) + return ServerMaster.warp_server(port) + + # on_echo: procss, message ={stdout,stderr,exitcode} + @staticmethod + def on_shell_echo(process, message): + log_it("------------- on_shell_echo -------------") + + log_it(message) + ServerMaster.put_proc(str(process.pid), process) + args = ServerMaster.args + + if args is None or args.get('on_echo') is None: + return True + + on_echo = args.get('on_echo') + on_echo(message=message) + return True + + + + @staticmethod + def getServers(): + proc_set = ServerMaster.get_proc_set() + result =[] + if len(proc_set) <= 0: + return result + + + for key in list(proc_set.keys()): + try: + x = proc_set[key] + exit_code = x.poll() + if not exit_code is None: + del proc_set[key] + continue + + item = { + "pid": x.pid, + "args": x.args, + } + result.append(item) + except Exception as ex: + pass + + return result + + @staticmethod + def stopServer(args): + proc_set = ServerMaster.get_proc_set() + #Popen.terminate() + for x in proc_set.values(): + try: + x.terminate() + except Exception as ex: + pass + + return True + + #----------------- biz logic ----------------- + @staticmethod + def warp_server(port): + result = { + "url": "http://localhost:{}".format(port) + } + ServerMaster._server = result; + return result + + @staticmethod + def get_open_file_url(args): + dir = args.get("dir") + file = args.get("file") + server = ServerMaster._server + end_point = server.get("url") + url = "{}/folder?dir={}&file={}".format(end_point,dir,file) + return url + + @staticmethod + def get_server_info(): + server = ServerMaster._server + headers = {"content-type":"application/json"} + #url = "http://localhost:3030/api/echo" + end_point = server.get("url") + url = "{}/api/info".format(end_point) + _logger.debug("get_server_info, url:%s",url) + response = requests.get(url, headers=headers) + json_response = response.json() + + buf = json_dumps(json_response) + _logger.debug('server_is_running response: %s',buf) + + return json_response + + @staticmethod + def post_folders(folders): + try: + return ServerMaster._post_folders(folders) + except Exception as e: + _logger.error('failed post folders,error:%s', e) + return {} + @staticmethod + def _post_folders(folders): + payload ={ + "folders":folders + } + data_buf = json_dumps(payload) + headers = {"content-type":"application/json"} + #url = "http://localhost:3030/api/folders" + end_point = ServerMaster._server.get("url") + url = "{}/api/folders".format(end_point) + response = requests.post(url, data=data_buf,headers=headers) + if response is None: + return {} + json_response = response.json() + + buf = json_dumps(json_response) + _logger.debug('call_post_folders response: %s',buf) + return json_response \ No newline at end of file diff --git a/packs/devs/kit_code/security/ir.model.access.csv b/packs/devs/kit_code/security/ir.model.access.csv new file mode 100644 index 0000000..a8a17ba --- /dev/null +++ b/packs/devs/kit_code/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_kit_code_server,kit_code_server,model_kit_code_server,base.group_user,1,1,1,1 +bot_kit_code_server,bot_kit_code_server,model_kit_code_server,base.group_public,1,1,1,1 +sys_kit_code_server,sys_kit_code_server,model_kit_code_server,base.group_system,1,1,1,1 diff --git a/packs/devs/kit_code/shell/sheller.py b/packs/devs/kit_code/shell/sheller.py new file mode 100644 index 0000000..4eb1cf9 --- /dev/null +++ b/packs/devs/kit_code/shell/sheller.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +import subprocess +from time import sleep +import threading + + +# command: ["python", "timer.py", "5"] +# on_echo: procss, message ={stdout,stderr,exitCode} +""" + procss is Popen instance + + Popen.terminate() + Stop the child. On POSIX OSs the method sends SIGTERM to the child. On Windows the Win32 API function TerminateProcess() is called to stop the child. + + Popen.kill() + +""" +def run(command,on_echo): + if isinstance(command, str): + command = command.split() + with subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + + ) as process: + + go = not on_echo is None + + def poll_and_read(): + # print(f"Output from poll: {process.poll()}") + # print(f"Output from stdout: {process.stdout.read1().decode('utf-8')}") + + exit_code = process.poll() + + if not exit_code is None: + on_echo(process,{'process exitcode':exit_code}) + return False + + #stdout, stderr = process.communicate() + + stdout = process.stdout.readline() + #stderr = process.stderr.readline() + # stdout = process.stdout.read1().decode('utf-8') + # stderr = process.stderr.read1().decode('utf-8') + stderr = "" + message ={} + if not stdout == '': + # Split output into lines. + stdout_buf = [s.rstrip("\n\r") for s in str(stdout, "utf8").splitlines()] + message['stdout']=stdout_buf + + if not stderr == '': + #stderr_buf = [s.rstrip("\n\r") for s in str(stderr, "utf8").splitlines()] + message['stderr']=stderr + + if len(message) == 0: + + return True + + return on_echo(process,message) + + + while go: + go = poll_and_read() + sleep(3) + +def async_run(command,on_echo): + thread = threading.Thread(target=run,args=(command,on_echo)) + thread.start() + return thread \ No newline at end of file diff --git a/packs/devs/kit_code/static/description/assets/code-browse.gif b/packs/devs/kit_code/static/description/assets/code-browse.gif new file mode 100644 index 0000000..24c69f0 Binary files /dev/null and b/packs/devs/kit_code/static/description/assets/code-browse.gif differ diff --git a/packs/devs/kit_code/static/description/assets/screenshots/browse.png b/packs/devs/kit_code/static/description/assets/screenshots/browse.png new file mode 100644 index 0000000..4166352 Binary files /dev/null and b/packs/devs/kit_code/static/description/assets/screenshots/browse.png differ diff --git a/packs/devs/kit_code/static/description/assets/screenshots/editor.png b/packs/devs/kit_code/static/description/assets/screenshots/editor.png new file mode 100644 index 0000000..5aff646 Binary files /dev/null and b/packs/devs/kit_code/static/description/assets/screenshots/editor.png differ diff --git a/packs/devs/kit_code/static/description/assets/spy-code.gif b/packs/devs/kit_code/static/description/assets/spy-code.gif new file mode 100644 index 0000000..9e452a7 Binary files /dev/null and b/packs/devs/kit_code/static/description/assets/spy-code.gif differ diff --git a/packs/devs/kit_code/static/description/assets/team-logo.png b/packs/devs/kit_code/static/description/assets/team-logo.png new file mode 100644 index 0000000..28fd072 Binary files /dev/null and b/packs/devs/kit_code/static/description/assets/team-logo.png differ diff --git a/packs/devs/kit_code/static/description/banner.png b/packs/devs/kit_code/static/description/banner.png new file mode 100644 index 0000000..5800bab Binary files /dev/null and b/packs/devs/kit_code/static/description/banner.png differ diff --git a/packs/devs/kit_code/static/description/icon.png b/packs/devs/kit_code/static/description/icon.png new file mode 100644 index 0000000..2cc88f7 Binary files /dev/null and b/packs/devs/kit_code/static/description/icon.png differ diff --git a/packs/devs/kit_code/static/description/index.html b/packs/devs/kit_code/static/description/index.html new file mode 100644 index 0000000..37552fb --- /dev/null +++ b/packs/devs/kit_code/static/description/index.html @@ -0,0 +1,262 @@ +
+
+
+

+ Odoo Code +

+
Third-Party
+ +

Help you build Odoo application code online!

+
+
+ +
+ +
+
+
+

Features

+
+
    +
  • View & Write Odoo code through web Code Editor in the same Odoo runtime environment; +


  • +
  • Hot reload, quickly and easily experiment, build UIs, add features, fix bugs & more; +


  • +
  • Locate the Templates Script source code quickly, save time & speed up development process; +


  • +
  • Write Odoo code anytime anywhere, free mind & happy coding! +
  • + +
+
+ +
+
+
+ + + + + +
+

+ Browse Modules: +

+
+
+
+ +
+ +
+
+
+ +
+

+ Launch Code Server, browse Odoo modules source code online. +

+
+ + +
+

+ Spy Element & Edit: +

+
+
+
+ +
+ +
+
+
+ +
+

+ Insight into the Odoo web page structure, super easy locate the feature source code line, The Spy Element feature should install the Odoo Spy module. +

+
+ +

Screenshots

+ +
+

+ Home Space: +

+
+ +
+
+ +
+ +
+
+
+ +
+

+ Browse Modules: +

+
+ +
+
+ +
+ +
+
+
+ +
+

+ All Odoo modules can be load in online code editor, write code, hot-reload frontend UIs, easily and quickly! +

+
+ + +

Usage

+ +
+
+

+     Start Code Server from the Code Console page of this module, once start success, browser auto navigate to new window that address is : + http://localhost:3030/ + + ; The core features of code editor server was provide by the open source Code Server + +

+ +
+ + + +
+

+ Additional Step: Manual install the Code Server +

+
+ +
    +
  • +

    + Download the Code Server program from the Code Server repository + or clone the source code and build the Code Sever binary package. +

    +
  • + +
  • +

    + Put the Code Sever binary package into the moudule directory './kit_code/vendors/', +

    +

    + eg:
    + mac platform : + it should be './kit_code/venders/code_server/code-server-macos' +
    + windows: + it should be './kit_code/venders/code_server/code-server-win.exe' +

    + + +
  • +
+ + +
+
+
+ +
+
+

+ Notice: +

+

+ This module should only use for development phase! Production Environment is not recommend! +

+
+ +
+ +
+

+ Enjoy & Happy Coding!   +

+
+ +

Support

+
+ +
+ + +
+

+ + by ixkit team + + +

+ +
+
diff --git a/packs/devs/kit_code/static/image/server-black.svg b/packs/devs/kit_code/static/image/server-black.svg new file mode 100644 index 0000000..cf34161 --- /dev/null +++ b/packs/devs/kit_code/static/image/server-black.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packs/devs/kit_code/static/image/server-running.svg b/packs/devs/kit_code/static/image/server-running.svg new file mode 100644 index 0000000..809cc2f --- /dev/null +++ b/packs/devs/kit_code/static/image/server-running.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packs/devs/kit_code/static/image/server-white.svg b/packs/devs/kit_code/static/image/server-white.svg new file mode 100644 index 0000000..f8bc1cb --- /dev/null +++ b/packs/devs/kit_code/static/image/server-white.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packs/devs/kit_code/static/jslib/jsontable/jsonviewr.js b/packs/devs/kit_code/static/jslib/jsontable/jsonviewr.js new file mode 100644 index 0000000..3f10105 --- /dev/null +++ b/packs/devs/kit_code/static/jslib/jsontable/jsonviewr.js @@ -0,0 +1,43 @@ +/** + * Minified by jsDelivr using Terser v5.19.2. + * Original file: /npm/@textea/json-viewer@3.4.1/dist/browser.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).JsonViewer=t()}(this,(function(){"use strict";function e(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(n){if("default"!==n&&!(n in e)){var r=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,r.get?r:{enumerable:!0,get:function(){return t[n]}})}}))})),Object.freeze(e)}function t(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function n(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function r(e){if(e.__esModule)return e;var t=e.default;if("function"==typeof t){var n=function e(){return this instanceof e?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};n.prototype=t.prototype}else n={};return Object.defineProperty(n,"__esModule",{value:!0}),Object.keys(e).forEach((function(t){var r=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(n,t,r.get?r:{enumerable:!0,get:function(){return e[t]}})})),n}var o={exports:{}},a={},l={exports:{}},i={},u=Symbol.for("react.element"),s=Symbol.for("react.portal"),c=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),p=Symbol.for("react.provider"),m=Symbol.for("react.context"),h=Symbol.for("react.forward_ref"),y=Symbol.for("react.suspense"),g=Symbol.for("react.memo"),v=Symbol.for("react.lazy"),b=Symbol.iterator;var x={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},k=Object.assign,w={};function S(e,t,n){this.props=e,this.context=t,this.refs=w,this.updater=n||x}function C(){}function E(e,t,n){this.props=e,this.context=t,this.refs=w,this.updater=n||x}S.prototype.isReactComponent={},S.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},S.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},C.prototype=S.prototype;var _=E.prototype=new C;_.constructor=E,k(_,S.prototype),_.isPureReactComponent=!0;var P=Array.isArray,z=Object.prototype.hasOwnProperty,T={current:null},O={key:!0,ref:!0,__self:!0,__source:!0};function j(e,t,n){var r,o={},a=null,l=null;if(null!=t)for(r in void 0!==t.ref&&(l=t.ref),void 0!==t.key&&(a=""+t.key),t)z.call(t,r)&&!O.hasOwnProperty(r)&&(o[r]=t[r]);var i=arguments.length-2;if(1===i)o.children=n;else if(1>>1,a=e[r];if(!(0>>1;ro(u,n))so(c,u)?(e[r]=c,e[s]=n,r=s):(e[r]=u,e[i]=n,r=i);else{if(!(so(c,n)))break e;e[r]=c,e[s]=n,r=s}}}return t}function o(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if("object"==typeof performance&&"function"==typeof performance.now){var a=performance;e.unstable_now=function(){return a.now()}}else{var l=Date,i=l.now();e.unstable_now=function(){return l.now()-i}}var u=[],s=[],c=1,f=null,d=3,p=!1,m=!1,h=!1,y="function"==typeof setTimeout?setTimeout:null,g="function"==typeof clearTimeout?clearTimeout:null,v="undefined"!=typeof setImmediate?setImmediate:null;function b(e){for(var o=n(s);null!==o;){if(null===o.callback)r(s);else{if(!(o.startTime<=e))break;r(s),o.sortIndex=o.expirationTime,t(u,o)}o=n(s)}}function x(e){if(h=!1,b(e),!m)if(null!==n(u))m=!0,N(k);else{var t=n(s);null!==t&&M(x,t.startTime-e)}}function k(t,o){m=!1,h&&(h=!1,g(E),E=-1),p=!0;var a=d;try{for(b(o),f=n(u);null!==f&&(!(f.expirationTime>o)||t&&!z());){var l=f.callback;if("function"==typeof l){f.callback=null,d=f.priorityLevel;var i=l(f.expirationTime<=o);o=e.unstable_now(),"function"==typeof i?f.callback=i:f===n(u)&&r(u),b(o)}else r(u);f=n(u)}if(null!==f)var c=!0;else{var y=n(s);null!==y&&M(x,y.startTime-o),c=!1}return c}finally{f=null,d=a,p=!1}}"undefined"!=typeof navigator&&void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);var w,S=!1,C=null,E=-1,_=5,P=-1;function z(){return!(e.unstable_now()-P<_)}function T(){if(null!==C){var t=e.unstable_now();P=t;var n=!0;try{n=C(!0,t)}finally{n?w():(S=!1,C=null)}}else S=!1}if("function"==typeof v)w=function(){v(T)};else if("undefined"!=typeof MessageChannel){var O=new MessageChannel,j=O.port2;O.port1.onmessage=T,w=function(){j.postMessage(null)}}else w=function(){y(T,0)};function N(e){C=e,S||(S=!0,w())}function M(t,n){E=y((function(){t(e.unstable_now())}),n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_continueExecution=function(){m||p||(m=!0,N(k))},e.unstable_forceFrameRate=function(e){0>e||125l?(r.sortIndex=a,t(s,r),null===n(u)&&r===n(s)&&(h?(g(E),E=-1):h=!0,M(x,a-l))):(r.sortIndex=i,t(u,r),m||p||(m=!0,N(k))),r},e.unstable_shouldYield=z,e.unstable_wrapCallback=function(e){var t=d;return function(){var n=d;d=t;try{return e.apply(this,arguments)}finally{d=n}}}}(te),ee.exports=te;var ne=ee.exports,re=V,oe=ne; +/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */function ae(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n