diff --git a/cms/__init__.py b/cms/__init__.py index 2a4b745..98795ba 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -6,7 +6,7 @@ import copy from jinja2 import Environment, BaseLoader, FileSystemLoader import importlib import importlib.util -from cms import disk, cloud +from cms import disk, cloud, engine # import cloud class components : diff --git a/cms/engine/__init__.py b/cms/engine/__init__.py new file mode 100644 index 0000000..18c283b --- /dev/null +++ b/cms/engine/__init__.py @@ -0,0 +1,309 @@ +import json + +from genericpath import isdir +import os +import pandas as pd +import transport +import copy +from jinja2 import Environment, BaseLoader, FileSystemLoader +import importlib +import importlib.util +from cms import disk, cloud + + +class Loader : + def __init__(self,**_args): + f = open (_args['path']) + self._config = json.loads(f.read()) + # + # + self._location = None + self._caller = None + if 'caller' in _args and _args['caller'] : + self._caller = _args['caller'] + self._location = _args['location'].split(os.sep) # needed for plugin loading + self._location = os.sep.join(self._location[:-1]) + self._config['system']['portal'] = self._caller != None + self._menu = {} + self._plugins={} + self.load() + + def load(self): + """ + This function will load menu (overwrite) and plugins + """ + self.init_menu() + self.init_plugins() + + def init_menu(self): + """ + This function will read menu and sub-menu items from disk structure, + The files are loaded will + """ + + _config = self._config + if 'source' in _config['system'] and _config['system']['source']['id'] == 'cloud' : + _sourceHandler = cloud + else: + _sourceHandler = disk + _object = _sourceHandler.build(_config) + # + # After building the site's menu, let us add the one from 3rd party apps + # + + + _layout = copy.deepcopy(_config['layout']) + _overwrite = _layout['overwrite'] if 'overwrite' in _layout else {} + + # + # @TODO: Find a way to translate rename/replace keys of the _object (menu) here + # + #-- applying overwrites to the menu items + for _name in _object : + _submenu = _object[_name] + _index = 0 + for _item in _submenu : + text = _item['text'].strip() + if text in _overwrite : + if 'uri' in _item and 'url' in 'url' in _overwrite[text] : + del _item['uri'] + _item = dict(_item,**_overwrite[text]) + + if 'uri' in _item and _item['type'] != 'open': + _item['uri'] = _item['uri'].replace(_layout['root'],'') + + _submenu[_index] = _item + _index += 1 + self.init_apps(_object) + self._menu = _object + self._order() + + def init_apps (self,_menu): + """ + Insuring that the apps are loaded into the menu with an approriate label + """ + _system = self._config['system'] + _context = _system['context'] + if 'routes' in _system : + # _items = [] + _overwrite = {} if 'overwrite' not in self._config['layout'] else self._config['layout']['overwrite'] + for _text in _system['routes'] : + _item = _system['routes'][_text] + if 'menu' not in _item : + continue + uri = f'{_context}/set/{_text}' + # _items.append ({"text":_text,'uri':uri,'type':'open'}) + _label = _item['menu'] + if _label not in _menu : + _menu [_label] = [] + _menu[_label].append ({"text":_text,'uri':uri,'type':'open'}) + # _overwrite[_text] = {'text': _text.replace('-',' ').replace('_',' '),'uri':uri,'type':'open'} + # _menu['products'] = _items + # + # given that the menu items assumes redirecting to a page ... + # This is not the case + # + # self._config['overwrite'] = _overwrite + else: + pass + + pass + def _order (self): + _config = self._config + if 'order' in _config['layout'] and 'menu' in _config['layout']['order']: + _sortedmenu = {} + _menu = self._menu + for _name in _config['layout']['order']['menu'] : + if _name in _menu : + _sortedmenu[_name] = _menu[_name] + + _menu = _sortedmenu if _sortedmenu else _menu + # + # If there are missing items in the sorting + _missing = list(set(self._menu.keys()) - set(_sortedmenu)) + if _missing : + for _name in _missing : + _menu[_name] = self._menu[_name] + _config['layout']['menu'] = _menu #cms.components.menu(_config) + self._menu = _menu + self._config = _config + def init_plugins(self) : + """ + This function looks for plugins in the folder on disk (no cloud support) and attempts to load them + """ + _config = self._config + PATH= os.sep.join([_config['layout']['root'],'_plugins']) + if not os.path.exists(PATH) and self._location and os.path.exists(self._location) : + # + # overriding the location of plugins ... + PATH = self._location + + _map = {} + # if not os.path.exists(PATH) : + # return _map + if 'plugins' not in _config : + _config['plugins'] = {} + _conf = _config['plugins'] + + for _key in _conf : + + _path = os.sep.join([PATH,_key+".py"]) + if not os.path.exists(_path): + continue + for _name in _conf[_key] : + _pointer = self._load_plugin(path=_path,name=_name) + if _pointer : + _uri = "/".join(["api",_key,_name]) + _map[_uri] = _pointer + # + # We are adding some source specific plugins to the user-defined plugins + # This is intended to have out-of the box plugins... + # + if 'source' in _config['system'] and _config['system']['source']['id'] == 'cloud' : + _plugins = cloud.plugins() + else: + _plugins = disk.plugins() + # + # If there are any plugins found, we should load them and use them + + if _plugins : + _map = dict(_map,**_plugins) + else: + pass + self._plugins = _map + self._config['plugins'] = self._plugins + + def _load_plugin(self,**_args): + """ + 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')] + 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(_name, _path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + return getattr(module,_name) if hasattr(module,_name) else None + +class Getter (Loader): + def __init__(self,**_args): + super().__init__(**_args) + _system = self.system() + _logo = _system['logo'] + if 'source' in _system and 'id' in _system['source'] and (_system['source']['id'] == 'cloud'): + _icon = f'/api/cloud/download?doc=/{_logo}' + _system['icon'] = _icon + + else: + _root = self._config['layout']['root'] + _icon = os.sep.join([_root,_icon]) + _system['icon'] = _icon + self._config['system'] = _system + def html(self,uri,id,_args={},_system={}) : + """ + This function reads a given uri and returns the appropriate html document, and applies environment context + + """ + _system = self._config['system'] + if 'source' in _system and _system['source']['id'] == 'cloud': + _html = cloud.html(uri,dict(_args,**{'system':_system})) + + else: + _html = disk.html(uri) + # _html = (open(uri)).read() + + + #return ' '.join(['
'.replace(':id',id),_html,'
']) + _html = ' '.join(['
'.replace(':id',id),_html,'
']) + appContext = Environment(loader=BaseLoader()).from_string(_html) + # + # If the rendering of the HTML happens here we should plugin custom functions (at the very least) + # + + return appContext.render(**_args) + # return _html + + def data (self,_args): + """ + :store data-store parameters (data-transport, github.com/lnyemba/data-transport) + :query query to be applied against the store (expected data-frame) + """ + _store = _args['store'] + reader = transport.factory.instance(**_store) + _queries= copy.deepcopy(_store['query']) + _data = reader.read(**_queries) + return _data + def csv(self,uri) : + return pd.read(uri).to_html() + + return _map + def menu(self): + return self._config['menu'] + def plugins(self): + return copy.deepcopy(self._plugins) if 'plugins' in self._config else {} + def context(self): + """ + adding custom variables functions to Jinja2, this function should be called after plugins are loaded + """ + _plugins = self.plugins() + # if not location: + # env = Environment(loader=BaseLoader()) + # else: + location = self._config['layout']['root'] + # env = Environment(loader=FileSystemLoader(location)) + env = Environment(loader=BaseLoader()) + # env.globals['routes'] = _config['plugins'] + return env + def config(self): + return copy.deepcopy(self._config) + def system(self,skip=[]): + """ + :skip keys to ignore in the object ... + """ + _data = copy.deepcopy(self._config['system']) + _system = {} + if skip and _system: + for key in _data.keys() : + if key not in skip : + _system[key] = _data[key] + else: + _system= _data + return _system + def get_app(self): + return self._config['system']['app'] + + +class Router : + def __init__(self,**_args) : + path = _args['path'] + _app = Getter (path = path) + self._id = 'main' + # _app.load() + self._apps = {} + _system = _app.system() + if 'routes' in _system : + _system = _system['routes'] + for _name in _system : + _path = _system[_name]['path'] + self._apps[_name] = Getter(path=_path,caller=_app,location=_path) + # self._apps[_name].load() + self._apps['main'] = _app + def set(self,_id): + self._id = _id + def get(self): + + return self._apps['main'] if self._id not in self._apps else self._apps[self._id] \ No newline at end of file diff --git a/cms/plugins.py b/cms/plugins.py index 8819807..e34862b 100644 --- a/cms/plugins.py +++ b/cms/plugins.py @@ -2,7 +2,7 @@ These are a few default plugins that will be exported and made available to the calling code The purpose of plugins is to perform data processing """ - +import json diff --git a/index.py b/index.py index 3c8cd44..73b0aa2 100644 --- a/index.py +++ b/index.py @@ -2,7 +2,7 @@ __doc__ = """ arguments : --config path of the configuration otherwise it will look for the default in the working directory """ -from flask import Flask,render_template,send_from_directory,request +from flask import Flask,render_template,send_from_directory,request, redirect import flask import transport from transport import providers @@ -16,35 +16,42 @@ import base64 from jinja2 import Environment, BaseLoader + _app = Flask(__name__) @_app.route('/favicon.ico') def favicon(): - global _config - - return send_from_directory(os.path.join(_app.root_path, 'static/img'), - 'logo.png', mimetype='image/vnd.microsoft.icon') + global _route + _system = _route.get ().system() + _logo =_system['icon'] if 'icon' in _system else 'static/img/logo.svg' + return _logo + # # _root = _route.get().config()['layout']['root'] + # # print ([_system]) + # # if 'source' in _system and 'id' in _system['source'] and (_system['source']['id'] == 'cloud'): + # # uri = f'/api/cloud/downloads?doc=/{_logo}' + # # print (['****' , uri]) + # # return redirect(uri,200) #,{'content-type':'application/image'} + # # else: + + # # return send_from_directory(_root, #_app.root_path, 'static/img'), + # _logo, mimetype='image/vnd.microsoft.icon') @_app.route("/") def _index (): global _config + global _route + _handler = _route.get() + _config = _handler.config() + _system = _handler.system() + _plugins= _handler.plugins() _args = {} - if 'plugins' in _config : - _args['routes']=['plugins'] - _system = cms.components.get_system(_config) #copy.deepcopy(_config['system']) + # if 'plugins' in _config : + # _args['routes']=_config['plugins'] + # _system = cms.components.get_system(_config) #copy.deepcopy(_config['system']) try: - _args['layout'] = _config['layout'] - # _args = dict(_args,**_config['layout']) - # _args = copy.copy(_config) - uri = os.sep.join([_config['layout']['root'], _config['layout']['index']]) - _html = cms.components.html(uri,'index',_args,_system) - - _args['index'] = _html - # e = Environment(loader=BaseLoader()).from_string(_html) - # e = cms.components.context(_config).from_string(_html) - # _args['index'] = e.render(**_args) + _html = _route.get().html(uri,'index',_config,_system) _index_page = "index.html" except Exception as e: print () @@ -52,9 +59,10 @@ def _index (): _index_page = "404.html" _args['uri'] = request.base_url pass - if 'source' in _system : - del _system['source'] - _args['system'] = _system + # if 'source' in _system : + # del _system['source'] + _args = {'layout':_config['layout'],'index':_html} + _args['system'] = _handler.system(skip=['source','app','route']) return render_template(_index_page,**_args) @@ -68,24 +76,28 @@ def _index (): @_app.route('/dialog') def _dialog (): - global _config - + # global _config + global _route + _handler = _route.get() + _system = _handler.system() + _config = _handler.config() _uri = os.sep.join([_config['layout']['root'],request.headers['uri']]) # _uri = request.headers['uri'] _id = request.headers['dom'] # _data = cms.components.data(_config) _args = {} #{'system':_config['system']} _args['title'] = _id - if 'plugins' in _config : - _args['routes'] = _config['plugins'] - _system = copy.deepcopy(_config['system']) + # if 'plugins' in _config : + # _args['routes'] = _config['plugins'] + # _system = copy.deepcopy(_config['system']) - _html = cms.components.html(_uri,_id,_config,_system) + # _html = cms.components.html(_uri,_id,_config,_system) + _html = _handler.html(_uri,_id,_config,_system) e = Environment(loader=BaseLoader()).from_string(_html) - if 'source' in _system : - del _system['source'] - _args['system'] = _system + # if 'source' in _system : + # del _system['source'] + _args['system'] = _handler.system(skip=['source','routes','app']) _args['html'] = _html _html = ''.join(["
",str( e.render(**_args)),'
']) @@ -104,17 +116,20 @@ def _getproxy(module,name) : :_module entry specified in plugins of the configuration :_name name of the function to execute """ - global _config + # global _config + global _route + _handler = _route.get() + uri = '/'.join(['api',module,name]) _args = dict(request.args,**{}) - _args['config'] = copy.deepcopy(_config) - - if uri not in _config['plugins'] : + _args['config'] = _handler.config() + _plugins = _handler.plugins() + if uri not in _plugins : _data = {} _code = 404 else: - pointer = _config['plugins'][uri] + pointer = _plugins[uri] if _args : _data = pointer (**_args) else: @@ -125,15 +140,18 @@ def _getproxy(module,name) : return _data,_code @_app.route("/api//" , methods=['POST']) def _post (module,name): - global _config + # global _config + global _route + _config = _route.get().config() + _plugins = _route.plugins() uri = '/'.join(['api',module,name]) _args = request.json code = 404 _info = "" - if uri in _config['plugins'] and _args: - _pointer = _config['plugins'][uri] + if uri in _plugins and _args: + _pointer = _plugins[uri] _info = _pointer(**_args) if _info: code = 200 @@ -154,7 +172,11 @@ def cms_page(): """ return the content of a folder formatted for a menu """ - global _config + # global _config + global _route + _handler = _route.get() + _config = _handler.config() + _uri = os.sep.join([_config['layout']['root'],request.headers['uri']]) _id = request.headers['dom'] _args = {'layout':_config['layout']} @@ -162,32 +184,38 @@ def cms_page(): _args['routes'] = _config['plugins'] - _system = cms.components.get_system(_config) - print ([_uri]) - _html = cms.components.html(_uri,_id,_args,_system) + _system = _handler.system() #cms.components.get_system(_config) + _html = _handler.html(_uri,_id,_args,_system) #cms.components.html(_uri,_id,_args,_system) e = Environment(loader=BaseLoader()).from_string(_html) # _data = {} #cms.components.data(_config) _system = cms.components.get_system(_config) - _args['system'] = _system + _args['system'] = _handler.system(skip=['source','app']) _html = e.render(**_args) return _html,200 @_app.route('/page') def _cms_page (): - global _config + # global _config + global _route + _handler = _route.get() + _config = _handler.config() _uri = request.args['uri'] _uri = os.sep.join([_config['layout']['root'],_uri]) _title = request.args['title'] if 'title' in request.args else '' - _args = {'system':cms.components.get_system(_config) } - if 'plugins' in _config: - _args['routes'] = _config['plugins'] - - _html = cms.components.html(_uri,_title,_args) - e = Environment(loader=BaseLoader()).from_string(_html) + _args = {'system':_handler.system()} #cms.components.get_system(_config) } + # if 'plugins' in _config: + # _args['routes'] = _config['plugins'] + _html = _handler.html(_uri,_title,_args) # cms.components.html(_uri,_title,_args) + e = Environment(loader=BaseLoader()).from_string(_html) + _args['system'] = _handler.system(skip=['app','source']) return e.render(**_args),200 -@_app.route('/reload',methods=['POST']) -def reload(): - pass + +@_app.route('/set/') +def set(id): + global _route + _handler = _route.set(id) + return redirect('/') + # # Let us bootup the application SYS_ARGS = {} @@ -210,44 +238,48 @@ if len(sys.argv) > 1: i += 2 if __name__ == '__main__' : + pass _path = SYS_ARGS['config'] if 'config' in SYS_ARGS else 'config.json' if os.path.exists(_path): - _config = json.loads((open (_path)).read()) - if 'theme' not in _config['system'] : - _config['system']['theme'] = 'magazine.css' - # - # root can be either on disk or in the cloud ... - # root: "" reading from disk - # root: {uid,token,folder} - # + _route = cms.engine.Router(path=_path) + _args = _route.get().get_app() + _app.run(**_args) + # _config = json.loads((open (_path)).read()) + # if 'theme' not in _config['system'] : + # _config['system']['theme'] = 'magazine.css' + # # + # # root can be either on disk or in the cloud ... + # # root: "" reading from disk + # # root: {uid,token,folder} + # # - _root = _config['layout']['root'] - _menu = cms.components.menu(_config) - if 'order' in _config['layout'] and 'menu' in _config['layout']['order']: - _sortedmenu = {} - for _name in _config['layout']['order']['menu'] : - if _name in _menu : - _sortedmenu[_name] = _menu[_name] + # _root = _config['layout']['root'] + # _menu = cms.components.menu(_config) + # if 'order' in _config['layout'] and 'menu' in _config['layout']['order']: + # _sortedmenu = {} + # for _name in _config['layout']['order']['menu'] : + # if _name in _menu : + # _sortedmenu[_name] = _menu[_name] - _menu = _sortedmenu if _sortedmenu else _menu - _config['layout']['menu'] = _menu #cms.components.menu(_config) - # if 'data' in _config : - # _config['data'] = cms.components.data(_config['data']) - # - _map = cms.components.plugins(_config) - _config['plugins'] = _map - # Let us load the plugins if any are available - # if 'plugins' in _config : - # _map = cms.components.plugins(_config) - # if _map : - # _config['plugins'] = _map - # - # register the functions with Jinja2 - # cms.components.context(_config) + # _menu = _sortedmenu if _sortedmenu else _menu + # _config['layout']['menu'] = _menu #cms.components.menu(_config) + # # if 'data' in _config : + # # _config['data'] = cms.components.data(_config['data']) + # # + # _map = cms.components.plugins(_config) + # _config['plugins'] = _map + # # Let us load the plugins if any are available + # # if 'plugins' in _config : + # # _map = cms.components.plugins(_config) + # # if _map : + # # _config['plugins'] = _map + # # + # # register the functions with Jinja2 + # # cms.components.context(_config) - _args = _config['system']['app'] - _app.run(**_args) - else: - print (__doc__) - print () + # _args = _config['system']['app'] + # _app.run(**_args) + # else: + # print (__doc__) + # print () diff --git a/static/css/default.css b/static/css/default.css index 77e81d3..2712a28 100644 --- a/static/css/default.css +++ b/static/css/default.css @@ -18,7 +18,7 @@ body { .bold {font-weight:bold} -.header img { width:100%;} +.header img { width:100%; height: 100%;} .footer { text-align:center; diff --git a/static/css/themes/oss.css b/static/css/themes/oss.css index 6be1c32..90e949f 100644 --- a/static/css/themes/oss.css +++ b/static/css/themes/oss.css @@ -18,6 +18,8 @@ } +.main .header .icon {width:40px; height:40px;} +.main .header .icon img {width:40px; height:40px;} .main .header .title { font-size:32px; text-transform: uppercase; font-weight:bold} .main .header .subtitle {font-style:italic;font-size:14px; color:gray; text-transform: capitalize;} @@ -37,9 +39,7 @@ blockquote { border-left:8px solid #D3D3D3; padding:4px; } - .main .content table {border-spacing: 2;} -.main .content table .fa-check {color:green; font-size:20px;} -.main .content table .fa-times {color:RED; font-size:20px;} +.main .content table {border-spacing: 2;} .main .content table .active {font-size:14px} .main .content .banner { height:300px; margin:4px; diff --git a/templates/header.html b/templates/header.html index 0a3d6b6..7b6cfca 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,5 +1,7 @@ {% if layout.header.logo == True %} - +
+ +
{% endif %}
diff --git a/templates/index.html b/templates/index.html index b2aff4d..11677ee 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,7 +17,7 @@ Vanderbilt University Medical Center {{layout.header.title}} - + diff --git a/templates/menu.html b/templates/menu.html index e9dcfaa..5cb1312 100644 --- a/templates/menu.html +++ b/templates/menu.html @@ -1,4 +1,8 @@ -
+{%if system.portal %} +
+{% else %} +
+{% endif %} {% for _name in layout.menu %}
@@ -8,14 +12,18 @@