[IMPROVED] Agregar packs devs al ambiente de desarrollo odoo
2
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
||||||
!bin/
|
!bin/
|
||||||
!bin/config/
|
!bin/config/
|
||||||
!packs
|
!packs
|
||||||
|
!packs/devs/
|
||||||
|
|
||||||
# Permitir el seguimiento de archivos específicos
|
# Permitir el seguimiento de archivos específicos
|
||||||
# !archivo1.txt
|
# !archivo1.txt
|
||||||
|
@ -20,3 +21,4 @@
|
||||||
# !carpeta1/*.txt
|
# !carpeta1/*.txt
|
||||||
!bin/*
|
!bin/*
|
||||||
!bin/config/*
|
!bin/config/*
|
||||||
|
!packs/devs/**
|
||||||
|
|
41
packs/devs/kit_code/README.rst
Normal file
|
@ -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: `<static/description/index.html>`
|
||||||
|
|
||||||
|
Github
|
||||||
|
---------
|
||||||
|
https://www.github.com/ixkit/odookit
|
4
packs/devs/kit_code/__config__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
24
packs/devs/kit_code/__init__.py
Normal file
|
@ -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()
|
71
packs/devs/kit_code/__manifest__.py
Normal file
|
@ -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,
|
||||||
|
}
|
57
packs/devs/kit_code/__support__.py
Normal file
|
@ -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')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
4
packs/devs/kit_code/controllers/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import main
|
||||||
|
from . import intent
|
38
packs/devs/kit_code/controllers/intent.py
Normal file
|
@ -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()
|
180
packs/devs/kit_code/controllers/main.py
Normal file
|
@ -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)
|
||||||
|
|
||||||
|
|
18
packs/devs/kit_code/hooks.py
Normal file
|
@ -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
|
||||||
|
|
||||||
|
|
7
packs/devs/kit_code/land/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import lang
|
||||||
|
from . import trace
|
||||||
|
from . import jsons
|
||||||
|
|
||||||
|
def smart_json(val):
|
||||||
|
jsons.SmartJson(val)
|
8
packs/devs/kit_code/land/config/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
from ..jsons import load_py_json
|
||||||
|
|
||||||
|
def load(path):
|
||||||
|
return load_py_json(path)
|
||||||
|
|
||||||
|
|
50
packs/devs/kit_code/land/jsons/__init__.py
Normal file
|
@ -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
|
368
packs/devs/kit_code/land/jsons/__smart_json__.py
Normal file
|
@ -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__
|
121
packs/devs/kit_code/land/lang/__init__.py
Normal file
|
@ -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 = '<t t-name="web_editor.snippet_options_image_optimization_widgets"><t t-set="filter_label">Filter</t>'
|
||||||
|
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
|
11
packs/devs/kit_code/land/lang/pattern.py
Normal file
|
@ -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
|
81
packs/devs/kit_code/land/lang/timeout.py
Normal file
|
@ -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
|
3
packs/devs/kit_code/land/trace/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from .tracer import Tracer
|
118
packs/devs/kit_code/land/trace/tracer.py
Normal file
|
@ -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)
|
||||||
|
|
||||||
|
|
17
packs/devs/kit_code/land/web/rpc_result.py
Normal file
|
@ -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)
|
5
packs/devs/kit_code/models/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import ir_http
|
||||||
|
from . import code_server
|
||||||
|
from . import notifyer
|
431
packs/devs/kit_code/models/code_server.py
Normal file
|
@ -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
|
15
packs/devs/kit_code/models/ir_http.py
Normal file
|
@ -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
|
19
packs/devs/kit_code/models/notifyer.py
Normal file
|
@ -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)
|
||||||
|
|
242
packs/devs/kit_code/models/server_master.py
Normal file
|
@ -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
|
4
packs/devs/kit_code/security/ir.model.access.csv
Normal file
|
@ -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
|
|
69
packs/devs/kit_code/shell/sheller.py
Normal file
|
@ -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
|
BIN
packs/devs/kit_code/static/description/assets/code-browse.gif
Normal file
After Width: | Height: | Size: 2.5 MiB |
After Width: | Height: | Size: 354 KiB |
After Width: | Height: | Size: 456 KiB |
BIN
packs/devs/kit_code/static/description/assets/spy-code.gif
Normal file
After Width: | Height: | Size: 4.8 MiB |
BIN
packs/devs/kit_code/static/description/assets/team-logo.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
packs/devs/kit_code/static/description/banner.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
packs/devs/kit_code/static/description/icon.png
Normal file
After Width: | Height: | Size: 28 KiB |
262
packs/devs/kit_code/static/description/index.html
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<div class="oe_span12">
|
||||||
|
<h2 class="text-center oe_slogan"
|
||||||
|
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bolder !important; color: hsl(0, 0%, 13%);">
|
||||||
|
Odoo Code
|
||||||
|
</h2>
|
||||||
|
<div class="text-center " style="font-weight: bolder !important; margin-top: -20px; color:#6f6060;" >Third-Party</div>
|
||||||
|
|
||||||
|
<h3 class="oe_slogan" style="text-shadow: 1px 1px 2px rgb(40, 244, 88);color: #cd0909e0;">Help you build Odoo application code online!</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="oe_container">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<div class="oe_span12">
|
||||||
|
<h4 class="oe_slogan"><b>Features</b></h4>
|
||||||
|
<div class="col-lg-12 d-flex justify-content-center align-items-center" style="margin: 2rem 0;font-size: 1.5rem !important; ">
|
||||||
|
<ul >
|
||||||
|
<li class="fa fa-star " style="color: rgb(217, 47, 9);"> <span style="color: #4a0404; margin-left: 6px;">View & Write Odoo code through web Code Editor in the same Odoo runtime environment;</span>
|
||||||
|
</li> <br><br>
|
||||||
|
<li class="fa fa-star" style="color: rgb(217, 47, 9);"> <span style="color: #4a0404; margin-left: 6px;">Hot reload, quickly and easily experiment, build UIs, add features, fix bugs & more;</span>
|
||||||
|
</li> <br><br>
|
||||||
|
<li class="fa fa-star" style="color: rgb(217, 47, 9);"> <span style="color: #4a0404; margin-left: 6px;">Locate the Templates Script source code quickly, save time & speed up development process;</span>
|
||||||
|
</li> <br><br>
|
||||||
|
<li class="fa fa-star" style="color: rgb(217, 47, 9);"> <span style="color: #4a0404; margin-left: 6px;">Write Odoo code anytime anywhere, free mind & happy coding! </span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- <section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="oe_row_img oe_centered" align="center">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot" src="assets/frontend.gif"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section> -->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Browse gif -->
|
||||||
|
<div class=" text-left">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
<font style="font-size: 120%;font-weight: bolder !important;color: #6f6060;">Browse Modules:</font>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
|
||||||
|
<div class="oe_screenshot" align="center">
|
||||||
|
<img style="max-width: 80%" src="assets/code-browse.gif"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="oe_mt32" style="font-size: 120%;">
|
||||||
|
Launch Code Server, browse Odoo modules source code online.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Spy-Code gif -->
|
||||||
|
<div class=" text-left">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
<font style="font-size: 120%;font-weight: bolder !important;color: #6f6060;">Spy Element & Edit:</font>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
|
||||||
|
<div class="oe_screenshot" align="center">
|
||||||
|
<img style="max-width: 80%" src="assets/spy-code.gif"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="oe_mt32" style="font-size: 120%;">
|
||||||
|
Insight into the Odoo web page structure, super easy locate the feature source code line, The Spy Element feature should install the <a href="https://apps.odoo.com/apps/modules/17.0/kit_spy">Odoo Spy</a> module.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="oe_slogan"><b>Screenshots</b></h4>
|
||||||
|
|
||||||
|
<div class=" text-left">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
<font style="font-size: 120%;font-weight: bolder !important;color: #6f6060;">Home Space:</font>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
|
||||||
|
<div class="oe_screenshot" align="center">
|
||||||
|
<img style="max-width: 80%" src="assets/screenshots/editor.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class=" text-left">
|
||||||
|
<p class="oe_mt32">
|
||||||
|
<font style="font-size: 120%;font-weight: bolder !important;color: #6f6060;">Browse Modules:</font>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
|
||||||
|
<div class="oe_screenshot" align="center">
|
||||||
|
<img style="max-width: 80%" src="assets/screenshots/browse.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="oe_mt32" style="font-size: 120%;">
|
||||||
|
All Odoo modules can be load in online code editor, write code, hot-reload frontend UIs, easily and quickly!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- <section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<div class="oe_span6">
|
||||||
|
<div class="oe_row_img oe_centered" align="center">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot" src="assets/screenshots/backend-app-studio-1.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_span6">
|
||||||
|
<div class="oe_row_img oe_centered" align="center">
|
||||||
|
<img class="oe_demo oe_picture oe_screenshot" src="assets/screenshots/backend-app-studio-2.png"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section> -->
|
||||||
|
|
||||||
|
<h4 class="oe_slogan"><b>Usage</b></h4>
|
||||||
|
|
||||||
|
<section class="oe_container text-left">
|
||||||
|
<div class="oe_row " >
|
||||||
|
<p class="oe_mt0" style="font-size: 120%;margin-left: 16px; margin-right: 16px; ">
|
||||||
|
Start Code Server from the Code Console page of this module, once start success, browser auto navigate to new window that address is :<span>
|
||||||
|
<a style="font-weight: bolder !important; color: #827d7d !important; cursor: pointer !important;" href="http://localhost:3030">http://localhost:3030/
|
||||||
|
</a>
|
||||||
|
</span>; The core features of code editor server was provide by the open source <span> <a style=" cursor: pointer !important;" href="https://github.com/ixkit/code-server">Code Server
|
||||||
|
</a> </span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="oe_row oe_spaced oe_mt0" style="text-align: center; width: 100%;">
|
||||||
|
<div>
|
||||||
|
<a href="https://github.com/ixkit/code-server"
|
||||||
|
target="_blank"
|
||||||
|
class="btn btn-block mb-2 deep_hover"
|
||||||
|
style="text-decoration: none; background-color: #413e3eaf; color: #FFF; border-radius: 4px;">
|
||||||
|
<i class="fa fa-github mr-2"></i>
|
||||||
|
<span style="margin-left: 6px;">CodeServer</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a style="font-size: 120%; color: #827d7d; cursor: pointer !important;" href="https://github.com/ixkit/code-server">https://github.com/ixkit/code-server
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_row oe_spaced oe_mt0">
|
||||||
|
<p class=" text-left" style="font-size: 120%;">
|
||||||
|
<font style="font-size: 100%;font-weight: bold !important;color: #6f6060;">Additional Step:</font> Manual install the Code Server
|
||||||
|
</p>
|
||||||
|
<div class="oe_span8" style="width: 100%; " >
|
||||||
|
|
||||||
|
<ul style="margin-left: -10px;" >
|
||||||
|
<li >
|
||||||
|
<p class="oe_mt8" style="font-size: 120%;">
|
||||||
|
Download the Code Server program from the Code Server<span> <a style=" cursor: pointer !important;" href="https://github.com/ixkit/code-server/releases">repository
|
||||||
|
</a> </span> or clone the source code and build the Code Sever binary package.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li >
|
||||||
|
<p class="oe_mt8" style="font-size: 120%;">
|
||||||
|
Put the Code Sever binary package into the moudule directory './kit_code/vendors/',
|
||||||
|
</p>
|
||||||
|
<p class="oe_mt8" style="font-size: 120%;">
|
||||||
|
eg:<br>
|
||||||
|
mac platform :
|
||||||
|
it should be './kit_code/venders/code_server/code-server-macos'
|
||||||
|
<br>
|
||||||
|
windows:
|
||||||
|
it should be './kit_code/venders/code_server/code-server-win.exe'
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="oe_row " style="margin-top: -20px;">
|
||||||
|
<div class="text-left">
|
||||||
|
<p class=" text-left" style="font-size: 120%;">
|
||||||
|
<font style="font-size: 100%;font-weight: bold !important;color: #6f6060;">Notice:</font>
|
||||||
|
</p>
|
||||||
|
<p class="oe_mt16 oe_mb16" style="font-size: 120%; ">
|
||||||
|
<i class="fa fa-exclamation-triangle" style="color: red" aria-hidden="true"></i> This module should only use for development phase! Production Environment is not recommend!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="oe_mb48 oe_mt32" style="font-size: 120%; color: #827d7d;">
|
||||||
|
Enjoy & Happy Coding! <i class="fa fa-smile-o" aria-hidden="true"></i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="oe_slogan"><b>Support</b></h4>
|
||||||
|
<section class="oe_container oe_dark">
|
||||||
|
<div class="oe_row oe_spaced">
|
||||||
|
<div class="oe_span6">
|
||||||
|
<div class="oe_row_img oe_centered" align="center">
|
||||||
|
<a href="mailto:odoo@ixkit.com" target="_blank"
|
||||||
|
class="btn btn-block mb-2 deep_hover"
|
||||||
|
style="text-decoration: none; background-color: #b94646af; color: #FFF; border-radius: 4px;"><i
|
||||||
|
class="fa fa-envelope mr-2"></i> odoo@ixkit.com</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="oe_span6">
|
||||||
|
<div class="oe_row_img oe_centered" align="center">
|
||||||
|
<a href="skype:live:.cid.c5d5ecc7e54128e2?call"
|
||||||
|
class="btn btn-block mb-2 deep_hover"
|
||||||
|
style="text-decoration: none; background-color: #1b9beabd; color: #FFF; border-radius: 4px;">
|
||||||
|
<i class="fa fa-skype mr-2"></i> ixkit
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="oe_container oe_dark oe_mt64 oe_mb64"></section>
|
||||||
|
<h4 class="oe_slogan "><i class="fa fa-heart" aria-hidden="true" style="font-size: 90%;color: rgb(217, 47, 9);"></i>
|
||||||
|
<span class="font-sans-serif" style="margin-left: 6px;font-size: 90%; cursor: pointer !important; ">
|
||||||
|
<a style="font-weight: bolder !important; color: #827d7d !important; cursor: pointer !important;" href="http://ixkit.com/odookit">by ixkit team
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
</section>
|
||||||
|
<section class="oe_container oe_separator">
|
||||||
|
</section>
|
45
packs/devs/kit_code/static/image/server-black.svg
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 503.607 503.607" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M503.607,125.902V25.18c0-13.43-10.911-25.18-25.18-25.18H25.18C10.911,0,0,10.911,0,25.18v100.721
|
||||||
|
c0,13.429,10.911,25.18,25.18,25.18v25.18c-13.43,0-25.18,10.911-25.18,25.18v100.721c0,13.43,10.911,25.18,25.18,25.18v25.18
|
||||||
|
c-13.43,0-25.18,10.911-25.18,25.18v100.721c0,13.43,10.911,25.18,25.18,25.18h453.246c13.43,0,25.18-10.911,25.18-25.18V377.705
|
||||||
|
c0-13.429-10.911-25.18-25.18-25.18v-25.18c13.43,0,25.18-10.911,25.18-25.18V201.443c0-13.43-10.911-25.18-25.18-25.18v-25.18
|
||||||
|
C491.856,151.082,503.607,140.171,503.607,125.902z M41.967,151.082h419.672v25.18H41.967V151.082z M33.574,276.984v-50.361
|
||||||
|
c0-5.036,3.357-8.393,8.393-8.393h100.721c5.036,0,8.393,3.357,8.393,8.393v50.361c0,5.036-3.357,8.393-8.393,8.393H41.967
|
||||||
|
C36.931,285.377,33.574,281.18,33.574,276.984z M461.639,352.525H41.967v-25.18h419.672V352.525z M176.262,276.984
|
||||||
|
c0-5.036,3.357-8.393,8.393-8.393s8.393,3.357,8.393,8.393c0,5.036-3.357,8.393-8.393,8.393S176.262,281.18,176.262,276.984z
|
||||||
|
M201.443,260.197c-5.036,0-8.393-3.357-8.393-8.393s3.357-8.393,8.393-8.393s8.393,3.357,8.393,8.393
|
||||||
|
S206.479,260.197,201.443,260.197z M218.229,268.59c5.036,0,8.393,3.357,8.393,8.393c0,5.036-3.357,8.393-8.393,8.393
|
||||||
|
s-8.393-3.357-8.393-8.393C209.836,271.948,213.193,268.59,218.229,268.59z M235.016,260.197c-5.036,0-8.393-3.357-8.393-8.393
|
||||||
|
s3.357-8.393,8.393-8.393s8.393,3.357,8.393,8.393S240.052,260.197,235.016,260.197z M251.803,268.59
|
||||||
|
c5.036,0,8.393,3.357,8.393,8.393c0,5.036-3.357,8.393-8.393,8.393c-5.036,0-8.393-3.357-8.393-8.393
|
||||||
|
C243.41,271.948,246.767,268.59,251.803,268.59z M268.59,260.197c-5.036,0-8.393-3.357-8.393-8.393s3.357-8.393,8.393-8.393
|
||||||
|
s8.393,3.357,8.393,8.393S273.626,260.197,268.59,260.197z M285.377,268.59c5.036,0,8.393,3.357,8.393,8.393
|
||||||
|
c0,5.036-3.357,8.393-8.393,8.393c-5.036,0-8.393-3.357-8.393-8.393C276.984,271.948,280.341,268.59,285.377,268.59z
|
||||||
|
M302.164,260.197c-5.036,0-8.393-3.357-8.393-8.393s3.357-8.393,8.393-8.393s8.393,3.357,8.393,8.393
|
||||||
|
S307.2,260.197,302.164,260.197z M318.951,268.59c5.036,0,8.393,3.357,8.393,8.393c0,5.036-3.357,8.393-8.393,8.393
|
||||||
|
c-5.036,0-8.393-3.357-8.393-8.393C310.557,271.948,313.915,268.59,318.951,268.59z M335.738,260.197
|
||||||
|
c-5.036,0-8.393-3.357-8.393-8.393s3.357-8.393,8.393-8.393c5.036,0,8.393,3.357,8.393,8.393S340.774,260.197,335.738,260.197z
|
||||||
|
M352.525,268.59c5.036,0,8.393,3.357,8.393,8.393c0,5.036-3.357,8.393-8.393,8.393c-5.036,0-8.393-3.357-8.393-8.393
|
||||||
|
C344.131,271.948,347.489,268.59,352.525,268.59z M369.311,260.197c-5.036,0-8.393-3.357-8.393-8.393s3.357-8.393,8.393-8.393
|
||||||
|
s8.393,3.357,8.393,8.393S374.348,260.197,369.311,260.197z M386.098,268.59c5.036,0,8.393,3.357,8.393,8.393
|
||||||
|
c0,5.036-3.357,8.393-8.393,8.393c-5.036,0-8.393-3.357-8.393-8.393C377.705,271.948,381.062,268.59,386.098,268.59z
|
||||||
|
M402.885,260.197c-5.036,0-8.393-3.357-8.393-8.393s3.357-8.393,8.393-8.393c5.036,0,8.393,3.357,8.393,8.393
|
||||||
|
S407.921,260.197,402.885,260.197z M419.672,268.59c5.036,0,8.393,3.357,8.393,8.393c0,5.036-3.357,8.393-8.393,8.393
|
||||||
|
s-8.393-3.357-8.393-8.393C411.279,271.948,414.636,268.59,419.672,268.59z M428.066,251.803c0-5.036,3.357-8.393,8.393-8.393
|
||||||
|
c5.036,0,8.393,3.357,8.393,8.393s-3.357,8.393-8.393,8.393C431.423,260.197,428.066,256,428.066,251.803z M453.246,285.377
|
||||||
|
c-5.036,0-8.393-3.357-8.393-8.393c0-5.036,3.357-8.393,8.393-8.393c5.036,0,8.393,3.357,8.393,8.393
|
||||||
|
C461.639,282.02,458.282,285.377,453.246,285.377z M461.639,235.016H176.262c-5.036,0-8.393-3.357-8.393-8.393
|
||||||
|
s3.357-8.393,8.393-8.393h285.377c5.036,0,8.393,3.357,8.393,8.393S466.675,235.016,461.639,235.016z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<rect x="50.361" y="235.016" width="83.934" height="33.574"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
81
packs/devs/kit_code/static/image/server-running.svg
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<g transform="translate(1 1)">
|
||||||
|
<path style="fill:#FCC309;" d="M58.733,186.733h392.533v-42.667H58.733V186.733z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M33.133,186.733h25.6v-42.667h-25.6V186.733z"/>
|
||||||
|
<path style="fill:#FD9808;" d="M451.267,186.733h25.6v-42.667h-25.6V186.733z"/>
|
||||||
|
<path style="fill:#FCC309;" d="M58.733,365.933h392.533v-42.667H58.733V365.933z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M33.133,365.933h25.6v-42.667h-25.6V365.933z"/>
|
||||||
|
<path style="fill:#FD9808;" d="M451.267,365.933h25.6v-42.667h-25.6V365.933z"/>
|
||||||
|
<path style="fill:#FFDD09;" d="M459.8,323.267H50.2c-9.387,0-17.067-7.68-17.067-17.067V203.8c0-9.387,7.68-17.067,17.067-17.067
|
||||||
|
h409.6c9.387,0,17.067,7.68,17.067,17.067v102.4C476.867,315.587,469.187,323.267,459.8,323.267"/>
|
||||||
|
<path style="fill:#1CD759;" d="M41.667,280.6h102.4v-51.2h-102.4V280.6z"/>
|
||||||
|
<path style="fill:#FFDD09;" d="M459.8,144.067H50.2c-9.387,0-17.067-7.68-17.067-17.067V24.6c0-9.387,7.68-17.067,17.067-17.067
|
||||||
|
h409.6c9.387,0,17.067,7.68,17.067,17.067V127C476.867,136.387,469.187,144.067,459.8,144.067"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M33.133,127V24.6c0-9.387,7.68-17.067,17.067-17.067H24.6c-9.387,0-17.067,7.68-17.067,17.067V127
|
||||||
|
c0,9.387,7.68,17.067,17.067,17.067h25.6C40.813,144.067,33.133,136.387,33.133,127"/>
|
||||||
|
<path style="fill:#FD9808;" d="M485.4,7.533h-25.6c9.387,0,17.067,7.68,17.067,17.067V127c0,9.387-7.68,17.067-17.067,17.067h25.6
|
||||||
|
c9.387,0,17.067-7.68,17.067-17.067V24.6C502.467,15.213,494.787,7.533,485.4,7.533"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M33.133,306.2V203.8c0-9.387,7.68-17.067,17.067-17.067H24.6c-9.387,0-17.067,7.68-17.067,17.067
|
||||||
|
v102.4c0,9.387,7.68,17.067,17.067,17.067h25.6C40.813,323.267,33.133,315.587,33.133,306.2"/>
|
||||||
|
<path style="fill:#FD9808;" d="M485.4,186.733h-25.6c9.387,0,17.067,7.68,17.067,17.067v102.4c0,9.387-7.68,17.067-17.067,17.067
|
||||||
|
h25.6c9.387,0,17.067-7.68,17.067-17.067V203.8C502.467,194.413,494.787,186.733,485.4,186.733"/>
|
||||||
|
<path style="fill:#FFDD09;" d="M459.8,502.467H50.2c-9.387,0-17.067-7.68-17.067-17.067V383c0-9.387,7.68-17.067,17.067-17.067
|
||||||
|
h409.6c9.387,0,17.067,7.68,17.067,17.067v102.4C476.867,494.787,469.187,502.467,459.8,502.467"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M33.133,485.4V383c0-9.387,7.68-17.067,17.067-17.067H24.6c-9.387,0-17.067,7.68-17.067,17.067
|
||||||
|
v102.4c0,9.387,7.68,17.067,17.067,17.067h25.6C40.813,502.467,33.133,494.787,33.133,485.4"/>
|
||||||
|
<path style="fill:#FD9808;" d="M485.4,365.933h-25.6c9.387,0,17.067,7.68,17.067,17.067v102.4c0,9.387-7.68,17.067-17.067,17.067
|
||||||
|
h25.6c9.387,0,17.067-7.68,17.067-17.067V383C502.467,373.613,494.787,365.933,485.4,365.933"/>
|
||||||
|
<path d="M485.4,152.6H24.6C10.947,152.6-1,141.507-1,127V24.6C-1,10.947,10.093-1,24.6-1h460.8C499.053-1,511,10.093,511,24.6V127
|
||||||
|
C511,141.507,499.907,152.6,485.4,152.6z M24.6,16.067c-5.12,0-8.533,3.413-8.533,8.533V127c0,4.267,3.413,8.533,8.533,8.533h460.8
|
||||||
|
c4.267,0,8.533-3.413,8.533-8.533V24.6c0-4.267-3.413-8.533-8.533-8.533H24.6z"/>
|
||||||
|
<path d="M485.4,511H24.6C10.947,511-1,499.907-1,485.4V383c0-13.653,11.093-25.6,25.6-25.6h460.8c13.653,0,25.6,11.093,25.6,25.6
|
||||||
|
v102.4C511,499.907,499.907,511,485.4,511z M24.6,374.467c-4.267,0-8.533,3.413-8.533,8.533v102.4c0,4.267,3.413,8.533,8.533,8.533
|
||||||
|
h460.8c4.267,0,8.533-3.413,8.533-8.533V383c0-4.267-3.413-8.533-8.533-8.533H24.6z"/>
|
||||||
|
<path d="M485.4,331.8H24.6C10.947,331.8-1,320.707-1,306.2V203.8c0-13.653,11.093-25.6,25.6-25.6h460.8
|
||||||
|
c13.653,0,25.6,11.093,25.6,25.6v102.4C511,320.707,499.907,331.8,485.4,331.8z M24.6,195.267c-4.267,0-8.533,3.413-8.533,8.533
|
||||||
|
v102.4c0,4.267,3.413,8.533,8.533,8.533h460.8c4.267,0,8.533-3.413,8.533-8.533V203.8c0-4.267-3.413-8.533-8.533-8.533H24.6z"/>
|
||||||
|
<path d="M365.933,280.6c0,5.12-3.413,8.533-8.533,8.533s-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
S365.933,275.48,365.933,280.6"/>
|
||||||
|
<path d="M400.067,280.6c0,5.12-3.413,8.533-8.533,8.533S383,285.72,383,280.6s3.413-8.533,8.533-8.533
|
||||||
|
S400.067,275.48,400.067,280.6"/>
|
||||||
|
<path d="M297.667,280.6c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C294.253,272.067,297.667,275.48,297.667,280.6"/>
|
||||||
|
<path d="M331.8,280.6c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C328.387,272.067,331.8,275.48,331.8,280.6"/>
|
||||||
|
<path d="M229.4,280.6c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C225.987,272.067,229.4,275.48,229.4,280.6"/>
|
||||||
|
<path d="M263.533,280.6c0,5.12-3.413,8.533-8.533,8.533s-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
S263.533,275.48,263.533,280.6"/>
|
||||||
|
<path d="M195.267,280.6c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C191.853,272.067,195.267,275.48,195.267,280.6"/>
|
||||||
|
<path d="M434.2,280.6c0,5.12-3.413,8.533-8.533,8.533s-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533S434.2,275.48,434.2,280.6"
|
||||||
|
/>
|
||||||
|
<path d="M468.333,280.6c0,5.12-3.413,8.533-8.533,8.533s-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
S468.333,275.48,468.333,280.6"/>
|
||||||
|
<path d="M383,255c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C379.587,246.467,383,249.88,383,255"/>
|
||||||
|
<path d="M417.133,255c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C413.72,246.467,417.133,249.88,417.133,255"/>
|
||||||
|
<path d="M314.733,255c0,5.12-3.413,8.533-8.533,8.533s-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
S314.733,249.88,314.733,255"/>
|
||||||
|
<path d="M348.867,255c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C345.453,246.467,348.867,249.88,348.867,255"/>
|
||||||
|
<path d="M246.467,255c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C243.053,246.467,246.467,249.88,246.467,255"/>
|
||||||
|
<path d="M280.6,255c0,5.12-3.413,8.533-8.533,8.533s-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533S280.6,249.88,280.6,255"/>
|
||||||
|
<path d="M212.333,255c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C208.92,246.467,212.333,249.88,212.333,255"/>
|
||||||
|
<path d="M451.267,255c0,5.12-3.413,8.533-8.533,8.533c-5.12,0-8.533-3.413-8.533-8.533s3.413-8.533,8.533-8.533
|
||||||
|
C447.853,246.467,451.267,249.88,451.267,255"/>
|
||||||
|
<path d="M468.333,237.933H178.2c-5.12,0-8.533-3.413-8.533-8.533c0-5.12,3.413-8.533,8.533-8.533h290.133
|
||||||
|
c5.12,0,8.533,3.413,8.533,8.533C476.867,234.52,473.453,237.933,468.333,237.933z"/>
|
||||||
|
<path d="M144.067,289.133h-102.4c-5.12,0-8.533-3.413-8.533-8.533v-51.2c0-5.12,3.413-8.533,8.533-8.533h102.4
|
||||||
|
c5.12,0,8.533,3.413,8.533,8.533v51.2C152.6,285.72,149.187,289.133,144.067,289.133z M50.2,272.067h85.333v-34.133H50.2V272.067z"
|
||||||
|
/>
|
||||||
|
<path d="M485.4,195.267H24.6v-59.733h460.8V195.267z M41.667,178.2h426.667v-25.6H41.667V178.2z"/>
|
||||||
|
<path d="M485.4,374.467H24.6v-59.733h460.8V374.467z M41.667,357.4h426.667v-25.6H41.667V357.4z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7 KiB |
60
packs/devs/kit_code/static/image/server-white.svg
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
viewBox="0 0 512 512" xml:space="preserve">
|
||||||
|
<g transform="translate(1 1)">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M511,127V24.6C511,10.093,499.053-1,485.4-1H24.6C10.093-1-1,10.947-1,24.6V127c0,14.507,11.947,25.6,25.6,25.6v25.6
|
||||||
|
C10.093,178.2-1,190.147-1,203.8v102.4c0,14.507,11.947,25.6,25.6,25.6v25.6C10.093,357.4-1,369.347-1,383v102.4
|
||||||
|
c0,14.507,11.947,25.6,25.6,25.6h460.8c14.507,0,25.6-11.093,25.6-25.6V383c0-14.507-11.947-25.6-25.6-25.6v-25.6
|
||||||
|
c14.507,0,25.6-11.093,25.6-25.6V203.8c0-14.507-11.947-25.6-25.6-25.6v-25.6C499.907,152.6,511,141.507,511,127z M493.933,383
|
||||||
|
v102.4c0,5.12-4.267,8.533-8.533,8.533H24.6c-5.12,0-8.533-4.267-8.533-8.533V383c0-5.12,4.267-8.533,8.533-8.533h460.8
|
||||||
|
C490.52,374.467,493.933,378.733,493.933,383z M468.333,357.4H41.667v-25.6h426.667V357.4z M493.933,203.8v102.4
|
||||||
|
c0,5.12-4.267,8.533-8.533,8.533H24.6c-5.12,0-8.533-4.267-8.533-8.533V203.8c0-5.12,4.267-8.533,8.533-8.533h460.8
|
||||||
|
C490.52,195.267,493.933,199.533,493.933,203.8z M468.333,178.2H41.667v-25.6h426.667V178.2z M24.6,135.533
|
||||||
|
c-5.12,0-8.533-4.267-8.533-8.533V24.6c0-5.12,3.413-8.533,8.533-8.533h460.8c5.12,0,8.533,4.267,8.533,8.533V127
|
||||||
|
c0,5.12-4.267,8.533-8.533,8.533H24.6z"/>
|
||||||
|
<path d="M357.4,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S362.52,272.067,357.4,272.067z"/>
|
||||||
|
<path d="M391.533,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533c5.12,0,8.533-3.413,8.533-8.533
|
||||||
|
S396.653,272.067,391.533,272.067z"/>
|
||||||
|
<path d="M289.133,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S294.253,272.067,289.133,272.067z"/>
|
||||||
|
<path d="M323.267,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S328.387,272.067,323.267,272.067z"/>
|
||||||
|
<path d="M220.867,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S225.987,272.067,220.867,272.067z"/>
|
||||||
|
<path d="M255,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S260.12,272.067,255,272.067z"/>
|
||||||
|
<path d="M186.733,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S191.853,272.067,186.733,272.067z"/>
|
||||||
|
<path d="M425.667,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S430.787,272.067,425.667,272.067z"/>
|
||||||
|
<path d="M459.8,272.067c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533c5.12,0,8.533-3.413,8.533-8.533
|
||||||
|
S464.92,272.067,459.8,272.067z"/>
|
||||||
|
<path d="M374.467,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533S383,260.12,383,255
|
||||||
|
S379.587,246.467,374.467,246.467z"/>
|
||||||
|
<path d="M408.6,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S413.72,246.467,408.6,246.467z"/>
|
||||||
|
<path d="M306.2,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S311.32,246.467,306.2,246.467z"/>
|
||||||
|
<path d="M340.333,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533c5.12,0,8.533-3.413,8.533-8.533
|
||||||
|
S345.453,246.467,340.333,246.467z"/>
|
||||||
|
<path d="M237.933,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S243.053,246.467,237.933,246.467z"/>
|
||||||
|
<path d="M272.067,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533c5.12,0,8.533-3.413,8.533-8.533
|
||||||
|
S277.187,246.467,272.067,246.467z"/>
|
||||||
|
<path d="M203.8,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533c5.12,0,8.533-3.413,8.533-8.533
|
||||||
|
S208.92,246.467,203.8,246.467z"/>
|
||||||
|
<path d="M442.733,246.467c-5.12,0-8.533,3.413-8.533,8.533s3.413,8.533,8.533,8.533s8.533-3.413,8.533-8.533
|
||||||
|
S447.853,246.467,442.733,246.467z"/>
|
||||||
|
<path d="M178.2,237.933h290.133c5.12,0,8.533-3.413,8.533-8.533s-3.413-8.533-8.533-8.533H178.2c-5.12,0-8.533,3.413-8.533,8.533
|
||||||
|
S173.08,237.933,178.2,237.933z"/>
|
||||||
|
<path d="M41.667,289.133h102.4c5.12,0,8.533-3.413,8.533-8.533v-51.2c0-5.12-3.413-8.533-8.533-8.533h-102.4
|
||||||
|
c-5.12,0-8.533,3.413-8.533,8.533v51.2C33.133,285.72,36.547,289.133,41.667,289.133z M50.2,237.933h85.333v34.133H50.2V237.933z
|
||||||
|
"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
43
packs/devs/kit_code/static/jslib/jsontable/jsonviewr.js
Normal file
21
packs/devs/kit_code/static/jslib/termynal/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 2017 Ines Montani
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
101
packs/devs/kit_code/static/jslib/termynal/termynal.css
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/**
|
||||||
|
* termynal.js
|
||||||
|
*
|
||||||
|
* @author Ines Montani <ines@ines.io>
|
||||||
|
* @version 0.0.1
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-bg: #252a33;
|
||||||
|
--color-text: #eee;
|
||||||
|
--color-text-subtle: #a2a2a2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-termynal] {
|
||||||
|
width: 750px;
|
||||||
|
max-width: 100%;
|
||||||
|
background: var(--color-bg);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 75px 45px 35px;
|
||||||
|
position: relative;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-termynal]:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
left: 15px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
/* A little hack to display the window buttons in one pseudo element. */
|
||||||
|
background: #d9515d;
|
||||||
|
-webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
|
||||||
|
box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-termynal]:after {
|
||||||
|
content: 'bash';
|
||||||
|
position: absolute;
|
||||||
|
color: var(--color-text-subtle);
|
||||||
|
top: 5px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ty] {
|
||||||
|
display: block;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ty]:before {
|
||||||
|
/* Set up defaults and ensure empty lines are displayed. */
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ty="input"]:before,
|
||||||
|
[data-ty-prompt]:before {
|
||||||
|
margin-right: 0.75em;
|
||||||
|
color: var(--color-text-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ty="input"]:before {
|
||||||
|
content: '$';
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ty][data-ty-prompt]:before {
|
||||||
|
content: attr(data-ty-prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-ty-cursor]:after {
|
||||||
|
content: attr(data-ty-cursor);
|
||||||
|
font-family: monospace;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
-webkit-animation: blink 1s infinite;
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Cursor animation */
|
||||||
|
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
231
packs/devs/kit_code/static/jslib/termynal/termynal.js
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
/**
|
||||||
|
* termynal.js
|
||||||
|
* A lightweight, modern and extensible animated terminal window, using
|
||||||
|
* async/await.
|
||||||
|
*
|
||||||
|
* @author Ines Montani <ines@ines.io>
|
||||||
|
* @version 0.0.1
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/** Generate a terminal widget. */
|
||||||
|
class Termynal {
|
||||||
|
/**
|
||||||
|
* Construct the widget's settings.
|
||||||
|
* @param {(string|Node)=} container - Query selector or container element.
|
||||||
|
* @param {Object=} options - Custom settings.
|
||||||
|
* @param {string} options.prefix - Prefix to use for data attributes.
|
||||||
|
* @param {number} options.startDelay - Delay before animation, in ms.
|
||||||
|
* @param {number} options.typeDelay - Delay between each typed character, in ms.
|
||||||
|
* @param {number} options.lineDelay - Delay between each line, in ms.
|
||||||
|
* @param {number} options.progressLength - Number of characters displayed as progress bar.
|
||||||
|
* @param {string} options.progressChar – Character to use for progress bar, defaults to █.
|
||||||
|
* @param {number} options.progressPercent - Max percent of progress.
|
||||||
|
* @param {string} options.cursor – Character to use for cursor, defaults to ▋.
|
||||||
|
* @param {Object[]} lineData - Dynamically loaded line data objects.
|
||||||
|
* @param {boolean} options.noInit - Don't initialise the animation.
|
||||||
|
*/
|
||||||
|
constructor(container = '#termynal', options = {}) {
|
||||||
|
this.container = (typeof container === 'string') ? document.querySelector(container) : container;
|
||||||
|
this.pfx = `data-${options.prefix || 'ty'}`;
|
||||||
|
|
||||||
|
this.maxLines = options.maxLines
|
||||||
|
|| parseInt(this.container.getAttribute(`${this.pfx}-maxLines`)) || 100;
|
||||||
|
|
||||||
|
this.startDelay = options.startDelay
|
||||||
|
|| parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
|
||||||
|
this.typeDelay = options.typeDelay
|
||||||
|
|| parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
|
||||||
|
this.lineDelay = options.lineDelay
|
||||||
|
|| parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
|
||||||
|
this.progressLength = options.progressLength
|
||||||
|
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
|
||||||
|
this.progressChar = options.progressChar
|
||||||
|
|| this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
|
||||||
|
this.progressPercent = options.progressPercent
|
||||||
|
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
|
||||||
|
this.cursor = options.cursor
|
||||||
|
|| this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
|
||||||
|
this.lineData = this.lineDataToElements(options.lineData || []);
|
||||||
|
if (!options.noInit) this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the widget, get lines, clear container and start animation.
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
// Appends dynamically loaded lines to existing line elements.
|
||||||
|
this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates width and height of Termynal container.
|
||||||
|
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
|
||||||
|
*/
|
||||||
|
const containerStyle = getComputedStyle(this.container);
|
||||||
|
this.container.style.width = containerStyle.width !== '0px' ?
|
||||||
|
containerStyle.width : undefined;
|
||||||
|
this.container.style.minHeight = containerStyle.height !== '0px' ?
|
||||||
|
containerStyle.height : undefined;
|
||||||
|
|
||||||
|
//this.container.setAttribute('data-termynal', '');
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the animation and rener the lines depending on their data attributes.
|
||||||
|
*/
|
||||||
|
async start() {
|
||||||
|
await this._wait(this.startDelay);
|
||||||
|
|
||||||
|
for (let line of this.lines) {
|
||||||
|
const type = line.getAttribute(this.pfx);
|
||||||
|
const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;
|
||||||
|
|
||||||
|
if (type == 'input') {
|
||||||
|
line.setAttribute(`${this.pfx}-cursor`, this.cursor);
|
||||||
|
await this.type(line);
|
||||||
|
await this._wait(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (type == 'progress') {
|
||||||
|
await this.progress(line);
|
||||||
|
await this._wait(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.container.appendChild(line);
|
||||||
|
await this._wait(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
line.removeAttribute(`${this.pfx}-cursor`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// <span data-ty="input">[('Hello', 'INTJ'), ('world', 'NOUN')]</span>
|
||||||
|
async appendLine(msg){
|
||||||
|
const lineNode = document.createElement("span");
|
||||||
|
lineNode.setAttribute('data-ty',"input");
|
||||||
|
lineNode.textContent = msg;
|
||||||
|
this.type(lineNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFirstLine(){
|
||||||
|
const container = this.container;
|
||||||
|
const node = container.firstElementChild;
|
||||||
|
if (!node){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
container.removeChild(node);
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
getLineCount(){
|
||||||
|
const container = this.container;
|
||||||
|
const size = container.childNodes.length;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
async removeLineIfNeeds(){
|
||||||
|
const size = this.getLineCount();
|
||||||
|
if (size <= this.maxLines){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return this.removeFirstLine()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Animate a typed line.
|
||||||
|
* @param {Node} line - The line element to render.
|
||||||
|
*/
|
||||||
|
async type(line) {
|
||||||
|
const chars = [...line.textContent];
|
||||||
|
const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
|
||||||
|
line.textContent = '';
|
||||||
|
this.container.appendChild(line);
|
||||||
|
|
||||||
|
for (let char of chars) {
|
||||||
|
await this._wait(delay);
|
||||||
|
line.textContent += char;
|
||||||
|
}
|
||||||
|
this.removeLineIfNeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animate a progress bar.
|
||||||
|
* @param {Node} line - The line element to render.
|
||||||
|
*/
|
||||||
|
async progress(line) {
|
||||||
|
const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
|
||||||
|
|| this.progressLength;
|
||||||
|
const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
|
||||||
|
|| this.progressChar;
|
||||||
|
const chars = progressChar.repeat(progressLength);
|
||||||
|
const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
|
||||||
|
|| this.progressPercent;
|
||||||
|
line.textContent = '';
|
||||||
|
this.container.appendChild(line);
|
||||||
|
|
||||||
|
for (let i = 1; i < chars.length + 1; i++) {
|
||||||
|
await this._wait(this.typeDelay);
|
||||||
|
const percent = Math.round(i / chars.length * 100);
|
||||||
|
line.textContent = `${chars.slice(0, i)} ${percent}%`;
|
||||||
|
if (percent>progressPercent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for animation delays, called with `await`.
|
||||||
|
* @param {number} time - Timeout, in ms.
|
||||||
|
*/
|
||||||
|
_wait(time) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, time));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts line data objects into line elements.
|
||||||
|
*
|
||||||
|
* @param {Object[]} lineData - Dynamically loaded lines.
|
||||||
|
* @param {Object} line - Line data object.
|
||||||
|
* @returns {Element[]} - Array of line elements.
|
||||||
|
*/
|
||||||
|
lineDataToElements(lineData) {
|
||||||
|
return lineData.map(line => {
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`;
|
||||||
|
|
||||||
|
return div.firstElementChild;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for generating attributes string.
|
||||||
|
*
|
||||||
|
* @param {Object} line - Line data object.
|
||||||
|
* @returns {string} - String of attributes.
|
||||||
|
*/
|
||||||
|
_attributes(line) {
|
||||||
|
let attrs = '';
|
||||||
|
for (let prop in line) {
|
||||||
|
attrs += this.pfx;
|
||||||
|
|
||||||
|
if (prop === 'type') {
|
||||||
|
attrs += `="${line[prop]}" `
|
||||||
|
} else if (prop !== 'value') {
|
||||||
|
attrs += `-${prop}="${line[prop]}" `
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTML API: If current script has container(s) specified, initialise Termynal.
|
||||||
|
*/
|
||||||
|
// if (document.currentScript.hasAttribute('data-termynal-container')) {
|
||||||
|
// const containers = 'termynal';// document.currentScript.getAttribute('data-termynal-container');
|
||||||
|
// containers.split('|')
|
||||||
|
// .forEach(container => new Termynal(container))
|
||||||
|
// }
|
||||||
|
|
236
packs/devs/kit_code/static/src/css/code_dashboard.css
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
.oh_dashboards{
|
||||||
|
padding-top :3px;
|
||||||
|
background-color: #f8faff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.oh-card h4 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
.breadcrumbs {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons button {
|
||||||
|
margin: 2px 0; }
|
||||||
|
|
||||||
|
/* Button Reset */
|
||||||
|
.btn, .button {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: all .15s ease-in-out;
|
||||||
|
border-radius: 0;
|
||||||
|
cursor: pointer; }
|
||||||
|
|
||||||
|
|
||||||
|
/* Widget One
|
||||||
|
---------------------------*/
|
||||||
|
.stat-content {
|
||||||
|
display: inline-block;
|
||||||
|
width: 66%;
|
||||||
|
}
|
||||||
|
.stat-icon{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-widget-image-ready {
|
||||||
|
|
||||||
|
margin: auto;
|
||||||
|
max-width: 60px;
|
||||||
|
max-height: 110px;
|
||||||
|
content: url("../../image/server-white.svg");
|
||||||
|
|
||||||
|
}
|
||||||
|
.status-widget-image-running {
|
||||||
|
|
||||||
|
margin: auto;
|
||||||
|
max-width: 60px;
|
||||||
|
max-height: 110px;
|
||||||
|
content: url("../../image/server-running.svg");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* HTML: <div class="status-widget-image-loading"></div> */
|
||||||
|
.status-widget-image-loading {
|
||||||
|
|
||||||
|
width: 50px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: grid;
|
||||||
|
color: #854f1d;
|
||||||
|
background: radial-gradient(farthest-side, currentColor calc(100% - 6px),#0000 calc(100% - 5px) 0);
|
||||||
|
-webkit-mask: radial-gradient(farthest-side,#0000 calc(100% - 13px),#000 calc(100% - 12px));
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: l19 2s infinite linear;
|
||||||
|
}
|
||||||
|
.status-widget-image-loading::before,
|
||||||
|
.status-widget-image-loading::after {
|
||||||
|
content: "";
|
||||||
|
grid-area: 1/1;
|
||||||
|
background:
|
||||||
|
linear-gradient(currentColor 0 0) center,
|
||||||
|
linear-gradient(currentColor 0 0) center;
|
||||||
|
background-size: 100% 10px,10px 100%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.status-widget-image-loading::after {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes l19 {
|
||||||
|
100%{transform: rotate(1turn)}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-one .stat-icon {
|
||||||
|
vertical-align: top;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
color: #01c490;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-one .stat-icon i {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 900;
|
||||||
|
display: inline-block;
|
||||||
|
color: #01c490;}
|
||||||
|
|
||||||
|
.stat-widget-one .stat-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #868e96;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-one .stat-digit {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #02448b; }
|
||||||
|
|
||||||
|
.stat-count {
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #00438b;}
|
||||||
|
|
||||||
|
.stat-title {
|
||||||
|
font-size: 17px;
|
||||||
|
text-align: center;
|
||||||
|
color: #00438b; }
|
||||||
|
|
||||||
|
.mb-0 .dash-title {
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.81);
|
||||||
|
}
|
||||||
|
.hr_birthday {
|
||||||
|
font-size: 28px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
color: #00438b;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .text-color {
|
||||||
|
color: #00438b;
|
||||||
|
}
|
||||||
|
.slice {
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*.dummy{
|
||||||
|
height:130vh;
|
||||||
|
}*/
|
||||||
|
.oh-card {
|
||||||
|
|
||||||
|
padding-top: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
border-radius: 0px;
|
||||||
|
box-shadow: none;
|
||||||
|
background: none;
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
will-change: transform, box-shadow;
|
||||||
|
|
||||||
|
}
|
||||||
|
.oh-card:hover {
|
||||||
|
|
||||||
|
transform: translateY(-2px) translateZ(0) !important;
|
||||||
|
box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
.oh-payslip {
|
||||||
|
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.oh-payslip .stat-icon {
|
||||||
|
|
||||||
|
width: 30%;
|
||||||
|
height: 85px;
|
||||||
|
text-align: center;
|
||||||
|
background: #ff8762;
|
||||||
|
color: #fff;
|
||||||
|
width: 32%;
|
||||||
|
padding-top: 2%;
|
||||||
|
font-size: xxx-large;
|
||||||
|
|
||||||
|
}
|
||||||
|
.oh-payslip .oh-card {
|
||||||
|
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
will-change: transform, box-shadow;
|
||||||
|
box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06);
|
||||||
|
|
||||||
|
}
|
||||||
|
.stat-widget-one .stat-text {
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
color: #ff8762;
|
||||||
|
margin-top: 2.3rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
.stat-widget-one .stat-digit {
|
||||||
|
font-size: 26px;
|
||||||
|
color:#993232;
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-top: -1px;
|
||||||
|
font-family: initial
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-widget-one .stat-icon i {
|
||||||
|
|
||||||
|
font-size: 25px;
|
||||||
|
font-weight: 900;
|
||||||
|
display: inline-block;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
}
|
||||||
|
.stat-widget-one {
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
}
|
||||||
|
.stat-widget-one {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.oh-payslip .stat-icon {
|
||||||
|
|
||||||
|
width: 30%;
|
||||||
|
height: 85px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 2%;
|
||||||
|
|
||||||
|
}
|
||||||
|
.row.main-section {
|
||||||
|
margin-right: 0px; !important;
|
||||||
|
}
|
||||||
|
.oh-card-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
388
packs/devs/kit_code/static/src/js/code_dashboard.js
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
/** @odoo-module **/
|
||||||
|
import { registry } from "@web/core/registry";
|
||||||
|
import { session } from "@web/session";
|
||||||
|
import { _t } from "@web/core/l10n/translation";
|
||||||
|
import { Component } from "@odoo/owl";
|
||||||
|
import { onWillStart, onMounted,onWillUnmount, onWillDestroy, useState } from "@odoo/owl";
|
||||||
|
import { useService } from "@web/core/utils/hooks";
|
||||||
|
import { ConfirmationDialog, AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||||
|
|
||||||
|
const actionRegistry = registry.category("actions");
|
||||||
|
|
||||||
|
export class CodeDashboard extends Component {
|
||||||
|
//Initializes the CodeDashboard component,
|
||||||
|
setup() {
|
||||||
|
super.setup(...arguments);
|
||||||
|
this.dialog = useService("dialog");
|
||||||
|
|
||||||
|
//message bus
|
||||||
|
const listenBus = (payload) => {
|
||||||
|
this.onMessage(payload)
|
||||||
|
}
|
||||||
|
this.busService = this.env.services.bus_service;
|
||||||
|
this.channel = "eventChannel";
|
||||||
|
this.busService.addChannel(this.channel);
|
||||||
|
// this.busService.addEventListener("notification", this.onMessage.bind(this))
|
||||||
|
this.busService.subscribe("notification", listenBus);
|
||||||
|
|
||||||
|
this.orm = useService("orm");
|
||||||
|
//this.user = useService("user");
|
||||||
|
this.actionService = useService("action");
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
|
||||||
|
this.state = useState({
|
||||||
|
serverStatus: "ready",
|
||||||
|
coderServer: null,
|
||||||
|
codeServerTip: null,
|
||||||
|
runtimeEnv: {}, //odoo runtime env
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the component is about to start, fetch data in tiles
|
||||||
|
onWillStart(async () => {
|
||||||
|
await this.fetch_data();
|
||||||
|
});
|
||||||
|
//When the component is mounted, render various charts
|
||||||
|
onMounted(async () => {
|
||||||
|
await this.render_server_status();
|
||||||
|
|
||||||
|
this._initConsole();
|
||||||
|
//this.mockInput();
|
||||||
|
|
||||||
|
this.getOdooEnv();
|
||||||
|
});
|
||||||
|
onWillUnmount(() => {
|
||||||
|
this.setTimerHandler(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
onWillDestroy(() => {
|
||||||
|
if (that.mockTyping) {
|
||||||
|
window.clearTimeout(this.mockTyping);
|
||||||
|
}
|
||||||
|
//this.busService.removeEventListener("notification", this.onMessage);
|
||||||
|
this.busService.unsubscribe("notification", listenBus);
|
||||||
|
this.busService.deleteChannel('eventChannel')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_initConsole() {
|
||||||
|
const container = "#termynal";
|
||||||
|
const termynal = new Termynal(container);
|
||||||
|
this.termynal = termynal;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mockInput() {
|
||||||
|
// await this.onclick_pos_sales('Hourly');
|
||||||
|
if (!this.termynal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const termynal = this.termynal;
|
||||||
|
const that = this;
|
||||||
|
function output() {
|
||||||
|
termynal.appendLine("Hello line @" + Date.now());
|
||||||
|
that.mockTyping = setTimeout(output, 3000);
|
||||||
|
try {
|
||||||
|
that.gotoLastLineIfNeeds();
|
||||||
|
} catch (ex) {
|
||||||
|
console.error("failed mockInput, ex?", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(output, 3000);
|
||||||
|
}
|
||||||
|
// array ?
|
||||||
|
async pathStatus(strOrArray) {
|
||||||
|
if (!strOrArray) return;
|
||||||
|
var out;
|
||||||
|
if (Array.isArray(strOrArray)) {
|
||||||
|
out = strOrArray[0];
|
||||||
|
} else {
|
||||||
|
out = strOrArray;
|
||||||
|
}
|
||||||
|
if (!out) return;
|
||||||
|
|
||||||
|
// Stop Code Server
|
||||||
|
// Start Code Server
|
||||||
|
let refresh = false;
|
||||||
|
if (out.includes("Stop Code Server")) {
|
||||||
|
refresh = true;
|
||||||
|
this.setServerStatus("ready");
|
||||||
|
}
|
||||||
|
if (out.includes("Start Code Server")) {
|
||||||
|
refresh = true;
|
||||||
|
this.setServerStatus("running");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
if (!refresh) return;
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
setTimeout(() => {
|
||||||
|
that.setServerStatus();
|
||||||
|
that.render_server_status();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"message": "{\"stdout\": [\"2024-10-22T04:21:58.341Z root INFO Changed application state from 'ready' to 'closing_window'.\"]}",
|
||||||
|
"ts": "2024-10-22 04:21:58"
|
||||||
|
},
|
||||||
|
"channel": "eventChannel"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
onMessage(playload) {
|
||||||
|
const that = this;
|
||||||
|
|
||||||
|
const data = playload.data;
|
||||||
|
const message = JSON.parse(data.message);
|
||||||
|
let out = message.stdout;
|
||||||
|
if (!out) {
|
||||||
|
out = message.stderr;
|
||||||
|
}
|
||||||
|
if (!out) {
|
||||||
|
out = JSON.stringify(message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
that.echo(out);
|
||||||
|
that.gotoLastLineIfNeeds();
|
||||||
|
} catch (ex) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage_17({ detail: notifications }) {
|
||||||
|
console.debug("onMessage, detail?", detail);
|
||||||
|
notifications = notifications.filter(
|
||||||
|
(item) => item.payload.channel === this.channel
|
||||||
|
);
|
||||||
|
|
||||||
|
const that = this;
|
||||||
|
notifications.forEach((item) => {
|
||||||
|
const data = item.payload.data;
|
||||||
|
const message = JSON.parse(data.message);
|
||||||
|
let out = message.stdout;
|
||||||
|
if (!out) {
|
||||||
|
out = message.stderr;
|
||||||
|
}
|
||||||
|
if (!out) {
|
||||||
|
out = JSON.stringify(message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
that.echo(out);
|
||||||
|
that.gotoLastLineIfNeeds();
|
||||||
|
} catch (ex) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async echo(msg) {
|
||||||
|
const termynal = this.termynal;
|
||||||
|
termynal.appendLine(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async gotoLastLineIfNeeds() {
|
||||||
|
const container = "#termynal";
|
||||||
|
try {
|
||||||
|
const elt = document.getElementById(container); // $( container).get(0);
|
||||||
|
if (!elt) return;
|
||||||
|
elt.animate({ scrollTop: elt.scrollHeight }, 5000);
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetch_data() {}
|
||||||
|
|
||||||
|
on_ctrl_server() {
|
||||||
|
const op = this.state.serverStatus === "ready" ? "start" : "stop";
|
||||||
|
const opTip = op === "start" ? "🚀 Start" : "❌ Stop";
|
||||||
|
const prompt = `${opTip} Code Server?`;
|
||||||
|
const that = this;
|
||||||
|
this.dialog.add(ConfirmationDialog, {
|
||||||
|
title: _t("Confirm"),
|
||||||
|
body: _t(prompt),
|
||||||
|
confirm: async () => {
|
||||||
|
await this._do_ctrl_server();
|
||||||
|
},
|
||||||
|
confirmLabel: _t("OK"),
|
||||||
|
cancel: () => {},
|
||||||
|
cancelLabel: _t("Cancel"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
getCodeServerUrl() {
|
||||||
|
const coderServer = this.state.coderServer;
|
||||||
|
if (!coderServer) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return coderServer.url;
|
||||||
|
|
||||||
|
return this.state.codeServerUrl;
|
||||||
|
const port = "3030";
|
||||||
|
const o = window.location.origin;
|
||||||
|
const list = o.split(":");
|
||||||
|
return `${list[0]}:${list[1]}:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toCodeServerTip(status) {
|
||||||
|
let op = status === "ready" ? "start" : "stop";
|
||||||
|
op = op.charAt(0).toUpperCase() + op.slice(1);
|
||||||
|
let result = `Click the server icon to ${op} the Code Server`;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _do_ctrl_server() {
|
||||||
|
const that = this;
|
||||||
|
|
||||||
|
const op = this.state.serverStatus === "ready" ? "start" : "stop";
|
||||||
|
|
||||||
|
this.orm
|
||||||
|
.call("kit.code.server", "ctrl_server", [op])
|
||||||
|
.then(function (result) {
|
||||||
|
//anyway refresh the UI
|
||||||
|
that.startCheckStatus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async startCheckStatus(delay = 2000) {
|
||||||
|
const that = this;
|
||||||
|
const timerHandler = setTimeout(() => {
|
||||||
|
that.render_server_status((x) => {
|
||||||
|
if (x === "running") {
|
||||||
|
try {
|
||||||
|
if (that) {
|
||||||
|
that.onCodeServerStarted();
|
||||||
|
}
|
||||||
|
} catch (ex) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (that) {
|
||||||
|
that.startCheckStatus(3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
this.setTimerHandler(timerHandler);
|
||||||
|
}
|
||||||
|
setTimerHandler(timer) {
|
||||||
|
const existTimer = this._timerHandler;
|
||||||
|
if (existTimer) {
|
||||||
|
clearTimeout(existTimer);
|
||||||
|
}
|
||||||
|
this._timerHandler = timer;
|
||||||
|
}
|
||||||
|
async onCodeServerStarted() {
|
||||||
|
const that = this;
|
||||||
|
const size = 1;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (1 > 10) {
|
||||||
|
const url = that.getCodeServerUrl();
|
||||||
|
that._openCodeServerPage(url);
|
||||||
|
} else {
|
||||||
|
that._openHome();
|
||||||
|
}
|
||||||
|
|
||||||
|
// should do this job in server internal
|
||||||
|
that.pushSpaceToCodeServer();
|
||||||
|
}, 1000 * size);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleCickCodeServer() {
|
||||||
|
const that = this;
|
||||||
|
const url = that.getCodeServerUrl();
|
||||||
|
if (!url || "" === url) return;
|
||||||
|
that._openCodeServerPage(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _openCodeServerPage(url) {
|
||||||
|
const that = this;
|
||||||
|
const tab = window.open(url, "modal=false,alwaysRaised=yes");
|
||||||
|
}
|
||||||
|
|
||||||
|
//status = "running" | "ready"
|
||||||
|
setServerStatus(status) {
|
||||||
|
try {
|
||||||
|
this.state.serverStatus = status;
|
||||||
|
this.state.codeServerTip = this.toCodeServerTip(status);
|
||||||
|
} catch (ex) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchServerInfo(onResult) {
|
||||||
|
const that = this;
|
||||||
|
this.orm
|
||||||
|
.call("kit.code.server", "fetch_server_info")
|
||||||
|
.then(function (rpcResult) {
|
||||||
|
if (rpcResult) {
|
||||||
|
if (rpcResult.success) {
|
||||||
|
const server_info = rpcResult.data;
|
||||||
|
that.state.coderServer = server_info;
|
||||||
|
onResult.apply(null, ["running"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
that.state.coderServer = null;
|
||||||
|
onResult.apply(null, ["ready"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async render_server_status(onStatusResult) {
|
||||||
|
const that = this;
|
||||||
|
this.fetchServerInfo((x) => {
|
||||||
|
that.setServerStatus(x);
|
||||||
|
if (!onStatusResult) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStatusResult.apply(null, [x]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async getOdooEnv(onResult) {
|
||||||
|
const that = this;
|
||||||
|
this.orm
|
||||||
|
.call("kit.code.server", "report_configuration")
|
||||||
|
.then(function (data) {
|
||||||
|
that.state.runtimeEnv = data;
|
||||||
|
that.renderOdooEnvData(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
renderOdooEnvData(data) {
|
||||||
|
this.jsonTable = new JsonViewer({
|
||||||
|
value: data,
|
||||||
|
}).render("#jsonTable");
|
||||||
|
}
|
||||||
|
async pushSpaceToCodeServer() {
|
||||||
|
const that = this;
|
||||||
|
this.orm
|
||||||
|
.call("kit.code.server", "push_space_to_code_server")
|
||||||
|
.then(function (data) {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _openHome() {
|
||||||
|
const intent = {
|
||||||
|
module: "kit_code",
|
||||||
|
file: "__manifest__.py",
|
||||||
|
lineNo: 1,
|
||||||
|
};
|
||||||
|
const that = this;
|
||||||
|
this.orm.call("kit.code.server", "get_home_link", []).then(function (data) {
|
||||||
|
const url = data.navigateTo;
|
||||||
|
that._openCodeServerPage(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
name:web.NavBar.DropdownItem
|
||||||
|
class:sub template
|
||||||
|
type:owl
|
||||||
|
url:/web/static/src/webclient/navbar/navbar.xml
|
||||||
|
file:/odoo/addons/web/static/src/webclient/navbar/navbar.xml
|
||||||
|
*/
|
||||||
|
async _openFile() {
|
||||||
|
const intent = {
|
||||||
|
name: "web.NavBar.DropdownItem",
|
||||||
|
url: "/web/static/src/webclient/navbar/navbar.xml",
|
||||||
|
file: "/odoo/addons/web/static/src/webclient/navbar/navbar.xml",
|
||||||
|
lineNo: 99,
|
||||||
|
};
|
||||||
|
const that = this;
|
||||||
|
this.orm
|
||||||
|
.call("kit.code.server", "get_editable_file", [intent])
|
||||||
|
.then(function (data) {
|
||||||
|
const url = data.navigateTo;
|
||||||
|
that._openCodeServerPage(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CodeDashboard.template = 'CodeDashboard'
|
||||||
|
registry.category("actions").add("open_code_dashboard", CodeDashboard)
|
71
packs/devs/kit_code/static/src/js/effect.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
const getAnimateCanvas =()=>{
|
||||||
|
const mycanvas = document.getElementById('codeStreamCanvas');
|
||||||
|
return mycanvas ;
|
||||||
|
}
|
||||||
|
const animate_coding_effect =()=> {
|
||||||
|
|
||||||
|
var H = window.innerHeight;
|
||||||
|
var W = window.innerWidth;
|
||||||
|
var mycanvas = getAnimateCanvas()
|
||||||
|
var ctx = mycanvas.getContext('2d');
|
||||||
|
|
||||||
|
mycanvas.height = H;
|
||||||
|
mycanvas.width = W;
|
||||||
|
|
||||||
|
var fontsize = 18;
|
||||||
|
var text = [];
|
||||||
|
var lie = Math.floor(W / fontsize);
|
||||||
|
var str = '01'
|
||||||
|
for (var i = 0; i < lie; i++) {
|
||||||
|
text.push(0);
|
||||||
|
}
|
||||||
|
ctx.font = fontsize + 'px ';
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
ctx.fillStyle = 'rgba(0,0,0,0.08)'
|
||||||
|
ctx.fillRect(0, 0, W, H);
|
||||||
|
ctx.fillStyle = randColor();
|
||||||
|
for (var i = 0; i < lie; i++) {
|
||||||
|
var index = Math.floor(Math.random() * str.length);
|
||||||
|
var x = Math.floor(fontsize * i)
|
||||||
|
var y = text[i] * fontsize
|
||||||
|
ctx.fillText(str[index], x, y);
|
||||||
|
if (y > H && Math.random() > 0.99) {
|
||||||
|
text[i] = 0
|
||||||
|
}
|
||||||
|
text[i]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randColor() {
|
||||||
|
var r = Math.ceil(Math.random() * 155) + 100;
|
||||||
|
var g = Math.ceil(Math.random() * 155) + 100;
|
||||||
|
var b = Math.ceil(Math.random() * 155) + 100;
|
||||||
|
return 'rgb(' + r + ',' + g + ',' + b + ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
var timer = setInterval(function() {
|
||||||
|
draw();
|
||||||
|
}, 1000 / 30)
|
||||||
|
}
|
||||||
|
const listenWindowSize = ()=>{
|
||||||
|
window.οnresize=function(){
|
||||||
|
var mycanvas = document.getElementById('mycanvas');
|
||||||
|
if (!mycanvas) return ;
|
||||||
|
var H = window.innerHeight;
|
||||||
|
var W = window.innerWidth;
|
||||||
|
mycanvas.height = H;
|
||||||
|
mycanvas.width = W;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.onload=function(){
|
||||||
|
//console.log('🚀 effect.js,window.onload')
|
||||||
|
if (getAnimateCanvas()){
|
||||||
|
animate_coding_effect()
|
||||||
|
listenWindowSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('🚀 effect.js')
|
91
packs/devs/kit_code/static/src/xml/code_dashboard.xml
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="kit_code.code_dashboard" xml:space="preserve">
|
||||||
|
<!-- Template for Code Dashboard -->
|
||||||
|
<t t-name="CodeDashboard">
|
||||||
|
<div class="oh_dashboards" style="height:100%;" >
|
||||||
|
<!-- <div class="debug-area" style="display:block">
|
||||||
|
<button t-on-click="pushSpaceToCodeServer" >pushSpaceToCodeServer</button>
|
||||||
|
<button t-on-click="_openFile" >openFile</button>
|
||||||
|
</div> -->
|
||||||
|
<div class="container-fluid o_dashboard" style="height:100%;">
|
||||||
|
<div class="row main-section" align="center" >
|
||||||
|
|
||||||
|
<div class=" oh-payslip " >
|
||||||
|
<div xclass="oh-card" >
|
||||||
|
|
||||||
|
<div class="oh-card-body" style="justify-content: center;">
|
||||||
|
|
||||||
|
<div style="cursor:pointer" class = 'button-grow-shadow'
|
||||||
|
t-on-click="on_ctrl_server" t-att-title='state.codeServerTip' >
|
||||||
|
|
||||||
|
<img t-att-class="state.serverStatus =='ready' ? 'status-widget-image-ready button ': 'status-widget-image-running button ' " alt="code server" />
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<span style="margin-left:20px;font-weight: bold;">Code Server </span>
|
||||||
|
<span style="margin-left:5px;">is <t t-esc="state.serverStatus"/></span>
|
||||||
|
<div style="margin-left:6px; cursor: -webkit-grab; cursor: grab;" title="Open Code Editor" t-if="state.serverStatus ==='running'" t-on-click="handleCickCodeServer" >
|
||||||
|
|
||||||
|
<span > at <t t-esc="state.coderServer.url"/></span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div style='font-size:12px;margin-left:10px;' >
|
||||||
|
<button name="" type="object"
|
||||||
|
class="btn-primary" icon="fa-star" t-on-click="on_ctrl_server">
|
||||||
|
<div>
|
||||||
|
<t t-if="state.serverStatus ==='running'">Stop</t>
|
||||||
|
<t t-else="">
|
||||||
|
Start
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- <div class="status-widget-image-loading"></div>
|
||||||
|
<div style="text-align: center; margin-top:3px; color:gray; "><t t-esc="state.codeServerTip" /></div>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style=" margin-top:-20px;" classx="col-md-4 col-sm-6" align="center">
|
||||||
|
<div style="height:100%;" >
|
||||||
|
<ul class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link active" id="console-tab" data-bs-toggle="tab" data-bs-target="#console" type="button" role="tab" aria-controls="console" aria-selected="true">Console</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="nav-link" id="odooEnv-tab" data-bs-toggle="tab" data-bs-target="#odooEnv" type="button" role="tab" aria-controls="odooEnv" aria-selected="false">Odoo Env</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="myTabContent" style=" height:100%;">
|
||||||
|
<div class="tab-pane fade show active" id="console" role="tabpanel" aria-labelledby="console-tab" style=" height:100%;">
|
||||||
|
<!-- the termynal container max-height:30vh;-->
|
||||||
|
<div data-termynal='' style=" padding:75px 45px 35px !important; width:98%; max-height:80vh " >
|
||||||
|
<div style="min-height:70vh;max-height:70vh;overflow: auto;">
|
||||||
|
<div id="termynal" data-ty-maxLines="50" style=" margin: 0px 10px 10px 10px;text-align: left; width:98%; ">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="odooEnv" role="tabpanel" aria-labelledby="odooEnv-tab" >
|
||||||
|
<div style="padding:5px 5px 5px !important; width:98%; height:100%;" >
|
||||||
|
<div><span style="font-weight: bold;">Odoo Runtime Environment</span>
|
||||||
|
</div>
|
||||||
|
<div id="jsonTable" style=" margin: 10px 10px 10px 10px;text-align: left;overflow: scroll; width:98%;max-height:75vh ">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
</templates>
|
12
packs/devs/kit_code/views/code_server/actions.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="act_window@code_server" model="ir.actions.act_window">
|
||||||
|
<field name="name">Message</field>
|
||||||
|
<field name="res_model">kit.code.server</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
21
packs/devs/kit_code/views/code_server/forms.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="viewForm@code_server" model="ir.ui.view">
|
||||||
|
<field name="name">Message Form</field>
|
||||||
|
<field name="model">kit.code.server</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="">
|
||||||
|
<sheet >
|
||||||
|
<notebook groups="base.group_user">
|
||||||
|
<page string="Message" >
|
||||||
|
<div > Time: <field name="ts" readonly="1" /> </div>
|
||||||
|
<div> Message: <field name="message" readonly="1" /></div>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
16
packs/devs/kit_code/views/code_server/lists.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="viewList@code_server" model="ir.ui.view" >
|
||||||
|
<field name="name">Message List</field>
|
||||||
|
<field name="model">kit.code.server</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<!-- <field name="name" eval="time.strftime('%Y-12-31')"/> -->
|
||||||
|
<field name="ts"/>
|
||||||
|
<field name="message"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
11
packs/devs/kit_code/views/code_server/menus.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<menuitem id="menu@root" name="Code🔨"
|
||||||
|
web_icon="kit_code,static/description/icon.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<menuitem id="menu@root_log" name="Log" parent="menu@root" action="act_window@code_server"/>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
86
packs/devs/kit_code/views/console/coding.html
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>代码雨炫酷效果:公众号AlbertYang</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
var H = window.innerHeight;
|
||||||
|
var W = window.innerWidth;
|
||||||
|
var mycanvas = document.getElementById('mycanvas');
|
||||||
|
var ctx = mycanvas.getContext('2d');
|
||||||
|
|
||||||
|
mycanvas.height = H;
|
||||||
|
mycanvas.width = W;
|
||||||
|
|
||||||
|
var fontsize = 18;
|
||||||
|
var text = [];
|
||||||
|
var lie = Math.floor(W / fontsize);
|
||||||
|
var str = '01'
|
||||||
|
for (var i = 0; i < lie; i++) {
|
||||||
|
text.push(0);
|
||||||
|
}
|
||||||
|
ctx.font = fontsize + 'px 微雅软黑';
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
ctx.fillStyle = 'rgba(0,0,0,0.08)'
|
||||||
|
ctx.fillRect(0, 0, W, H);
|
||||||
|
ctx.fillStyle = randColor();
|
||||||
|
for (var i = 0; i < lie; i++) {
|
||||||
|
var index = Math.floor(Math.random() * str.length);
|
||||||
|
var x = Math.floor(fontsize * i)
|
||||||
|
var y = text[i] * fontsize
|
||||||
|
ctx.fillText(str[index], x, y);
|
||||||
|
if (y > H && Math.random() > 0.99) {
|
||||||
|
text[i] = 0
|
||||||
|
}
|
||||||
|
text[i]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function randColor() {
|
||||||
|
var r = Math.ceil(Math.random() * 155) + 100;
|
||||||
|
var g = Math.ceil(Math.random() * 155) + 100;
|
||||||
|
var b = Math.ceil(Math.random() * 155) + 100;
|
||||||
|
return 'rgb(' + r + ',' + g + ',' + b + ')'
|
||||||
|
}
|
||||||
|
console.log(randColor())
|
||||||
|
var timer = setInterval(function() {
|
||||||
|
draw();
|
||||||
|
}, 1000 / 30)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body data-gr-c-s-loaded="true">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="" style="display:relative;">
|
||||||
|
<div id="contentDiv"style="margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
height:100px;width:100px; color: aqua;
|
||||||
|
z-index:99999;">
|
||||||
|
fdsafdsa
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<canvas id="mycanvas" style="position:absolute;z-index:-1;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
packs/devs/kit_code/views/console/console.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<!-- Action for model, tag was point by js call -->
|
||||||
|
<record id="act_client@console" model="ir.actions.client">
|
||||||
|
<field name="name">Console</field>
|
||||||
|
<field name="tag">open_code_dashboard</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem name="Console" id="menu@root_console" parent="menu@root" sequence="0"
|
||||||
|
action="act_client@console"/>
|
||||||
|
</odoo>
|
11
packs/devs/kit_code/views/page/effect.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo><data>
|
||||||
|
|
||||||
|
<template id="kit_code.code_effect_js" >
|
||||||
|
<script src="/kit_code/static/src/js/effect.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!-- console.debug('❌ effect.xml') -->
|
||||||
|
</script>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</data></odoo>
|
90
packs/devs/kit_code/views/page/web.xml
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<odoo><data>
|
||||||
|
<template id = "kit_code.home_page" >
|
||||||
|
<div class="" style="display:relative;">
|
||||||
|
<div id="contentDiv" style="margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: aqua;
|
||||||
|
z-index:99999;">
|
||||||
|
<t t-call='kit_code.home_page_feature'></t>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<canvas id="codeStreamCanvas" style="position:absolute;z-index:-1;"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<t t-call='kit_code.code_effect_js'></t>
|
||||||
|
</template>
|
||||||
|
<template id="kit_code.home_page_feature" >
|
||||||
|
<t t-set="tracer" t-value='not true'></t>
|
||||||
|
<t t-set='codeServerIsRunning' t-value="(not codeServer is None and codeServer.get('status') == 'running') "/>
|
||||||
|
<div style=' display: inline-flex; height:100%;width:100%;flex-direction: column; justify-content: center;align-items: center; font-size:25px; '>
|
||||||
|
|
||||||
|
<div t-if="error" style="color:red; ">
|
||||||
|
<div><t t-out="error"/></div>
|
||||||
|
<div><t t-out="codeServer"/></div>
|
||||||
|
<div><t t-out="nextUrl"/></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="not error">
|
||||||
|
<div style=" justify-content: center;align-items: center; " >
|
||||||
|
<div t-if="nextUrl" style="display:none">
|
||||||
|
<!-- debug -->
|
||||||
|
<span id='nextUrl' ><t t-out="nextUrl"/></span>
|
||||||
|
</div>
|
||||||
|
<div t-if="codeServerIsRunning" >
|
||||||
|
Loading <span style="font-weight: bold; "><t t-out="moduleName"/></span> ...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div t-if=" not codeServerIsRunning" >
|
||||||
|
Code Server not running, launch now?
|
||||||
|
<div align='center' style="margin-top:10px;">
|
||||||
|
<span style="cursor:pointer; font-weight: bold; color:yellow; font-size:25px;" onclick='onClickStart()' >Start⚡︎</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-if="tracer" style="background:blue;color:white">
|
||||||
|
<div>error:<t t-out="error"/></div>
|
||||||
|
<div>codeServer:<t t-out="codeServer"/></div>
|
||||||
|
<div>nextUrl:<t t-out="nextUrl"/></div>
|
||||||
|
<div>startUrl:<t t-out="startUrl"/></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script t-if="codeServerIsRunning and not nextUrl is None">
|
||||||
|
|
||||||
|
function go(){
|
||||||
|
let forwardUrl = '<t t-out="nextUrl"/>'
|
||||||
|
//console.log('forwardUrl?', forwardUrl)
|
||||||
|
if (! forwardUrl || '' === forwardUrl){
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
forwardUrl = decodeURIComponent(forwardUrl)
|
||||||
|
forwardUrl = forwardUrl.replace('amp;','')
|
||||||
|
|
||||||
|
window.location.href = forwardUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(()=>{
|
||||||
|
go();
|
||||||
|
},1000*1)
|
||||||
|
</script>
|
||||||
|
<script t-if="not codeServerIsRunning">
|
||||||
|
function onClickStart(){
|
||||||
|
let forwardUrl = '<t t-out="startUrl"/>'
|
||||||
|
//forwardUrl = decodeURIComponent(forwardUrl)
|
||||||
|
|
||||||
|
location.href = forwardUrl;
|
||||||
|
}
|
||||||
|
//console.debug('☁️ start....')
|
||||||
|
</script>
|
||||||
|
<!-- <t t-call='kit_code.code_effect_js'></t> -->
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</data></odoo>
|
21
packs/devs/kit_spy/README.rst
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
Odoo Spy🕵️
|
||||||
|
==================
|
||||||
|
* Odoo Spy🕵️ help you insight into the rendering templates of the Odoo web page,speed up development process🚀
|
||||||
|
|
||||||
|
============
|
||||||
|
|
||||||
|
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: `<static/description/index.html>`
|
||||||
|
|
6
packs/devs/kit_spy/__config__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
{
|
||||||
|
"hook_paths_test" : ["/web#","/odookit","/about-us","/contactus"],
|
||||||
|
|
||||||
|
"hook_paths" : ["*"]
|
||||||
|
}
|
28
packs/devs/kit_spy/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from . import land
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
from .__support__ import get_config_file,setup_tracer
|
||||||
|
|
||||||
|
from .land import config
|
||||||
|
from .land.trace import Tracer
|
||||||
|
|
||||||
|
from .odo import runtime
|
||||||
|
|
||||||
|
from .hooks import module_pre_init_hook, module_post_init_hook,module_uninstall_hook,set_hook_url_paths
|
||||||
|
|
||||||
|
from .odo import start_spy
|
||||||
|
|
||||||
|
|
||||||
|
def main_init():
|
||||||
|
config_data = config.load(get_config_file())
|
||||||
|
set_hook_url_paths(config_data.get('hook_paths'))
|
||||||
|
start_spy()
|
||||||
|
|
||||||
|
|
||||||
|
#-- main ---
|
||||||
|
main_init()
|
98
packs/devs/kit_spy/__manifest__.py
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ixkit - odoo spy
|
||||||
|
#
|
||||||
|
#@team : ixkit
|
||||||
|
#@author : Artificer@ixkit.com
|
||||||
|
#@date : 2024-3-15
|
||||||
|
#@version : 1.0.0
|
||||||
|
#
|
||||||
|
#-----------------------------------------------------------------------
|
||||||
|
{
|
||||||
|
"name": "Odoo Spy🕵️",
|
||||||
|
"version": "18.0.1.0.0",
|
||||||
|
|
||||||
|
"summary": """
|
||||||
|
Help you insight into the rendering Templates of the Odoo web page,light up the template snippets,speed up development process🚀
|
||||||
|
""",
|
||||||
|
|
||||||
|
"description":"Spy the rendering Templates structure",
|
||||||
|
'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', 'website'
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
],
|
||||||
|
"assets": {
|
||||||
|
|
||||||
|
"kit_spy._assets_extends_owl": [
|
||||||
|
|
||||||
|
"kit_spy/static/src/owl/call_tracer.js",
|
||||||
|
"kit_spy/static/src/owl/spy.js",
|
||||||
|
"kit_spy/static/src/owl/owl_track_xml.js",
|
||||||
|
# ("replace", "web/static/lib/owl/owl.js", "kit_spy/static/src/owl/17/owl.js"),
|
||||||
|
|
||||||
|
"kit_spy/static/src/owl/owl_meta_builder.js",
|
||||||
|
"kit_spy/static/src/owl/owl_override.js",
|
||||||
|
|
||||||
|
],
|
||||||
|
"web.assets_backend": [
|
||||||
|
|
||||||
|
("include", "kit_spy._assets_extends_owl"),
|
||||||
|
|
||||||
|
"kit_spy/static/jslib/underscore/underscore.js",
|
||||||
|
|
||||||
|
"kit_spy/static/src/tooltip/spy_popover.js",
|
||||||
|
"kit_spy/static/src/tooltip/spy_tooltip.v17.js",
|
||||||
|
|
||||||
|
"kit_spy/static/src/tooltip/service/tooltip.js",
|
||||||
|
"kit_spy/static/src/tooltip/service/tooltip_service.js",
|
||||||
|
|
||||||
|
"kit_spy/static/src/tooltip/service/spy_tooltip.scss",
|
||||||
|
"kit_spy/static/src/tooltip/xml/spy_tooltip.xml",
|
||||||
|
|
||||||
|
# extends website editor
|
||||||
|
("before","website/static/src/systray_items/edit_website.js", "kit_spy/static/src/website/systray_items/spy.website.systray.js")
|
||||||
|
],
|
||||||
|
|
||||||
|
'web.assets_frontend_lazy': [
|
||||||
|
|
||||||
|
"kit_spy/static/src/website/spy.snippets.animation.v17.js",
|
||||||
|
],
|
||||||
|
|
||||||
|
"web.assets_frontend": [
|
||||||
|
|
||||||
|
("include", "kit_spy._assets_extends_owl"),
|
||||||
|
|
||||||
|
"kit_spy/static/src/website/spy.snippets.animation.v17.js",
|
||||||
|
|
||||||
|
"kit_spy/static/jslib/underscore/underscore.js",
|
||||||
|
|
||||||
|
"kit_spy/static/src/tooltip/spy_popover.js",
|
||||||
|
"kit_spy/static/src/tooltip/spy_tooltip.v17.js",
|
||||||
|
|
||||||
|
"kit_spy/static/src/tooltip/service/tooltip.js",
|
||||||
|
"kit_spy/static/src/tooltip/service/tooltip_service.js",
|
||||||
|
|
||||||
|
"kit_spy/static/src/tooltip/service/spy_tooltip.scss",
|
||||||
|
"kit_spy/static/src/tooltip/xml/spy_tooltip.xml",
|
||||||
|
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'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,
|
||||||
|
}
|
53
packs/devs/kit_spy/__support__.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- 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_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')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
3
packs/devs/kit_spy/controllers/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import main
|
42
packs/devs/kit_spy/controllers/main.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- 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 odoo.addons.web.controllers.utils import ensure_db
|
||||||
|
|
||||||
|
from ..odo import clean_cache
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanQWebCache(env):
|
||||||
|
if True:
|
||||||
|
new_cr = env.registry.cursor()
|
||||||
|
env = api.Environment(new_cr, SUPERUSER_ID, {})
|
||||||
|
#env["ir.qweb"].clear_caches()
|
||||||
|
clean_cache(env)
|
||||||
|
|
||||||
|
class Main(http.Controller):
|
||||||
|
|
||||||
|
|
||||||
|
@http.route(
|
||||||
|
"/api/v1/spy/reset",
|
||||||
|
type="http",
|
||||||
|
auth="none",
|
||||||
|
csrf=False,
|
||||||
|
)
|
||||||
|
def reset(self, **kwargs):
|
||||||
|
ensure_db()
|
||||||
|
cleanQWebCache(http.request.env)
|
||||||
|
data = {
|
||||||
|
"success" : True
|
||||||
|
}
|
||||||
|
return request.make_json_response(data, status=200)
|
||||||
|
|
||||||
|
|
39
packs/devs/kit_spy/dev-doc/Collapse.html
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0px ;
|
||||||
|
padding: 0px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
#menu-checkbox{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#menu-checkbox:checked ~ .tag-list{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
li{
|
||||||
|
float: left;
|
||||||
|
margin: 2px;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<label for="menu-checkbox">主菜单</label>
|
||||||
|
<input id="menu-checkbox" type="checkbox" name="" value="">
|
||||||
|
<div class="tag-list">
|
||||||
|
<ul>
|
||||||
|
<li><a href="#">首页</a> </li>
|
||||||
|
<li><a href="#">正文</a> </li>
|
||||||
|
<li><a href="#">设置</a> </li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
BIN
packs/devs/kit_spy/dev-doc/backend.png
Normal file
After Width: | Height: | Size: 3.1 MiB |
90
packs/devs/kit_spy/dev-doc/coll.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<style>
|
||||||
|
/* .tab-content can be styled as you like */
|
||||||
|
main {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
color: #000;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] summary~* {
|
||||||
|
animation: sweep .5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sweep {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
margin-top: -10px
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
margin-top: 0px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details>summary::after {
|
||||||
|
position: absolute;
|
||||||
|
content: "+";
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open]>summary::after {
|
||||||
|
position: absolute;
|
||||||
|
content: "-";
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
details>summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<details open>
|
||||||
|
<summary>HTML</summary>
|
||||||
|
<div class="tab-content">
|
||||||
|
<p>your text goes here</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>CSS</summary>
|
||||||
|
<div class="tab-content">
|
||||||
|
<p>your text goes here</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>javascript</summary>
|
||||||
|
<div class="tab-content">
|
||||||
|
<p>your text goes here</p>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
1550
packs/devs/kit_spy/dev-doc/home-spy.html
Normal file
1450
packs/devs/kit_spy/dev-doc/home.html
Normal file
12
packs/devs/kit_spy/dev-doc/owl.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
root(ComponentNode)
|
||||||
|
app(owl.App){
|
||||||
|
Root: WebClient
|
||||||
|
env:{
|
||||||
|
bus: EventBus
|
||||||
|
services:[
|
||||||
|
tooltip: //register js service
|
||||||
|
]
|
||||||
|
}
|
||||||
|
rawTemplates:
|
||||||
|
}
|
186
packs/devs/kit_spy/dev-doc/product.out.xml
Normal file
182
packs/devs/kit_spy/dev-doc/product.xml
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
<t name="Products" t-name="website_sale.products">
|
||||||
|
<t t-call="website.layout">
|
||||||
|
<t t-set="additional_title">Shop</t>
|
||||||
|
<t t-set="grid_block_name">Grid</t>
|
||||||
|
<t t-set="product_block_name">Product</t>
|
||||||
|
|
||||||
|
<!-- Qweb variable defining the class suffix for navbar items.
|
||||||
|
Change accordingly to the derired visual result (eg. `primary`, `dark`...)-->
|
||||||
|
<t t-set="navClass" t-valuef="light"/>
|
||||||
|
|
||||||
|
<!-- Check for active options: the stored value may be used in sub-templates too -->
|
||||||
|
<t t-set="opt_wsale_categories" t-value="is_view_active('website_sale.products_categories')"/>
|
||||||
|
<t t-set="opt_wsale_attributes" t-value="is_view_active('website_sale.products_attributes')"/>
|
||||||
|
<t t-set="opt_wsale_filter_price" t-value="is_view_active('website_sale.filter_products_price')"/>
|
||||||
|
<t t-set="opt_wsale_filter_tags" t-value="is_view_active('website_sale.filter_products_tags')"/>
|
||||||
|
|
||||||
|
<t t-set="opt_wsale_categories_top" t-value="is_view_active('website_sale.products_categories_top')"/>
|
||||||
|
<t t-set="opt_wsale_attributes_top" t-value="is_view_active('website_sale.products_attributes_top')"/>
|
||||||
|
|
||||||
|
<t t-set="website_sale_pricelists" t-value="website.get_pricelist_available(show_visible=True)"/>
|
||||||
|
<t t-set="website_sale_sortable" t-value="website._get_product_sort_mapping()"/>
|
||||||
|
|
||||||
|
<t t-set="hasLeftColumn" t-value="opt_wsale_categories or opt_wsale_attributes"/>
|
||||||
|
|
||||||
|
<t t-set="isFilteringByPrice" t-if="opt_wsale_filter_price" t-value="float_round(available_min_price, 2) != float_round(min_price, 2) or float_round(available_max_price, 2) != float_round(max_price, 2)"/>
|
||||||
|
<t t-set="hasPricelistDropdown" t-value="website_sale_pricelists and len(website_sale_pricelists)>1"/>
|
||||||
|
<t t-set="isSortingBy" t-value="[sort for sort in website_sale_sortable if sort[0]==request.params.get('order', '')]"/>
|
||||||
|
|
||||||
|
<div id="wrap" class="js_sale o_wsale_products_page">
|
||||||
|
<div class="oe_structure oe_empty oe_structure_not_nearest" id="oe_structure_website_sale_products_1"/>
|
||||||
|
<div class="container oe_website_sale pt-2">
|
||||||
|
<div class="row o_wsale_products_main_row align-items-start flex-nowrap">
|
||||||
|
<aside t-if="hasLeftColumn" id="products_grid_before" class="d-none d-lg-block position-sticky col-3 px-3 clearfix">
|
||||||
|
<div class="o_wsale_products_grid_before_rail vh-100 ms-n2 mt-n2 pt-2 pe-lg-2 pb-lg-5 ps-2 overflow-y-scroll">
|
||||||
|
<div t-if="opt_wsale_categories" class="products_categories mb-3">
|
||||||
|
<t t-call="website_sale.products_categories_list"/>
|
||||||
|
</div>
|
||||||
|
<div class="products_attributes_filters">
|
||||||
|
<div id="wsale_products_attributes_collapse" class=" position-relative">
|
||||||
|
<form t-if="attributes or all_tags" class="js_attributes position-relative mb-2" method="get">
|
||||||
|
<input t-if="category" type="hidden" name="category" t-att-value="category.id"/>
|
||||||
|
<input type="hidden" name="search" t-att-value="search"/>
|
||||||
|
<input type="hidden" name="order" t-att-value="order"/>
|
||||||
|
<a t-if="attrib_values or tags" t-att-href="keep('/shop'+ ('/category/'+slug(category)) if category else None, attrib=0, tags=0)" t-attf-class="btn btn-{{navClass}} d-flex align-items-center py-1 mb-2">
|
||||||
|
<small class="mx-auto"><b>Clear Filters</b></small>
|
||||||
|
<i class="oi oi-close"/>
|
||||||
|
</a>
|
||||||
|
<t t-foreach="attributes" t-as="a">
|
||||||
|
<t t-cache="a,attrib_set">
|
||||||
|
<div class="accordion-item nav-item mb-1 border-0" t-if="a.value_ids and len(a.value_ids) > 1">
|
||||||
|
<h6 class="mb-3">
|
||||||
|
<b class="o_products_attributes_title d-none d-lg-block" t-field="a.name"/>
|
||||||
|
</h6>
|
||||||
|
<div t-attf-id="o_products_attributes_{{a.id}}" class="">
|
||||||
|
<t t-if="a.display_type == 'select'">
|
||||||
|
<select class="form-select css_attribute_select mb-2" name="attrib">
|
||||||
|
<option value="" selected="true">-</option>
|
||||||
|
<t t-foreach="a.value_ids" t-as="v">
|
||||||
|
<option t-att-value="'%s-%s' % (a.id,v.id)" t-esc="v.name" t-att-selected="v.id in attrib_set"/>
|
||||||
|
</t>
|
||||||
|
</select>
|
||||||
|
</t>
|
||||||
|
<div t-elif="a.display_type == 'color'" class="mb-3">
|
||||||
|
<t t-call="website_sale.o_wsale_offcanvas_color_attribute"/>
|
||||||
|
</div>
|
||||||
|
<div t-elif="a.display_type in ('radio', 'pills', 'multi')" class="flex-column mb-3">
|
||||||
|
<t t-foreach="a.value_ids" t-as="v">
|
||||||
|
<div class="form-check mb-1">
|
||||||
|
<input type="checkbox" name="attrib" class="form-check-input" t-att-id="'%s-%s' % (a.id,v.id)" t-att-value="'%s-%s' % (a.id,v.id)" t-att-checked="'checked' if v.id in attrib_set else None"/>
|
||||||
|
<label class="form-check-label fw-normal" t-att-for="'%s-%s' % (a.id,v.id)" t-field="v.name"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<t t-if="opt_wsale_filter_tags and opt_wsale_attributes" t-call="website_sale.filter_products_tags">
|
||||||
|
<t t-set="all_tags" t-value="all_tags"/>
|
||||||
|
</t>
|
||||||
|
</form>
|
||||||
|
</div></div>
|
||||||
|
<t t-if="opt_wsale_filter_price and opt_wsale_attributes" t-call="website_sale.filter_products_price"/>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<div id="products_grid" t-attf-class="#{'o_wsale_layout_list' if layout_mode == 'list' else ''} {{'col-lg-9' if hasLeftColumn else 'col-12'}}">
|
||||||
|
<t t-call="website_sale.products_breadcrumb">
|
||||||
|
<t t-set="_classes" t-valuef="d-none d-lg-flex w-100 p-0 small"/>
|
||||||
|
</t>
|
||||||
|
<div class="products_header btn-toolbar flex-nowrap align-items-center justify-content-between gap-3 mb-3">
|
||||||
|
<t t-if="is_view_active('website_sale.search')" t-call="website_sale.search">
|
||||||
|
<t t-set="search" t-value="original_search or search"/>
|
||||||
|
<t t-set="_form_classes" t-valuef="d-lg-inline {{'d-inline' if not category else 'd-none'}}"/>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-call="website_sale.pricelist_list" t-cache="pricelist">
|
||||||
|
<t t-set="_classes" t-valuef="d-none d-lg-inline"/>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-if="is_view_active('website_sale.sort')" t-call="website_sale.sort">
|
||||||
|
<t t-set="_classes" t-valuef="d-none d-lg-inline-block"/>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<div t-if="category" class="d-flex align-items-center d-lg-none me-auto">
|
||||||
|
<t t-if="not category.parent_id" t-set="backUrl" t-valuef="/shop"/>
|
||||||
|
<t t-else="" t-set="backUrl" t-value="keep('/shop/category/' + slug(category.parent_id), category=0)"/>
|
||||||
|
|
||||||
|
<a t-attf-class="btn btn-{{navClass}} me-2" t-att-href="category.parent_id and keep('/shop/category/' + slug(category.parent_id), category=0) or '/shop'">
|
||||||
|
<i class="fa fa-angle-left"/>
|
||||||
|
</a>
|
||||||
|
<h4 t-out="category.name" class="mb-0 me-auto"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-if="is_view_active('website_sale.add_grid_or_list_option')" t-call="website_sale.add_grid_or_list_option">
|
||||||
|
<t t-set="_classes" t-valuef="d-flex"/>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<button t-if="is_view_active('website_sale.sort') or opt_wsale_categories or opt_wsale_attributes or opt_wsale_attributes_top" t-attf-class="btn btn-{{navClass}} position-relative {{not opt_wsale_attributes_top and 'd-lg-none'}}" data-bs-toggle="offcanvas" data-bs-target="#o_wsale_offcanvas">
|
||||||
|
<i class="fa fa-sliders"/>
|
||||||
|
<span t-if="isFilteringByPrice or attrib_set or tags" t-attf-class="position-absolute top-0 start-100 translate-middle border border-{{navClass}} rounded-circle bg-danger p-1"><span class="visually-hidden">filters active</span></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-if="opt_wsale_categories_top" t-call="website_sale.filmstrip_categories"/>
|
||||||
|
|
||||||
|
<div t-if="original_search and products" class="alert alert-warning mt8">
|
||||||
|
No results found for '<span t-esc="original_search"/>'. Showing results for '<span t-esc="search"/>'.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-if="category">
|
||||||
|
<t t-set="editor_msg">Drag building blocks here to customize the header for "<t t-esc="category.name"/>" category.</t>
|
||||||
|
<div class="mb16" id="category_header" t-att-data-editor-message="editor_msg" t-field="category.website_description"/>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<div t-if="products" class="o_wsale_products_grid_table_wrapper pt-3 pt-lg-0">
|
||||||
|
<table class="table table-borderless h-100 m-0 o_wsale_context_thumb_cover" t-att-data-ppg="ppg" t-att-data-ppr="ppr" t-att-data-default-sort="website.shop_default_sort" t-att-data-name="grid_block_name">
|
||||||
|
<colgroup t-ignore="true">
|
||||||
|
<!-- Force the number of columns (useful when only one row of (x < ppr) products) -->
|
||||||
|
<col t-foreach="ppr" t-as="p"/>
|
||||||
|
</colgroup>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="bins" t-as="tr_product">
|
||||||
|
<t t-foreach="tr_product" t-as="td_product">
|
||||||
|
<t t-if="td_product">
|
||||||
|
<!-- We use t-attf-class here to allow easier customization -->
|
||||||
|
<td t-att-colspan="td_product['x'] != 1 and td_product['x']" t-att-rowspan="td_product['y'] != 1 and td_product['y']" t-attf-class="oe_product" t-att-data-ribbon-id="td_product['ribbon'].id" t-att-data-name="product_block_name">
|
||||||
|
<div t-attf-class="o_wsale_product_grid_wrapper position-relative h-100 o_wsale_product_grid_wrapper_#{td_product['x']}_#{td_product['y']}">
|
||||||
|
<t t-call="website_sale.products_item">
|
||||||
|
<t t-set="product" t-value="td_product['product']"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</t>
|
||||||
|
<td t-else=""/>
|
||||||
|
</t>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div t-nocache="get the actual search" t-else="" class="text-center text-muted mt128 mb256">
|
||||||
|
<t t-if="not search">
|
||||||
|
<h3 class="mt8">No product defined</h3>
|
||||||
|
<p t-if="category">No product defined in this category.</p>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<h3 class="mt8">No results</h3>
|
||||||
|
<p>No results for "<strong t-esc="search"/>"<t t-if="category"> in category "<strong t-esc="category.display_name"/>"</t>.</p>
|
||||||
|
</t>
|
||||||
|
<p t-ignore="true" groups="sales_team.group_sale_manager">Click <i>'New'</i> in the top-right corner to create your first product.</p>
|
||||||
|
</div>
|
||||||
|
<div class="products_pager d-flex justify-content-center pt-5 pb-3">
|
||||||
|
<t t-call="website.pager"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-call="website_sale.o_wsale_offcanvas"/>
|
||||||
|
</div>
|
||||||
|
<div class="oe_structure oe_empty oe_structure_not_nearest" id="oe_structure_website_sale_products_2"/>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
4
packs/devs/kit_spy/dev-doc/request.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
_request_stack = werkzeug.local.LocalStack()
|
||||||
|
request = _request_stack()
|
||||||
|
|
||||||
|
debug = request and request.session.debug
|
1
packs/devs/kit_spy/dev-doc/tooltip.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<div role="tooltip" class="o_popover popover mw-100 shadow-sm bs-popover-bottom o-popover-bottom o-popover--be" popover-id="13" style="position: fixed; top: 40px; left: 634.444px;"><div class="popover-arrow start-auto"></div><div class="o-tooltip px-2 py-1"><div class="o-tooltip"><div class="o-tooltip--string" role="tooltip"> Template : web.Dropdown</div><ul class="o-tooltip--technical" role="tooltip"><li data-item="object"><span class="o-tooltip--technical--title">name:</span>web.Dropdown</li><li data-item="object"><span class="o-tooltip--technical--title">class:</span>sub x template</li><li data-item="view_type"><span class="o-tooltip--technical--title">type:</span>owl</li><li data-item="special"><span style="cursor:pointer"><span class="o-tooltip--technical--title">url:</span>/web/static/src/core/dropdown/dropdown.xml</span></li><li data-item="special"><span style="cursor:pointer"><span class="o-tooltip--technical--title">file:</span>/Users/icoco/WorkSpace/2022/prj/python/odoo/odoo-16.0/addons/web/static/src/core/dropdown/dropdown.xml</span></li></ul></div></div></div>
|
44
packs/devs/kit_spy/dev-doc/uiview.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"id": 181,
|
||||||
|
"is_seo_optimized": false,
|
||||||
|
"website_meta_title": false,
|
||||||
|
"website_meta_description": false,
|
||||||
|
"website_meta_keywords": false,
|
||||||
|
"website_meta_og_img": false,
|
||||||
|
"seo_name": false,
|
||||||
|
"name": "Brand Promotion Message",
|
||||||
|
"model": false,
|
||||||
|
"key": "web.brand_promotion_message",
|
||||||
|
"priority": 16,
|
||||||
|
"type": "qweb",
|
||||||
|
"arch": "<t name="Brand Promotion Message" t-name="web.brand_promotion_message">\n <t t-set="odoo_logo">\n <a target="_blank" t-attf-href="http://www.odoo.com?utm_source=db&utm_medium=#{_utm_medium}" class="badge text-bg-light">\n <img alt="Odoo" src="/web/static/img/odoo_logo_tiny.png" width="62" height="20" style="width: auto; height: 1em; vertical-align: baseline;"/>\n </a>\n </t>\n <t t-set="final_message">Powered by %s%s</t>\n <t t-out="final_message % (odoo_logo, _message and (\"- \" + _message) or \"\")"/>\n </t>\n ",
|
||||||
|
"arch_base": "<t name="Brand Promotion Message" t-name="web.brand_promotion_message">\n <t t-set="odoo_logo">\n <a target="_blank" t-attf-href="http://www.odoo.com?utm_source=db&utm_medium=#{_utm_medium}" class="badge text-bg-light">\n <img alt="Odoo" src="/web/static/img/odoo_logo_tiny.png" width="62" height="20" style="width: auto; height: 1em; vertical-align: baseline;"/>\n </a>\n </t>\n <t t-set="final_message">Powered by %s%s</t>\n <t t-out="final_message % (odoo_logo, _message and (\"- \" + _message) or \"\")"/>\n </t>\n ",
|
||||||
|
"arch_db": "<t name="Brand Promotion Message" t-name="web.brand_promotion_message">\n <t t-set="odoo_logo">\n <a target="_blank" t-attf-href="http://www.odoo.com?utm_source=db&utm_medium=#{_utm_medium}" class="badge text-bg-light">\n <img alt="Odoo" src="/web/static/img/odoo_logo_tiny.png" width="62" height="20" style="width: auto; height: 1em; vertical-align: baseline;"/>\n </a>\n </t>\n <t t-set="final_message">Powered by %s%s</t>\n <t t-out="final_message % (odoo_logo, _message and (\"- \" + _message) or \"\")"/>\n </t>",
|
||||||
|
"arch_fs": "web/views/webclient_templates.xml",
|
||||||
|
"arch_updated": false,
|
||||||
|
"arch_prev": "<t name="Brand Promotion Message" t-name="web.brand_promotion_message">\n <t t-set="odoo_logo">\n <a target="_blank" t-attf-href="http://www.odoo.com?utm_source=db&utm_medium=#{_utm_medium}" class="badge text-bg-light">\n <img alt="Odoo" src="/web/static/img/odoo_logo_tiny.png" width="62" height="20" style="width: auto; height: 1em; vertical-align: baseline;"/>\n </a>\n </t>\n <t t-set="final_message">Powered by %s%s</t>\n <t t-out="final_message % (odoo_logo, _message and (\"- \" + _message) or \"\")"/>\n </t>",
|
||||||
|
"inherit_id": "ir.ui.view()",
|
||||||
|
"inherit_children_ids": "ir.ui.view(2573,)",
|
||||||
|
"field_parent": false,
|
||||||
|
"model_data_id": "ir.model.data(5280,)",
|
||||||
|
"xml_id": "web.brand_promotion_message",
|
||||||
|
"groups_id": "res.groups()",
|
||||||
|
"mode": "primary",
|
||||||
|
"active": true,
|
||||||
|
|
||||||
|
"__last_update": "datetime.datetime(2024, 2, 27, 9, 23, 51, 656358)",
|
||||||
|
"display_name": "Brand Promotion Message",
|
||||||
|
"create_uid": "res.users(1,)",
|
||||||
|
"create_date": "datetime.datetime(2024, 1, 26, 15, 12, 26, 758238)",
|
||||||
|
"write_uid": "res.users(1,)",
|
||||||
|
"write_date": "datetime.datetime(2024, 2, 27, 9, 23, 51, 656358)",
|
||||||
|
"customize_show": false,
|
||||||
|
"website_id": "website()",
|
||||||
|
"page_ids": "website.page()",
|
||||||
|
"first_page_id": "website.page()",
|
||||||
|
"track": false,
|
||||||
|
"visibility": false,
|
||||||
|
"visibility_password": false,
|
||||||
|
"visibility_password_display": "",
|
||||||
|
"theme_template_id": "theme.ir.ui.view()"
|
||||||
|
}
|
230
packs/devs/kit_spy/dev-doc/view.md
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
POST: http://localhost:8120/web/action/load
|
||||||
|
request: {"id":0,"jsonrpc":"2.0","method":"call","params":{"action_id":609,"additional_context":{}}}
|
||||||
|
|
||||||
|
response: {
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 0,
|
||||||
|
"result": {
|
||||||
|
"id": 609,
|
||||||
|
"name": "Base",
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"xml_id": "anvil_base.action_window_anvil_base_regular",
|
||||||
|
"help": false,
|
||||||
|
"binding_model_id": false,
|
||||||
|
"binding_type": "action",
|
||||||
|
"binding_view_types": "list,form",
|
||||||
|
"display_name": "Base",
|
||||||
|
"view_id": false,
|
||||||
|
"domain": false,
|
||||||
|
"context": "{}",
|
||||||
|
"res_id": 0,
|
||||||
|
"res_model": "anvil.base",
|
||||||
|
"target": "current",
|
||||||
|
"view_mode": "list,kanban,form",
|
||||||
|
"mobile_view_mode": "kanban",
|
||||||
|
"views": [
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
"list"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
"kanban"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
false,
|
||||||
|
"form"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"limit": 80,
|
||||||
|
"groups_id": [],
|
||||||
|
"search_view_id": false,
|
||||||
|
"filter": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
POST: http://localhost:8120/web/dataset/call_kw/anvil.base/get_views
|
||||||
|
request:
|
||||||
|
{"id":3,"jsonrpc":"2.0","method":"call","params":{"model":"anvil.base","method":"get_views","args":[],"kwargs":{"context":{"lang":"en_US","tz":"Europe/Brussels","uid":2,"allowed_company_ids":[1]},"views":[[false,"list"],[false,"kanban"],[false,"form"],[false,"search"]],"options":{"action_id":609,"load_filters":true,"toolbar":true}}}}
|
||||||
|
|
||||||
|
response:
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 3,
|
||||||
|
"result": {
|
||||||
|
"views": {
|
||||||
|
"list": {
|
||||||
|
"arch": "<tree>\n <field name=\"name\"/>\n <field name=\"value\" on_change=\"1\"/> \n <field name=\"value2\"/>\n </tree>",
|
||||||
|
"id": 2545,
|
||||||
|
"model": "anvil.base",
|
||||||
|
"toolbar": {}
|
||||||
|
},
|
||||||
|
"kanban": {
|
||||||
|
"arch": "<kanban string=\"anvil.base\"><templates><t t-name=\"kanban-box\"><div t-attf-class=\"oe_kanban_card oe_kanban_global_click\"><div class=\"o_kanban_card_content\"><field name=\"name\"/></div></div></t></templates></kanban>",
|
||||||
|
"id": false,
|
||||||
|
"model": "anvil.base",
|
||||||
|
"toolbar": {}
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"arch": "<form string=\"AnvilBase\">\n <sheet style=\"background-color:red\">\n <!-- <group col=\"2\" string=\"Page Details\">\n <group colspan=\"1\" string=\"Page\">\n <field name=\"name\" />\n <field name=\"value\"/>\n <label for=\"name_slugified\" string=\"URL\"/>\n <div>\n <span>/value2/</span>\n <field name=\"value2\" nolabel=\"1\" />\n \n </div>\n \n </group>\n \n </group> -->\n <notebook>\n <page string=\"View\">\n <group> \n <div style=\"background-color:red\">\n <field name=\"name\"/>\n </div> \n </group>\n </page>\n <page string=\"Value\">\n <div style=\"background-color:yellow\">\n <field name=\"value\"/>\n </div> \n </page>\n </notebook>\n </sheet>\n </form>",
|
||||||
|
"id": 2554,
|
||||||
|
"model": "anvil.base",
|
||||||
|
"toolbar": {}
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"arch": "<search string=\"anvil.base\"><field name=\"name\"/></search>",
|
||||||
|
"id": false,
|
||||||
|
"model": "anvil.base",
|
||||||
|
"toolbar": {},
|
||||||
|
"filters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"models": {
|
||||||
|
"anvil.base": {
|
||||||
|
"name": {
|
||||||
|
"change_default": false,
|
||||||
|
"name": "name",
|
||||||
|
"readonly": false,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Name",
|
||||||
|
"translate": false,
|
||||||
|
"trim": true,
|
||||||
|
"type": "char"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"change_default": false,
|
||||||
|
"group_operator": "sum",
|
||||||
|
"name": "value",
|
||||||
|
"readonly": false,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Value",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"value2": {
|
||||||
|
"change_default": false,
|
||||||
|
"group_operator": "sum",
|
||||||
|
"name": "value2",
|
||||||
|
"readonly": true,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Value2",
|
||||||
|
"type": "float"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"change_default": false,
|
||||||
|
"name": "description",
|
||||||
|
"readonly": false,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Description",
|
||||||
|
"translate": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"change_default": false,
|
||||||
|
"name": "id",
|
||||||
|
"readonly": true,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "ID",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"display_name": {
|
||||||
|
"change_default": false,
|
||||||
|
"name": "display_name",
|
||||||
|
"readonly": true,
|
||||||
|
"required": false,
|
||||||
|
"searchable": false,
|
||||||
|
"sortable": false,
|
||||||
|
"store": false,
|
||||||
|
"string": "Display Name",
|
||||||
|
"translate": false,
|
||||||
|
"trim": true,
|
||||||
|
"type": "char"
|
||||||
|
},
|
||||||
|
"create_uid": {
|
||||||
|
"change_default": false,
|
||||||
|
"context": {},
|
||||||
|
"domain": [],
|
||||||
|
"name": "create_uid",
|
||||||
|
"readonly": true,
|
||||||
|
"relation": "res.users",
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Created by",
|
||||||
|
"type": "many2one"
|
||||||
|
},
|
||||||
|
"create_date": {
|
||||||
|
"change_default": false,
|
||||||
|
"name": "create_date",
|
||||||
|
"readonly": true,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Created on",
|
||||||
|
"type": "datetime"
|
||||||
|
},
|
||||||
|
"write_uid": {
|
||||||
|
"change_default": false,
|
||||||
|
"context": {},
|
||||||
|
"domain": [],
|
||||||
|
"name": "write_uid",
|
||||||
|
"readonly": true,
|
||||||
|
"relation": "res.users",
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Last Updated by",
|
||||||
|
"type": "many2one"
|
||||||
|
},
|
||||||
|
"write_date": {
|
||||||
|
"change_default": false,
|
||||||
|
"name": "write_date",
|
||||||
|
"readonly": true,
|
||||||
|
"required": false,
|
||||||
|
"searchable": true,
|
||||||
|
"sortable": true,
|
||||||
|
"store": true,
|
||||||
|
"string": "Last Updated on",
|
||||||
|
"type": "datetime"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
POST:
|
||||||
|
http://localhost:8120/web/dataset/call_kw/anvil.base/web_read
|
||||||
|
|
||||||
|
request:
|
||||||
|
{"id":5,"jsonrpc":"2.0","method":"call","params":{"model":"anvil.base","method":"web_read","args":[[2]],"kwargs":{"context":{"lang":"en_US","tz":"Europe/Brussels","uid":2,"allowed_company_ids":[1],"bin_size":true,"params":{"id":2,"cids":1,"menu_id":413,"action":609,"model":"anvil.base","view_type":"form"}},"specification":{"name":{},"value":{},"display_name":{}}}}}
|
||||||
|
|
||||||
|
response:
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "xxxxxx",
|
||||||
|
"value": 0,
|
||||||
|
"display_name": "xxxxxx"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
33
packs/devs/kit_spy/hooks.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .odo.runtime import get_config,set_config
|
||||||
|
|
||||||
|
from .odo import start_spy, end_spy, clean_cache
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def module_pre_init_hook(env):
|
||||||
|
start_spy()
|
||||||
|
|
||||||
|
|
||||||
|
def module_post_init_hook(env):
|
||||||
|
clean_cache(env)
|
||||||
|
|
||||||
|
|
||||||
|
def module_uninstall_hook(env):
|
||||||
|
clean_cache(env)
|
||||||
|
end_spy()
|
||||||
|
|
||||||
|
def set_hook_url_paths(paths=None):
|
||||||
|
hook_url_paths = get_config('hook_url_paths')
|
||||||
|
|
||||||
|
if hook_url_paths is None:
|
||||||
|
hook_url_paths = []
|
||||||
|
if not paths is None:
|
||||||
|
hook_url_paths = hook_url_paths + paths
|
||||||
|
|
||||||
|
set_config('hook_url_paths',hook_url_paths)
|
||||||
|
|
||||||
|
return True
|
7
packs/devs/kit_spy/land/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import lang
|
||||||
|
from . import trace
|
||||||
|
from . import jsons
|
||||||
|
|
||||||
|
def smart_json(val):
|
||||||
|
jsons.SmartJson(val)
|
8
packs/devs/kit_spy/land/config/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
from ..jsons import load_py_json
|
||||||
|
|
||||||
|
def load(path):
|
||||||
|
return load_py_json(path)
|
||||||
|
|
||||||
|
|
38
packs/devs/kit_spy/land/jsons/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- 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
|
368
packs/devs/kit_spy/land/jsons/__smart_json__.py
Normal file
|
@ -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__
|
121
packs/devs/kit_spy/land/lang/__init__.py
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import pattern
|
||||||
|
from . import class_helper
|
||||||
|
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 = '<t t-name="web_editor.snippet_options_image_optimization_widgets"><t t-set="filter_label">Filter</t>'
|
||||||
|
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
|
24
packs/devs/kit_spy/land/lang/class_helper.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from types import MethodType
|
||||||
|
|
||||||
|
"""
|
||||||
|
replace a class method with new function dynamic
|
||||||
|
@clazz: target class
|
||||||
|
@func: could be global function that do not need to consider class style
|
||||||
|
@method_name: if special then replace it
|
||||||
|
"""
|
||||||
|
def set_class_method(clazz, func, method_name=None, back_orginal=False):
|
||||||
|
original_method = None
|
||||||
|
if (back_orginal):
|
||||||
|
original_method = getattr(clazz,method_name or func.__name__)
|
||||||
|
setattr(clazz, method_name or func.__name__, func)
|
||||||
|
return original_method
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
not modify clazz definition only apply on the object instance
|
||||||
|
"""
|
||||||
|
def set_instance_method(target_instance, func, method_name):
|
||||||
|
new_method = MethodType(func, target_instance)
|
||||||
|
setattr(target_instance, method_name, new_method)
|
11
packs/devs/kit_spy/land/lang/pattern.py
Normal file
|
@ -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
|
81
packs/devs/kit_spy/land/lang/timeout.py
Normal file
|
@ -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
|
3
packs/devs/kit_spy/land/trace/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from .tracer import Tracer
|
118
packs/devs/kit_spy/land/trace/tracer.py
Normal file
|
@ -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)
|
||||||
|
|
||||||
|
|
3
packs/devs/kit_spy/models/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import ir_http
|
15
packs/devs/kit_spy/models/ir_http.py
Normal file
|
@ -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
|
55
packs/devs/kit_spy/odo/__init__.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from . import runtime
|
||||||
|
from . import session
|
||||||
|
from . import spy
|
||||||
|
|
||||||
|
from ..__support__ import get_support_major_version
|
||||||
|
|
||||||
|
|
||||||
|
from ..land.trace import Tracer
|
||||||
|
|
||||||
|
_logger = Tracer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def now_times():
|
||||||
|
now = datetime.now()
|
||||||
|
current_time = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
return current_time
|
||||||
|
|
||||||
|
def start_spy():
|
||||||
|
_logger.debug("start_spy 🚀🔥...{}", now_times())
|
||||||
|
try:
|
||||||
|
result = spy.hook()
|
||||||
|
_logger.debug("hook result:{}",str(result))
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
finally:
|
||||||
|
_logger.debug("start_spy done,{}",now_times(),step=False)
|
||||||
|
|
||||||
|
def end_spy():
|
||||||
|
_logger.debug("end_spy ...{}", now_times())
|
||||||
|
try:
|
||||||
|
result = spy.unhook()
|
||||||
|
_logger.debug("unhook result:{}",str(result))
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
finally:
|
||||||
|
_logger.debug("end_spy done,{}",now_times(),step=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def clean_cache(env):
|
||||||
|
ver = get_support_major_version()
|
||||||
|
if '17' == ver :
|
||||||
|
# env.registry.clear_cache('assets')
|
||||||
|
# env.registry.clear_cache('templates')
|
||||||
|
# env.registry.clear_cache('routing')
|
||||||
|
env.registry.clear_all_caches()
|
||||||
|
|
||||||
|
return ver
|
||||||
|
# 16
|
||||||
|
env["ir.qweb"].clear_caches()
|
||||||
|
env["ir.ui.view"].clear_caches()
|
2
packs/devs/kit_spy/odo/addons/base/models/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import ir_qweb_up
|
1309
packs/devs/kit_spy/odo/addons/base/models/assetsbundle.py
Normal file
139
packs/devs/kit_spy/odo/addons/base/models/assetsbundle_up.py
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ixkit - odoo spy
|
||||||
|
#
|
||||||
|
#@purpose : inject new logic code make asset pass meta information ( url,file name etc) to js layer
|
||||||
|
#@author : Artificer@ixkit.com
|
||||||
|
#@date : 2024-3-4
|
||||||
|
#@version : 1.0.0
|
||||||
|
#
|
||||||
|
#-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
from contextlib import closing
|
||||||
|
from collections import OrderedDict
|
||||||
|
from datetime import datetime
|
||||||
|
from lxml import etree
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import base64
|
||||||
|
import copy
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import itertools
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import textwrap
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
from odoo import release, SUPERUSER_ID, _
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.modules.module import get_resource_path
|
||||||
|
# from odoo.tools import (func, misc, transpile_javascript,
|
||||||
|
# is_odoo_module, SourceMapGenerator, profiler,
|
||||||
|
# apply_inheritance_specs)
|
||||||
|
from odoo.tools.misc import file_open, html_escape as escape
|
||||||
|
from odoo.tools.pycompat import to_text
|
||||||
|
|
||||||
|
from odoo.addons.base.models.assetsbundle import WebAsset, XMLAsset, AssetError,AssetNotFound
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
EXTENSIONS = (".js", ".css", ".scss", ".sass", ".less", ".xml")
|
||||||
|
|
||||||
|
"""
|
||||||
|
class Asset{
|
||||||
|
self._filename = filename
|
||||||
|
self.url = url
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
def _to_meta_json_str(asset):
|
||||||
|
meta = {
|
||||||
|
"url" : asset.url,
|
||||||
|
"file" : asset._filename,
|
||||||
|
"asset_time": datetime.now().timestamp()
|
||||||
|
# "content": content
|
||||||
|
}
|
||||||
|
result = json.dumps(meta)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _mark_json_str(val):
|
||||||
|
result = val.replace("{", "[")
|
||||||
|
result = result.replace("}", "]")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _to_meta_str(asset):
|
||||||
|
meta = {
|
||||||
|
"url" : asset.url,
|
||||||
|
"file" : asset._filename,
|
||||||
|
# "content": content
|
||||||
|
}
|
||||||
|
result = "\"{}\"=\"{}\"".format("url",asset.url)
|
||||||
|
result = result + ";\"{}\"=\"{}\"".format("file",asset._filename)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _super_fetch_content(self):
|
||||||
|
""" Fetch content from file or database"""
|
||||||
|
try:
|
||||||
|
self.stat()
|
||||||
|
if self._filename:
|
||||||
|
with closing(file_open(self._filename, 'rb', filter_ext=EXTENSIONS)) as fp:
|
||||||
|
return fp.read().decode('utf-8')
|
||||||
|
else:
|
||||||
|
return self._ir_attach.raw.decode()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
raise AssetError('%s is not utf-8 encoded.' % self.name)
|
||||||
|
except IOError:
|
||||||
|
raise AssetNotFound('File %s does not exist.' % self.name)
|
||||||
|
except:
|
||||||
|
raise AssetError('Could not get content for %s.' % self.name)
|
||||||
|
|
||||||
|
|
||||||
|
#class MyXMLAsset(XMLAsset):
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_content(self):
|
||||||
|
"""
|
||||||
|
_content = None
|
||||||
|
_filename = None
|
||||||
|
_ir_attach = None
|
||||||
|
_id = None
|
||||||
|
"""
|
||||||
|
tracer = sys.modules['tracer']
|
||||||
|
|
||||||
|
try:
|
||||||
|
tracer.debug("🧐👽 try _fetch_content, self:{}", self, step=False)
|
||||||
|
#content = super()._fetch_content()
|
||||||
|
content = _super_fetch_content(self)
|
||||||
|
except AssetError as e:
|
||||||
|
_logger.error("Error when _fetch_content,self:%s",self, exc_info=True)
|
||||||
|
return f'<error data-asset-bundle={self.bundle.name!r} data-asset-version={self.bundle.version!r}>{json.dumps(to_text(e))}</error>'
|
||||||
|
|
||||||
|
parser = etree.XMLParser(ns_clean=True, recover=True, remove_comments=True)
|
||||||
|
root = etree.parse(io.BytesIO(content.encode('utf-8')), parser=parser).getroot()
|
||||||
|
|
||||||
|
#@@ original logic
|
||||||
|
# if root.tag in ('templates', 'template'):
|
||||||
|
# return ''.join(etree.tostring(el, encoding='unicode') for el in root)
|
||||||
|
# return etree.tostring(root, encoding='unicode')
|
||||||
|
|
||||||
|
"""
|
||||||
|
apply new logic, pass the meta information
|
||||||
|
"""
|
||||||
|
if root.tag in ('templates', 'template'):
|
||||||
|
buf = ""
|
||||||
|
for el in root:
|
||||||
|
if 't-name' in el.attrib:
|
||||||
|
meta = _to_meta_json_str(self)
|
||||||
|
el.attrib['t-meta']= meta
|
||||||
|
|
||||||
|
buf = buf + etree.tostring(el, encoding='unicode')
|
||||||
|
tracer.debug("🧐 XMLAsset->_fetch_content,template?? buf:{}", buf ,step=False)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
result = etree.tostring(root, encoding='unicode')
|
||||||
|
tracer.debug("🧐👽 XMLAsset->_fetch_content,no template? result:{}", result ,step=False)
|
||||||
|
return result
|
2726
packs/devs/kit_spy/odo/addons/base/models/ir_qweb.py
Normal file
53
packs/devs/kit_spy/odo/addons/base/models/ir_qweb_up.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ixkit - odoo spy
|
||||||
|
#
|
||||||
|
#@purpose : jnject new logic replace exist methods, append template meta information (name,url,file, db row_id, etc), render by qweb
|
||||||
|
#@author : Artificer@ixkit.com
|
||||||
|
#@date : 2024-2-15
|
||||||
|
#@version : 1.0.0
|
||||||
|
#
|
||||||
|
#-----------------------------------------------------------------------
|
||||||
|
import sys
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
|
#class IrQWeb(models.AbstractModel):
|
||||||
|
|
||||||
|
|
||||||
|
def _load(self, ref):
|
||||||
|
"""
|
||||||
|
Load the template referenced by ``ref``.
|
||||||
|
|
||||||
|
:returns: The loaded template (as string or etree) and its
|
||||||
|
identifier
|
||||||
|
:rtype: Tuple[Union[etree, str], Optional[str, int]]
|
||||||
|
"""
|
||||||
|
IrUIView = self.env['ir.ui.view'].sudo()
|
||||||
|
view = IrUIView._get(ref)
|
||||||
|
template = IrUIView._read_template(view.id)
|
||||||
|
#@@ inject spy code
|
||||||
|
template = sys.modules['spy'].inject_spy_template(template,view,ref)
|
||||||
|
#@@ end
|
||||||
|
etree_view = etree.fromstring(template)
|
||||||
|
|
||||||
|
xmlid = view.key or ref
|
||||||
|
if isinstance(ref, int):
|
||||||
|
domain = [('model', '=', 'ir.ui.view'), ('res_id', '=', view.id)]
|
||||||
|
model_data = self.env['ir.model.data'].sudo().search_read(domain, ['module', 'name'], limit=1)
|
||||||
|
if model_data:
|
||||||
|
xmlid = f"{model_data[0]['module']}.{model_data[0]['name']}"
|
||||||
|
|
||||||
|
# QWeb's ``_read_template`` will check if one of the first children of
|
||||||
|
# what we send to it has a "t-name" attribute having ``ref`` as value
|
||||||
|
# to consider it has found it. As it'll never be the case when working
|
||||||
|
# with view ids or children view or children primary views, force it here.
|
||||||
|
if view.inherit_id is not None:
|
||||||
|
for node in etree_view:
|
||||||
|
if node.get('t-name') == str(ref) or node.get('t-name') == str(view.key):
|
||||||
|
node.attrib.pop('name', None)
|
||||||
|
node.attrib.pop('id', None)
|
||||||
|
etree_view = node
|
||||||
|
break
|
||||||
|
etree_view.set('t-name', str(xmlid))
|
||||||
|
return (etree_view, view.id)
|
6
packs/devs/kit_spy/odo/addons/base/models/readme.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
"""
|
||||||
|
@purpose: Never execute; copy from odoo framework folder, only for read easy
|
||||||
|
|
||||||
|
"""
|
||||||
|
files: ir_qweb.py, assetsbundle.py
|
2
packs/devs/kit_spy/odo/root/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import http_up
|
2224
packs/devs/kit_spy/odo/root/http.py
Normal file
61
packs/devs/kit_spy/odo/root/http_up.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
hook the class Session, track the 'debug' state that identify the spy mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from odoo.http import Session
|
||||||
|
|
||||||
|
from ...land.trace import Tracer
|
||||||
|
|
||||||
|
def _log_spy(spy):
|
||||||
|
Tracer.debug("🪝🧐 _log_spy,spy:{}",spy)
|
||||||
|
|
||||||
|
|
||||||
|
def _set_debug_mode(val):
|
||||||
|
spy = sys.modules['spy']
|
||||||
|
if spy is None:
|
||||||
|
return False
|
||||||
|
if val is None or '' == val:
|
||||||
|
# if not spy.get('debug') is None:
|
||||||
|
# del spy.debug
|
||||||
|
spy.debug = None
|
||||||
|
|
||||||
|
_log_spy(spy)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
spy.debug = {
|
||||||
|
'value': val,
|
||||||
|
'ts': datetime.timestamp(datetime.now())
|
||||||
|
}
|
||||||
|
_log_spy(spy)
|
||||||
|
return True
|
||||||
|
|
||||||
|
#@ref from odoo/http.py
|
||||||
|
#class SessionUp(collections.abc.MutableMapping):
|
||||||
|
|
||||||
|
def __setattr__(self, key, val):
|
||||||
|
#print('🪝🧐 __setattr__' + str(key) + "=>" + str(val))
|
||||||
|
if key in self.__slots__:
|
||||||
|
#super().__setattr__(key, val)
|
||||||
|
super(Session, self).__setattr__(key,val)
|
||||||
|
else:
|
||||||
|
self[key] = val
|
||||||
|
|
||||||
|
if 'debug' == key:
|
||||||
|
_set_debug_mode(val)
|
||||||
|
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
if not self.__data is None:
|
||||||
|
self.__data.clear()
|
||||||
|
self.is_dirty = True
|
||||||
|
|
||||||
|
_set_debug_mode(None)
|
||||||
|
|
||||||
|
|
||||||
|
|
18
packs/devs/kit_spy/odo/runtime/__init__.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys
|
||||||
|
from . import runtime_options
|
||||||
|
from . import sys_register
|
||||||
|
from ...land.trace import Tracer
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(key):
|
||||||
|
if runtime_options.config.get(key):
|
||||||
|
return runtime_options.config[key]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_config(key,val):
|
||||||
|
sys.modules['runtime_options'].set(key,val)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
sys_register.register_instance('tracer',Tracer)
|
35
packs/devs/kit_spy/odo/runtime/runtime_options.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from odoo.tools import config
|
||||||
|
|
||||||
|
from ...land.lang.pattern import singleton
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
class RuntimeOptions(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConstError(TypeError): pass
|
||||||
|
|
||||||
|
def __setattr__(self, key, value):
|
||||||
|
# self.__dict__.update()
|
||||||
|
if key in self.__dict__:
|
||||||
|
raise self.ConstError("constant reassignment error!, key:{},value:{},dict:{} ".format(key,value, self.__dict__ ))
|
||||||
|
self.__dict__[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set(self, key,val):
|
||||||
|
return self.__setattr__(key,val)
|
||||||
|
|
||||||
|
def get(self,key):
|
||||||
|
return self.__dict__.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
sys.modules['runtime_options'] = RuntimeOptions()
|
||||||
|
|
||||||
|
|
||||||
|
RuntimeOptions().config = config
|
18
packs/devs/kit_spy/odo/runtime/sys_register.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from odoo.tools import config
|
||||||
|
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def register_instance(name,value):
|
||||||
|
sys.modules[name] = value
|
||||||
|
_logger.debug('register_instance, infrastructure name:%s, value:%s',name,value)
|
||||||
|
|
||||||
|
def unregister_instance(name):
|
||||||
|
del sys.modules[name]
|
||||||
|
_logger.debug('unregister_instance, infrastructure name:%s',name)
|
3
packs/devs/kit_spy/odo/session/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import request
|