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 # handling cli interface
cli = typer.Typer() 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") @cli.command(name="info")
def _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, Manage plugins list loaded plugins,
""" """
manifest = get_manifest(manifest)
_config = config.get(manifest) _config = config.get(manifest)
_root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins']) _root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
if os.path.exists(_root) : 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 : if not _plugins :
_msg = f"""{FAILED} no plugins are loaded\n\t{manifest}""" _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
else: else:
_data = plugins.stats(_plugins) # _data = plugins.stats(_plugins)
_data = cms.Plugin.stats(_plugins)
print (_data) print (_data)
_msg = f"""{PASSED} found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}""" _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 : if add in [True,False] and pointer :
file,fnName = pointer.split('.') 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 _fnpointer and add:
if file not in _plugins : if file not in _plugins :
_plugins[file] = [] _plugins[file] = []
@ -269,8 +279,8 @@ def reload (
""" """
Reload a site/portal given the manifest ... Reload a site/portal given the manifest ...
""" """
_config = config.get(path) _config = config.get( get_manifest(path))
if 'key' in _config['system']['source'] : if 'source' in _config['system'] and 'key' in _config['system']['source'] :
f = open(_config['system']['source']['key']) f = open(_config['system']['source']['key'])
key = f.read() key = f.read()
f.close() f.close()
@ -283,7 +293,7 @@ def reload (
_msg = f"""{FAILED} failed to reload, status code {resp.status_code}\n{url} _msg = f"""{FAILED} failed to reload, status code {resp.status_code}\n{url}
""" """
else: else:
_msg = f"""{FAILED} no secure key found in manifest""" _msg = f"""{FAILED} no secure key found in manifest to request reload"""
print (_msg) print (_msg)
@cli.command(name="bootup") @cli.command(name="bootup")
def bootup ( def bootup (
@ -293,9 +303,10 @@ def bootup (
""" """
This function will launch a site/project given the location of the manifest file This function will launch a site/project given the location of the manifest file
""" """
if not manifest.endswith('json') and os.path.isdir(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]) # 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 = os.sep.join([manifest,'qcms-manifest.json'])
manifest = get_manifest(manifest)
index.start(manifest,port) index.start(manifest,port)
@cli.command(name='theme') @cli.command(name='theme')
def handle_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 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) _config = config.get(manifest)
_root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']]) _root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']])

@ -6,8 +6,121 @@ import copy
from jinja2 import Environment, BaseLoader, FileSystemLoader from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib import importlib
import importlib.util 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) : # def _get_config (path) :
# if os.path.exists(path) : # if os.path.exists(path) :

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

@ -4,6 +4,11 @@ import importlib
import importlib.util import importlib.util
import os 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) : def stats (_config) :
""" """
Returns the statistics of the plugins Returns the statistics of the plugins
@ -37,6 +42,9 @@ def load(_path,_filename,_name) :
spec = importlib.util.spec_from_file_location(_filename, _path) spec = importlib.util.spec_from_file_location(_filename, _path)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) 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 return getattr(module,_name) if hasattr(module,_name) else None

