|
|
|
@ -1,4 +1,14 @@
|
|
|
|
import uuid
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
import requests
|
|
|
|
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
import copy
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
import hashlib
|
|
|
|
|
|
|
|
import random
|
|
|
|
|
|
|
|
import string
|
|
|
|
|
|
|
|
import networkx as nx
|
|
|
|
|
|
|
|
|
|
|
|
from rest_framework import generics, status
|
|
|
|
from rest_framework import generics, status
|
|
|
|
from rest_framework.permissions import IsAuthenticated
|
|
|
|
from rest_framework.permissions import IsAuthenticated
|
|
|
|
from rest_framework.response import Response
|
|
|
|
from rest_framework.response import Response
|
|
|
|
@ -11,6 +21,142 @@ from organizations.utils import get_user_org
|
|
|
|
|
|
|
|
|
|
|
|
from .utils import get_project_obj_by_uuid
|
|
|
|
from .utils import get_project_obj_by_uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
base_network = {
|
|
|
|
|
|
|
|
"key": "",
|
|
|
|
|
|
|
|
"type": "",
|
|
|
|
|
|
|
|
"position": {
|
|
|
|
|
|
|
|
"left": 0,
|
|
|
|
|
|
|
|
"top": 0
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"inputs": [],
|
|
|
|
|
|
|
|
"outputs": [],
|
|
|
|
|
|
|
|
"canvasConfig": {
|
|
|
|
|
|
|
|
"node_name": ""
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
base_node = {
|
|
|
|
|
|
|
|
"key": "",
|
|
|
|
|
|
|
|
"type": "",
|
|
|
|
|
|
|
|
"position": {
|
|
|
|
|
|
|
|
"left": 0,
|
|
|
|
|
|
|
|
"top": 0
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"inputs": [],
|
|
|
|
|
|
|
|
"outputs": [],
|
|
|
|
|
|
|
|
"canvasConfig": {
|
|
|
|
|
|
|
|
"node_name": ""
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
base_project = {
|
|
|
|
|
|
|
|
"canvas": {
|
|
|
|
|
|
|
|
"position": {
|
|
|
|
|
|
|
|
"top": 0,
|
|
|
|
|
|
|
|
"left": 0,
|
|
|
|
|
|
|
|
"scale": 1
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
"nodes": {},
|
|
|
|
|
|
|
|
"connections": [],
|
|
|
|
|
|
|
|
"networks": {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def normalize_position(pos):
|
|
|
|
|
|
|
|
offet = 200
|
|
|
|
|
|
|
|
position_offset = 150
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
"top": (pos[0] * position_offset) + offet,
|
|
|
|
|
|
|
|
"left": (pos[1] * position_offset) + offet
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_service_node(key, uuid, service_config, positions):
|
|
|
|
|
|
|
|
node = copy.deepcopy(base_node)
|
|
|
|
|
|
|
|
node["canvasConfig"]["node_name"] = key
|
|
|
|
|
|
|
|
node["serviceConfig"] = service_config
|
|
|
|
|
|
|
|
node["key"] = uuid
|
|
|
|
|
|
|
|
node["type"] = "SERVICE"
|
|
|
|
|
|
|
|
node["inputs"] = [f"ip_{uuid}"]
|
|
|
|
|
|
|
|
node["position"] = normalize_position(positions[uuid])
|
|
|
|
|
|
|
|
node["outputs"] = [f"op_{uuid}"]
|
|
|
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_network_node(key, uuid, network_config, positions):
|
|
|
|
|
|
|
|
node = copy.deepcopy(base_node)
|
|
|
|
|
|
|
|
node["canvasConfig"]["node_name"] = key
|
|
|
|
|
|
|
|
node["networkConfig"] = network_config
|
|
|
|
|
|
|
|
node["key"] = uuid
|
|
|
|
|
|
|
|
node["position"] = normalize_position(positions[uuid])
|
|
|
|
|
|
|
|
node["type"] = "NETWORK"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectImportAPIView(generics.CreateAPIView):
|
|
|
|
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
|
|
|
|
serializer_class = ProjectSerializer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create(self, request, *args, **kwargs):
|
|
|
|
|
|
|
|
org = get_user_org(request.user)
|
|
|
|
|
|
|
|
data = { **request.data }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
project = copy.deepcopy(base_project)
|
|
|
|
|
|
|
|
req = requests.get(data["url"], allow_redirects=True)
|
|
|
|
|
|
|
|
import_data = yaml.safe_load(req.content)
|
|
|
|
|
|
|
|
project_hash = str(int(hashlib.sha1(data["url"].encode("utf-8")).hexdigest(), 16) % (10 ** 8))
|
|
|
|
|
|
|
|
project_uuid = "_".join(["".join([chr(ord('a') + int(x)) for x in project_hash]), "".join(random.choices(string.ascii_lowercase, k=5))])
|
|
|
|
|
|
|
|
node_name_uuid = {}
|
|
|
|
|
|
|
|
graph = nx.Graph()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "services" in import_data:
|
|
|
|
|
|
|
|
for key in import_data["services"]:
|
|
|
|
|
|
|
|
node_uuid = f"service-{uuid.uuid4()}"
|
|
|
|
|
|
|
|
node_name_uuid[key] = node_uuid
|
|
|
|
|
|
|
|
graph.add_node(node_uuid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "networks" in import_data:
|
|
|
|
|
|
|
|
for key in import_data["networks"]:
|
|
|
|
|
|
|
|
node_uuid = f"network-{uuid.uuid4()}"
|
|
|
|
|
|
|
|
node_name_uuid[key] = node_uuid
|
|
|
|
|
|
|
|
graph.add_node(node_uuid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
positions = nx.spring_layout(graph, k=1, iterations=50)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "services" in import_data:
|
|
|
|
|
|
|
|
for key in import_data["services"]:
|
|
|
|
|
|
|
|
service_uuid = node_name_uuid[key]
|
|
|
|
|
|
|
|
service_config = import_data["services"][key]
|
|
|
|
|
|
|
|
project["canvas"]["nodes"][service_uuid] = (
|
|
|
|
|
|
|
|
build_service_node(key, service_uuid, service_config, positions))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "depends_on" in import_data["services"][key]:
|
|
|
|
|
|
|
|
for dep in import_data["services"][key]["depends_on"]:
|
|
|
|
|
|
|
|
project["canvas"]["connections"].append(
|
|
|
|
|
|
|
|
[service_uuid, node_name_uuid[dep]])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "networks" in import_data:
|
|
|
|
|
|
|
|
for key in import_data["networks"]:
|
|
|
|
|
|
|
|
network_uuid = node_name_uuid[key]
|
|
|
|
|
|
|
|
network_config = import_data["networks"][key]
|
|
|
|
|
|
|
|
project["canvas"]["nodes"][network_uuid] = (
|
|
|
|
|
|
|
|
build_network_node(key, network_uuid, network_config, positions))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
obj = Project.objects.get(uuid=project_uuid, org=org)
|
|
|
|
|
|
|
|
obj.data = json.dumps(project)
|
|
|
|
|
|
|
|
obj.visibility = data["visibility"]
|
|
|
|
|
|
|
|
obj.save()
|
|
|
|
|
|
|
|
return Response(
|
|
|
|
|
|
|
|
ProjectSerializer(obj).data,
|
|
|
|
|
|
|
|
status=status.HTTP_201_CREATED)
|
|
|
|
|
|
|
|
except Project.DoesNotExist:
|
|
|
|
|
|
|
|
obj = Project(
|
|
|
|
|
|
|
|
org=org,
|
|
|
|
|
|
|
|
name=project_uuid,
|
|
|
|
|
|
|
|
uuid=project_uuid,
|
|
|
|
|
|
|
|
visibility=data["visibility"],
|
|
|
|
|
|
|
|
data=json.dumps(project))
|
|
|
|
|
|
|
|
obj.save()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return Response(ProjectSerializer(obj).data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectListCreateAPIView(generics.ListCreateAPIView):
|
|
|
|
class ProjectListCreateAPIView(generics.ListCreateAPIView):
|
|
|
|
permission_classes = []
|
|
|
|
permission_classes = []
|
|
|
|
@ -66,7 +212,11 @@ class ProjectGenericAPIView(generics.GenericAPIView):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
org = None if request.user.is_anonymous else get_user_org(request.user)
|
|
|
|
org = None if request.user.is_anonymous else get_user_org(request.user)
|
|
|
|
if project_obj := get_project_obj_by_uuid(uuid):
|
|
|
|
if project_obj := get_project_obj_by_uuid(uuid):
|
|
|
|
return Response(ProjectSerializer(project_obj).data)
|
|
|
|
if project_obj.org == org:
|
|
|
|
|
|
|
|
return Response(ProjectSerializer(project_obj).data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if project_obj.visibility == 1:
|
|
|
|
|
|
|
|
return Response(ProjectSerializer(project_obj).data)
|
|
|
|
except Exception as e:
|
|
|
|
except Exception as e:
|
|
|
|
return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
return Response(str(e), status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
|
|
|
|
@ -75,19 +225,21 @@ class ProjectGenericAPIView(generics.GenericAPIView):
|
|
|
|
def put(self, request, uuid):
|
|
|
|
def put(self, request, uuid):
|
|
|
|
org = None if request.user.is_anonymous else get_user_org(request.user)
|
|
|
|
org = None if request.user.is_anonymous else get_user_org(request.user)
|
|
|
|
if project_obj := get_project_obj_by_uuid(uuid):
|
|
|
|
if project_obj := get_project_obj_by_uuid(uuid):
|
|
|
|
data = request.data
|
|
|
|
if project_obj.org == org:
|
|
|
|
serializer = ProjectSerializer(project_obj, data=data)
|
|
|
|
data = request.data
|
|
|
|
|
|
|
|
serializer = ProjectSerializer(project_obj, data=data)
|
|
|
|
|
|
|
|
|
|
|
|
if serializer.is_valid():
|
|
|
|
if serializer.is_valid():
|
|
|
|
serializer.save()
|
|
|
|
serializer.save()
|
|
|
|
return Response(serializer.data)
|
|
|
|
return Response(serializer.data)
|
|
|
|
|
|
|
|
|
|
|
|
return Response(serializer.errors, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
return Response(serializer.errors, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
return Response({}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
return Response({}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
|
|
|
|
|
|
|
def delete(self, request, uuid):
|
|
|
|
def delete(self, request, uuid):
|
|
|
|
org = None if request.user.is_anonymous else get_user_org(request.user)
|
|
|
|
org = None if request.user.is_anonymous else get_user_org(request.user)
|
|
|
|
if project_obj := get_project_obj_by_uuid(uuid):
|
|
|
|
if project_obj := get_project_obj_by_uuid(uuid):
|
|
|
|
project_obj.delete()
|
|
|
|
if project_obj.org == org:
|
|
|
|
return Response({}, status=status.HTTP_204_NO_CONTENT)
|
|
|
|
project_obj.delete()
|
|
|
|
|
|
|
|
return Response({}, status=status.HTTP_204_NO_CONTENT)
|
|
|
|
return Response({}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
return Response({}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|