From e94e2ce2d19cc551541967fffe285578efe3c8c4 Mon Sep 17 00:00:00 2001 From: Artem Golub Date: Thu, 21 Jul 2022 15:33:56 +0300 Subject: [PATCH] feat: code generation cleanup --- docker-compose.yml | 14 +- services/backend/requirements.txt | 9 - services/backend/src/api/views/generate.py | 10 +- services/backend/src/api/views/utils.py | 558 +----------------- services/frontend/configs/nginx/nginx.conf | 1 - .../src/components/Canvas/VolumeNode.tsx | 8 +- .../src/components/Modal/Volume/Create.tsx | 6 + .../src/components/Modal/Volume/Edit.tsx | 19 +- .../src/components/Modal/Volume/General.tsx | 1 + services/frontend/src/types/index.ts | 11 +- services/frontend/src/utils/generators.ts | 40 +- 11 files changed, 63 insertions(+), 614 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cf2091e..e7a5276 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,6 @@ volumes: name: ctk_django_static services: - postgres: container_name: ctk-postgres image: postgres:11 @@ -52,15 +51,10 @@ services: links: - backend volumes: - # configs - - ${PWD}/services/frontend/configs/nginx/uwsgi_params:/home/config/uwsgi/uwsgi_params - - ${PWD}/services/frontend/configs/nginx/localhost.conf:/etc/nginx/conf.d/default.conf - - ${PWD}/services/frontend/configs/nginx/nginx.conf:/etc/nginx/nginx.conf - - # serve django static stuff + - ./services/frontend/configs/nginx/uwsgi_params:/home/config/uwsgi/uwsgi_params + - ./services/frontend/configs/nginx/localhost.conf:/etc/nginx/conf.d/default.conf + - ./services/frontend/configs/nginx/nginx.conf:/etc/nginx/nginx.conf + - ./services/frontend/build:/usr/share/nginx/html/ - django-static:/home/server/static/ - - # serve composer built react app - - ${PWD}/services/frontend/build:/usr/share/nginx/html/ ports: - "80:80" diff --git a/services/backend/requirements.txt b/services/backend/requirements.txt index 3a4b165..aaf9746 100644 --- a/services/backend/requirements.txt +++ b/services/backend/requirements.txt @@ -12,15 +12,6 @@ psycopg2-binary==2.9.3 uwsgi==2.0.20 botocore==1.24.46 boto3==1.21.46 -Jinja2==3.1.1 -validators==0.19.0 requests==2.27.1 -celery==5.2.3 -redis==4.3.1 -simple-salesforce==1.11.6 -cryptography==37.0.2 -chardet==4.0.0 pyaml==21.10.1 -docker==5.0.3 ruamel.yaml==0.17.21 -better-profanity==0.7.0 diff --git a/services/backend/src/api/views/generate.py b/services/backend/src/api/views/generate.py index dc347e8..9b7907a 100644 --- a/services/backend/src/api/views/generate.py +++ b/services/backend/src/api/views/generate.py @@ -1,7 +1,7 @@ from rest_framework import generics, status from rest_framework.response import Response -from .utils import generate_dc +from .utils import generate class GenerateGenericAPIView(generics.GenericAPIView): @@ -14,19 +14,13 @@ class GenerateGenericAPIView(generics.GenericAPIView): request_data = request.data version = request_data['data'].get('version', '3') services = request_data['data'].get('services', None) - connections = request_data['data'].get('connections', None) volumes = request_data['data'].get('volumes', None) networks = request_data['data'].get('networks', None) - secrets = request_data['data'].get('secrets', None) - configs = request_data['data'].get('configs', None) - code = generate_dc( + code = generate( services, - connections, volumes, networks, - secrets, - configs, version=version, return_format='yaml') resp = {'code': code} diff --git a/services/backend/src/api/views/utils.py b/services/backend/src/api/views/utils.py index df3be0f..8f2322e 100644 --- a/services/backend/src/api/views/utils.py +++ b/services/backend/src/api/views/utils.py @@ -1,18 +1,7 @@ import io -import re -import sys -import random -import string -import ast -import uuid import contextlib -import docker -from better_profanity import profanity - from ruamel.yaml import YAML -from ruamel.yaml.scalarstring import ( - SingleQuotedScalarString, - DoubleQuotedScalarString) +from ruamel.yaml.scalarstring import DoubleQuotedScalarString from api.models import Project @@ -28,18 +17,11 @@ else: def indent(text, amount, ch=' '): return textwrap.indent(text, amount * ch) - -def get_project_obj_by_id(id): - with contextlib.suppress(Project.DoesNotExist): - return Project.objects.get(pk=id) - return None - def get_project_obj_by_uuid(uuid): with contextlib.suppress(Project.DoesNotExist): return Project.objects.get(uuid=uuid) return None - def sequence_indent_four(s): ret_val = '' first_indent = True @@ -58,7 +40,6 @@ def sequence_indent_four(s): return ret_val - def sequence_indent_one(s): ret_val = '' first_indent = True @@ -77,405 +58,13 @@ def sequence_indent_one(s): return ret_val - -def format_quotes(s): - if '\'' in s: - return SingleQuotedScalarString(s.replace("'", '')) - if '"' in s: - return DoubleQuotedScalarString(s.replace('"', '')) - - return SingleQuotedScalarString(s) - - -def format_volumes_top_level(volumes, compose_version): - ret = {} - - for volume in volumes: - volume_custom_name = volume.get('volume_name', None) - volume_driver = volume.get('driver', None) - external = volume.get('external', False) - external_name = volume.get('external_name', False) - ret[volume['name']] = {} - - if external: - ret[volume['name']]['external'] = True - - if external_name: - ret[volume['name']]['external'] = { - 'name': external_name - } - - if volume_custom_name: - ret[volume['name']]['name'] = volume_custom_name - - if volume_driver: - ret[volume['name']]['driver'] = volume_driver - - if compose_version in [2, 3]: - if labels := volume.get('labels', None): - ret[volume['name']]['labels'] = {} - for label in labels: - ret[volume['name']]['labels'][label['key']] = format_quotes(label['value']) - - if not ret[volume['name']]: - ret[volume['name']] = None - - return ret - - -def format_networks_top_level(networks, compose_version): - ret = {} - - for network in networks: - network_custom_name = network.get('object_name', None) - network_driver = network.get('driver', None) - network_custom_driver = network.get('driver_custom', False) - external = network.get('external', False) - external_name = network.get('external_name', False) - driver_opts = network.get('driver_opts', None) - ret[network['name']] = {} - - if external: - ret[network['name']]['external'] = True - ret[network['name']]['name'] = external_name - - if network_custom_name: - ret[network['name']]['name'] = network_custom_name - - if network_driver: - ret[network['name']]['driver'] = network_driver - - if driver_opts: - ret[network['name']]['driver_opts'] = {} - for driver_opt in driver_opts: - ret[network['name']]['driver_opts'][driver_opt['key']] = format_quotes(driver_opt['value']) - - if compose_version in [2, 3]: - if labels := network.get('labels', None): - ret[network['name']]['labels'] = {} - for label in labels: - ret[network['name']]['labels'][label['key']] = format_quotes(label['value']) - - if not ret[network['name']]: - ret[network['name']] = None - - return ret - - -def format_key_val_pairs(pairs): - return {pair_part['key']: pair_part['value'] for pair_part in pairs} - - -def format_ports(ports): - service_ports_formatted = [] - for port in ports: - port_published = port['published'] - port_target = port['target'] - formatter_string = DoubleQuotedScalarString(f"{port_published}") - - if port_target: - formatter_string = DoubleQuotedScalarString(f"{port_published}:{port_target}") - - service_ports_formatted.append(formatter_string) - - return service_ports_formatted - - -def format_volumes(service_volumes, volumes): - ret = [] - for service_volume in service_volumes: - for volume in volumes: - if 'volume' in service_volume and service_volume['volume'] == volume['uuid']: - volume_mount_str = f"{volume['name']}:{service_volume['destination']}" - ret.append(volume_mount_str) - - if 'relativePathSource' in service_volume: - volume_mount_str = f"{service_volume['relativePathSource']}:{service_volume['destination']}" - ret.append(volume_mount_str) - - return ret - - -def format_networks(service_networks, networks): - ret = [] - - if service_networks: - for service_network_uuid in service_networks: - for network in networks: - if service_network_uuid == network['uuid']: - network_str = f"{network['name']}" - ret.append(network_str) - - return ret - - -def clean_string(string): - string = " ".join(re.findall(r"[a-zA-Z0-9]+", string)) - string = string.lower() - string = string.replace(' ', '-') - return string - - -def format_command_string(command): - """ - Format command list of string for v1, v2, v3. - param: command: string - return: list - """ - command_list = [] - command = str(command) - command_list = command.replace("\n", "") - - try: - # try to convert the string into list - command_list = ast.literal_eval(command_list) - except (ValueError, SyntaxError) as e: - # special case - if "\n" in command: - command_list = command.split("\n") - else: - return command - except Exception as e: - return command - - if len(command_list) > 1: - longest_str = max(command_list, key=len) - - if len(longest_str) >= 30: - return [format_quotes(i) for i in command_list] - - return FSlist(command_list) - - return command_list[0] - - -def format_build(specified_version, build): - if isinstance(build, str): - return build - - build_str = build.get('build', None) - context_str = build.get('context', None) - ret = {} - - if specified_version < 2: - if build_str: - return build_str - elif context_str: - return context_str - else: - return None - - if build_str: - return build_str - - for _key, _val in build.items(): - if _val: - if _key in ['args', 'cache_from', 'labels']: - ret[_key] = format_key_val_pairs(_val) - else: - ret[_key] = _val - - return ret - - -def _remove_missing_and_underscored_keys(str): - if not str: return str - for key in list(str.keys()): - if isinstance(str[key], list): - str[key] = list(filter(None, str[key])) - if not str.get(key): - del str[key] - elif isinstance(str[key], dict): - str[key] = _remove_missing_and_underscored_keys(str[key]) - if str[key] is None or str[key] == {}: - del str[key] - - return str - - -def format_deploy(deploy): - ret = deploy - - with contextlib.suppress(Exception): - placement_preferences = deploy['placement']['preferences'] - ret['placement']['preferences'] = format_key_val_pairs(placement_preferences) - with contextlib.suppress(Exception): - labels = deploy['labels'] - ret['labels'] = format_key_val_pairs(labels) - ret = _remove_missing_and_underscored_keys(ret) - return ret - - def get_version(verion): try: return int(verion) except ValueError: return float(verion) - -def format_services_version_one(specified_version, services, volumes, networks): - services_formatted = {} - - for service in services: - service_formatted = {} - - if image := service.get('image', None): - image_tag = "latest" - - try: - image_tag = service['tag'] - service_formatted['image'] = f"{image}:{image_tag}" - except KeyError: - service_formatted['image'] = f"{image}" - - with contextlib.suppress(KeyError): - if service['container_name']: - service_formatted['container_name'] = service['container_name'] - with contextlib.suppress(KeyError): - if service['restart']: - service_formatted['restart'] = service['restart'] - with contextlib.suppress(KeyError): - if service['command']: - service_formatted['command'] = format_command_string(service['command']) - with contextlib.suppress(KeyError): - if service['entrypoint']: - service_formatted['entrypoint'] = format_command_string(service['entrypoint']) - with contextlib.suppress(KeyError): - if service['working_dir']: - service_formatted['working_dir'] = service['working_dir'] - with contextlib.suppress(KeyError): - if service['ports']: - service_formatted['ports'] = format_ports(service['ports']) - with contextlib.suppress(KeyError): - if links := service.get('links', []): - service_formatted['links'] = [] - for link in links: - for service_obj in services: - if link == service_obj['uuid']: - service_formatted['links'].append(f"{service_obj['name']}") - with contextlib.suppress(KeyError): - if service['environment']: - envs = service['environment'] - service_formatted['environment'] = format_key_val_pairs(envs) - with contextlib.suppress(KeyError): - service_volumes = service['volumes'] - if formatted_volumes := format_volumes(service_volumes, volumes): - service_formatted['volumes'] = formatted_volumes - else: - del service_formatted['volumes'] - with contextlib.suppress(KeyError): - if build := format_build(specified_version, service['build']): - service_formatted['build'] = build - services_formatted[service['name']] = service_formatted - - return services_formatted - - -def get_service_by_label_key(key, services): - for service in services: - with contextlib.suppress(KeyError): - if key == service["labels"]["key"]: - return service - - return None - - -def get_connected_services(service_key, connections, services): - connected_services = [] - for connection in connections: - if service_key == connection[0]: - if connected_service := get_service_by_label_key(connection[1], services): - connected_services.append(connected_service) - return connected_services - - -def format_services_version_three(specified_version, services, connections, volumes, networks): - services_formatted = {} - - for service in services: - service_formatted = {} - service_key = "" - - # add labels excluding certain keys - if labels := service.get('labels', {}): - clean_labels = {x: labels[x] for x in labels if x not in ["key"]} - if "key" in labels: - service_key = labels["key"] - if bool(clean_labels): - service_formatted['labels'] = clean_labels - - # image name - if image := service.get('image', None): - image_tag = "latest" - - try: - image_tag = service['tag'] - service_formatted['image'] = f"{image}:{image_tag}" - except KeyError: - service_formatted['image'] = f"{image}" - - # dependencies - with contextlib.suppress(KeyError): - if connected_services := get_connected_services(service_key, connections, services): - service_formatted['depends_on'] = [] - for connected_service in connected_services: - service_formatted['depends_on'].append(f"{connected_service['service_name']}") - with contextlib.suppress(KeyError): - if service['container_name']: - service_formatted['container_name'] = service['container_name'] - with contextlib.suppress(KeyError): - if service['restart']: - service_formatted['restart'] = service['restart'] - with contextlib.suppress(KeyError): - if service['command']: - service_formatted['command'] = format_command_string(service['command']) - with contextlib.suppress(KeyError): - if service['entrypoint']: - service_formatted['entrypoint'] = format_command_string(service['entrypoint']) - with contextlib.suppress(KeyError): - if working_dir_str := service['working_dir']: - service_formatted['working_dir'] = working_dir_str - with contextlib.suppress(KeyError): - if service['ports']: - service_formatted['ports'] = format_ports(service['ports']) - with contextlib.suppress(KeyError): - if service['environment']: - envs = service['environment'] - service_formatted['environment'] = format_key_val_pairs(envs) - with contextlib.suppress(KeyError): - service_volumes = service['volumes'] - if formatted_volumes := format_volumes(service_volumes, volumes): - service_formatted['volumes'] = formatted_volumes - else: - del service_formatted['volumes'] - with contextlib.suppress(KeyError): - service_networks = service.get('networks', []) - if formatted_networks := format_networks(service_networks, networks): - service_formatted['networks'] = formatted_networks - else: - del service_formatted['networks'] - with contextlib.suppress(KeyError): - if build := format_build(specified_version, service['build']): - service_formatted['build'] = build - if int(float(specified_version)) >= 3: - with contextlib.suppress(KeyError): - if deploy := format_deploy(service['deploy']): - service_formatted['deploy'] = deploy - services_formatted[service['service_name']] = service_formatted - - return services_formatted - - -def FSlist(l): # concert list into flow-style (default is block style) - from ruamel.yaml.comments import CommentedSeq - - double_quoted_list = [DoubleQuotedScalarString(x) for x in l] - cs = CommentedSeq(double_quoted_list) - cs.fa.set_flow_style() - return cs - - -def generate_dc(services, connections, volumes, networks, secrets, configs, version="3", return_format='yaml'): +def generate(services, volumes, networks, version="3", return_format='yaml'): if return_format != 'yaml': return @@ -487,153 +76,26 @@ def generate_dc(services, connections, volumes, networks, secrets, configs, vers base_version = int(specified_version) if services: + ret_yaml.dump({'version': DoubleQuotedScalarString(specified_version)}, s) + ret_yaml.explicit_start = False + s.write('\n') + if base_version in {2, 3}: - ret_yaml.dump({'version': DoubleQuotedScalarString(specified_version)}, s) - ret_yaml.explicit_start = False - s.write('\n') - services_formatted = format_services_version_three(specified_version, services, connections, volumes, networks) - ret_yaml.dump({'services': services_formatted}, s, transform=sequence_indent_four) + ret_yaml.dump({'services': services}, s, transform=sequence_indent_four) if base_version == 1: - ret_yaml.dump({'version': DoubleQuotedScalarString(specified_version)}, s) - ret_yaml.explicit_start = False - s.write('\n') - services_formatted = format_services_version_one(specified_version, services, volumes, networks) - ret_yaml.dump(services_formatted, s, transform=sequence_indent_one) + ret_yaml.dump(services, s, transform=sequence_indent_one) s.write('\n') if base_version in {3, 2} and networks: - networks_formatted = format_networks_top_level(networks, version) - ret_yaml.dump({'networks': networks_formatted}, s) + ret_yaml.dump({'networks': networks}, s) s.write('\n') if volumes: - volumes_formatted = format_volumes_top_level(volumes, version) - ret_yaml.dump({'volumes': volumes_formatted}, s) - s.write('\n') - - if secrets: - ret_yaml.dump({'secrets': secrets}, s) - s.write('\n') - - if configs: - ret_yaml.dump({'configs': configs}, s) + ret_yaml.dump({'volumes': volumes}, s) s.write('\n') s.seek(0) return s - - -def generate(cname): - c = docker.from_env() - - try: - cid = [x.short_id for x in c.containers.list() if cname == x.name or x.short_id in cname][0] - except IndexError: - sys.exit(1) - - cattrs = c.containers.get(cid).attrs - cfile = {cattrs['Name'][1:]: {}} - ct = cfile[cattrs['Name'][1:]] - - values = { - 'cap_add': cattrs['HostConfig']['CapAdd'], - 'cap_drop': cattrs['HostConfig']['CapDrop'], - 'cgroup_parent': cattrs['HostConfig']['CgroupParent'], - 'container_name': cattrs['Name'][1:], - 'devices': cattrs['HostConfig']['Devices'], - 'dns': cattrs['HostConfig']['Dns'], - 'dns_search': cattrs['HostConfig']['DnsSearch'], - 'environment': cattrs['Config']['Env'], - 'extra_hosts': cattrs['HostConfig']['ExtraHosts'], - 'image': cattrs['Config']['Image'], - 'labels': cattrs['Config']['Labels'], - 'links': cattrs['HostConfig']['Links'], - #'log_driver': cattrs['HostConfig']['LogConfig']['Type'], - #'log_opt': cattrs['HostConfig']['LogConfig']['Config'], - 'logging': {'driver': cattrs['HostConfig']['LogConfig']['Type'], 'options': cattrs['HostConfig']['LogConfig']['Config']}, - 'networks': {x: {'aliases': cattrs['NetworkSettings']['Networks'][x]['Aliases']} for x in cattrs['NetworkSettings']['Networks'].keys()}, - 'security_opt': cattrs['HostConfig']['SecurityOpt'], - 'ulimits': cattrs['HostConfig']['Ulimits'], - 'volumes': cattrs['HostConfig']['Binds'], - 'volume_driver': cattrs['HostConfig']['VolumeDriver'], - 'volumes_from': cattrs['HostConfig']['VolumesFrom'], - 'cpu_shares': cattrs['HostConfig']['CpuShares'], - 'cpuset': cattrs['HostConfig']['CpusetCpus']+','+cattrs['HostConfig']['CpusetMems'], - 'entrypoint': cattrs['Config']['Entrypoint'], - 'user': cattrs['Config']['User'], - 'working_dir': cattrs['Config']['WorkingDir'], - 'domainname': cattrs['Config']['Domainname'], - 'hostname': cattrs['Config']['Hostname'], - 'ipc': cattrs['HostConfig']['IpcMode'], - 'mac_address': cattrs['NetworkSettings']['MacAddress'], - 'mem_limit': cattrs['HostConfig']['Memory'], - 'memswap_limit': cattrs['HostConfig']['MemorySwap'], - 'privileged': cattrs['HostConfig']['Privileged'], - 'restart': cattrs['HostConfig']['RestartPolicy']['Name'], - 'read_only': cattrs['HostConfig']['ReadonlyRootfs'], - 'stdin_open': cattrs['Config']['OpenStdin'], - 'tty': cattrs['Config']['Tty'] - } - - networklist = c.networks.list() - networks = {network.attrs['Name']: {'external': (not network.attrs['Internal'])} for network in networklist if network.attrs['Name'] in values['networks'].keys()} - - # Check for command and add it if present. - if cattrs['Config']['Cmd'] != None: - values['command'] = " ".join(cattrs['Config']['Cmd']), - - # Check for exposed/bound ports and add them if needed. - try: - expose_value = list(cattrs['Config']['ExposedPorts'].keys()) - ports_value = [cattrs['HostConfig']['PortBindings'][key][0]['HostIp']+':'+cattrs['HostConfig']['PortBindings'][key][0]['HostPort']+':'+key for key in cattrs['HostConfig']['PortBindings']] - - # If bound ports found, don't use the 'expose' value. - if ports_value not in [None, "", [], 'null', {}, "default", 0, ",", "no"]: - for index, port in enumerate(ports_value): - if port[0] == ':': - ports_value[index] = port[1:] - - values['ports'] = ports_value - else: - values['expose'] = expose_value - - except (KeyError, TypeError): - # No ports exposed/bound. Continue without them. - ports = None - - # Iterate through values to finish building yaml dict. - for key, value in values.items(): - if value not in [None, "", [], 'null', {}, "default", 0, ",", "no"]: - ct[key] = value - - return cfile, networks - - -def generate_uuid(): - return uuid.uuid4().hex[:6].upper() - - -def random_string(string_length=10): - """ - Generate a random string of fixed length - :param string_length: integer - :return: string - """ - final_string = ''.join(random.choice( - string.ascii_uppercase + - string.ascii_lowercase + - string.digits) for _ in range(string_length)) - final_string = profanity.censor(final_string, '').lower() - - return final_string - - -def generate_rand_string(): - return "".join( - random.choice( - string.ascii_uppercase + string.ascii_lowercase + string.digits - ) for _ in range(16) - ) \ No newline at end of file diff --git a/services/frontend/configs/nginx/nginx.conf b/services/frontend/configs/nginx/nginx.conf index 217ccce..6e16d69 100644 --- a/services/frontend/configs/nginx/nginx.conf +++ b/services/frontend/configs/nginx/nginx.conf @@ -1,4 +1,3 @@ -user www-data; worker_processes 4; pid /run/nginx.pid; diff --git a/services/frontend/src/components/Canvas/VolumeNode.tsx b/services/frontend/src/components/Canvas/VolumeNode.tsx index 781e962..fd197d7 100644 --- a/services/frontend/src/components/Canvas/VolumeNode.tsx +++ b/services/frontend/src/components/Canvas/VolumeNode.tsx @@ -56,8 +56,14 @@ export default function VolumeNode(props: INodeProps) { )}
<> - {node.volumeConfig.name && ( + {node.canvasConfig.node_name && (
+ {truncateStr(node.canvasConfig.node_name, 12)} +
+ )} + + {node.volumeConfig.name && ( +
{truncateStr(node.volumeConfig.name, 20)}
)} diff --git a/services/frontend/src/components/Modal/Volume/Create.tsx b/services/frontend/src/components/Modal/Volume/Create.tsx index ec40630..fd75afe 100644 --- a/services/frontend/src/components/Modal/Volume/Create.tsx +++ b/services/frontend/src/components/Modal/Volume/Create.tsx @@ -23,6 +23,12 @@ const ModalVolumeCreate = (props: IModalVolumeCreate) => { formik.resetForm(); }; const validationSchema = yup.object({ + canvasConfig: yup.object({ + node_name: yup + .string() + .max(256, "volume name should be 256 characters or less") + .required("volume name is required") + }), volumeConfig: yup.object({ name: yup .string() diff --git a/services/frontend/src/components/Modal/Volume/Edit.tsx b/services/frontend/src/components/Modal/Volume/Edit.tsx index 2424560..9eaf6e6 100644 --- a/services/frontend/src/components/Modal/Volume/Edit.tsx +++ b/services/frontend/src/components/Modal/Volume/Edit.tsx @@ -4,7 +4,12 @@ import * as yup from "yup"; import { XIcon } from "@heroicons/react/outline"; import General from "./General"; import Labels from "./Labels"; -import { CallbackFunction, IVolumeNodeItem } from "../../../types"; +import { + CallbackFunction, + ICanvasConfig, + IVolumeNodeItem, + IVolumeTopLevel +} from "../../../types"; interface IModalVolumeEdit { node: IVolumeNodeItem; @@ -18,10 +23,17 @@ const ModalVolumeEdit = (props: IModalVolumeEdit) => { const [selectedNode, setSelectedNode] = useState(); const handleUpdate = (values: any) => { const updated = { ...selectedNode }; + updated.canvasConfig = values.canvasConfig; updated.volumeConfig = values.volumeConfig; onUpdateEndpoint(updated); }; const validationSchema = yup.object({ + canvasConfig: yup.object({ + node_name: yup + .string() + .max(256, "volume name should be 256 characters or less") + .required("volume name is required") + }), volumeConfig: yup.object({ name: yup .string() @@ -79,9 +91,12 @@ const ModalVolumeEdit = (props: IModalVolumeEdit) => { {selectedNode && ( { diff --git a/services/frontend/src/components/Modal/Volume/General.tsx b/services/frontend/src/components/Modal/Volume/General.tsx index 01d817a..fe1b2f0 100644 --- a/services/frontend/src/components/Modal/Volume/General.tsx +++ b/services/frontend/src/components/Modal/Volume/General.tsx @@ -3,6 +3,7 @@ import TextField from "../../global/FormElements/InputField"; const General = () => { return ( <> + ); diff --git a/services/frontend/src/types/index.ts b/services/frontend/src/types/index.ts index 0d88f2b..35cb56b 100644 --- a/services/frontend/src/types/index.ts +++ b/services/frontend/src/types/index.ts @@ -343,16 +343,11 @@ export interface IProjectPayload { }; } -export interface ISaturatedService extends Partial, ICanvasConfig {} - export interface IGeneratePayload { data: { - configs: []; - networks: []; - secrets: []; - services: ISaturatedService[]; - connections: [[string, string]]; version: number; - volumes: []; + networks: Partial[]; + services: Record>; + volumes: Record>; }; } diff --git a/services/frontend/src/utils/generators.ts b/services/frontend/src/utils/generators.ts index 7040ab7..546547e 100644 --- a/services/frontend/src/utils/generators.ts +++ b/services/frontend/src/utils/generators.ts @@ -1,40 +1,26 @@ -import { - IServiceNodeItem, - IGeneratePayload, - ISaturatedService -} from "../types"; -import { Dictionary } from "lodash"; - -const getServices = ( - graphNodes: Dictionary -): ISaturatedService[] => { - const ret: ISaturatedService[] = []; - for (const [, value] of Object.entries(graphNodes)) { - ret.push({ - ...value.canvasConfig, - ...value.serviceConfig - }); - } - - return ret; -}; +import { IGeneratePayload } from "../types"; export const flattenGraphData = (graphData: any): IGeneratePayload => { const nodes = graphData["nodes"]; const base: IGeneratePayload = { data: { version: 3, - configs: [], networks: [], - secrets: [], - services: [], - connections: graphData["connections"], - volumes: [] + services: {}, + volumes: {} } }; - getServices(nodes).forEach((x) => { - base.data.services.push(x); + Object.keys(nodes).forEach((key) => { + if (nodes[key].type === "SERVICE") { + base.data.services[nodes[key].canvasConfig.node_name] = + nodes[key].serviceConfig; + } + + if (nodes[key].type === "VOLUME") { + base.data.volumes[nodes[key].canvasConfig.node_name] = + nodes[key].volumeConfig; + } }); return base;