diff --git a/CHANGELOG.md b/CHANGELOG.md index 5de21cf14..39c5f7c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # Release Notes +## Version 0.25.0 +**Release Date**: 18th August 2021 +### Features +1. Templates Moved from main menu to sidebar and now opened on a Double Click. +2. Pan and Zoom added to SVG display returning to 1:1 during redraw. +3. Display Git Directories as a side panel and allow for double click open. +4. Reorganise Reference Architecture Templates + +### Bug Fixes +1. Fix query pagination issues where not all resources were being retrieved. +2. Remove uses of Terraform Data Resource oci_identity_tenancy +3. GitHub Issue #406: Sub-Menu options can't be selected in Chrome (Subtract scrollbar width) + + ## Version 0.24.5 **Release Date**: 19th July 2021 ### Bug Fixes diff --git a/Dockerfile b/Dockerfile index dd9c60d8d..e7fa4f564 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM oraclelinux:7-slim LABEL "provider"="Oracle" \ "issues"="https://github.com/oracle/oci-designer-toolkit/issues" \ - "version"="0.24.5" \ + "version"="0.25.0" \ "description"="OKIT Web Server Container." \ "copyright"="Copyright (c) 2020, 2021, Oracle and/or its affiliates." SHELL ["/bin/bash", "-c"] @@ -57,7 +57,7 @@ RUN yum install -y \ # Create Workspace && mkdir -p /github \ && git clone -c core.autocrlf=input https://github.com/oracle/oci-designer-toolkit.git /github/oci-designer-toolkit \ - && mkdir -p /okit/{log,workspace,ssl} \ + && mkdir -p /okit/{git,local,log,instance/git,instance/local,instance/templates/user,workspace,ssl} \ && mkdir -p /root/bin \ && openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /okit/ssl/okit.key -out /okit/ssl/okit.crt -subj "/C=GB/ST=Berkshire/L=Reading/O=Oracle/OU=OKIT/CN=www.oci_okit.com" \ && ln -sv /github/oci-designer-toolkit/config /okit/config \ @@ -65,7 +65,7 @@ RUN yum install -y \ && ln -sv /github/oci-designer-toolkit/visualiser /okit/visualiser \ && ln -sv /github/oci-designer-toolkit/containers/docker/run-server.sh /root/bin/run-server.sh \ && mkdir -p /okit/okitweb/static/okit/templates \ - && ln -sv /okit/templates /okit/okitweb/static/okit/templates/user \ + #&& ln -sv /okit/templates /okit/okitweb/static/okit/templates/user \ && chmod a+x /root/bin/run-server.sh # Add entrypoint to automatically start webserver CMD ["run-server.sh"] diff --git a/README.md b/README.md index 8912607a5..83b895a3b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,16 @@ -# Oracle Cloud Infrastructure Designer and Visualisation Toolkit [0.24.5](CHANGELOG.md#version-0.24.5) +# Oracle Cloud Infrastructure Designer and Visualisation Toolkit [0.25.0](CHANGELOG.md#version-0.25.0) + +```diff ++ The 0.25.0 release has now moved the templates from the menu to a new side panel and the can be opened by double clicking ++ the file. This new format allows for user templates with sub directories. The corresponding Save As functionality also ++ allows the user to create sub directories by specifying the full path. + +- The GIT functionality has also been moved to a side panel and on opening the designer page OKIT will attempt to clone/pull +- from the git repositories specified in the ~/.oci/git_repositories file. If the pull fails then an empty details section +- will be displayed. + ++ The User templates and Git directories can be mounted from the local environment as shown in the updated run instructions. +``` OCI designer and visualisation toolKIT (OKIT) is a browser based tool that allows the user to [design](https://www.ateam-oracle.com/introduction-to-okit-the-oci-designer-toolkit), [deploy](https://www.ateam-oracle.com/introduction-to-okit-the-oci-designer-toolkit) and visualise ([introspect/query](https://www.ateam-oracle.com/the-oci-designer-toolkit-query-feature)) @@ -129,10 +141,16 @@ Host github.com #### Run Container +##### Simple ```bash docker run -d --rm -p 80:80 --volume /okit/user/templates:/okit/templates --volume /.oci:/root/.oci --volume /.ssh:/root/.ssh --name okit okit ``` +##### Mount User Templates and Git Directories +```bash +docker run -d --rm -p 80:80 --volume /okit/user/templates:/okit/templates --volume /.oci:/root/.oci --volume /.ssh:/root/.ssh --volume :/okit/instance/user --volume :/okit/instance/git --volume :/okit/instance/local --name okit okit +``` + Once started the Designer BUI can be accessed on [http://localhost/okit/designer](http://localhost/okit/designer) ## Usage / Examples diff --git a/containers/docker/Dockerfile b/containers/docker/Dockerfile index 74a81d837..70662ab73 100644 --- a/containers/docker/Dockerfile +++ b/containers/docker/Dockerfile @@ -5,7 +5,7 @@ FROM oraclelinux:7-slim LABEL "provider"="Oracle" \ "issues"="https://github.com/oracle/oci-designer-toolkit/issues" \ - "version"="0.24.5" \ + "version"="0.25.0" \ "description"="OKIT Web Server Container." \ "copyright"="Copyright (c) 2020, 2021, Oracle and/or its affiliates." SHELL ["/bin/bash", "-c"] @@ -55,12 +55,12 @@ RUN yum install -y \ requests==2.25.1 \ xlsxwriter==1.3.7 \ # Create Workspace - && mkdir -p /okit/{config,git,log,ssl,visualiser,okitweb,workspace,templates,skeletons} \ - && mkdir -p /okit/okitweb/static/okit/templates \ - && openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /okit/ssl/okit.key -out /okit/ssl/okit.crt -subj "/C=GB/ST=Berkshire/L=Reading/O=Oracle/OU=OKIT/CN=www.oci_okit.com" \ - && ln -s /okit/templates /okit/okitweb/static/okit/templates/user \ - && ln -s /okit/git /okit/okitweb/static/okit/templates/git \ - && ln -s /okit/git /okit/okitweb/static/okit/git + && mkdir -p /okit/{config,git,instance/git,instance/local,instance/templates/user,local,log,ssl,visualiser,okitweb,workspace,skeletons} \ +# && mkdir -p /okit/okitweb/static/okit/templates \ + && openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /okit/ssl/okit.key -out /okit/ssl/okit.crt -subj "/C=GB/ST=Berkshire/L=Reading/O=Oracle/OU=OKIT/CN=www.oci_okit.com" +# && ln -s /okit/templates /okit/okitweb/static/okit/templates/user \ +# && ln -s /okit/git /okit/okitweb/static/okit/templates/git \ +# && ln -s /okit/git /okit/okitweb/static/okit/git # Copy source code COPY config /okit/config COPY okitweb /okit/okitweb @@ -69,7 +69,8 @@ COPY visualiser /okit/visualiser COPY skeletons /okit/skeletons COPY containers/oci/* /root/.oci/ COPY containers/ssh/* /root/.ssh/ -RUN chmod 600 /root/.ssh/* +RUN chmod 600 /root/.ssh/* \ + && ln -s /okit/okitweb/static/okit/templates/reference_architecture /okit/instance/templates/reference_architecture # Add entrypoint to automatically start webserver ENTRYPOINT ["gunicorn", "okitweb.wsgi:app"] diff --git a/okitweb/__init__.py b/okitweb/__init__.py index 626bbeb97..c78bd9838 100644 --- a/okitweb/__init__.py +++ b/okitweb/__init__.py @@ -26,7 +26,7 @@ def create_local_app(test_config=None): # Create and Configure OKIT Web Designer App - app = Flask(__name__, instance_relative_config=True) + app = Flask(__name__, instance_relative_config=True, instance_path='/okit/instance') # Load Config if test_config is None: @@ -68,7 +68,7 @@ def favicon(): def create_authenticated_app(test_config=None): # Create and Configure OKIT Web Designer App - app = Flask(__name__, instance_relative_config=True) + app = Flask(__name__, instance_relative_config=True, instance_path='/okit/instance') # Load Config if test_config is None: diff --git a/okitweb/okitWebDesigner.py b/okitweb/okitWebDesigner.py index f61451d57..6cae9ed67 100644 --- a/okitweb/okitWebDesigner.py +++ b/okitweb/okitWebDesigner.py @@ -12,6 +12,7 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# import configparser +import functools import oci import os import shutil @@ -77,7 +78,7 @@ def readConfigFileSections(config_file='~/.oci/config'): config_sections = ['Instance Principal'] return config_sections -def readConfigFileSettings(config_file='~/.oci/git_repositories'): +def readGitConfigFile(config_file='~/.oci/git_repositories'): logger.debug('Setting File {0!s:s}'.format(config_file)) abs_config_file = os.path.expanduser(config_file) logger.debug('Setting File {0!s:s}'.format(abs_config_file)) @@ -86,6 +87,7 @@ def readConfigFileSettings(config_file='~/.oci/git_repositories'): repo_list = [] for each_git_section in config.sections(): repo_list.append({'label': each_git_section, 'branch': config[each_git_section]['branch'], 'url': config[each_git_section]['url']}) + logger.info(repo_list) return repo_list def getConfigFileValue(section, key, config_file='~/.oci/config'): @@ -159,6 +161,14 @@ def designer(): cd3_mode = (request.args.get('cd3', default='false') == 'true') if cd3_mode: logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<< CD3 Mode >>>>>>>>>>>>>>>>>>>>>>>>>>") + # Test if PCA mode + pca_mode = (request.args.get('pca', default='false') == 'true') + if pca_mode: + logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<< PCA Mode >>>>>>>>>>>>>>>>>>>>>>>>>>") + # Test if A2C mode + a2c_mode = (request.args.get('a2c', default='false') == 'true') + if a2c_mode: + logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<< A2C Mode >>>>>>>>>>>>>>>>>>>>>>>>>>") # Read Artifact Model Specific JavaScript Files artefact_model_js_files = sorted(os.listdir(os.path.join(bp.static_folder, 'model', 'js', 'artefacts'))) # Read Artifact View Specific JavaScript Files @@ -197,56 +207,6 @@ def designer(): logger.debug('Palette Icon Groups : {0!s:s}'.format(palette_icon_groups)) logJson(palette_icon_groups) - # Read Fragment Files - fragment_files = os.listdir(os.path.join(bp.static_folder, 'fragments', 'svg')) - fragment_icons = [] - for fragment_svg in sorted(fragment_files): - logger.debug('Fragment : {0!s:s}'.format(fragment_svg)) - logger.debug('Fragment full : {0!s:s}'.format(os.path.join(bp.static_folder, 'fragments', 'svg', fragment_svg))) - fragment_icon = {'svg': fragment_svg, 'title': os.path.basename(fragment_svg).split('.')[0].replace('_', ' ').title()} - logger.debug('Icon : {0!s:s}'.format(fragment_icon)) - fragment_icons.append(fragment_icon) - - # Walk Template directory Structure - template_files = [] - template_dirs = {} - logger.debug('Walking the template directories') - rootdir = os.path.join(bp.static_folder, 'templates') - for (dirpath, dirnames, filenames) in os.walk(rootdir, followlinks=True): - logger.debug('dirpath : {0!s:s}'.format(dirpath)) - logger.debug('dirnames : {0!s:s}'.format(dirnames)) - logger.debug('filenames : {0!s:s}'.format(filenames)) - relpath = os.path.relpath(dirpath, rootdir) - logger.debug('Relative Path : {0!s:s}'.format(relpath)) - template_files.extend([os.path.join(relpath, f) for f in filenames if f.endswith(".json")]) - template_dirs[relpath] = [f for f in filenames if f.endswith(".json")] - logger.debug('Files Walk : {0!s:s}'.format(template_files)) - logger.debug('Template Dirs {0!s:s}'.format(template_dirs)) - - template_groups = [] - for key in sorted(template_dirs.keys()): - template_group = {'id': '{0!s:s}_template_menu_group'.format(str(key)), 'name': str(key).replace('_', ' ').title(), 'templates': [], 'directories': {}} - for template_file in sorted(template_dirs[key]): - try: - okit_template = {'json': os.path.join(key, template_file), 'id': template_file.replace('.', '_')} - filename = os.path.join(bp.static_folder, 'templates', key, template_file) - template_json = readJsonFile(filename) - logger.debug('Template Json : {0!s:s}'.format(template_json)) - okit_template['title'] = template_json['title'] - okit_template['description'] = template_json.get('description', template_json['title']) - template_group['templates'].append(okit_template) - except Exception as e: - logger.debug(e) - template_groups.append(template_group) - logger.debug(f'Template Groups : {jsonToFormattedString(template_groups)}') - - template_categories = {} - for key in sorted(template_dirs.keys()): - name = str(key.split('/')[0]).replace('_', ' ').title() - path = key - category = template_categories.get(name, {'path': path, 'name': '', 'templates': [], 'children': {}}) - template_categories[name] = build_categories(path, key, category, sorted(template_dirs[key])) - logger.debug(f'Categories : {jsonToFormattedString(template_categories)}') config_sections = {"sections": readConfigFileSections()} @@ -256,204 +216,186 @@ def designer(): artefact_view_js_files=artefact_view_js_files, palette_json=palette_json, palette_icon_groups=palette_icon_groups, - fragment_icons=fragment_icons, - okit_templates_groups=template_groups, - okit_template_categories=template_categories, + # fragment_icons=fragment_icons, + # okit_templates_groups=template_groups, + # okit_template_categories=template_categories, local_okit=local, - developer_mode=developer_mode, experimental_mode=experimental_mode, cd3_mode=cd3_mode) - - -@bp.route('/console', methods=(['GET'])) -def console(): - local = current_app.config.get('LOCAL', False) - if not local and session.get('username', None) is None: - logger.info('<<<<<<<<<<<<<<<<<<<<<<<<< Redirect to Login >>>>>>>>>>>>>>>>>>>>>>>>>') - return redirect(url_for('okit.login'), code=302) - # Test if developer mode - developer_mode = (request.args.get('developer', default='false') == 'true') - if developer_mode: - logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<< Developer Mode >>>>>>>>>>>>>>>>>>>>>>>>>>") - # Test if experimental mode - experimental_mode = (request.args.get('experimental', default='false') == 'true') - if experimental_mode: - logger.info("<<<<<<<<<<<<<<<<<<<<<<<<<< Experimental Mode >>>>>>>>>>>>>>>>>>>>>>>>>>") - # Read Artifact Model Specific JavaScript Files - artefact_model_js_files = sorted(os.listdir(os.path.join(bp.static_folder, 'model', 'js', 'artefacts'))) - # Read Artifact View Specific JavaScript Files - if os.path.exists(os.path.join(bp.static_folder, 'view', 'js', 'artefacts')) and os.path.isdir(os.path.join(bp.static_folder, 'view', 'js', 'artefacts')): - artefact_view_js_files = sorted(os.listdir(os.path.join(bp.static_folder, 'view', 'js', 'artefacts'))) - else: - artefact_view_js_files = [] - artefact_view_js_files.extend(sorted(os.listdir(os.path.join(bp.static_folder, 'view', 'designer', 'js', 'artefacts')))) - - # Get Palette Icon Groups / Icons - svg_files = [] - svg_icon_groups = {} - # Read Files - for (dirpath, dirnames, filenames) in os.walk(os.path.join(bp.static_folder, 'palette')): - logger.debug('dirpath : {0!s:s}'.format(dirpath)) - logger.debug('dirnames : {0!s:s}'.format(dirnames)) - logger.debug('filenames : {0!s:s}'.format(filenames)) - if os.path.basename(dirpath) != 'palette': - svg_files.extend([os.path.join(os.path.basename(dirpath), f) for f in filenames if f.endswith(".svg")]) - svg_icon_groups[os.path.basename(dirpath)] = [f for f in filenames if f.endswith(".svg")] - else: - svg_files.extend([f for f in filenames if f.endswith(".svg")]) - logger.debug('Files Walk : {0!s:s}'.format(svg_files)) - logger.debug('SVG Icon Groups {0!s:s}'.format(svg_icon_groups)) - - palette_icon_groups = [] - for key in sorted(svg_icon_groups.keys()): - palette_icon_group = {'name': str(key).title(), 'icons': []} - for palette_svg in sorted(svg_icon_groups[key]): - palette_icon = {'svg': os.path.join(key, palette_svg), 'title': os.path.basename(palette_svg).split('.')[0].replace('_', ' ')} - palette_icon_group['icons'].append(palette_icon) - palette_icon_groups.append(palette_icon_group) - logger.debug('Palette Icon Groups : {0!s:s}'.format(palette_icon_groups)) - logJson(palette_icon_groups) - - # Read Fragment Files - fragment_files = os.listdir(os.path.join(bp.static_folder, 'fragments', 'svg')) - fragment_icons = [] - for fragment_svg in sorted(fragment_files): - logger.debug('Fragment : {0!s:s}'.format(fragment_svg)) - logger.debug('Fragment full : {0!s:s}'.format(os.path.join(bp.static_folder, 'fragments', 'svg', fragment_svg))) - fragment_icon = {'svg': fragment_svg, 'title': os.path.basename(fragment_svg).split('.')[0].replace('_', ' ').title()} - logger.debug('Icon : {0!s:s}'.format(fragment_icon)) - fragment_icons.append(fragment_icon) - - # Walk Template directory Structure - template_files = [] - template_dirs = {} - logger.debug('Walking the template directories') - rootdir = os.path.join(bp.static_folder, 'templates') - for (dirpath, dirnames, filenames) in os.walk(rootdir, followlinks=True): - logger.debug('dirpath : {0!s:s}'.format(dirpath)) - logger.debug('dirnames : {0!s:s}'.format(dirnames)) - logger.debug('filenames : {0!s:s}'.format(filenames)) - relpath = os.path.relpath(dirpath, rootdir) - logger.debug('Relative Path : {0!s:s}'.format(relpath)) - template_files.extend([os.path.join(relpath, f) for f in filenames if f.endswith(".json")]) - template_dirs[relpath] = [f for f in filenames if f.endswith(".json")] - logger.debug('Files Walk : {0!s:s}'.format(template_files)) - logger.debug('Template Dirs {0!s:s}'.format(template_dirs)) - - template_groups = [] - for key in sorted(template_dirs.keys()): - template_group = {'name': str(key).replace('_', ' ').title(), 'templates': [], 'directories': {}} - for template_file in sorted(template_dirs[key]): - try: - okit_template = {'json': os.path.join(key, template_file), 'id': template_file.replace('.', '_')} - filename = os.path.join(bp.static_folder, 'templates', key, template_file) - template_json = readJsonFile(filename) - logger.debug('Template Json : {0!s:s}'.format(template_json)) - okit_template['title'] = template_json['title'] - okit_template['description'] = template_json.get('description', template_json['title']) - template_group['templates'].append(okit_template) - except Exception as e: - logger.debug(e) - template_groups.append(template_group) - logger.debug('Template Groups {0!s:s}'.format(template_groups)) - logJson(template_groups) - - template_categories = {} - for key in sorted(template_dirs.keys()): - name = str(key.split('/')[0]).replace('_', ' ').title() - path = key - category = template_categories.get(name, {'path': path, 'name': '', 'templates': [], 'children': {}}) - template_categories[name] = build_categories(path, key, category, sorted(template_dirs[key])) - logger.debug('Categories {0!s:s}'.format(template_categories)) - logJson(template_categories) - - config_sections = {"sections": readConfigFileSections()} - logger.debug('Config Sections {0!s:s}'.format(config_sections)) - logger.debug(jsonToFormattedString(palette_icon_groups)) + developer_mode=developer_mode, experimental_mode=experimental_mode, cd3_mode=cd3_mode, a2c_mode=a2c_mode, pca_mode=pca_mode) + + +# Template Processing +@bp.route('/panel/templates', methods=(['GET'])) +def templates_panel(): + # ref_arch_root = os.path.join(bp.static_folder, 'templates', 'reference_architecture') + ref_arch_root = os.path.join(current_app.instance_path, 'templates', 'reference_architecture') + ref_arch_templates = dir_to_json(ref_arch_root, current_app.instance_path, 'children', 'templates') + # ref_arch_templates = dir_to_json(ref_arch_root, ref_arch_root, 'children', 'templates') + ref_arch_category = {'name': 'Reference Architectures', 'path': 'reference_architecture', 'children': [], 'templates': []} + ref_arch_category = hierarchy_category(ref_arch_category, ref_arch_templates, current_app.instance_path) + # user_root = os.path.join('okit', 'templates', 'user') + user_root = os.path.join(current_app.instance_path, 'templates', 'user') + user_templates = dir_to_json(user_root, current_app.instance_path, 'children', 'templates') + # user_templates = dir_to_json(user_root, user_root, 'children', 'templates') + user_category = {'name': 'User', 'path': 'user', 'children': [], 'templates': []} + user_category = hierarchy_category(user_category, user_templates, current_app.instance_path) + template_categories = [ref_arch_category, user_category] + logger.debug(f'Template Categories : {jsonToFormattedString(template_categories)}') #Render The Template - return render_template('okit/console.html', - artefact_model_js_files=artefact_model_js_files, - artefact_view_js_files=artefact_view_js_files, - palette_icon_groups=palette_icon_groups, - fragment_icons=fragment_icons, - okit_templates_groups=template_groups, - okit_template_categories=template_categories, - local_okit=local, - developer_mode=developer_mode, experimental_mode=experimental_mode) + return render_template('okit/templates_panel.html', template_categories=template_categories) + +def dir_to_json(rootdir, reltodir=None, dkey='dirs', fkey='files'): + # logger.info(f'Root Path: {rootdir}') + # logger.info(f'Relative to Path: {reltodir}') + # logger.info(f'Relative Path: {os.path.relpath(rootdir, reltodir)}') + hierarchy = { + 'id': os.path.relpath(rootdir, reltodir).replace('/','_'), + 'name': os.path.basename(rootdir), + 'path': rootdir + } + hierarchy[dkey] = [] + hierarchy[fkey] = [] + if reltodir is not None: + hierarchy['path'] = os.path.relpath(rootdir, reltodir) + + with os.scandir(rootdir) as it: + for entry in it: + if not entry.name.startswith('.'): + if entry.name.endswith('.json') and entry.is_file(): + # hierarchy[fkey].append(entry.name) + hierarchy[fkey].append({'id': entry.name.replace('.','_'), 'name': entry.name, 'json': entry.name, 'path': hierarchy['path']}) + elif entry.is_dir(): + hierarchy[dkey].append(dir_to_json(os.path.join(rootdir, entry.name), reltodir, dkey, fkey)) + + logger.debug(f'Directory Hierarchy : {jsonToFormattedString(hierarchy)}') + return hierarchy + +def hierarchy_category(category, hierarchy, root=''): + logger.debug(f'Category : {jsonToFormattedString(category)}') + logger.debug(f'Hierarchy : {jsonToFormattedString(hierarchy)}') + logger.debug(f'Root : {root}') + for template in hierarchy['templates']: + path = hierarchy['path'] if hierarchy['path'] != '.' else '' + category['templates'].append(get_template_entry(root, path, template['json'])) + for child in hierarchy['children']: + category['children'].append(hierarchy_category({"name": os.path.basename(child["path"]).replace("_", " ").title(), "path": child["path"], "id": child["id"], "children": [], "templates": []}, child, root)) + return category +def get_template_entry(root, path, json_file): + # json_file = os.path.join(path, template_file) + okit_template = {'path': path, 'json': json_file, 'id': json_file.replace('.', '_').replace('/', '_')} + try: + filename = os.path.join(root, okit_template['path'], okit_template['json']) + template_json = readJsonFile(filename) + logger.debug('Template Json : {0!s:s}'.format(template_json)) + okit_template['name'] = template_json['title'] + okit_template['description'] = template_json.get('description', template_json['title']) + okit_template['description'] = template_json['title'] + except Exception as e: + logger.debug(e) + return okit_template + + +@bp.route('/templates/load', methods=(['GET'])) +def templates(): + if request.method == 'GET': + query_string = request.query_string + parsed_query_string = urllib.parse.unquote(query_string.decode()) + query_json = json.loads(parsed_query_string) + templates_root = os.path.join(current_app.instance_path, query_json['root_dir'].strip('/')) + templates = dir_to_json(templates_root, current_app.instance_path) + logger.debug(f'Templates : {jsonToFormattedString(templates)}') + return templates -def build_categories(path, key, category, templates): - category['name'] = str(key.split('/')[0]).replace('_', ' ').title() - if len(key.split('/')) > 1: - child_key = '/'.join(key.split('/')[1:]) - child_category = category['children'].get(str(child_key.split('/')[0]).replace('_', ' ').title(), {'path': path, 'name': '', 'templates': [], 'children': {}}) - build_categories(path, child_key, child_category, templates) - category['children'][str(child_key.split('/')[0]).replace('_', ' ').title()] = child_category - else: - category['templates'] = templates - category['templates'] = [] - for template_file in sorted(templates): - try: - json_file = os.path.join(category['path'], template_file) - okit_template = {'json': json_file, 'id': json_file.replace('.', '_').replace('/', '_')} - filename = os.path.join(bp.static_folder, 'templates', okit_template['json']) - template_json = readJsonFile(filename) - logger.debug('Template Json : {0!s:s}'.format(template_json)) - okit_template['title'] = template_json['title'] - okit_template['description'] = template_json.get('description', template_json['title']) - category['templates'].append(okit_template) - except Exception as e: - logger.debug(e) - logger.debug(category) - return category + +@bp.route('/template/load', methods=(['GET'])) +def template_load(): + if request.method == 'GET': + query_string = request.query_string + parsed_query_string = urllib.parse.unquote(query_string.decode()) + query_json = json.loads(parsed_query_string) + template_file = query_json['template_file'] + return send_from_directory(current_app.instance_path, template_file, mimetype='application/json', as_attachment=False) -@bp.route('/templates/', methods=(['GET'])) -def templates(root): - # Walk Template directory Structure - template_files = [] - template_dirs = {} - logger.info(f'Walking the template directories {root}') - rootdir = os.path.join(bp.static_folder, 'templates', root) - logger.info(f'Root Dir : {rootdir}') - for (dirpath, dirnames, filenames) in os.walk(rootdir, followlinks=True): - logger.info('dirpath : {0!s:s}'.format(dirpath)) - logger.info('dirnames : {0!s:s}'.format(dirnames)) - logger.info('filenames : {0!s:s}'.format(filenames)) - relpath = os.path.relpath(dirpath, rootdir) - logger.info('Relative Path : {0!s:s}'.format(relpath)) - template_files.extend([os.path.join(relpath, f) for f in filenames if f.endswith(".json")]) - template_dirs[relpath] = [f for f in filenames if f.endswith(".json")] - logger.debug('Files Walk : {0!s:s}'.format(template_files)) - logger.debug('Template Dirs {0!s:s}'.format(template_dirs)) - - template_groups = [] - for key in sorted(template_dirs.keys()): - name = root if key == '.' else key - template_group = {'id': '{0!s:s}_template_menu_group'.format(str(name)), 'name': str(name).replace('_', ' ').title(), 'templates': [], 'directories': {}} - for template_file in sorted(template_dirs[key]): +@bp.route('/template/save', methods=(['POST'])) +def template_save(): + if request.method == 'POST': + instance_path = current_app.instance_path + root_dir = request.json["root_dir"].strip('/') + template_filename = request.json["template_file"].strip('/') + okit_json = request.json["okit_json"] + git = request.json.get('git', False) + git_commit_msg = request.json.get('git_commit_msg', '') + logger.info(f'Save Template : {root_dir}') + + template_dir = os.path.dirname(template_filename) + full_dir = os.path.join(instance_path, root_dir, template_dir) + full_filename = os.path.join(full_dir, os.path.basename(template_filename)) + full_filename = os.path.join(instance_path, root_dir, template_filename) + if not os.path.exists(full_dir): + os.makedirs(full_dir, exist_ok=True) + writeJsonFile(okit_json, full_filename) + if git: + top_dir = os.path.normpath(os.path.dirname(template_filename)).split(os.sep) + git_repo_dir = os.path.join(instance_path, root_dir, top_dir[0], top_dir[1]) + # while top_dir != '': + # git_repo_dir = os.path.join(instance_path, root_dir, top_dir) + # logger.info(f'Top Dir : {top_dir}') + # top_dir = os.path.dirname(top_dir) + logger.info(f'Git Root Dir : {git_repo_dir}') + repo = Repo(git_repo_dir) + repo.index.add(full_filename) + repo.index.commit("commit changes from okit:" + git_commit_msg) + repo.remotes.origin.pull() + repo.remotes.origin.push() + return template_filename + + +# Git Processing +@bp.route('/panel/git', methods=(['GET'])) +def git_panel(): + if request.method == 'GET': + repositories = readGitConfigFile() + git_resources = {} + for repo in repositories: + logger.debug(f'Repo: {jsonToFormattedString(repo)}') + label = repo['label'] + branch = repo['branch'] + url = repo['url'] + parsed_url = giturlparse.parse(url) + logger.debug(f'Parsed Url: {parsed_url}') + git_resource_dir = os.path.join(current_app.instance_path, 'git', parsed_url.resource) + git_repo_dir = os.path.join(git_resource_dir, parsed_url.name) try: - okit_template = {'json': os.path.join(name, template_file), 'id': template_file.replace('.', '_')} - filename = os.path.join(bp.static_folder, 'templates', name, template_file) - template_json = readJsonFile(filename) - logger.debug('Template Json : {0!s:s}'.format(template_json)) - okit_template['title'] = template_json['title'] - okit_template['description'] = template_json.get('description', template_json['title']) - template_group['templates'].append(okit_template) + if os.path.exists(git_repo_dir): + repo = Repo(git_repo_dir) + repo.remotes.origin.pull() + else: + repo = Repo.clone_from(url, git_repo_dir, branch=branch, no_single_branch=True) + repo.remotes.origin.pull() except Exception as e: - logger.debug(e) - template_groups.append(template_group) - logger.debug(f'Template Groups : {jsonToFormattedString(template_groups)}') - - template_categories = {} - # for key in sorted(template_dirs.keys()): - # name = str(key.split('/')[0]).replace('_', ' ').title() - # path = key - # category = template_categories.get(name, {'path': path, 'name': '', 'templates': [], 'children': {}}) - # template_categories[name] = build_categories(path, key, category, sorted(template_dirs[key])) - logger.debug(f'Categories : {jsonToFormattedString(template_categories)}') - - #Render The Template - return render_template('okit/templates_menu.html', templates=template_groups, okit_templates_groups=template_groups, okit_template_categories=template_categories) + logger.exception(e) + git_resources[parsed_url.resource] = git_resource_dir + git_repositories = [] + for git_resource, git_resource_dir in git_resources.items(): + repo_templates = dir_to_json(git_resource_dir, current_app.instance_path, 'children', 'templates') + repository = {'name': git_resource, 'path': git_resource_dir, 'children': [], 'templates': []} + git_repositories.append(repo_templates) + #Render The Template + logger.debug(f'Repository: {jsonToFormattedString(git_repositories)}') + return render_template('okit/git_repositories_panel.html', git_repositories=git_repositories) + + +# Local Filesystem Processing +@bp.route('/panel/local', methods=(['GET'])) +def local_panel(): + if request.method == 'GET': + local_filesystem_dir = os.path.join(current_app.instance_path, 'local') + local_filesystem = [dir_to_json(local_filesystem_dir, current_app.instance_path, 'children', 'templates')] + #Render The Template + logger.debug(f'Local Filesystem: {jsonToFormattedString(local_filesystem)}') + return render_template('okit/local_panel.html', local_filesystem=local_filesystem) @bp.route('/propertysheets/', methods=(['GET'])) @@ -523,6 +465,7 @@ def generate(language, destination): return send_from_directory('/tmp', "okit-{0:s}.zip".format(str(language)), mimetype='application/zip', as_attachment=True) +# TODO: Delete @bp.route('/saveas/', methods=(['POST'])) def saveas(savetype): logger.info('Save Type : {0:s} - {1:s}'.format(str(savetype), str(request.method))) @@ -619,7 +562,7 @@ def configSections(): @bp.route('config/appsettings', methods=(['GET'])) def appSettings(): if request.method == 'GET': - config_settings = {"gitsections": readConfigFileSettings()} + config_settings = {"gitsections": readGitConfigFile()} logger.info('Config Settings {0!s:s}'.format(config_settings)) return config_settings else: @@ -654,6 +597,7 @@ def validateJson(): else: return '404' +# TODO: Delete @bp.route('loadfromgit', methods=(['POST'])) def loadfromgit(): logger.debug('JSON : {0:s}'.format(str(request.json))) diff --git a/okitweb/package.json b/okitweb/package.json index 654cd13ee..4821ab005 100644 --- a/okitweb/package.json +++ b/okitweb/package.json @@ -1,6 +1,6 @@ { "name": "okitweb", - "version": "0.24.5", + "version": "0.25.0", "description": "OKIT Web application", "scripts": {}, "author": "Oracle", diff --git a/okitweb/static/okit/css/okit_designer.css b/okitweb/static/okit/css/okit_designer.css index 17edd054e..b2733740e 100644 --- a/okitweb/static/okit/css/okit_designer.css +++ b/okitweb/static/okit/css/okit_designer.css @@ -96,6 +96,11 @@ .okit-palette-icon-group-icons {} +.okit-icons-only { + display: grid; + grid-template-columns: auto auto auto; +} + .okit-icons-only > div { display: initial; } diff --git a/okitweb/static/okit/css/okit_panels.css b/okitweb/static/okit/css/okit_panels.css new file mode 100644 index 000000000..fa6095290 --- /dev/null +++ b/okitweb/static/okit/css/okit_panels.css @@ -0,0 +1,97 @@ +/* +# Copyright (c) 2020, 2021, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + */ + + .okit-template-panel { + font-size: small; + overflow: scroll; + width: 100%; + height: 100%; + white-space: nowrap; +} + +.okit-template-panel > details { + width: 180px; +} + +.okit-template-panel > details > summary { + text-transform: uppercase; + border-bottom: 1px solid black; +} + +.okit-template-panel ul { + list-style: none; + display: block; + padding-left: 0.2em; + margin: 0; +} + +.okit-template-panel li > ul { + padding-left: 0.5em; +} + +.okit-template-panel li { + padding-left: 0.5em; + margin-top: 2px; +} + +.okit-template-panel li > div { + display: inline-block; + margin-left: 0; + padding-left: 0; + background-repeat: no-repeat; + background-position: left; + background-position-x: 0.75em; + background-size: 1.25em 1.25em; + white-space: nowrap; +} + +.okit-template-panel li label { + clear: both; + text-wrap: none; + white-space: nowrap; +} + +.okit-template-panel input[type=checkbox] {display: none; visibility: hidden;} + +.okit-template-panel li > label::before { + content: '\25B9'; + color: #336600; + display: inline-block; + text-align: left; + padding-right: 0.5em; +} + +.okit-template-panel li > input:checked ~ label::before { + content: '\25BF'; +} + +.okit-template-group ul {display: none} + +.okit-template-group input:checked ~ ul {display: block} + +.okit-template-panel li a.okit-template-item { + text-decoration: none; + font-weight: normal; + color: black; + cursor: default; +} + +.okit-template-panel li a.okit-template-item:hover { + color: #364150; + font-weight: 700; + font-size: 14px; +} + +.okit-template-panel li.okit-template-group { + font-weight: bolder; +} + +.okit-template-category-summary { + text-overflow: ellipsis; +} + +.okit-template-category-summary > label { + padding-left: 5px; +} diff --git a/okitweb/static/okit/css/theme.css b/okitweb/static/okit/css/theme.css index 6b3a680cc..b142d7d3a 100644 --- a/okitweb/static/okit/css/theme.css +++ b/okitweb/static/okit/css/theme.css @@ -155,7 +155,7 @@ } .lpg { - background-image: url("/static/okit/palette/gateways/Local_Peering_Gateway.svg"); + background-image: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJwcmVmaXhfX0xheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMCIgeT0iMCIgdmlld0JveD0iMCAwIDQyIDEzLjYyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnByZWZpeF9fc3Qwe2ZpbGw6I2ZmZn0ucHJlZml4X19zdDF7ZmlsbDojMmM1OTY3fTwvc3R5bGU+PHBhdGggY2xhc3M9InByZWZpeF9fc3QwIiBkPSJNMzUuMTkgMTMuMTJjLTMuMDYgMC01LjY1LTIuMTgtNi4yLTUuMTVIMTMuMDFBNi4zMDcgNi4zMDcgMCAwMS41IDYuODFDLjUgMy4zMyAzLjMzLjUgNi44MS41YzMuMDYgMCA1LjY1IDIuMTggNi4yIDUuMTVIMjlhNi4zMDcgNi4zMDcgMCAwMTEyLjUxIDEuMTZjMCAzLjQ4LTIuODQgNi4zMS02LjMyIDYuMzF6Ii8+PHBhdGggY2xhc3M9InByZWZpeF9fc3QwIiBkPSJNMzUuMTkgMUMzOC4zOSAxIDQxIDMuNjEgNDEgNi44MXMtMi42MSA1LjgxLTUuODEgNS44MWMtMi45OCAwLTUuNDQtMi4yNi01Ljc3LTUuMTVIMTIuNThjLS4zMyAyLjg5LTIuNzkgNS4xNS01Ljc3IDUuMTUtMy4yIDAtNS44MS0yLjYxLTUuODEtNS44MVMzLjYxIDEgNi44MSAxYzIuOTggMCA1LjQ0IDIuMjYgNS43NyA1LjE1aDE2Ljg1QTUuODAzIDUuODAzIDAgMDEzNS4xOSAxbTAtMWMtMy4xNiAwLTUuODUgMi4xNi02LjYgNS4xNUgxMy40MUE2LjgwNCA2LjgwNCAwIDAwNi44MSAwQzMuMDUgMCAwIDMuMDUgMCA2LjgxczMuMDUgNi44MSA2LjgxIDYuODFjMy4xNiAwIDUuODUtMi4xNiA2LjYtNS4xNWgxNS4xOGE2LjgwNCA2LjgwNCAwIDAwNi42IDUuMTVjMy43NSAwIDYuODEtMy4wNSA2LjgxLTYuODFTMzguOTUgMCAzNS4xOSAweiIvPjxnPjxwYXRoIGNsYXNzPSJwcmVmaXhfX3N0MSIgZD0iTTI4LjQzIDYuMTVIMTMuNTdhNy4yODYgNy4yODYgMCAwMTAgMS4zMmgxNC44NmE3LjI4NiA3LjI4NiAwIDAxMC0xLjMyek0xMi42MiA2LjgxYzAtMy4yLTIuNjEtNS44MS01LjgxLTUuODFTMSAzLjYxIDEgNi44MXMyLjYxIDUuODEgNS44MSA1LjgxIDUuODEtMi42MSA1LjgxLTUuODF6TTYuODEgMTEuM2E0LjQ5IDQuNDkgMCAxMTAtOC45OCA0LjQ5IDQuNDkgMCAwMTAgOC45OHpNMzUuMTkgMWMtMy4yIDAtNS44MSAyLjYxLTUuODEgNS44MXMyLjYxIDUuODEgNS44MSA1LjgxUzQxIDEwLjAxIDQxIDYuODEgMzguMzkgMSAzNS4xOSAxem0wIDEwLjNhNC40OSA0LjQ5IDAgMTEwLTguOTggNC40OSA0LjQ5IDAgMDEwIDguOTh6Ii8+PHBhdGggY2xhc3M9InByZWZpeF9fc3QxIiBkPSJNNy40NiA0LjY0bC0uOTMtLjk0LTMuMSAzLjEgMy4xIDMuMS45My0uOTMtMS41MS0xLjVoNC4zYy4wNC0uMjEuMDYtLjQzLjA2LS42NnMtLjAyLS40NS0uMDYtLjY2aC00LjNsMS41MS0xLjUxek0zNC4zOCA4Ljk4bC45My45MyAzLjEtMy4xLTMuMS0zLjEtLjkzLjkzIDEuNTEgMS41MWgtNC4xM2MtLjA0LjIxLS4wNi40My0uMDYuNjZzLjAyLjQ1LjA2LjY2aDQuMTNsLTEuNTEgMS41MXoiLz48L2c+PC9zdmc+"); } .mysql-database-system { @@ -178,11 +178,15 @@ background-image: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJwcmVmaXhfX0xheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMCIgeT0iMCIgdmlld0JveD0iMCAwIDQyIDQyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnByZWZpeF9fc3Qwe2ZpbGw6I2ZmZn0ucHJlZml4X19zdDF7ZmlsbDojMmM1OTY3fTwvc3R5bGU+PHBhdGggY2xhc3M9InByZWZpeF9fc3QwIiBkPSJNMi42NyA0MS41QzEuNDggNDEuNS41IDQwLjUzLjUgMzkuMzNWMi42N0MuNSAxLjQ3IDEuNDcuNSAyLjY3LjVoMzYuNjdjMS4xOSAwIDIuMTcuOTcgMi4xNyAyLjE3djM2LjY3YzAgMS4yLS45NyAyLjE3LTIuMTcgMi4xN0gyLjY3eiIvPjxwYXRoIGNsYXNzPSJwcmVmaXhfX3N0MCIgZD0iTTM5LjMzIDFjLjkyIDAgMS42Ny43NSAxLjY3IDEuNjd2MzYuNjdjMCAuOTItLjc1IDEuNjctMS42NyAxLjY3SDIuNjdDMS43NSA0MSAxIDQwLjI1IDEgMzkuMzNWMi42N0MxIDEuNzUgMS43NSAxIDIuNjcgMWgzNi42Nm0wLTFIMi42N0MxLjIgMCAwIDEuMiAwIDIuNjd2MzYuNjdDMCA0MC44IDEuMiA0MiAyLjY3IDQyaDM2LjY3QzQwLjggNDIgNDIgNDAuOCA0MiAzOS4zM1YyLjY3QzQyIDEuMiA0MC44IDAgMzkuMzMgMHoiLz48cGF0aCBjbGFzcz0icHJlZml4X19zdDEiIGQ9Ik0zMC40NCAxMS42NWwyLjcxIDQuNzItMi43NCA0LjcyaC01LjQ1bC0yLjcxLTQuNzIgMi43NC00LjcyaDUuNDVtLjg2LTEuNDhoLTcuMTZsLTMuNTkgNi4yIDMuNTcgNi4yaDcuMTZsMy41OS02LjItMy41Ny02LjJ6TTIwLjcyIDI1Ljk0YTQuNjkgNC42OSAwIDExLTQuNjkgNC42OWMuMDEtMi41OCAyLjExLTQuNjkgNC42OS00LjY5bTAtMS40OGE2LjE2IDYuMTYgMCAwMC02LjE3IDYuMTdjMCAzLjQyIDIuNzkgNi4xNyA2LjE3IDYuMTcgMy4zOSAwIDYuMTctMi43NiA2LjE3LTYuMTcuMDEtMy40MS0yLjc1LTYuMTctNi4xNy02LjE3ek0xMy42IDEyLjgzbDQuMzggNy45M0g5LjI2bDQuMzQtNy45M20tLjAxLTMuMDhMNi43NiAyMi4yNGgxMy43MkwxMy41OSA5Ljc1eiIvPjxnPjxwYXRoIGNsYXNzPSJwcmVmaXhfX3N0MSIgZD0iTTM5LjMzIDQxSDIuNjdDMS43NSA0MSAxIDQwLjI1IDEgMzkuMzNWNC45OWg0MHYzNC4zNWMwIC45MS0uNzUgMS42Ni0xLjY3IDEuNjZ6TTIuNDggNi40N3YzMi44N2MwIC4xLjA4LjE5LjE5LjE5aDM2LjY3Yy4xIDAgLjE5LS4wOC4xOS0uMTlWNi40N0gyLjQ4eiIvPjwvZz48Zz48cGF0aCBjbGFzcz0icHJlZml4X19zdDEiIGQ9Ik00MSAzLjg3SDF2LTEuMkMxIDEuNzUgMS43NSAxIDIuNjcgMWgzNi42N2MuOTEgMCAxLjY2Ljc1IDEuNjYgMS42N3YxLjJ6Ii8+PC9nPjwvc3ZnPg=="); } +.remote-peering-gateway { + background-image: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJwcmVmaXhfX0xheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMCIgeT0iMCIgdmlld0JveD0iMCAwIDQyIDE3LjQxIiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3R5bGU+LnByZWZpeF9fc3Qwe2ZpbGw6I2ZmZn0ucHJlZml4X19zdDF7ZmlsbDojMmM1OTY3fTwvc3R5bGU+PHBhdGggY2xhc3M9InByZWZpeF9fc3QwIiBkPSJNMzAuMTYgNi4xYTE0LjU3MiAxNC41NzIgMCAwMC05LjIzLTMuMjljLTMuMzQgMC02LjU5IDEuMTQtOS4xNiAzLjIybC0uMzQuMjctLjMyLS4zYy0uMy0uMjgtLjYzLS41NC0uOTgtLjc1bC0uNTgtLjM1LjUzLS40NGMuNDItLjM1Ljg0LS42NyAxLjI2LS45NkMxNC4xNyAxLjU0IDE3LjQ4LjUxIDIwLjkzLjUxYTE2LjcyNSAxNi43MjUgMCAwMTEwLjkxIDQuMDFsLjUxLjQ0LS41Ny4zN2MtLjM0LjIyLS42Ny40OC0uOTcuNzdsLS4zMS4yOS0uMzQtLjI5eiIvPjxwYXRoIGNsYXNzPSJwcmVmaXhfX3N0MCIgZD0iTTIwLjkyIDFjMy4zNSAwIDYuNTcgMSA5LjMxIDIuOS40NS4zMS44Ny42NCAxLjI5Ljk5LS4zNy4yNC0uNzIuNTItMS4wNC44My0yLjYzLTIuMTYtNS45Ny0zLjQtOS41NS0zLjQtMy41NSAwLTYuODYgMS4yMi05LjQ3IDMuMzMtLjMyLS4zLS42OC0uNTgtMS4wNi0uODEuMzktLjMzLjgtLjY0IDEuMjItLjkzQzE0LjM2IDIgMTcuNTggMSAyMC45MiAxbTAtMUExNy4yNDUgMTcuMjQ1IDAgMDA5Ljc1IDQuMDdsLTEuMDUuODkgMS4xNy43M2MuMzIuMi42Mi40My45LjdsLjY0LjYuNjgtLjU1YzIuNDgtMi4wMSA1LjYyLTMuMTEgOC44NC0zLjExIDMuMjYgMCA2LjQyIDEuMTMgOC45MiAzLjE3bC42OS41Ny42NC0uNjJjLjI3LS4yNi41Ny0uNS44OS0uNzFMMzMuMTkgNWwtMS4wMy0uODdjLS40NS0uMzgtLjkxLS43My0xLjM2LTEuMDVBMTcuMjIyIDE3LjIyMiAwIDAwMjAuOTIgMHoiLz48Y2lyY2xlIGNsYXNzPSJwcmVmaXhfX3N0MCIgY3g9IjYuODEiIGN5PSIxMC42IiByPSI2LjMxIi8+PHBhdGggY2xhc3M9InByZWZpeF9fc3QwIiBkPSJNNi44MSA0LjhjMy4yIDAgNS44MSAyLjYxIDUuODEgNS44MXMtMi42MSA1LjgxLTUuODEgNS44MUMzLjYxIDE2LjQxIDEgMTMuODEgMSAxMC42czIuNjEtNS44IDUuODEtNS44bTAtMUMzLjA1IDMuOCAwIDYuODUgMCAxMC42czMuMDUgNi44MSA2LjgxIDYuODFjMy43NSAwIDYuODEtMy4wNSA2LjgxLTYuODEgMC0zLjc1LTMuMDYtNi44LTYuODEtNi44eiIvPjxnPjxjaXJjbGUgY2xhc3M9InByZWZpeF9fc3QwIiBjeD0iMzUuMTkiIGN5PSIxMC42IiByPSI2LjMxIi8+PHBhdGggY2xhc3M9InByZWZpeF9fc3QwIiBkPSJNMzUuMTkgNC44YzMuMiAwIDUuODEgMi42IDUuODEgNS44cy0yLjYxIDUuODEtNS44MSA1LjgxLTUuODEtMi42MS01LjgxLTUuODEgMi42MS01LjggNS44MS01LjhtMC0xYy0zLjc1IDAtNi44MSAzLjA1LTYuODEgNi44MSAwIDMuNzUgMy4wNSA2LjgxIDYuODEgNi44MVM0MiAxNC4zNiA0MiAxMC42YzAtMy43NS0zLjA1LTYuOC02LjgxLTYuOHoiLz48L2c+PGc+PHBhdGggY2xhc3M9InByZWZpeF9fc3QxIiBkPSJNMzAuMjMgMy45QzI3LjQ5IDIgMjQuMjcgMSAyMC45MiAxcy02LjU3IDEtOS4zMSAyLjljLS40Mi4yOS0uODMuNjEtMS4yMi45My4zOC4yNC43My41MSAxLjA2LjgxIDIuNjEtMi4xMiA1LjkyLTMuMzMgOS40Ny0zLjMzIDMuNTggMCA2LjkyIDEuMjQgOS41NSAzLjQuMzItLjMxLjY3LS41OCAxLjA0LS44My0uNC0uMzQtLjgzLS42Ny0xLjI4LS45OHpNNi44MSA0LjhDMy42MSA0LjggMSA3LjQgMSAxMC42czIuNjEgNS44MSA1LjgxIDUuODEgNS44MS0yLjYxIDUuODEtNS44MS0yLjYxLTUuOC01LjgxLTUuOHptNC40OSA1LjhhNC40OSA0LjQ5IDAgMTEtOC45OCAwIDQuNDkgNC40OSAwIDAxOC45OCAwek0zNS4xOSA0LjhjLTMuMiAwLTUuODEgMi42MS01LjgxIDUuODFzMi42MSA1LjgxIDUuODEgNS44MWMzLjIgMCA1LjgxLTIuNjEgNS44MS01LjgxUzM4LjM5IDQuOCAzNS4xOSA0Ljh6bTAgMTAuMjlhNC40OSA0LjQ5IDAgMTEwLTguOTggNC40OSA0LjQ5IDAgMDEwIDguOTh6Ii8+PHBhdGggY2xhc3M9InByZWZpeF9fc3QxIiBkPSJNNS4xIDguODFsLTEuMTkuNTUgMS44MSAzLjg5IDMuODktMS44MS0uNTYtMS4xOS0xLjguODRDNy43NiA5Ljk2IDguNCA4LjkzIDkuMTQgOGMtLjMzLS4zLS43MS0uNTMtMS4xNC0uNjgtLjggMS4wMy0xLjQ4IDIuMTYtMi4wMiAzLjM3TDUuMSA4Ljgxek0zMi43NCAxMC4yNGwtLjU2IDEuMTkgMy44OSAxLjgxIDEuODEtMy44OS0xLjE5LS41Ni0uODUgMS44MmExNi41IDE2LjUgMCAwMC0xLjk2LTMuMjZjLS40MS4xNy0uNzkuNDEtMS4xLjcyLjcyLjkyIDEuMzQgMS45NCAxLjg0IDMuMDRsLTEuODgtLjg3eiIvPjwvZz48Zz48cGF0aCBjbGFzcz0icHJlZml4X19zdDEiIGQ9Ik0zMC4yMyAzLjlDMjcuNDkgMiAyNC4yNyAxIDIwLjkyIDFzLTYuNTcgMS05LjMxIDIuOWMtLjQyLjI5LS44My42MS0xLjIyLjkzLjM4LjI0LjczLjUxIDEuMDYuODEgMi42MS0yLjEyIDUuOTItMy4zMyA5LjQ3LTMuMzMgMy41OCAwIDYuOTIgMS4yNCA5LjU1IDMuNC4zMi0uMzEuNjctLjU4IDEuMDQtLjgzLS40LS4zNC0uODMtLjY3LTEuMjgtLjk4ek02LjgxIDQuOEMzLjYxIDQuOCAxIDcuNCAxIDEwLjZzMi42MSA1LjgxIDUuODEgNS44MSA1LjgxLTIuNjEgNS44MS01LjgxLTIuNjEtNS44LTUuODEtNS44em00LjQ5IDUuOGE0LjQ5IDQuNDkgMCAxMS04Ljk4IDAgNC40OSA0LjQ5IDAgMDE4Ljk4IDB6TTM1LjE5IDQuOGMtMy4yIDAtNS44MSAyLjYxLTUuODEgNS44MXMyLjYxIDUuODEgNS44MSA1LjgxYzMuMiAwIDUuODEtMi42MSA1LjgxLTUuODFTMzguMzkgNC44IDM1LjE5IDQuOHptMCAxMC4yOWE0LjQ5IDQuNDkgMCAxMTAtOC45OCA0LjQ5IDQuNDkgMCAwMTAgOC45OHoiLz48cGF0aCBjbGFzcz0icHJlZml4X19zdDEiIGQ9Ik01LjEgOC44MWwtMS4xOS41NSAxLjgxIDMuODkgMy44OS0xLjgxLS41Ni0xLjE5LTEuOC44NEM3Ljc2IDkuOTYgOC40IDguOTMgOS4xNCA4Yy0uMzMtLjMtLjcxLS41My0xLjE0LS42OC0uOCAxLjAzLTEuNDggMi4xNi0yLjAyIDMuMzdMNS4xIDguODF6TTMyLjc0IDEwLjI0bC0uNTYgMS4xOSAzLjg5IDEuODEgMS44MS0zLjg5LTEuMTktLjU2LS44NSAxLjgyYTE2LjUgMTYuNSAwIDAwLTEuOTYtMy4yNmMtLjQxLjE3LS43OS40MS0xLjEuNzIuNzIuOTIgMS4zNCAxLjk0IDEuODQgMy4wNGwtMS44OC0uODd6Ii8+PC9nPjwvc3ZnPg=="); +} + .route-table { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MiA0MiI+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTS41LjVoNDF2NDFILjV6Ii8+PHBhdGggZD0iTTQxIDF2NDBIMVYxaDQwbTEtMUgwdjQyaDQyVjB6Ii8+PC9nPjxnIGZpbGw9IiNiYjUwMWMiPjxwYXRoIGQ9Ik0xIDF2NDBoNDBWMUgxem0zOC41IDEyLjhIMTMuNlYyLjVoMjUuOXYxMS4zem0tMzcgMTQuNGg5LjZ2MTEuNEgyLjVWMjguMnptOS42LTI1Ljd2MTEuNEgyLjVWMi41aDkuNnptMCAxMi44djExLjRIMi41VjE1LjNoOS42em0xLjUgMjQuMlYyOC4yaDI1Ljl2MTEuNEgxMy42em0wLTEyLjhWMTUuM2gyNS45djExLjRIMTMuNnoiLz48cGF0aCBkPSJNMTUgMTIuM2gxLjdsLjktMS43LjkgMS43aDEuNmwtMS41LTIuN0wyMCA3LjFoLTEuNmwtLjggMS42LS45LTEuNmgtMS42bDEuNSAyLjV6TTIwLjkgMTEuMWgxLjR2MS4yaC0xLjR6TTI1IDEyLjNsLjktMS43LjkgMS43aDEuNmwtMS42LTIuNyAxLjUtMi41aC0xLjVsLS45IDEuNi0uOS0xLjZoLTEuNmwxLjUgMi41LTEuNSAyLjd6TTI4LjkgMTEuMWgxLjR2MS4yaC0xLjR6TTMyLjcgMTIuM2wuOS0xLjcgMSAxLjdoMS41bC0xLjUtMi43TDM2IDcuMWgtMS41bC0uOSAxLjYtLjktMS42aC0xLjVsMS40IDIuNS0xLjUgMi43ek0zNi42IDExLjFIMzh2MS4yaC0xLjR6TTIwIDIwLjFoLTEuNmwtLjggMS42LS45LTEuNmgtMS42bDEuNSAyLjUtMS42IDIuN2gxLjdsLjktMS43LjkgMS43aDEuNmwtMS41LTIuN3pNMjAuOSAyNGgxLjR2MS4yaC0xLjR6TTI4LjMgMjAuMWgtMS41bC0uOSAxLjYtLjktMS42aC0xLjZsMS41IDIuNS0xLjUgMi43SDI1bC45LTEuNy45IDEuN2gxLjZsLTEuNi0yLjd6TTI4LjkgMjRoMS40djEuMmgtMS40ek0zNiAyMC4xaC0xLjVsLS45IDEuNi0uOS0xLjZoLTEuNWwxLjQgMi41LTEuNSAyLjdoMS42bC45LTEuNyAxIDEuN2gxLjVsLTEuNS0yLjd6TTM2LjYgMjRIMzh2MS4yaC0xLjR6TTIwIDMyLjloLTEuNmwtLjggMS41LS45LTEuNWgtMS42bDEuNSAyLjRMMTUgMzhoMS43bC45LTEuNy45IDEuN2gxLjZsLTEuNS0yLjZ6TTIwLjkgMzYuOGgxLjRWMzhoLTEuNHpNMjguMyAzMi45aC0xLjVsLS45IDEuNS0uOS0xLjVoLTEuNmwxLjUgMi40LTEuNSAyLjdIMjVsLjktMS43LjkgMS43aDEuNmwtMS42LTIuNnpNMjguOSAzNi44aDEuNFYzOGgtMS40ek0zNiAzMi45aC0xLjVsLS45IDEuNS0uOS0xLjVoLTEuNWwxLjQgMi40LTEuNSAyLjdoMS42bC45LTEuNyAxIDEuN2gxLjVsLTEuNS0yLjZ6TTM2LjYgMzYuOEgzOFYzOGgtMS40eiIvPjwvZz48L3N2Zz4="); } -.security-list-tree-view > div { +.security-list { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMi4zIDQyLjIiPjxnIGZpbGw9IiNmZmYiPjxwYXRoIGQ9Ik0xNS44IDQxLjVDOCAzOC42IDQuMyAzMy42IDIuNSAyOS44Yy0yLTQuMy0yLTcuOS0yLTguMVY3LjNMMTYuMi42bDE1LjYgNi43djE0LjVjMCAuNi0uMiAxNC4yLTE1LjEgMTkuN2wtLjQuMi0uNS0uMnoiLz48cGF0aCBkPSJNMTYuMiAxLjFsMTUuMSA2LjV2MTQuMmMwIC42LS4yIDEzLjgtMTQuOCAxOS4ybC0uMy4xLS4yLS4xQzguMyAzOC4yIDQuNyAzMy4zIDMgMjkuNmMtMS45LTQuMS0yLTcuNy0yLTcuOFY3LjZsMTUuMi02LjVtMC0xLjFsLS40LjJMLjYgNi43bC0uNi4yVjIxLjhjMCAuMiAwIDMuOSAyLjEgOC4zIDEuOCAzLjggNS42IDkgMTMuNiAxMS45bC4zLjEuMy4xLjMtLjEuMy0uMWMxNS4zLTUuNiAxNS41LTE5LjYgMTUuNS0yMC4yVjYuOWwtLjYtLjNMMTYuNi4ybC0uNC0uMnoiLz48L2c+PHBhdGggZD0iTTIuNSA4LjZ2MTMuMmMwIC4xIDAgMy40IDEuOCA3LjIgMi4zIDQuOSA2LjMgOC40IDExLjkgMTAuNSAxMy40LTUuMSAxMy42LTE3LjIgMTMuNi0xNy43VjguNkwxNi4yIDIuNyAyLjUgOC42em0xMy42IDE5LjdIN3YtMS41aDkuMXYxLjV6bTAtNi44SDdWMjBoOS4xdjEuNXptMC03LjNIN3YtMS41aDkuMXYxLjV6bTQuMS0xLjVsMS4yIDEuMiAzLTMgMSAxLTQgNC0yLjItMi4yIDEtMXptNC42IDkuNWwtMSAxLTEuNS0xLjUtMS41IDEuNS0xLTEgMS41LTEuNS0xLjUtMS41IDEtMSAxLjUgMS41IDEuNS0xLjUgMSAxLTEuNSAxLjUgMS41IDEuNXptLTQuNiA0LjZsMS4yIDEuMiAzLTMgMSAxLTQgNC0yLjItMi4yIDEtMXoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTYuMiAxLjFMMSA3LjZ2MTQuMmMwIC4xIDAgMy43IDIgNy44IDEuNyAzLjcgNS40IDguNiAxMyAxMS40bC4zLjEuMy0uMWMxNC42LTUuNCAxNC44LTE4LjcgMTQuOC0xOS4yVjcuNkwxNi4yIDEuMXptMTMuNiAyMC43YzAgLjUtLjIgMTIuNi0xMy42IDE3LjctNS42LTIuMS05LjYtNS42LTExLjktMTAuNS0xLjgtMy45LTEuOC03LjEtMS44LTcuMlY4LjZsMTMuNy01LjkgMTMuNiA1Ljl2MTMuMnoiIGZpbGw9IiNiYjUwMWMiLz48cGF0aCBmaWxsPSIjYmI1MDFjIiBkPSJNNyAxMi43aDkuMXYxLjVIN3pNMjUuNCAxMmwtMS4xLTEtMi45IDIuOS0xLjItMS4yLTEuMSAxLjEgMi4zIDIuMnpNNyAyMGg5LjF2MS41SDd6TTIzLjggMTguMWwtMS41IDEuNi0xLjYtMS42LTEgMS4xIDEuNSAxLjUtMS41IDEuNSAxIDEuMSAxLjYtMS41IDEuNSAxLjUgMS0xLjEtMS41LTEuNSAxLjUtMS41ek03IDI2LjhoOS4xdjEuNUg3ek0yNS40IDI2LjFsLTEuMS0xLTIuOSAyLjktMS4yLTEuMi0xLjEgMS4xIDIuMyAyLjJ6Ii8+PC9zdmc+"); } diff --git a/okitweb/static/okit/js/okit.js b/okitweb/static/okit/js/okit.js index 42755f527..6734aa5b5 100644 --- a/okitweb/static/okit/js/okit.js +++ b/okitweb/static/okit/js/okit.js @@ -74,6 +74,7 @@ class OkitGITConfig { class OkitOCIData { key = "OkitDropdownCache"; + day_milliseconds = 86400000; constructor(profile) { this.compartments = []; this.dropdown_data = {} @@ -97,6 +98,7 @@ class OkitOCIData { let cache = {} if (local_data) cache = JSON.parse(local_data) if (profile && cache[profile]) { + // Add test for stale cache && cache[profile].cache_date && ((Date.now() - cache[profile].cache_date) / this.day_milliseconds) <= 7 console.info(`Found Local Dropdown Data for ${profile}`); this.dropdown_data = cache[profile] return true; @@ -161,6 +163,7 @@ class OkitOCIData { self.dropdown_data = {...self.dropdown_data, ...resp}; delete self.dropdown_data.default delete self.dropdown_data.shipped + self.dropdown_data.cache_date = Date.now() const end = new Date().getTime() console.info('Queried Dropdown Data for', profile, 'took', end - start, 'ms') if (save) this.save(profile) diff --git a/okitweb/static/okit/js/okit_console.js b/okitweb/static/okit/js/okit_console.js index 79d6a750b..e3054e757 100644 --- a/okitweb/static/okit/js/okit_console.js +++ b/okitweb/static/okit/js/okit_console.js @@ -4,8 +4,8 @@ */ console.info('Loaded Console Javascript'); -const okitVersion = '0.24.5'; -const okitReleaseDate = '19th July 2021'; +const okitVersion = '0.25.0'; +const okitReleaseDate = '18th August 2021'; // Validation const validate_error_colour = "#ff4d4d"; const validate_warning_colour = "#ffd633"; @@ -53,6 +53,9 @@ function handleDropdownMenuMouseOver(event) { const scrollY = $('#navigation_menu').scrollTop(); const navX = $('#navigation_menu').offset().left; const navY = $('#navigation_menu').offset().top; + const element = document.getElementById('navigation_menu') + const scrollBarWidth = element.offsetWidth - element.clientWidth; + console.info('Scroll Bar Width', scrollBarWidth) const $slideout = $('> .dropdown-content', $(self)); // console.info('=================================', self.id) // console.info('Parent', parentX, parentY, parentW) @@ -61,7 +64,7 @@ function handleDropdownMenuMouseOver(event) { // console.info('Nav', navX, navY) $slideout.css('position', 'absolute'); $slideout.css('top', menuY + scrollY); - $slideout.css('left', parentX + menuX + width); + $slideout.css('left', parentX + menuX + width - scrollBarWidth - 5); } function handleDropdownMenuMouseOverOld(event) { diff --git a/okitweb/static/okit/js/okit_designer.js b/okitweb/static/okit/js/okit_designer.js index 11223526d..cd651def5 100644 --- a/okitweb/static/okit/js/okit_designer.js +++ b/okitweb/static/okit/js/okit_designer.js @@ -240,6 +240,241 @@ function saveJson(text, filename){ /* ** Save Model As Template */ +function displaySaveAsTemplateDialog(title, callback, root_dir='templates/user') { + $(jqId('modal_dialog_title')).text(title); + $(jqId('modal_dialog_body')).empty(); + $(jqId('modal_dialog_footer')).empty(); + // Add Save Options + const table = d3.select(d3Id('modal_dialog_body')).append('div').append('div') + .attr('class', 'table okit-table okit-modal-dialog-table'); + const tbody = table.append('div').attr('class', 'tbody'); + // Template Directory + const templates_select = tbody.append('div').attr('class', 'tr').append('div').attr('class', 'td').append('select') + .attr('id', 'user_template_select') + .attr('size', '10') + .on('click', () => { + let name = $('#user_template_select').val().replace(root_dir, '') + if (!name.endsWith('.json')) name = `${name}/${okitJsonModel.title.split(' ').join('_').toLowerCase()}.json` + $('#template_file_name').val(name) + }) + // templates_select.append('option') + // .attr('style', 'font-weight: bolder') + // .attr('value', '.') + // .text('user') + $.ajax({ + type: 'get', + url: `templates/load`, + dataType: 'text', // Response Type + contentType: 'application/json', // Sent Message Type + data: JSON.stringify({root_dir: root_dir}), + success: function(resp) { + hierarchy = JSON.parse(resp) + console.info(hierarchy) + const add_dir = (select, depth, dir) => { + dir.files.forEach(file => select.append('option').attr('style', `padding-left: ${depth * 1}em`).attr('value', `${dir.path}/${file.json}`).text(file.json)) + dir.dirs.forEach((d) => { + select.append('option').attr('style', `padding-left: ${depth * 1}em; font-weight: bolder`).attr('value', `${d.path}`).text(d.name) + add_dir(select, (depth + 1), d) + }) + } + add_dir(templates_select, 1, hierarchy) + }, + error: function(xhr, status, error) { + console.error('Status : '+ status) + console.error('Error : '+ error) + } + }); + // Template directory + // tbody.append('div').attr('class', 'tr').append('div').attr('class', 'td').text('Directory'); + // tbody.append('div').attr('class', 'tr').append('div').attr('class', 'td').append('input') + // .attr('class', 'okit-input') + // .attr('id', 'template_dir_name') + // .attr('name', 'template_dir_name') + // .attr('readonly', 'readonly') + // .attr('type', 'text'); + // Template name + tbody.append('div').attr('class', 'tr').append('div').attr('class', 'td').text('Name'); + tbody.append('div').attr('class', 'tr').append('div').attr('class', 'td').append('input') + .attr('class', 'okit-input') + .attr('id', 'template_file_name') + .attr('name', 'template_file_name') + .attr('type', 'text') + .on('keydown', (e) => { + if (d3.event.keyCode == 220) { + d3.event.preventDefault() + } else if (d3.event.keyCode == 32) { + d3.event.preventDefault() + } + }) + .on('blur', () => {$('#template_file_name').val($('#template_file_name').val().replace(' ', '_').replace('\\', '/'))}); + // Submit Button + const submit = d3.select(d3Id('modal_dialog_footer')).append('div').append('button') + .attr('id', 'submit_btn') + .attr('type', 'button') + .text('Save') + .on('click', callback); + $(jqId('modal_dialog_wrapper')).removeClass('hidden'); +} +function handleSaveAsTemplate(e) { + const root_dir = 'templates/user' + displaySaveAsTemplateDialog('Save as Template', () => { + okitJsonModel.updated = getCurrentDateTime(); + $.ajax({ + type: 'post', + url: 'template/save', + dataType: 'text', + contentType: 'application/json', + data: JSON.stringify({root_dir: root_dir, template_file: $('#template_file_name').val(), okit_json: okitJsonModel}), + success: function(resp) { + console.info('Response : ' + resp); + loadTemplatePanel(); + }, + error: function(xhr, status, error) { + console.info('Status : '+ status) + console.info('Error : '+ error) + }, + complete: function() { + // Hide modal dialog + $(jqId('modal_dialog_wrapper')).addClass('hidden'); + } + }); + }, root_dir) +} +const loadTemplatePanel = () => { + const id = 'templates_panel' + $.ajax({ + type: 'get', + url: `panel/templates`, + dataType: 'text', // Response Type + contentType: 'application/json', // Sent Message Type + success: function(resp) { + const parser = new DOMParser(); + const doc = parser.parseFromString(resp, "text/html"); + const new_panel = doc.getElementById(id); + const current_panel = document.getElementById(id); + const template_panel = document.getElementById('designer_left_column') + // Check if menu section already exists + const hidden = $(`#${id}`).hasClass('hidden') + $(`#${id}`).addClass('hidden') + current_panel !== null ? current_panel.replaceWith(new_panel) : template_panel.appendChild(new_panel); + if (!hidden) $(`#${id}`).removeClass('hidden') + }, + error: function(xhr, status, error) { + console.error('Status : '+ status) + console.error('Error : '+ error) + } + }); +} +/* +** Save Model to Git + */ +function handleSaveToGit(e) { + const root_dir = 'git' + displaySaveAsTemplateDialog('Save to Git', () => { + okitJsonModel.updated = getCurrentDateTime(); + $.ajax({ + type: 'post', + url: 'template/save', + dataType: 'text', + contentType: 'application/json', + data: JSON.stringify({root_dir: root_dir, template_file: $('#template_file_name').val(), okit_json: okitJsonModel, git: true}), + success: function(resp) { + console.info('Response : ' + resp); + loadGitPanel(); + }, + error: function(xhr, status, error) { + console.info('Status : '+ status) + console.info('Error : '+ error) + }, + complete: function() { + // Hide modal dialog + $(jqId('modal_dialog_wrapper')).addClass('hidden'); + } + }); + }, root_dir) +} +const loadGitPanel = () => { + const id = 'git_panel' + $.ajax({ + type: 'get', + url: `panel/git`, + dataType: 'text', // Response Type + contentType: 'application/json', // Sent Message Type + success: function(resp) { + const parser = new DOMParser(); + const doc = parser.parseFromString(resp, "text/html"); + const new_panel = doc.getElementById(id); + const current_panel = document.getElementById(id); + const template_panel = document.getElementById('designer_left_column') + // Check if menu section already exists + const hidden = $(`#${id}`).hasClass('hidden') + $(`#${id}`).addClass('hidden') + current_panel !== null ? current_panel.replaceWith(new_panel) : template_panel.appendChild(new_panel); + if (!hidden) $(`#${id}`).removeClass('hidden') + }, + error: function(xhr, status, error) { + console.error('Status : '+ status) + console.error('Error : '+ error) + } + }); +} +/* +** Save Model to Container + */ +function handleSaveToContainer(e) { + const root_dir = 'local' + displaySaveAsTemplateDialog('Save to Container', () => { + okitJsonModel.updated = getCurrentDateTime(); + $.ajax({ + type: 'post', + url: 'template/save', + dataType: 'text', + contentType: 'application/json', + data: JSON.stringify({root_dir: root_dir, template_file: $('#template_file_name').val(), okit_json: okitJsonModel}), + success: function(resp) { + console.info('Response : ' + resp); + loadGitPanel(); + }, + error: function(xhr, status, error) { + console.info('Status : '+ status) + console.info('Error : '+ error) + }, + complete: function() { + // Hide modal dialog + $(jqId('modal_dialog_wrapper')).addClass('hidden'); + } + }); + }, root_dir) +} +const loadFilesystemPanel = () => { + const id = 'local_panel' + $.ajax({ + type: 'get', + url: `panel/local`, + dataType: 'text', // Response Type + contentType: 'application/json', // Sent Message Type + success: function(resp) { + const parser = new DOMParser(); + const doc = parser.parseFromString(resp, "text/html"); + const new_panel = doc.getElementById(id); + const current_panel = document.getElementById(id); + const template_panel = document.getElementById('designer_left_column') + // Check if menu section already exists + const hidden = $(`#${id}`).hasClass('hidden') + $(`#${id}`).addClass('hidden') + current_panel !== null ? current_panel.replaceWith(new_panel) : template_panel.appendChild(new_panel); + if (!hidden) $(`#${id}`).removeClass('hidden') + }, + error: function(xhr, status, error) { + console.error('Status : '+ status) + console.error('Error : '+ error) + } + }); +} +/* +** Save Model As Template + */ +// TODO: Delete function displayGitSaveDialog(title, callback, show_dir=true, show_filename=true) { $(jqId('modal_dialog_title')).text(title); $(jqId('modal_dialog_body')).empty(); @@ -307,99 +542,8 @@ function displayGitSaveDialog(title, callback, show_dir=true, show_filename=true .on('click', callback); $(jqId('modal_dialog_wrapper')).removeClass('hidden'); } -function handleSaveAs(evt) { - // Display Save As Dialog - $(jqId('modal_dialog_title')).text('Save As Template'); - $(jqId('modal_dialog_body')).empty(); - $(jqId('modal_dialog_footer')).empty(); - let table = d3.select(d3Id('modal_dialog_body')).append('div').append('div') - .attr('id', 'save_as_template_table') - .attr('class', 'table okit-table okit-modal-dialog-table'); - let tbody = table.append('div').attr('class', 'tbody'); - // Title - let tr = tbody.append('div').attr('class', 'tr'); - tr.append('div').attr('class', 'td').text('Title'); - tr.append('div').attr('class', 'td').append('input') - .attr('class', 'okit-input') - .attr('id', 'template_title') - .attr('name', 'template_title') - .attr('type', 'text'); - // Description - tr = tbody.append('div').attr('class', 'tr'); - tr.append('div').attr('class', 'td').text('Description'); - tr.append('div').attr('class', 'td').append('input') - .attr('class', 'okit-input') - .attr('id', 'template_description') - .attr('name', 'template_description') - .attr('type', 'text'); - // Type - /* TODO: Reinstate when sub template types are implemented - tr = tbody.append('div').attr('class', 'tr'); - tr.append('div').attr('class', 'td').text('Type'); - tr.append('div').attr('class', 'td').append('input') - .attr('class', 'okit-input') - .attr('id', 'template_type') - .attr('name', 'template_type') - .attr('type', 'text'); - */ - // Save - let save_button = d3.select(d3Id('modal_dialog_footer')).append('div').append('button') - .attr('id', 'save_as_button') - .attr('type', 'button') - .text('Save'); - save_button.on("click", handleSaveAsTemplate); - $(jqId('modal_dialog_wrapper')).removeClass('hidden'); -} -function handleSaveAsTemplate(e) { - //okitJsonModel.title = $(jqId('template_title')).val(); - //okitJsonModel.description = $(jqId('template_description')).val(); - //okitJsonModel.template_type = $(jqId('template_type')).val(); - okitJsonModel.template_type = 'User'; - okitJsonModel.updated = getCurrentDateTime(); - $.ajax({ - type: 'post', - url: 'saveas/template', - dataType: 'text', - contentType: 'application/json', - data: JSON.stringify(okitJsonModel), - success: function(resp) { - console.info('Response : ' + resp); - reloadTemplateMenu('user'); - }, - error: function(xhr, status, error) { - console.info('Status : '+ status) - console.info('Error : '+ error) - }, - complete: function() { - // Hide modal dialog - $(jqId('modal_dialog_wrapper')).addClass('hidden'); - } - }); -} -function reloadTemplateMenu(section) { - const id = `${section}_template_menu_group`; - $.ajax({ - type: 'get', - url: `templates/${section}`, - dataType: 'text', // Response Type - contentType: 'application/json', // Sent Message Type - success: function(resp) { - const parser = new DOMParser(); - const doc = parser.parseFromString(resp, "text/html"); - const new_menu = doc.getElementById(id); - const current_menu = document.getElementById(id); - const template_menu = document.getElementById('templates_menu') - // Check if menu section already exists - current_menu !== null ? current_menu.replaceWith(new_menu) : template_menu.appendChild(new_menu); - addMenuDropdownMouseOver(`#${id}`); - }, - error: function(xhr, status, error) { - console.error('Status : '+ status) - console.error('Error : '+ error) - } - }); -} -function handleSaveToGit(e) { +// TODO: Delete +function handleSaveToGit1(e) { displayGitSaveDialog('Save To Git', () => { let request_json = JSON.clone(okitJsonModel); @@ -465,9 +609,10 @@ function loadTemplate(template_url) { resetDesigner(); $.ajax({ type: 'get', - url: template_url, - dataType: 'text', - contentType: 'application/json', + url: 'template/load', + dataType: 'text', // Response Type + contentType: 'application/json', // Sent Message Type + data: JSON.stringify({template_file: template_url}), success: function(resp) { okitJsonModel = new OkitJson(resp); newDesignerView(); @@ -482,6 +627,79 @@ function loadTemplate(template_url) { }); } /* +** Import Model From Template + */ +function importTemplate(template_url, event) { + console.info('Import Template Event', event) + console.info('<<<<<< NEED TO RESOLVE TOP LEVEL SVG REPRESENTATION >>>>>>') + return + // event = event | window.event + event.preventDefault() + event.stopPropagation() + const right_coll_offset = $('#designer_right_column').offset() + const position = {top: event.pageY - right_coll_offset.top - 10, left: event.pageX - 10}; + console.info('Context Position', position, right_coll_offset) + $(jqId("context-menu")).empty(); + $(jqId("context-menu")).css(position); + const contextmenu = d3.select(d3Id("context-menu")); + contextmenu.on('mouseenter', function () { + $(jqId("context-menu")).removeClass("hidden"); + }) + .on('mouseleave', function () { + $(jqId("context-menu")).addClass("hidden"); + }); + const ul = contextmenu.append('ul').attr('class', 'okit-context-menu-list'); + ul.append('li').append('a') + .attr('class', 'parent-item') + .attr('href', 'javascript:void(0)') + .text('Import') + .on('click', () => { + $.ajax({ + type: 'get', + url: 'template/load', + dataType: 'text', // Response Type + contentType: 'application/json', // Sent Message Type + data: JSON.stringify({template_file: template_url}), + success: function(resp) { + okitJsonModel.load(JSON.parse(resp)) + okitJsonView.load() + displayOkitJson(); + displayDesignerView(); + displayTreeView(); + }, + error: function(xhr, status, error) { + console.error('Status : '+ status); + console.error('Error : '+ error); + } + }); + $(jqId("context-menu")).addClass("hidden"); + }); + $(jqId("context-menu")).removeClass("hidden"); + +} +// TODO: Delete +// function loadTemplate(template_url) { +// hideNavMenu(); +// resetDesigner(); +// $.ajax({ +// type: 'get', +// url: template_url, +// dataType: 'text', +// contentType: 'application/json', +// success: function(resp) { +// okitJsonModel = new OkitJson(resp); +// newDesignerView(); +// displayOkitJson(); +// displayDesignerView(); +// displayTreeView(); +// }, +// error: function(xhr, status, error) { +// console.error('Status : '+ status); +// console.error('Error : '+ error); +// } +// }); +// } +/* ** Query OCI */ function displayQueryDialog() { diff --git a/okitweb/static/okit/js/okit_designer_ready.js b/okitweb/static/okit/js/okit_designer_ready.js index e0f442e65..9b17a703c 100644 --- a/okitweb/static/okit/js/okit_designer_ready.js +++ b/okitweb/static/okit/js/okit_designer_ready.js @@ -90,39 +90,55 @@ $(document).ready(function() { checkLeftColumn(); }) .text('Explorer'); - if (developer_mode) { - // Templates - d3.select(d3Id('console_left_bar')).append('label') - .attr('id', 'toggle_templates_button') - .attr('class', 'okit-pointer-cursor') - .on('click', function () { - let open = $(this).hasClass('okit-bar-panel-displayed'); - slideLeftPanelsOffScreen(); - if (!open) { - $('#templates_panel').removeClass('hidden'); - $(this).addClass('okit-bar-panel-displayed'); - } else { - $('#templates_panel').empty(); - } - checkLeftColumn(); - }) - .text('Templates'); - // Git + // Templates + d3.select(d3Id('console_left_bar')).append('label') + .attr('id', 'toggle_templates_button') + .attr('class', 'okit-pointer-cursor') + .on('click', function () { + let open = $(this).hasClass('okit-bar-panel-displayed'); + slideLeftPanelsOffScreen(); + if (!open) { + $('#templates_panel').removeClass('hidden'); + $(this).addClass('okit-bar-panel-displayed'); + } else { + // $('#templates_panel').empty(); + } + checkLeftColumn(); + }) + .text('Templates'); + // Git + d3.select(d3Id('console_left_bar')).append('label') + .attr('id', 'toggle_git_button') + .attr('class', 'okit-pointer-cursor') + .on('click', function () { + let open = $(this).hasClass('okit-bar-panel-displayed'); + slideLeftPanelsOffScreen(); + if (!open) { + $('#git_panel').removeClass('hidden'); + $(this).addClass('okit-bar-panel-displayed'); + } else { + $('#git_panel').empty(); + } + checkLeftColumn(); + }) + .text('Git Repositories'); + if (a2c_mode) { + // Container d3.select(d3Id('console_left_bar')).append('label') - .attr('id', 'toggle_git_button') + .attr('id', 'toggle_local_button') .attr('class', 'okit-pointer-cursor') .on('click', function () { let open = $(this).hasClass('okit-bar-panel-displayed'); slideLeftPanelsOffScreen(); if (!open) { - $('#git_panel').removeClass('hidden'); + $('#local_panel').removeClass('hidden'); $(this).addClass('okit-bar-panel-displayed'); } else { - $('#git_panel').empty(); + $('#local_panel').empty(); } checkLeftColumn(); }) - .text('Git Repositories'); + .text('Filesystem'); } // Preferences d3.select(d3Id('console_left_bar')).append('label') @@ -381,6 +397,14 @@ $(document).ready(function() { /* ** Add redraw on resize */ - window.addEventListener('resize', () => { redrawSVGCanvas(true) }); + // window.addEventListener('resize', () => { redrawSVGCanvas(true) }); + + /* + ** Load Side Panels in Background + */ + + loadTemplatePanel() + loadGitPanel() + loadFilesystemPanel() }); diff --git a/okitweb/static/okit/json/release.json b/okitweb/static/okit/json/release.json index 55c54acae..37ee40a09 100644 --- a/okitweb/static/okit/json/release.json +++ b/okitweb/static/okit/json/release.json @@ -1,4 +1,4 @@ { - "release": "0.24.5", - "tag": "v0.24.5" + "release": "0.25.0", + "tag": "v0.25.0" } \ No newline at end of file diff --git a/okitweb/static/okit/model/js/okit_model.js b/okitweb/static/okit/model/js/okit_model.js index 564881dc9..ed710d1a0 100644 --- a/okitweb/static/okit/model/js/okit_model.js +++ b/okitweb/static/okit/model/js/okit_model.js @@ -545,6 +545,11 @@ class OkitJson { } } + // All Instance Resources + getAllInstanceTypes() { + return [...this.getInstances(), ...this.getAnalyticsInstances()] + } + // Instance newInstance(data) { console.info('New Instance'); diff --git a/okitweb/static/okit/templates/reference_architecture/cockroachdb_cluster.json b/okitweb/static/okit/templates/reference_architecture/database/cockroachdb_cluster.json similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/cockroachdb_cluster.json rename to okitweb/static/okit/templates/reference_architecture/database/cockroachdb_cluster.json diff --git a/okitweb/static/okit/templates/reference_architecture/cockroachdb_cluster.md b/okitweb/static/okit/templates/reference_architecture/database/cockroachdb_cluster.md similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/cockroachdb_cluster.md rename to okitweb/static/okit/templates/reference_architecture/database/cockroachdb_cluster.md diff --git a/okitweb/static/okit/templates/reference_architecture/mysql_ha_cluster.json b/okitweb/static/okit/templates/reference_architecture/database/mysql_ha_cluster.json similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/mysql_ha_cluster.json rename to okitweb/static/okit/templates/reference_architecture/database/mysql_ha_cluster.json diff --git a/okitweb/static/okit/templates/reference_architecture/postgresql_cluster.json b/okitweb/static/okit/templates/reference_architecture/database/postgresql_cluster.json similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/postgresql_cluster.json rename to okitweb/static/okit/templates/reference_architecture/database/postgresql_cluster.json diff --git a/okitweb/static/okit/templates/reference_architecture/sql_server.json b/okitweb/static/okit/templates/reference_architecture/database/sql_server.json similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/sql_server.json rename to okitweb/static/okit/templates/reference_architecture/database/sql_server.json diff --git a/okitweb/static/okit/templates/reference_architecture/ha_web_application.json b/okitweb/static/okit/templates/reference_architecture/infrastructure/ha_web_application.json similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/ha_web_application.json rename to okitweb/static/okit/templates/reference_architecture/infrastructure/ha_web_application.json diff --git a/okitweb/static/okit/templates/reference_architecture/hub_and_spoke_network_topology.json b/okitweb/static/okit/templates/reference_architecture/infrastructure/hub_and_spoke_network_topology.json similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/hub_and_spoke_network_topology.json rename to okitweb/static/okit/templates/reference_architecture/infrastructure/hub_and_spoke_network_topology.json diff --git a/okitweb/static/okit/templates/reference_architecture/hub_and_spoke_network_topology.md b/okitweb/static/okit/templates/reference_architecture/infrastructure/hub_and_spoke_network_topology.md similarity index 100% rename from okitweb/static/okit/templates/reference_architecture/hub_and_spoke_network_topology.md rename to okitweb/static/okit/templates/reference_architecture/infrastructure/hub_and_spoke_network_topology.md diff --git a/okitweb/static/okit/view/designer/js/artefacts/load_balancer.js b/okitweb/static/okit/view/designer/js/artefacts/load_balancer.js index 8d461c602..c7ca7c01c 100644 --- a/okitweb/static/okit/view/designer/js/artefacts/load_balancer.js +++ b/okitweb/static/okit/view/designer/js/artefacts/load_balancer.js @@ -25,7 +25,7 @@ class LoadBalancerView extends OkitDesignerArtefactView { if (this.backend_sets) { for (let [key, value] of Object.entries(this.backend_sets)) { for (let backend of value.backends) { - for (let instance of this.getOkitJson().getInstances()) { + for (let instance of this.getOkitJson().getAllInstanceTypes()) { if (instance.primary_vnic.private_ip === backend.ip_address) { if (!this.instance_ids.includes(instance.id)) { this.instance_ids.push(instance.id); @@ -58,7 +58,7 @@ class LoadBalancerView extends OkitDesignerArtefactView { $(jqId(PROPERTIES_PANEL)).load("propertysheets/load_balancer.html", () => { // Load Referenced Ids let instances_select = d3.select(d3Id('instance_ids')); - for (let instance of me.artefact.getOkitJson().instances) { + for (let instance of self.getOkitJson().getAllInstanceTypes()) { let div = instances_select.append('div'); div.append('input') .attr('type', 'checkbox') diff --git a/okitweb/static/okit/view/designer/js/okit_designer_view.js b/okitweb/static/okit/view/designer/js/okit_designer_view.js index b59f808fc..68207e591 100644 --- a/okitweb/static/okit/view/designer/js/okit_designer_view.js +++ b/okitweb/static/okit/view/designer/js/okit_designer_view.js @@ -246,41 +246,25 @@ class OkitDesignerJsonView extends OkitJsonView { } // Empty existing Canvas canvas_div.selectAll('*').remove(); + // Zoom & Pan SVG + const canvas_root_svg = canvas_div.append("svg") + .attr("id", 'canvas_root_svg') + .attr("width", "100%") + .attr("height", "100%") + .attr("preserveAspectRatio", "xMinYMin meet") + .call(d3.zoom().scaleExtent([0.1, 3]).on("zoom", () => {d3.select("#canvas_root_svg g").attr("transform", d3.event.transform)})); + const transform_group = canvas_root_svg.append('g'); + // Wrapper SVG Element to define ViewBox etc - let canvas_svg = canvas_div.append("svg") + // let canvas_svg = canvas_div.append("svg") + let canvas_svg = transform_group.append("svg") .attr("id", 'canvas-svg') .attr("x", 0) .attr("y", 0) .attr("width", width) .attr("height", height) .attr("viewBox", "0 0 " + width + " " + height) - .attr("preserveAspectRatio", "xMinYMin meet") - // .on("wheel", () => { - // const event = window.event - // let scale = 1 - // event.preventDefault(); - // if (event.deltaY < 0) { - // // Zoom in - // scale *= event.deltaY * -2; - // } - // else { - // // Zoom out - // scale /= event.deltaY * 2; - // } - // // Restrict scale - // scale = Math.min(Math.max(.125, scale), 4); - // console.info('Scale:', scale, event.deltaY) - // const transform_group = d3.select(d3Id("canvas-svg-group")); - // let transform = transform_group.attr("transform"); - // console.info('Matrix', transform); - // const matrix = transform.replace('matrix(','').replace(')', '').split(' '); - // for (let i = 0; i < 4; i++) { - // matrix[i] *= scale; - // } - // transform = `matrix(${matrix.join(' ')})`; - // transform_group.attr("transform", transform); - // console.info('Transformed Matrix', transform); - // }); + .attr("preserveAspectRatio", "xMinYMin meet"); this.clearCanvas(); @@ -292,9 +276,6 @@ class OkitDesignerJsonView extends OkitJsonView { canvas_svg.selectAll('*').remove(); this.styleCanvas(canvas_svg); this.addDefinitions(canvas_svg); - const transform_group = canvas_svg.append('g') - .attr("id", "canvas-svg-group") - .attr("transform", "matrix(1 0 0 1 0 0)"); canvas_svg.append('rect') .attr("id", "canvas-rect") .attr("width", "100%") @@ -312,8 +293,6 @@ class OkitDesignerJsonView extends OkitJsonView { let defid = key.replace(/ /g, '') + 'Svg'; defs.append('g') .attr("id", defid) - //.attr("transform", "translate(-20, -20) scale(0.3, 0.3)") - // .attr("transform", "translate(-1, -1) scale(0.29, 0.29)") .attr("transform", "translate(4.5, 4.5) scale(0.8, 0.8)") .html(this.palette_svg[key]); } diff --git a/okitweb/static/okit/view/js/okit_view.js b/okitweb/static/okit/view/js/okit_view.js index 31af5c6d8..9add3ebf9 100644 --- a/okitweb/static/okit/view/js/okit_view.js +++ b/okitweb/static/okit/view/js/okit_view.js @@ -1388,7 +1388,7 @@ class OkitArtefactView { this.drawText(svg, this.svg_info_text); this.drawText(svg, this.svg_label_text); this.drawTitle(svg); - this.drawIcon(svg); + const icon = this.drawIcon(svg); // if (this.read_only) this.drawIconOverlay(svg) // Add standard / common click event this.addClickEvent(svg); @@ -1398,6 +1398,7 @@ class OkitArtefactView { this.addMouseEvents(svg); // Add Drag Handling Events this.addDragEvents(svg); + this.addIconDragEvents(icon); // Add Context Menu (Right-Click) this.addContextMenu(svg); // Add Custom Data Attributes @@ -1414,6 +1415,8 @@ class OkitArtefactView { // console.warn('Parent SVG Id', this.parent_svg_id) // Get attributes as local constant before create to stop NaN because append adds element before adding attributes. const definition = this.svg_definition; + // const g = parent_svg.append("g") + // .attr("transform", `translate(${definition.x}, ${definition.y})`) const svg = parent_svg.append("svg") .attr("id", definition.id) .attr("data-type", this.artefact ? this.artefact.getArtifactReference() : '') @@ -1572,6 +1575,15 @@ class OkitArtefactView { .on("dragend", dragEnd); } + addIconDragEvents(svg) { + const self = this + svg.call(d3.drag() + .on("start", () => console.warn(`'Drag Start Event' ${self.display_name}`)) + .on("drag", () => console.warn(`'Drag Event' ${self.display_name}`)) + .on("end", () => console.warn(`'Drag End Event' ${self.display_name}`)) + ) + } + addContextMenu(svg) { const self = this; svg.on("contextmenu", function () { @@ -2565,6 +2577,7 @@ class OkitContainerArtefactView extends OkitArtefactView { self.recalculate_dimensions = true; self.getJsonView().draw(); }); + return icon } getPadding() { diff --git a/okitweb/static/okit/view/relationship/js/okit_relationship_view.js b/okitweb/static/okit/view/relationship/js/okit_relationship_view.js index 23e874388..476b85ded 100644 --- a/okitweb/static/okit/view/relationship/js/okit_relationship_view.js +++ b/okitweb/static/okit/view/relationship/js/okit_relationship_view.js @@ -93,7 +93,7 @@ class OkitRelationshipJsonView extends OkitJsonView { .attr("id", 'relationship-svg') .attr("width", '100%') .attr("height", "100%") - .call(d3.zoom().on("zoom", function () {d3.select("#relationship-svg g").attr("transform", d3.event.transform)})) + .call(d3.zoom().scaleExtent([0.1, 3]).on("zoom", function () {d3.select("#relationship-svg g").attr("transform", d3.event.transform)})) .append("g") // .attr('transform', function(d) {return 'translate(' + [width / 2, height / 2] + ')'}); diff --git a/okitweb/templates/okit/git_repositories_panel.html b/okitweb/templates/okit/git_repositories_panel.html new file mode 100644 index 000000000..4018ed47d --- /dev/null +++ b/okitweb/templates/okit/git_repositories_panel.html @@ -0,0 +1,2 @@ +{% import "okit/panel_macros.jinja2" as panel with context %} +{{ panel.add_git_side_panel(git_repositories) }} \ No newline at end of file diff --git a/okitweb/templates/okit/local_panel.html b/okitweb/templates/okit/local_panel.html new file mode 100644 index 000000000..69117ad8f --- /dev/null +++ b/okitweb/templates/okit/local_panel.html @@ -0,0 +1,2 @@ +{% import "okit/panel_macros.jinja2" as panel with context %} +{{ panel.add_local_side_panel(local_filesystem) }} \ No newline at end of file diff --git a/okitweb/templates/okit/menu.html b/okitweb/templates/okit/menu.html new file mode 100644 index 000000000..ff895f6f1 --- /dev/null +++ b/okitweb/templates/okit/menu.html @@ -0,0 +1,112 @@ + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/okitweb/templates/okit/okit_console.html b/okitweb/templates/okit/okit_console.html index dac82ef89..b06169a8d 100644 --- a/okitweb/templates/okit/okit_console.html +++ b/okitweb/templates/okit/okit_console.html @@ -10,6 +10,7 @@ + diff --git a/okitweb/templates/okit/okit_designer.html b/okitweb/templates/okit/okit_designer.html index 0850e7e48..5f445cbbb 100644 --- a/okitweb/templates/okit/okit_designer.html +++ b/okitweb/templates/okit/okit_designer.html @@ -5,6 +5,7 @@ {% extends "okit/okit_console.html" %} {% import "okit/menu_macros.jinja2" as menu with context %} +{% import "okit/panel_macros.jinja2" as panel with context %} {% import "okit/palette_macros.jinja2" as palette with context %} {% block headscripts %} @@ -87,41 +88,11 @@ {% block headcss %} + {% endblock %} -{% block navmenublocknew %} - -{% endblock %} - {% block navmenublock %}