@ -7,6 +7,7 @@ import flask
import transport import transport
from transport import providers from transport import providers
import cms import cms
from cms import Plugin
import sys import sys
import os import os
import json import json
@ -45,8 +46,10 @@ def _getHandler (app_id,resource=None) :
_id = _getId(app_id,resource) _id = _getId(app_id,resource)
return _route._apps[_id] return _route._apps[_id]
def _getId(app_id,resource): def _getId(app_id,app_x):
return '/'.join([app_id,resource]) if resource else app_id if app_x not in [None,''] :
return '/'.join([app_id,app_x])
return app_id
def _setHandler (app_id,resource) : def _setHandler (app_id,resource) :
session['app_id'] = _getId(app_id,resource) session['app_id'] = _getId(app_id,resource)
@ -95,33 +98,14 @@ def _getIndex (app_id ,resource=None):
@_app.route("/") @_app.route("/")
def _index (): def _index ():
return _getIndex('main') 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>/<resource>")
@_app.route("/<app>",defaults={'resource':None}) @_app.route("/<app>",defaults={'resource':None})
def _aindex (app,resource=None): def _aindex (app,resource=None):
_handler = _getHandler(app,resource) _handler = _getHandler(app,resource)
_setHandler(app,resource) _setHandler(app,resource)
_html,_code = _getIndex(app,resource) _html,_code = _getIndex(app,resource)
return _html,_code return _html,_code
# @_app.route('/id/<uid>') # @_app.route('/id/<uid>')
# def people(uid): # def people(uid):
@ -144,28 +128,16 @@ def _dialog (app):
_args['title'] = _id _args['title'] = _id
return render_template('dialog.html',**_args) #title=_id,html=_html) return render_template('dialog.html',**_args) #title=_id,html=_html)
@_app.route("/api/<module>/<name>",defaults={'app':'main','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}) @_app.route("/<app>/api/<module>/<name>",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("/<app>/<key>/<module>/<name>",defaults={'key':None}) @_app.route("/<app>/<key>/api/<module>/<name>",methods=['GET','POST','DELETE','PUT'])
def _delegate_call(app,key,module,name): def _delegate_call(app,key,module,name):
_handler = _getHandler(app,key) _handler = _getHandler(app,key)
return _delegate(_handler,module,name) # print (_handler.config()/)
uri = f'api/{module}/{name}'
# @_app.route('/api/<module>/<name>') return Plugin.call(uri=uri,handler=_handler,request=request)
@_app.route("/<app_id>/<key>/api/<module>/<name>", methods=['GET']) # return _delegate(_handler,module,name)
@_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)
def _delegate(_handler,module,name): def _delegate(_handler,module,name):
global _route global _route
@ -186,7 +158,10 @@ def _delegate(_handler,module,name):
# _data = pointer (**_args) # _data = pointer (**_args)
# else: # else:
# _data = pointer() # _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
@ -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>/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'],defaults={'app_id':'main','key':None})
@_app.route("/<app_id>/<key>/api/<module>/<name>", 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("/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>/api/<module>/<name>",defaults={'key':None},methods=['POST'])
def _post (app_id,key,module,name): # def _post (app_id,key,module,name):
# global _config # # global _config
# global _route # # global _route
# _handler = _route.get() # # _handler = _route.get()
# app_id = '/'.join([app_id,key]) if key else app_id # # app_id = '/'.join([app_id,key]) if key else app_id
_handler = _getHandler(app_id,key) # _handler = _getHandler(app_id,key)
return _delegate(_handler,module,name) # return _delegate(_handler,module,name)
@_app.route('/version') @_app.route('/version')
def _version (): def _version ():
@ -310,31 +285,6 @@ def _cms_page (app_id,resource):
_args = _route.render(_uri,_title,session.get(app_id,'main')) _args = _route.render(_uri,_title,session.get(app_id,'main'))
return _args[_title],200 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() @cli.command()
def start ( def start (

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

@ -111,6 +111,11 @@ menu.events._dialog = function (_item,_context){
// var url = _args['url'] // var url = _args['url']
_item.type = (_item.type == null)? 'redirect' :_item.type _item.type = (_item.type == null)? 'redirect' :_item.type
var http = HttpClient.instance() 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('uri',_item.uri)
http.setHeader('dom',(_item.title)?_item.title:'dialog') http.setHeader('dom',(_item.title)?_item.title:'dialog')
// http.setHeader('dom',_args.text) // 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){ menu.events._open = function (id,uri,_context){
id = id.replace(/ /g,'-') 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.uri && this.data.type != 'open') {
if (this.data.type == 'dialog') { if (this.data.type == 'dialog') {
// console.log(this.data)
menu.events._dialog(this.data,_context) menu.events._dialog(this.data,_context)
}else{ }else{
menu.events._open(menu.utils.format(this.data.text),this.data.uri,_context) 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 // _button._uri = _label._uri
// if(this._layout.icons[text] != null) { // if(this._layout.icons[text] != null) {
if (this._layout.icon){
var _icon = jx.dom.get.instance('I') var _icon = jx.dom.get.instance('I')
_icon.className = this._layout.icons[text] _icon.className = this._layout.icons[text]
$(_label).append(_icon) $(_label).append(_icon)
}
text = ' ' + text 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}} var _layout = {{layout|tojson}}
sessionStorage.setItem('{{system.id}}','{{system.context|safe}}') //sessionStorage.setItem('{{system.id}}','{{system.context|safe}}')
$(document).ready( function(){ $(document).ready( function(){
bootup.init('{{system.id}}',_layout) bootup.init('{{system.id}}',_layout)

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

Loading…
Cancel
Save