diff --git a/bin/qcms b/bin/qcms index 5371120..3f5e6e1 100755 --- a/bin/qcms +++ b/bin/qcms @@ -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 diff --git a/cms/disk.py b/cms/disk.py index 04228b5..7ac7235 100644 --- a/cms/disk.py +++ b/cms/disk.py @@ -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 - diff --git a/cms/engine/plugins/login.py b/cms/engine/plugins/login.py new file mode 100644 index 0000000..b65f26e --- /dev/null +++ b/cms/engine/plugins/login.py @@ -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 +# \ No newline at end of file diff --git a/cms/index.py b/cms/index.py index 56f5e63..8890711 100644 --- a/cms/index.py +++ b/cms/index.py @@ -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('//dialog', methods=['POST','GET'], strict_slashes = False) - +@_app.route('//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('/',defaults={}, methods=['POST','GET','PUT'], strict_slashes = False) + # @_app.route('//files/', defaults=[{'module':None}]) # @_app.route('///files/') 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('//page',methods=['POST'],defaults={'key':None}) -@_app.route('///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('//page',methods=['POST'],defaults={'key':None}) +# @_app.route('///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('//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') diff --git a/cms/meta/__init__.py b/cms/meta/__init__.py index 59deace..cc45a5f 100644 --- a/cms/meta/__init__.py +++ b/cms/meta/__init__.py @@ -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 diff --git a/cms/secure.py b/cms/secure.py index 33aa12c..e5b4c66 100644 --- a/cms/secure.py +++ b/cms/secure.py @@ -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,14 +64,14 @@ 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']) self._permissions = reader.read() - + # # @TODO: add the login page provided int the configuration with full permissions # or diff --git a/cms/sites/__init__.py b/cms/sites/__init__.py index 6cf4305..6497e58 100644 --- a/cms/sites/__init__.py +++ b/cms/sites/__init__.py @@ -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 '/'.join(request.path[1:].split('/')[:2]) in self._routes + 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): - - _names.append('/'.join(_items[:i])) - - _names=[_item for _item in _names if _item in self._routes] - return _names[-1] if _names else None + # _items = request.path[1:].split('/') + + # 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) + + return self._map.get(_uri,None) class IOConfig: """ @@ -50,7 +81,7 @@ class IOConfig: self._config = {'system':{},'layout':{},'plugins':{}} self._caller = None self._location= _args['location'] if 'location' in _args else None - + self._logs = [] def get(self,_key) : # @@ -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 : @@ -123,7 +162,7 @@ class Initialization (IOConfig): self._config['layout']['location'] = None if not self.get('layout.menu') : self._config['layout']['menu'] = {} - + self.context(**_args) self.menu() @@ -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)) + # path.append(uri) + # return os.path.exists( os.sep.join(path)) + + return os.path.exists(uri) - # 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'
',_html,"
"]) - # 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'
{_content}
' if _uri.split('.')[-1].strip().lower() == 'md': _content = mistune.html(_content).replace(""",'"').replace("<","<").replace(">",">") if _uri[-2:] in ['md','MD','Md','mD'] else _content - _content = f"
{_content}
" #if 'dom' not in _request.headers else f'
{_content}
' + # _content = f"
{_content}
" #if 'dom' not in _request.headers else f'
{_content}
' else: - _env = Environment(loader=BaseLoader()).from_string(_content) + _env = Environment(loader=BaseLoader()).from_string(f"
{_content}
") _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 = """
404
File Not Found
""" 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'
{_html}
') return _env.render(**_kwargs) def run(self,_request) : @@ -564,12 +700,15 @@ class Site(Initialization) : _data = f"
404
{_request.path} Not Found
" _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 : diff --git a/cms/static/js/qcms/login/oauth2.0.js b/cms/static/js/qcms/login/oauth2.0.js new file mode 100644 index 0000000..ec6b414 --- /dev/null +++ b/cms/static/js/qcms/login/oauth2.0.js @@ -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 + } +} \ No newline at end of file diff --git a/cms/static/js/qcms/login/pam.js b/cms/static/js/qcms/login/pam.js new file mode 100644 index 0000000..2e5c6bd --- /dev/null +++ b/cms/static/js/qcms/login/pam.js @@ -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('/') +} \ No newline at end of file diff --git a/cms/static/js/qcms/menu.js b/cms/static/js/qcms/menu.js index 8e19192..bc6845a 100644 --- a/cms/static/js/qcms/menu.js +++ b/cms/static/js/qcms/menu.js @@ -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') } diff --git a/cms/static/js/qcms/page-loader.js b/cms/static/js/qcms/page-loader.js index cf0a872..36dbdc0 100644 --- a/cms/static/js/qcms/page-loader.js +++ b/cms/static/js/qcms/page-loader.js @@ -36,7 +36,7 @@ qcms.page.Observer = function (_parentId,_domId,_uri){ http.post(_uri,function(x){ var _dom = $(x.responseText) - + _dom = $('
').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){ diff --git a/cms/static/js/qcms/utils.js b/cms/static/js/qcms/utils.js new file mode 100644 index 0000000..41a5265 --- /dev/null +++ b/cms/static/js/qcms/utils.js @@ -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 = $('
') + var _frame = $('
') + _data.forEach((_row,_index) =>{ + // _div = $('
').html(_row.alias).append($('
').html(_row.description) ) + // _div = $(`
${_row.alias}
${_row.description}
`) + _div = $(`
${_row.alias}
${_row.description}
`) + $(_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($('
')) + $(_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'}) + } +} + + diff --git a/cms/templates/header.html b/cms/templates/header.html index 4b86142..632f483 100644 --- a/cms/templates/header.html +++ b/cms/templates/header.html @@ -1,9 +1,10 @@ -
+
-
{{layout.header.title}}
-
{{layout.header.subtitle}}
+ +
{{layout.header.title}}
+
{{layout.header.subtitle}}
diff --git a/cms/templates/index.html b/cms/templates/index.html index 5d2ab29..22bcb98 100644 --- a/cms/templates/index.html +++ b/cms/templates/index.html @@ -46,24 +46,24 @@ Vanderbilt University Medical Center -
-