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

Reviewed-on: cloud/cms#17
pull/18/head
Steve L. Nyemba 2 months ago
commit 1504a5e9db

@ -21,11 +21,14 @@ import cms
from cms import index
from cms.engine.config.structure import Layout, System
from cms.engine import project, config, themes, plugins
from cms.engine import project, themes
import cms.engine.config
import cms.engine.project
import pandas as pd
import requests
import plugin_ix
from rich.table import Table
from rich import print
start = index.start
__doc__ = f"""
Built and designed by Steve L. Nyemba, steve@the-phi.com
@ -33,8 +36,10 @@ version {meta.__version__}
{meta.__license__}"""
PASSED = ' '.join(['[',colored('\u2713', 'green'),']'])
FAILED= ' '.join(['[',colored('\u2717','red'),']'])
# PASSED = ' '.join(['[',colored('\u2713', 'green'),']'])
# FAILED= ' '.join(['[',colored('\u2717','red'),']'])
FAILED = '[ [red] \u2717 [/red] ]'
PASSED = '[ [green] \u2713 [/green] ]'
INVALID_FOLDER = """
{FAILED} Unable to proceed, could not find project manifest. It should be qcms-manifest.json
@ -43,6 +48,20 @@ INVALID_FOLDER = """
# handling cli interface
cli = typer.Typer()
def to_Table(df: pd.DataFrame):
"""Displays a Pandas DataFrame as a rich table."""
table = Table(show_header=True, header_style="bold magenta")
for col in df.columns:
table.add_column(col)
for _, row in df.iterrows():
table.add_row(*row.astype(str).tolist())
# console.print(table)
return table
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])
@ -63,9 +82,12 @@ def _info():
@cli.command(name='setup')
# def set_app (host:str="0.0.0.0",context:str="",port:int=8084,debug=True):
def set_app (host:Annotated[str,typer.Argument(help="bind host IP address")]="0.0.0.0",
context:Annotated[str,typer.Argument(help="if behind a proxy server (no forward slash needed)")]="",
port:Annotated[int,typer.Argument(help="port on which to run the application")]=8084,
def set_app (
manifest:Annotated[str,typer.Argument(help="path to manifest or manifest folder")],
port:int=typer.Option(default=None,help="port on which to run the application"),
host:str=typer.Option(default='0.0.0.0',help="bind host IP address"),
context:str=typer.Option(default='',help="if behind a proxy server (no forward slash needed)"),
debug:Annotated[bool,typer.Argument(help="set debug mode on|off")]=True):
"""
Setup application access i.e port, debug, and/or context
@ -73,27 +95,30 @@ def set_app (host:Annotated[str,typer.Argument(help="bind host IP address")]="0.
"""
global INVALID_FOLDER
_config = config.get()
path = get_manifest(manifest)
_config = cms.engine.config.get(path)
if _config :
# _system = _config['system']['app']
_app = _config['system']['app']
_app['host'] = host
_app['port'] = port
_app['debug'] = debug
_app['host'] = host if host != _app['host'] else _app['host']
_app['port'] = port if port else _app['port']
_app['debug'] = debug if _app['debug'] != debug else _app['debug']
_config['system']['context'] = context
_config['app'] = _app
config.write(_config)
_msg = f"""{PASSED} Successful update, good job !
cms.engine.config.write(_config,path)
_msg = f"""{PASSED} [bold] {_config['layout']['header']['title']}[/bold]: Successful update, good job !
"""
else:
_msg = INVALID_FOLDER
print (_msg)
@cli.command(name='cloud')
# def set_cloud(path:str): #url:str,uid:str,token:str):
def set_cloud(path:Annotated[str,typer.Argument(help="path of the auth-file for the cloud")]): #url:str,uid:str,token:str):
def set_cloud(manifest:Annotated[str,typer.Argument(help="path of the auth-file for the cloud")]): #url:str,uid:str,token:str):
"""
Setup qcms to generate a site from files on nextcloud
The path must refrence auth-file (data-transport)
"""
path = get_manifest(manifest)
if os.path.exists(path):
f = open (path)
_auth = json.loads(f.read())
@ -101,11 +126,11 @@ def set_cloud(path:Annotated[str,typer.Argument(help="path of the auth-file for
url = _auth['url']
if os.path.exists('qcms-manifest.json') :
_config = config.get()
_config = cms.engine.config.get()
_config['system']['source'] = path #{'id':'cloud','auth':{'url':url,'uid':uid,'token':token}}
config.write(_config)
cms.engine.config.write(_config)
title = _config['layout']['header']['title']
_msg = f"""{PASSED} Successfully update, good job!
_msg = f"""{PASSED} [bold]{_config['system']['layout']['header']['title']}[/bold] : successfully update, good job!
{url}
"""
else:
@ -132,16 +157,17 @@ def secure(
f.write(str(uuid.uuid4()))
f.close()
#
_config = config.get(manifest)
manifest = get_manifest(manifest)
_config = cms.engine.config.get(manifest)
if 'source' not in _config['system']:
_config['system']['source'] = {'id':'disk'}
_config['system']['source']['key'] = keyfile
config.write(_config,manifest)
_msg = f"""{PASSED} A key was generated and written to {keyfile}
cms.engine.config.write(_config,manifest)
_msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold] : A key was generated and written to {keyfile}
use this key in header to enable reload of the site ...`
"""
else:
_msg = f"""{FAILED} Could NOT generate a key, because it would seem you already have one
_msg = f"""{FAILED} [bold]{_config['system']['layout']['header']['title']}[/bold] : could [bold]NOT[/bold] generate a key, because it would seem you already have one
Please manually delete {keyfile}
@ -173,8 +199,9 @@ def load(**_args):
return getattr(module,_name) if hasattr(module,_name) else None
@cli.command(name='plugins')
def plug_info (manifest:Annotated[str,typer.Argument(help="path to manifest file")],
def plugin_manager (manifest:Annotated[str,typer.Argument(help="path to manifest file")],
show:bool=typer.Option(default=False,help="list plugins loaded"),
add: Annotated[Optional[bool],typer.Option("--register/--unregister",help="add/remove a plugin to manifest use with --pointer option")] = None,
pointer:str=typer.Option(default=None,help="pointer is structured as 'filename.function'")
@ -183,53 +210,67 @@ 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) :
_config = cms.engine.config.get(manifest)
if _config :
_root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
else :
_root = None
if _root and os.path.exists(_root) :
# files = os.listdir(_root)
_msg = f"""{FAILED} no operation was specified, please use --help option"""
# if 'plugins' in _config :
_plugins = _config['plugins'] if 'plugins' in _config else {}
if show :
if not _plugins :
_msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
else:
# _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}"""
_data = []
_plugConf = _config['plugins']
for _name in _plugConf :
_log = {"files":_name,"loaded":len(_plugConf[_name]),"functions":json.dumps(_plugConf[_name])}
_data.append(_log)
_data= pd.DataFrame(_data)
# # # _data = plugins.stats(_plugins)
# # _data = cms.Plugin.stats(_plugins)
print (to_Table(_data))
_msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: 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 = cms.Plugin.load(_root,file+'.py',fnName)
if _fnpointer and add:
# _fnpointer = cms.Plugin.load(_root,file+'.py',fnName)
_ploader = plugin_ix.Loader(file = os.sep.join([_root,file+'.py']))
if add and _ploader.has(fnName):
if file not in _plugins :
_plugins[file] = []
if fnName not in _plugins[file] :
_plugins[file].append(fnName)
_msg = f"""{PASSED} registered {pointer}, use the --show option to list loaded plugins\n{manifest} """
_msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: registered {pointer}, use the --show option to list loaded plugins"""
else:
_msg = f"""{FAILED} could not register {pointer}, it already exists\n\t{manifest} """
_msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: could not register {pointer}, it already exists"""
elif add is False and file in _plugins:
_plugins[file] = [_name.strip() for _name in _plugins[file] if _name.strip() != fnName.strip() ]
_msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: unregistered {pointer}, use the --show option to list loaded plugins """
_msg = f"""{PASSED} unregistered {pointer}, use the --show option to list loaded plugins\n{manifest} """
#
# We need to write this down !!
# #
# # We need to write this down !!
if add in [True,False] :
_config['plugins'] = _plugins
config.write(_config,manifest)
cms.engine.config.write(_config,manifest)
# else:
# _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
# _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: no plugins are loaded """
print()
print(_msg)
else:
_msg = f"""{FAILED} no plugin folder found """
pass
_msg = f"""{FAILED} No plugin folder could be found in {manifest}"""
print (_msg)
@cli.command (name='create')
@ -263,7 +304,7 @@ def create(folder:Annotated[str,typer.Argument(help="path of the project folder"
#
# Setup Project on disk
#
project.make(folder=folder,config=_config)
cms.engine.project.make(folder=folder,config=_config)
print (f"""{PASSED} created project at {folder} """)
else:
print ()
@ -281,7 +322,7 @@ def reload (
Reload a site/portal given the manifest ...
"""
path = get_manifest(path)
_config = config.get( path)
_config = cms.engine.config.get( path)
if 'source' in _config['system'] and 'key' in _config['system']['source'] :
_spath = _config['system']['source']['key']
# f = open(_config['system']['source']['key'])
@ -298,7 +339,7 @@ def reload (
url = f"http://localhost:{_port}/reload"
resp = requests.post(url, headers={"key":key})
if resp.status_code == 200 :
_msg = f"""{PASSED} successfully reloaded {url}"""
_msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold] : successfully reloaded {url}"""
else:
_msg = f"""{FAILED} failed to reload, status code {resp.status_code}\n{url}
"""
@ -329,7 +370,7 @@ def handle_theme (
"""
manifest = get_manifest(manifest)
_config = config.get(manifest)
_config = cms.engine.config.get(manifest)
_root = os.sep.join( manifest.split(os.sep)[:-1]+[_config['layout']['root']])
if show :
@ -353,8 +394,8 @@ def handle_theme (
# values.sort()
# _df['installed'] = values
else:
_df = f"""{FAILED} No themes were found in registry,\ncurl {themes.URL}/api/themes/List (QCMS_HOST_URL should be set)"""
print (_df)
_df = f"""{FAILED} [bold]{_config['system']['layout']['header']['title']}[/bold] : No themes were found in registry,\ncurl {themes.URL}/api/themes/List (QCMS_HOST_URL should be set)"""
print (to_Table(_df))
if name and not show:
# we need to install the theme, i.e download it and update the configuration
#
@ -374,11 +415,11 @@ def handle_theme (
# Let us update the configuration file ...
#
_config['system']['theme'] = name
config.write(_config,manifest)
_msg = f"""{PASSED} successfully downloaded {name} \n{PASSED} updated manifest {manifest}
cms.engine.config.write(_config,manifest)
_msg = f"""{PASSED} [bold]{_config['system']['layout']['header']['title']}[/bold] : successfully downloaded {name} \n{PASSED} updated manifest {manifest}
"""
except Exception as e:
_msg = f"""{FAILED} operation failed "{str(e)}"
_msg = f"""{FAILED} [[bold]{_config['system']['layout']['header']['title']}[/bold] : operation failed "{str(e)}"
"""
pass

@ -8,6 +8,8 @@ import importlib
import importlib.util
import json
from . import apexchart
class Plugin :
#
# decorator for plugin functions, this is a preliminary to enable future developement
@ -27,45 +29,45 @@ class Plugin :
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 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):
@ -122,59 +124,56 @@ class Plugin :
return _data,_code,{'Content-Type':_mimeType}
pass
# def _get_config (path) :
# if os.path.exists(path) :
# f = open(path)
# _conf = json.loads(f.read())
# f.close()
# else:
# _conf = {}
# return _conf
# def _isvalid(_allowed,**_args):
# if not list(set(_allowed) - set(_args.keys())) :
# _pargs = {}
# for key in _allowed :
# _pargs [key] = _args[key]
# return _pargs
# return False
# def write_config(_config, path):
# f = open(path,'w')
# f.write( json.dumps(_config)) ;
# f.close()
# def _system(**_args):
# """
# Both version and context must be provided otherwise they are ignored
# :version
# :context context
# """
# _info = _isvalid(['version','context'],**_args)
# _info['theme'] = 'default.css'
# _info = _info if _info else {'version':'0.0.0','context':'','theme':'default.css'}
# if _info :
# _info['logo'] = None if 'logo' not in _args else _args['logo']
# #
# # There is an aggregation entry here in app
# _appInfo = {'debug':True,'port':8084,'threaded':True,'host':'0.0.0.0'}
# if 'app' not in _args:
# _info['app'] = _appInfo
# else:
# _info['app'] = dict(_appInfo,**_args['app'])
# return _info
# def _header(**_args):
# return _isvalid(['logo','title','subtitle'],**_args)
# def _layout(**_args):
# _info = _isvalid(['root','index'],**_args)
# _info['on'] = {"load":{},"error":{}}
# _url = 'qcms.co'
# _overwrite = {"folder1":{"type":"redirect","url":_url},"folder2":{"type":"dialog"},"folder3":{"type":"dialog","url":_url}}
# _info['icons'] = {"comment":"use folder names as keys and fontawesome type as values to add icons to menu"}
# _info["api"] = {"comment":"use keys as uri and function calls as values"}
# _info['map'] = {},
# _info['order'] = {'menu':[]}
# _info['overwrite'] = _overwrite
# return _info
class delegate:
def __call__(self,**_args) :
_handler= _args['handler'] #-- module 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]
_data = {}
if hasattr(_pointer,'mimetype') and hasattr(_pointer,'method'):
#
# we constraint the methods given their execution ...
_mimeType = _pointer.mimetype
if _request.method in _pointer.method :
_data = _pointer(request=_request,config=_config)
elif not hasattr(_pointer,'mimetype'):
_data,_mimeType = _pointer(request=_request,config=_config)
else:
_mimeType = 'application/octet-stream'
# _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>
//
// I should
_msg = '<div class="border-round" style="display:grid; grid-template-columns:100px auto; gap:8px; align-content:center"><div class="large-text">404</div>,div> Resource NOT found</di><div>'
</script>
"""
return _data,_code,{'Content-Type':_mimeType}

@ -0,0 +1,185 @@
import numpy as np
import pandas as pd
class chart :
@staticmethod
def get(_data,_config) :
# r = {}
# for _key in _config :
# r[_key] = {'about':_config[_key]['about'],'chart':[]}
# _pointers = _config[_key]['apply']
# _pointers = [_pointers] if type(_pointers) == str else _pointers
# r[_key]['chart'] += [getattr(chart,_name)(_data,_config[_key]) for _name in _pointers if hasattr(chart,_name)]
# return [r]
r = {}
for _key in _config :
_options = _config[_key]['options']
r[_key] = {'about':_config[_key]['about'],'css':_config[_key]['css'],'charts':[]}
_charts = []
for _itemOption in _options :
_type = _itemOption['type']
if hasattr(chart,_type) :
_pointer = getattr(chart,_type)
_chartOption = _pointer(_data,_itemOption)
_tag = 'options' if _type != 'scalar' else 'html'
if 'title' in _itemOption and _itemOption['type'] != 'scalar' :
_chartOption['title'] = {'text':_itemOption['title'],'align':'center'}
_chartOption['legend'] = {'position':'bottom','itemMargin':{'horizontal':4,'vertical':10}}
_chartOption['chart']['height'] = 300
# _chartOption['chart']['height'] = '100%'
# _chartOption['responsive'] = [{'breakpoint':480,'options':{'chart':{'width':300}}}]
_charts.append ({"type":_type,_tag:_chartOption})
if _charts :
r[_key]['charts'] = _charts
# for _pointer in _pointers :
# r[_key]['chart'].append(_pointer(_data,_config[_key]))
# _pointers = [getattr(chart,_name) for _name in _config[_key]['options']['apply'] if hasattr(chart,_name)]
# r[_key] = {'about':_config[_key]['about'],'chart':[]}
# for _pointer in _pointers :
# r[_key]['chart'].append(_pointer(_data,_config[_key]))
return r
@staticmethod
def format_digit (value):
if value > 1000000 :
return np.divide(value,1000000).round(2).astype(str) + ' M'
elif value > 1000 :
return np.divide(value,1000).round(2).astype(str)+ ' K'
return value
@staticmethod
def scalar(_data,_config) :
"""
Only focusing on axis.y
"""
_columns = _config['axis']['y']
if _data.shape[0] > 1 :
_apply = 'sum' if 'apply' not in _config else _config['apply']
# values = _data[_columns].sum().values.tolist()
values = getattr(_data[_columns],_apply)().values.round(3).tolist()
else:
values = _data[_columns].values.tolist()[0]
_html = [f'<div class="scalar"><div class="value">{chart.format_digit(values[_index])}</div><div class="label">{_columns[_index].replace("_"," ") }</div></div>' for _index in np.arange(len(values))]
return ' '.join(_html)
@staticmethod
def donut(_data,_config):
options = {"chart":{"type":"donut"}}
_yaxis = _config['axis']['y']
_apply = 'sum' if 'apply' not in _config else _config['apply']
# options['series'] = _data[_yaxis].sum().values.tolist()
options['series'] = getattr(_data[_yaxis],_apply)().values.round(3).tolist()
options['labels'] = [_name.replace('_',' ').upper() for _name in _yaxis]
options["dataLabels"]= {
"enabled": False
}
return options
@staticmethod
def column(_data,_config):
if 'apply' in _config :
_fn = _config['apply']
_yaxis = _config['axis']['y']
_values = getattr(_data[_yaxis],_fn)().values #.sum()
_data = (pd.DataFrame([dict(zip(_yaxis,_values))]))
options = chart.barStacked(_data,_config)
options['chart'] = {'type':'bar'}
if 'title' in _config :
options['title'] = {'text':_config['title'],'align':'center','style':{'text-transform':'upperCase','fontSize':'18px'}}
pass
options['stroke'] = {'show':True,'width':2,'colors':['transparent']}
if _data.shape[0] == 1:
options['xaxis']['categories'] = [_name.replace('_',' ').upper() for _name in _config['axis']['x']]
# options['plotOptions'] = {'bar':{'columnWidth':'55%'}}
return options
@staticmethod
def barStacked(_data,_config):
options = {"series":[], "chart": {
"type": 'bar','stacked':True}
}
# options['plotOptions'] = {'bar':{'horizontal':True}}
# options['legend'] = {'position':'bottom'} # {'position':'right','horizontalAlign':'left','offsetX':40}
_xaxis = _data[_config['axis']['x']].values.tolist()
options["xaxis"]={"categories":_xaxis}
for _col in _config['axis']['y'] :
options['series'] += [{'name':_col.replace('_',' ').upper(), 'data':_data[_col].tolist()}]
return options
@staticmethod
def radialBar (_data,_config) :
_options = {
"series": _config["axis"]["y"],
"labels": _config["axis"]["x"],
"chart": {
"type": 'radialBar',
"offsetY": -20,
"sparkline": {
"enabled": True
}
},
# "plotOptions": {
# "radialBar": {
# "startAngle": -90,
# "endAngle": 90,
# "track": {
# "background": "#e7e7e7",
# "strokeWidth": '97%',
# "margin": 5,
# "dropShadow": {
# "enabled": True,
# "top": 2,
# "left": 0,
# "color": '#999',
# "opacity": 1,
# "blur": 2
# }
# },
# "dataLabels": {
# "name": {
# "show": False
# },
# "value": {
# "offsetY": -2,
# "fontSize": '18px'
# }
# }
# }
# },
# "grid": {
# "padding": {
# "top":10
# }
# },
}
return _options
@staticmethod
def barGrouped (_data,_config):
"""
"""
options = {"series":[],"chart":{"type":"bar"},"plotOptions": {
"bar": {
"horizontal": True,
"dataLabels": {
"position": 'top',
}}},
}
_yaxis = _config["axis"]["y"]
for _name in _yaxis :
options["series"] += [{'name':_name.replace('_',' ').upper(),"data":_data[_name].tolist()}]
#
# _xaxis
_xaxis = _config['axis']['x']
options['xaxis'] = {'categories':_data[_xaxis].values.tolist()}
return options

@ -8,6 +8,7 @@ import copy
import mistune
from mistune import markdown
import re
import plugin_ix
def folders (_path,_config):
"""
@ -144,28 +145,33 @@ def plugins (**_args):
return {key:read}
_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')]
if files:
_uri = [_path,files[0]]
if _context :
_uri = [_context] + _uri
_path = os.sep.join(_uri)
else:
return None
else:
#
# LOG: not a file
return None
#-- We have a file ...
_name = _args['name']
spec = importlib.util.spec_from_file_location(_name, _path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
#
# LOG This plugin ....
return getattr(module,_name) if hasattr(module,_name) else None
loader = plugin_ix.Loader(file=_path)
if _args['name'] in loader.names() :
_pointer = loader.get(_args['name'])
return _pointer
return None
# if os.path.isdir(_path):
# files = os.listdir(_path)
# if files :
# files = [name for name in files if name.endswith('.py')]
# if files:
# _uri = [_path,files[0]]
# if _context :
# _uri = [_context] + _uri
# _path = os.sep.join(_uri)
# else:
# return None
# else:
# #
# # LOG: not a file
# return None
# #-- We have a file ...
# _name = _args['name']
# spec = importlib.util.spec_from_file_location(_name, _path)
# module = importlib.util.module_from_spec(spec)
# spec.loader.exec_module(module)
# #
# # LOG This plugin ....
# return getattr(module,_name) if hasattr(module,_name) else None

@ -140,8 +140,9 @@ class Initializer :
if 'key' in _source :
#
_path = _source['key']
if 'location' in self._config['layout'] :
if 'location' in self._config['layout'] and not os.path.exists(_path):
_path = os.sep.join([self._config['layout']['location'],_path])
if os.path.exists(_path) :
f = open(_path)
_source['key'] = f.read()

@ -9,42 +9,42 @@ import os
#
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)
pass
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'])
# 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)
# pass
# 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
# 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
# return getattr(module,_name) if hasattr(module,_name) else None

@ -49,6 +49,28 @@ def make_folder (projectFolder, webroot):
_projectPath = os.sep.join([_path,folder])
if not os.path.exists(_projectPath) :
os.makedirs(_projectPath)
def _icode(_path,_root):
"""
This function will generate some default plugins to show the users how plugins work and can be used/written
:path location of the project
"""
_code = """
import cms
#
# register this in config.plugins: {"demo":["info"]}
@cms.plugins(mimetype='application/json') :
def info (**_args):
_request= _args['request']
_config = _args['config']
return {"version":_config['system']['version'],'title':_config['layout']['header']['title']}
pass
"""
loc = os.sep.join([_path,_root,'_plugins','demo.py'])
f = open(loc,'w')
f.write(_code)
f.close()
def _ilogo (_path):
"""
This function creates a default logo in a designated folder, after the project folder has been created
@ -143,13 +165,13 @@ def make (**_args) :
_config['plugins'] = {}
_folder = _args['folder']
_root = _config['layout']['root'] #-- web root folder
make_folder(_folder,_root)
f = open(os.sep.join([_folder,'qcms-manifest.json']),'w')
make_folder(_folder,_root) #-- creating the project folder structure
f = open(os.sep.join([_folder,'qcms-manifest.json']),'w') #-- adding the manifest file
f.write( json.dumps(_config))
f.close()
_ilogo(os.sep.join([_folder,_root]))
print ([_folder,_root])
_ilogo(os.sep.join([_folder,_root])) #-- adding logo
_index(os.sep.join([_folder,_root]),_root)
_itheme(_folder,_root)
_itheme(_folder,_root) #-- adding theme folder
_icode(_folder,_root) #-- adding the plugins sample code

@ -32,7 +32,11 @@ def Get(theme,_url= URL) :
"""
try:
_url = '/'.join([_url,'api','themes','Get']) +f'?theme={theme}'
return requests.get(_url).json()
try:
return requests.get(_url).json()
except Exception as e:
pass
return None
except Exception as e:
pass
return {}

@ -137,58 +137,10 @@ def _delegate_call(app,key,module,name):
_handler = _getHandler(app,key)
# print (_handler.config()/)
uri = f'api/{module}/{name}'
return Plugin.call(uri=uri,handler=_handler,request=request)
# return _delegate(_handler,module,name)
# return Plugin.call(uri=uri,handler=_handler,request=request)
_delegate = cms.delegate()
return _delegate(uri=uri,handler=_handler,request=request)
def _delegate(_handler,module,name):
global _route
uri = '/'.join(['api',module,name])
# _args = dict(request.args,**{})
# _args['config'] = _handler.config()
_plugins = _handler.plugins()
_context = _handler.system()['context']
if _context :
uri = f'{_context}/{uri}'
_mimeType = 'application/octet-stream'
if uri not in _plugins :
_data = {}
_code = 404
else:
pointer = _plugins[uri]
# if _args :
# _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())
_mimeType = 'application/octet-stream' if not _mimeType else _mimeType
if type(_data) == pd.DataFrame :
_data = _data.to_dict(orient='records')
if type(_data) == list:
_data = json.dumps(_data)
_code = 200 if _data else 500
return _data,_code,{'Content-Type':_mimeType}
# @_app.route("/api/<module>/<name>" , methods=['POST'],defaults={'app_id':'main','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'])
# @_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
# _handler = _getHandler(app_id,key)
# return _delegate(_handler,module,name)
@_app.route('/version')
def _version ():
@ -206,7 +158,7 @@ def _reload(key) :
_systemKey = None
elif 'key' in _system['source'] and _system['source']['key']:
_systemKey = _system['source']['key']
print ([key,_systemKey,_systemKey == key])
# print ([key,_systemKey,_systemKey == key])
if key and _systemKey and _systemKey == key :
_handler.reload()
return "",200
@ -253,18 +205,7 @@ def _POST_CMSPage(app_id,key):
_id = _uri.split('/')[-1].split('.')[0]
else:
_id = request.headers['dom']
# _args = {'layout':_config['layout']}
# if 'plugins' in _config:
# _args['routes'] = _config['plugins']
# _system = _handler.system() #cms.components.get_system(_config)
# # _html = _handler.html(_uri,_id,_args,_system) #cms.components.html(_uri,_id,_args,_system)
# _html = _handler.html(_uri,_id)
# # _system = cms.components.get_system(_config)
# _args['system'] = _handler.system(skip=['source','app'])
# e = Environment(loader=BaseLoader()).from_string(_html)
# _html = e.render(**_args)
if 'read?uri=' in _uri or 'download?doc=' in _uri :
_uri = _uri.split('=')[1]
_args = _route.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))

@ -179,20 +179,52 @@ if(!qcms){
var qcms = {}
}
var _dashboard = function(_context,_uri){
this._context = _context ;
qcms.dashboard = function(_uri,_id){
this._uri = _uri
this.get = function (_args){
this._id = _id
this.get = function (){
var _uri = this._uri ;
if (this._context){
_uri = this._context + _uri
}
var http = HttpClient.instance()
http.setHeader('Content-Type','application/json')
http.setData(JSON.stringify(_args))
//http.setData(JSON.stringify(_args))
var _render = this.render
var _id = this._id
http.post(_uri,function(x){
if(x.readyState == 4 && x.status == 200){}
if(x.readyState == 4 && x.status == 200){
var _logs = JSON.parse(x.responseText)
_events = []
_logs.forEach(_item=>{
var _e = _render(_item,_id)
if (_e != null){
_events.push(_e)
}
})
_events.forEach(_e=>{ _e.render() })
}
})
}
this.render = function (_item,_id){
if (! _item.options){
//
// This is html to be rendered
if(_item.constructor.name == 'Array'){
_item.forEach(_div =>{$(_id).append(_div) })
}else{
$(_id).append($(_item))
}
return null
}else {
//
// rendering apexcharts
//
var _divChart = jx.dom.get.instance('DIV')
_divChart.className = 'chart '+_item.type
$(_id).append(_divChart)
var _chart = new ApexCharts(_divChart,_item.options)
return _chart ;
}
}
}

@ -5,6 +5,9 @@ dialog.show = function(_args,_pointer){
// http.setData({title:_title,html:_message},'application/json')
var uri = _args.context+'/dialog'
http.setHeader('dom',_args.title)
if (_args.uri.match(/=/)){
_args.uri = _args.uri.split(/=/)[1]
}
http.setHeader('uri',_args.uri)
http.get(uri,function(x){
$('.jxmodal').remove()

@ -0,0 +1,14 @@
var Search = function(_searchBoxId,_paneId,_bind){
var _text = jx.dom.get.value(_searchBoxId)
_regex = new RegExp(_text.toLowerCase())
_paneId = (_paneId['#'])?_paneId:('#'+_paneId)
$(_paneId).slideUp()
(_paneId).children().each(_index=>{
_div = $(_paneId).children()[_index]
if (_div._data.match(_regex)){
$(_div).slideDown()
}
})
}

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

Loading…
Cancel
Save