Merge pull request 'integration of branch - v2.2' (#25) from v2.2 into master

Reviewed-on: cloud/cms#25
master
Steve L. Nyemba 3 weeks ago
commit 415e3401a5

@ -7,7 +7,7 @@ from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib
import importlib.util
import json
from . import sites
from . import apexchart
class Plugin :
@ -132,13 +132,18 @@ class delegate:
#
# we need to update the _uri (if context/embeded apps)
#
_context = _handler.system()['context']
# _context = _handler.system()['context']
_context = _handler.get('system.context')
if _context :
_uri = f'{_context}/{_uri}'
_plugins = _handler.plugins()
# print ([' ** ',_uri, _args['uri']])
# _plugins = _handler.plugins()
_plugins = _handler.get('plugins')
_code = 200
if _uri in _plugins :
_config = _handler.config() #_args['config']
# _config = _handler.config() #_args['config']
_config = _handler.get(None) #_args['config']
_pointer = _plugins[_uri]
@ -159,6 +164,7 @@ class delegate:
if type(_data) == pd.DataFrame :
_data = _data.to_json(orient='records')
elif type(_data) in [dict,list] :
_data = json.dumps(_data)
pass
else:

@ -43,11 +43,11 @@ def build (_config, keep=[]): #(_path,_content):
:path path of the files on disk
:config configuration associated with the
"""
_path = _config['layout']['root']
# if 'location' in _config['layout'] :
# _path = _config['layout']['location']
_path = _realpath(_path,_config)
# print (_path)
_items = folders(_path,_config)
_subItems = [ content (os.sep.join([_path,_name]),_config)for _name in _items ]
@ -57,7 +57,6 @@ def build (_config, keep=[]): #(_path,_content):
_index = _items.index(_name)
if _name.startswith('_') or len(_subItems[_index]) == 0:
continue
# print ([_name,_subItems[_index]])
if _name not in _r :
_r[_name] = []
_r[_name] += _subItems[_index]
@ -68,9 +67,8 @@ def build (_config, keep=[]): #(_path,_content):
def _realpath (uri,_config) :
_layout = _config['layout']
_uri = copy.copy(uri)
if 'location' in _layout :
if 'location' in _layout and _layout['location']:
_uri = os.sep.join([_layout['location'],_uri])
return _uri
@ -119,7 +117,9 @@ def html(_uri,_config) :
_context = str(_config['system']['context'])
# if '/' in _context :
# _context = _context.split('/')[-1]
_html = ( open(_path)).read()
_layout = _config['layout']
if 'location' in _layout :
if not _config :

@ -9,7 +9,7 @@
# import importlib
# import importlib.util
from cms import disk, cloud
from . import basic
# from . import basic
# class Loader :
# """

@ -1,473 +0,0 @@
import json
import os
import io
import copy
from cms import disk, cloud
from jinja2 import Environment, BaseLoader, FileSystemLoader
# import importlib
# import importlib.util
"""
There are four classes at play here:
[ Initializer ] <|-- [ Module ] <|-- [ MicroService ] <--<>[ CMS ]
"""
class Initializer :
"""
This class handles initialization of all sorts associated with "cms engine"
:path
:location
:shared
"""
def __init__(self,**_args):
self._config = {'system':{},'layout':{},'plugins':{}}
# self._shared = False if not 'shared' in _args else _args['shared']
self._location= _args['location'] if 'location' in _args else None
self._menu = {}
# _source = self._config ['system']['source'] if 'source' in self._config['system'] else {}
# self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud'
# print ([self._ISCLOUD,self._config['system'].keys()])
self._ISCLOUD = False
self._caller = None if 'caller' not in _args else _args['caller']
self._args = _args
# if 'context' in _args :
# self._config
self.reload() #-- this is an initial load of various components
#
# @TODO:
# Each module should submit it's routers to the parent, and adjust the references given the callers
#
def reload(self):
self._iconfig(**self._args)
self._uconfig(**self._args)
self._isource()
self._imenu()
self._iplugins()
self._iroutes ()
# self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud'
def _handler (self):
"""
This function returns the appropriate handler to the calling code, The handler enables read/write from a location
"""
if self._ISCLOUD: #'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud' :
return cloud
else:
return disk
def _iroutes (self):
"""
Initializing routes to be submitted to the CMS Handler
The routes must be able to :
1. submit an object (dependency to the cms)
2. submit with the object a route associated
The CMS handler will resolve dependencies/redundancies
"""
pass
def _imenu(self,**_args) :
pass
def _iplugins(self,**_args) :
"""
Initialize plugins from disk (always)
:plugins
"""
_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 ...
if os.path.isfile(self._location) :
_location = os.sep.join(self._location.split(os.sep)[:-1])
else:
_location = self._location
PATH = os.sep.join([_location, _config['layout']['root'],'_plugins'])
_context = _config['system']['context']
_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 = disk.plugins(path=_path,name=_name,context=_context)
if _pointer :
_uri = "/".join(["api",_key,_name])
if _context :
_uri = f'{_context}/{_uri}'
_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 self._ISCLOUD :
_plugins = cloud.plugins(_context)
else:
_plugins = disk.plugins(context=_context)
#
# 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 _isource (self):
"""
Initializing the source of the data, so we can read/write load from anywhere
"""
if 'source' not in self._config['system'] :
return
#
#
self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud'
_source = self._config['system']['source']
if 'key' in _source :
#
_path = _source['key']
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()
f.close()
self._config['system']['source'] = _source
def _ilayout(self,**_args):
"""
Initialization of the layout section (should only happen if ) being called via framework
:path path to the dependent apps
"""
_layout = self._config['layout']
_path = os.sep.join(_args['path'].split(os.sep)[:-1])
#
# find the new root and the one associated with the dependent app
#
pass
def _imenu(self,**_args):
_gshandler = self._handler()
_object = _gshandler.build(self._config) #-- this will build the menu
#
# post-processing menu, overwrite items and behaviors
#
_layout = copy.deepcopy(self._config['layout'])
_overwrite = _layout['overwrite'] if 'overwrite' in _layout else {}
_context = self.system()['context']
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 _overwrite[text] :
del _item['uri']
_item = dict(_item,**_overwrite[text])
if 'uri' in _item and 'type' in _item and _item['type'] != 'open':
_item['uri'] = _item['uri'] #.replace(_layout['root'],'')
# _item['uri'] = _gshandler._format(_item['uri'],self._config)
_submenu[_index] = _item
_index += 1
#
# updating menu _items as it relates to apps, configuration and the order in which they appear
#
_layout['menu'] = _object
self._menu = _object
self._config['layout'] = _layout
self._iapps()
self._iorder()
pass
def _iorder (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 _iapps (self):
"""
Initializing dependent applications into a menu area if need be
"""
_layout = self._config['layout']
_menu = _layout['menu'] if 'menu' in _layout else {}
_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}/{_text}'
# _items.append ({"text":_text,'uri':uri,'type':'open'})
_label = _item['menu']
if _text in _overwrite :
_text = _overwrite[_text]['text']
if _label not in _menu :
_menu [_label] = []
_menu[_label].append ({"text":_text,'uri':uri,'type':'open'})
#
# update the menu items and the configuration
#
_keys = list(_menu.keys())
# for _key in _keys :
# if len(_menu[_key]) == 0 :
# del _menu[_key]
# #
# # doing some house-keeping work here to make sure the front-end gets clean data
# #
_layout['menu'] = _menu
self._config['layout'] = _layout
def _ilogo(self):
_gshandler = self._handler()
pass
def _iconfig(self,**_args):
"""
Implement this in a base class
:path or uri
"""
raise Exception ("Configuration Initialization is NOT implemented")
def _uconfig(self,**_args):
"""
This file will update the configuration provided the CMS is run in shared mode (framework level)
"""
if not self._location :
return ;
_path = os.sep.join(self._location.split(os.sep)[:-1])
_layout = self._config['layout']
_oroot = _layout['root']
_orw = _layout['overwrite']
_index = _layout['index']
_newpath = os.sep.join([_path,_oroot])
self._config['system']['portal'] = self._caller != None
_context = self.system()['context']
if self._caller :
#
_callerContext = self._caller.system()['context']
if not self._config['system']['context'] :
self._config['system']['context'] = _callerContext
self._config['system']['caller'] = {'icon': '/main'+self._caller.system()['icon'].replace(_callerContext,'')}
_context = '/'.join([_callerContext,_context]) if _callerContext != '' else _context
if os.path.exists(_newpath) and not self._ISCLOUD:
#
# LOG: rewrite due to the mode in which the site is being run
#
_api = f'{_context}/api/disk/read?uri='+_oroot
_stream = json.dumps(self._config)
_stream = _stream.replace(_oroot,_api)
# self._config = json.loads(_stream)
self._config['layout']['root'] = _oroot
# self._config['layout']['overwrite'] = _orw
#
# We need to update the logo/icon
_logo = self._config['system']['logo']
if self._ISCLOUD:
_icon = f'{_context}/api/cloud/download?doc=/{_logo}'
else:
_icon = f'{_context}/api/disk/read?uri={_logo}'
# if disk.exists(uri=_logo,config=self._config):
# _icon = _logo
_logo = _icon
if self._location :
self._config['layout']['location'] = _path
self._config['system']['icon'] = _icon
self._config['system']['logo'] = _logo
# self.set('layout.root',os.sep.join([_path,_oroot]))
pass
class Module (Initializer):
"""
This is a basic structure for an application working in either portal or app mode,
"""
def __init__(self,**_args):
super().__init__(**_args)
pass
def _iconfig(self, **_args):
"""
initialization of the configuration file i.e loading the files and having a baseline workable structure
:path|stream path of the configuration file
or stream of JSON configuration file
"""
if 'path' in _args :
f = open(_args['path'])
self._config = json.loads(f.read())
f.close()
elif 'stream' in _args :
_stream = _args['stream']
if type(_stream) == 'str' :
self._config = json.loads(_stream)
elif type(_stream) == io.StringIO :
self._config = json.loads( _stream.read())
self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source'] and self._config['system']['source']['id'] == 'cloud'
if self._caller :
self._config['system']['parentContext'] = self._caller.system()['context']
else:
self._config['system']['parentContext'] = self._config['system']['context']
if 'context' in _args :
self._config['system']['context'] = _args['context']
if '/' in self._config['system']['context'] :
self._config['system']['context'] = self._config['system']['context'].split('/')[-1]
#
#
# self._name = self._config['system']['name'] if 'name' in self._config['system'] else _args['name']
def system (self,skip=[]):
"""
This function returns system attributes without specific components
"""
_data = copy.deepcopy(self._config['system'])
exclude = skip
_system = {}
if exclude and _system:
for key in _data.keys() :
if key not in exclude :
_system[key] = _data[key]
else:
_system= _data
return _system
def layout (self):
return copy.copy(self._config['layout'])
def plugins (self):
return copy.copy(self._config['plugins'])
def config (self):
return copy.copy(self._config)
def app(self):
_system = self.system()
return _system['app']
def set(self,key,value):
"""
This function will update/set an attribute with a given value
:key
"""
_keys = key.split('.')
_found = 0
if _keys[0] in self._config :
_object = self._config[_keys[0]]
for _akey in _object.keys() :
if _akey == _keys[-1] :
_object[_akey] = value
_found = 1
break
#
#
return _found
#
class MicroService (Module):
"""
This is a CMS MicroService class that is capable of initializing a site and exposing Module functions
"""
def __init__(self,**_args):
super().__init__(**_args)
def format(_content,mimetype):
pass
def html (self,uri, id) :
_system = self.system()
_gshandler = self._handler()
#
#@TODO:
# The uri here must be properly formatted, We need to define the conditions for this
#
_html = _gshandler.html(uri,self._config)
return " ".join([f'<div id="{id}" > ',_html, '</div>'])
def context(self):
return Environment(loader=BaseLoader())
def data (self,**_args):
request = _args['request']
def icon (self):
_handler = self._handler()
_uri = self.system()['icon']
return _handler.read(uri=_uri,config=self._config)
class CMS:
"""
This class aggregates microservices and allows the application of a given service (site/app)
"""
def __init__(self,**_args) :
# _app = Getter (path = path)
# _void = MicroService()
_app = MicroService (**_args)
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] = MicroService(context=_name,path=_path,caller=_app,location=_path)
self._apps['main'] = _app
#
# The following are just a simple delegation pattern (it makes the calling code simpler)
#
def config (self):
return self.get().config()
def render(self,_uri,_id,_appid):
# _handler = self.get()
_handler = self._apps[_appid]
_config = _handler.config()
_args = {'layout':_handler.layout()}
if 'plugins' in _config:
_args['routes'] = _config['plugins']
_html = _handler.html(_uri,_id)
_args['system'] = _handler.system(skip=['source','app'])
e = Environment(loader=BaseLoader()).from_string(_html)
_args[_id] = str(e.render(**_args)) #,_args
return _args
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]
def get_main(self):
return self._apps['main']

