bug fix: with context handling

pull/25/head
Steve Nyemba 3 weeks ago
parent cbf63d3f56
commit d9bf641a24

@ -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 :

@ -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>/<resource>")
@_app.route("/<app>",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)
_uri = os.sep.join([_site.get('layout.root'),_site.get('layout.index')])
return render_template('index.html',**_route.render(_uri,'index',_id)),200
_setHandler(app,resource)
_html,_code = _getIndex(app,resource)
return _html,_code
# def _aindex (app,resource=None):
# _handler = _getHandler(app,resource)
# _setHandler(app,resource)
# _html,_code = _getIndex(app,resource)
# return _html,_code
# @_app.route('/id/<uid>')
# def people(uid):
# """
@ -116,31 +134,34 @@ def _aindex (app,resource=None):
# global _config
# return "0",200
@_app.route('/<app>/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()
_site = _route.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['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 _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)
# _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=_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()
_site = _route.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 :
# # 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('/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':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):
"""
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('/<app_id>/page',defaults={'resource':None},methods=['GET'])
@_app.route('/<app_id>/<resource>/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())

@ -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'<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]

@ -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]) ;

@ -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}}" />
@ -84,8 +87,9 @@ 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 %}

Loading…
Cancel
Save