diff --git a/cms/engine/config/__init__.py b/cms/engine/config/__init__.py new file mode 100644 index 0000000..0523e4d --- /dev/null +++ b/cms/engine/config/__init__.py @@ -0,0 +1,28 @@ +""" +This file handles all things configuration i.e even the parts of the configuration we are interested in +""" +import os +import json + +def get (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, path): + f = open(path,'w') + f.write( json.dumps(_config)) ; + f.close() + diff --git a/cms/engine/config/structure.py b/cms/engine/config/structure.py new file mode 100644 index 0000000..93aca9c --- /dev/null +++ b/cms/engine/config/structure.py @@ -0,0 +1,77 @@ +""" +This file handles the structure (strict) of the configuration file, +Not all elements are programmatically editable (for now) +""" +import meta +class Section : + + def build (self,**_args): + _data = {} + for _attr in dir(self): + if not _attr.startswith('_') and _attr not in ['build','update']: + _kwargs = _args if _attr not in _args or type(_args[_attr]) !=dict else _args[_attr] + _data[_attr] = getattr(self,_attr)(**_kwargs) + _name = type (self).__name__.lower() + + return {_name:_data } + + + + def update(self,_default,**_args) : + for _attr in _default.keys(): + + if _attr in _args : + _default[_attr] = _args[_attr] + return _default + +class System (Section) : + + def logo(self,**_args): + + return 'www/html/_assets/images/logo.png' if 'logo' not in _args else _args['logo'] + def theme (self,**_args): + """ + setting the theme given the name of the theme + :name name of the theme to set (optional) + """ + return 'default' if 'name' not in _args else _args['name'] + def version(self,**_args): + return meta.__version__ if 'version' not in _args else _args['version'] + def context (self,**_args): + return "" if 'context' not in _args else _args['context'] + def app (self,**_args): + _data = {'debug':True,'port':8084,'threaded':True,'host':'0.0.0.0'} + return self.update(_data,**_args) + + def source(self,**_args): + _data = {'id':'disk'} + if set(['key','auth']) & set(_args.keys()): + # + # key: reboot the app & auth: for cloud access + # + for _attr in ['key','auth'] : + if _attr in _args: + _data[_attr] = _args[_attr] + + _data = self.update(_data,**_args) + return _data + +class Layout (Section): + def index (self,**_args): + return "index.html" if 'index' not in _args else _args['index'] + def root(self,**_args): + return 'wwww/html' if 'root' not in _args else _args['root'] + def footer(self,**_args): + return [{'text':'Powered by QCMS'}] if 'footer' not in _args else _args['footer'] + def on(self,**_args): + return {'load':{}} if 'on' not in _args else _args['on'] + def order(self,**_args): + return {'menu':[]} if 'order' not in _args else _args['order'] + # def menu (self,**_args): + # return {'menu':[]} if 'menu' not in _args else _args['menu'] + def overwrite(self,**_args): + return {} + def header (self,**_args): + _data = {"title":"QCMS Project", "subtitle":"Powered by Python Flask"} + return self.update(_data,**_args) + \ No newline at end of file diff --git a/cms/engine/plugins/__init__.py b/cms/engine/plugins/__init__.py new file mode 100644 index 0000000..6ada10b --- /dev/null +++ b/cms/engine/plugins/__init__.py @@ -0,0 +1,42 @@ +import json +import pandas as pd +import importlib +import importlib.util +import os + +def stats (_config) : + """ + Returns the statistics of the plugins + """ + _data = [] + for _name in _config : + _log = {"files":_name,"loaded":len(_config[_name]),"logs":json.dumps(_config[_name])} + _data.append(_log) + return pd.DataFrame(_data) + pass +def load(_path,_filename,_name) : + """ + 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') and name == _filename] + 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(_filename, _path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return getattr(module,_name) if hasattr(module,_name) else None + + diff --git a/cms/engine/project/__init__.py b/cms/engine/project/__init__.py new file mode 100644 index 0000000..a24339f --- /dev/null +++ b/cms/engine/project/__init__.py @@ -0,0 +1,154 @@ +# from cms import _system, _header,_layout +import base64 +import os +import json +import meta +from cms.engine import themes +# 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['footer']] +# else: +# _info['layout']['footer'] = [{'text':'Powered by QCMS'}] + +# return _info +def make_folder (projectFolder, webroot): + """ + This function creates project folders, inside the project folder is the web root folder + """ + + if not os.path.exists(projectFolder): + os.makedirs(projectFolder) + _path = os.sep.join([projectFolder,webroot]) + folders = ['_assets',os.sep.join(['_assets','images']), os.sep.join(['_assets','themes']), '_plugins'] + for folder in folders : + _projectPath = os.sep.join([_path,folder]) + if not os.path.exists(_projectPath) : + os.makedirs(_projectPath) +def _ilogo (_path): + """ + This function creates a default logo in a designated folder, after the project folder has been created + :_path project path + """ + _image = """data:image/png;base64,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""" + _image1 = """data:image/png;base64,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""" + _image2 = """data:image/png;base64,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""" + _index = 0 + for _image in [_image1,_image2] : + _,_image= _image.split(',',1) + _logoPath = os.sep.join([_path,'_assets','images','logo.png']) + if _index > 0 : + _logoPath = _logoPath.replace('.png',f'-{_index}.png') + f = open(_logoPath,'wb') + f.write(base64.b64decode(_image)) + f.close() + _index += 1 + +def _index (_path,root): + """ + Creating a default index.html for the site given the project root location + """ + _html = f""" +
+
Thank you for considering QCMS
+
version {meta.__version__} by {meta.__author__}, {meta.__email__}
+
+

