Merge pull request 'v2.2' (#12) from v2.2 into master

Reviewed-on: cloud/cms#12
master
Steve L. Nyemba 3 weeks ago
commit 81c346ee53

@ -43,6 +43,13 @@ INVALID_FOLDER = """
# handling cli interface
cli = typer.Typer()
def get_manifest (manifest):
if not manifest.endswith('json') and os.path.isdir(manifest):
manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
return os.sep.join([manifest,'qcms-manifest.json'])
else:
return manifest
@cli.command(name="info")
def _info():
@ -174,6 +181,7 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
"""
Manage plugins list loaded plugins,
"""
manifest = get_manifest(manifest)
_config = config.get(manifest)
_root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
if os.path.exists(_root) :
@ -185,13 +193,15 @@ def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file
if not _plugins :
_msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
else:
_data = plugins.stats(_plugins)
# _data = plugins.stats(_plugins)
_data = cms.Plugin.stats(_plugins)
print (_data)
_msg = f"""{PASSED} found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}"""
if add in [True,False] and pointer :
file,fnName = pointer.split('.')
_fnpointer = plugins.load(_root,file+'.py',fnName)
# _fnpointer = plugins.load(_root,file+'.py',fnName)
_fnpointer = cms.Plugin.load(_root,file+'.py',fnName)
if _fnpointer and add:
if file not in _plugins :
_plugins[file] = []
@ -269,8 +279,8 @@ def reload (
"""
Reload a site/portal given the manifest ...
"""
_config = config.get(path)
if 'key' in _config['system']['source'] :
_config = config.get( get_manifest(path))
if 'source' in _config['system'] and 'key' in _config['system']['source'] :
f = open(_config['system']['source']['key'])
key = f.read()
f.close()
@ -283,7 +293,7 @@ def reload (
_msg = f"""{FAILED} failed to reload, status code {resp.status_code}\n{url}
"""
else:
_msg = f"""{FAILED} no secure key found in manifest"""
_msg = f"""{FAILED} no secure key found in manifest to request reload"""
print (_msg)
@cli.command(name="bootup")
def bootup (
@ -293,9 +303,10 @@ def bootup (
"""
This function will launch a site/project given the location of the manifest file
"""
if not manifest.endswith('json') and os.path.isdir(manifest):
manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
manifest = os.sep.join([manifest,'qcms-manifest.json'])
# if not manifest.endswith('json') and os.path.isdir(manifest):
# manifest = manifest if not manifest.endswith(os.sep) else os.sep.join(manifest.split(os.sep)[:-1])
# manifest = os.sep.join([manifest,'qcms-manifest.json'])
manifest = get_manifest(manifest)
index.start(manifest,port)
@cli.command(name='theme')
def handle_theme (
@ -307,6 +318,7 @@ def handle_theme (
This function will show the list available themes and can also set a theme in a manifest
"""
manifest = get_manifest(manifest)
_config = config.get(manifest)
_root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']])

