From 92696d0e6194332332370912714feeaaf7afaee4 Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 15:18:44 +0300 Subject: [PATCH 01/20] chore: README.md update --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 4dd21d3..9a9d79d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Container ToolKit -Visually generate docker compose manifests and deploy apps to AWS ECS (coming soon). +Visually generate docker compose & kubernetes manifests. ![Alt text](https://ctk-public.s3.amazonaws.com/ui.png?raw=true "UI") @@ -27,12 +27,6 @@ make dev_server ... this command will bring up the backend, the database, sync migrations, -## Project roadmap - -- Kubernetes manifest generation. -- ECS deployment. -- K8S deployment. - ## Docs - https://docs.jsplumbtoolkit.com/community/ From e665a0037f4fe36040087055baafc86ade1508ef Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 15:20:39 +0300 Subject: [PATCH 02/20] fix: cleanup docker-compose --- deploy/Makefile | 2 +- docker-compose.yml | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/deploy/Makefile b/deploy/Makefile index a70c33a..496a95f 100644 --- a/deploy/Makefile +++ b/deploy/Makefile @@ -12,7 +12,7 @@ endif build-image : docker build -t $(ORGANIZATION)/$(CONTAINER):$(VERSION) . -deploy : +run : docker run --rm --name $(CONTAINER) \ --env AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ --env AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ diff --git a/docker-compose.yml b/docker-compose.yml index a4acf52..73d0ca6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,9 +33,11 @@ services: - postgres volumes: - ./services/backend/src:/home/server/ + - ./services/backend/configs:/home/configs/ - django-static:/static/ ports: - "9001:9001" + - "9000:9000" environment: - DB_REMOTE=False - APP_URL= @@ -47,15 +49,8 @@ services: context: ./ dockerfile: ./services/frontend/Dockerfile image: corpulent/ctk-frontend:1.0.0 - depends_on: - - backend - links: - - backend volumes: - - ./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/configs/nginx/default.conf:/etc/nginx/conf.d/default.conf - ./services/frontend/build:/usr/share/nginx/html/ - - django-static:/home/server/static/ ports: - - "80:80" + - "8080:8080" From 595ff998d554538ca126e22b2ca425db34b623ef Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 15:21:16 +0300 Subject: [PATCH 03/20] fix: backend image fixes --- services/backend/Dockerfile | 23 +++++++++----------- services/backend/configs/supervisor/api.conf | 4 ++-- services/backend/configs/uwsgi/uwsgi.ini | 8 +++++-- services/backend/requirements.txt | 1 - 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/services/backend/Dockerfile b/services/backend/Dockerfile index e2a45b0..c29ff26 100644 --- a/services/backend/Dockerfile +++ b/services/backend/Dockerfile @@ -13,11 +13,15 @@ RUN apt-get update && \ postgresql-contrib \ wget \ nano \ - curl \ lsof \ + curl \ supervisor && \ rm -rf /var/lib/apt/lists/* +RUN wget https://github.com/kubernetes/kompose/releases/download/v1.26.1/kompose_1.26.1_amd64.deb +RUN apt install ./kompose_1.26.1_amd64.deb && \ + rm kompose_1.26.1_amd64.deb + RUN useradd uwsgi && adduser uwsgi root RUN useradd supervisor && adduser supervisor root @@ -26,19 +30,12 @@ RUN pip install --upgrade pip && \ pip install -r ./requirements.txt && \ rm ./requirements.txt -RUN touch /var/log/backend_out.log && \ - touch /var/log/django.log - -RUN chmod g+w -R /var/log/ - -EXPOSE 9000 9001 - COPY ./services/backend/src ./server COPY ./services/backend/configs/supervisor/api.conf /etc/supervisor/conf.d/api.conf -COPY ./services/backend/configs/uwsgi ./config/uwsgi +COPY ./services/backend/configs/uwsgi ./configs/uwsgi + +EXPOSE 9000 9001 -RUN rm -rf /tmp/uwsgi && \ - mkdir -p /tmp/uwsgi && \ - ln -s ./config/uwsgi/uwsgi.ini /tmp/uwsgi/ +HEALTHCHECK CMD curl --fail http://localhost:9000/v1 || exit 1 -CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/supervisord.conf"] +CMD ["/usr/local/bin/uwsgi", "--ini", "/home/configs/uwsgi/uwsgi.ini"] diff --git a/services/backend/configs/supervisor/api.conf b/services/backend/configs/supervisor/api.conf index 7cd1b64..fe12574 100644 --- a/services/backend/configs/supervisor/api.conf +++ b/services/backend/configs/supervisor/api.conf @@ -3,6 +3,6 @@ nodaemon=true [program:app] priority=1 -user = uwsgi -command = /usr/local/bin/uwsgi --ini /tmp/uwsgi/uwsgi.ini +user=uwsgi +command=/usr/local/bin/uwsgi --ini /home/config/uwsgi/uwsgi.ini autorestart=false diff --git a/services/backend/configs/uwsgi/uwsgi.ini b/services/backend/configs/uwsgi/uwsgi.ini index 40edf1a..6bf4dab 100644 --- a/services/backend/configs/uwsgi/uwsgi.ini +++ b/services/backend/configs/uwsgi/uwsgi.ini @@ -1,6 +1,10 @@ [uwsgi] ini = :base -socket = 0.0.0.0:9000 + +# use socket option a third-party router (nginx), +# use http option to set uwsgi to accept incoming +# HTTP requests and route them by itself +http = 0.0.0.0:9000 master = true processes = 5 @@ -8,7 +12,7 @@ processes = 5 [base] chdir = /home/server -module = server.wsgi:application +module = main.wsgi:application chmod-socket=666 uid = uwsgi gid = uwsgi diff --git a/services/backend/requirements.txt b/services/backend/requirements.txt index 52b8034..45010ad 100644 --- a/services/backend/requirements.txt +++ b/services/backend/requirements.txt @@ -1,6 +1,5 @@ django==4.0.4 django-cors-headers==3.11.0 -django-axes==5.32.0 djangorestframework==3.13.1 djangorestframework-simplejwt==5.1.0 django-storages==1.13.1 From 00462b27a7ecc2d9af93ef27729172d245a3597a Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 15:22:18 +0300 Subject: [PATCH 04/20] fix: ui image fixes --- services/frontend/Dockerfile | 4 +- services/frontend/configs/nginx/default.conf | 20 +++++++++ .../frontend/configs/nginx/localhost.conf | 38 ---------------- services/frontend/configs/nginx/nginx.conf | 43 ------------------- services/frontend/configs/nginx/uwsgi_params | 15 ------- 5 files changed, 22 insertions(+), 98 deletions(-) create mode 100644 services/frontend/configs/nginx/default.conf delete mode 100644 services/frontend/configs/nginx/localhost.conf delete mode 100644 services/frontend/configs/nginx/nginx.conf delete mode 100644 services/frontend/configs/nginx/uwsgi_params diff --git a/services/frontend/Dockerfile b/services/frontend/Dockerfile index 12a44bd..3a5e2fd 100644 --- a/services/frontend/Dockerfile +++ b/services/frontend/Dockerfile @@ -7,6 +7,6 @@ RUN npm run build FROM nginx:stable-alpine COPY --from=build /build/build /usr/share/nginx/html -COPY --from=build /build/configs/nginx/nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 +COPY --from=build /build/configs/nginx/default.conf /etc/nginx/conf.d/default.conf +EXPOSE 8080 CMD ["nginx", "-g", "daemon off;"] diff --git a/services/frontend/configs/nginx/default.conf b/services/frontend/configs/nginx/default.conf new file mode 100644 index 0000000..1272468 --- /dev/null +++ b/services/frontend/configs/nginx/default.conf @@ -0,0 +1,20 @@ +# vi:syntax=nginx + +server { + listen 8080; + listen [::]:8080; + server_name localhost; + + charset utf-8; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/services/frontend/configs/nginx/localhost.conf b/services/frontend/configs/nginx/localhost.conf deleted file mode 100644 index 91d7479..0000000 --- a/services/frontend/configs/nginx/localhost.conf +++ /dev/null @@ -1,38 +0,0 @@ -# vi:syntax=nginx - -upstream django { - server backend:9000; -} - -server { - listen 80; - listen [::]:80; - server_name localhost; - - charset utf-8; - - location /static { - alias /home/server/static; - } - - location /admin { - uwsgi_pass django; - include /home/config/uwsgi/uwsgi_params; - } - - location /api { - uwsgi_pass django; - include /home/config/uwsgi/uwsgi_params; - } - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} \ No newline at end of file diff --git a/services/frontend/configs/nginx/nginx.conf b/services/frontend/configs/nginx/nginx.conf deleted file mode 100644 index 6e16d69..0000000 --- a/services/frontend/configs/nginx/nginx.conf +++ /dev/null @@ -1,43 +0,0 @@ -worker_processes 4; -pid /run/nginx.pid; - -events { - worker_connections 768; - # multi_accept on; -} - -http { - # Basic Settings - tcp_nopush on; - tcp_nodelay on; - #keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - access_log /var/log/nginx/access.log ; - - sendfile on; - #tcp_nopush on; - client_max_body_size 20M; - - keepalive_timeout 0; - - uwsgi_read_timeout 86400; - uwsgi_send_timeout 86400; - - # Gzip Settings - - gzip on; - gzip_disable "msie6"; - - map $http_upgrade $connection_upgrade { - default upgrade; - '' close; - } - - # Virtual Host Configs - include /etc/nginx/conf.d/*.conf; - #include /etc/nginx/sites-enabled/*; -} \ No newline at end of file diff --git a/services/frontend/configs/nginx/uwsgi_params b/services/frontend/configs/nginx/uwsgi_params deleted file mode 100644 index 0bdf13b..0000000 --- a/services/frontend/configs/nginx/uwsgi_params +++ /dev/null @@ -1,15 +0,0 @@ -uwsgi_param QUERY_STRING $query_string; -uwsgi_param REQUEST_METHOD $request_method; -uwsgi_param CONTENT_TYPE $content_type; -uwsgi_param CONTENT_LENGTH $content_length; - -uwsgi_param REQUEST_URI $request_uri; -uwsgi_param PATH_INFO $document_uri; -uwsgi_param DOCUMENT_ROOT $document_root; -uwsgi_param SERVER_PROTOCOL $server_protocol; -uwsgi_param HTTPS $https if_not_empty; - -uwsgi_param REMOTE_ADDR $remote_addr; -uwsgi_param REMOTE_PORT $remote_port; -uwsgi_param SERVER_PORT $server_port; -uwsgi_param SERVER_NAME $server_name; \ No newline at end of file From 65b6a7eea70ef4eeae171feb819466a2a8c8b544 Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 15:24:03 +0300 Subject: [PATCH 05/20] feat: server kompose setup --- services/backend/src/api/routing.py | 4 +- services/backend/src/api/views/generate.py | 92 +++++++++++++++++++--- services/backend/src/api/views/utils.py | 28 +++++-- services/backend/src/api/views/view.py | 2 +- services/backend/src/main/settings.py | 7 +- 5 files changed, 108 insertions(+), 25 deletions(-) diff --git a/services/backend/src/api/routing.py b/services/backend/src/api/routing.py index 72e9f02..28f53e7 100644 --- a/services/backend/src/api/routing.py +++ b/services/backend/src/api/routing.py @@ -17,7 +17,9 @@ api_urls = [ path("projects/", project.ProjectListCreateAPIView.as_view()), path("projects/import/", project.ProjectImportAPIView.as_view()), path("projects//", project.ProjectGenericAPIView.as_view()), - path("generate/", generate.GenerateGenericAPIView.as_view()), + path("generate/", generate.GenerateDockerComposeView.as_view()), + path("generate/docker-compose", generate.GenerateDockerComposeView.as_view()), + path("generate/kubernetes", generate.GenerateK8sView.as_view()), path("auth/self/", user.UserGenericAPIView.as_view()), path("auth/", include("dj_rest_auth.urls")), path("auth/github/", auth.GitHubLogin.as_view(), name="github_login"), diff --git a/services/backend/src/api/views/generate.py b/services/backend/src/api/views/generate.py index 9ffeae3..2aff923 100644 --- a/services/backend/src/api/views/generate.py +++ b/services/backend/src/api/views/generate.py @@ -1,11 +1,31 @@ +import re +import io +import shutil import json +import subprocess as sp + +from ruamel.yaml import YAML +from pathlib import Path from rest_framework import generics, status from rest_framework.response import Response +from .utils import ( + generate, clean_dict, get_random_string, read_dir) + -from .utils import generate +def generate_docker_compose(data): + version = data.get('version', '3') + services = data.get('services', None) + volumes = data.get('volumes', None) + networks = data.get('networks', None) + return generate( + services, + volumes, + networks, + version=version, + return_format='yaml') -class GenerateGenericAPIView(generics.GenericAPIView): +class GenerateDockerComposeView(generics.GenericAPIView): permission_classes = [] def get(self, request): @@ -13,18 +33,64 @@ class GenerateGenericAPIView(generics.GenericAPIView): def post(self, request, format=None): request_data = json.loads(request.data) - version = request_data['data'].get('version', '3') - services = request_data['data'].get('services', None) - volumes = request_data['data'].get('volumes', None) - networks = request_data['data'].get('networks', None) - - code = generate( - services, - volumes, - networks, - version=version, - return_format='yaml') + code = generate_docker_compose(request_data["data"]) resp = {'code': code} + return Response(resp, status=status.HTTP_200_OK) + +class GenerateK8sView(generics.GenericAPIView): + permission_classes = [] + + def get(self, request): + return Response({}, status=status.HTTP_404_NOT_FOUND) + + def post(self, request, format=None): + resp = { + 'code': "", + 'error': "" + } + workdir = f"/tmp/{get_random_string(8)}" + request_data = json.loads(request.data) + omitted = clean_dict(request_data["data"], ["env_file", "build", "secrets"]) + docker_compose_code = generate_docker_compose(omitted) + path = Path(workdir) + path.mkdir(exist_ok=True) + + with open(f"{path}/docker-compose.yaml", 'w') as f: + f.write(docker_compose_code) + + process = sp.Popen([ + "kompose", + "--suppress-warnings", + "--file", + f"{path}/docker-compose.yaml", "convert" + ], cwd=workdir, stdout=sp.PIPE, stderr=sp.PIPE) + process.wait() + _, out = process.communicate() + + if out: + out = out.decode("utf-8") + parts = out.split(" ") + parts.pop() + parts.pop(0) + final_list = [re.sub(r'\[.*?;.*?m', '', x) for x in parts if any(x)] + resp["error"] = " ".join(final_list) + + workdir_files = read_dir(workdir) + workdir_files.remove("docker-compose.yaml") + + for file in workdir_files: + with open(f"{workdir}/{file}") as f: + yaml = YAML() + yaml.indent(mapping=2, sequence=4, offset=2) + yaml.explicit_start = True + data = yaml.load(f) + + del data["metadata"]["annotations"] + del data["spec"]["template"]["metadata"]["annotations"] + buf = io.BytesIO() + yaml.dump(data, buf) + resp["code"] = buf.getvalue() + shutil.rmtree(workdir) return Response(resp, status=status.HTTP_200_OK) diff --git a/services/backend/src/api/views/utils.py b/services/backend/src/api/views/utils.py index 8d933d8..c496f68 100644 --- a/services/backend/src/api/views/utils.py +++ b/services/backend/src/api/views/utils.py @@ -1,5 +1,8 @@ import io +import os import contextlib +import random +import string from ruamel.yaml import YAML from ruamel.yaml.scalarstring import DoubleQuotedScalarString @@ -58,11 +61,11 @@ def sequence_indent_one(s): return ret_val -def get_version(verion): +def get_version(version): try: - return int(verion) + return int(version) except ValueError: - return float(verion) + return float(version) def generate(services, volumes, networks, version="3", return_format='yaml'): if return_format != 'yaml': @@ -95,8 +98,23 @@ def generate(services, volumes, networks, version="3", return_format='yaml'): if volumes: ret_yaml.dump({'volumes': volumes}, s) - s.write('\n') s.seek(0) + return s.read() + +def clean_dict(dic, omit=None): + if type(dic) is dict: + for key, item in dic.copy().items(): + if omit and key in omit: + del dic[key] + elif type(item) is dict: + dic[key] = clean_dict(item, omit) + + return dic + +def get_random_string(length): + letters = string.ascii_lowercase + return ''.join(random.choice(letters) for _ in range(length)) - return s +def read_dir(path): + return [f for f in os.listdir(path) if os.path.isfile(f"{path}/{f}")] diff --git a/services/backend/src/api/views/view.py b/services/backend/src/api/views/view.py index 486d5b6..ba6992f 100644 --- a/services/backend/src/api/views/view.py +++ b/services/backend/src/api/views/view.py @@ -6,4 +6,4 @@ class ViewGenericAPIView(generics.GenericAPIView): permission_classes = [] def get(self, request): - return Response({}, status=status.HTTP_404_NOT_FOUND) + return Response({}, status=status.HTTP_200_OK) diff --git a/services/backend/src/main/settings.py b/services/backend/src/main/settings.py index 3e71290..e9e41b9 100644 --- a/services/backend/src/main/settings.py +++ b/services/backend/src/main/settings.py @@ -56,7 +56,6 @@ INSTALLED_APPS = [ "dj_rest_auth.registration", "storages", "corsheaders", - "axes", "organizations", "api", ] @@ -69,8 +68,7 @@ MIDDLEWARE = [ "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "axes.middleware.AxesMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware" ] ROOT_URLCONF = "main.urls" @@ -162,8 +160,7 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" AUTHENTICATION_BACKENDS = [ "django.contrib.auth.backends.ModelBackend", - "allauth.account.auth_backends.AuthenticationBackend", - "axes.backends.AxesBackend", + "allauth.account.auth_backends.AuthenticationBackend" ] REST_FRAMEWORK = { From 674c0934bbaecc8e3af88ecfd8dffdc65b049fa2 Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 15:24:23 +0300 Subject: [PATCH 06/20] chore: node types version bump --- services/frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/frontend/package.json b/services/frontend/package.json index f60986f..30899d0 100644 --- a/services/frontend/package.json +++ b/services/frontend/package.json @@ -80,7 +80,7 @@ "@types/d3": "^7.1.0", "@types/jest": "^27.4.0", "@types/lodash": "^4.14.178", - "@types/node": "^16.11.22", + "@types/node": "^16.11.56", "@types/react": "^17.0.45", "@types/react-dom": "^17.0.11", "@types/uuid": "^8.3.4", From 5485572d134ee4e6733a23896cc315fc5b4186d1 Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 16:05:40 +0300 Subject: [PATCH 07/20] fix: add missing visibility field to IProject --- services/frontend/src/types/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/frontend/src/types/index.ts b/services/frontend/src/types/index.ts index eadd3c5..825fea6 100644 --- a/services/frontend/src/types/index.ts +++ b/services/frontend/src/types/index.ts @@ -21,6 +21,7 @@ export interface IServiceNodePosition { export interface IProject { id: number; name: string; + visibility: number; uuid: string; data: string; created_at: string; From a44e44cd0cce2989f9e0f5cedab9e0e7789a6d55 Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 16:06:01 +0300 Subject: [PATCH 08/20] feat: k8s and docker svg logos --- .../src/components/global/dc-logo.tsx | 29 +++++++++++++++++++ .../src/components/global/k8s-logo.tsx | 26 +++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 services/frontend/src/components/global/dc-logo.tsx create mode 100644 services/frontend/src/components/global/k8s-logo.tsx diff --git a/services/frontend/src/components/global/dc-logo.tsx b/services/frontend/src/components/global/dc-logo.tsx new file mode 100644 index 0000000..992074d --- /dev/null +++ b/services/frontend/src/components/global/dc-logo.tsx @@ -0,0 +1,29 @@ +const DcLogo = () => { + return ( + + + + + + + + + + + + ); +}; + +export default DcLogo; diff --git a/services/frontend/src/components/global/k8s-logo.tsx b/services/frontend/src/components/global/k8s-logo.tsx new file mode 100644 index 0000000..9faf79c --- /dev/null +++ b/services/frontend/src/components/global/k8s-logo.tsx @@ -0,0 +1,26 @@ +const K8sLogo = () => { + return ( + + + + + + ); +}; + +export default K8sLogo; From b4c987721812bed7ba778433ddd4ea921183a07b Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 16:06:33 +0300 Subject: [PATCH 09/20] fix: show toaster on project save and errors --- services/frontend/src/hooks/useProject.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/frontend/src/hooks/useProject.ts b/services/frontend/src/hooks/useProject.ts index ab4178e..1622bef 100644 --- a/services/frontend/src/hooks/useProject.ts +++ b/services/frontend/src/hooks/useProject.ts @@ -137,6 +137,7 @@ export const useUpdateProject = (uuid: string | undefined) => { }, { onSuccess: (projectData) => { + toaster("Project saved!", "success"); queryClient.setQueryData(["projects", uuid], projectData); } } @@ -157,9 +158,9 @@ export const useDeleteProject = (uuid: string | undefined) => { return data; } catch (err: any) { if (err.response.status === 404) { - // console.error("Resource could not be found!"); + toaster("Resource could not be found!", "error"); } else { - // console.error(err.message); + toaster(err.message, "error"); } } }, @@ -180,6 +181,7 @@ export const useDeleteProject = (uuid: string | undefined) => { } else { queryClient.invalidateQueries(["projects"]); } + toaster("Project deleted!", "success"); } } ); From 1f27a0541bffe5f767dda3851407f1a7303c7d6d Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 16:07:09 +0300 Subject: [PATCH 10/20] fix: event bus type fix --- services/frontend/src/events/eventBus.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/frontend/src/events/eventBus.ts b/services/frontend/src/events/eventBus.ts index 8214bf8..0d18654 100644 --- a/services/frontend/src/events/eventBus.ts +++ b/services/frontend/src/events/eventBus.ts @@ -7,7 +7,7 @@ const eventBus = { }, dispatch( event: string, - data: { message: { id: string } | { id: string } | { node: any } } + data: { message: { id: string } | { data: any } | { node: any } } ) { document.dispatchEvent(new CustomEvent(event, { detail: data })); }, From 5b826f6a273931c2eac5dce4ea51c1380645a77d Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 16:08:23 +0300 Subject: [PATCH 11/20] feat: support k8s in ui, some component separation --- .../src/components/Project/CodeBox.tsx | 156 +++++++++++ .../src/components/Project/Header.tsx | 106 +++++++ .../src/components/Project/ManifestSelect.tsx | 51 ++++ .../frontend/src/components/Project/index.tsx | 261 +++--------------- .../frontend/src/components/useJsPlumb.ts | 4 +- services/frontend/src/services/generate.ts | 13 +- 6 files changed, 371 insertions(+), 220 deletions(-) create mode 100644 services/frontend/src/components/Project/CodeBox.tsx create mode 100644 services/frontend/src/components/Project/Header.tsx create mode 100644 services/frontend/src/components/Project/ManifestSelect.tsx diff --git a/services/frontend/src/components/Project/CodeBox.tsx b/services/frontend/src/components/Project/CodeBox.tsx new file mode 100644 index 0000000..8e7e633 --- /dev/null +++ b/services/frontend/src/components/Project/CodeBox.tsx @@ -0,0 +1,156 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import YAML from "yaml"; +import { debounce } from "lodash"; +import { generatePayload } from "../../utils/generators"; +import { checkHttpStatus } from "../../services/helpers"; +import { generateHttp } from "../../services/generate"; +import { toaster } from "../../utils"; +import eventBus from "../../events/eventBus"; +import ManifestSelect from "./ManifestSelect"; +import CodeEditor from "../CodeEditor"; +import useWindowDimensions from "../../hooks/useWindowDimensions"; + +const CodeBox = () => { + const versionRef = useRef(); + const manifestRef = useRef(); + const [language, setLanguage] = useState("yaml"); + const [version, setVersion] = useState("3"); + const [copyText, setCopyText] = useState("Copy"); + const [generatedCode, setGeneratedCode] = useState(""); + const [formattedCode, setFormattedCode] = useState(""); + const [manifest, setManifest] = useState(0); + const { height } = useWindowDimensions(); + + versionRef.current = version; + manifestRef.current = manifest; + + const getCode = (payload: any, manifest: number) => { + generateHttp(JSON.stringify(payload), manifest) + .then(checkHttpStatus) + .then((data) => { + if (data["code"]) { + setGeneratedCode(data["code"]); + } else { + setGeneratedCode(""); + } + + if (data["error"].length) { + setGeneratedCode(""); + toaster(`error ${data["error"]}`, "error"); + } + }) + .catch(() => undefined) + .finally(() => undefined); + }; + + const debouncedOnGraphUpdate = useMemo( + () => + debounce((payload, manifest) => { + getCode(payload, manifest); + }, 600), + [] + ); + + const versionChange = (e: any) => { + setVersion(e.target.value); + }; + + const copy = () => { + navigator.clipboard.writeText(formattedCode); + setCopyText("Copied"); + + setTimeout(() => { + setCopyText("Copy"); + }, 300); + }; + + useEffect(() => { + if (language === "json") { + setFormattedCode( + JSON.stringify(YAML.parseAllDocuments(generatedCode), null, 2) + ); + } + + if (language === "yaml") { + setFormattedCode(generatedCode); + } + }, [language, generatedCode]); + + useEffect(() => { + eventBus.dispatch("GENERATE", { + message: { + id: "" + } + }); + }, [version, manifest]); + + useEffect(() => { + eventBus.on("FETCH_CODE", (data) => { + const graphData = data.detail.message; + graphData.version = versionRef.current; + debouncedOnGraphUpdate(generatePayload(graphData), manifestRef.current); + }); + + return () => { + eventBus.remove("FETCH_CODE", () => undefined); + }; + }, []); + + return ( + <> +
+ + + + + +
+ +
+ +
+ + { + return; + }} + disabled={true} + lineWrapping={false} + height={height - 64} + /> + + ); +}; + +export default CodeBox; diff --git a/services/frontend/src/components/Project/Header.tsx b/services/frontend/src/components/Project/Header.tsx new file mode 100644 index 0000000..00b19cc --- /dev/null +++ b/services/frontend/src/components/Project/Header.tsx @@ -0,0 +1,106 @@ +import { useEffect, useState } from "react"; +import { CallbackFunction, IProject } from "../../types"; +import Spinner from "../global/Spinner"; +import VisibilitySwitch from "../global/VisibilitySwitch"; + +interface IManifestSelectProps { + onSave: CallbackFunction; + isLoading: boolean; + projectData: IProject; + isAuthenticated: boolean; +} + +const ManifestSelect = (props: IManifestSelectProps) => { + const { onSave, isLoading, projectData, isAuthenticated } = props; + const [visibility, setVisibility] = useState(false); + const [projectName, setProjectName] = useState("Untitled"); + + const handleNameChange = (e: any) => { + setProjectName(e.target.value); + }; + + const handleSave = () => { + const data: any = { + name: projectName, + visibility: +visibility + }; + + onSave(data); + }; + + useEffect(() => { + if (!projectData) { + return; + } + + setProjectName(projectData.name); + setVisibility(Boolean(projectData.visibility)); + }, [projectData]); + + return ( + <> +
+
+ + +
+ {isAuthenticated && ( + { + setVisibility(!visibility); + }} + /> + )} + + +
+
+
+ + ); +}; + +export default ManifestSelect; diff --git a/services/frontend/src/components/Project/ManifestSelect.tsx b/services/frontend/src/components/Project/ManifestSelect.tsx new file mode 100644 index 0000000..6508085 --- /dev/null +++ b/services/frontend/src/components/Project/ManifestSelect.tsx @@ -0,0 +1,51 @@ +import { useState } from "react"; +import DcLogo from "../global/dc-logo"; +import K8sLogo from "../global/k8s-logo"; + +const styles = { + default: { + filter: "grayscale(100%)", + opacity: "90%" + }, + selected: { + filter: "grayscale(0)", + opacity: "100%" + } +}; + +interface IManifestSelectProps { + setManifest: any; +} + +const ManifestSelect = (props: IManifestSelectProps) => { + const { setManifest } = props; + const [selected, setSelected] = useState(0); + + return ( + <> + + + + + ); +}; + +export default ManifestSelect; diff --git a/services/frontend/src/components/Project/index.tsx b/services/frontend/src/components/Project/index.tsx index 37e3130..cc7bc99 100644 --- a/services/frontend/src/components/Project/index.tsx +++ b/services/frontend/src/components/Project/index.tsx @@ -1,25 +1,22 @@ -import { useEffect, useState, useRef, useMemo } from "react"; +import { useEffect, useState, useRef } from "react"; import { useParams } from "react-router-dom"; -import { debounce, Dictionary, omit } from "lodash"; -import YAML from "yaml"; +import { Dictionary, omit } from "lodash"; +import _ from "lodash"; import { GlobeAltIcon, CubeIcon, FolderAddIcon } from "@heroicons/react/solid"; import { - IProjectPayload, IServiceNodeItem, IVolumeNodeItem, IServiceNodePosition, IProject, - IEditServiceFormDependsOn + IProjectPayload, } from "../../types"; import eventBus from "../../events/eventBus"; -import { useMutation } from "react-query"; import { + createProject, useProject, - useUpdateProject, - createProject + useUpdateProject } from "../../hooks/useProject"; import useWindowDimensions from "../../hooks/useWindowDimensions"; -import { generatePayload } from "../../utils/generators"; import { nodeLibraries } from "../../utils/data/libraries"; import { getClientNodeItem, @@ -28,8 +25,6 @@ import { getClientNodesAndConnections, getMatchingSetIndex } from "../../utils"; -import { checkHttpStatus } from "../../services/helpers"; -import { generateHttp } from "../../services/generate"; import { Canvas } from "../Canvas"; import Spinner from "../global/Spinner"; import ModalConfirmDelete from "../modals/ConfirmDelete"; @@ -38,10 +33,10 @@ import ModalServiceEdit from "../modals/docker-compose/service/Edit"; import ModalNetwork from "../modals/docker-compose/network"; import CreateVolumeModal from "../modals/docker-compose/volume/CreateVolumeModal"; import EditVolumeModal from "../modals/docker-compose/volume/EditVolumeModal"; -import CodeEditor from "../CodeEditor"; import { useTitle } from "../../hooks"; -import VisibilitySwitch from "../global/VisibilitySwitch"; -import _ from "lodash"; +import CodeBox from "./CodeBox"; +import Header from "./Header"; +import { useMutation } from "react-query"; interface IProjectProps { isAuthenticated: boolean; @@ -56,10 +51,8 @@ export default function Project(props: IProjectProps) { useRef>(); const stateConnectionsRef = useRef<[[string, string]] | []>(); const stateNetworksRef = useRef({}); + const stateProjectRef = useRef(); - const [isVisible, setIsVisible] = useState(false); - const [generatedCode, setGeneratedCode] = useState(); - const [formattedCode, setFormattedCode] = useState(""); const [showModalCreateService, setShowModalCreateService] = useState(false); const [showVolumesModal, setShowVolumesModal] = useState(false); const [showNetworksModal, setShowNetworksModal] = useState(false); @@ -74,14 +67,9 @@ export default function Project(props: IProjectProps) { const [volumeToDelete, setVolumeToDelete] = useState( null ); - const [language, setLanguage] = useState("yaml"); - const [version, setVersion] = useState("3"); - const [copyText, setCopyText] = useState("Copy"); const [nodes, setNodes] = useState>({}); const [connections, setConnections] = useState<[[string, string]] | []>([]); const [networks, setNetworks] = useState>({}); - const [projectName, setProjectName] = useState("Untitled"); - const [canvasPosition, setCanvasPosition] = useState({ top: 0, left: 0, @@ -109,10 +97,7 @@ export default function Project(props: IProjectProps) { stateNodesRef.current = nodes; stateConnectionsRef.current = connections; stateNetworksRef.current = networks; - - const handleNameChange = (e: any) => { - setProjectName(e.target.value); - }; + stateProjectRef.current = data; const onNodeUpdate = (positionData: IServiceNodePosition) => { if (stateNodesRef.current) { @@ -124,36 +109,6 @@ export default function Project(props: IProjectProps) { } }; - const onSave = () => { - const payload: IProjectPayload = { - name: projectName, - visibility: +isVisible, - data: { - canvas: { - position: canvasPosition, - nodes: nodes, - connections: connections, - networks: networks - } - } - }; - - if (uuid) { - updateProjectMutation.mutate(payload); - } else { - createProjectMutation.mutate(payload); - } - }; - - const copy = () => { - navigator.clipboard.writeText(formattedCode); - setCopyText("Copied"); - - setTimeout(() => { - setCopyText("Copy"); - }, 300); - }; - useEffect(() => { if (!data) { return; @@ -167,42 +122,41 @@ export default function Project(props: IProjectProps) { nodesAsList, nodeLibraries ); - - setProjectName(data.name); - setIsVisible(Boolean(data.visibility)); setNodes(clientNodeItems); setConnections(canvasData.canvas.connections); setNetworks(canvasData.canvas.networks); setCanvasPosition(canvasData.canvas.position); }, [data]); - const debouncedOnGraphUpdate = useMemo( - () => - debounce((payload) => { - generateHttp(JSON.stringify(payload)) - .then(checkHttpStatus) - .then((data) => { - if (data["code"].length) { - for (let i = 0; i < data["code"].length; ++i) { - data["code"][i] = data["code"][i].replace(/(\r\n|\n|\r)/gm, ""); - } + const onSave = (partial: any) => { + const base: IProjectPayload = { + name: data?.name ?? "", + visibility: data?.visibility ?? 0, + data: { + canvas: { + position: canvasPosition, + nodes: nodes, + connections: connections, + networks: networks + } + } + }; - const code = data["code"].join("\n"); - setGeneratedCode(code); - } - }) - .catch(() => undefined) - .finally(() => undefined); - }, 600), - [] - ); + const payload = { ...base, ...partial }; + + if (uuid) { + updateProjectMutation.mutate(payload); + } else { + createProjectMutation.mutate(payload); + } + }; const onGraphUpdate = (graphData: any) => { const data = { ...graphData }; - data.version = version; data.networks = stateNetworksRef.current; - const payload = generatePayload(data); - debouncedOnGraphUpdate(payload); + eventBus.dispatch("FETCH_CODE", { + message: data + }); }; const onCanvasUpdate = (updatedCanvasPosition: any) => { @@ -404,32 +358,6 @@ export default function Project(props: IProjectProps) { eventBus.dispatch("NODE_DELETED", { message: { node: node } }); }; - const versionChange = (e: any) => { - setVersion(e.target.value); - }; - - useEffect(() => { - if (!generatedCode) { - return; - } - - if (language === "json") { - setFormattedCode(JSON.stringify(YAML.parse(generatedCode), null, 2)); - } - - if (language === "yaml") { - setFormattedCode(generatedCode); - } - }, [language, generatedCode]); - - useEffect(() => { - eventBus.dispatch("GENERATE", { - message: { - id: "" - } - }); - }, [version]); - if (!isFetching) { if (!error) { return ( @@ -495,70 +423,15 @@ export default function Project(props: IProjectProps) { ) : null}
-
-
- - -
- {isAuthenticated && ( - { - setIsVisible(!isVisible); - }} - /> - )} - - -
-
-
+
-
- - - - - -
- - { - return; - }} - disabled={true} - lineWrapping={false} - height={height - 64} - /> +
diff --git a/services/frontend/src/components/useJsPlumb.ts b/services/frontend/src/components/useJsPlumb.ts index d622486..295a4b5 100644 --- a/services/frontend/src/components/useJsPlumb.ts +++ b/services/frontend/src/components/useJsPlumb.ts @@ -433,13 +433,13 @@ export const useJsPlumb = ( useEffect(() => { eventBus.on("GENERATE", () => { - if (!instance) return; + if (!instanceRef.current) return; if (stateRef.current) { onGraphUpdate({ nodes: stateRef.current, connections: getConnections( - instance.getConnections({}, true) as Connection[] + instanceRef.current.getConnections({}, true) as Connection[] ) }); } diff --git a/services/frontend/src/services/generate.ts b/services/frontend/src/services/generate.ts index c6d3901..09b6b29 100644 --- a/services/frontend/src/services/generate.ts +++ b/services/frontend/src/services/generate.ts @@ -1,7 +1,16 @@ import { API_SERVER_URL } from "../constants"; -export const generateHttp = (data: string) => { - return fetch(`${API_SERVER_URL}/generate/`, { +export const generateHttp = (data: string, manifest: number) => { + let endpoint = `${API_SERVER_URL}/generate/`; + if (manifest === 0) { + endpoint += "docker-compose"; + } + + if (manifest === 1) { + endpoint += "kubernetes"; + } + + return fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" From 425ba5c96c2565225c1d9f37232fce9759575a30 Mon Sep 17 00:00:00 2001 From: Samuel Rowe Date: Wed, 7 Sep 2022 13:41:41 +0530 Subject: [PATCH 12/20] feat: updated to show original logo color for manifest select on hover --- .../src/components/Project/ManifestSelect.tsx | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/services/frontend/src/components/Project/ManifestSelect.tsx b/services/frontend/src/components/Project/ManifestSelect.tsx index 6508085..4b8e2dd 100644 --- a/services/frontend/src/components/Project/ManifestSelect.tsx +++ b/services/frontend/src/components/Project/ManifestSelect.tsx @@ -1,17 +1,22 @@ -import { useState } from "react"; +import { styled } from "@mui/joy"; +import { useCallback, useState } from "react"; import DcLogo from "../global/dc-logo"; import K8sLogo from "../global/k8s-logo"; -const styles = { - default: { - filter: "grayscale(100%)", - opacity: "90%" - }, - selected: { - filter: "grayscale(0)", - opacity: "100%" +interface IButtonProps { + selected: boolean; +} + +const Button = styled("button", { + shouldForwardProp: (name) => name !== "selected" +})` + filter: grayscale(${({ selected }) => (selected ? "0%" : "100%")}); + opacity: ${({ selected }) => (selected ? "100%" : "80%")}; + + &:hover { + filter: grayscale(0%); } -}; +`; interface IManifestSelectProps { setManifest: any; @@ -21,29 +26,25 @@ const ManifestSelect = (props: IManifestSelectProps) => { const { setManifest } = props; const [selected, setSelected] = useState(0); + const handleK8s = useCallback(() => { + setManifest(1); + setSelected(1); + }, []); + + const handleDC = useCallback(() => { + setManifest(0); + setSelected(0); + }, []); + return ( <> - - - + + + ); }; From cb48917164a2fd97de63f740f170e5b20047d887 Mon Sep 17 00:00:00 2001 From: corpulent Date: Tue, 6 Sep 2022 16:08:23 +0300 Subject: [PATCH 13/20] feat: support k8s in ui, some component separation --- services/frontend/src/components/Project/ManifestSelect.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/frontend/src/components/Project/ManifestSelect.tsx b/services/frontend/src/components/Project/ManifestSelect.tsx index 4b8e2dd..404f079 100644 --- a/services/frontend/src/components/Project/ManifestSelect.tsx +++ b/services/frontend/src/components/Project/ManifestSelect.tsx @@ -9,7 +9,7 @@ interface IButtonProps { const Button = styled("button", { shouldForwardProp: (name) => name !== "selected" -})` +}) ` filter: grayscale(${({ selected }) => (selected ? "0%" : "100%")}); opacity: ${({ selected }) => (selected ? "100%" : "80%")}; @@ -49,4 +49,4 @@ const ManifestSelect = (props: IManifestSelectProps) => { ); }; -export default ManifestSelect; +export default ManifestSelect; \ No newline at end of file From 868f31ea0061ab835eef269ee5d482ace376d857 Mon Sep 17 00:00:00 2001 From: corpulent Date: Wed, 7 Sep 2022 16:13:16 +0300 Subject: [PATCH 14/20] fix: mui/material switch --- services/frontend/src/components/Project/ManifestSelect.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/frontend/src/components/Project/ManifestSelect.tsx b/services/frontend/src/components/Project/ManifestSelect.tsx index 404f079..56dd4c8 100644 --- a/services/frontend/src/components/Project/ManifestSelect.tsx +++ b/services/frontend/src/components/Project/ManifestSelect.tsx @@ -1,4 +1,4 @@ -import { styled } from "@mui/joy"; +import { styled } from "@mui/material"; import { useCallback, useState } from "react"; import DcLogo from "../global/dc-logo"; import K8sLogo from "../global/k8s-logo"; @@ -9,7 +9,7 @@ interface IButtonProps { const Button = styled("button", { shouldForwardProp: (name) => name !== "selected" -}) ` +})` filter: grayscale(${({ selected }) => (selected ? "0%" : "100%")}); opacity: ${({ selected }) => (selected ? "100%" : "80%")}; @@ -49,4 +49,4 @@ const ManifestSelect = (props: IManifestSelectProps) => { ); }; -export default ManifestSelect; \ No newline at end of file +export default ManifestSelect; From 5837b4b04e82bdf18431e22fc9f45e526cdd71d4 Mon Sep 17 00:00:00 2001 From: corpulent Date: Wed, 7 Sep 2022 16:30:26 +0300 Subject: [PATCH 15/20] fix: callback wrap --- services/frontend/src/components/Project/Header.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/services/frontend/src/components/Project/Header.tsx b/services/frontend/src/components/Project/Header.tsx index 00b19cc..fee2fdd 100644 --- a/services/frontend/src/components/Project/Header.tsx +++ b/services/frontend/src/components/Project/Header.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { CallbackFunction, IProject } from "../../types"; import Spinner from "../global/Spinner"; import VisibilitySwitch from "../global/VisibilitySwitch"; @@ -15,18 +15,18 @@ const ManifestSelect = (props: IManifestSelectProps) => { const [visibility, setVisibility] = useState(false); const [projectName, setProjectName] = useState("Untitled"); - const handleNameChange = (e: any) => { + const handleNameChange = useCallback((e: any) => { setProjectName(e.target.value); - }; + }, []); - const handleSave = () => { + const handleSave = useCallback(() => { const data: any = { name: projectName, visibility: +visibility }; onSave(data); - }; + }, []); useEffect(() => { if (!projectData) { From 1c8876eab12f5577a13a45f1e71c605e83309237 Mon Sep 17 00:00:00 2001 From: corpulent Date: Wed, 7 Sep 2022 16:30:39 +0300 Subject: [PATCH 16/20] fix: manifest types --- .../src/components/Project/CodeBox.tsx | 13 +++++------ .../src/components/Project/ManifestSelect.tsx | 23 +++++++++++++------ services/frontend/src/constants/index.ts | 4 ++++ services/frontend/src/services/generate.ts | 7 +++--- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/services/frontend/src/components/Project/CodeBox.tsx b/services/frontend/src/components/Project/CodeBox.tsx index 8e7e633..d765da2 100644 --- a/services/frontend/src/components/Project/CodeBox.tsx +++ b/services/frontend/src/components/Project/CodeBox.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import YAML from "yaml"; import { debounce } from "lodash"; +import { manifestTypes } from "../../constants"; import { generatePayload } from "../../utils/generators"; import { checkHttpStatus } from "../../services/helpers"; import { generateHttp } from "../../services/generate"; @@ -12,19 +13,19 @@ import useWindowDimensions from "../../hooks/useWindowDimensions"; const CodeBox = () => { const versionRef = useRef(); - const manifestRef = useRef(); + const manifestRef = useRef(); const [language, setLanguage] = useState("yaml"); const [version, setVersion] = useState("3"); const [copyText, setCopyText] = useState("Copy"); const [generatedCode, setGeneratedCode] = useState(""); const [formattedCode, setFormattedCode] = useState(""); - const [manifest, setManifest] = useState(0); + const [manifest, setManifest] = useState(manifestTypes.DOCKER_COMPOSE); const { height } = useWindowDimensions(); versionRef.current = version; manifestRef.current = manifest; - const getCode = (payload: any, manifest: number) => { + const getCode = (payload: any, manifest: string) => { generateHttp(JSON.stringify(payload), manifest) .then(checkHttpStatus) .then((data) => { @@ -34,13 +35,11 @@ const CodeBox = () => { setGeneratedCode(""); } - if (data["error"].length) { + if (data["error"]) { setGeneratedCode(""); toaster(`error ${data["error"]}`, "error"); } - }) - .catch(() => undefined) - .finally(() => undefined); + }); }; const debouncedOnGraphUpdate = useMemo( diff --git a/services/frontend/src/components/Project/ManifestSelect.tsx b/services/frontend/src/components/Project/ManifestSelect.tsx index 56dd4c8..1444f5d 100644 --- a/services/frontend/src/components/Project/ManifestSelect.tsx +++ b/services/frontend/src/components/Project/ManifestSelect.tsx @@ -1,5 +1,6 @@ import { styled } from "@mui/material"; import { useCallback, useState } from "react"; +import { manifestTypes } from "../../constants"; import DcLogo from "../global/dc-logo"; import K8sLogo from "../global/k8s-logo"; @@ -24,25 +25,33 @@ interface IManifestSelectProps { const ManifestSelect = (props: IManifestSelectProps) => { const { setManifest } = props; - const [selected, setSelected] = useState(0); + const [selected, setSelected] = useState(manifestTypes.DOCKER_COMPOSE); const handleK8s = useCallback(() => { - setManifest(1); - setSelected(1); + setManifest(manifestTypes.KUBERNETES); + setSelected(manifestTypes.KUBERNETES); }, []); const handleDC = useCallback(() => { - setManifest(0); - setSelected(0); + setManifest(manifestTypes.DOCKER_COMPOSE); + setSelected(manifestTypes.DOCKER_COMPOSE); }, []); return ( <> - - diff --git a/services/frontend/src/constants/index.ts b/services/frontend/src/constants/index.ts index 0ca9c77..0e89549 100644 --- a/services/frontend/src/constants/index.ts +++ b/services/frontend/src/constants/index.ts @@ -4,3 +4,7 @@ export const REACT_APP_GITHUB_CLIENT_ID = export const REACT_APP_GITHUB_SCOPE = process.env.REACT_APP_GITHUB_SCOPE; export const PROJECTS_FETCH_LIMIT = 300; export const LOCAL_STORAGE = "CtkLocalStorage"; +export const manifestTypes = { + DOCKER_COMPOSE: "DOCKER_COMPOSE", + KUBERNETES: "KUBERNETES" +}; diff --git a/services/frontend/src/services/generate.ts b/services/frontend/src/services/generate.ts index 09b6b29..1f8364c 100644 --- a/services/frontend/src/services/generate.ts +++ b/services/frontend/src/services/generate.ts @@ -1,12 +1,13 @@ +import { manifestTypes } from "../constants"; import { API_SERVER_URL } from "../constants"; -export const generateHttp = (data: string, manifest: number) => { +export const generateHttp = (data: string, manifest: string) => { let endpoint = `${API_SERVER_URL}/generate/`; - if (manifest === 0) { + if (manifest === manifestTypes.DOCKER_COMPOSE) { endpoint += "docker-compose"; } - if (manifest === 1) { + if (manifest === manifestTypes.KUBERNETES) { endpoint += "kubernetes"; } From 4f967a1f1b59e689b626e412bb6433b52861725c Mon Sep 17 00:00:00 2001 From: corpulent Date: Thu, 8 Sep 2022 13:53:50 +0300 Subject: [PATCH 17/20] chore: eslint fix --- services/frontend/src/components/Project/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/frontend/src/components/Project/index.tsx b/services/frontend/src/components/Project/index.tsx index cc7bc99..c3ecb79 100644 --- a/services/frontend/src/components/Project/index.tsx +++ b/services/frontend/src/components/Project/index.tsx @@ -8,7 +8,7 @@ import { IVolumeNodeItem, IServiceNodePosition, IProject, - IProjectPayload, + IProjectPayload } from "../../types"; import eventBus from "../../events/eventBus"; import { From 7ae450dbbebfc9c8d889abef032aefccf61d2242 Mon Sep 17 00:00:00 2001 From: corpulent Date: Thu, 8 Sep 2022 15:03:08 +0300 Subject: [PATCH 18/20] fix: suppress key errors, minor fixes --- services/backend/src/api/views/generate.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/services/backend/src/api/views/generate.py b/services/backend/src/api/views/generate.py index 2aff923..9ab7eb7 100644 --- a/services/backend/src/api/views/generate.py +++ b/services/backend/src/api/views/generate.py @@ -1,3 +1,4 @@ +import contextlib import re import io import shutil @@ -50,7 +51,9 @@ class GenerateK8sView(generics.GenericAPIView): } workdir = f"/tmp/{get_random_string(8)}" request_data = json.loads(request.data) - omitted = clean_dict(request_data["data"], ["env_file", "build", "secrets"]) + omitted = clean_dict( + request_data["data"], + ["env_file", "build", "secrets", "profiles"]) docker_compose_code = generate_docker_compose(omitted) path = Path(workdir) path.mkdir(exist_ok=True) @@ -85,12 +88,17 @@ class GenerateK8sView(generics.GenericAPIView): yaml.explicit_start = True data = yaml.load(f) - del data["metadata"]["annotations"] - del data["spec"]["template"]["metadata"]["annotations"] + with contextlib.suppress(KeyError): + del data["metadata"]["annotations"] + with contextlib.suppress(KeyError): + del data["spec"]["template"]["metadata"]["annotations"] buf = io.BytesIO() yaml.dump(data, buf) - resp["code"] = buf.getvalue() + resp["code"] += buf.getvalue().decode() + + if file != workdir_files[-1]: + resp["code"] += "\n" shutil.rmtree(workdir) return Response(resp, status=status.HTTP_200_OK) From 6dcf7f22fd776cea06bbe04155495ff9e5f7660d Mon Sep 17 00:00:00 2001 From: corpulent Date: Thu, 8 Sep 2022 15:03:36 +0300 Subject: [PATCH 19/20] fix: data references --- .../src/components/Project/Header.tsx | 20 +++++++++++++------ .../frontend/src/components/Project/index.tsx | 11 +++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/services/frontend/src/components/Project/Header.tsx b/services/frontend/src/components/Project/Header.tsx index fee2fdd..c78cae6 100644 --- a/services/frontend/src/components/Project/Header.tsx +++ b/services/frontend/src/components/Project/Header.tsx @@ -1,28 +1,32 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { CallbackFunction, IProject } from "../../types"; import Spinner from "../global/Spinner"; import VisibilitySwitch from "../global/VisibilitySwitch"; -interface IManifestSelectProps { +interface IHeaderProps { onSave: CallbackFunction; isLoading: boolean; projectData: IProject; isAuthenticated: boolean; } -const ManifestSelect = (props: IManifestSelectProps) => { +const Header = (props: IHeaderProps) => { const { onSave, isLoading, projectData, isAuthenticated } = props; const [visibility, setVisibility] = useState(false); const [projectName, setProjectName] = useState("Untitled"); + const visibilityRef = useRef(false); + const projectNameRef = useRef("Untitled"); + const handleNameChange = useCallback((e: any) => { setProjectName(e.target.value); + projectNameRef.current = e.target.value; }, []); const handleSave = useCallback(() => { const data: any = { - name: projectName, - visibility: +visibility + name: projectNameRef.current, + visibility: +visibilityRef.current }; onSave(data); @@ -35,6 +39,9 @@ const ManifestSelect = (props: IManifestSelectProps) => { setProjectName(projectData.name); setVisibility(Boolean(projectData.visibility)); + + visibilityRef.current = Boolean(projectData.visibility); + projectNameRef.current = projectData.name; }, [projectData]); return ( @@ -81,6 +88,7 @@ const ManifestSelect = (props: IManifestSelectProps) => { isVisible={visibility} onToggle={() => { setVisibility(!visibility); + visibilityRef.current = !visibility; }} /> )} @@ -103,4 +111,4 @@ const ManifestSelect = (props: IManifestSelectProps) => { ); }; -export default ManifestSelect; +export default Header; diff --git a/services/frontend/src/components/Project/index.tsx b/services/frontend/src/components/Project/index.tsx index c3ecb79..6d9d5b5 100644 --- a/services/frontend/src/components/Project/index.tsx +++ b/services/frontend/src/components/Project/index.tsx @@ -1,7 +1,6 @@ import { useEffect, useState, useRef } from "react"; import { useParams } from "react-router-dom"; -import { Dictionary, omit } from "lodash"; -import _ from "lodash"; +import { Dictionary, omit, remove } from "lodash"; import { GlobeAltIcon, CubeIcon, FolderAddIcon } from "@heroicons/react/solid"; import { IServiceNodeItem, @@ -135,9 +134,9 @@ export default function Project(props: IProjectProps) { data: { canvas: { position: canvasPosition, - nodes: nodes, - connections: connections, - networks: networks + nodes: stateNodesRef.current, + connections: stateConnectionsRef.current, + networks: stateNetworksRef.current } } }; @@ -279,7 +278,7 @@ export default function Project(props: IProjectProps) { dependsOnKeys.forEach((key: string) => { if (key === targetServiceName) { if (Array.isArray(sourceDependsOn)) { - _.remove(sourceDependsOn, (key) => key === targetServiceName); + remove(sourceDependsOn, (key) => key === targetServiceName); } if (sourceDependsOn && sourceDependsOn.constructor === Object) { From 05d1b23c47a51efa5fd8b162b9c624c6737e0b63 Mon Sep 17 00:00:00 2001 From: corpulent Date: Thu, 8 Sep 2022 15:33:48 +0300 Subject: [PATCH 20/20] fix: remove double loader --- services/frontend/src/components/Project/Header.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/services/frontend/src/components/Project/Header.tsx b/services/frontend/src/components/Project/Header.tsx index c78cae6..5186e4d 100644 --- a/services/frontend/src/components/Project/Header.tsx +++ b/services/frontend/src/components/Project/Header.tsx @@ -99,7 +99,6 @@ const Header = (props: IHeaderProps) => { className="btn-util text-white bg-green-600 hover:bg-green-700 sm:w-auto" >
- {isLoading && } {isLoading && } Save