@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					recursive-include cms/templates *
 | 
				
			||||||
 | 
					recursive-include cms/static *
 | 
				
			||||||
@ -0,0 +1,354 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import importlib
 | 
				
			||||||
 | 
					import importlib.util
 | 
				
			||||||
 | 
					from git.repo.base import Repo
 | 
				
			||||||
 | 
					import typer
 | 
				
			||||||
 | 
					from typing_extensions import Annotated
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					import meta
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					from termcolor import colored
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					from cms import index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					start = index.start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__doc__ = """
 | 
				
			||||||
 | 
					(c) 2022 Quick Content Management System - QCMS
 | 
				
			||||||
 | 
					Health Information Privacy Lab - Vanderbilt University Medical Center
 | 
				
			||||||
 | 
					MIT License
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Is a Python/Flask template for creating websites using disk structure to build the site and loading custom code
 | 
				
			||||||
 | 
					The basic template for a flask application will be created in a specified location, within this location will be found a folder "content"
 | 
				
			||||||
 | 
					    - within the folder specify the folder structure that constitute the menu
 | 
				
			||||||
 | 
					Usage :
 | 
				
			||||||
 | 
					    qcms --create <path> --version <value> --title <title> --subtitle <subtitle>
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PASSED = ' '.join(['[',colored(u'\u2713', 'green'),']'])
 | 
				
			||||||
 | 
					FAILED= ' '.join(['[',colored(u'\u2717','red'),']'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					INVALID_FOLDER = """
 | 
				
			||||||
 | 
					{FAILED} Unable to proceed, could not find project manifest. It should be qcms-manifest.json
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# handling cli interface
 | 
				
			||||||
 | 
					cli = typer.Typer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_config (path) :
 | 
				
			||||||
 | 
					    if os.path.exists(path) :
 | 
				
			||||||
 | 
					        f = open(path) 
 | 
				
			||||||
 | 
					        _conf = json.loads(f.read())
 | 
				
			||||||
 | 
					        f.close()
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _conf = {}
 | 
				
			||||||
 | 
					    return _conf
 | 
				
			||||||
 | 
					def _isvalid(_allowed,**_args):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if not list(set(_allowed) - set(_args.keys())) :
 | 
				
			||||||
 | 
					        _pargs = {}
 | 
				
			||||||
 | 
					        for key in _allowed :
 | 
				
			||||||
 | 
					            _pargs [key] = _args[key]
 | 
				
			||||||
 | 
					        return _pargs
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					 
 | 
				
			||||||
 | 
					def write_config(_config, path):
 | 
				
			||||||
 | 
					        f = open(path,'w')
 | 
				
			||||||
 | 
					        f.write( json.dumps(_config)) ;
 | 
				
			||||||
 | 
					        f.close()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					def _system(**_args):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					        Both version and context must be provided otherwise they are ignored
 | 
				
			||||||
 | 
					        :version
 | 
				
			||||||
 | 
					        :context    context
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _info =  _isvalid(['version','context'],**_args) 
 | 
				
			||||||
 | 
					    _info['theme'] = 'default.css'
 | 
				
			||||||
 | 
					    _info = _info if _info else {'version':'0.0.0','context':'','theme':'default.css'}
 | 
				
			||||||
 | 
					    if _info :
 | 
				
			||||||
 | 
					        _info['logo'] = None if 'logo' not in _args else _args['logo']
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # There is an aggregation entry here in app
 | 
				
			||||||
 | 
					    _appInfo = {'debug':True,'port':8084,'threaded':True,'host':'0.0.0.0'}
 | 
				
			||||||
 | 
					    if 'app' not in _args:
 | 
				
			||||||
 | 
					        _info['app']  = _appInfo
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _info['app'] = dict(_appInfo,**_args['app'])
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    return _info
 | 
				
			||||||
 | 
					def _header(**_args):
 | 
				
			||||||
 | 
					    return  _isvalid(['logo','title','subtitle'],**_args)
 | 
				
			||||||
 | 
					def _layout(**_args):
 | 
				
			||||||
 | 
					    _info = _isvalid(['root','index'],**_args)
 | 
				
			||||||
 | 
					    _info['on'] = {"load":{},"error":{}}
 | 
				
			||||||
 | 
					    _url = 'qcms.co'
 | 
				
			||||||
 | 
					    _overwrite = {"folder1":{"type":"redirect","url":_url},"folder2":{"type":"dialog"},"folder3":{"type":"dialog","url":_url}}
 | 
				
			||||||
 | 
					    _info['icons'] = {"comment":"use folder names as keys and fontawesome type as values to add icons to menu"}
 | 
				
			||||||
 | 
					    _info["api"] = {"comment":"use keys as uri and function calls as values"}
 | 
				
			||||||
 | 
					    _info['map'] = {},
 | 
				
			||||||
 | 
					    _info['order'] = {'menu':[]}
 | 
				
			||||||
 | 
					    _info['overwrite'] = _overwrite
 | 
				
			||||||
 | 
					    return _info
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					def make (**_args):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    :context
 | 
				
			||||||
 | 
					    :port   port to be served
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    :title  title of the application
 | 
				
			||||||
 | 
					    :root   folder of the content to be scanned
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if 'port' in _args :
 | 
				
			||||||
 | 
					        _args['app'] = {'port':_args['port']}
 | 
				
			||||||
 | 
					        del _args['port']
 | 
				
			||||||
 | 
					    if 'context' not in _args :
 | 
				
			||||||
 | 
					        _args['context'] = ''
 | 
				
			||||||
 | 
					    _info ={'system': _system(**_args)}
 | 
				
			||||||
 | 
					    _hargs = {'title':'QCMS'} if 'title' not in _args else {'title':_args['title']}
 | 
				
			||||||
 | 
					    _hargs['subtitle'] = '' if 'subtitle' not in _args else _args['subtitle']
 | 
				
			||||||
 | 
					    _hargs['logo'] = True
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    _info['layout'] = _layout(root=_args['root'],index='index.html')
 | 
				
			||||||
 | 
					    _info['layout']['header'] = _header(**_hargs)
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if 'footer' in _args :
 | 
				
			||||||
 | 
					        _info['layout']['footer'] = [{'text':_args['footer']}] if type(_args['footer']) != list else [{'text':term} for term in _args['footeer']]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _info['layout']['footer'] = [{'text':'Vanderbilt University Medical Center'}]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return _info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@cli.command(name="info")
 | 
				
			||||||
 | 
					def _info():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function returns metadata information about this program, no parameters are needed
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    print ()
 | 
				
			||||||
 | 
					    print (meta.__name__,meta.__version__)
 | 
				
			||||||
 | 
					    print (meta.__author__,meta.__email__)
 | 
				
			||||||
 | 
					    print ()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@cli.command(name='set-app')
 | 
				
			||||||
 | 
					# def set_app (host:str="0.0.0.0",context:str="",port:int=8084,debug=True):
 | 
				
			||||||
 | 
					def set_app (host:Annotated[str,typer.Argument(help="bind host IP address")]="0.0.0.0",
 | 
				
			||||||
 | 
					             context:Annotated[str,typer.Argument(help="if behind a proxy server (no forward slash needed)")]="",
 | 
				
			||||||
 | 
					             port:Annotated[int,typer.Argument(help="port on which to run the application")]=8084,
 | 
				
			||||||
 | 
					             debug:Annotated[bool,typer.Argument(help="set debug mode on|off")]=True):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function consists in editing application access i.e port, debug, and/or context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :context    alias or path for applications living behind proxy server
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global INVALID_FOLDER
 | 
				
			||||||
 | 
					    _config = _get_config()
 | 
				
			||||||
 | 
					    if _config :
 | 
				
			||||||
 | 
					        _app = _config['system']['app']
 | 
				
			||||||
 | 
					        _app['host'] = host
 | 
				
			||||||
 | 
					        _app['port'] = port
 | 
				
			||||||
 | 
					        _app['debug'] = debug
 | 
				
			||||||
 | 
					        _config['system']['context'] = context
 | 
				
			||||||
 | 
					        _config['app'] = _app
 | 
				
			||||||
 | 
					        write_config(_config)
 | 
				
			||||||
 | 
					        _msg = f"""{PASSED} Successful update, good job !
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _msg = INVALID_FOLDER
 | 
				
			||||||
 | 
					    print (_msg)
 | 
				
			||||||
 | 
					@cli.command(name='set-cloud')
 | 
				
			||||||
 | 
					# def set_cloud(path:str): #url:str,uid:str,token:str):
 | 
				
			||||||
 | 
					def set_cloud(path:Annotated[str,typer.Argument(help="path of the auth-file for the cloud")]): #url:str,uid:str,token:str):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function overwrites or sets token information for nextcloud. This function will load the content from the cloud instead of locally
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :url    url of the nextcloud
 | 
				
			||||||
 | 
					    :uid    account identifier
 | 
				
			||||||
 | 
					    :token  token to be used to signin
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if os.path.exists(path):
 | 
				
			||||||
 | 
					        f = open (path)
 | 
				
			||||||
 | 
					        _auth = json.loads(f.read())
 | 
				
			||||||
 | 
					        if not set(['url','token','uid'])  -  set(_auth.keys()) :
 | 
				
			||||||
 | 
					            url = _auth['url']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if os.path.exists('qcms-manifest.json') :
 | 
				
			||||||
 | 
					                _config = _get_config()
 | 
				
			||||||
 | 
					                _config['system']['source'] = path #{'id':'cloud','auth':{'url':url,'uid':uid,'token':token}}
 | 
				
			||||||
 | 
					                write_config(_config)
 | 
				
			||||||
 | 
					                title = _config['layout']['header']['title']
 | 
				
			||||||
 | 
					                _msg = f"""
 | 
				
			||||||
 | 
					                Successfully update, good job!
 | 
				
			||||||
 | 
					                {url} 
 | 
				
			||||||
 | 
					                """
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                _msg = INVALID_FOLDER
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            _msg = """NOT A VALID NEXTCLOUD FILE"""
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _msg = INVALID_FOLDER
 | 
				
			||||||
 | 
					    print (_msg)
 | 
				
			||||||
 | 
					@cli.command(name='set-key')
 | 
				
			||||||
 | 
					# def set_key(path):
 | 
				
			||||||
 | 
					def set_key(
 | 
				
			||||||
 | 
					    manifest:Annotated[str,typer.Argument(help="path of the manifest file")],
 | 
				
			||||||
 | 
					    keyfile:Annotated[str,typer.Argument(help="path of the key file to generate")]):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    create a security key to control the application during developement. The location must have been created prior.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not os.path.exists(keyfile):
 | 
				
			||||||
 | 
					        f = open(keyfile,'w')
 | 
				
			||||||
 | 
					        f.write(str(uuid.uuid4()))
 | 
				
			||||||
 | 
					        f.close()
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        _config = _get_config(manifest)
 | 
				
			||||||
 | 
					        if 'source' not in _config['system']:
 | 
				
			||||||
 | 
					            _config['system']['source'] = {'id':'disk'}
 | 
				
			||||||
 | 
					        _config['system']['source']['key'] = keyfile
 | 
				
			||||||
 | 
					        write_config(_config,manifest)
 | 
				
			||||||
 | 
					        _msg = f"""{PASSED} A key was generated and written to {keyfile}
 | 
				
			||||||
 | 
					            use this key in header to enable reload of the site ...`
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _msg = f"""{FAILED} Could NOT generate a key, because it would seem you already have one
 | 
				
			||||||
 | 
					            Please manually delete {keyfile}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    print (_msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load(**_args):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function will load external module form a given location and return a pointer to a function in a given module
 | 
				
			||||||
 | 
					    :path   absolute path of the file (considered plugin) to be loaded
 | 
				
			||||||
 | 
					    :name   name of the function to be applied
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    _path = _args['path'] #os.sep.join([_args['root'],'plugin'])
 | 
				
			||||||
 | 
					    if os.path.isdir(_path):
 | 
				
			||||||
 | 
					        files = os.listdir(_path)
 | 
				
			||||||
 | 
					        if files :
 | 
				
			||||||
 | 
					            files = [name for name in files if name.endswith('.py')]
 | 
				
			||||||
 | 
					            if files:
 | 
				
			||||||
 | 
					                _path = os.sep.join([_path,files[0]])
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            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)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return getattr(module,_name) if hasattr(module,_name) else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@cli.command (name='create')
 | 
				
			||||||
 | 
					# def create(folder:str,root:str,index:str='index.html',title:str='qcms',subtitle:str='',footer:str='Quick Content Management System',version:str='0.1'):
 | 
				
			||||||
 | 
					def create(folder:Annotated[str,typer.Argument(help="path of the project folder")],
 | 
				
			||||||
 | 
					           root:Annotated[str,typer.Argument(help="folder of the content (inside the project folder)")],
 | 
				
			||||||
 | 
					           index:str=typer.Option(default="index.html",help="index file (markup or html)"), #Annotated[str,typer.Argument(help="index file to load (.html or markup)")]='index.html',
 | 
				
			||||||
 | 
					           title:str=typer.Option(default="QCMS PROJECT", help="name of the project") , #Annotated[str,typer.Argument(help="title of the project")]='QCMS - TITLE',
 | 
				
			||||||
 | 
					           subtitle:str=typer.Option(default="designed & built by The Phi Technology",help="tag line about the project (sub-title)"), #Annotated[str,typer.Argument(help="subtitle of the project")]='qcms - subtitle',
 | 
				
			||||||
 | 
					           footer:str=typer.Option(default="Quick Content Management System",help="text to be placed as footer"), #Annotated[str,typer.Argument(help="text on the footer of the main page")]='Quick Content Management System',
 | 
				
			||||||
 | 
					           version:str=typer.Option(default="0.2",help="version number") #Annotated[str,typer.Argument(help="version of the site")]='0.1'
 | 
				
			||||||
 | 
					           ):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function will create a project folder by performing a git clone (pull the template project)
 | 
				
			||||||
 | 
					    and adding a configuration file to that will bootup the project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    folder  project folder
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    root    name/location of root folder associated with the web application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @TODO: 
 | 
				
			||||||
 | 
					        - add port, 
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    #global SYS_ARGS
 | 
				
			||||||
 | 
					    #folder  = SYS_ARGS['create']
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					    url = "https://dev.the-phi.com/git/cloud/cms.git" 
 | 
				
			||||||
 | 
					    #if 'url' in SYS_ARGS :
 | 
				
			||||||
 | 
					    #    url = SYS_ARGS['url']
 | 
				
			||||||
 | 
					    print ("\tcreating project into folder " +folder)
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # if standalone, then we should create the git repository
 | 
				
			||||||
 | 
					    # if standalone :
 | 
				
			||||||
 | 
					    #     _repo = Repo.clone_from(url,folder)
 | 
				
			||||||
 | 
					    #     #
 | 
				
			||||||
 | 
					    #     # removing remote folder associated with the project
 | 
				
			||||||
 | 
					    #     _repo.delete_remote(remote='origin')
 | 
				
			||||||
 | 
					    # else:
 | 
				
			||||||
 | 
					    #     os.makedirs(folder)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # @TODO: root should be handled in 
 | 
				
			||||||
 | 
					    rootpath = os.sep.join([folder,root]) 
 | 
				
			||||||
 | 
					    _img = None
 | 
				
			||||||
 | 
					    if not os.path.exists(rootpath) :
 | 
				
			||||||
 | 
					        print (f"{PASSED} Creating content folder "+rootpath)
 | 
				
			||||||
 | 
					        os.makedirs(rootpath)
 | 
				
			||||||
 | 
					        os.makedirs(os.sep.join([rootpath,'_images']))
 | 
				
			||||||
 | 
					        _img = """data:image/png;base64,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"""
 | 
				
			||||||
 | 
					        _,_img = _img.split(',',1)
 | 
				
			||||||
 | 
					        logo = os.sep.join([rootpath,'_images','logo.png'])
 | 
				
			||||||
 | 
					        f = open(logo,'wb')
 | 
				
			||||||
 | 
					        f.write(base64.b64decode(_img))
 | 
				
			||||||
 | 
					        f.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _html = __doc__.replace("<","<").replace(">",">").replace("Usage","<br>Usage")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if not os.path.exists(os.sep.join([rootpath,'index.html'])) :
 | 
				
			||||||
 | 
					        f = open(os.sep.join([rootpath,'index.html']),'w')
 | 
				
			||||||
 | 
					        f.write(_html)
 | 
				
			||||||
 | 
					        f.close()
 | 
				
			||||||
 | 
					    print (f'{PASSED} configuration being written to project folder')
 | 
				
			||||||
 | 
					    _args = {'title':title,'subtitle':subtitle,'version':version,'root':root,'index':index}
 | 
				
			||||||
 | 
					    if footer :
 | 
				
			||||||
 | 
					        _args['footer'] = footer
 | 
				
			||||||
 | 
					    _config = make(**_args)
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # updating logo reference (default for now)
 | 
				
			||||||
 | 
					    _config['system']['logo'] = os.sep.join([_config['layout']['root'],'_images/logo.png'])
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    f = open(os.sep.join([folder,'qcms-manifest.json']),'w')
 | 
				
			||||||
 | 
					    f.write(json.dumps(_config))
 | 
				
			||||||
 | 
					    f.close()
 | 
				
			||||||
 | 
					    return _config     
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@cli.command (name='reload')
 | 
				
			||||||
 | 
					def reload (path) :
 | 
				
			||||||
 | 
					    if os.path.exists (path):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					@cli.command(name="bootup")
 | 
				
			||||||
 | 
					def bootup (
 | 
				
			||||||
 | 
					    path: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)")
 | 
				
			||||||
 | 
					    ):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function will launch a site/project given the location of the manifest file
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    index.start(path,port)
 | 
				
			||||||
 | 
					    # if not port :
 | 
				
			||||||
 | 
					    #     index.start(path)
 | 
				
			||||||
 | 
					    # else:
 | 
				
			||||||
 | 
					    #     index.start(path,port)
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					def reset():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global SYS_ARGS
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    cli()
 | 
				
			||||||
@ -0,0 +1,433 @@
 | 
				
			|||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					from cms import disk, cloud
 | 
				
			||||||
 | 
					from jinja2 import Environment, BaseLoader, FileSystemLoader
 | 
				
			||||||
 | 
					import importlib
 | 
				
			||||||
 | 
					import importlib.util
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					There are four classes at play here:
 | 
				
			||||||
 | 
					[ Initializer ] <|-- [ Module ] <|-- [ MicroService ] <--<>[ CMS ]
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Initializer :
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This class handles initialization of all sorts associated with "cms engine" 
 | 
				
			||||||
 | 
					    :path
 | 
				
			||||||
 | 
					    :location
 | 
				
			||||||
 | 
					    :shared
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self,**_args):
 | 
				
			||||||
 | 
					        self._config = {'system':{},'layout':{},'plugins':{}}
 | 
				
			||||||
 | 
					        self._shared = False if not 'shared' in _args else _args['shared']
 | 
				
			||||||
 | 
					        self._location= _args['location'] if 'location' in _args else None
 | 
				
			||||||
 | 
					        self._menu = {}
 | 
				
			||||||
 | 
					        # _source = self._config ['system']['source'] if 'source' in self._config['system'] else {}
 | 
				
			||||||
 | 
					        # self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud'
 | 
				
			||||||
 | 
					        # print ([self._ISCLOUD,self._config['system'].keys()])
 | 
				
			||||||
 | 
					        self._ISCLOUD = False
 | 
				
			||||||
 | 
					        self._caller = None if 'caller'  not in _args else _args['caller']
 | 
				
			||||||
 | 
					        self._args = _args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.reload() #-- this is an initial load of various components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reload(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._iconfig(**self._args)
 | 
				
			||||||
 | 
					        self._uconfig(**self._args)
 | 
				
			||||||
 | 
					        self._isource()
 | 
				
			||||||
 | 
					        self._imenu()        
 | 
				
			||||||
 | 
					        self._iplugins()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _handler  (self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This function returns the appropriate handler to the calling code, The handler enables read/write from a location
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if self._ISCLOUD: #'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud' :
 | 
				
			||||||
 | 
					            return cloud
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return disk
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def _imenu(self,**_args) :
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    def _iplugins(self,**_args) :
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialize plugins from disk (always)
 | 
				
			||||||
 | 
					        :plugins
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        _config = self._config
 | 
				
			||||||
 | 
					        PATH= os.sep.join([_config['layout']['root'],'_plugins'])
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not os.path.exists(PATH) and self._location and os.path.exists(self._location) :
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            # overriding the location of plugins ...
 | 
				
			||||||
 | 
					            if os.path.isfile(self._location) :
 | 
				
			||||||
 | 
					                _location = os.sep.join(self._location.split(os.sep)[:-1])
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                _location = self._location 
 | 
				
			||||||
 | 
					            PATH = os.sep.join([_location, _config['layout']['root'],'_plugins'])
 | 
				
			||||||
 | 
					        _context = _config['system']['context']
 | 
				
			||||||
 | 
					        _map = {}
 | 
				
			||||||
 | 
					        # if not os.path.exists(PATH) :
 | 
				
			||||||
 | 
					        #     return _map
 | 
				
			||||||
 | 
					        if 'plugins' not in _config :
 | 
				
			||||||
 | 
					            _config['plugins'] = {}
 | 
				
			||||||
 | 
					        _conf = _config['plugins'] 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for _key in _conf :
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _path = os.sep.join([PATH,_key+".py"])
 | 
				
			||||||
 | 
					            if not os.path.exists(_path):
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            for _name in _conf[_key] :
 | 
				
			||||||
 | 
					                _pointer = disk.plugins(path=_path,name=_name,context=_context)
 | 
				
			||||||
 | 
					                if _pointer :
 | 
				
			||||||
 | 
					                    _uri = "/".join(["api",_key,_name]) 
 | 
				
			||||||
 | 
					                    if _context :
 | 
				
			||||||
 | 
					                        _uri = f'{_context}/{_uri}'
 | 
				
			||||||
 | 
					                    _map[_uri] = _pointer
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # We are adding some source specific plugins to the user-defined plugins
 | 
				
			||||||
 | 
					        # This is intended to have out-of the box plugins...
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if self._ISCLOUD :
 | 
				
			||||||
 | 
					            _plugins = cloud.plugins(_context)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            _plugins = disk.plugins(context=_context)
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # If there are any plugins found, we should load them and use them
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if _plugins :
 | 
				
			||||||
 | 
					            _map = dict(_map,**_plugins)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self._plugins =  _map
 | 
				
			||||||
 | 
					        self._config['plugins'] = self._plugins
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _isource (self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initializing the source of the data, so we can read/write load from anywhere
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if 'source' not in self._config['system'] :
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source']['id'] == 'cloud'
 | 
				
			||||||
 | 
					        _source = self._config['system']['source']
 | 
				
			||||||
 | 
					        if 'key' in _source :
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            _path = _source['key']
 | 
				
			||||||
 | 
					            if os.path.exists(_path) :
 | 
				
			||||||
 | 
					                f = open(_path)
 | 
				
			||||||
 | 
					                _source['key'] = f.read()
 | 
				
			||||||
 | 
					                f.close()
 | 
				
			||||||
 | 
					                self._config['system']['source'] = _source
 | 
				
			||||||
 | 
					    def _ilayout(self,**_args):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialization of the layout section (should only happen if ) being called via framework
 | 
				
			||||||
 | 
					        :path   path to the dependent apps
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        _layout = self._config['layout']
 | 
				
			||||||
 | 
					        _path = os.sep.join(_args['path'].split(os.sep)[:-1])
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # find the new root and the one associated with the dependent app
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    def _imenu(self,**_args):
 | 
				
			||||||
 | 
					        _gshandler = self._handler()
 | 
				
			||||||
 | 
					        _object = _gshandler.build(self._config) #-- this will build the menu
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # post-processing menu, overwrite items and behaviors
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        _layout = copy.deepcopy(self._config['layout'])
 | 
				
			||||||
 | 
					        _overwrite = _layout['overwrite'] if 'overwrite' in _layout else {}
 | 
				
			||||||
 | 
					        _context = self.system()['context']
 | 
				
			||||||
 | 
					        for _name in _object :
 | 
				
			||||||
 | 
					            _submenu = _object[_name]
 | 
				
			||||||
 | 
					            _index = 0
 | 
				
			||||||
 | 
					            for _item in _submenu :
 | 
				
			||||||
 | 
					                text = _item['text'].strip()
 | 
				
			||||||
 | 
					                if text in _overwrite :
 | 
				
			||||||
 | 
					                    if 'uri' in _item and  'url' in _overwrite[text] :
 | 
				
			||||||
 | 
					                        del _item['uri']
 | 
				
			||||||
 | 
					                    _item = dict(_item,**_overwrite[text])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if 'uri' in _item and 'type' in _item and _item['type'] != 'open':
 | 
				
			||||||
 | 
					                    _item['uri'] = _item['uri'] #.replace(_layout['root'],'')
 | 
				
			||||||
 | 
					                    # _item['uri'] = _gshandler._format(_item['uri'],self._config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _submenu[_index] = _item
 | 
				
			||||||
 | 
					                _index += 1
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # updating menu _items as it relates to apps, configuration and the order in which they appear
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        _layout['menu'] = _object
 | 
				
			||||||
 | 
					        self._menu      = _object 
 | 
				
			||||||
 | 
					        self._config['layout'] = _layout
 | 
				
			||||||
 | 
					        self._iapps()
 | 
				
			||||||
 | 
					        self._iorder()
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _iorder (self):
 | 
				
			||||||
 | 
					        _config = self._config
 | 
				
			||||||
 | 
					        if 'order' in _config['layout'] and 'menu' in _config['layout']['order']:
 | 
				
			||||||
 | 
					            _sortedmenu = {}
 | 
				
			||||||
 | 
					            _menu = self._menu
 | 
				
			||||||
 | 
					            for _name in _config['layout']['order']['menu'] :
 | 
				
			||||||
 | 
					                if _name in _menu :
 | 
				
			||||||
 | 
					                    _sortedmenu[_name] = _menu[_name]
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _menu = _sortedmenu if _sortedmenu else _menu
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            # If there are missing items in the sorting
 | 
				
			||||||
 | 
					            _missing = list(set(self._menu.keys()) - set(_sortedmenu))
 | 
				
			||||||
 | 
					            if _missing :
 | 
				
			||||||
 | 
					                for _name in _missing :
 | 
				
			||||||
 | 
					                    _menu[_name] = self._menu[_name]
 | 
				
			||||||
 | 
					        _config['layout']['menu'] = _menu #cms.components.menu(_config)  
 | 
				
			||||||
 | 
					        self._menu = _menu    
 | 
				
			||||||
 | 
					        self._config = _config  
 | 
				
			||||||
 | 
					    def _iapps (self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initializing dependent applications into a menu area if need be
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        _layout = self._config['layout']
 | 
				
			||||||
 | 
					        _menu   = _layout['menu'] if 'menu' in _layout else {}
 | 
				
			||||||
 | 
					        _system = self._config['system']
 | 
				
			||||||
 | 
					        _context= _system['context']
 | 
				
			||||||
 | 
					        if 'routes' in _system :
 | 
				
			||||||
 | 
					            # _items = []
 | 
				
			||||||
 | 
					            _overwrite = {} if 'overwrite' not in self._config['layout'] else self._config['layout']['overwrite']
 | 
				
			||||||
 | 
					            for _text in _system['routes'] :
 | 
				
			||||||
 | 
					                _item = _system['routes'][_text]
 | 
				
			||||||
 | 
					                if 'menu' not in _item :
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                uri = f'{_context}/{_text}'
 | 
				
			||||||
 | 
					                # _items.append ({"text":_text,'uri':uri,'type':'open'})
 | 
				
			||||||
 | 
					                _label = _item['menu']
 | 
				
			||||||
 | 
					                if _label not in _menu :
 | 
				
			||||||
 | 
					                    _menu [_label] = []
 | 
				
			||||||
 | 
					                _menu[_label].append ({"text":_text,'uri':uri,'type':'open'})
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # update the menu items and the configuration 
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        _layout['menu'] = _menu
 | 
				
			||||||
 | 
					        self._config['layout'] = _layout
 | 
				
			||||||
 | 
					    def _ilogo(self):
 | 
				
			||||||
 | 
					        _gshandler = self._handler()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    def _iconfig(self,**_args):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Implement this in a base class
 | 
				
			||||||
 | 
					        :path or uri
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        raise Exception ("Configuration Initialization is NOT implemented")
 | 
				
			||||||
 | 
					    def _uconfig(self,**_args):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This file will update the configuration provided the CMS is run in shared mode (framework level)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if not self._location :
 | 
				
			||||||
 | 
					            return ;
 | 
				
			||||||
 | 
					        _path = os.sep.join(self._location.split(os.sep)[:-1])
 | 
				
			||||||
 | 
					        _layout = self._config['layout']
 | 
				
			||||||
 | 
					        _oroot = _layout['root']
 | 
				
			||||||
 | 
					        _orw = _layout['overwrite']
 | 
				
			||||||
 | 
					        _index = _layout['index']
 | 
				
			||||||
 | 
					        _newpath = os.sep.join([_path,_oroot])
 | 
				
			||||||
 | 
					        self._config['system']['portal'] = self._caller != None
 | 
				
			||||||
 | 
					        _context = self.system()['context']
 | 
				
			||||||
 | 
					        if self._caller :
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            _callerContext = self._caller.system()['context']
 | 
				
			||||||
 | 
					            if not self._config['system']['context'] :
 | 
				
			||||||
 | 
					                self._config['system']['context'] = _callerContext
 | 
				
			||||||
 | 
					            self._config['system']['caller'] = {'icon': 'caller/main/'+self._caller.system()['icon'].replace(_callerContext,'')}
 | 
				
			||||||
 | 
					            _context = _callerContext
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if os.path.exists(_newpath) and not self._ISCLOUD:
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            # LOG: rewrite due to the mode in which the site is being run
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            _api = f'{_context}/api/disk/read?uri='+_oroot
 | 
				
			||||||
 | 
					            _stream = json.dumps(self._config)
 | 
				
			||||||
 | 
					            _stream = _stream.replace(_oroot,_api)
 | 
				
			||||||
 | 
					            # self._config = json.loads(_stream)            
 | 
				
			||||||
 | 
					            self._config['layout']['root'] = _oroot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # self._config['layout']['overwrite'] = _orw
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # We need to update the  logo/icon
 | 
				
			||||||
 | 
					        _logo = self._config['system']['logo']
 | 
				
			||||||
 | 
					        if self._ISCLOUD:
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _icon = f'{_context}/api/cloud/download?doc=/{_logo}'
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            _icon = f'{_context}/api/disk/read?uri={_logo}'  
 | 
				
			||||||
 | 
					            if disk.exists(uri=_logo,config=self._config):
 | 
				
			||||||
 | 
					                _icon = _logo
 | 
				
			||||||
 | 
					            if self._location :
 | 
				
			||||||
 | 
					                self._config['layout']['location'] = _path
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					        self._config['system']['icon'] = _icon 
 | 
				
			||||||
 | 
					        self._config['system']['logo'] = _logo 
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # self.set('layout.root',os.sep.join([_path,_oroot]))
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					class Module (Initializer):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This is a basic structure for an application working in either portal or app mode,
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self,**_args):
 | 
				
			||||||
 | 
					        super().__init__(**_args)
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    def _iconfig(self, **_args):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        initialization of the configuration file i.e loading the files and having a baseline workable structure
 | 
				
			||||||
 | 
					        :path|stream   path of the configuration file
 | 
				
			||||||
 | 
					                        or stream of JSON configuration file
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        if 'path' in _args :
 | 
				
			||||||
 | 
					            f = open(_args['path'])
 | 
				
			||||||
 | 
					            self._config = json.loads(f.read())
 | 
				
			||||||
 | 
					            f.close()
 | 
				
			||||||
 | 
					        elif 'stream' in _args :
 | 
				
			||||||
 | 
					            _stream = _args['stream']
 | 
				
			||||||
 | 
					            if type(_stream) == 'str' :
 | 
				
			||||||
 | 
					                self._config  = json.loads(_stream)
 | 
				
			||||||
 | 
					            elif type(_stream) == io.StringIO :
 | 
				
			||||||
 | 
					                self._config =  json.loads( _stream.read())
 | 
				
			||||||
 | 
					        self._ISCLOUD = 'source' in self._config['system'] and self._config['system']['source'] and self._config['system']['source']['id'] == 'cloud'
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        # self._name = self._config['system']['name'] if 'name' in self._config['system'] else _args['name']
 | 
				
			||||||
 | 
					    def system (self,skip=[]):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This function returns system attributes without specific components
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        _data = copy.deepcopy(self._config['system'])
 | 
				
			||||||
 | 
					        exclude = skip
 | 
				
			||||||
 | 
					        _system = {}
 | 
				
			||||||
 | 
					        if exclude and _system:
 | 
				
			||||||
 | 
					            for key in _data.keys() :
 | 
				
			||||||
 | 
					                if key not in exclude :
 | 
				
			||||||
 | 
					                    _system[key] = _data[key]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            _system= _data
 | 
				
			||||||
 | 
					        return _system 
 | 
				
			||||||
 | 
					    def layout (self):
 | 
				
			||||||
 | 
					        return copy.copy(self._config['layout'])
 | 
				
			||||||
 | 
					    def plugins (self):
 | 
				
			||||||
 | 
					        return copy.copy(self._config['plugins'])
 | 
				
			||||||
 | 
					    def config (self):
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return copy.copy(self._config)
 | 
				
			||||||
 | 
					    def app(self):
 | 
				
			||||||
 | 
					        _system = self.system()
 | 
				
			||||||
 | 
					        return _system['app'] 
 | 
				
			||||||
 | 
					    def set(self,key,value):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        This function will update/set an attribute with a given value
 | 
				
			||||||
 | 
					        :key    
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        _keys = key.split('.')
 | 
				
			||||||
 | 
					        _found = 0
 | 
				
			||||||
 | 
					        if _keys[0] in self._config :
 | 
				
			||||||
 | 
					            _object = self._config[_keys[0]]
 | 
				
			||||||
 | 
					            for _akey in _object.keys() :
 | 
				
			||||||
 | 
					                if _akey == _keys[-1] :
 | 
				
			||||||
 | 
					                    _object[_akey] = value
 | 
				
			||||||
 | 
					                    _found = 1
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        return _found
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					class MicroService (Module):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This is a CMS MicroService class that is capable of initializing a site and exposing Module functions
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self,**_args):
 | 
				
			||||||
 | 
					        super().__init__(**_args)
 | 
				
			||||||
 | 
					    def format(_content,mimetype):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    def html (self,uri, id) :
 | 
				
			||||||
 | 
					        _system = self.system()
 | 
				
			||||||
 | 
					        _gshandler = self._handler()
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        #@TODO:
 | 
				
			||||||
 | 
					        # The uri here must be properly formatted, We need to define the conditions for this
 | 
				
			||||||
 | 
					        #
 | 
				
			||||||
 | 
					        _html = _gshandler.html(uri,self._config)
 | 
				
			||||||
 | 
					        return " ".join([f'<div id="{id}" > ',_html, '</div>'])
 | 
				
			||||||
 | 
					    def context(self):
 | 
				
			||||||
 | 
					        return Environment(loader=BaseLoader())
 | 
				
			||||||
 | 
					    def data (self,**_args):
 | 
				
			||||||
 | 
					        request = _args['request']
 | 
				
			||||||
 | 
					    def icon (self):
 | 
				
			||||||
 | 
					        _handler = self._handler()
 | 
				
			||||||
 | 
					        _uri = self.system()['icon']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return _handler.read(uri=_uri,config=self._config)
 | 
				
			||||||
 | 
					class CMS:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This class aggregates microservices and allows the application of a given service (site/app)
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self,**_args) :
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # _app = Getter (path = path)
 | 
				
			||||||
 | 
					        # _void = MicroService()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        _app = MicroService (**_args)
 | 
				
			||||||
 | 
					        self._id = 'main'
 | 
				
			||||||
 | 
					        # _app.load()
 | 
				
			||||||
 | 
					        self._apps = {}
 | 
				
			||||||
 | 
					        _system = _app.system()
 | 
				
			||||||
 | 
					        if 'routes' in _system :
 | 
				
			||||||
 | 
					            _system = _system['routes']
 | 
				
			||||||
 | 
					            for _name in _system :
 | 
				
			||||||
 | 
					                _path = _system[_name]['path']
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                self._apps[_name] = MicroService(context=_name,path=_path,caller=_app,location=_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._apps['main'] = _app    
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # The following are just a simple delegation pattern (it makes the calling code simpler)
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    def config (self):
 | 
				
			||||||
 | 
					        return self.get().config()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def render(self,_uri,_id,_appid):
 | 
				
			||||||
 | 
					        # _handler = self.get()
 | 
				
			||||||
 | 
					        _handler = self._apps[_appid]
 | 
				
			||||||
 | 
					        _config = _handler.config()
 | 
				
			||||||
 | 
					        _args = {'layout':_handler.layout()}
 | 
				
			||||||
 | 
					        if 'plugins' in _config:
 | 
				
			||||||
 | 
					            _args['routes'] = _config['plugins']
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        _html =  _handler.html(_uri,_id)
 | 
				
			||||||
 | 
					        _args['system'] = _handler.system(skip=['source','app'])
 | 
				
			||||||
 | 
					        e = Environment(loader=BaseLoader()).from_string(_html)
 | 
				
			||||||
 | 
					        _args[_id] =  str(e.render(**_args)) #,_args
 | 
				
			||||||
 | 
					        return _args
 | 
				
			||||||
 | 
					    def set(self,_id):
 | 
				
			||||||
 | 
					        self._id = _id
 | 
				
			||||||
 | 
					    def get(self):
 | 
				
			||||||
 | 
					        return self._apps['main'] if self._id not in self._apps else self._apps[self._id]
 | 
				
			||||||
 | 
					    def get_main(self):
 | 
				
			||||||
 | 
					        return self._apps['main']
 | 
				
			||||||
@ -0,0 +1,329 @@
 | 
				
			|||||||
 | 
					__doc__ = """
 | 
				
			||||||
 | 
					    arguments :
 | 
				
			||||||
 | 
					        --config    path of the configuration otherwise it will look for the default in the working directory
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from flask import Flask,render_template,send_from_directory,request, redirect, Response, session
 | 
				
			||||||
 | 
					import flask
 | 
				
			||||||
 | 
					import transport
 | 
				
			||||||
 | 
					from transport import providers
 | 
				
			||||||
 | 
					import cms
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import base64
 | 
				
			||||||
 | 
					from jinja2 import Environment, BaseLoader
 | 
				
			||||||
 | 
					import typer
 | 
				
			||||||
 | 
					from typing_extensions import Annotated
 | 
				
			||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pandas as pd
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cms import disk, cloud, engine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_app = Flask(__name__)
 | 
				
			||||||
 | 
					cli = typer.Typer()
 | 
				
			||||||
 | 
					# @_app.route('/favicon.ico')
 | 
				
			||||||
 | 
					# def favicon():
 | 
				
			||||||
 | 
					#     global _route
 | 
				
			||||||
 | 
					#     _system = _route.get ().system()
 | 
				
			||||||
 | 
					#     _handler = _route.get()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#     _logo =_system['icon'] if 'icon' in _system else 'static/img/logo.svg'
 | 
				
			||||||
 | 
					#     return _handler.get(_logo)
 | 
				
			||||||
 | 
					#     # # _root = _route.get().config()['layout']['root']
 | 
				
			||||||
 | 
					#     # # print ([_system])
 | 
				
			||||||
 | 
					#     # # if 'source' in _system and 'id' in _system['source'] and (_system['source']['id'] == 'cloud'):
 | 
				
			||||||
 | 
					#     # #     uri = f'/api/cloud/downloads?doc=/{_logo}'
 | 
				
			||||||
 | 
					#     # #     print (['****' , uri])
 | 
				
			||||||
 | 
					#     #     # return redirect(uri,200) #,{'content-type':'application/image'}
 | 
				
			||||||
 | 
					#     # # else:
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					#     # #     return send_from_directory(_root, #_app.root_path, 'static/img'),
 | 
				
			||||||
 | 
					#     #                            _logo, mimetype='image/vnd.microsoft.icon')
 | 
				
			||||||
 | 
					def _getHandler () :
 | 
				
			||||||
 | 
					    _id = session.get('app_id','main')
 | 
				
			||||||
 | 
					    return _route._apps[_id]
 | 
				
			||||||
 | 
					def _setHandler (id) :
 | 
				
			||||||
 | 
					    session['app_id'] = id
 | 
				
			||||||
 | 
					@_app.route("/robots.txt")
 | 
				
			||||||
 | 
					def robots_txt():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function will generate a robots expression for a variety of crawlers, the paths will be provided by
 | 
				
			||||||
 | 
					    menu options
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    _system = _route.get ().system()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _info  = ['''
 | 
				
			||||||
 | 
					    User-agent: *
 | 
				
			||||||
 | 
					    Allow: /
 | 
				
			||||||
 | 
					    ''']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if 'routes' in _system  :
 | 
				
			||||||
 | 
					        for _key in _system['routes'] :
 | 
				
			||||||
 | 
					            _uri = '/'.join(['',_key])
 | 
				
			||||||
 | 
					            _info.append(f'''
 | 
				
			||||||
 | 
					    User-agent: *
 | 
				
			||||||
 | 
					    Allow: {_uri}
 | 
				
			||||||
 | 
					                        ''')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # return '\n'.join(_info),200,{'Content-Type':'plain/text'}
 | 
				
			||||||
 | 
					    return Response('\n'.join(_info), mimetype='text/plain')    
 | 
				
			||||||
 | 
					@_app.route("/")
 | 
				
			||||||
 | 
					def _index ():
 | 
				
			||||||
 | 
					    # global _config
 | 
				
			||||||
 | 
					    # global _route
 | 
				
			||||||
 | 
					    # _handler = _route.get() 
 | 
				
			||||||
 | 
					    # _config = _route.config()
 | 
				
			||||||
 | 
					    _handler = _getHandler()
 | 
				
			||||||
 | 
					    _config = _handler.config()
 | 
				
			||||||
 | 
					    print ([' serving ',session.get('app_id','NA'),_handler.layout()['root']])
 | 
				
			||||||
 | 
					    # _system = _handler.system()
 | 
				
			||||||
 | 
					    # _plugins= _handler.plugins()
 | 
				
			||||||
 | 
					    # _args = {}
 | 
				
			||||||
 | 
					    # # if 'plugins' in _config :
 | 
				
			||||||
 | 
					    # #     _args['routes']=_config['plugins']
 | 
				
			||||||
 | 
					    # # _system = cms.components.get_system(_config) #copy.deepcopy(_config['system'])
 | 
				
			||||||
 | 
					    # _html = ""
 | 
				
			||||||
 | 
					    _args={'system':_handler.system(skip=['source','app','data']),'layout':_handler.layout()}
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        uri = os.sep.join([_config['layout']['root'], _config['layout']['index']])
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    #     # _html = _route.get().html(uri,'index',_config,_system)
 | 
				
			||||||
 | 
					    #     _html = _handler.html(uri,'index')
 | 
				
			||||||
 | 
					        _index_page = "index.html"
 | 
				
			||||||
 | 
					        _args = _route.render(uri,'index',session.get('app_id','main'))
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					    #     print ()
 | 
				
			||||||
 | 
					        print (e)
 | 
				
			||||||
 | 
					        _index_page = "404.html"
 | 
				
			||||||
 | 
					    #     _args['uri'] = request.base_url
 | 
				
			||||||
 | 
					    #     pass
 | 
				
			||||||
 | 
					    # # if 'source' in _system :
 | 
				
			||||||
 | 
					    # #     del _system['source']
 | 
				
			||||||
 | 
					    # _args = {'layout':_config['layout'],'index':_html}
 | 
				
			||||||
 | 
					    # _args['system'] = _handler.system(skip=['source','app','route'])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return render_template(_index_page,**_args),200 if _index_page != "404.html" else 200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# @_app.route('/id/<uid>') 
 | 
				
			||||||
 | 
					# def people(uid):
 | 
				
			||||||
 | 
					#     """
 | 
				
			||||||
 | 
					#     This function will implement hardened links that can directly "do something"
 | 
				
			||||||
 | 
					#     """
 | 
				
			||||||
 | 
					#     global _config    
 | 
				
			||||||
 | 
					#     return "0",200
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					@_app.route('/dialog')
 | 
				
			||||||
 | 
					def _dialog ():
 | 
				
			||||||
 | 
					    # global _config 
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    _handler = _route.get()
 | 
				
			||||||
 | 
					    _uri = request.headers['uri']
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    _id = request.headers['dom']
 | 
				
			||||||
 | 
					    # _html = ''.join(["<div style='padding:1%'>",str( e.render(**_args)),'</div>'])
 | 
				
			||||||
 | 
					    _args = _route.render(_uri,'html',session.get('app_id','main'))
 | 
				
			||||||
 | 
					    _args['title'] = _id
 | 
				
			||||||
 | 
					    return render_template('dialog.html',**_args) #title=_id,html=_html)
 | 
				
			||||||
 | 
					@_app.route("/caller/<app>/api/<module>/<name>")
 | 
				
			||||||
 | 
					def _delegate_call(app,module,name):
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    _handler = _route._apps[app]
 | 
				
			||||||
 | 
					    return _delegate(_handler,module,name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@_app.route('/api/<module>/<name>')
 | 
				
			||||||
 | 
					def _getproxy(module,name) :
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This endpoint will load a module and make a function call
 | 
				
			||||||
 | 
					    :_module entry specified in plugins of the configuration
 | 
				
			||||||
 | 
					    :_name  name of the function to execute
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # global _config
 | 
				
			||||||
 | 
					    # global _route
 | 
				
			||||||
 | 
					    # _handler = _route.get()
 | 
				
			||||||
 | 
					    _handler = _getHandler()
 | 
				
			||||||
 | 
					    return _delegate(_handler,module,name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _delegate(_handler,module,name):
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    uri =  '/'.join(['api',module,name])
 | 
				
			||||||
 | 
					    # _args = dict(request.args,**{})
 | 
				
			||||||
 | 
					    # _args['config'] = _handler.config()
 | 
				
			||||||
 | 
					    _plugins = _handler.plugins()
 | 
				
			||||||
 | 
					    _context = _handler.system()['context']
 | 
				
			||||||
 | 
					    if _context :
 | 
				
			||||||
 | 
					        uri  = f'{_context}/{uri}'
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if uri not in _plugins :
 | 
				
			||||||
 | 
					        _data = {}
 | 
				
			||||||
 | 
					        _code = 404
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        pointer = _plugins[uri]
 | 
				
			||||||
 | 
					        # if _args :
 | 
				
			||||||
 | 
					        #     _data = pointer (**_args)
 | 
				
			||||||
 | 
					        # else:
 | 
				
			||||||
 | 
					        #     _data = pointer()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        _data = pointer(request=request,config=_handler.config())
 | 
				
			||||||
 | 
					        if type(_data) == pd.DataFrame :
 | 
				
			||||||
 | 
					            _data = _data.to_dict(orient='records')
 | 
				
			||||||
 | 
					        if type(_data) == list:
 | 
				
			||||||
 | 
					            _data = json.dumps(_data)   
 | 
				
			||||||
 | 
					        _code = 200 if _data else 500
 | 
				
			||||||
 | 
					    return _data,_code
 | 
				
			||||||
 | 
					@_app.route("/api/<module>/<name>" , methods=['POST'])
 | 
				
			||||||
 | 
					def _post (module,name):
 | 
				
			||||||
 | 
					    # global _config
 | 
				
			||||||
 | 
					    # global _route
 | 
				
			||||||
 | 
					    # _handler = _route.get()
 | 
				
			||||||
 | 
					    _handler = _getHandler()
 | 
				
			||||||
 | 
					    return _delegate(_handler,module,name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@_app.route('/version')
 | 
				
			||||||
 | 
					def _version ():
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    _handler = _route.get()
 | 
				
			||||||
 | 
					    global _config 
 | 
				
			||||||
 | 
					    return _handler.system()['version']
 | 
				
			||||||
 | 
					@_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
 | 
				
			||||||
 | 
					    if not 'source' in _system :
 | 
				
			||||||
 | 
					        _systemKey = None
 | 
				
			||||||
 | 
					    elif 'key' in _system['source'] and _system['source']['key']:
 | 
				
			||||||
 | 
					        _systemKey = _system['source']['key']
 | 
				
			||||||
 | 
					        print ([_key,_systemKey,_systemKey == _key])
 | 
				
			||||||
 | 
					        if _key and _systemKey and _systemKey == _key :
 | 
				
			||||||
 | 
					            _handler.reload()
 | 
				
			||||||
 | 
					            return "",200
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					    return "",403
 | 
				
			||||||
 | 
					@_app.route('/page',methods=['POST'])
 | 
				
			||||||
 | 
					def cms_page():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    return the content of a folder formatted for a menu
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # global _config
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    # _handler = _route.get()
 | 
				
			||||||
 | 
					    # _config = _handler.config()
 | 
				
			||||||
 | 
					    _handler = _getHandler()
 | 
				
			||||||
 | 
					    _config = _handler.config()
 | 
				
			||||||
 | 
					    # _uri = os.sep.join([_config['layout']['root'],request.headers['uri']])
 | 
				
			||||||
 | 
					    _uri = request.headers['uri']
 | 
				
			||||||
 | 
					    if 'dom' not in request.headers :
 | 
				
			||||||
 | 
					        _id = _uri.split('/')[-1].split('.')[0]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _id = request.headers['dom']
 | 
				
			||||||
 | 
					    # _args = {'layout':_config['layout']}
 | 
				
			||||||
 | 
					    # if 'plugins' in _config:
 | 
				
			||||||
 | 
					    #     _args['routes'] = _config['plugins']
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # _system = _handler.system() #cms.components.get_system(_config)
 | 
				
			||||||
 | 
					    # # _html =  _handler.html(_uri,_id,_args,_system) #cms.components.html(_uri,_id,_args,_system)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # _html =  _handler.html(_uri,_id)
 | 
				
			||||||
 | 
					    # # _system = cms.components.get_system(_config)
 | 
				
			||||||
 | 
					    # _args['system'] = _handler.system(skip=['source','app'])
 | 
				
			||||||
 | 
					    # e = Environment(loader=BaseLoader()).from_string(_html)
 | 
				
			||||||
 | 
					    # _html = e.render(**_args)
 | 
				
			||||||
 | 
					    if 'read?uri=' in _uri or 'download?doc=' in _uri :
 | 
				
			||||||
 | 
					        _uri = _uri.split('=')[1]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    _args = _route.render(_uri,_id,session.get('app_id','main'))
 | 
				
			||||||
 | 
					    return _args[_id],200
 | 
				
			||||||
 | 
					    # return _html,200
 | 
				
			||||||
 | 
					@_app.route('/page')
 | 
				
			||||||
 | 
					def _cms_page ():
 | 
				
			||||||
 | 
					    # global _config
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    # _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'))
 | 
				
			||||||
 | 
					    return _args[_title],200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@_app.route('/set/<id>')
 | 
				
			||||||
 | 
					def set(id):
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    _setHandler(id)
 | 
				
			||||||
 | 
					    # _route.set(id)
 | 
				
			||||||
 | 
					    # _handler = _route.get()
 | 
				
			||||||
 | 
					    _handler = _getHandler()
 | 
				
			||||||
 | 
					    _context = _handler.system()['context']
 | 
				
			||||||
 | 
					    _uri = f'/{_context}'.replace('//','/')
 | 
				
			||||||
 | 
					    return redirect(_uri)
 | 
				
			||||||
 | 
					@_app.route('/<id>')    
 | 
				
			||||||
 | 
					def _open(id):
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					    # _handler = _route.get()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _handler = _getHandler()
 | 
				
			||||||
 | 
					    if id not in _route._apps :
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _args = {'config':_handler.config(), 'layout':_handler.layout(),'system':_handler.system(skip=['source','app'])}
 | 
				
			||||||
 | 
					        return render_template("404.html",**_args)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _setHandler(id)
 | 
				
			||||||
 | 
					        # _route.set(id)
 | 
				
			||||||
 | 
					        return _index()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@cli.command()
 | 
				
			||||||
 | 
					def start (
 | 
				
			||||||
 | 
					    path: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)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ) :
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    This function is designed to start the application with its associated manifest (configuration) location
 | 
				
			||||||
 | 
					    :path   path to the  the manifest
 | 
				
			||||||
 | 
					    :shared run in shared mode i.e 
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    global _route
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if os.path.exists(path) and os.path.isfile(path):
 | 
				
			||||||
 | 
					        _args = {'path':path}
 | 
				
			||||||
 | 
					        # if shared :
 | 
				
			||||||
 | 
					        #     _args['location'] = path
 | 
				
			||||||
 | 
					        #     _args['shared'] = shared
 | 
				
			||||||
 | 
					        _args['location'] = path
 | 
				
			||||||
 | 
					        _args['shared'] = True
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # _route = cms.engine.Router(**_args) #path=path,shared=shared)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        _route = cms.engine.basic.CMS(**_args)
 | 
				
			||||||
 | 
					        # dir(_route)
 | 
				
			||||||
 | 
					        # _args = _route.get().get_app()
 | 
				
			||||||
 | 
					        _args = _route.get().app()
 | 
				
			||||||
 | 
					        if port :
 | 
				
			||||||
 | 
					            _args['port'] = port
 | 
				
			||||||
 | 
					        _app.secret_key = str(uuid.uuid4())
 | 
				
			||||||
 | 
					        _app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(hours=24)
 | 
				
			||||||
 | 
					        _app.run(**_args)
 | 
				
			||||||
 | 
					        _status = 'found'
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        _status = 'not found'
 | 
				
			||||||
 | 
					    print(f'''
 | 
				
			||||||
 | 
					            manifest: {path}
 | 
				
			||||||
 | 
					            status  : {_status}
 | 
				
			||||||
 | 
					        ''')
 | 
				
			||||||
 | 
					@cli.command(name='help')
 | 
				
			||||||
 | 
					def _help() :
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 | 
					if __name__ == '__main__' :
 | 
				
			||||||
 | 
					    cli()
 | 
				
			||||||
@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					.main { font-size:14px; font-weight: lighter;}
 | 
				
			||||||
@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					.content {
 | 
				
			||||||
 | 
					    min-height:85vh;
 | 
				
			||||||
 | 
					    line-height: 1.5;
 | 
				
			||||||
 | 
					    font-weight:lighter;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.main .header {
 | 
				
			||||||
 | 
					    display:grid;
 | 
				
			||||||
 | 
					    grid-template-columns: 64px auto; gap:4px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.main .header .title {font-weight:bold; font-size:24px; text-transform: capitalize;}
 | 
				
			||||||
 | 
					.main .header .subtitle {font-size:14px; text-transform: capitalize;}
 | 
				
			||||||
 | 
					.main .header img {height:64px; width:64px}
 | 
				
			||||||
 | 
					.main .menu {display:none}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.pane {width:50%; height:100px; background-color: pink;}
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 1.9 MiB  | 
| 
		 After Width: | Height: | Size: 2.5 MiB  | 
| 
		 After Width: | Height: | Size: 3.3 MiB  | 
| 
		 After Width: | Height: | Size: 1.6 MiB  | 
| 
		 After Width: | Height: | Size: 786 KiB  | 
| 
		 After Width: | Height: | Size: 1.7 MiB  | 
| 
		 Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB  | 
| 
		 Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB  | 
| 
		 After Width: | Height: | Size: 116 KiB  | 
| 
		 After Width: | Height: | Size: 396 KiB  | 
| 
		 After Width: | Height: | Size: 187 KiB  | 
@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					var dialog = {}
 | 
				
			||||||
 | 
					dialog.context = ''
 | 
				
			||||||
 | 
					dialog.show = function(_title,_internalURI,_message,_pointer){
 | 
				
			||||||
 | 
					   var http = HttpClient.instance()
 | 
				
			||||||
 | 
					//    http.setData({title:_title,html:_message},'application/json')
 | 
				
			||||||
 | 
					   var uri = dialog.context+'/dialog'
 | 
				
			||||||
 | 
					   http.setHeader('dom',_title)
 | 
				
			||||||
 | 
					   http.setHeader('uri',_internalURI)
 | 
				
			||||||
 | 
					   http.get(uri,function(x){
 | 
				
			||||||
 | 
					      $('.jxmodal').remove()
 | 
				
			||||||
 | 
					       jx.modal.show({html:x.responseText,id:'body'})
 | 
				
			||||||
 | 
					       if(jx.dom.exists('dialog-message') && _message != null){
 | 
				
			||||||
 | 
					          jx.dom.set.value('dialog-message',_message)
 | 
				
			||||||
 | 
					         
 | 
				
			||||||
 | 
					       }
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      // In order to perhaps execute any js script that should have been executed on load ...
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      var scripts = $('.jxmodal script')
 | 
				
			||||||
 | 
					      jx.utils.patterns.visitor(scripts,function(_item){
 | 
				
			||||||
 | 
					         if(_item.text.trim().length > 0){
 | 
				
			||||||
 | 
					            var _routine = eval(_item.text)
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					            //@TODO:
 | 
				
			||||||
 | 
					            // Find a way to add the running function into the page to enable scripts to work
 | 
				
			||||||
 | 
					            //
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					   })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||