+ +

+ +
+
+
+ +
+ QCMS is powered by Python/Flask +
    + Small footprint & can work as either a portal or a standalone application +
+
    + Simple paradigm that leverages folder structure to generate a site +
+
+ +
+
+ QCMS has built-in support for industry standard frameworks +
+ + +
+ + + +
+
+

+

Learn more about QCMS and at {themes.URL}
+

+ """ + _indexfile = os.sep.join([_path,'index.html']) + if os.path.exists(_indexfile): + # + # In case a project needs to be upgraded ... + f = open(_indexfile,'w') + f.write(_html) + f.close() +def _itheme(_path,_root,_name='default'): + _data = themes.Get(_name)[_name] + _themefolder = os.sep.join([_path,_root,'_assets','themes',_name]) + if not os.path.exists(_themefolder) : + os.makedirs(_themefolder) + + for _name in _data : + f = open(os.sep.join([_themefolder,_name+'.css']),'w') + f.write(_data[_name]) + f.close() + pass +def make (**_args) : + """ + This function create a project folder and within the folder are all the elements needed + """ + _config = _args['config'] + _config['plugins'] = {} + _folder = _args['folder'] + _root = _config['layout']['root'] #-- web root folder + make_folder(_folder,_root) + f = open(os.sep.join([_folder,'qcms-manifest.json']),'w') + f.write( json.dumps(_config)) + f.close() + + _ilogo(os.sep.join([_folder,_root])) + _index(os.sep.join([_folder,_root]),_root) + _itheme(_folder,_root) + \ No newline at end of file diff --git a/cms/engine/themes/__init__.py b/cms/engine/themes/__init__.py new file mode 100644 index 0000000..d3d00b7 --- /dev/null +++ b/cms/engine/themes/__init__.py @@ -0,0 +1,44 @@ +""" +This class implements the base infrastructure to handle themes, the class must leverage +default themes will be stored in layout.root._assets.themes, The expected files in a theme are the following: + - borders + - buttons + - layout + - menu + - header +If the following are not available then we should load the default one. +This would avoid crashes but would come at the expense of a lack of consistent visual layout (alas) +""" +import requests +import os +URL = os.environ['QCMS_HOME_URL'] if 'QCMS_HOME_URL' in os.environ else 'https://dev.the-phi.com/qcms' +def current (_system) : + return _system['theme'] +def List (_url = URL) : + """ + calling qcms to list the available URL + """ + try: + _url = '/'.join([_url,'api','themes','List']) + return requests.get(_url).json() + except Exception as e: + + pass + return [] +def Get(theme,_url= URL) : + """ + This function retrieves a particular theme from a remote source + The name should be in the list provided by the above function + """ + try: + _url = '/'.join([_url,'api','themes','Get']) +f'?theme={theme}' + return requests.get(_url).json() + except Exception as e: + pass + return {} +def installed (path): + return os.listdir(os.sep.join([path,'_assets','themes'])) + +def Set(theme,_system) : + _system['theme'] = theme + return _system diff --git a/cms/static/css/dialog.css b/cms/static/css/dialog.css new file mode 100644 index 0000000..7ec2adf --- /dev/null +++ b/cms/static/css/dialog.css @@ -0,0 +1,10 @@ + +.dialog-title { + background-color:#FF6500;color:#FFFFFF; + text-transform:capitalize; font-weight:bold; align-items:center;display:grid; grid-template-columns:auto 32px; +} +.dialog-button { + display:grid; + grid-template-columns: auto 115px; + gap:4px; +} diff --git a/cms/static/css/source-code.css b/cms/static/css/source-code.css new file mode 100644 index 0000000..e46f72c --- /dev/null +++ b/cms/static/css/source-code.css @@ -0,0 +1,18 @@ +.source-code { + background-color: #000000; COLOR:#ffffff; + font-family: 'Courier New', Courier, monospace; + padding:8px; + padding-left:10px; + text-wrap: wrap; + width:calc(100% - 40px); + border-left:8px solid #CAD5E0; margin-left:10px; font-weight: bold; + font-size:14px; +} + +.editor { + background-color:#f3f3f3; + color:#000000; +} + +.editor .keyword {color: #4682b4; font-weight:bold} +.code-comment { font-style: italic; color:gray; font-size:13px;} \ No newline at end of file