@ -6,8 +6,121 @@ import copy
from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib
import importlib.util
import json
class Plugin :
#
# decorator for plugin functions, this is a preliminary to enable future developement
#
def __init__(self,**_args):
self._mimetype = _args['mimetype']
if 'method' in _args :
_method = _args['method']
if type(_method) != list :
_method = [_method]
else:
_method = ['POST','GET']
self._method = _method
def __call__(self,_callback):
def wrapper(**_args):
return _callback(**_args)
setattr(wrapper,'method',self._method)
setattr(wrapper,'mimetype',self._mimetype)
return wrapper
@staticmethod
def load(_path,_filename,_name) :
"""
This function will load external module form a given location and return a pointer to a function in a given module
:path absolute path of the file (considered plugin) to be loaded
:name name of the function to be applied
"""
# _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
if os.path.isdir(_path):
files = os.listdir(_path)
if files :
files = [name for name in files if name.endswith('.py') and name == _filename]
if files:
_path = os.sep.join([_path,files[0]])
else:
return None
else:
return None
#-- We have a file ...
# _name = _args['name']
spec = importlib.util.spec_from_file_location(_filename, _path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
#
# we need to make sure we have the plugin decorator here to make sure
return getattr(module,_name) if hasattr(module,_name) else None
@staticmethod
def stats (_config) :
"""
Returns the statistics of the plugins
"""
_data = []
for _name in _config :
_log = {"files":_name,"loaded":len(_config[_name]),"logs":json.dumps(_config[_name])}
_data.append(_log)
return pd.DataFrame(_data)
@staticmethod
def call(**_args):
"""
This function will execute a plugged-in function given
"""
# _uri = _args['uri'] if 'uri' in _args else '/'.join([_args['module'],_args['name']])
_handler= _args['handler'] #-- handler
_request= _args['request']
_uri = _args['uri'] #_request.path[1:]
#
# we need to update the _uri (if context/embeded apps)
#
_context = _handler.system()['context']
if _context :
_uri = f'{_context}/{_uri}'
_plugins = _handler.plugins()
_code = 200
if _uri in _plugins :
_config = _handler.config() #_args['config']
_pointer = _plugins[_uri]
if hasattr(_pointer,'mimetype') and _request.method in _pointer.method:
#
# we constraint the methods given their execution ...
_mimeType = _pointer.mimetype
_data = _pointer(request=_request,config=_config)
else:
_mimeType = 'application/octet-stream'
try:
_data,_mimeType = _pointer(request=_request,config=_config)
except Exception as e:
_data = _pointer(request=_request,config=_config)
if type(_data) == pd.DataFrame :
_data = _data.to_json(orient='records')
elif type(_data) in [dict,list] :
_data = json.dumps(_data)
pass
else:
_code = 404
#
# We should generate a 500 error in this case with a message ...
#
_mimeType = 'plain/html'
_data = f"""
<script>
</script>
"""
return _data,_code,{'Content-Type':_mimeType}
pass
# def _get_config (path) :
# if os.path.exists(path) :

@ -4,8 +4,8 @@ import io
import copy
from cms import disk, cloud
from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib
import importlib.util
# import importlib
# import importlib.util
"""
There are four classes at play here:
[ Initializer ] <|-- [ Module ] <|-- [ MicroService ] <--<>[ CMS ]

@ -4,6 +4,11 @@ import importlib
import importlib.util
import os
#
# Defining the decorator to be used in plugins, this will enable simple loading and assigning mimetype to the output (if any)
#
def stats (_config) :
"""
Returns the statistics of the plugins
@ -37,6 +42,9 @@ def load(_path,_filename,_name) :
spec = importlib.util.spec_from_file_location(_filename, _path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
#
# we need to make sure we have the plugin decorator here to make sure
return getattr(module,_name) if hasattr(module,_name) else None

@ -7,6 +7,7 @@ import flask
import transport
from transport import providers
import cms
from cms import Plugin
import sys
import os
import json
@ -45,8 +46,10 @@ def _getHandler (app_id,resource=None) :
_id = _getId(app_id,resource)
return _route._apps[_id]
def _getId(app_id,resource):
return '/'.join([app_id,resource]) if resource else app_id
def _getId(app_id,app_x):
if app_x not in [None,''] :
return '/'.join([app_id,app_x])
return app_id
def _setHandler (app_id,resource) :
session['app_id'] = _getId(app_id,resource)
@ -95,33 +98,14 @@ def _getIndex (app_id ,resource=None):
@_app.route("/")
def _index ():
return _getIndex('main')
# def _xindex ():
# _handler = _getHandler()
# _config = _handler.config()
# global _route
# # print ([' serving ',session.get('app_id','NA'),_handler.layout()['root']])
# _args={'system':_handler.system(skip=['source','app','data']),'layout':_handler.layout()}
# try:
# uri = os.sep.join([_config['layout']['root'], _config['layout']['index']])
# _index_page = "index.html"
# _args = _route.render(uri,'index',session.get('app_id','main'))
# # _setHandler('main')
# except Exception as e:
# # print ()
# print (e)
# _index_page = "404.html"
# return render_template(_index_page,**_args),200 if _index_page != "404.html" else 200
@_app.route("/<app>/<resource>")
@_app.route("/<app>",defaults={'resource':None})
def _aindex (app,resource=None):
_handler = _getHandler(app,resource)
_setHandler(app,resource)
_html,_code = _getIndex(app,resource)
return _html,_code
# @_app.route('/id/<uid>')
# def people(uid):
@ -144,28 +128,16 @@ def _dialog (app):
_args['title'] = _id
return render_template('dialog.html',**_args) #title=_id,html=_html)
@_app.route("/api/<module>/<name>",defaults={'app':'main','key':None})
@_app.route("/<app>/api/<module>/<name>",defaults={'key':None})
@_app.route("/<app>/<key>/<module>/<name>",defaults={'key':None})
@_app.route("/api/<module>/<name>",defaults={'app':'main','key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("/<app>/api/<module>/<name>",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("/<app>/<key>/api/<module>/<name>",methods=['GET','POST','DELETE','PUT'])
def _delegate_call(app,key,module,name):
_handler = _getHandler(app,key)
return _delegate(_handler,module,name)
# @_app.route('/api/<module>/<name>')
@_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['GET'])
@_app.route("/api/<module>/<name>",defaults={'app_id':'main','key':None})
@_app.route("/<app_id>/api/<module>/<name>",defaults={'key':None})
def _api(app_id,key,module,name) :
"""
This endpoint will load a module and make a function call
:_module entry specified in plugins of the configuration
:_name name of the function to execute
"""
_handler = _getHandler( app_id,key)
return _delegate(_handler,module,name)
# print (_handler.config()/)
uri = f'api/{module}/{name}'
return Plugin.call(uri=uri,handler=_handler,request=request)
# return _delegate(_handler,module,name)
def _delegate(_handler,module,name):
global _route
@ -186,10 +158,13 @@ def _delegate(_handler,module,name):
# _data = pointer (**_args)
# else:
# _data = pointer()
if hasattr(pointer,'mimetype') :
_data = pointer(request=request,config=_handler.config())
_mimeType = pointer.mimetype
else:
_data,_mimeType = pointer(request=request,config=_handler.config())
_data,_mimeType = pointer(request=request,config=_handler.config())
_mimeType = 'application/octet-stream' if not _mimeType else _mimeType
_mimeType = 'application/octet-stream' if not _mimeType else _mimeType
if type(_data) == pd.DataFrame :
_data = _data.to_dict(orient='records')
if type(_data) == list:
@ -201,18 +176,18 @@ def _delegate(_handler,module,name):
# @_app.route('/<app_id>/api/<module>/<name>',methods=['POST'],defaults={'key':None})
# @_app.route('/<app_id>/<key>/api/<module>/<name>',methods=['POST'],defaults={'app_id':'main','key':None})
@_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['POST'])
@_app.route("/api/<module>/<name>",defaults={'app_id':'main','key':None},methods=['POST'])
@_app.route("/<app_id>/api/<module>/<name>",defaults={'key':None},methods=['POST'])
# @_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['POST'])
# @_app.route("/api/<module>/<name>",defaults={'app_id':'main','key':None},methods=['POST'])
# @_app.route("/<app_id>/api/<module>/<name>",defaults={'key':None},methods=['POST'])
def _post (app_id,key,module,name):
# global _config
# global _route
# _handler = _route.get()
# app_id = '/'.join([app_id,key]) if key else app_id
# def _post (app_id,key,module,name):
# # global _config
# # global _route
# # _handler = _route.get()
# # app_id = '/'.join([app_id,key]) if key else app_id
_handler = _getHandler(app_id,key)
return _delegate(_handler,module,name)
# _handler = _getHandler(app_id,key)
# return _delegate(_handler,module,name)
@_app.route('/version')
def _version ():
@ -310,31 +285,6 @@ def _cms_page (app_id,resource):
_args = _route.render(_uri,_title,session.get(app_id,'main'))
return _args[_title],200
# @_app.route('/set/<id>')
# def set(id):
# global _route
# _setHandler(id)
# # _route.set(id)
# # _handler = _route.get()
# _handler = _getHandler()
# _context = _handler.system()['context']
# _uri = f'/{_context}'.replace('//','/')
# return redirect(_uri)
# @_app.route('/<id>')
# def _open(id):
# global _route
# # _handler = _route.get()
# _handler = _getHandler()
# if id not in _route._apps :
# _args = {'config':_handler.config(), 'layout':_handler.layout(),'system':_handler.system(skip=['source','app'])}
# return render_template("404.html",**_args)
# else:
# _setHandler(id)
# # _route.set(id)
# return _index()
@cli.command()
def start (

@ -8,6 +8,8 @@
border-left:8px solid #CAD5E0; margin-left:10px; font-weight: bold;
font-size:14px;
}
.source-code .fa-copy {float:right; margin:4px; cursor:pointer}
/* .source-code .fa-copy:hover */
.editor {
background-color:#f3f3f3;

@ -111,6 +111,11 @@ menu.events._dialog = function (_item,_context){
// var url = _args['url']
_item.type = (_item.type == null)? 'redirect' :_item.type
var http = HttpClient.instance()
_regex = /uri=(.+)/;
if (_item.uri.match(_regex)) {
_seg = _item.uri.match(_regex)
_item.uri = _seg[_seg.length - 1]
}
http.setHeader('uri',_item.uri)
http.setHeader('dom',(_item.title)?_item.title:'dialog')
// http.setHeader('dom',_args.text)
@ -121,6 +126,11 @@ menu.events._dialog = function (_item,_context){
})
}
menu.events._openTabs = function (_TabContentPane, _id) {
_id = _id[0] != '.' ? ('.'+_id) : _id
$(_TabContentPane).children().slideUp('fast')
$(_id).slideDown()
}
menu.events._open = function (id,uri,_context){
id = id.replace(/ /g,'-')
@ -243,6 +253,7 @@ var QCMSBasic= function(_layout,_context,_clickEvent) {
if(this.data.uri && this.data.type != 'open') {
if (this.data.type == 'dialog') {
// console.log(this.data)
menu.events._dialog(this.data,_context)
}else{
menu.events._open(menu.utils.format(this.data.text),this.data.uri,_context)
@ -313,9 +324,13 @@ var QCMSTabs = function(_layout,_context,_clickEvent){
// _button._uri = _label._uri
// if(this._layout.icons[text] != null) {
var _icon = jx.dom.get.instance('I')
_icon.className = this._layout.icons[text]
$(_label).append(_icon)
if (this._layout.icon){
var _icon = jx.dom.get.instance('I')
_icon.className = this._layout.icons[text]
$(_label).append(_icon)
}
text = ' ' + text
// }
@ -413,3 +428,22 @@ menu.init =function (_layout,_context){
}
/***
*
* Source Code
*/
if (! code){
var code = {}
}
code.copy = function(_node) {
var _code = $(_node.parentNode).text().trim().replace(/ {8}/g,'').replace(/ {4}/g,'\t').replace(/\r/g,'\n')
navigator.clipboard.writeText(_code);
$(_node).empty()
$(_node).html('<i class="fa-solid fa-check"></i>')
setTimeout(function(){
$(_node).empty()
$(_node).html('<i class="fa-regular fa-copy"></i>')
},750)
}

@ -62,7 +62,7 @@ Vanderbilt University Medical Center
var _layout = {{layout|tojson}}
sessionStorage.setItem('{{system.id}}','{{system.context|safe}}')
//sessionStorage.setItem('{{system.id}}','{{system.context|safe}}')
$(document).ready( function(){
bootup.init('{{system.id}}',_layout)

@ -1,5 +1,5 @@
__author__ = "Steve L. Nyemba"
__version__= "2.1.6"
__version__= "2.2.2"
__email__ = "steve@the-phi.com"
__license__="""
Copyright 2010 - 2024, Steve L. Nyemba, Vanderbilt University Medical Center

Loading…
Cancel
Save