@ -26,51 +26,43 @@ import datetime
import requests
from cms import disk, cloud, engine
import cms.sites
_app = Flask(__name__)
cli = typer.Typer()
@_app.route('/favicon.ico')
@_app.route('/<id>/favicon.ico')
def favicon(id):
global _route
# _system = _route.get ().system()
# _handler = _route.get()
_handler = _getHandler(id)
_system = _handler.system()
_logo =_system['icon'] #if 'icon' in _system else 'static/img/logo.svg'
_stream = requests.get(''.join([request.host_url,_logo]))
return "_stream",200,{"Content-Type":"image/png"} #_handler.get(_logo),200,{"content-type":"image/png"}
def _getHandler (app_id,resource=None) :
global _route
global _qcms
_id = _getId(app_id,resource)
return _route._apps[_id]
return _qcms._apps[_id]
def _getId(app_id,app_x):
if app_x not in [None,''] :
return '/'.join([app_id,app_x])
_uri = '/'.join([app_id,app_x])
return _uri[:-1] if _uri.endswith('/') else _uri
return app_id
def _setHandler (app_id,resource) :
session['app_id'] = _getId(app_id,resource)
@_app.route("/robots.txt")
def robots_txt():
@_app.route("/<_id>/robots.txt")
@_app.route("/robots.txt",defaults={'_id':None})
def robots_txt(_id):
"""
This function will generate a robots expression for a variety of crawlers, the paths will be provided by
menu options
"""
global _route
_system = _route.get ().system()
_info = ['''
global _qcms
_site = _qcms.get (_id)
_routes = _site.get('system.routes')
_context = _site.get('system.context')
_info = [f'''
User-agent: *
Allow: /
Allow: /{_context}
''']
if 'routes' in _system :
for _key in _system['routes'] :
_uri = '/'.join(['',_key])
if _routes :
for _key in _routes:
_uri = '/'.join(["",_context,_key])
_info.append(f'''
User-agent: *
Allow: {_uri}
@ -82,14 +74,14 @@ def _getIndex (app_id ,resource=None):
_handler = _getHandler(app_id,resource)
_layout = _handler.layout()
_status_code = 200
global _route
global _qcms
_index_page = 'index.html'
_args = {}
try :
_uri = os.sep.join([_layout['root'],_layout['index']])
_id = _getId(app_id,resource)
_args = _route.render(_uri,'index',_id)
_args = _qcms.render(_uri,'index',_id)
except Exception as e:
_status_code = 404
@ -98,108 +90,81 @@ def _getIndex (app_id ,resource=None):
return render_template(_index_page,**_args),_status_code
@_app.route("/")
def _index ():
return _getIndex('main')
# return _getIndex('main')
global _qcms
_app = _qcms.get(None)
_uri = os.sep.join([_app.get('layout.root'),_app.get('layout.index')])
_html = _qcms.render(_uri,'index')
return render_template('index.html',**_qcms.render(_uri,'index')),200
@_app.route("/<app>/<resource>")
@_app.route("/<app>",defaults={'resource':None})
def _aindex (app,resource=None):
_handler = _getHandler(app,resource)
def _altIndex(app,resource):
global _qcms
_id = _getId(app,resource)
_site = _qcms.get(_id)
_uri = os.sep.join([_site.get('layout.root'),_site.get('layout.index')])
return render_template('index.html',**_qcms.render(_uri,'index',_id)),200
_setHandler(app,resource)
_html,_code = _getIndex(app,resource)
return _html,_code
# @_app.route('/id/<uid>')
# def people(uid):
# """
# This function will implement hardened links that can directly "do something"
# """
# global _config
# return "0",200
@_app.route('/<app>/dialog')
@_app.route('/dialog',defaults={'app':'main'})
def _dialog (app):
# global _config
global _route
_handler = _route.get()
@_app.route('/dialog',defaults={'app':None})
def _getdialog(app):
global _qcms
_site = _qcms.get(app)
_uri = request.headers['uri']
_id = request.headers['dom']
# _html = ''.join(["<div style='padding:1%'>",str( e.render(**_args)),'</div>'])
_args = _route.render(_uri,'html',app) #session.get('app_id','main'))
_args = _qcms.render(_uri,'html',app) #session.get('app_id','main'))
_args['title'] = _id
return render_template('dialog.html',**_args) #title=_id,html=_html)
@_app.route("/api/<module>/<name>",defaults={'app':'main','key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("/api/<module>/<name>",defaults={'app':None,'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 _proxyCall(app,key,module,name):
global _qcms
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)
_delegate = cms.delegate()
return _delegate(uri=uri,handler=_handler,request=request)
_id = _getId(app,key)
_site = _qcms.get(_id)
_uri = f'api/{module}/{name}'
_delegate = cms.delegate()
return _delegate(uri=_uri, handler=_site, request=request)
@_app.route('/version')
def _version ():
global _route
_handler = _route.get()
global _qcms
_site = _qcms.get()
global _config
return _handler.system()['version']
return _site.get('system.version')
@_app.route("/reload/<key>")
def _reload(key) :
global _route
global _qcms
_handler = _route.get_main()
_system = _handler.system()
if not 'source' in _system :
_systemKey = None
elif 'key' in _system['source'] and _system['source']['key']:
_systemKey = _system['source']['key']
# print ([key,_systemKey,_systemKey == key])
if key and _systemKey and _systemKey == key :
_handler.reload()
_site = _qcms.get(None)
_systemKey = _site.get('system.source.key')
if _systemKey == key and _systemKey:
_site.reload()
return "",200
pass
else:
return "",403
@_app.route('/reload',methods=['POST'])
def reload():
# global _route
# _handler = _route.get_main()
# _system = _handler.system()
_key = request.headers['key'] if 'key' in request.headers else None
return _reload(_key)
# if not 'source' in _system :
# _systemKey = None
# elif 'key' in _system['source'] and _system['source']['key']:
# _systemKey = _system['source']['key']
# print ([_key,_systemKey,_systemKey == _key])
# if _key and _systemKey and _systemKey == _key :
# _handler.reload()
# return "",200
# pass
# return "",403
@_app.route('/page',methods=['POST'],defaults={'app_id':'main','key':None})
@_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None})
@_app.route('/<app_id>/page',methods=['POST'],defaults={'key':None})
@_app.route('/<app_id>/<key>/page',methods=['POST'])
def _POST_CMSPage(app_id,key):
def getPostedPage(app_id,key):
"""
return the content of a folder formatted for a menu
This function will return a page given a location parameter
"""
# global _config
global _route
# _handler = _route.get()
# _config = _handler.config()
_handler = _getHandler(app_id,key)
_setHandler(app_id,key)
_config = _handler.config()
# _uri = os.sep.join([_config['layout']['root'],request.headers['uri']])
global _qcms
_id = _getId(app_id,key)
_site = _qcms.get(_id)
_uri = request.headers['uri']
if 'dom' not in request.headers :
_id = _uri.split('/')[-1].split('.')[0]
@ -208,22 +173,17 @@ def _POST_CMSPage(app_id,key):
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'))
_args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
return _args[_id],200
# return _html,200
@_app.route('/page',defaults={'app_id':'main','resource':None})
@_app.route('/page',defaults={'app_id':None,'resource':None})
@_app.route('/<app_id>/page',defaults={'resource':None},methods=['GET'])
@_app.route('/<app_id>/<resource>/page',methods=['GET'])
def _cms_page (app_id,resource):
# global _config
global _route
# _handler = _route.get()
# _config = _handler.config()
global _qcms
_uri = request.args['uri']
# _uri = os.sep.join([_config['layout']['root'],_uri])
_title = request.args['title'] if 'title' in request.args else ''
_args = _route.render(_uri,_title,session.get(app_id,'main'))
_args = _qcms.render(_uri,_title,app_id)
return _args[_title],200
@ -238,23 +198,16 @@ def start (
:path path to the the manifest
:shared run in shared mode i.e
"""
global _route
global _qcms
if os.path.exists(path) and os.path.isfile(path):
_args = {'path':path}
# if shared :
# _args['location'] = path
# _args['shared'] = shared
_args['location'] = path
_args['shared'] = True
# _route = cms.engine.Router(**_args) #path=path,shared=shared)
_route = cms.engine.basic.CMS(**_args)
_qcms = cms.sites.QCMS(**_args)
_args = _qcms.get().get('system.app')
# dir(_route)
# _args = _route.get().get_app()
_args = _route.get().app()
if port :
_args['port'] = port
_app.secret_key = str(uuid.uuid4())

@ -0,0 +1,352 @@
"""
This file will describe how a site is loaded
"""
from cms.engine.config.structure import Layout, System
from cms import disk, cloud
import copy
import json
import io
import os
from jinja2 import Environment, BaseLoader, FileSystemLoader
from datetime import datetime
class IOConfig:
"""
This class encapsulates read/write for configuration
"""
def __init__(self,**_args):
self._config = {'system':{},'layout':{},'plugins':{}}
self._caller = None
self._location= _args['location'] if 'location' in _args else None
self._logs = []
def get(self,_key) :
if not _key :
return self._config
_keys = _key.split('.') if '.' in _key else [_key]
_value= None
_object = copy.deepcopy(self._config)
for _key in _keys :
if _key in _object :
_object = _object[_key]
else:
return None
return _object
def set(self,_expr,value):
if len(_expr.split('.')) == 1 :
self._config[_expr] = value
else:
_object = self._config
for _attr in _expr.split('.')[:-1] :
if _attr not in _object :
_object[_attr] = {}
_object = _object[_attr]
_attr = _expr.split('.')[-1]
_object[_attr] = value
def log(self,**_args):
_row = dict({'_date':datetime.now().strftime('%Y-%m-%d'),'time':datetime.now().strftime('%H:%M:%S'), 'context':self.get('system.context')},**_args)
self._logs.append(_row)
def debug(self,**_args):
return self._logs,'application/json'
class Initialization (IOConfig):
def __init__(self,**_args):
super().__init__(**_args)
self._config = self.read_config(**_args)
self._args = _args
#
# Invoke initialization
self.reload()
def reload (self):
_args = self._args
self._caller = _args['caller'] if 'caller' in _args else None
if (self._caller):
self._config['system']['parentContext'] = ''
if not self.get('system.icon') :
self._config['system']['icon'] = None
self._config['layout']['location'] = None
if not self.get('layout.menu') :
self._config['layout']['menu'] = {}
self.context(**_args)
self.menu()
self.plugins()
def read_config(self,**_args) :
_config = {}
if 'path' in _args :
f = open(_args['path'])
_config = json.loads(f.read())
f.close()
elif 'stream' in _args :
_stream = _args['stream']
if type(_stream) == 'str' :
_config = json.loads(_stream)
elif type(_stream) == io.StringIO :
_config = json.loads( _stream.read())
_name = self._caller.__name__ if self._caller else None
self.log(action='init.config',module='read_config',input={'caller':_name})
return _config
def context(self,**_args):
"""
Updating the context so that we can have a consistent representation
"""
if ('context' in _args):
self.set('system.context',_args['context'])
pass
_context = self.get('system.context')
if self._caller :
#
# There is a parent context we need to account and we are updating the current context to reflect the caller context
_parentContext = self._caller.get('system.context')
_context = '/'.join([self._caller.get('system.context'),_context])
#
# updating the configuratioin
_iconURI = '/'.join(["",_parentContext,self._caller.get('system.icon')])
# print ([self._caller.get('system.context'), _parentContext,_context])
if self._caller.get('system.context') != '':
_parentContext = "/"+_parentContext
_context = "/"+_context
self.set('system.onport',0)
self._config['system']['onport'] = 0
else:
_iconURI = _iconURI.replace("///","/")
self.set('system.onport',1)
self._config['system']['onport'] = 1
self.set('system.caller',{'icon':_iconURI,'context':self._caller.get('system.context')})
# _context = _context.replace('/','')
else:
#
# If we have no parent site, there should be no parent context (or empty string)
_parentContext = _context #@TODO: should be none ?
#
# Updating context so it is usable throughout the code base
if self._caller:
self.set('system.parentContext', _parentContext.strip())
self.set('system.context',_context.strip())
# self._config['system']['context'] = _context.strip()
self._config['system']['parentContext'] = _parentContext
p = {'has_caller':self._caller != None,'context':_context}
self.log(action='init.context',module='context',input=p)
#
# loosly context to a certain extent involves locations of icons and document root
#
self.locations(**_args)
#
# submit log of status
def locations(self,**_args):
"""
We are updating the icons, project location file and security key (if any), we need to make sure that context is updated first
"""
#
# updating location & path of the application
# if self.get('system.caller') :
# return
_context = self.get('system.context')
_logo = self.get('system.logo')
_root = self.get('layout.root')
if self.get('system.source.id') == 'cloud' :
_icon = f'{_context}/api/cloud/download?doc={_logo}'
else:
_icon = f'{_context}/api/disk/read?uri={_logo}'
# _root = f'{_context}/api/disk/read?uri={_root}'
# self.set('layout.root',_root)
self.set('system.icon',_icon)
self.set('system.logo',_icon)
#
# from the path provided we can determine the location of the project
_homefolder = os.sep.join(_args['path'].split(os.sep)[:-1])
if _homefolder and os.path.exists(os.sep.join([_homefolder,_root])) :
self.set('layout.location',_homefolder)
#
# updating the security key and trying to determine the location
if self.get('system.source.key') :
_path = self.get('system.source.key')
f = None
if os.path.exists(_path) :
f = open(_path)
elif os.path.exists(os.sep.join([self.get('layout.location'),_path])) :
f = open(os.sep.join([self.get('layout.location'),_path]))
if f :
self.set('system.source.key',f.read())
f.close()
def menu (self,**_args):
"""
This function will build the menu of the site (if need be)
"""
_handler = cloud if self.get('system.source.id') == 'cloud' else disk
_object = _handler.build(self._config)
_overwrite = self.get('layout.overwrite')
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 _overwrite :
del _item['uri']
_item = dict(_item,**_overwrite[_text])
_submenu[_index] = _item
_index += 1
self.set('layout.menu',_object)
# self._config['layout']['menu'] = _object
self.routes() #-- other apps/sites integrated in case we are operating in portal mode
#
# At this point the entire menu is build and we need to have it sorted
self.order()
# _labels = list(self.get('layout.menu').keys())
# self.log(action='init.menu',module='menu',input=_labels)
# print (self.get('layout.menu'))
# print (_object)
def order(self,**_args):
if self.get('layout.order.menu') :
_sorted = {}
_menu = self.get('layout.menu')
_ordered = self.get('layout.order.menu')
for _name in _ordered :
if _name in _menu :
_sorted[_name] = _menu[_name]
if _sorted :
_menu = _sorted
_missing = list(set(_menu.keys()) - set(_sorted))
if _missing :
for _name in _missing :
_menu[_name] = _menu[_name]
self.set('layout.menu',_menu)
def routes (self,**_args):
"""
This method will update menu with dependent sites/apps and add them to the menu
"""
_overwrite = self.get('layout.overwrite')
_routes = self.get('system.routes')
_context = self.get('system.context')
_menu = self.get('layout.menu')
if _routes :
for _text in _routes :
_item = _routes[_text]
if 'menu' in _item :
_uri = f'{_context}/{_text}'
_label = _item['menu']
if _text in _overwrite :
_text = _overwrite[_text]['text']
if _label not in _menu :
_menu[_label] = []
_menu[_label].append({"text":_text,"uri":_uri,"type":"open"})
#
# updating menu ...
self.set('layout.menu',_menu)
# self._config['layout']['menu'] = _menu
else:
pass
def plugins (self,**_args):
"""
This function will load the plugins from disk (only)
"""
_folder = os.sep.join([self.get('layout.location'),self.get('layout.root'),'_plugins'])
_context = self.get('system.context')
_parentContext = self.get('system.parentContext')
_map = {}
_plugins = {}
if self.get('system.source.id') == 'cloud' :
_plugins = cloud.plugins(_context)
else:
_plugins = disk.plugins(context=_context)
_uri = 'api/system/debug'
_uri = _uri if not _context else f'{_context}/{_uri}'
_plugins[_uri] = self.debug
if os.path.exists(_folder) and self.get('plugins'):
_items = self.get('plugins')
for _filename in _items:
_path = os.sep.join([_folder,_filename+'.py'])
if os.path.exists(_path) :
for _module in _items[_filename] :
_pointer = disk.plugins(path=_path,name=_module,context=_context)
if _pointer :
_uri = f"api/{_filename}/{_module}"
_uri = f"{_context}/{_uri}" if _context else _uri
_map[_uri] = _pointer
if _parentContext :
# _uri = f"{_parentContext}/{_context}"
_map[_uri] = _pointer
self.log(action='load.plugin',module='plugins',input={'uri':_uri,'path':_path,'name':_module})
else:
self.log(action='missing.function',module='plugins',input={'name':_module,'path':_path})
else:
#
# log the error ...
self.log(action='missing.plugins',module='plugins',input=_path)
#
# Updating plugins from disk/cloud
_plugins = _map if not _plugins else dict(_plugins,**_map)
#
#
self.set('plugins',_plugins)
self.log(action='init.plugins',module='plugins',input=list(_plugins.keys()))
class Site(Initialization) :
def __init__(self,**_args):
super().__init__(**_args)
self._config['system']['portal'] = (self.get('system.routes')) == None
def html(self,_uri,_id) :
_handler = cloud if self.get('system.source.id') == 'cloud' else disk
_html = _handler.html(_uri, self.get(None))
return " ".join([f'<div id="{_id}"> ',_html,"</div>"])
class QCMS:
def __init__(self,**_args):
_app = Site(**_args)
self._id = _app.get('system.context') #if _app.get('system.context') else 'main'
self._sites = {self._id:_app}
if _app.get('system.routes') :
_routes = _app.get('system.routes')
for _name in _routes :
_path = _routes[_name]['path']
self._sites[_name] = Site(context=_name,path=_path,caller=_app)
def render(self,_uri,_id,_appid=None):
_site = self._sites[_appid] if _appid else self._sites[self._id]
_args = {'layout':_site.get('layout')}
_system = _site.get('system')
for k in ['source','app'] :
if k in _system :
del _system[k]
_args['system'] = _system
_html = _site.html(_uri,_id)
_env = Environment(loader=BaseLoader()).from_string(_html)
_args[_id] = str(_env.render(**_args))
return _args
def set(self,_id):
self._id = _id
def get(self,_id=None):
return self._sites[self._id] if not _id else self._sites[_id]

@ -0,0 +1,60 @@
/***
*
* This file build forms from a given set of specifications (in JSON) format
*/
var _form =function(_id,_data) {
this._data = _data // {input,defaults,choices}
this._id = _id
//
// let's clean up the data :
this._data.defaults = (this._data.defaults == null)? {}:this._data.defaults
this._data.choices = (this._data.choices == null)?{}: this._data.choices
this.render = function (){
_title = $('<div></div>').html(this._id).addClass(this._id)
var _input = this._data.input
var _defaults = this._data.defaults
var _choices = this._data.choices
_input.forEach (_label=>{
_value = _defaults[_label]
if (_choices[_label]){
}else{
//
// This
_input = this.get.input(_label,_value)
}
})
}
this.get = {}
this.get.input = function (label,value){
_frame = $('<div></div>')
_label = $('<div></div>').html(label)
_input = $('<input type="text"/>').attr('placeholder',label.0.replace(/_/,' ').trim()).addClass("input-"+label)
$(_input)[0].data = label
if (value){
$(_input).val(value)
}
_frame.append(_label,_input)
return _frame
}
this.get.choice= function (label,_default,choices) {
var _input = this.get.input(label,_default)
$(input).addClass("hidden-input")
$(_input).on('click',function (){
})
_pane = $('<div class="choices"></div>')
choices.forEach(_value=>{
_option = $('<div><div>').addClass("option").addClass("option-"+_value)
$(_pane).append(_option)
})
_pane.append(_input,choices)
_frame = $('<div></div>').append(_pane)
return _frame
}
}

@ -196,6 +196,7 @@ jx.utils.patterns.observer = function(lobservers,init){
// let's fire the design pattern
//
p.start() ;
return p
}
/**

@ -281,7 +281,6 @@ var QCMSBasic= function(_layout,_context,_clickEvent) {
// Object.keys(this._layout.menu)
_names.forEach(function(_name){
var _div = _me._make(_layout.menu[_name]) ;
@ -299,7 +298,7 @@ var QCMSBasic= function(_layout,_context,_clickEvent) {
}
}
var QCMSTabs = function(_layout,_context,_clickEvent){
var QCMSTabs = function(_layout,_context,_id){
//
// This object will make tabs in the area of the menu
// @TODO: we can parameterize where the menu is made to improve flexibility
@ -308,6 +307,7 @@ var QCMSTabs = function(_layout,_context,_clickEvent){
this.tabs.className = 'tabs'
this._context = _context
this._layout = _layout
this._target = (_id == null)?'.main .menu':_id
this._make = function (text,_item,_event){
var text = text.trim().replace(/(_|-)/ig,' ').trim()
var _context = this._context;
@ -364,9 +364,9 @@ var QCMSTabs = function(_layout,_context,_clickEvent){
})
this.tabs.className = 'tabs'
$('.main .menu').append(this.tabs)
$('.main .menu').css({'border':'1px solid transparent'})
$('.main .menu').css({'grid-template-columns':'64px auto'})
$(this._target).append(this.tabs)
$(this._target).css({'border':'1px solid transparent'})
$(this._target).css({'grid-template-columns':'64px auto'})
}
//
// We need to load the pages here ...

@ -15,6 +15,7 @@ Vanderbilt University Medical Center
<!DOCTYPE html>
<html lang="en">
<head >
<title>{{layout.header.title}}</title>
<link rel="shortcut icon" href="{{system.icon}}">
@ -37,6 +38,7 @@ Vanderbilt University Medical Center
<link href="{{system.parentContext}}/static/css/dialog.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/dashboard.css" rel="stylesheet" type="text/css">
<!-- applying themes as can -->
<link href="{{system.context}}/api/disk/read?uri={{layout.root}}/_assets/themes/{{system.theme}}/layout.css" rel="stylesheet" type="text/css">
<link href="{{system.context}}/api/disk/read?uri={{layout.root}}/_assets/themes/{{system.theme}}/header.css" rel="stylesheet" type="text/css">
@ -44,6 +46,7 @@ Vanderbilt University Medical Center
<link href="{{system.context}}/api/disk/read?uri={{layout.root}}/_assets/themes/{{system.theme}}/borders.css" rel="stylesheet" type="text/css">
<link href="{{system.context}}/api/disk/read?uri={{layout.root}}/_assets/themes/{{system.theme}}/footer.css" rel="stylesheet" type="text/css">
<link href="{{system.context}}/api/disk/read?uri={{layout.root}}/_assets/themes/{{system.theme}}/pane.css" rel="stylesheet" type="text/css">
<link href="{{system.context}}/api/disk/read?uri={{layout.root}}/_assets/themes/{{system.theme}}/responsive.css" rel="stylesheet" type="text/css">
<!-- -->
<meta property="og:title" content="{{layout.header.title}}" />
@ -85,7 +88,7 @@ Vanderbilt University Medical Center
</script>
<body>
<div class="main {{system.theme}}" >
<div id="header" class="header" onclick="window.location.href='{{system.parentContext}}'" style="cursor:pointer">
<div id="header" class="header" onclick="window.location.href='{{system.context}}'" style="cursor:pointer">
{%include "header.html" %}
</div>

@ -1,8 +1,12 @@
{%if system.portal %}
{%if system.caller %}
{% if system.parentContext == ""%}
{% set _backURI = "/" %}
{% else %}
{% set _backURI = system.parentContext%}
{% endif %}
<div class="icon active">
<div align="center" class="button" onclick="window.open('{{system.parentContext}}/','_self')" style="display:grid; grid-template-columns:auto auto; gap:4px; align-items:center ">
<div align="center" class="button" onclick="window.open('{{_backURI}}','_self')" style="display:grid; grid-template-columns:auto auto; gap:4px; align-items:center ">
<i class="fa-solid fa-chevron-left" style="color:darkgray; display:block"></i>
<img src="{{system.caller.icon}}" style="height:100%"/>
</div>
@ -12,3 +16,4 @@
<i class="fa-solid fa-home"></i>
</div>
{% endif %}

@ -38,6 +38,7 @@ The content is created in ./qcms-demo/www/html
Things to update with qcms runner :
- system.icon location, when creating a project
- applying themes
- upgrade all plugins to return data-type ?

Loading…
Cancel
Save