From d9bf641a2480ba53636e1119b2b4e42faf32f8df Mon Sep 17 00:00:00 2001 From: Steve Nyemba Date: Thu, 5 Jun 2025 08:54:00 -0500 Subject: [PATCH] bug fix: with context handling --- cms/__init__.py | 14 +- cms/disk.py | 8 +- cms/index.py | 133 +++++++------- cms/sites/__init__.py | 352 ++++++++++++++++++++++++++++++++++++++ cms/static/js/jx/utils.js | 1 + cms/static/js/menu.js | 1 - cms/templates/index.html | 8 +- cms/templates/menu.html | 13 +- 8 files changed, 450 insertions(+), 80 deletions(-) create mode 100644 cms/sites/__init__.py diff --git a/cms/__init__.py b/cms/__init__.py index d621116..2300b33 100644 --- a/cms/__init__.py +++ b/cms/__init__.py @@ -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: diff --git a/cms/disk.py b/cms/disk.py index bd1b6ce..4d105b4 100644 --- a/cms/disk.py +++ b/cms/disk.py @@ -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 : diff --git a/cms/index.py b/cms/index.py index de5870b..bfe2ceb 100644 --- a/cms/index.py +++ b/cms/index.py @@ -26,6 +26,7 @@ import datetime import requests from cms import disk, cloud, engine +import cms.sites _app = Flask(__name__) cli = typer.Typer() @@ -49,7 +50,8 @@ def _getHandler (app_id,resource=None) : return _route._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) @@ -98,16 +100,32 @@ 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 _route + _app = _route.get(None) + _uri = os.sep.join([_app.get('layout.root'),_app.get('layout.index')]) + _html = _route.render(_uri,'index') + return render_template('index.html',**_route.render(_uri,'index')),200 @_app.route("//") @_app.route("/",defaults={'resource':None}) -def _aindex (app,resource=None): - _handler = _getHandler(app,resource) +def _altIndex(app,resource): + global _route + _id = _getId(app,resource) + + _site = _route.get(_id) - _setHandler(app,resource) - _html,_code = _getIndex(app,resource) - return _html,_code + _uri = os.sep.join([_site.get('layout.root'),_site.get('layout.index')]) + return render_template('index.html',**_route.render(_uri,'index',_id)),200 + + +# def _aindex (app,resource=None): + +# _handler = _getHandler(app,resource) + +# _setHandler(app,resource) +# _html,_code = _getIndex(app,resource) +# return _html,_code # @_app.route('/id/') # def people(uid): # """ @@ -116,31 +134,34 @@ def _aindex (app,resource=None): # global _config # return "0",200 @_app.route('//dialog') -@_app.route('/dialog',defaults={'app':'main'}) -def _dialog (app): - # global _config +@_app.route('/dialog',defaults={'app':None}) +def _getdialog(app): global _route - _handler = _route.get() - _uri = request.headers['uri'] - + _site = _route.get(app) + _uri = request.headers['uri'] _id = request.headers['dom'] # _html = ''.join(["
",str( e.render(**_args)),'
']) _args = _route.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//",defaults={'app':'main','key':None},methods=['GET','POST','DELETE','PUT']) +@_app.route("/api//",defaults={'app':None,'key':None},methods=['GET','POST','DELETE','PUT']) @_app.route("//api//",defaults={'key':None},methods=['GET','POST','DELETE','PUT']) @_app.route("///api//",methods=['GET','POST','DELETE','PUT']) 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) + # _handler = _getHandler(app,key) + # uri = f'api/{module}/{name}' + # _delegate = cms.delegate() + # return _delegate(uri=uri,handler=_handler,request=request) + global _route + + _id = _getId(app,key) + + _site = _route.get(_id) + _uri = f'api/{module}/{name}' _delegate = cms.delegate() - return _delegate(uri=uri,handler=_handler,request=request) - + return _delegate(uri=_uri, handler=_site, request=request) @_app.route('/version') def _version (): @@ -152,54 +173,38 @@ def _version (): def _reload(key) : global _route - _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() - return "",200 - pass - return "",403 - -@_app.route('/reload',methods=['POST']) -def reload(): - # global _route - - # _handler = _route.get_main() + _site = _route.get(None) + _systemKey = _site.get('system.source.key') + if _systemKey == key and _systemKey: + _site.reload() + return "",200 + else: + return "",403 # _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 : + # # print ([key,_systemKey,_systemKey == key]) + # if key and _systemKey and _systemKey == key : # _handler.reload() # return "",200 # pass - # return "",403 + # return "",403 + +@_app.route('/reload',methods=['POST']) +def reload(): + _key = request.headers['key'] if 'key' in request.headers else None + return _reload(_key) + -@_app.route('/page',methods=['POST'],defaults={'app_id':'main','key':None}) +@_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None}) @_app.route('//page',methods=['POST'],defaults={'key':None}) @_app.route('///page',methods=['POST']) -def _POST_CMSPage(app_id,key): - """ - return the content of a folder formatted for a menu - """ - # global _config +def getPostedPage(app_id,key): 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']]) + _id = _getId(app_id,key) + _site = _route.get(_id) _uri = request.headers['uri'] if 'dom' not in request.headers : _id = _uri.split('/')[-1].split('.')[0] @@ -210,8 +215,8 @@ def _POST_CMSPage(app_id,key): _uri = _uri.split('=')[1] _args = _route.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('//page',defaults={'resource':None},methods=['GET']) @_app.route('///page',methods=['GET']) def _cms_page (app_id,resource): @@ -220,10 +225,8 @@ def _cms_page (app_id,resource): # _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 = _route.render(_uri,_title,session.get(app_id,'main')) + _args = _route.render(_uri,_title,app_id) return _args[_title],200 @@ -250,11 +253,11 @@ def start ( # _route = cms.engine.Router(**_args) #path=path,shared=shared) - _route = cms.engine.basic.CMS(**_args) + # _route = cms.engine.basic.CMS(**_args) + # _args = _route.get().app() + _route = cms.sites.QCMS(**_args) + _args = _route.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()) diff --git a/cms/sites/__init__.py b/cms/sites/__init__.py new file mode 100644 index 0000000..a122f9f --- /dev/null +++ b/cms/sites/__init__.py @@ -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.locations(**_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'
',_html,"
"]) + +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] diff --git a/cms/static/js/jx/utils.js b/cms/static/js/jx/utils.js index a3834ac..52d54a5 100755 --- a/cms/static/js/jx/utils.js +++ b/cms/static/js/jx/utils.js @@ -196,6 +196,7 @@ jx.utils.patterns.observer = function(lobservers,init){ // let's fire the design pattern // p.start() ; + return p } /** diff --git a/cms/static/js/menu.js b/cms/static/js/menu.js index 794efdb..7d234a2 100644 --- a/cms/static/js/menu.js +++ b/cms/static/js/menu.js @@ -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]) ; diff --git a/cms/templates/index.html b/cms/templates/index.html index 347d440..8bbe33b 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -15,6 +15,7 @@ Vanderbilt University Medical Center + {{layout.header.title}} @@ -37,6 +38,7 @@ Vanderbilt University Medical Center + @@ -44,6 +46,7 @@ Vanderbilt University Medical Center + @@ -84,8 +87,9 @@ Vanderbilt University Medical Center -
-