diff --git a/cms/__init__.py b/cms/__init__.py
index d621116..2300b33 100644
--- a/cms/__init__.py
+++ b/cms/__init__.py
@@ -7,7 +7,7 @@ from jinja2 import Environment, BaseLoader, FileSystemLoader
import importlib
import importlib.util
import json
-
+from . import sites
from . import apexchart
class Plugin :
@@ -132,13 +132,18 @@ class delegate:
#
# we need to update the _uri (if context/embeded apps)
#
- _context = _handler.system()['context']
+ # _context = _handler.system()['context']
+ _context = _handler.get('system.context')
if _context :
_uri = f'{_context}/{_uri}'
- _plugins = _handler.plugins()
+ # print ([' ** ',_uri, _args['uri']])
+ # _plugins = _handler.plugins()
+ _plugins = _handler.get('plugins')
_code = 200
+
if _uri in _plugins :
- _config = _handler.config() #_args['config']
+ # _config = _handler.config() #_args['config']
+ _config = _handler.get(None) #_args['config']
_pointer = _plugins[_uri]
@@ -159,6 +164,7 @@ class delegate:
if type(_data) == pd.DataFrame :
_data = _data.to_json(orient='records')
elif type(_data) in [dict,list] :
+
_data = json.dumps(_data)
pass
else:
diff --git a/cms/disk.py b/cms/disk.py
index bd1b6ce..4d105b4 100644
--- a/cms/disk.py
+++ b/cms/disk.py
@@ -43,11 +43,11 @@ def build (_config, keep=[]): #(_path,_content):
:path path of the files on disk
:config configuration associated with the
"""
+
_path = _config['layout']['root']
# if 'location' in _config['layout'] :
# _path = _config['layout']['location']
_path = _realpath(_path,_config)
- # print (_path)
_items = folders(_path,_config)
_subItems = [ content (os.sep.join([_path,_name]),_config)for _name in _items ]
@@ -57,7 +57,6 @@ def build (_config, keep=[]): #(_path,_content):
_index = _items.index(_name)
if _name.startswith('_') or len(_subItems[_index]) == 0:
continue
- # print ([_name,_subItems[_index]])
if _name not in _r :
_r[_name] = []
_r[_name] += _subItems[_index]
@@ -68,9 +67,8 @@ def build (_config, keep=[]): #(_path,_content):
def _realpath (uri,_config) :
_layout = _config['layout']
-
_uri = copy.copy(uri)
- if 'location' in _layout :
+ if 'location' in _layout and _layout['location']:
_uri = os.sep.join([_layout['location'],_uri])
return _uri
@@ -119,7 +117,9 @@ def html(_uri,_config) :
_context = str(_config['system']['context'])
# if '/' in _context :
# _context = _context.split('/')[-1]
+
_html = ( open(_path)).read()
+
_layout = _config['layout']
if 'location' in _layout :
if not _config :
diff --git a/cms/engine/__init__.py b/cms/engine/__init__.py
index 9d03eac..ed826f4 100644
--- a/cms/engine/__init__.py
+++ b/cms/engine/__init__.py
@@ -9,7 +9,7 @@
# import importlib
# import importlib.util
from cms import disk, cloud
-from . import basic
+# from . import basic
# class Loader :
# """
diff --git a/cms/engine/basic.py b/cms/engine/basic.py
deleted file mode 100644
index d7201e8..0000000
--- a/cms/engine/basic.py
+++ /dev/null
@@ -1,473 +0,0 @@
-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
- # if 'context' in _args :
- # self._config
- self.reload() #-- this is an initial load of various components
- #
- # @TODO:
- # Each module should submit it's routers to the parent, and adjust the references given the callers
- #
-
- def reload(self):
-
- self._iconfig(**self._args)
- self._uconfig(**self._args)
- self._isource()
- self._imenu()
- self._iplugins()
- self._iroutes ()
-
- # 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 _iroutes (self):
- """
- Initializing routes to be submitted to the CMS Handler
- The routes must be able to :
- 1. submit an object (dependency to the cms)
- 2. submit with the object a route associated
- The CMS handler will resolve dependencies/redundancies
- """
- pass
- 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 'location' in self._config['layout'] and not os.path.exists(_path):
- _path = os.sep.join([self._config['layout']['location'],_path])
-
- 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 _text in _overwrite :
- _text = _overwrite[_text]['text']
- if _label not in _menu :
- _menu [_label] = []
- _menu[_label].append ({"text":_text,'uri':uri,'type':'open'})
- #
- # update the menu items and the configuration
- #
- _keys = list(_menu.keys())
- # for _key in _keys :
- # if len(_menu[_key]) == 0 :
- # del _menu[_key]
- # #
- # # doing some house-keeping work here to make sure the front-end gets clean data
- # #
- _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': '/main'+self._caller.system()['icon'].replace(_callerContext,'')}
- _context = '/'.join([_callerContext,_context]) if _callerContext != '' else _context
-
-
-
- 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
- _logo = _icon
- 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'
- if self._caller :
- self._config['system']['parentContext'] = self._caller.system()['context']
- else:
- self._config['system']['parentContext'] = self._config['system']['context']
- if 'context' in _args :
- self._config['system']['context'] = _args['context']
-
- if '/' in self._config['system']['context'] :
- self._config['system']['context'] = self._config['system']['context'].split('/')[-1]
- #
- #
- # 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'
',_html, '
'])
- 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']
diff --git a/cms/index.py b/cms/index.py
index de5870b..d5234f4 100644
--- a/cms/index.py
+++ b/cms/index.py
@@ -26,51 +26,43 @@ import datetime
import requests
from cms import disk, cloud, engine
+import cms.sites
_app = Flask(__name__)
cli = typer.Typer()
-@_app.route('/favicon.ico')
-@_app.route('//favicon.ico')
-def favicon(id):
- global _route
- # _system = _route.get ().system()
- # _handler = _route.get()
- _handler = _getHandler(id)
- _system = _handler.system()
- _logo =_system['icon'] #if 'icon' in _system else 'static/img/logo.svg'
- _stream = requests.get(''.join([request.host_url,_logo]))
-
- return "_stream",200,{"Content-Type":"image/png"} #_handler.get(_logo),200,{"content-type":"image/png"}
-
+
def _getHandler (app_id,resource=None) :
- global _route
+ global _qcms
_id = _getId(app_id,resource)
- return _route._apps[_id]
+ return _qcms._apps[_id]
def _getId(app_id,app_x):
if app_x not in [None,''] :
- return '/'.join([app_id,app_x])
+ _uri = '/'.join([app_id,app_x])
+ return _uri[:-1] if _uri.endswith('/') else _uri
return app_id
def _setHandler (app_id,resource) :
session['app_id'] = _getId(app_id,resource)
-@_app.route("/robots.txt")
-def robots_txt():
+@_app.route("/<_id>/robots.txt")
+@_app.route("/robots.txt",defaults={'_id':None})
+def robots_txt(_id):
"""
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 = ['''
+ global _qcms
+ _site = _qcms.get (_id)
+ _routes = _site.get('system.routes')
+ _context = _site.get('system.context')
+ _info = [f'''
User-agent: *
- Allow: /
+ Allow: /{_context}
''']
- if 'routes' in _system :
- for _key in _system['routes'] :
- _uri = '/'.join(['',_key])
+ if _routes :
+ for _key in _routes:
+ _uri = '/'.join(["",_context,_key])
_info.append(f'''
User-agent: *
Allow: {_uri}
@@ -82,14 +74,14 @@ def _getIndex (app_id ,resource=None):
_handler = _getHandler(app_id,resource)
_layout = _handler.layout()
_status_code = 200
- global _route
+ global _qcms
_index_page = 'index.html'
_args = {}
try :
_uri = os.sep.join([_layout['root'],_layout['index']])
_id = _getId(app_id,resource)
- _args = _route.render(_uri,'index',_id)
+ _args = _qcms.render(_uri,'index',_id)
except Exception as e:
_status_code = 404
@@ -98,108 +90,81 @@ def _getIndex (app_id ,resource=None):
return render_template(_index_page,**_args),_status_code
@_app.route("/")
def _index ():
- return _getIndex('main')
+ # return _getIndex('main')
+ global _qcms
+ _app = _qcms.get(None)
+ _uri = os.sep.join([_app.get('layout.root'),_app.get('layout.index')])
+ _html = _qcms.render(_uri,'index')
+ return render_template('index.html',**_qcms.render(_uri,'index')),200
@_app.route("//")
@_app.route("/",defaults={'resource':None})
-def _aindex (app,resource=None):
- _handler = _getHandler(app,resource)
-
- _setHandler(app,resource)
- _html,_code = _getIndex(app,resource)
- return _html,_code
-# @_app.route('/id/')
-# def people(uid):
-# """
-# This function will implement hardened links that can directly "do something"
-# """
-# global _config
-# return "0",200
+def _altIndex(app,resource):
+ global _qcms
+ _id = _getId(app,resource)
+ _site = _qcms.get(_id)
+ _uri = os.sep.join([_site.get('layout.root'),_site.get('layout.index')])
+ return render_template('index.html',**_qcms.render(_uri,'index',_id)),200
+
@_app.route('//dialog')
-@_app.route('/dialog',defaults={'app':'main'})
-def _dialog (app):
- # global _config
- global _route
- _handler = _route.get()
- _uri = request.headers['uri']
-
+@_app.route('/dialog',defaults={'app':None})
+def _getdialog(app):
+ global _qcms
+ _site = _qcms.get(app)
+ _uri = request.headers['uri']
_id = request.headers['dom']
# _html = ''.join(["
",str( e.render(**_args)),'
'])
- _args = _route.render(_uri,'html',app) #session.get('app_id','main'))
+ _args = _qcms.render(_uri,'html',app) #session.get('app_id','main'))
_args['title'] = _id
return render_template('dialog.html',**_args) #title=_id,html=_html)
-@_app.route("/api//",defaults={'app':'main','key':None},methods=['GET','POST','DELETE','PUT'])
+@_app.route("/api//",defaults={'app':None,'key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("//api//",defaults={'key':None},methods=['GET','POST','DELETE','PUT'])
@_app.route("///api//",methods=['GET','POST','DELETE','PUT'])
-
-def _delegate_call(app,key,module,name):
- _handler = _getHandler(app,key)
- # print (_handler.config()/)
- uri = f'api/{module}/{name}'
- # return Plugin.call(uri=uri,handler=_handler,request=request)
+def _proxyCall(app,key,module,name):
+ global _qcms
+
+ _id = _getId(app,key)
+
+ _site = _qcms.get(_id)
+ _uri = f'api/{module}/{name}'
_delegate = cms.delegate()
- return _delegate(uri=uri,handler=_handler,request=request)
-
+ return _delegate(uri=_uri, handler=_site, request=request)
@_app.route('/version')
def _version ():
- global _route
- _handler = _route.get()
+ global _qcms
+ _site = _qcms.get()
global _config
- return _handler.system()['version']
+ return _site.get('system.version')
@_app.route("/reload/")
def _reload(key) :
- global _route
+ global _qcms
- _handler = _route.get_main()
- _system = _handler.system()
- if not 'source' in _system :
- _systemKey = None
- elif 'key' in _system['source'] and _system['source']['key']:
- _systemKey = _system['source']['key']
- # print ([key,_systemKey,_systemKey == key])
- if key and _systemKey and _systemKey == key :
- _handler.reload()
- return "",200
- pass
- return "",403
+ _site = _qcms.get(None)
+ _systemKey = _site.get('system.source.key')
+ if _systemKey == key and _systemKey:
+ _site.reload()
+ return "",200
+ else:
+ return "",403
@_app.route('/reload',methods=['POST'])
def reload():
- # global _route
-
- # _handler = _route.get_main()
- # _system = _handler.system()
_key = request.headers['key'] if 'key' in request.headers else None
return _reload(_key)
- # if not 'source' in _system :
- # _systemKey = None
- # elif 'key' in _system['source'] and _system['source']['key']:
- # _systemKey = _system['source']['key']
- # print ([_key,_systemKey,_systemKey == _key])
- # if _key and _systemKey and _systemKey == _key :
- # _handler.reload()
- # return "",200
- # pass
- # return "",403
+
-@_app.route('/page',methods=['POST'],defaults={'app_id':'main','key':None})
+@_app.route('/page',methods=['POST'],defaults={'app_id':None,'key':None})
@_app.route('//page',methods=['POST'],defaults={'key':None})
@_app.route('///page',methods=['POST'])
-def _POST_CMSPage(app_id,key):
+def getPostedPage(app_id,key):
"""
- return the content of a folder formatted for a menu
+ This function will return a page given a location parameter
"""
- # global _config
- global _route
- # _handler = _route.get()
- # _config = _handler.config()
- _handler = _getHandler(app_id,key)
- _setHandler(app_id,key)
-
- _config = _handler.config()
- # _uri = os.sep.join([_config['layout']['root'],request.headers['uri']])
+ 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]
@@ -208,22 +173,17 @@ def _POST_CMSPage(app_id,key):
if 'read?uri=' in _uri or 'download?doc=' in _uri :
_uri = _uri.split('=')[1]
- _args = _route.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
+ _args = _qcms.render(_uri,_id,_getId(app_id,key)) #session.get(app_id,'main'))
return _args[_id],200
- # return _html,200
-@_app.route('/page',defaults={'app_id':'main','resource':None})
+
+@_app.route('/page',defaults={'app_id':None,'resource':None})
@_app.route('//page',defaults={'resource':None},methods=['GET'])
@_app.route('///page',methods=['GET'])
def _cms_page (app_id,resource):
- # global _config
- global _route
- # _handler = _route.get()
- # _config = _handler.config()
+ global _qcms
_uri = request.args['uri']
-
- # _uri = os.sep.join([_config['layout']['root'],_uri])
_title = request.args['title'] if 'title' in request.args else ''
- _args = _route.render(_uri,_title,session.get(app_id,'main'))
+ _args = _qcms.render(_uri,_title,app_id)
return _args[_title],200
@@ -238,23 +198,16 @@ def start (
:path path to the the manifest
:shared run in shared mode i.e
"""
- global _route
+ global _qcms
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)
+ _qcms = cms.sites.QCMS(**_args)
+ _args = _qcms.get().get('system.app')
- # dir(_route)
- # _args = _route.get().get_app()
- _args = _route.get().app()
if port :
_args['port'] = port
_app.secret_key = str(uuid.uuid4())
diff --git a/cms/sites/__init__.py b/cms/sites/__init__.py
new file mode 100644
index 0000000..d0d8ca2
--- /dev/null
+++ b/cms/sites/__init__.py
@@ -0,0 +1,352 @@
+"""
+This file will describe how a site is loaded
+"""
+from cms.engine.config.structure import Layout, System
+from cms import disk, cloud
+import copy
+import json
+import io
+import os
+from jinja2 import Environment, BaseLoader, FileSystemLoader
+from datetime import datetime
+
+
+class IOConfig:
+ """
+ This class encapsulates read/write for configuration
+ """
+ def __init__(self,**_args):
+ self._config = {'system':{},'layout':{},'plugins':{}}
+ self._caller = None
+ self._location= _args['location'] if 'location' in _args else None
+ self._logs = []
+ def get(self,_key) :
+ if not _key :
+ return self._config
+ _keys = _key.split('.') if '.' in _key else [_key]
+ _value= None
+ _object = copy.deepcopy(self._config)
+ for _key in _keys :
+ if _key in _object :
+ _object = _object[_key]
+ else:
+ return None
+
+ return _object
+ def set(self,_expr,value):
+ if len(_expr.split('.')) == 1 :
+ self._config[_expr] = value
+ else:
+ _object = self._config
+ for _attr in _expr.split('.')[:-1] :
+ if _attr not in _object :
+ _object[_attr] = {}
+ _object = _object[_attr]
+ _attr = _expr.split('.')[-1]
+ _object[_attr] = value
+
+ def log(self,**_args):
+
+ _row = dict({'_date':datetime.now().strftime('%Y-%m-%d'),'time':datetime.now().strftime('%H:%M:%S'), 'context':self.get('system.context')},**_args)
+ self._logs.append(_row)
+
+ def debug(self,**_args):
+ return self._logs,'application/json'
+
+
+class Initialization (IOConfig):
+ def __init__(self,**_args):
+ super().__init__(**_args)
+ self._config = self.read_config(**_args)
+ self._args = _args
+ #
+ # Invoke initialization
+ self.reload()
+
+ def reload (self):
+ _args = self._args
+
+ self._caller = _args['caller'] if 'caller' in _args else None
+ if (self._caller):
+ self._config['system']['parentContext'] = ''
+ if not self.get('system.icon') :
+ self._config['system']['icon'] = None
+
+ self._config['layout']['location'] = None
+ if not self.get('layout.menu') :
+ self._config['layout']['menu'] = {}
+
+ self.context(**_args)
+
+ self.menu()
+ self.plugins()
+ def read_config(self,**_args) :
+ _config = {}
+ if 'path' in _args :
+ f = open(_args['path'])
+ _config = json.loads(f.read())
+ f.close()
+ elif 'stream' in _args :
+ _stream = _args['stream']
+ if type(_stream) == 'str' :
+ _config = json.loads(_stream)
+ elif type(_stream) == io.StringIO :
+ _config = json.loads( _stream.read())
+ _name = self._caller.__name__ if self._caller else None
+ self.log(action='init.config',module='read_config',input={'caller':_name})
+ return _config
+ def context(self,**_args):
+ """
+ Updating the context so that we can have a consistent representation
+ """
+ if ('context' in _args):
+ self.set('system.context',_args['context'])
+
+ pass
+ _context = self.get('system.context')
+
+ if self._caller :
+ #
+ # There is a parent context we need to account and we are updating the current context to reflect the caller context
+ _parentContext = self._caller.get('system.context')
+ _context = '/'.join([self._caller.get('system.context'),_context])
+ #
+ # updating the configuratioin
+ _iconURI = '/'.join(["",_parentContext,self._caller.get('system.icon')])
+
+ # print ([self._caller.get('system.context'), _parentContext,_context])
+ if self._caller.get('system.context') != '':
+ _parentContext = "/"+_parentContext
+ _context = "/"+_context
+ self.set('system.onport',0)
+ self._config['system']['onport'] = 0
+ else:
+ _iconURI = _iconURI.replace("///","/")
+ self.set('system.onport',1)
+ self._config['system']['onport'] = 1
+ self.set('system.caller',{'icon':_iconURI,'context':self._caller.get('system.context')})
+
+ # _context = _context.replace('/','')
+ else:
+ #
+ # If we have no parent site, there should be no parent context (or empty string)
+ _parentContext = _context #@TODO: should be none ?
+
+
+ #
+ # Updating context so it is usable throughout the code base
+ if self._caller:
+ self.set('system.parentContext', _parentContext.strip())
+ self.set('system.context',_context.strip())
+ # self._config['system']['context'] = _context.strip()
+ self._config['system']['parentContext'] = _parentContext
+ p = {'has_caller':self._caller != None,'context':_context}
+ self.log(action='init.context',module='context',input=p)
+ #
+ # loosly context to a certain extent involves locations of icons and document root
+ #
+
+ self.locations(**_args)
+
+ #
+ # submit log of status
+
+ def locations(self,**_args):
+ """
+ We are updating the icons, project location file and security key (if any), we need to make sure that context is updated first
+ """
+ #
+ # updating location & path of the application
+ # if self.get('system.caller') :
+ # return
+ _context = self.get('system.context')
+ _logo = self.get('system.logo')
+ _root = self.get('layout.root')
+ if self.get('system.source.id') == 'cloud' :
+ _icon = f'{_context}/api/cloud/download?doc={_logo}'
+ else:
+ _icon = f'{_context}/api/disk/read?uri={_logo}'
+ # _root = f'{_context}/api/disk/read?uri={_root}'
+
+ # self.set('layout.root',_root)
+ self.set('system.icon',_icon)
+ self.set('system.logo',_icon)
+ #
+ # from the path provided we can determine the location of the project
+ _homefolder = os.sep.join(_args['path'].split(os.sep)[:-1])
+ if _homefolder and os.path.exists(os.sep.join([_homefolder,_root])) :
+ self.set('layout.location',_homefolder)
+
+ #
+ # updating the security key and trying to determine the location
+ if self.get('system.source.key') :
+ _path = self.get('system.source.key')
+ f = None
+ if os.path.exists(_path) :
+ f = open(_path)
+ elif os.path.exists(os.sep.join([self.get('layout.location'),_path])) :
+ f = open(os.sep.join([self.get('layout.location'),_path]))
+ if f :
+ self.set('system.source.key',f.read())
+ f.close()
+
+ def menu (self,**_args):
+ """
+ This function will build the menu of the site (if need be)
+ """
+ _handler = cloud if self.get('system.source.id') == 'cloud' else disk
+ _object = _handler.build(self._config)
+
+ _overwrite = self.get('layout.overwrite')
+ for _name in _object :
+ _submenu = _object[_name]
+ _index = 0
+ for _item in _submenu :
+ _text = _item['text'].strip()
+ if _text in _overwrite :
+ if 'uri' in _item and 'url' in _overwrite :
+ del _item['uri']
+ _item = dict(_item,**_overwrite[_text])
+ _submenu[_index] = _item
+ _index += 1
+ self.set('layout.menu',_object)
+ # self._config['layout']['menu'] = _object
+ self.routes() #-- other apps/sites integrated in case we are operating in portal mode
+ #
+ # At this point the entire menu is build and we need to have it sorted
+ self.order()
+ # _labels = list(self.get('layout.menu').keys())
+ # self.log(action='init.menu',module='menu',input=_labels)
+ # print (self.get('layout.menu'))
+ # print (_object)
+ def order(self,**_args):
+ if self.get('layout.order.menu') :
+ _sorted = {}
+ _menu = self.get('layout.menu')
+ _ordered = self.get('layout.order.menu')
+ for _name in _ordered :
+ if _name in _menu :
+ _sorted[_name] = _menu[_name]
+ if _sorted :
+ _menu = _sorted
+ _missing = list(set(_menu.keys()) - set(_sorted))
+ if _missing :
+ for _name in _missing :
+ _menu[_name] = _menu[_name]
+ self.set('layout.menu',_menu)
+ def routes (self,**_args):
+ """
+ This method will update menu with dependent sites/apps and add them to the menu
+ """
+ _overwrite = self.get('layout.overwrite')
+ _routes = self.get('system.routes')
+ _context = self.get('system.context')
+ _menu = self.get('layout.menu')
+
+ if _routes :
+ for _text in _routes :
+ _item = _routes[_text]
+ if 'menu' in _item :
+ _uri = f'{_context}/{_text}'
+ _label = _item['menu']
+ if _text in _overwrite :
+ _text = _overwrite[_text]['text']
+ if _label not in _menu :
+ _menu[_label] = []
+ _menu[_label].append({"text":_text,"uri":_uri,"type":"open"})
+ #
+ # updating menu ...
+ self.set('layout.menu',_menu)
+ # self._config['layout']['menu'] = _menu
+ else:
+ pass
+ def plugins (self,**_args):
+ """
+ This function will load the plugins from disk (only)
+ """
+ _folder = os.sep.join([self.get('layout.location'),self.get('layout.root'),'_plugins'])
+ _context = self.get('system.context')
+ _parentContext = self.get('system.parentContext')
+ _map = {}
+ _plugins = {}
+ if self.get('system.source.id') == 'cloud' :
+ _plugins = cloud.plugins(_context)
+ else:
+ _plugins = disk.plugins(context=_context)
+ _uri = 'api/system/debug'
+ _uri = _uri if not _context else f'{_context}/{_uri}'
+ _plugins[_uri] = self.debug
+ if os.path.exists(_folder) and self.get('plugins'):
+
+ _items = self.get('plugins')
+
+ for _filename in _items:
+ _path = os.sep.join([_folder,_filename+'.py'])
+
+ if os.path.exists(_path) :
+ for _module in _items[_filename] :
+ _pointer = disk.plugins(path=_path,name=_module,context=_context)
+ if _pointer :
+ _uri = f"api/{_filename}/{_module}"
+ _uri = f"{_context}/{_uri}" if _context else _uri
+ _map[_uri] = _pointer
+ if _parentContext :
+ # _uri = f"{_parentContext}/{_context}"
+ _map[_uri] = _pointer
+
+ self.log(action='load.plugin',module='plugins',input={'uri':_uri,'path':_path,'name':_module})
+ else:
+ self.log(action='missing.function',module='plugins',input={'name':_module,'path':_path})
+
+ else:
+ #
+ # log the error ...
+ self.log(action='missing.plugins',module='plugins',input=_path)
+ #
+ # Updating plugins from disk/cloud
+ _plugins = _map if not _plugins else dict(_plugins,**_map)
+ #
+ #
+ self.set('plugins',_plugins)
+ self.log(action='init.plugins',module='plugins',input=list(_plugins.keys()))
+
+class Site(Initialization) :
+ def __init__(self,**_args):
+ super().__init__(**_args)
+ self._config['system']['portal'] = (self.get('system.routes')) == None
+ def html(self,_uri,_id) :
+ _handler = cloud if self.get('system.source.id') == 'cloud' else disk
+
+ _html = _handler.html(_uri, self.get(None))
+
+ return " ".join([f'
',_html,"
"])
+
+class QCMS:
+ def __init__(self,**_args):
+ _app = Site(**_args)
+ self._id = _app.get('system.context') #if _app.get('system.context') else 'main'
+ self._sites = {self._id:_app}
+ if _app.get('system.routes') :
+ _routes = _app.get('system.routes')
+
+ for _name in _routes :
+ _path = _routes[_name]['path']
+ self._sites[_name] = Site(context=_name,path=_path,caller=_app)
+ def render(self,_uri,_id,_appid=None):
+ _site = self._sites[_appid] if _appid else self._sites[self._id]
+ _args = {'layout':_site.get('layout')}
+ _system = _site.get('system')
+ for k in ['source','app'] :
+ if k in _system :
+ del _system[k]
+ _args['system'] = _system
+ _html = _site.html(_uri,_id)
+
+ _env = Environment(loader=BaseLoader()).from_string(_html)
+ _args[_id] = str(_env.render(**_args))
+ return _args
+ def set(self,_id):
+ self._id = _id
+ def get(self,_id=None):
+
+ return self._sites[self._id] if not _id else self._sites[_id]
diff --git a/cms/static/js/forms.js b/cms/static/js/forms.js
new file mode 100644
index 0000000..f068d7a
--- /dev/null
+++ b/cms/static/js/forms.js
@@ -0,0 +1,60 @@
+/***
+ *
+ * This file build forms from a given set of specifications (in JSON) format
+ */
+
+var _form =function(_id,_data) {
+ this._data = _data // {input,defaults,choices}
+ this._id = _id
+ //
+ // let's clean up the data :
+ this._data.defaults = (this._data.defaults == null)? {}:this._data.defaults
+ this._data.choices = (this._data.choices == null)?{}: this._data.choices
+ this.render = function (){
+ _title = $('').html(this._id).addClass(this._id)
+ var _input = this._data.input
+ var _defaults = this._data.defaults
+ var _choices = this._data.choices
+
+ _input.forEach (_label=>{
+ _value = _defaults[_label]
+ if (_choices[_label]){
+
+ }else{
+ //
+ // This
+
+ _input = this.get.input(_label,_value)
+ }
+
+ })
+ }
+
+ this.get = {}
+ this.get.input = function (label,value){
+ _frame = $('')
+ _label = $('').html(label)
+ _input = $('').attr('placeholder',label.0.replace(/_/,' ').trim()).addClass("input-"+label)
+ $(_input)[0].data = label
+ if (value){
+ $(_input).val(value)
+ }
+ _frame.append(_label,_input)
+ return _frame
+ }
+ this.get.choice= function (label,_default,choices) {
+ var _input = this.get.input(label,_default)
+ $(input).addClass("hidden-input")
+ $(_input).on('click',function (){
+
+ })
+ _pane = $('')
+ choices.forEach(_value=>{
+ _option = $('
').addClass("option").addClass("option-"+_value)
+ $(_pane).append(_option)
+ })
+ _pane.append(_input,choices)
+ _frame = $('').append(_pane)
+ return _frame
+ }
+}
\ No newline at end of file
diff --git a/cms/static/js/jx/utils.js b/cms/static/js/jx/utils.js
index a3834ac..52d54a5 100755
--- a/cms/static/js/jx/utils.js
+++ b/cms/static/js/jx/utils.js
@@ -196,6 +196,7 @@ jx.utils.patterns.observer = function(lobservers,init){
// let's fire the design pattern
//
p.start() ;
+ return p
}
/**
diff --git a/cms/static/js/menu.js b/cms/static/js/menu.js
index 04a2f1b..7d234a2 100644
--- a/cms/static/js/menu.js
+++ b/cms/static/js/menu.js
@@ -281,7 +281,6 @@ var QCMSBasic= function(_layout,_context,_clickEvent) {
// Object.keys(this._layout.menu)
_names.forEach(function(_name){
-
var _div = _me._make(_layout.menu[_name]) ;
@@ -299,7 +298,7 @@ var QCMSBasic= function(_layout,_context,_clickEvent) {
}
}
-var QCMSTabs = function(_layout,_context,_clickEvent){
+var QCMSTabs = function(_layout,_context,_id){
//
// This object will make tabs in the area of the menu
// @TODO: we can parameterize where the menu is made to improve flexibility
@@ -308,6 +307,7 @@ var QCMSTabs = function(_layout,_context,_clickEvent){
this.tabs.className = 'tabs'
this._context = _context
this._layout = _layout
+ this._target = (_id == null)?'.main .menu':_id
this._make = function (text,_item,_event){
var text = text.trim().replace(/(_|-)/ig,' ').trim()
var _context = this._context;
@@ -364,9 +364,9 @@ var QCMSTabs = function(_layout,_context,_clickEvent){
})
this.tabs.className = 'tabs'
- $('.main .menu').append(this.tabs)
- $('.main .menu').css({'border':'1px solid transparent'})
- $('.main .menu').css({'grid-template-columns':'64px auto'})
+ $(this._target).append(this.tabs)
+ $(this._target).css({'border':'1px solid transparent'})
+ $(this._target).css({'grid-template-columns':'64px auto'})
}
//
// We need to load the pages here ...
diff --git a/cms/templates/index.html b/cms/templates/index.html
index 347d440..c08f957 100644
--- a/cms/templates/index.html
+++ b/cms/templates/index.html
@@ -15,6 +15,7 @@ Vanderbilt University Medical Center
+
{{layout.header.title}}
@@ -37,6 +38,7 @@ Vanderbilt University Medical Center
+
@@ -44,6 +46,7 @@ Vanderbilt University Medical Center
+
@@ -84,8 +87,8 @@ Vanderbilt University Medical Center
-
-{% endif %}
\ No newline at end of file
+{% endif %}
+
diff --git a/readme.md b/readme.md
index 9bd0345..11855bb 100644
--- a/readme.md
+++ b/readme.md
@@ -38,6 +38,7 @@ The content is created in ./qcms-demo/www/html
Things to update with qcms runner :
+
- system.icon location, when creating a project
- applying themes
- upgrade all plugins to return data-type ?