Merge pull request 'v3.0' (#62) from v3.0 into master

Reviewed-on: cloud/cms#62
master
Steve L. Nyemba 2 weeks ago
commit 5f2517d442

@ -28,6 +28,8 @@ import cms.engine.project
import pandas as pd
import requests
import plugin_ix
from enum import Enum
from rich.table import Table
from rich import print
start = index.start
@ -202,78 +204,6 @@ def load(**_args):
return getattr(module,_name) if hasattr(module,_name) else None
# @cli.command(name='plugins')
# def plugin_manager (manifest:Annotated[str,typer.Argument(help="path to manifest file")],
# show:bool=typer.Option(default=False,help="list plugins loaded"),
# add: Annotated[Optional[bool],typer.Option("--register/--unregister",help="add/remove a plugin to manifest use with --pointer option")] = None,
# pointer:str=typer.Option(default=None,help="pointer is structured as 'filename.function'")
# ) :
# """
# Manage plugins list loaded plugins,
# """
# manifest = get_manifest(manifest)
# _config = cms.engine.config.get(manifest)
# if _config :
# _root = os.sep.join(manifest.split(os.sep)[:-1] + [_config['layout']['root'],'_plugins'])
# else :
# _root = None
# if _root and os.path.exists(_root) :
# # files = os.listdir(_root)
# _msg = f"""{FAILED} no operation was specified, please use --help option"""
# # if 'plugins' in _config :
# _plugins = _config['plugins'] if 'plugins' in _config else {}
# if show :
# if not _plugins :
# _msg = f"""{FAILED} no plugins are loaded\n\t{manifest}"""
# else:
# _data = []
# _plugConf = _config['plugins']
# for _name in _plugConf :
# _log = {"files":_name,"loaded":len(_plugConf[_name]),"functions":json.dumps(_plugConf[_name])}
# _data.append(_log)
# _data= pd.DataFrame(_data)
# # # # _data = plugins.stats(_plugins)
# # # _data = cms.Plugin.stats(_plugins)
# print (to_Table(_data))
# _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: found a total of {_data.loaded.sum()} plugins loaded from {_data.shape[0]} file(s)\n\t{_root}"""
# if add in [True,False] and pointer :
# file,fnName = pointer.split('.')
# # _fnpointer = plugins.load(_root,file+'.py',fnName)
# # _fnpointer = cms.Plugin.load(_root,file+'.py',fnName)
# _ploader = plugin_ix.Loader(file = os.sep.join([_root,file+'.py']))
# if add and _ploader.has(fnName):
# if file not in _plugins :
# _plugins[file] = []
# if fnName not in _plugins[file] :
# _plugins[file].append(fnName)
# _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: registered {pointer}, use the --show option to list loaded plugins"""
# else:
# _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: could not register {pointer}, it already exists"""
# elif add is False and file in _plugins:
# _plugins[file] = [_name.strip() for _name in _plugins[file] if _name.strip() != fnName.strip() ]
# _msg = f"""{PASSED} [bold]{_config['layout']['header']['title']}[/bold]: unregistered {pointer}, use the --show option to list loaded plugins """
# # #
# # # We need to write this down !!
# if add in [True,False] :
# _config['plugins'] = _plugins
# cms.engine.config.write(_config,manifest)
# # else:
# # _msg = f"""{FAILED} [bold]{_config['layout']['header']['title']}[/bold]: no plugins are loaded """
# print()
# print(_msg)
# else:
# _msg = f"""{FAILED} No plugin folder could be found in {manifest}"""
# print (_msg)
@cli.command (name='create')
def create(folder:Annotated[str,typer.Argument(help="path of the project folder")],
@ -348,10 +278,16 @@ def reload (
else:
_msg = f"""{FAILED} no secure key found in manifest to request reload"""
print (_msg)
class Platform(str, Enum):
desktop = "desktop"
web = "web"
# cli = "cli"
@cli.command(name="bootup")
def bootup (
manifest:Annotated[str,typer.Argument(help="path of the manifest file")]='qcms-manifest.json',
port:int=typer.Option(default=None, help="port number to serve on (will override configuration)")
port:int=typer.Option(default=None, help="port number to serve on (will override configuration)"),
):
"""
This function will launch a site/project given the location of the manifest file

@ -170,28 +170,3 @@ def plugins (**_args):
_pointer = loader.get(_args['name'])
return _pointer
return None
# if os.path.isdir(_path):
# files = os.listdir(_path)
# if files :
# files = [name for name in files if name.endswith('.py')]
# if files:
# _uri = [_path,files[0]]
# if _context :
# _uri = [_context] + _uri
# _path = os.sep.join(_uri)
# else:
# return None
# else:
# #
# # LOG: not a file
# return None
# #-- We have a file ...
# _name = _args['name']
# spec = importlib.util.spec_from_file_location(_name, _path)
# module = importlib.util.module_from_spec(spec)
# spec.loader.exec_module(module)
# #
# # LOG This plugin ....
# return getattr(module,_name) if hasattr(module,_name) else None

@ -0,0 +1,25 @@
import json
import pandas as pd
import importlib
import importlib.util
import os
#
# Defining the decorator to be used in plugins, this will enable simple loading and assigning mimetype to the output (if any)
#
import cms
# @cms.Plugin(mimetype="application/json")
# def authenticate(**_args) :
# _config = _args['config']
# _request= _args['request']
# #
# # let's load the configuration for
# _manager = cms.secure.Manager(config=_config)
# #
# #
# _cookies,_args = _manager.authenticate(request=_request)
#
# The html page weill be provided by the front-end
#

@ -48,7 +48,7 @@ def favicon (app):
_site = _qcms.get (request)
# return _read(app,None,_site.get('system.icon'))
return redirect(_site.get('system.icon')[1:]) #_site.read(request,uri=_site.get('system.icon'))
return redirect(_site.get('system.icon')) #_site.read(request,uri=_site.get('system.icon'))
@_app.route("/<_id>/robots.txt")
@_app.route("/robots.txt",defaults={'_id':None})
def robots_txt(_id):
@ -178,8 +178,9 @@ def logout():
@_app.route('/dialog',defaults={'app':None},methods=['POST','GET'], strict_slashes = False)
@_app.route('/<path:app>/dialog', methods=['POST','GET'], strict_slashes = False)
@_app.route('/<path:app>/dialog/', methods=['POST','GET'], strict_slashes = False)
def _dialog(app):
global _qcms
# _site = _qcms.get(app)
_site = _qcms.get(request)
@ -210,6 +211,7 @@ def reload():
@_app.route("/",defaults={'file':None}, methods=['POST','GET','PUT'], strict_slashes = False)
@_app.route('/<path:file>',defaults={}, methods=['POST','GET','PUT'], strict_slashes = False)
# @_app.route('/<app>/files/<path:file>', defaults=[{'module':None}])
# @_app.route('/<app>/<module>/files/<path:file>')
def _read(file):
@ -218,7 +220,7 @@ def _read(file):
# apply security here (on the top level site only)
# @TODO: upon failure, need to forward this to an error page ...
#
_site = _qcms.get(None)
_site = _qcms.get(request)
if _site.secure.allow(request=request) :
return _qcms.delegate(request)
else:
@ -252,27 +254,27 @@ def _version ():
return _site.get('system.version')
@_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 getPostedPage(app_id,key):
"""
This function will return a page given a location parameter
"""
global _qcms
_id = _getId(app_id,key)
_site = _qcms.get(_id)
_uri = request.headers['uri']
# @_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 getPostedPage(app_id,key):
# """
# This function will return a page given a location parameter
# """
# 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]
else:
_id = request.headers['dom']
# if 'dom' not in request.headers :
# _id = _uri.split('/')[-1].split('.')[0]
# else:
# _id = request.headers['dom']
if 'read?uri=' in _uri or 'download?doc=' in _uri :
_uri = _uri.split('=')[1]
_args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
return _args[_id],200
# if 'read?uri=' in _uri or 'download?doc=' in _uri :
# _uri = _uri.split('=')[1]
# _args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
# return _args[_id],200
@_app.route('/page',defaults={'app_id':None,'resource':None})
@_app.route('/<app_id>/page',defaults={'resource':None},methods=['GET'])
@ -303,6 +305,7 @@ def start (
_args['location'] = path
_args['shared'] = True
_qcms = cms.sites.QCMS(**_args)
_args = _qcms.get().get('system.app')

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

@ -29,8 +29,8 @@ class Manager :
_csv = """user,uri,allow\n*,*,1"""
self._permissions = pd.read_csv(io.StringIO(_csv))
if 'secure' in _args['config']['system']['source'] :
_source = {} if 'source' not in _args['config']['system'] else _args['config']['system']['source']
if 'secure' in _source :
self._authContext = _appConfig['system']['source']['secure']['id']
self._path = _appConfig['system']['source']['secure']['path']
self.inspect(self._path,'authentication configuration')
@ -52,8 +52,10 @@ class Manager :
#
# we need to load the permissions here ...
#
_kwargs = _appConfig['system']['source']['secure']
self._loginURI = [_appConfig['system']['context']]
self._loginURI = [_appConfig['system'].get('context','')]
if 'uri' in _kwargs :
self._loginURI.appendd(_kwargs['uri'])
else:
@ -62,7 +64,7 @@ class Manager :
#
# we will assume that all authenticated users have access to every part of the site
#
if 'authorization' in _kwargs:
if 'authorization' in _kwargs and _kwargs['authorization']:
# #
# # loading permissions table from a designated location
reader = transport.get.reader(**_kwargs['authorization'])

@ -18,29 +18,60 @@ import pandas as pd
class RequestController :
def __init__(self,_route):
# self._plugins = _plugins
self._routes = _route
#
# let's build a map of routes i.e ends with / or not they should reference the same objects
# This allows us to gain has map performance without writing much code
self._map = {}
for _name in self._routes :
self._map[_name] = _name
self._map[f'/{_name}/'.replace('//','/')] = _name
self._map[f'/{_name}'.replace('//','/')] = _name
def isfile(self,request):
#
# call self.exists to determin if this is an actual file or not
return '.' in request.path
def isroute(self,request):
# return '/'.join(request.path[1:].split('/')[:2]) in self._routes
return self.get_route(request) in self._routes
return False
return self.get_route(request)
# p = self.get_route(request) in self._routes
# q = request.path != '' and request.path.split('/')[1] in self._routes
# _context = '' if request.path.strip() == '' else request.path.split('/')[1]
# print ([' ## ',q, q, request.path,_context])
# return p or q
def isapi(self,request):
return 'api' in request.path
def get_route(self,request):
_items = request.path[1:].split('/')
_items = _items if _items[-1] != '' else _items[:-1]
N = len(_items) + 1
_names = []
for i in range(1, len(_items) + 1):
# _items = request.path[1:].split('/')
_names.append('/'.join(_items[:i]))
# if not '.' in request.path :
# print (self._routes)
# print (_items)
# if _items[0] in self._routes :
# return _items[0]
# else:
# return None
#
# We should find the appropriate route for any resource access
_uri = request.path
if _uri not in self._map :
_items = _uri.split('/')
_x = [_items[0]]
for _name in _items[1:] :
if '/'.join(_x+[_name]) in self._map :
_x.append(_name)
else:
break
#
#
if '/'.join(_x) in self._map :
self._map[_uri] = self._map['/'.join(_x)]
_uri = '/'.join(_x)
_names=[_item for _item in _names if _item in self._routes]
return _names[-1] if _names else None
return self._map.get(_uri,None)
class IOConfig:
"""
@ -99,7 +130,15 @@ class Initialization (IOConfig):
# self._config = self.read_config(**_args)
self._args = _args
#
# in case the path provided does NOT have the manifest
if 'path' in self._args and not os.path.isfile(self._args['path']) :
#
#
self._args['path'] = f"{self._args['path']}{os.sep}qcms-manifest.json"
#
# Invoke initialization
self.reload()
#
if self._caller and self._caller.secure :
@ -146,6 +185,53 @@ class Initialization (IOConfig):
self.log(action='init.config',module='read_config',input={'caller':_name})
return _config
def context(self,**_args):
"""
updating context and parent context
"""
_m = {}
_context = _args['context'] if 'context' in _args else self.get('system.context')
_parentContext = self.get('system.parentContext') if not self._caller else self.get('system.parentContext')
#
# Logging what we've done ...
#
if self._caller :
_x = self._caller.get('system.context').split('/') + [_context]
_context = f'{self._caller.get("system.context")}/{_context}/'.replace('//','/')
_parentContext= f'/{self._caller.get("system.context")}'.replace('//','/')
if _parentContext == '/' :
#
# This is the case when the site is the actual website (e.g: https://foo.com)
_parentContext = ''
_parentIcon = self._caller.get('system.icon')
_parentIcon = _parentIcon if _parentIcon.startswith('/') else f'/{_parentIcon}'
self.set('system.parentContext',_parentContext)
self.set('system.caller',{'icon':_parentIcon,'context':_parentContext})
else:
_context = f'/{_context}/' if _context not in ['','/'] else _context
_parentContext = _context
_context = _context.replace('//','/')
_parentContext = _parentContext.replace('//','/') if _parentContext else ''
self.set('system.context',_context)
self.set('system.parentContext',_parentContext)
# self.set('system.parentContext',_parentContext)
#
# If for whatever reason the main page has context and ends with forward slash
# we need to update the expression of context
#
# request = _args['request']
self.log(action='init.context',module='context',input={'context':_context, 'parentContext':_parentContext})
#
# loosly context to a certain extent involves locations of icons and document root
#
self.locations(**_args)
def _context(self,**_args):
"""
Updating the context so that we can have a consistent representation
"""
@ -174,6 +260,8 @@ class Initialization (IOConfig):
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')})
@ -193,6 +281,12 @@ class Initialization (IOConfig):
# self._config['system']['context'] = _context.strip()
self._config['system']['parentContext'] = _parentContext
p = {'has_caller':self._caller != None,'context':_context}
#
# If for whatever reason the main page has context and ends with forward slash
# we need to update the expression of context
#
# request = _args['request']
self.log(action='init.context',module='context',input=p)
#
# loosly context to a certain extent involves locations of icons and document root
@ -219,15 +313,20 @@ class Initialization (IOConfig):
# else:
# _icon = f'{_context}/api/disk/read?uri={_logo}'
# _icon = f'{_context}/{_logo}'.replace(_root,'')
_icon = f'{_context}/{_logo}' if _context else _logo
_icon = f'/{_context}/{_logo}' if _context else _logo
self.set('system.icon',_icon)
self.set('system.logo',_icon)
# self.set('system.icon',_icon)
# self.set('system.logo',_icon)
self.set('system.icon',f'{_context}{_logo}') #f'/{_context}/{_logo}'.replace('//','/'))
self.set('system.logo',_logo)
#
# 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)
@ -310,6 +409,20 @@ class Initialization (IOConfig):
# self._config['layout']['menu'] = _menu
else:
pass
def _api(self):
"""
This function will update public facing api provided
"""
# _context = self.get('system.context')
_plugins = self.get('plugins')
for _key in ['debug','log'] :
_pointer = getattr(self,_key)
_uri = f'api/system/{_key}'
# if _uri[0] == '/' :
# _uri = _uri[1:]
_plugins[_uri] = _pointer
self.set('plugins',_plugins)
def plugins (self,**_args):
"""
This function will load the plugins from disk (only)
@ -319,15 +432,18 @@ class Initialization (IOConfig):
_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 = f'{_context}/api/system/debug'
if _uri.startswith('/') :
_uri = _uri[1:]
# _uri = _uri if not _context else f'{_context}/{_uri}'
_plugins[_uri] = self.debug
# _uri = f'{_context}/api/system/debug'
# if _uri.startswith('/') :
# _uri = _uri[1:]
# # _uri = _uri if not _context else f'{_context}/{_uri}'
# _plugins[_uri] = self.debug
self._api() #-- adding default api calls
if os.path.exists(_folder) and self.get('plugins'):
_items = self.get('plugins')
@ -340,10 +456,10 @@ class Initialization (IOConfig):
_pointer = disk.plugins(path=_path,name=_module,context=_context)
if _pointer :
_uri = f"api/{_filename}/{_module}"
_uri = f"{_context}/{_uri}" if _context else _uri
# _uri = f"{_context}/{_uri}" if _context else _uri
if (_uri.startswith("/")) :
_uri = _uri[1:]
_uri.replace('//','/')
_map[_uri] = _pointer
if _parentContext :
# _uri = f"{_parentContext}/{_context}"
@ -360,11 +476,14 @@ class Initialization (IOConfig):
#
# Updating plugins from disk/cloud
_plugins = _map if not _plugins else dict(_plugins,**_map)
#
# if we have login enabled we should add them as a plugins
#
self.set('plugins',_plugins)
self._api() #-- updating with system level api
self.log(action='init.plugins',module='plugins',input=list(_plugins.keys()))
class Site(Initialization) :
@ -374,6 +493,7 @@ class Site(Initialization) :
self._routes = []
if self.get('system.routes') :
self._routes = list(self.get('system.routes').keys())
self.log(action='init.routes',modules='site',input=self._routes)
if self._caller :
self._routes = self._caller._routes
self.inspect = RequestController(self._routes)
@ -386,24 +506,17 @@ class Site(Initialization) :
_plugins['api/oauth2/final'] = cms.oauthFinalize
self.set('plugins',_plugins)
def exists (self,uri):
path = []
if self.get('layout.location'):
path.append(self.get('layout.location'))
if self.get('layout.root') not in uri :
path.append(self.get('layout.root'))
# path = []
# if self.get('layout.location'):
# path.append(self.get('layout.location'))
# if self.get('layout.root') not in uri :
# path.append(self.get('layout.root'))
# path.append(uri)
# return os.path.exists( os.sep.join(path))
return os.path.exists(uri)
path.append(uri)
return os.path.exists( os.sep.join(path))
# def _html(self,_uri,_id,request) :
# if self.secure.allow(request=request):
# _handler = cloud if self.get('system.source.id') == 'cloud' else disk
# _html = _handler.html(_uri, self.get(None))
# #
# # maybe apply the environment here ??
# return " ".join([f'<div id="{_id}"> ',_html,"</div>"])
# else:
# None
def mimeType(self,uri):
_extension = uri.split('.')[-1].strip().lower()
_mimeType = 'application/octet-stream'
@ -419,13 +532,13 @@ class Site(Initialization) :
return _mimeType
def path(self,_uri):
path = []
if self.get('layout.location'):
path.append(self.get('layout.location'))
if self.get('layout.root') not in _uri :
path.append(self.get('layout.root'))
path.append(_uri)
return os.sep.join(path)
def html (self,_request):
_uri = self.uri(_request)
@ -439,11 +552,12 @@ class Site(Initialization) :
# _content = f'<div>{_content}</div>'
if _uri.split('.')[-1].strip().lower() == 'md':
_content = mistune.html(_content).replace("&quot;",'"').replace("&lt;","<").replace("&gt;",">") if _uri[-2:] in ['md','MD','Md','mD'] else _content
_content = f"<div>{_content}</div>" #if 'dom' not in _request.headers else f'<div id="{_request.headers['dom']}">{_content}</div>'
# _content = f"<div>{_content}</div>" #if 'dom' not in _request.headers else f'<div id="{_request.headers['dom']}">{_content}</div>'
else:
_env = Environment(loader=BaseLoader()).from_string(_content)
_env = Environment(loader=BaseLoader()).from_string(f"<div>{_content}</div>")
_content = str(_env.render(**self.get(None)))
pass
return _content,_mimeType
def uri(self,request):
if 'uri' in request.headers or 'uri' in request.args :
@ -455,6 +569,7 @@ class Site(Initialization) :
#
file = request.path.replace(f'{_route}','')
#
if file.startswith('//') or file.startswith('/') :
# NOTE: false positive if file.startswith('/') is used
file = file[2:] if file.startswith('//') else file[1:]
@ -482,9 +597,14 @@ class Site(Initialization) :
#
# We can NOT serve python files over the web (possible security issue)
return None
f = open(_uri,_mode)
_content = f.read()
f.close()
#
# If the file doesn't exists we should return 404
if self.exists(_uri):
f = open(_uri,_mode)
_content = f.read()
f.close()
else:
_content = """<div align="center"><div class="large-text">404</div><div>File Not Found</div></div>"""
return _content
def read(self,request) :
_kwargs = {'allow':0}
@ -505,6 +625,10 @@ class Site(Initialization) :
# _content = io.BytesIO(f.read())
# f.close()
#
# maybe the path needs some massage ...
_content = self.open(uri=self.path(_uri),mode='rb')
_content = io.BytesIO( _content )
_kwargs = {'allow':1,'mimeType':_mimeType,'extension':_extension,
@ -513,7 +637,7 @@ class Site(Initialization) :
self.log(action='file.read',module='site.read',input=_kwargs)
if _content :
return _content, _mimeType
return None, 'plain/html'
return None, 'text/html'
# if 'html' in _mimetype :
# _args = {'layout':self.get('layout'),'system':self.get('system')}
@ -537,6 +661,17 @@ class Site(Initialization) :
_kwargs = {'layout':self.get('layout')}
_system = self.get('system')
#
# we are going to correct an issue with context given that the initial url (portal) can end with forward slash
# This happens when using proxy-forwarding
_context = _system['context']
# if not self._caller :
# _system['context'] = f'/{_context}/'.replace('//','/')
# _system['parentContext'] = f'/{_context}/'.replace('//','/')
# else:
# _system['parentContext'] = f'/{self._caller.get("system.context")}/'
for k in ['source','app'] :
if k in _system :
del _system[k]
@ -557,6 +692,7 @@ class Site(Initialization) :
return None
def apply_tags (self,_html):
_kwargs = {'layout':self.get('layout'),'system':self.get('system')}
_env = Environment(loader=BaseLoader()).from_string(f'<div>{_html}</div>')
return _env.render(**_kwargs)
def run(self,_request) :
@ -564,12 +700,15 @@ class Site(Initialization) :
_data = f"<div align='center' style='padding:20%'><div style='font-size:32px; font-weight:bold'>404</div style='font-weight:lighter'><div>{_request.path} Not Found</div></div>"
_mimeType = 'text/html'
_code = 404
_key = _request.path[1:] #if self.get('system.context') != '' else _request.path[1:]
# print ([_key,_key in list(_plugins.keys())])
# print (list (_plugins.keys()))
# _key = _request.path[1:] #if self.get('system.context') != '' else _request.path[1:]
_index = _request.path.index('api')
_key = _request.path[_index:]
if _plugins and _key in _plugins:
_mimeType = 'application/octet-stream'
_pointer = _plugins.get(_key)
if hasattr(_pointer,'mimetype'):
_mimeType = _pointer.mimetype
_data = _pointer(request=_request,config=self.get(None))
@ -585,6 +724,7 @@ class Site(Initialization) :
elif type(_data) in [dict,list] :
_data = json.dumps(_data)
# #
# # return the ata
_code = _code if not _data else 200
@ -600,23 +740,25 @@ class QCMS:
self._id = _app.get('system.context') #if _app.get('system.context') else 'main'
# if self._id == '' :
# self._id = '/'
self._sites = {self._id:_app, '/':_app}
self._sites = { self._id:_app, '':_app,'/':_app}
self._routes = []
if _app.get('system.routes') :
_routes = _app.get('system.routes')
for _name in _routes :
self._routes.append(_name)
_path = _routes[_name]['path']
self._sites[_name] = Site(context=_name,path=_path,caller=_app)
# self._sites[f'{_name}/'] = self._sites[_name]
# self._sites[f'/{_name}/'.replace('//','/')] = self._sites[_name]
self.inspect = RequestController(self._routes)
def _render(self,request):
_site = self.get(request)
_args = _site.render(request=request,id='index')
return render_template('index.html',**_args)
def _read(self,request):
@ -626,6 +768,7 @@ class QCMS:
isfile = self.inspect.isfile(request)
isapi = self.inspect.isapi(request)
isroute= self.inspect.isroute(request)
if not isapi and not isfile:
return self._render(request)
#
@ -637,7 +780,10 @@ class QCMS:
return _content,200,{'Content-Type':_mimeType}
if isapi :
_site = self.get(request)
return _site.run(request)
return _site.run(request)
def get(self,request=None) :
if request :

@ -0,0 +1,22 @@
if(!qcms){
var qcms = {}
}
if(!qcms.login){
qcms.login = {}
}
qcms.login.oauth = function (_config){
//
// configuration for the oauth
if (_config){
var url = [_config.authorization_url+'?']
_attr = ['client_id','client_secret','scope','redirect_uri']
_attr.forEach((_key)=>{
url.push( _config[_key].trim() )
})
//
// We should have the URL that will implement the Oauth 2.0 flow
this.url = url
}
}

@ -0,0 +1,12 @@
if(!qcms){
var qcms = {}
}
if(!qcms.login){
qcms.login = {}
}
qcms.login.pam = function (_args){
this.url = (_args.uri)?_args.uri : 'login/pam-login.html'
this.url = ([qcms.context,this.url]).join('/')
}

@ -162,7 +162,7 @@ qcms.menu.Tabs = function (_layout,_outputId,_domId){
_button.name = 'menu-tabs'
_label.data = {id:text.toLowerCase(),uri:_item[0].uri}
_label.data.text = _label.data.id // aug-2026 bug fix
if (this._layout.icon){
var _icon = jx.dom.get.instance('I')
_icon.className = this._layout.icons[text]
@ -175,7 +175,7 @@ qcms.menu.Tabs = function (_layout,_outputId,_domId){
$(_button).val(text.toLowerCase())
_label.htmlFor = _button.id
// var _open = this._open
// var _parentId = this._parentId
var _parentId = this._parentId // aug-2026 bug fix
var _me = this ;
$(_label).on('click',function (){
@ -189,9 +189,11 @@ qcms.menu.Tabs = function (_layout,_outputId,_domId){
var _names = _layout.order.menu.length > 0 ? _layout.order.menu : Object.keys(_layout.menu)
// Object.keys(_layout.menu).
var _me = this ;
var tabs = this.tabs ;
_names.forEach(function(_text){
_item = _layout.menu[_text]
_tabItem = this._build(_text,_item)
_tabItem = _me._build(_text,_item)
$(tabs).append(_tabItem)
})
@ -224,9 +226,9 @@ qcms.menu.init = function(_layout){
})
if (_count == _items){
var _menuObject = new qcms.menu.Tabs (_layout,'.main #content','.main .menu')
var _menuObject = new qcms.menu.Tabs (_layout,'.qcms-main .qcms-content','.qcms-main .qcms-menu')
}else{
var _menuObject = new qcms.menu.Basic (_layout,'.main #content','.main .menu')
var _menuObject = new qcms.menu.Basic (_layout,'.qcms-main .qcms-content','.qcms-main .qcms-menu')
}

@ -36,7 +36,7 @@ qcms.page.Observer = function (_parentId,_domId,_uri){
http.post(_uri,function(x){
var _dom = $(x.responseText)
_dom = $('<div></div>').append(_dom)
var _found = qcms.html.hasNode( $(_id), $(_dom))
if (_found == 0){
@ -82,7 +82,15 @@ qcms.page.loader = function(_parentId,_layout){
})
}
}
qcms.page.load = function (_uri,_id){
var http = HttpClient.instance()
http.get(_uri,(_x)=>{
// var _id = _id[0] == '#' || _id[0] == '.'?_id : (jx.dom.exists(_id)?`#${_id}`:`.${_id}`)
// alert(_id)
$(_id).html(_x.responseText)
})
}
//
// making backwards compatibility
if(!bootup){

@ -0,0 +1,107 @@
/**
* This file contains utilities for performing various tasks
*/
if (!qcms){
var qcms = {}
}
qcms.utils = {}
qcms.utils.csv = function (_dhandler){ //(uri,_render,_error) {
var http = HttpClient.instance()
var format = (_value)=>{return _value.trim()}
var _error = (_error == null)? console.log: _error
http.get(_dhandler._uri,(_x)=>{
if (_x.status == 200){
var _data = []
_x.responseText.split('\n').forEach((_row,_index)=>{
values = jx.utils.patterns.visitor(_row.split(','),format)
if(_index == 0){
_attr = values
}else{
//
// Let's build the object here
var _object = {}
_attr.forEach((_field,_index)=>{
_object[_field] = values[_index]
})
_data.push(_object)
}
})
//--
// At this point we have formatted csv to an array of JSON objects
if (_data.length && _dhandler.render != null){
_dhandler.render (_data)
}else{
//
// @TODO: We need an error handler otherwise we will have to handle it ourselves
_error (_data)
}
}
})
}
qcms.code = {}
qcms.code.documentation= function(_uri,_id) {
this._uri = _uri
this._id = _id[0] == '#'?_id :( _id[0] == '.' ?_id: '#'+_id)
this.events = {_open:{},_find:{}}
this.events._open = function(_uri){
qcms.page.load(_uri,`${_id} .content`)
}
this.render = function (_data){
//
//NOTE: Because this function will be a callback, it will lose reference to the class members but will be able to access them without 'this'
//
var _open = this.events._open
var _menu = $('<div class="toc"><div class="border-round"><input type="text" class="doc-find" placeholder="[keyword, term]"/></div></div>')
var _frame = $('<div class="items"></div>')
_data.forEach((_row,_index) =>{
// _div = $('<div class="active label bold"></div>').html(_row.alias).append($('<div class="small"></div>').html(_row.description) )
// _div = $(`<div class="active bold label">${_row.alias}</div><div class="small">${_row.description}</div>`)
_div = $(`<div class="active bold label" align="left">${_row.alias}</div><div class="small" style="padding-left:8px;">${_row.description}</div>`)
$(_div).attr('file',_row.file)
_div[0].onclick = function (){
//
// we need to load the area with the given page
console.log($(this).attr('file'))
var _uri = $(this).attr('file')
// qcms.page.load(_uri,'')
console.log([_uri ,_id, ' ########'])
_open(_uri,_id)
}
$(_frame).append(_div)
})
//
// adding the menu to the location specified
$(_id).empty()
$(_menu).append(_frame)
$(_id).append(_menu)
$(_id).append($('<div class="content"></div>'))
$(_id).attr('class','doc-browser')
$(_id).show()
//
// click the first item on the list
$(_frame).children()[0].click()
}
this.reload = function (){
// var _id = this._id
// var events = this.events
// var _openEvent =this._openEvent
// var _find = this.events._find ;
$(this._id).empty()
qcms.utils.csv(this) //(this._uri,this.render)
$(this._id).find('*.child').show() //css({display:'block'})
}
}

@ -1,9 +1,10 @@
<div class="icon">
<div class="qcms-icon">
<img src="{{system.icon}}">
</div>
<div>
<div class="title">{{layout.header.title}}</div>
<div class="subtitle">{{layout.header.subtitle}}</div>
<div class="qcms-title">{{layout.header.title}}</div>
<div class="qcms-subtitle">{{layout.header.subtitle}}</div>
</div>

@ -46,24 +46,24 @@ Vanderbilt University Medical Center
</script>
<body>
<div class="main {{system.theme}}" >
<div id="header" class="header" onclick="window.location.href='{{system.context}}'" style="cursor:pointer">
<div class="qcms-main {{system.theme}}" >
<div id="qcms-header" class="qcms-header" onclick="window.location.href='{{system.context}}'" style="cursor:pointer">
{%include "header.html" %}
</div>
<div id="menu" class="menu">{%include "menu.html" %}</div>
<div id="qcms-menu" class="qcms-menu">{%include "menu.html" %}</div>
<div id="content" class="content">
<div id="qcms-content" class="qcms-content">
{%include "content.html" %}
</div>
<div id="pane" class="pane">
<div id="qcms-pane" class="qcms-pane">
{%include "pane.html" %}
</div>
<div id="footer" class="footer">
<div id="footer" class="qcms-footer">
{%include "footer.html" %}
</div>
</div>

@ -1,4 +1,6 @@
<link href="{{system.parentContext}}/static/css/icons.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/icons.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/source-code.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/search.css" rel="stylesheet" type="text/css">
<link href="{{system.parentContext}}/static/css/dialog.css" rel="stylesheet" type="text/css">
@ -19,11 +21,12 @@
<meta property="og:title" content="{{layout.header.title}}" />
<meta property="og:type" content="website" />
<!-- <meta property="og:url" content="https://www.yourwebsite.com/page" /> -->
<meta property="og:image" content="{{system.logo}}" />
<meta property="og:image" content="{{system.icon}}" />
<meta property="og:image:alt" content="{{layout.header.title}}" />
<meta property="og:description" content="{{layout.header.title}}; {{layout.header.subtitle}}, version {{system.version}}" />
<meta property="og:site_name" content="{{layout.header.title}}" />
<script src="{{system.parentContext}}/static/js/qcms/qcms.js"></script>
<script src="{{system.parentContext}}/static/js/qcms/utils.js"></script>
<script src="{{system.parentContext}}/static/js/qcms/menu.js"></script>
<script src="{{system.parentContext}}/static/js/qcms/page-loader.js"></script>
<script src="{{system.parentContext}}/static/js/qcms/dialog.js"></script>

@ -3,11 +3,11 @@
{% if system.parentContext == ""%}
{% set _backURI = "/" %}
{% else %}
{% set _backURI = system.parentContext%}
{% set _backURI = system.caller.context %}
{% endif %}
<div class="icon" style="margin:0px" >
<div align="left" class="back-button active" onclick="window.open('{{_backURI}}','_self')" >
<div class="qcms-icon" style="margin:0px" >
<div align="left" class="qcms-back-button active" onclick="window.open('{{_backURI}}','_self')" >
<div style="display:grid; align-items:center; grid-template-columns: auto auto;">
<i class="fa-solid fa-chevron-left" style="color:darkgray;"></i>
<img src="{{system.caller.icon}}"/>
@ -15,7 +15,7 @@
</div>
</div>
{% else %}
<div class="icon">
<div class="qcms-icon">
<i class="fa-solid fa-home"></i>
</div>
{% endif %}

@ -25,22 +25,22 @@
<body>
<div class="main" style="">
<div class="qcms-main" style="">
<div class="header" onclick="window.location.href='{{system.context}}/'" style="cursor:pointer">
<div class="qcms-header" onclick="window.location.href='{{system.context}}/'" style="cursor:pointer">
{%include "header.html" %}
</div>
<div class="menu">
<div class="qcms-menu">
{% include "menu.html" %}
</div>
<div class="content">
<div class="qcms-content">
<div id="me">{{html|safe}}</div>
{%include "content.html" %}
</div>
<div class="footer">
<div class="qcms-footer">
{%include "footer.html" %}
</div>
</div>

Loading…
Cancel
Save