diff --git a/Dockerfile b/Dockerfile index dc8f6864..93212cdc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,28 @@ -# build environment -FROM node:12-alpine as builder -WORKDIR /app -ENV PATH /app/node_modules/.bin:$PATH -COPY ui/package.json /app/package.json -RUN npm install -COPY ui /app - -RUN npm run build - -# production environment -FROM python:3.7-slim -ENV PYTHONUNBUFFERED=1 -RUN apt-get update && apt-get install nginx vim -y --no-install-recommends -WORKDIR /code -COPY api/requirements.txt /code/ -RUN pip install -r requirements.txt --no-cache-dir -COPY api /code/ -COPY --from=builder /app/build /usr/share/nginx/html -COPY nginx.conf /etc/nginx/sites-available/default -RUN ln -sf /dev/stdout /var/log/nginx/access.log \ - && ln -sf /dev/stderr /var/log/nginx/error.log -RUN chmod +x /code/start_server.sh -RUN chown -R www-data:www-data /code -EXPOSE 80 -STOPSIGNAL SIGTERM -CMD ["/code/start_server.sh"] +# build environment +FROM node:10-alpine as builder +WORKDIR /app +ENV PATH /app/node_modules/.bin:$PATH +COPY ui/package.json /app/package.json +RUN npm install +RUN npm i react-router-dom +COPY ui /app + +RUN npm run build + +# production environment +FROM python:3.7-slim +ENV PYTHONUNBUFFERED=1 +RUN apt-get update && apt-get install nginx vim -y --no-install-recommends +WORKDIR /code +COPY api/requirements.txt /code/ +RUN pip install -r requirements.txt --no-cache-dir +COPY api /code/ +COPY --from=builder /app/build /usr/share/nginx/html +COPY nginx.conf /etc/nginx/sites-available/default +RUN ln -sf /dev/stdout /var/log/nginx/access.log \ + && ln -sf /dev/stderr /var/log/nginx/error.log +RUN chmod +x /code/start_server.sh +RUN chown -R www-data:www-data /code +EXPOSE 80 +STOPSIGNAL SIGTERM +CMD ["/code/start_server.sh"] \ No newline at end of file diff --git a/api/.env.dev b/api/.env.dev index 64f3ba90..9e61a9bf 100644 --- a/api/.env.dev +++ b/api/.env.dev @@ -3,17 +3,21 @@ export ENVIRONMENT=dev export REDIS_BROKER_URL=redis://localhost:6379/0 export ZEPPELIN_HOST=http://localhost export ZEPPELIN_PORT=8080 +export POD_NAMESPACE=test ## DB SETTINGS -export POSTGRES_DB_HOST="localhost" -export POSTGRES_DB_USERNAME="postgres" -export POSTGRES_DB_PASSWORD="postgres" -export POSTGRES_DB_SCHEMA="cuelake" +export POSTGRES_DB_HOST=localhost +export POSTGRES_DB_USERNAME=postgres +export POSTGRES_DB_PASSWORD=postgres +export POSTGRES_DB_SCHEMA=cuelake export POSTGRES_DB_PORT=5432 ## Metastore Settings -export METASTORE_POSTGRES_HOST="localhost" +export METASTORE_POSTGRES_HOST=localhost export METASORE_POSTGRES_PORT=5432 -export METASORE_POSTGRES_USERNAME="postgres" -export METASORE_POSTGRES_PASSWORD="postgres" -export METASORE_POSTGRES_DATABASE="cuelake_metastore" +export METASORE_POSTGRES_USERNAME=postgres +export METASORE_POSTGRES_PASSWORD=postgres +export METASORE_POSTGRES_DATABASE=cuelake + +## KUBE CONFIG CONFIGURATION LOCATION FOR DOCKER COMPOSE DEVLOPMENT +export KUBECONFIG=/.kube/config diff --git a/api/Dockerfile.dev b/api/Dockerfile.dev index 1eb0b796..a43b14bd 100644 --- a/api/Dockerfile.dev +++ b/api/Dockerfile.dev @@ -1,10 +1,14 @@ -# production environment -FROM python:3.7-slim -ENV PYTHONUNBUFFERED=1 -RUN apt-get update && apt-get install nginx vim -y --no-install-recommends -WORKDIR /code -COPY requirements.txt /code/ -RUN pip install -r requirements.txt --no-cache-dir -EXPOSE 8000 -STOPSIGNAL SIGTERM -CMD ["/code/start_server.dev.sh"] +# production environment +FROM python:3.7-slim +ENV PYTHONUNBUFFERED=1 +RUN apt-get update && apt-get install nginx vim -y --no-install-recommends +RUN apt-get install -y apt-transport-https ca-certificates curl lsof +RUN curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg +RUN echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | tee /etc/apt/sources.list.d/kubernetes.list +RUN apt-get update && apt-get install -y kubectl +WORKDIR /code +COPY requirements.txt /code/ +RUN pip install -r requirements.txt --no-cache-dir +EXPOSE 8000 +STOPSIGNAL SIGTERM +CMD ["/code/start_server.dev.sh"] diff --git a/api/app/settings.py b/api/app/settings.py index 8a2bbe45..1ec60c96 100644 --- a/api/app/settings.py +++ b/api/app/settings.py @@ -32,6 +32,7 @@ ALLOWED_HOSTS = ["*", "localhost"] CORS_ORIGIN_ALLOW_ALL = True HTTP_HTTPS = "http://" +DEFAULT_AUTO_FIELD='django.db.models.AutoField' # Application definition INSTALLED_APPS = [ @@ -46,7 +47,8 @@ 'system', 'rest_framework', 'django_celery_beat', - 'workflows' + 'workflows', + 'workspace' ] MIDDLEWARE = [ @@ -166,5 +168,4 @@ METASORE_POSTGRES_PASSWORD = os.environ.get("METASORE_POSTGRES_PASSWORD", "postgres") METASORE_POSTGRES_DATABASE = os.environ.get("METASORE_POSTGRES_DATABASE", "cuelake_metastore") -HADOOP_S3_PREFIX = os.environ.get("HADOOP_S3_PREFIX", "cuelake/") -SOME_KEY = "AKIAGWHGSIECJLJN7VGX" +HADOOP_S3_PREFIX = os.environ.get("HADOOP_S3_PREFIX", "cuelake/") \ No newline at end of file diff --git a/api/app/urls.py b/api/app/urls.py index 645f56a6..40c478cc 100644 --- a/api/app/urls.py +++ b/api/app/urls.py @@ -21,4 +21,5 @@ path("api/genie/", include("genie.urls")), path("api/system/", include("system.urls")), path("api/workflows/", include("workflows.urls")), + path("api/workspace/", include("workspace.urls")), ] diff --git a/api/genie/services/notebookJobs.py b/api/genie/services/notebookJobs.py index 61b6d270..caafdac0 100644 --- a/api/genie/services/notebookJobs.py +++ b/api/genie/services/notebookJobs.py @@ -1,3 +1,4 @@ +from utils.helperFunctions import helperFunctions import asyncio import json import pytz @@ -10,7 +11,7 @@ from genie.serializers import NotebookObjectSerializer, NotebookRunLogsSerializer from workflows.models import Workflow, WorkflowNotebookMap from utils.apiResponse import ApiResponse -from utils.zeppelinAPI import Zeppelin, ZeppelinAPI +from utils.zeppelinAPI import ZeppelinAPI from genie.tasks import runNotebookJob as runNotebookJobTask from django.conf import settings @@ -28,27 +29,29 @@ class NotebookJobServices: Class containing services related to NotebookJob model """ @staticmethod - async def _fetchNotebookStatuses(notebooks: list): + async def _fetchNotebookStatuses(notebooks: list, workspaceId: int = 0): """ Async method to fetch notebook status details for multiple notebooks Returns a dict with notebook ids as keys :param notebooks: List of notebook describing dicts each containing the 'id' field """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) notebookStatuses = {} - for future in asyncio.as_completed([Zeppelin.getNotebookStatus(notebook["id"]) for notebook in notebooks]): + for future in asyncio.as_completed([ZeppelinAPI(workspaceName).getNotebookStatus(notebook["id"]) for notebook in notebooks]): status = await future notebookStatuses[status["id"]] = status return notebookStatuses @staticmethod - def getNotebooks(offset: int = 0, limit: int = None , searchQuery: str = None, sorter: dict = None, _filter: dict = None): + def getNotebooks(offset: int = 0, limit: int = None , searchQuery: str = None, sorter: dict = None, _filter: dict = None, workspaceId: int = 0): """ Service to fetch and serialize NotebookJob objects Number of NotebookObjects fetched is stored as the constant GET_NOTEBOOKOJECTS_LIMIT :param offset: Offset for fetching NotebookJob objects """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error retrieving notebooks") - notebooks = Zeppelin.getAllNotebooks() + notebooks = ZeppelinAPI(workspaceName).getAllNotebooks() if searchQuery: notebooks = NotebookJobServices.search(notebooks, "path", searchQuery) if sorter.get('order', False): @@ -159,12 +162,13 @@ def sortingOnNotebook(notebooks, sorter, _filter): return notebooks @staticmethod - def archivedNotebooks(): + def archivedNotebooks(workspaceId: int = 0): """ Get archived notebooks """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error retrieving archived notebooks") - notebooks = Zeppelin.getAllNotebooks("~Trash") + notebooks = ZeppelinAPI(workspaceName).getAllNotebooks("~Trash") if notebooks: res.update(True, "Archived notebooks retrieved successfully", notebooks) return res @@ -183,10 +187,11 @@ def getNotebookObject(notebookObjId: int): @staticmethod - def getNotebooksLight(): + def getNotebooksLight(workspaceId: int = 0): """ Gets concise notebook data""" + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error retrieving notebooks") - notebooks = Zeppelin.getAllNotebooks() + notebooks = ZeppelinAPI(workspaceName).getAllNotebooks() res.update(True, "Notebooks retrieved successfully", notebooks) return res @@ -235,7 +240,7 @@ def _prepareNotebookJson(notebookTemplate: NotebookTemplate, payload: dict): @staticmethod - def addNotebook(payload: dict): + def addNotebook(payload: dict, workspaceId: int = 0): """ Service to create and add a template based notebook :param payload: Dict containing notebook template info @@ -244,28 +249,30 @@ def addNotebook(payload: dict): defaultPayload = payload.copy() notebookTemplate = NotebookTemplate.objects.get(id=payload.get("notebookTemplateId", 0)) notebook, connection = NotebookJobServices._prepareNotebookJson(notebookTemplate, payload) - notebookZeppelinId = Zeppelin.addNotebook(notebook) + workspaceName = helperFunctions.getWorkspaceName(workspaceId) + notebookZeppelinId = ZeppelinAPI(workspaceName).addNotebook(notebook) if notebookZeppelinId: NotebookObject.objects.create(notebookZeppelinId=notebookZeppelinId, connection=connection, notebookTemplate=notebookTemplate, defaultPayload=defaultPayload) res.update(True, "Notebook added successfully") return res @staticmethod - def editNotebook(notebookObjId: int, payload: dict): + def editNotebook(notebookObjId: int, payload: dict, workspaceId: int = 0): """ Service to update a template based notebook :param notebookObjId: ID of the NotebookObject to be edited :param payload: Dict containing notebook template info """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error updating notebook") defaultPayload = payload.copy() notebookObject = NotebookObject.objects.get(id=notebookObjId) notebook, connection = NotebookJobServices._prepareNotebookJson(notebookObject.notebookTemplate, payload) - updateSuccess = Zeppelin.updateNotebookParagraphs(notebookObject.notebookZeppelinId, notebook) + updateSuccess = ZeppelinAPI(workspaceName).updateNotebookParagraphs(notebookObject.notebookZeppelinId, notebook) if updateSuccess: if defaultPayload.get("name"): - Zeppelin.renameNotebook(notebookObject.notebookZeppelinId, defaultPayload.get("name")) + ZeppelinAPI(workspaceName).renameNotebook(notebookObject.notebookZeppelinId, defaultPayload.get("name")) notebookObject.defaultPayload = defaultPayload notebookObject.connection = connection notebookObject.save() @@ -313,13 +320,13 @@ def deleteNotebookJob(notebookId: int): return res @staticmethod - def runNotebookJob(notebookId: str): + def runNotebookJob(notebookId: str, workspaceId: int = 0): """ Service to run notebook job """ res = ApiResponse("Error in running notebook") notebookRunLogs = NotebookRunLogs.objects.create(notebookId=notebookId, status=NOTEBOOK_STATUS_QUEUED, runType="Manual") - runNotebookJobTask.delay(notebookId=notebookId, notebookRunLogsId=notebookRunLogs.id, runType="Manual") + runNotebookJobTask.delay(notebookId=notebookId, notebookRunLogsId=notebookRunLogs.id, runType="Manual", workspaceId=workspaceId) res.update(True, "Notebook triggered successfully", None) return res @@ -341,56 +348,61 @@ def stopNotebookJob(notebookId: str): return res @staticmethod - def clearNotebookResults(notebookId: str): + def clearNotebookResults(notebookId: str, workspaceId: int = 0): """ Service to clear notebook job """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error in clearing notebook") - response = Zeppelin.clearNotebookResults(notebookId) + response = ZeppelinAPI(workspaceName).clearNotebookResults(notebookId) if response: res.update(True, "Notebook cleared successfully", None) return res @staticmethod - def cloneNotebook(notebookId: str, payload: dict): + def cloneNotebook(notebookId: str, payload: dict, workspaceId: int = 0): """ Service to clone notebook job """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error in cloning notebook") - response = Zeppelin.cloneNotebook(notebookId, json.dumps(payload)) + response = ZeppelinAPI(workspaceName).cloneNotebook(notebookId, json.dumps(payload)) if response: res.update(True, "Notebook cloned successfully", None) return res @staticmethod - def archiveNotebook(notebookId: str, notebookName: str): + def archiveNotebook(notebookId: str, notebookName: str, workspaceId: int = 0): """ Service to run notebook """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error in archiving notebook") - response = Zeppelin.renameNotebook(notebookId, "~Trash/" + notebookName) + response = ZeppelinAPI(workspaceName).renameNotebook(notebookId, "~Trash/" + notebookName) if response: res.update(True, "Notebook archived successfully", None) return res @staticmethod - def unarchiveNotebook(notebookId: str, notebookName: str): + def unarchiveNotebook(notebookId: str, notebookName: str, workspaceId: int = 0): """ Service to unarchive notebook """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error in archiving notebook") - response = Zeppelin.renameNotebook(notebookId, notebookName) + response = ZeppelinAPI(workspaceName).renameNotebook(notebookId, notebookName) if response: res.update(True, "Notebook unarchived successfully", None) return res @staticmethod - def deleteNotebook(notebookId: str): + def deleteNotebook(notebookId: str, workspaceId: int = 0): """ Service to run notebook job """ + workspaceName = helperFunctions.getWorkspaceName(workspaceId) res = ApiResponse(message="Error in deleting notebook") - response = Zeppelin.deleteNotebook(notebookId) + response = ZeppelinAPI(workspaceName).deleteNotebook(notebookId) if response: NotebookObject.objects.filter(notebookZeppelinId=notebookId).delete() res.update(True, "Notebook deleted successfully", None) diff --git a/api/genie/tests/test_views_notebookJobs.py b/api/genie/tests/test_views_notebookJobs.py index b56c787a..1213c15c 100644 --- a/api/genie/tests/test_views_notebookJobs.py +++ b/api/genie/tests/test_views_notebookJobs.py @@ -9,12 +9,13 @@ from genie.services import NotebookJobServices from genie.models import NotebookObject -from genie.tasks import NotebookRunLogs +from genie.tasks import RunStatus @pytest.mark.django_db def test_getNotebooks(client, populate_seed_data, mocker): - path = reverse('notebooks', kwargs={"offset": 0}) + path = reverse('notebooks', kwargs={"offset": 0, "workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.getAllNotebooks", return_value = [{"path": "notebook", "id": "BX976MDDE"}]) response = client.get(path, content_type="application/json") assert response.status_code == 200 @@ -24,7 +25,8 @@ def test_getNotebooks(client, populate_seed_data, mocker): @pytest.mark.django_db def test_addNotebook(client, populate_seed_data, mocker): - path = reverse('notebook') + path = reverse('notebook', kwargs={"workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") data = {"notebookTemplateId": 1} mixer.blend("genie.notebookTemplate") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.addNotebook", return_value = True) @@ -36,7 +38,8 @@ def test_addNotebook(client, populate_seed_data, mocker): @pytest.mark.django_db def test_notebooksLightView(client, populate_seed_data, mocker): - path = reverse('notebooksLightView') + path = reverse('notebooksLightView', kwargs={"workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.getAllNotebooks", return_value = [{"path": "notebook", "id": "BX976MDDE"}]) response = client.get(path, content_type="application/json") print(response.data['data']) @@ -45,7 +48,8 @@ def test_notebooksLightView(client, populate_seed_data, mocker): @pytest.mark.django_db def test_cloneNotebook(client, populate_seed_data, mocker): - path = reverse('notebookOperations', kwargs={"notebookId": "BX976MDDE"}) + path = reverse('notebookOperations', kwargs={"notebookId": "BX976MDDE", "workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.cloneNotebook", return_value = True) response = client.post(path, content_type="application/json") assert response.status_code == 200 @@ -53,7 +57,8 @@ def test_cloneNotebook(client, populate_seed_data, mocker): @pytest.mark.django_db def test_deleteNotebook(client, populate_seed_data, mocker): - path = reverse('notebookOperations', kwargs={"notebookId": "BX976MDDE"}) + path = reverse('notebookOperations', kwargs={"notebookId": "BX976MDDE", "workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.deleteNotebook", return_value = True) response = client.delete(path, content_type="application/json") assert response.status_code == 200 @@ -61,7 +66,8 @@ def test_deleteNotebook(client, populate_seed_data, mocker): @pytest.mark.django_db def test_runAndStopNotebookJob(client, populate_seed_data, mocker): - path = reverse('notebookActions', kwargs={"notebookId": "BX976MDDE"}) + path = reverse('notebookActions', kwargs={"notebookId": "BX976MDDE", "workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") mocker.patch("genie.tasks.runNotebookJob.delay", auto_spec=True) response = client.post(path, content_type="application/json") assert response.status_code == 200 @@ -73,7 +79,8 @@ def test_runAndStopNotebookJob(client, populate_seed_data, mocker): @pytest.mark.django_db def test_clearNotebookResults(client, populate_seed_data, mocker): - path = reverse('notebookActions', kwargs={"notebookId": "BX976MDDE"}) + path = reverse('notebookActions', kwargs={"notebookId": "BX976MDDE", "workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.clearNotebookResults", return_value = True) response = client.put(path, content_type="application/json") assert response.status_code == 200 @@ -82,12 +89,12 @@ def test_clearNotebookResults(client, populate_seed_data, mocker): @pytest.mark.django_db def test_getNotebookJobs(client, populate_seed_data, mocker): path = reverse('notebookJobView', kwargs={"notebookId": "BX976MDDE"}) - mixer.blend("genie.notebookRunLogs", notebookId="BX976MDDE") + mixer.blend("genie.runStatus", notebookId="BX976MDDE") mocker.patch("utils.zeppelinAPI.ZeppelinAPI.clearNotebookResults", return_value = True) response = client.get(path, content_type="application/json") assert response.status_code == 200 assert response.data['success'] == True - assert len(response.data["data"]["notebookRunLogs"]) == 1 + assert len(response.data["data"]["runStatuses"]) == 1 assert response.data["data"]["count"] == 1 @pytest.mark.django_db diff --git a/api/genie/urls.py b/api/genie/urls.py index 7d26d938..5cfd3d2e 100644 --- a/api/genie/urls.py +++ b/api/genie/urls.py @@ -3,15 +3,15 @@ from . import views urlpatterns = [ - path("notebooks/", views.NotebookView.as_view(), name="notebooks"), - path("notebooks/archive", views.ArchivedNotebooksView.as_view(), name="archivedNotebooksView"), - path("notebooksLight", views.NotebooksLightView.as_view(), name="notebooksLightView"), - path("notebook/", views.NotebookOperationsView.as_view(), name="notebookOperations"), - path("notebook/actions/", views.NotebookActionsView.as_view(), name="notebookActions"), - path("notebook//archive/", views.ArchiveNotebookView.as_view(), name="archiveNotebookView"), + path("notebooks//", views.NotebookView.as_view(), name="notebooks"), + path("notebooks/archive/", views.ArchivedNotebooksView.as_view(), name="archivedNotebooksView"), + path("notebooksLight/", views.NotebooksLightView.as_view(), name="notebooksLightView"), + path("notebook//", views.NotebookOperationsView.as_view(), name="notebookOperations"), + path("notebook/actions//", views.NotebookActionsView.as_view(), name="notebookActions"), + path("notebook//archive//", views.ArchiveNotebookView.as_view(), name="archiveNotebookView"), path("notebook//unarchive/", views.UnarchiveNotebookView.as_view(), name="unarchiveNotebookView"), - path("notebook", views.NotebookView.as_view(), name="notebook"), - path("notebookObject/", views.getNotebookObject, name="notebookObject"), + path("notebook/", views.NotebookView.as_view(), name="notebook"), + path("notebookObject//", views.getNotebookObject, name="notebookObject"), path("notebookjob/", views.NotebookJobView.as_view(), name="notebookJobView"), path("notebookjob/", views.NotebookJobView.as_view(), name="notebooksJobView"), path("notebookTemplates/", views.NotebookTemplateView.as_view(), name="notebookTemplateView"), diff --git a/api/genie/views.py b/api/genie/views.py index 62852627..e4028f0d 100644 --- a/api/genie/views.py +++ b/api/genie/views.py @@ -9,60 +9,60 @@ class NotebookOperationsView(APIView): """ Class to get notebooks from zeppelin server """ - def post(self, request, notebookId): - res = NotebookJobServices.cloneNotebook(notebookId, request.data) + def post(self, request, notebookId, workspaceId): + res = NotebookJobServices.cloneNotebook(notebookId, request.data, workspaceId) return Response(res.json()) - def delete(self, request, notebookId): - res = NotebookJobServices.deleteNotebook(notebookId) + def delete(self, request, notebookId, workspaceId): + res = NotebookJobServices.deleteNotebook(notebookId, workspaceId) return Response(res.json()) class ArchivedNotebooksView(APIView): """ Class to get archived notebooks """ - def get(self, request): - res = NotebookJobServices.archivedNotebooks() + def get(self, request, workspaceId): + res = NotebookJobServices.archivedNotebooks(workspaceId) return Response(res.json()) class NotebookActionsView(APIView): """ Class to get notebooks from zeppelin server """ - def post(self, request, notebookId): - res = NotebookJobServices.runNotebookJob(notebookId) + def post(self, request, notebookId, workspaceId): + res = NotebookJobServices.runNotebookJob(notebookId, workspaceId) return Response(res.json()) - def delete(self, request, notebookId): + def delete(self, request, notebookId, workspaceId): res = NotebookJobServices.stopNotebookJob(notebookId) return Response(res.json()) - def put(self, request, notebookId): - res = NotebookJobServices.clearNotebookResults(notebookId) + def put(self, request, notebookId, workspaceId): + res = NotebookJobServices.clearNotebookResults(notebookId, workspaceId) return Response(res.json()) class ArchiveNotebookView(APIView): """ Class to archive notebook """ - def get(self, request, notebookId, notebookName): - res = NotebookJobServices.archiveNotebook(notebookId, notebookName) + def get(self, request, notebookId, notebookName, workspaceId): + res = NotebookJobServices.archiveNotebook(notebookId, notebookName, workspaceId) return Response(res.json()) class UnarchiveNotebookView(APIView): """ Class to unarchive notebook """ - def get(self, request, notebookId, notebookName): - res = NotebookJobServices.unarchiveNotebook(notebookId, notebookName) + def get(self, request, notebookId, notebookName, workspaceId): + res = NotebookJobServices.unarchiveNotebook(notebookId, notebookName, workspaceId) return Response(res.json()) class NotebooksLightView(APIView): """ Get concise notebook data """ - def get(self, request): - res = NotebookJobServices.getNotebooksLight() + def get(self, request, workspaceId): + res = NotebookJobServices.getNotebooksLight(workspaceId) return Response(res.json()) @@ -70,16 +70,16 @@ class NotebookView(APIView): """ Class to get notebooks from zeppelin server """ - def get(self, request, offset: int ): + def get(self, request, workspaceId, offset: int): limit = request.GET.get('limit', 25) searchQuery = request.GET.get('searchText', '') sorter = json.loads(request.GET.get('sorter', '{}')) _filter = json.loads(request.GET.get('filter', '{}')) - res = NotebookJobServices.getNotebooks(offset, limit, searchQuery, sorter, _filter) + res = NotebookJobServices.getNotebooks(offset, limit, searchQuery, sorter, _filter, workspaceId) return Response(res.json()) - def post(self, request): - res = NotebookJobServices.addNotebook(request.data) + def post(self, request, workspaceId): + res = NotebookJobServices.addNotebook(request.data, workspaceId) return Response(res.json()) @@ -207,7 +207,7 @@ def datasetDetails(request: HttpRequest) -> Response: return Response(res.json()) @api_view(["GET", "PUT"]) -def getNotebookObject(request: HttpRequest, notebookObjId: int) -> Response: +def getNotebookObject(request: HttpRequest, notebookObjId: int, workspaceId: int) -> Response: """ Method to get details of Notebook Object with given id :param notebookObjId: ID of the notebook object @@ -216,7 +216,7 @@ def getNotebookObject(request: HttpRequest, notebookObjId: int) -> Response: res = NotebookJobServices.getNotebookObject(notebookObjId) return Response(res.json()) if request.method == "PUT": - res = NotebookJobServices.editNotebook(notebookObjId, request.data) + res = NotebookJobServices.editNotebook(notebookObjId, request.data, workspaceId) return Response(res.json()) diff --git a/api/start_server.dev.sh b/api/start_server.dev.sh new file mode 100755 index 00000000..a1912b48 --- /dev/null +++ b/api/start_server.dev.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# start-server.sh +if [ -n "$DJANGO_SUPERUSER_USERNAME" ] && [ -n "$DJANGO_SUPERUSER_PASSWORD" ] ; then + (python manage.py createsuperuser --no-input) +fi +export DEBUG=true +source .env.dev +sleep 10 +python manage.py makemigrations +python manage.py migrate +python manage.py loaddata seeddata/*.json +chmod -R 777 db +chown -R www-data:www-data db +(python manage.py runserver 0.0.0.0:8000) & +(celery -A app worker --concurrency=500 -P gevent -l INFO --purge) & +(celery -A app beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler) \ No newline at end of file diff --git a/api/utils/dbUtils.py b/api/utils/dbUtils.py new file mode 100755 index 00000000..a5dfea95 --- /dev/null +++ b/api/utils/dbUtils.py @@ -0,0 +1,28 @@ +import os +import psycopg2 + +class DbUtils: + + """ + Class to interact into database for creating hive metastore + """ + def __init__(self, message=None): + """ + Response constructor with defaults + :param message: Optional init message for the response + """ + + + def createMetastoreDB(self, workspaceName: str): + self.connection = psycopg2.connect(user=os.environ.get("POSTGRES_DB_USERNAME"), password = os.environ.get("POSTGRES_DB_PASSWORD"), host = os.environ.get("POSTGRES_DB_HOST"), port = os.environ.get("POSTGRES_DB_PORT")) + self.connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) + self.cursor = self.connection.cursor() + self.cursor.execute(f"CREATE DATABASE {workspaceName}_metastore;") + self.database = (f"{workspaceName}_metastore") + + def metastoreSchema(self): + self.connection = psycopg2.connect(dbname = self.database,user=os.environ.get("POSTGRES_DB_USERNAME"), password = os.environ.get("POSTGRES_DB_PASSWORD"), host = os.environ.get("POSTGRES_DB_HOST"), port = os.environ.get("POSTGRES_DB_PORT")) + self.connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) + self.cursor = self.connection.cursor() + self.cursor.execute(open("utils/schema/hive-schema-2.3.0.postgres.sql", "r").read()) + diff --git a/api/utils/dockerHubAPI.py b/api/utils/dockerHubAPI.py new file mode 100755 index 00000000..34a8059e --- /dev/null +++ b/api/utils/dockerHubAPI.py @@ -0,0 +1,24 @@ +import logging +import requests +from django.conf import settings +from rest_framework import response + +# Get an instance of a logger +logger = logging.getLogger(__name__) + +BASE_URL = "https://hub.docker.com/v2/repositories/cuebook/" +URL_SUFFIX = "/tags/?page_size=100&page=1&ordering=last_updated" + +class DockerHubAPI: + """ + Functionalities around docker hub APIs + """ + def getImageTags(self, repository: str): + try: + response = requests.get(f"{BASE_URL}{repository}{URL_SUFFIX}") + return response.json().get('results', []) + except Exception as ex: + return [] + +# Export initalized class +dockerHubAPI = DockerHubAPI() diff --git a/api/utils/helperFunctions.py b/api/utils/helperFunctions.py new file mode 100755 index 00000000..ce1325ac --- /dev/null +++ b/api/utils/helperFunctions.py @@ -0,0 +1,16 @@ +import os +from workspace.models import ( + Workspace +) + +class helperFunctions: + + def getWorkspaceName(self, workspaceId: int): + if os.environ.get("ENVIRONMENT","") == "dev": + workspaceHost = "localhost" + else: + workspaceHost = "zeppelin-server-" + Workspace.objects.get(pk=workspaceId).name + return workspaceHost + +# Export initalized class +helperFunctions = helperFunctions() \ No newline at end of file diff --git a/api/utils/kubernetesAPI.py b/api/utils/kubernetesAPI.py index 49c13e0e..3da3da38 100644 --- a/api/utils/kubernetesAPI.py +++ b/api/utils/kubernetesAPI.py @@ -1,98 +1,384 @@ -import os -from time import sleep -import time -import yaml -import logging -from django.conf import settings -from kubernetes import config, client -from utils.safeDict import SafeDict -import random -import subprocess - -# Get an instance of a logger -logger = logging.getLogger(__name__) - - -class KubernetesAPI: - """ - Functionalities around zeppelin APIs - """ - if os.environ.get("ENVIRONMENT","") == "dev": - config.load_kube_config() - elif os.environ.get("ENVIRONMENT","") != "test": - config.load_incluster_config() - - POD_NAMESPACE = os.environ.get("POD_NAMESPACE", "default") - - def getDriversCount(self): - """ - Gets Driver and executors count - """ - runningDrivers = 0 - runningExecutors = 0 - pendingDrivers = 0 - pendingExecutors = 0 - v1 = client.CoreV1Api() - ret = v1.list_namespaced_pod(self.POD_NAMESPACE, watch=False) - pods = ret.items - pods_name = [pod.metadata.name for pod in pods] - podLabels = [[pod.metadata.labels, pod.status.phase] for pod in pods] # list - podStatus = [pod.status for pod in pods] - - for label in podLabels: - if "interpreterSettingName" in label[0] and label[0]["interpreterSettingName"] == "spark" and label[1]=="Running": - runningDrivers += 1 - - if "interpreterSettingName" in label[0] and label[0]["interpreterSettingName"] == "spark" and label[1]=="Pending": - pendingDrivers += 1 - if "spark-role" in label[0] and label[0]["spark-role"] == "executor" and label[1]=="Running": - runningExecutors += 1 - - if "spark-role" in label[0] and label[0]["spark-role"] == "executor" and label[1]=="Pending": - pendingExecutors += 1 - data = {"runningDrivers":runningDrivers, - "pendingDrivers":pendingDrivers, - "runningExecutors":runningExecutors, - "pendingExecutors":pendingExecutors - } - return data - - def addZeppelinServer(self, podId): - v1 = client.CoreV1Api() - podTemplateFile = open("utils/kubernetesTemplates/zeppelinServer.yaml", "r") - podBody = podTemplateFile.read() - podBody = podBody.format_map(SafeDict(podId=podId, podNamespace=self.POD_NAMESPACE)) - podBody = yaml.safe_load(podBody) - v1.create_namespaced_pod(namespace=self.POD_NAMESPACE, body=podBody) - serviceTemplateFile = open("utils/kubernetesTemplates/zeppelinService.yaml", "r") - serviceBody = serviceTemplateFile.read() - serviceBody = serviceBody.format_map(SafeDict(podId=podId, podNamespace=self.POD_NAMESPACE)) - serviceBody = yaml.safe_load(serviceBody) - v1.create_namespaced_service(namespace=self.POD_NAMESPACE, body=serviceBody) - - def removeZeppelinServer(self, podId): - v1 = client.CoreV1Api() - try: - v1.delete_namespaced_pod(name=podId, namespace=self.POD_NAMESPACE) - v1.delete_namespaced_service(name=podId, namespace=self.POD_NAMESPACE) - except Exception as ex: - logger.error(f"Error removing zeppelin server: {podId}. Error: {str(ex)}") - - def getPodStatus(self, podId): - v1 = client.CoreV1Api() - podResponse = v1.read_namespaced_pod(name=podId, namespace=self.POD_NAMESPACE) - return podResponse.status.phase - - def getPods(self): - v1 = client.CoreV1Api() - pods = v1.list_namespaced_pod(namespace=self.POD_NAMESPACE) - return pods.items - - def portForward(self, zeppelinServerId): - port = str(random.randint(10000,65000)) - time.sleep(3) - subprocess.Popen(["kubectl", "port-forward", "pod/" + zeppelinServerId, port + ":8080", "-n", self.POD_NAMESPACE]) - return port - -# Export initalized class +from utils.helperFunctions import helperFunctions +from utils.dbUtils import DbUtils +import os +from time import sleep +import time +import yaml +import logging +from django.conf import settings +from kubernetes import config, client +from workspace.models import ( + Workspace, + WorkspaceConfig +) +from utils.safeDict import SafeDict +import random +import subprocess +from pathlib import Path + +# Get an instance of a logger +logger = logging.getLogger(__name__) + +ICEBERG = "iceberg" +DELTA = "delta" + +class KubernetesAPI: + """ + Functionalities around zeppelin APIs + """ + if os.environ.get("ENVIRONMENT","") == "dev": + config.load_kube_config() + elif os.environ.get("ENVIRONMENT","") != "test": + config.load_incluster_config() + + POD_NAMESPACE = os.environ.get("POD_NAMESPACE", "default") + + def getDriversCount(self): + """ + Gets Driver and executors count + """ + runningDrivers = 0 + runningExecutors = 0 + pendingDrivers = 0 + pendingExecutors = 0 + v1 = client.CoreV1Api() + ret = v1.list_namespaced_pod(self.POD_NAMESPACE, watch=False) + pods = ret.items + pods_name = [pod.metadata.name for pod in pods] + podLabels = [[pod.metadata.labels, pod.status.phase] for pod in pods] # list + podStatus = [pod.status for pod in pods] + + for label in podLabels: + if "interpreterSettingName" in label[0] and label[0]["interpreterSettingName"] == "spark" and label[1]=="Running": + runningDrivers += 1 + + if "interpreterSettingName" in label[0] and label[0]["interpreterSettingName"] == "spark" and label[1]=="Pending": + pendingDrivers += 1 + if "spark-role" in label[0] and label[0]["spark-role"] == "executor" and label[1]=="Running": + runningExecutors += 1 + + if "spark-role" in label[0] and label[0]["spark-role"] == "executor" and label[1]=="Pending": + pendingExecutors += 1 + data = {"runningDrivers":runningDrivers, + "pendingDrivers":pendingDrivers, + "runningExecutors":runningExecutors, + "pendingExecutors":pendingExecutors + } + return data + + def addZeppelinServer(self, workspaceName: str, workspaceConfig: dict, isNew: bool = False): + sparkConfigJSON = "" + if isNew: + try: + db = DbUtils() + db.createMetastoreDB(workspaceName) + db.metastoreSchema() + except: + pass + if(workspaceConfig['storage'] == "S3"): + if(workspaceConfig['acidProvider'] == DELTA): + sparkConfigJSON = """ + "AWS_ACCESS_KEY_ID": { + "name": "AWS_ACCESS_KEY_ID", + "value": "S3ACCESSKEY", + "type": "textarea" + }, + "AWS_SECRET_ACCESS_KEY": { + "name": "AWS_SECRET_ACCESS_KEY", + "value": "S3SECRETKEY", + "type": "textarea" + }, + "spark.sql.warehouse.dir":{ + "name": "spark.sql.warehouse.dir", + "value": "WAREHOUSELOCATION", + "type": "textarea" + }, + "spark.sql.extensions":{ + "name": "spark.sql.extensions", + "value": "io.delta.sql.DeltaSparkSessionExtension", + "type": "textarea" + }, + "spark.sql.catalog.spark_catalog":{ + "name": "spark.sql.catalog.spark_catalog", + "value": "org.apache.spark.sql.delta.catalog.DeltaCatalog", + "type": "textarea" + } + """ + sparkConfigJSON = sparkConfigJSON.replace('S3ACCESSKEY', workspaceConfig['s3AccessKey']).replace('S3SECRETKEY', workspaceConfig['s3SecretKey']).replace('WAREHOUSELOCATION', workspaceConfig['warehouseLocation']) + elif(workspaceConfig['acidProvider'] == ICEBERG): + sparkConfigJSON = """"AWS_ACCESS_KEY_ID": { + "name": "AWS_ACCESS_KEY_ID", + "value": "S3ACCESSKEY", + "type": "textarea" + }, + "AWS_SECRET_ACCESS_KEY": { + "name": "AWS_SECRET_ACCESS_KEY", + "value": "s3SecretKey", + "type": "textarea" + }, + "spark.sql.warehouse.dir":{ + "name": "spark.sql.warehouse.dir", + "value": "WAREHOUSELOCATION", + "type": "textarea" + }, + "spark.sql.extensions":{ + "name": "spark.sql.extensions", + "value": "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtension", + "type": "textarea" + }, + "spark.sql.catalog.spark_catalog":{ + "name": "spark.sql.catalog.spark_catalog", + "value": "org.apache.iceberg.spark.SparkSessionCatalog", + "type": "textarea" + } + """ + sparkConfigJSON = sparkConfigJSON.replace('S3ACCESSKEY', workspaceConfig['s3AccessKey']).replace('S3SECRETKEY', workspaceConfig['s3SecretKey']).replace('WAREHOUSELOCATION', workspaceConfig['warehouseLocation']) + elif(workspaceConfig['storage'] == "AZFS"): + if(workspaceConfig['acidProvider'] == DELTA): + sparkConfigJSON = """"spark.hadoop.fs.azure.account.key.AZUREACCOUNT.blob.core.windows.net": { + "name": "spark.hadoop.fs.azure.account.key.AZUREACCOUNT.blob.core.windows.net", + "value": "AZUREKEY", + "type": "textarea" + }, + "spark.sql.warehouse.dir":{ + "name": "spark.sql.warehouse.dir", + "value": "WAREHOUSELOCATION", + "type": "textarea" + }, + "spark.sql.extensions":{ + "name": "spark.sql.extensions", + "value": "io.delta.sql.DeltaSparkSessionExtension", + "type": "textarea" + }, + "spark.sql.catalog.spark_catalog":{ + "name": "spark.sql.catalog.spark_catalog", + "value": "org.apache.spark.sql.delta.catalog.DeltaCatalog", + "type": "textarea" + } + """ + sparkConfigJSON = sparkConfigJSON.replace('AZUREKEY', workspaceConfig['azureKey']).replace('AZUREACCOUNT', workspaceConfig['azureAccount']).replace('WAREHOUSELOCATION', workspaceConfig['warehouseLocation']) + elif(workspaceConfig['acidProvider'] == ICEBERG): + sparkConfigJSON = """"spark.hadoop.fs.azure.account.key.AZUREACCOUNT.blob.core.windows.net": { + "name": "spark.hadoop.fs.azure.account.key.AZUREACCOUNT.blob.core.windows.net", + "value": "AZUREKEY", + "type": "textarea" + }, + "spark.sql.warehouse.dir":{ + "name": "spark.sql.warehouse.dir", + "value": "WAREHOUSELOCATION", + "type": "textarea" + }, + "spark.sql.extensions":{ + "name": "spark.sql.extensions", + "value": "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtension", + "type": "textarea" + }, + "spark.sql.catalog.spark_catalog":{ + "name": "spark.sql.catalog.spark_catalog", + "value": "org.apache.iceberg.spark.SparkSessionCatalog", + "type": "textarea" + } + """ + sparkConfigJSON = sparkConfigJSON.replace('AZUREKEY', workspaceConfig['azureKey']).replace('AZUREACCOUNT', workspaceConfig['azureAccount']).replace('WAREHOUSELOCATION', workspaceConfig['warehouseLocation']) + elif(workspaceConfig['storage'] == "GS"): + if(workspaceConfig['acidProvider'] == DELTA): + sparkConfigJSON = """spark.kubernetes.driver.secrets.cuelake-bucket-key": { + "name": "spark.kubernetes.driver.secrets.cuelake-bucket-key", + "value": "GOOGLEKEY", + "type": "textarea" + }, + "spark.sql.warehouse.dir":{ + "name": "spark.sql.warehouse.dir", + "value": "WAREHOUSELOCATION", + "type": "textarea" + }, + "spark.hadoop.fs.AbstractFileSystem.gs.impl":{ + "name": "spark.hadoop.fs.AbstractFileSystem.gs.impl", + "value": "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS", + "type": "textarea" + }, + "spark.hadoop.google.cloud.auth.service.account.enable":{ + "name": "spark.hadoop.google.cloud.auth.service.account.enable", + "value": "true", + "type": "textarea" + }, + "spark.delta.logStore.gs.impl":{ + "name": "spark.delta.logStore.gs.impl", + "value": "io.delta.storage.GCSLogStore", + "type": "textarea" + }, + "spark.hadoop.fs.gs.impl":{ + "name": "spark.hadoop.fs.gs.impl", + "value": "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem", + "type": "textarea" + }, + "spark.sql.extensions":{ + "name": "spark.sql.extensions", + "value": "io.delta.sql.DeltaSparkSessionExtension", + "type": "textarea" + }, + "spark.sql.catalog.spark_catalog":{ + "name": "spark.sql.catalog.spark_catalog", + "value": "org.apache.spark.sql.delta.catalog.DeltaCatalog", + "type": "textarea" + } + """ + sparkConfigJSON = sparkConfigJSON.replace('GOOGLEKEY', workspaceConfig['googleKey']).replace('WAREHOUSELOCATION', workspaceConfig['warehouseLocation']) + elif(workspaceConfig['acidProvider'] == ICEBERG): + sparkConfigJSON = """spark.kubernetes.driver.secrets.cuelake-bucket-key": { + "name": "spark.kubernetes.driver.secrets.cuelake-bucket-key", + "value": "GOOGLEKEY", + "type": "textarea" + }, + "spark.kubernetes.authenticate.driver.serviceAccountName": { + "name": "spark.kubernetes.authenticate.driver.serviceAccountName", + "value": "{workspaceConfig['azureAccount']}", + "type": "textarea" + }, + "spark.sql.warehouse.dir":{ + "name": "spark.sql.warehouse.dir", + "value": "WAREHOUSELOCATION", + "type": "textarea" + }, + "spark.hadoop.fs.AbstractFileSystem.gs.impl":{ + "name": "spark.hadoop.fs.AbstractFileSystem.gs.impl", + "value": "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFS", + "type": "textarea" + }, + "spark.hadoop.google.cloud.auth.service.account.enable":{ + "name": "spark.hadoop.google.cloud.auth.service.account.enable", + "value": "true", + "type": "textarea" + }, + "spark.delta.logStore.gs.impl":{ + "name": "spark.delta.logStore.gs.impl", + "value": "io.delta.storage.GCSLogStore", + "type": "textarea" + }, + "spark.hadoop.fs.gs.impl":{ + "name": "spark.hadoop.fs.gs.impl", + "value": "com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem", + "type": "textarea" + }, + "spark.sql.extensions":{ + "name": "spark.sql.extensions", + "value": "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtension", + "type": "textarea" + }, + "spark.sql.catalog.spark_catalog":{ + "name": "spark.sql.catalog.spark_catalog", + "value": "org.apache.iceberg.spark.SparkSessionCatalog", + "type": "textarea" + } + """ + sparkConfigJSON = sparkConfigJSON.replace('GOOGLEKEY', workspaceConfig['googleKey']).replace('WAREHOUSELOCATION', workspaceConfig['warehouseLocation']) + with open("workspace/services/templates/conf/interpreter.json", "r") as file: + zeppelinInterpreterTemplate = file.read() + zeppelinInterpreterTemplate = zeppelinInterpreterTemplate.replace('sparkConfigJSON', sparkConfigJSON) + if isNew: + zeppelinInterpreterTemplate = zeppelinInterpreterTemplate.replace('_HOST', os.environ.get("POSTGRES_DB_HOST")).replace('_PORT', os.environ.get("POSTGRES_DB_PORT")).replace('_USERNAME', os.environ.get("POSTGRES_DB_USERNAME")).replace('_PASSWORD', os.environ.get("POSTGRES_DB_PASSWORD")).replace('_DBNAME', (f"{workspaceName}_metastore")) + + Path(f"data/workspaces/{workspaceName}/conf").mkdir(parents=True, exist_ok=True) + with open(f"data/workspaces/{workspaceName}/conf/interpreter.json", "w") as file: + file.write(zeppelinInterpreterTemplate) + + zeppelinEnvTemplate = open("workspace/services/templates/conf/zeppelin-env.sh", "r") + zeppelinEnvTemplate = zeppelinEnvTemplate.read() + zeppelinEnvTemplate = zeppelinEnvTemplate.format_map(SafeDict(inactivityTimeout=workspaceConfig['inactivityTimeout'])) + Path(f"data/workspaces/{workspaceName}/conf").mkdir(parents=True, exist_ok=True) + zeppelinEnvFile = open(f"data/workspaces/{workspaceName}/conf/zeppelin-env.sh", "w") + zeppelinEnvFile.write(zeppelinEnvTemplate) + + interpreterSpecTemplate = open("workspace/services/templates/zeppelinInterpreter.yaml", "r") + interpreterSpecTemplate = interpreterSpecTemplate.read() + Path(f"data/workspaces/{workspaceName}/k8s/interpreter").mkdir(parents=True, exist_ok=True) + interpreterSpecFile = open(f"data/workspaces/{workspaceName}/k8s/interpreter/100-interpreter-spec.yaml", "w") + interpreterSpecFile.write(interpreterSpecTemplate) + + if os.environ.get("ENVIRONMENT","") == "dev": + subprocess.Popen([ + "kubectl cp data/workspaces/WORKSPACE_NAME POD_NAMESPACE/$(kubectl get pods -n POD_NAMESPACE | grep lakehouse | awk '{print $1}'):/code/data/WORKSPACE_NAME".replace('POD_NAMESPACE', self.POD_NAMESPACE).replace("WORKSPACE_NAME", workspaceName)],shell=True) + + v1Core = client.CoreV1Api() + v1App = client.AppsV1Api() + deploymentTemplateFile = open("workspace/services/templates/zeppelinServer.yaml", "r") + deploymentBody = deploymentTemplateFile.read() + deploymentBody = deploymentBody.format_map(SafeDict(workspaceName=workspaceName, podNamespace=self.POD_NAMESPACE)) + deploymentBody = yaml.safe_load(deploymentBody) + v1App.create_namespaced_deployment(namespace=self.POD_NAMESPACE, body=deploymentBody) + serviceTemplateFile = open("workspace/services/templates/zeppelinService.yaml", "r") + serviceBody = serviceTemplateFile.read() + serviceBody = serviceBody.format_map(SafeDict(workspaceName=workspaceName, podNamespace=self.POD_NAMESPACE)) + serviceBody = yaml.safe_load(serviceBody) + v1Core.create_namespaced_service(namespace=self.POD_NAMESPACE, body=serviceBody) + pvcTemplateFile = open("workspace/services/templates/zeppelinPVC.yaml", "r") + pvcBody = pvcTemplateFile.read() + pvcBody = pvcBody.format_map(SafeDict(workspaceName=workspaceName, podNamespace=self.POD_NAMESPACE)) + pvcBody = yaml.safe_load(pvcBody) + if isNew: + try: + v1Core.create_namespaced_persistent_volume_claim(namespace=self.POD_NAMESPACE, body=pvcBody) + except: + pass + + def addZeppelinJobServer(self, podId, workspaceId): + v1 = client.CoreV1Api() + podTemplateFile = open("utils/kubernetesTemplates/zeppelinServer.yaml", "r") + podBody = podTemplateFile.read() + workspaceName = "zeppelin-server-" + Workspace.objects.get(pk=workspaceId).name + podBody = podBody.format_map(SafeDict(podId=podId, podNamespace=self.POD_NAMESPACE, workspace=workspaceName)) + podBody = yaml.safe_load(podBody) + v1.create_namespaced_pod(namespace=self.POD_NAMESPACE, body=podBody) + serviceTemplateFile = open("utils/kubernetesTemplates/zeppelinService.yaml", "r") + serviceBody = serviceTemplateFile.read() + serviceBody = serviceBody.format_map(SafeDict(podId=podId, podNamespace=self.POD_NAMESPACE)) + serviceBody = yaml.safe_load(serviceBody) + v1.create_namespaced_service(namespace=self.POD_NAMESPACE, body=serviceBody) + + def removeZeppelinServer(self, podId): + v1 = client.CoreV1Api() + try: + v1.delete_namespaced_pod(name=podId, namespace=self.POD_NAMESPACE) + v1.delete_namespaced_service(name=podId, namespace=self.POD_NAMESPACE) + except Exception as ex: + logger.error(f"Error removing zeppelin server: {podId}. Error: {str(ex)}") + + def removeWorkspace(self, podId): + v1App = client.AppsV1Api() + v1 = client.CoreV1Api() + serverName = "zeppelin-server-" + podId + try: + v1App.delete_namespaced_deployment(name=serverName, namespace=self.POD_NAMESPACE) + v1.delete_namespaced_service(name=serverName, namespace=self.POD_NAMESPACE) + except Exception as ex: + logger.error(f"Error removing zeppelin workspace: {serverName}. Error: {str(ex)}") + + def switchWorkspaceServer(self, workspaceId: int): + workspaceName = Workspace.objects.get(pk=workspaceId).name + if os.environ.get("ENVIRONMENT","") == "dev": + os.system("kill -9 $(lsof -t -i:8080)") + subprocess.Popen(["kubectl", "port-forward", "services/zeppelin-server-" + workspaceName, "8080:80", "-n", self.POD_NAMESPACE]) + + def getPodStatus(self, podId): + v1 = client.CoreV1Api() + podResponse = v1.read_namespaced_pod(name=podId, namespace=self.POD_NAMESPACE) + return podResponse.status.phase + + def getPods(self): + v1 = client.CoreV1Api() + pods = v1.list_namespaced_pod(namespace=self.POD_NAMESPACE) + return pods.items + + def getDeployments(self): + v1App = client.AppsV1Api() + deployments = v1App.list_namespaced_deployment(namespace=self.POD_NAMESPACE) + return deployments.items + + def portForward(self, zeppelinServerId): + port = str(random.randint(10000,65000)) + time.sleep(3) + subprocess.Popen(["kubectl", "port-forward", "pod/" + zeppelinServerId, port + ":8080", "-n", self.POD_NAMESPACE]) + return port + +# Export initalized class Kubernetes = KubernetesAPI() \ No newline at end of file diff --git a/api/utils/kubernetesTemplates/100-interpreter-spec.yaml b/api/utils/kubernetesTemplates/100-interpreter-spec.yaml new file mode 100755 index 00000000..c497da04 --- /dev/null +++ b/api/utils/kubernetesTemplates/100-interpreter-spec.yaml @@ -0,0 +1,163 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +kind: Pod +apiVersion: v1 +metadata: + namespace: {{zeppelin.k8s.namespace}} + name: {{zeppelin.k8s.interpreter.pod.name}} + labels: + app: {{zeppelin.k8s.interpreter.pod.name}} + interpreterGroupId: {{zeppelin.k8s.interpreter.group.id}} + interpreterSettingName: {{zeppelin.k8s.interpreter.setting.name}} + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +spec: + {% if zeppelin.k8s.interpreter.group.name == "spark" %} + automountServiceAccountToken: true + {% else %} + automountServiceAccountToken: false + {% endif %} + restartPolicy: Never + terminationGracePeriodSeconds: 30 + containers: + - name: {{zeppelin.k8s.interpreter.container.name}} + image: {{zeppelin.k8s.interpreter.container.image}} + args: + - "$(ZEPPELIN_HOME)/bin/interpreter.sh" + - "-d" + - "$(ZEPPELIN_HOME)/interpreter/{{zeppelin.k8s.interpreter.group.name}}" + - "-r" + - "{{zeppelin.k8s.interpreter.rpc.portRange}}" + - "-c" + - "{{zeppelin.k8s.server.rpc.service}}" + - "-p" + - "{{zeppelin.k8s.server.rpc.portRange}}" + - "-i" + - "{{zeppelin.k8s.interpreter.group.id}}" + - "-l" + - "{{zeppelin.k8s.interpreter.localRepo}}/{{zeppelin.k8s.interpreter.setting.name}}" + - "-g" + - "{{zeppelin.k8s.interpreter.setting.name}}" + env: + {% for key, value in zeppelin.k8s.envs.items() %} + - name: {{key}} + value: {{value}} + {% endfor %} + - name: ZEPPELIN_HOME + value: /zeppelin + {% if zeppelin.k8s.interpreter.cores is defined and zeppelin.k8s.interpreter.memory is defined %} + resources: + requests: + memory: "{{zeppelin.k8s.interpreter.memory}}" + cpu: "{{zeppelin.k8s.interpreter.cores}}" +{# limits.memory is not set because of a potential OOM-Killer. https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits #} + limits: + cpu: "{{zeppelin.k8s.interpreter.cores}}" + {% endif %} + {% if zeppelin.k8s.interpreter.group.name == "spark" %} + volumeMounts: + - name: spark-home + mountPath: /spark + initContainers: + - name: spark-home-init + image: {{zeppelin.k8s.spark.container.image}} + command: ["sh", "-c", "cp -r /opt/spark/* /spark/"] + volumeMounts: + - name: spark-home + mountPath: /spark + volumes: + - name: spark-home + emptyDir: {} + {% endif %} +--- +kind: Service +apiVersion: v1 +metadata: + namespace: {{zeppelin.k8s.namespace}} + name: {{zeppelin.k8s.interpreter.pod.name}} # keep Service name the same to Pod name. + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +spec: + clusterIP: None + ports: + - name: intp + port: 12321 + {% if zeppelin.k8s.interpreter.group.name == "spark" %} + - name: spark-driver + port: 22321 + - name: spark-blockmanager + port: 22322 + - name: spark-ui + port: 4040 + {% endif %} + selector: + app: {{zeppelin.k8s.interpreter.pod.name}} +{% if zeppelin.k8s.interpreter.group.name == "spark" %} +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{zeppelin.k8s.interpreter.pod.name}} + namespace: {{zeppelin.k8s.namespace}} + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +rules: +- apiGroups: [""] + resources: ["pods", "services"] + verbs: ["create", "get", "update", "list", "delete", "watch" ] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{zeppelin.k8s.interpreter.pod.name}} + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +subjects: +- kind: ServiceAccount + name: default +roleRef: + kind: Role + name: {{zeppelin.k8s.interpreter.pod.name}} + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/api/utils/kubernetesTemplates/zeppelinServer.yaml b/api/utils/kubernetesTemplates/zeppelinServer.yaml index 81995d7d..a53921e7 100644 --- a/api/utils/kubernetesTemplates/zeppelinServer.yaml +++ b/api/utils/kubernetesTemplates/zeppelinServer.yaml @@ -28,9 +28,9 @@ spec: mountPath: /shared-k8s args: - >- - kubectl cp $(kubectl get pods | grep zeppelin-server | awk '{print $1}' ):/zeppelin/conf /shared-conf && - kubectl cp $(kubectl get pods | grep zeppelin-server | awk '{print $1}' ):/zeppelin/k8s /shared-k8s && - kubectl cp $(kubectl get pods | grep zeppelin-server | awk '{print $1}' ):/zeppelin/notebook /shared-notebook + kubectl cp $(kubectl get pods | grep {workspace}- | awk '{print $1}' ):/zeppelin/conf /shared-conf && + kubectl cp $(kubectl get pods | grep {workspace}- | awk '{print $1}' ):/zeppelin/k8s /shared-k8s && + kubectl cp $(kubectl get pods | grep {workspace}- | awk '{print $1}' ):/zeppelin/notebook /shared-notebook containers: - name: zeppelin-server image: 'cuebook/zeppelin-server-lite:0.2' diff --git a/api/utils/schema/hive-schema-2.3.0.postgres.sql b/api/utils/schema/hive-schema-2.3.0.postgres.sql new file mode 100755 index 00000000..6dbb82be --- /dev/null +++ b/api/utils/schema/hive-schema-2.3.0.postgres.sql @@ -0,0 +1,1591 @@ +-- +-- PostgreSQL database dump +-- + +SET statement_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = off; +SET check_function_bodies = false; +SET client_min_messages = warning; +SET escape_string_warning = off; + +SET search_path = public, pg_catalog; + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: BUCKETING_COLS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "BUCKETING_COLS" ( + "SD_ID" bigint NOT NULL, + "BUCKET_COL_NAME" character varying(256) DEFAULT NULL::character varying, + "INTEGER_IDX" bigint NOT NULL +); + + +-- +-- Name: CDS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "CDS" ( + "CD_ID" bigint NOT NULL +); + + +-- +-- Name: COLUMNS_V2; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "COLUMNS_V2" ( + "CD_ID" bigint NOT NULL, + "COMMENT" character varying(4000), + "COLUMN_NAME" character varying(767) NOT NULL, + "TYPE_NAME" text, + "INTEGER_IDX" integer NOT NULL +); + + +-- +-- Name: DATABASE_PARAMS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "DATABASE_PARAMS" ( + "DB_ID" bigint NOT NULL, + "PARAM_KEY" character varying(180) NOT NULL, + "PARAM_VALUE" character varying(4000) DEFAULT NULL::character varying +); + + +-- +-- Name: DBS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "DBS" ( + "DB_ID" bigint NOT NULL, + "DESC" character varying(4000) DEFAULT NULL::character varying, + "DB_LOCATION_URI" character varying(4000) NOT NULL, + "NAME" character varying(128) DEFAULT NULL::character varying, + "OWNER_NAME" character varying(128) DEFAULT NULL::character varying, + "OWNER_TYPE" character varying(10) DEFAULT NULL::character varying +); + + +-- +-- Name: DB_PRIVS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "DB_PRIVS" ( + "DB_GRANT_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "DB_ID" bigint, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "DB_PRIV" character varying(128) DEFAULT NULL::character varying +); + + +-- +-- Name: GLOBAL_PRIVS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "GLOBAL_PRIVS" ( + "USER_GRANT_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "USER_PRIV" character varying(128) DEFAULT NULL::character varying +); + + +-- +-- Name: IDXS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "IDXS" ( + "INDEX_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "DEFERRED_REBUILD" boolean NOT NULL, + "INDEX_HANDLER_CLASS" character varying(4000) DEFAULT NULL::character varying, + "INDEX_NAME" character varying(128) DEFAULT NULL::character varying, + "INDEX_TBL_ID" bigint, + "LAST_ACCESS_TIME" bigint NOT NULL, + "ORIG_TBL_ID" bigint, + "SD_ID" bigint +); + + +-- +-- Name: INDEX_PARAMS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "INDEX_PARAMS" ( + "INDEX_ID" bigint NOT NULL, + "PARAM_KEY" character varying(256) NOT NULL, + "PARAM_VALUE" character varying(4000) DEFAULT NULL::character varying +); + + +-- +-- Name: NUCLEUS_TABLES; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "NUCLEUS_TABLES" ( + "CLASS_NAME" character varying(128) NOT NULL, + "TABLE_NAME" character varying(128) NOT NULL, + "TYPE" character varying(4) NOT NULL, + "OWNER" character varying(2) NOT NULL, + "VERSION" character varying(20) NOT NULL, + "INTERFACE_NAME" character varying(255) DEFAULT NULL::character varying +); + + +-- +-- Name: PARTITIONS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PARTITIONS" ( + "PART_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "LAST_ACCESS_TIME" bigint NOT NULL, + "PART_NAME" character varying(767) DEFAULT NULL::character varying, + "SD_ID" bigint, + "TBL_ID" bigint +); + + +-- +-- Name: PARTITION_EVENTS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PARTITION_EVENTS" ( + "PART_NAME_ID" bigint NOT NULL, + "DB_NAME" character varying(128), + "EVENT_TIME" bigint NOT NULL, + "EVENT_TYPE" integer NOT NULL, + "PARTITION_NAME" character varying(767), + "TBL_NAME" character varying(256) +); + + +-- +-- Name: PARTITION_KEYS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PARTITION_KEYS" ( + "TBL_ID" bigint NOT NULL, + "PKEY_COMMENT" character varying(4000) DEFAULT NULL::character varying, + "PKEY_NAME" character varying(128) NOT NULL, + "PKEY_TYPE" character varying(767) NOT NULL, + "INTEGER_IDX" bigint NOT NULL +); + + +-- +-- Name: PARTITION_KEY_VALS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PARTITION_KEY_VALS" ( + "PART_ID" bigint NOT NULL, + "PART_KEY_VAL" character varying(256) DEFAULT NULL::character varying, + "INTEGER_IDX" bigint NOT NULL +); + + +-- +-- Name: PARTITION_PARAMS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PARTITION_PARAMS" ( + "PART_ID" bigint NOT NULL, + "PARAM_KEY" character varying(256) NOT NULL, + "PARAM_VALUE" character varying(4000) DEFAULT NULL::character varying +); + + +-- +-- Name: PART_COL_PRIVS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PART_COL_PRIVS" ( + "PART_COLUMN_GRANT_ID" bigint NOT NULL, + "COLUMN_NAME" character varying(767) DEFAULT NULL::character varying, + "CREATE_TIME" bigint NOT NULL, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PART_ID" bigint, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "PART_COL_PRIV" character varying(128) DEFAULT NULL::character varying +); + + +-- +-- Name: PART_PRIVS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PART_PRIVS" ( + "PART_GRANT_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PART_ID" bigint, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "PART_PRIV" character varying(128) DEFAULT NULL::character varying +); + + +-- +-- Name: ROLES; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "ROLES" ( + "ROLE_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "OWNER_NAME" character varying(128) DEFAULT NULL::character varying, + "ROLE_NAME" character varying(128) DEFAULT NULL::character varying +); + + +-- +-- Name: ROLE_MAP; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "ROLE_MAP" ( + "ROLE_GRANT_ID" bigint NOT NULL, + "ADD_TIME" bigint NOT NULL, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "ROLE_ID" bigint +); + + +-- +-- Name: SDS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "SDS" ( + "SD_ID" bigint NOT NULL, + "INPUT_FORMAT" character varying(4000) DEFAULT NULL::character varying, + "IS_COMPRESSED" boolean NOT NULL, + "LOCATION" character varying(4000) DEFAULT NULL::character varying, + "NUM_BUCKETS" bigint NOT NULL, + "OUTPUT_FORMAT" character varying(4000) DEFAULT NULL::character varying, + "SERDE_ID" bigint, + "CD_ID" bigint, + "IS_STOREDASSUBDIRECTORIES" boolean NOT NULL +); + + +-- +-- Name: SD_PARAMS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "SD_PARAMS" ( + "SD_ID" bigint NOT NULL, + "PARAM_KEY" character varying(256) NOT NULL, + "PARAM_VALUE" text DEFAULT NULL +); + + +-- +-- Name: SEQUENCE_TABLE; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "SEQUENCE_TABLE" ( + "SEQUENCE_NAME" character varying(255) NOT NULL, + "NEXT_VAL" bigint NOT NULL +); + + +-- +-- Name: SERDES; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "SERDES" ( + "SERDE_ID" bigint NOT NULL, + "NAME" character varying(128) DEFAULT NULL::character varying, + "SLIB" character varying(4000) DEFAULT NULL::character varying +); + + +-- +-- Name: SERDE_PARAMS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "SERDE_PARAMS" ( + "SERDE_ID" bigint NOT NULL, + "PARAM_KEY" character varying(256) NOT NULL, + "PARAM_VALUE" text DEFAULT NULL +); + + +-- +-- Name: SORT_COLS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "SORT_COLS" ( + "SD_ID" bigint NOT NULL, + "COLUMN_NAME" character varying(767) DEFAULT NULL::character varying, + "ORDER" bigint NOT NULL, + "INTEGER_IDX" bigint NOT NULL +); + + +-- +-- Name: TABLE_PARAMS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "TABLE_PARAMS" ( + "TBL_ID" bigint NOT NULL, + "PARAM_KEY" character varying(256) NOT NULL, + "PARAM_VALUE" text DEFAULT NULL +); + + +-- +-- Name: TBLS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "TBLS" ( + "TBL_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "DB_ID" bigint, + "LAST_ACCESS_TIME" bigint NOT NULL, + "OWNER" character varying(767) DEFAULT NULL::character varying, + "RETENTION" bigint NOT NULL, + "SD_ID" bigint, + "TBL_NAME" character varying(256) DEFAULT NULL::character varying, + "TBL_TYPE" character varying(128) DEFAULT NULL::character varying, + "VIEW_EXPANDED_TEXT" text, + "VIEW_ORIGINAL_TEXT" text, + "IS_REWRITE_ENABLED" boolean NOT NULL DEFAULT false +); + + +-- +-- Name: TBL_COL_PRIVS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "TBL_COL_PRIVS" ( + "TBL_COLUMN_GRANT_ID" bigint NOT NULL, + "COLUMN_NAME" character varying(767) DEFAULT NULL::character varying, + "CREATE_TIME" bigint NOT NULL, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "TBL_COL_PRIV" character varying(128) DEFAULT NULL::character varying, + "TBL_ID" bigint +); + + +-- +-- Name: TBL_PRIVS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "TBL_PRIVS" ( + "TBL_GRANT_ID" bigint NOT NULL, + "CREATE_TIME" bigint NOT NULL, + "GRANT_OPTION" smallint NOT NULL, + "GRANTOR" character varying(128) DEFAULT NULL::character varying, + "GRANTOR_TYPE" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_NAME" character varying(128) DEFAULT NULL::character varying, + "PRINCIPAL_TYPE" character varying(128) DEFAULT NULL::character varying, + "TBL_PRIV" character varying(128) DEFAULT NULL::character varying, + "TBL_ID" bigint +); + + +-- +-- Name: TYPES; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "TYPES" ( + "TYPES_ID" bigint NOT NULL, + "TYPE_NAME" character varying(128) DEFAULT NULL::character varying, + "TYPE1" character varying(767) DEFAULT NULL::character varying, + "TYPE2" character varying(767) DEFAULT NULL::character varying +); + + +-- +-- Name: TYPE_FIELDS; Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "TYPE_FIELDS" ( + "TYPE_NAME" bigint NOT NULL, + "COMMENT" character varying(256) DEFAULT NULL::character varying, + "FIELD_NAME" character varying(128) NOT NULL, + "FIELD_TYPE" character varying(767) NOT NULL, + "INTEGER_IDX" bigint NOT NULL +); + +CREATE TABLE "SKEWED_STRING_LIST" ( + "STRING_LIST_ID" bigint NOT NULL +); + +CREATE TABLE "SKEWED_STRING_LIST_VALUES" ( + "STRING_LIST_ID" bigint NOT NULL, + "STRING_LIST_VALUE" character varying(256) DEFAULT NULL::character varying, + "INTEGER_IDX" bigint NOT NULL +); + +CREATE TABLE "SKEWED_COL_NAMES" ( + "SD_ID" bigint NOT NULL, + "SKEWED_COL_NAME" character varying(256) DEFAULT NULL::character varying, + "INTEGER_IDX" bigint NOT NULL +); + +CREATE TABLE "SKEWED_COL_VALUE_LOC_MAP" ( + "SD_ID" bigint NOT NULL, + "STRING_LIST_ID_KID" bigint NOT NULL, + "LOCATION" character varying(4000) DEFAULT NULL::character varying +); + +CREATE TABLE "SKEWED_VALUES" ( + "SD_ID_OID" bigint NOT NULL, + "STRING_LIST_ID_EID" bigint NOT NULL, + "INTEGER_IDX" bigint NOT NULL +); + + +-- +-- Name: TAB_COL_STATS Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "MASTER_KEYS" +( + "KEY_ID" SERIAL, + "MASTER_KEY" varchar(767) NULL, + PRIMARY KEY ("KEY_ID") +); + +CREATE TABLE "DELEGATION_TOKENS" +( + "TOKEN_IDENT" varchar(767) NOT NULL, + "TOKEN" varchar(767) NULL, + PRIMARY KEY ("TOKEN_IDENT") +); + +CREATE TABLE "TAB_COL_STATS" ( + "CS_ID" bigint NOT NULL, + "DB_NAME" character varying(128) DEFAULT NULL::character varying, + "TABLE_NAME" character varying(256) DEFAULT NULL::character varying, + "COLUMN_NAME" character varying(767) DEFAULT NULL::character varying, + "COLUMN_TYPE" character varying(128) DEFAULT NULL::character varying, + "TBL_ID" bigint NOT NULL, + "LONG_LOW_VALUE" bigint, + "LONG_HIGH_VALUE" bigint, + "DOUBLE_LOW_VALUE" double precision, + "DOUBLE_HIGH_VALUE" double precision, + "BIG_DECIMAL_LOW_VALUE" character varying(4000) DEFAULT NULL::character varying, + "BIG_DECIMAL_HIGH_VALUE" character varying(4000) DEFAULT NULL::character varying, + "NUM_NULLS" bigint NOT NULL, + "NUM_DISTINCTS" bigint, + "AVG_COL_LEN" double precision, + "MAX_COL_LEN" bigint, + "NUM_TRUES" bigint, + "NUM_FALSES" bigint, + "LAST_ANALYZED" bigint NOT NULL +); + +-- +-- Table structure for VERSION +-- +CREATE TABLE "VERSION" ( + "VER_ID" bigint, + "SCHEMA_VERSION" character varying(127) NOT NULL, + "VERSION_COMMENT" character varying(255) NOT NULL +); + +-- +-- Name: PART_COL_STATS Type: TABLE; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE TABLE "PART_COL_STATS" ( + "CS_ID" bigint NOT NULL, + "DB_NAME" character varying(128) DEFAULT NULL::character varying, + "TABLE_NAME" character varying(256) DEFAULT NULL::character varying, + "PARTITION_NAME" character varying(767) DEFAULT NULL::character varying, + "COLUMN_NAME" character varying(767) DEFAULT NULL::character varying, + "COLUMN_TYPE" character varying(128) DEFAULT NULL::character varying, + "PART_ID" bigint NOT NULL, + "LONG_LOW_VALUE" bigint, + "LONG_HIGH_VALUE" bigint, + "DOUBLE_LOW_VALUE" double precision, + "DOUBLE_HIGH_VALUE" double precision, + "BIG_DECIMAL_LOW_VALUE" character varying(4000) DEFAULT NULL::character varying, + "BIG_DECIMAL_HIGH_VALUE" character varying(4000) DEFAULT NULL::character varying, + "NUM_NULLS" bigint NOT NULL, + "NUM_DISTINCTS" bigint, + "AVG_COL_LEN" double precision, + "MAX_COL_LEN" bigint, + "NUM_TRUES" bigint, + "NUM_FALSES" bigint, + "LAST_ANALYZED" bigint NOT NULL +); + +-- +-- Table structure for FUNCS +-- +CREATE TABLE "FUNCS" ( + "FUNC_ID" BIGINT NOT NULL, + "CLASS_NAME" VARCHAR(4000), + "CREATE_TIME" INTEGER NOT NULL, + "DB_ID" BIGINT, + "FUNC_NAME" VARCHAR(128), + "FUNC_TYPE" INTEGER NOT NULL, + "OWNER_NAME" VARCHAR(128), + "OWNER_TYPE" VARCHAR(10), + PRIMARY KEY ("FUNC_ID") +); + +-- +-- Table structure for FUNC_RU +-- +CREATE TABLE "FUNC_RU" ( + "FUNC_ID" BIGINT NOT NULL, + "RESOURCE_TYPE" INTEGER NOT NULL, + "RESOURCE_URI" VARCHAR(4000), + "INTEGER_IDX" INTEGER NOT NULL, + PRIMARY KEY ("FUNC_ID", "INTEGER_IDX") +); + +CREATE TABLE "NOTIFICATION_LOG" +( + "NL_ID" BIGINT NOT NULL, + "EVENT_ID" BIGINT NOT NULL, + "EVENT_TIME" INTEGER NOT NULL, + "EVENT_TYPE" VARCHAR(32) NOT NULL, + "DB_NAME" VARCHAR(128), + "TBL_NAME" VARCHAR(256), + "MESSAGE" text, + "MESSAGE_FORMAT" VARCHAR(16), + PRIMARY KEY ("NL_ID") +); + +CREATE TABLE "NOTIFICATION_SEQUENCE" +( + "NNI_ID" BIGINT NOT NULL, + "NEXT_EVENT_ID" BIGINT NOT NULL, + PRIMARY KEY ("NNI_ID") +); + +CREATE TABLE "KEY_CONSTRAINTS" +( + "CHILD_CD_ID" BIGINT, + "CHILD_INTEGER_IDX" BIGINT, + "CHILD_TBL_ID" BIGINT, + "PARENT_CD_ID" BIGINT NOT NULL, + "PARENT_INTEGER_IDX" BIGINT NOT NULL, + "PARENT_TBL_ID" BIGINT NOT NULL, + "POSITION" BIGINT NOT NULL, + "CONSTRAINT_NAME" VARCHAR(400) NOT NULL, + "CONSTRAINT_TYPE" SMALLINT NOT NULL, + "UPDATE_RULE" SMALLINT, + "DELETE_RULE" SMALLINT, + "ENABLE_VALIDATE_RELY" SMALLINT NOT NULL, + PRIMARY KEY ("CONSTRAINT_NAME", "POSITION") +) ; + +CREATE INDEX "CONSTRAINTS_PARENT_TBLID_INDEX" ON "KEY_CONSTRAINTS" USING BTREE ("PARENT_TBL_ID"); + +-- +-- Name: BUCKETING_COLS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "BUCKETING_COLS" + ADD CONSTRAINT "BUCKETING_COLS_pkey" PRIMARY KEY ("SD_ID", "INTEGER_IDX"); + + +-- +-- Name: CDS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "CDS" + ADD CONSTRAINT "CDS_pkey" PRIMARY KEY ("CD_ID"); + + +-- +-- Name: COLUMNS_V2_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "COLUMNS_V2" + ADD CONSTRAINT "COLUMNS_V2_pkey" PRIMARY KEY ("CD_ID", "COLUMN_NAME"); + + +-- +-- Name: DATABASE_PARAMS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "DATABASE_PARAMS" + ADD CONSTRAINT "DATABASE_PARAMS_pkey" PRIMARY KEY ("DB_ID", "PARAM_KEY"); + + +-- +-- Name: DBPRIVILEGEINDEX; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "DB_PRIVS" + ADD CONSTRAINT "DBPRIVILEGEINDEX" UNIQUE ("DB_ID", "PRINCIPAL_NAME", "PRINCIPAL_TYPE", "DB_PRIV", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: DBS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "DBS" + ADD CONSTRAINT "DBS_pkey" PRIMARY KEY ("DB_ID"); + + +-- +-- Name: DB_PRIVS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "DB_PRIVS" + ADD CONSTRAINT "DB_PRIVS_pkey" PRIMARY KEY ("DB_GRANT_ID"); + + +-- +-- Name: GLOBALPRIVILEGEINDEX; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "GLOBAL_PRIVS" + ADD CONSTRAINT "GLOBALPRIVILEGEINDEX" UNIQUE ("PRINCIPAL_NAME", "PRINCIPAL_TYPE", "USER_PRIV", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: GLOBAL_PRIVS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "GLOBAL_PRIVS" + ADD CONSTRAINT "GLOBAL_PRIVS_pkey" PRIMARY KEY ("USER_GRANT_ID"); + + +-- +-- Name: IDXS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "IDXS" + ADD CONSTRAINT "IDXS_pkey" PRIMARY KEY ("INDEX_ID"); + + +-- +-- Name: INDEX_PARAMS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "INDEX_PARAMS" + ADD CONSTRAINT "INDEX_PARAMS_pkey" PRIMARY KEY ("INDEX_ID", "PARAM_KEY"); + + +-- +-- Name: NUCLEUS_TABLES_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "NUCLEUS_TABLES" + ADD CONSTRAINT "NUCLEUS_TABLES_pkey" PRIMARY KEY ("CLASS_NAME"); + + +-- +-- Name: PARTITIONS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PARTITIONS" + ADD CONSTRAINT "PARTITIONS_pkey" PRIMARY KEY ("PART_ID"); + + +-- +-- Name: PARTITION_EVENTS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PARTITION_EVENTS" + ADD CONSTRAINT "PARTITION_EVENTS_pkey" PRIMARY KEY ("PART_NAME_ID"); + + +-- +-- Name: PARTITION_KEYS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PARTITION_KEYS" + ADD CONSTRAINT "PARTITION_KEYS_pkey" PRIMARY KEY ("TBL_ID", "PKEY_NAME"); + + +-- +-- Name: PARTITION_KEY_VALS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PARTITION_KEY_VALS" + ADD CONSTRAINT "PARTITION_KEY_VALS_pkey" PRIMARY KEY ("PART_ID", "INTEGER_IDX"); + + +-- +-- Name: PARTITION_PARAMS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PARTITION_PARAMS" + ADD CONSTRAINT "PARTITION_PARAMS_pkey" PRIMARY KEY ("PART_ID", "PARAM_KEY"); + + +-- +-- Name: PART_COL_PRIVS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PART_COL_PRIVS" + ADD CONSTRAINT "PART_COL_PRIVS_pkey" PRIMARY KEY ("PART_COLUMN_GRANT_ID"); + + +-- +-- Name: PART_PRIVS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PART_PRIVS" + ADD CONSTRAINT "PART_PRIVS_pkey" PRIMARY KEY ("PART_GRANT_ID"); + + +-- +-- Name: ROLEENTITYINDEX; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "ROLES" + ADD CONSTRAINT "ROLEENTITYINDEX" UNIQUE ("ROLE_NAME"); + + +-- +-- Name: ROLES_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "ROLES" + ADD CONSTRAINT "ROLES_pkey" PRIMARY KEY ("ROLE_ID"); + + +-- +-- Name: ROLE_MAP_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "ROLE_MAP" + ADD CONSTRAINT "ROLE_MAP_pkey" PRIMARY KEY ("ROLE_GRANT_ID"); + + +-- +-- Name: SDS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "SDS" + ADD CONSTRAINT "SDS_pkey" PRIMARY KEY ("SD_ID"); + + +-- +-- Name: SD_PARAMS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "SD_PARAMS" + ADD CONSTRAINT "SD_PARAMS_pkey" PRIMARY KEY ("SD_ID", "PARAM_KEY"); + + +-- +-- Name: SEQUENCE_TABLE_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "SEQUENCE_TABLE" + ADD CONSTRAINT "SEQUENCE_TABLE_pkey" PRIMARY KEY ("SEQUENCE_NAME"); + + +-- +-- Name: SERDES_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "SERDES" + ADD CONSTRAINT "SERDES_pkey" PRIMARY KEY ("SERDE_ID"); + + +-- +-- Name: SERDE_PARAMS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "SERDE_PARAMS" + ADD CONSTRAINT "SERDE_PARAMS_pkey" PRIMARY KEY ("SERDE_ID", "PARAM_KEY"); + + +-- +-- Name: SORT_COLS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "SORT_COLS" + ADD CONSTRAINT "SORT_COLS_pkey" PRIMARY KEY ("SD_ID", "INTEGER_IDX"); + + +-- +-- Name: TABLE_PARAMS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TABLE_PARAMS" + ADD CONSTRAINT "TABLE_PARAMS_pkey" PRIMARY KEY ("TBL_ID", "PARAM_KEY"); + + +-- +-- Name: TBLS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TBLS" + ADD CONSTRAINT "TBLS_pkey" PRIMARY KEY ("TBL_ID"); + + +-- +-- Name: TBL_COL_PRIVS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TBL_COL_PRIVS" + ADD CONSTRAINT "TBL_COL_PRIVS_pkey" PRIMARY KEY ("TBL_COLUMN_GRANT_ID"); + + +-- +-- Name: TBL_PRIVS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TBL_PRIVS" + ADD CONSTRAINT "TBL_PRIVS_pkey" PRIMARY KEY ("TBL_GRANT_ID"); + + +-- +-- Name: TYPES_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TYPES" + ADD CONSTRAINT "TYPES_pkey" PRIMARY KEY ("TYPES_ID"); + + +-- +-- Name: TYPE_FIELDS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TYPE_FIELDS" + ADD CONSTRAINT "TYPE_FIELDS_pkey" PRIMARY KEY ("TYPE_NAME", "FIELD_NAME"); + +ALTER TABLE ONLY "SKEWED_STRING_LIST" + ADD CONSTRAINT "SKEWED_STRING_LIST_pkey" PRIMARY KEY ("STRING_LIST_ID"); + +ALTER TABLE ONLY "SKEWED_STRING_LIST_VALUES" + ADD CONSTRAINT "SKEWED_STRING_LIST_VALUES_pkey" PRIMARY KEY ("STRING_LIST_ID", "INTEGER_IDX"); + + +ALTER TABLE ONLY "SKEWED_COL_NAMES" + ADD CONSTRAINT "SKEWED_COL_NAMES_pkey" PRIMARY KEY ("SD_ID", "INTEGER_IDX"); + +ALTER TABLE ONLY "SKEWED_COL_VALUE_LOC_MAP" + ADD CONSTRAINT "SKEWED_COL_VALUE_LOC_MAP_pkey" PRIMARY KEY ("SD_ID", "STRING_LIST_ID_KID"); + +ALTER TABLE ONLY "SKEWED_VALUES" + ADD CONSTRAINT "SKEWED_VALUES_pkey" PRIMARY KEY ("SD_ID_OID", "INTEGER_IDX"); + +-- +-- Name: TAB_COL_STATS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- +ALTER TABLE ONLY "TAB_COL_STATS" ADD CONSTRAINT "TAB_COL_STATS_pkey" PRIMARY KEY("CS_ID"); + +-- +-- Name: PART_COL_STATS_pkey; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- +ALTER TABLE ONLY "PART_COL_STATS" ADD CONSTRAINT "PART_COL_STATS_pkey" PRIMARY KEY("CS_ID"); + +-- +-- Name: UNIQUEINDEX; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "IDXS" + ADD CONSTRAINT "UNIQUEINDEX" UNIQUE ("INDEX_NAME", "ORIG_TBL_ID"); + + +-- +-- Name: UNIQUEPARTITION; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "PARTITIONS" + ADD CONSTRAINT "UNIQUEPARTITION" UNIQUE ("PART_NAME", "TBL_ID"); + + +-- +-- Name: UNIQUETABLE; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TBLS" + ADD CONSTRAINT "UNIQUETABLE" UNIQUE ("TBL_NAME", "DB_ID"); + + +-- +-- Name: UNIQUE_DATABASE; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "DBS" + ADD CONSTRAINT "UNIQUE_DATABASE" UNIQUE ("NAME"); + + +-- +-- Name: UNIQUE_TYPE; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "TYPES" + ADD CONSTRAINT "UNIQUE_TYPE" UNIQUE ("TYPE_NAME"); + + +-- +-- Name: USERROLEMAPINDEX; Type: CONSTRAINT; Schema: public; Owner: hiveuser; Tablespace: +-- + +ALTER TABLE ONLY "ROLE_MAP" + ADD CONSTRAINT "USERROLEMAPINDEX" UNIQUE ("PRINCIPAL_NAME", "ROLE_ID", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: BUCKETING_COLS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "BUCKETING_COLS_N49" ON "BUCKETING_COLS" USING btree ("SD_ID"); + + +-- +-- Name: DATABASE_PARAMS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "DATABASE_PARAMS_N49" ON "DATABASE_PARAMS" USING btree ("DB_ID"); + + +-- +-- Name: DB_PRIVS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "DB_PRIVS_N49" ON "DB_PRIVS" USING btree ("DB_ID"); + + +-- +-- Name: IDXS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "IDXS_N49" ON "IDXS" USING btree ("ORIG_TBL_ID"); + + +-- +-- Name: IDXS_N50; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "IDXS_N50" ON "IDXS" USING btree ("INDEX_TBL_ID"); + + +-- +-- Name: IDXS_N51; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "IDXS_N51" ON "IDXS" USING btree ("SD_ID"); + + +-- +-- Name: INDEX_PARAMS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "INDEX_PARAMS_N49" ON "INDEX_PARAMS" USING btree ("INDEX_ID"); + + +-- +-- Name: PARTITIONCOLUMNPRIVILEGEINDEX; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITIONCOLUMNPRIVILEGEINDEX" ON "PART_COL_PRIVS" USING btree ("PART_ID", "COLUMN_NAME", "PRINCIPAL_NAME", "PRINCIPAL_TYPE", "PART_COL_PRIV", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: PARTITIONEVENTINDEX; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITIONEVENTINDEX" ON "PARTITION_EVENTS" USING btree ("PARTITION_NAME"); + + +-- +-- Name: PARTITIONS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITIONS_N49" ON "PARTITIONS" USING btree ("TBL_ID"); + + +-- +-- Name: PARTITIONS_N50; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITIONS_N50" ON "PARTITIONS" USING btree ("SD_ID"); + + +-- +-- Name: PARTITION_KEYS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITION_KEYS_N49" ON "PARTITION_KEYS" USING btree ("TBL_ID"); + + +-- +-- Name: PARTITION_KEY_VALS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITION_KEY_VALS_N49" ON "PARTITION_KEY_VALS" USING btree ("PART_ID"); + + +-- +-- Name: PARTITION_PARAMS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTITION_PARAMS_N49" ON "PARTITION_PARAMS" USING btree ("PART_ID"); + + +-- +-- Name: PARTPRIVILEGEINDEX; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PARTPRIVILEGEINDEX" ON "PART_PRIVS" USING btree ("PART_ID", "PRINCIPAL_NAME", "PRINCIPAL_TYPE", "PART_PRIV", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: PART_COL_PRIVS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PART_COL_PRIVS_N49" ON "PART_COL_PRIVS" USING btree ("PART_ID"); + + +-- +-- Name: PART_PRIVS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PART_PRIVS_N49" ON "PART_PRIVS" USING btree ("PART_ID"); + + +-- +-- Name: PCS_STATS_IDX; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PCS_STATS_IDX" ON "PART_COL_STATS" USING btree ("DB_NAME","TABLE_NAME","COLUMN_NAME","PARTITION_NAME"); + + +-- +-- Name: ROLE_MAP_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "ROLE_MAP_N49" ON "ROLE_MAP" USING btree ("ROLE_ID"); + + +-- +-- Name: SDS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "SDS_N49" ON "SDS" USING btree ("SERDE_ID"); + + +-- +-- Name: SD_PARAMS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "SD_PARAMS_N49" ON "SD_PARAMS" USING btree ("SD_ID"); + + +-- +-- Name: SERDE_PARAMS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "SERDE_PARAMS_N49" ON "SERDE_PARAMS" USING btree ("SERDE_ID"); + + +-- +-- Name: SORT_COLS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "SORT_COLS_N49" ON "SORT_COLS" USING btree ("SD_ID"); + + +-- +-- Name: TABLECOLUMNPRIVILEGEINDEX; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TABLECOLUMNPRIVILEGEINDEX" ON "TBL_COL_PRIVS" USING btree ("TBL_ID", "COLUMN_NAME", "PRINCIPAL_NAME", "PRINCIPAL_TYPE", "TBL_COL_PRIV", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: TABLEPRIVILEGEINDEX; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TABLEPRIVILEGEINDEX" ON "TBL_PRIVS" USING btree ("TBL_ID", "PRINCIPAL_NAME", "PRINCIPAL_TYPE", "TBL_PRIV", "GRANTOR", "GRANTOR_TYPE"); + + +-- +-- Name: TABLE_PARAMS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TABLE_PARAMS_N49" ON "TABLE_PARAMS" USING btree ("TBL_ID"); + + +-- +-- Name: TBLS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TBLS_N49" ON "TBLS" USING btree ("DB_ID"); + + +-- +-- Name: TBLS_N50; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TBLS_N50" ON "TBLS" USING btree ("SD_ID"); + + +-- +-- Name: TBL_COL_PRIVS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TBL_COL_PRIVS_N49" ON "TBL_COL_PRIVS" USING btree ("TBL_ID"); + + +-- +-- Name: TBL_PRIVS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TBL_PRIVS_N49" ON "TBL_PRIVS" USING btree ("TBL_ID"); + + +-- +-- Name: TYPE_FIELDS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TYPE_FIELDS_N49" ON "TYPE_FIELDS" USING btree ("TYPE_NAME"); + +-- +-- Name: TAB_COL_STATS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "TAB_COL_STATS_N49" ON "TAB_COL_STATS" USING btree ("TBL_ID"); + +-- +-- Name: PART_COL_STATS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "PART_COL_STATS_N49" ON "PART_COL_STATS" USING btree ("PART_ID"); + +-- +-- Name: UNIQUEFUNCTION; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE UNIQUE INDEX "UNIQUEFUNCTION" ON "FUNCS" ("FUNC_NAME", "DB_ID"); + +-- +-- Name: FUNCS_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "FUNCS_N49" ON "FUNCS" ("DB_ID"); + +-- +-- Name: FUNC_RU_N49; Type: INDEX; Schema: public; Owner: hiveuser; Tablespace: +-- + +CREATE INDEX "FUNC_RU_N49" ON "FUNC_RU" ("FUNC_ID"); + + +ALTER TABLE ONLY "SKEWED_STRING_LIST_VALUES" + ADD CONSTRAINT "SKEWED_STRING_LIST_VALUES_fkey" FOREIGN KEY ("STRING_LIST_ID") REFERENCES "SKEWED_STRING_LIST"("STRING_LIST_ID") DEFERRABLE; + + +ALTER TABLE ONLY "SKEWED_COL_NAMES" + ADD CONSTRAINT "SKEWED_COL_NAMES_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +ALTER TABLE ONLY "SKEWED_COL_VALUE_LOC_MAP" + ADD CONSTRAINT "SKEWED_COL_VALUE_LOC_MAP_fkey1" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + +ALTER TABLE ONLY "SKEWED_COL_VALUE_LOC_MAP" + ADD CONSTRAINT "SKEWED_COL_VALUE_LOC_MAP_fkey2" FOREIGN KEY ("STRING_LIST_ID_KID") REFERENCES "SKEWED_STRING_LIST"("STRING_LIST_ID") DEFERRABLE; + +ALTER TABLE ONLY "SKEWED_VALUES" + ADD CONSTRAINT "SKEWED_VALUES_fkey1" FOREIGN KEY ("STRING_LIST_ID_EID") REFERENCES "SKEWED_STRING_LIST"("STRING_LIST_ID") DEFERRABLE; + +ALTER TABLE ONLY "SKEWED_VALUES" + ADD CONSTRAINT "SKEWED_VALUES_fkey2" FOREIGN KEY ("SD_ID_OID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: BUCKETING_COLS_SD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "BUCKETING_COLS" + ADD CONSTRAINT "BUCKETING_COLS_SD_ID_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: COLUMNS_V2_CD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "COLUMNS_V2" + ADD CONSTRAINT "COLUMNS_V2_CD_ID_fkey" FOREIGN KEY ("CD_ID") REFERENCES "CDS"("CD_ID") DEFERRABLE; + + +-- +-- Name: DATABASE_PARAMS_DB_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "DATABASE_PARAMS" + ADD CONSTRAINT "DATABASE_PARAMS_DB_ID_fkey" FOREIGN KEY ("DB_ID") REFERENCES "DBS"("DB_ID") DEFERRABLE; + + +-- +-- Name: DB_PRIVS_DB_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "DB_PRIVS" + ADD CONSTRAINT "DB_PRIVS_DB_ID_fkey" FOREIGN KEY ("DB_ID") REFERENCES "DBS"("DB_ID") DEFERRABLE; + + +-- +-- Name: IDXS_INDEX_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "IDXS" + ADD CONSTRAINT "IDXS_INDEX_TBL_ID_fkey" FOREIGN KEY ("INDEX_TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: IDXS_ORIG_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "IDXS" + ADD CONSTRAINT "IDXS_ORIG_TBL_ID_fkey" FOREIGN KEY ("ORIG_TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: IDXS_SD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "IDXS" + ADD CONSTRAINT "IDXS_SD_ID_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: INDEX_PARAMS_INDEX_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "INDEX_PARAMS" + ADD CONSTRAINT "INDEX_PARAMS_INDEX_ID_fkey" FOREIGN KEY ("INDEX_ID") REFERENCES "IDXS"("INDEX_ID") DEFERRABLE; + + +-- +-- Name: PARTITIONS_SD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PARTITIONS" + ADD CONSTRAINT "PARTITIONS_SD_ID_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: PARTITIONS_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PARTITIONS" + ADD CONSTRAINT "PARTITIONS_TBL_ID_fkey" FOREIGN KEY ("TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: PARTITION_KEYS_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PARTITION_KEYS" + ADD CONSTRAINT "PARTITION_KEYS_TBL_ID_fkey" FOREIGN KEY ("TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: PARTITION_KEY_VALS_PART_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PARTITION_KEY_VALS" + ADD CONSTRAINT "PARTITION_KEY_VALS_PART_ID_fkey" FOREIGN KEY ("PART_ID") REFERENCES "PARTITIONS"("PART_ID") DEFERRABLE; + + +-- +-- Name: PARTITION_PARAMS_PART_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PARTITION_PARAMS" + ADD CONSTRAINT "PARTITION_PARAMS_PART_ID_fkey" FOREIGN KEY ("PART_ID") REFERENCES "PARTITIONS"("PART_ID") DEFERRABLE; + + +-- +-- Name: PART_COL_PRIVS_PART_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PART_COL_PRIVS" + ADD CONSTRAINT "PART_COL_PRIVS_PART_ID_fkey" FOREIGN KEY ("PART_ID") REFERENCES "PARTITIONS"("PART_ID") DEFERRABLE; + + +-- +-- Name: PART_PRIVS_PART_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "PART_PRIVS" + ADD CONSTRAINT "PART_PRIVS_PART_ID_fkey" FOREIGN KEY ("PART_ID") REFERENCES "PARTITIONS"("PART_ID") DEFERRABLE; + + +-- +-- Name: ROLE_MAP_ROLE_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "ROLE_MAP" + ADD CONSTRAINT "ROLE_MAP_ROLE_ID_fkey" FOREIGN KEY ("ROLE_ID") REFERENCES "ROLES"("ROLE_ID") DEFERRABLE; + + +-- +-- Name: SDS_CD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "SDS" + ADD CONSTRAINT "SDS_CD_ID_fkey" FOREIGN KEY ("CD_ID") REFERENCES "CDS"("CD_ID") DEFERRABLE; + + +-- +-- Name: SDS_SERDE_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "SDS" + ADD CONSTRAINT "SDS_SERDE_ID_fkey" FOREIGN KEY ("SERDE_ID") REFERENCES "SERDES"("SERDE_ID") DEFERRABLE; + + +-- +-- Name: SD_PARAMS_SD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "SD_PARAMS" + ADD CONSTRAINT "SD_PARAMS_SD_ID_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: SERDE_PARAMS_SERDE_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "SERDE_PARAMS" + ADD CONSTRAINT "SERDE_PARAMS_SERDE_ID_fkey" FOREIGN KEY ("SERDE_ID") REFERENCES "SERDES"("SERDE_ID") DEFERRABLE; + + +-- +-- Name: SORT_COLS_SD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "SORT_COLS" + ADD CONSTRAINT "SORT_COLS_SD_ID_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: TABLE_PARAMS_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "TABLE_PARAMS" + ADD CONSTRAINT "TABLE_PARAMS_TBL_ID_fkey" FOREIGN KEY ("TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: TBLS_DB_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "TBLS" + ADD CONSTRAINT "TBLS_DB_ID_fkey" FOREIGN KEY ("DB_ID") REFERENCES "DBS"("DB_ID") DEFERRABLE; + + +-- +-- Name: TBLS_SD_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "TBLS" + ADD CONSTRAINT "TBLS_SD_ID_fkey" FOREIGN KEY ("SD_ID") REFERENCES "SDS"("SD_ID") DEFERRABLE; + + +-- +-- Name: TBL_COL_PRIVS_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "TBL_COL_PRIVS" + ADD CONSTRAINT "TBL_COL_PRIVS_TBL_ID_fkey" FOREIGN KEY ("TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: TBL_PRIVS_TBL_ID_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "TBL_PRIVS" + ADD CONSTRAINT "TBL_PRIVS_TBL_ID_fkey" FOREIGN KEY ("TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: TYPE_FIELDS_TYPE_NAME_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- + +ALTER TABLE ONLY "TYPE_FIELDS" + ADD CONSTRAINT "TYPE_FIELDS_TYPE_NAME_fkey" FOREIGN KEY ("TYPE_NAME") REFERENCES "TYPES"("TYPES_ID") DEFERRABLE; + +-- +-- Name: TAB_COL_STATS_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- +ALTER TABLE ONLY "TAB_COL_STATS" ADD CONSTRAINT "TAB_COL_STATS_fkey" FOREIGN KEY("TBL_ID") REFERENCES "TBLS"("TBL_ID") DEFERRABLE; + + +-- +-- Name: PART_COL_STATS_fkey; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +-- +ALTER TABLE ONLY "PART_COL_STATS" ADD CONSTRAINT "PART_COL_STATS_fkey" FOREIGN KEY("PART_ID") REFERENCES "PARTITIONS"("PART_ID") DEFERRABLE; + + +ALTER TABLE ONLY "VERSION" ADD CONSTRAINT "VERSION_pkey" PRIMARY KEY ("VER_ID"); + +-- Name: FUNCS_FK1; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +ALTER TABLE ONLY "FUNCS" + ADD CONSTRAINT "FUNCS_FK1" FOREIGN KEY ("DB_ID") REFERENCES "DBS" ("DB_ID") DEFERRABLE; + +-- Name: FUNC_RU_FK1; Type: FK CONSTRAINT; Schema: public; Owner: hiveuser +ALTER TABLE ONLY "FUNC_RU" + ADD CONSTRAINT "FUNC_RU_FK1" FOREIGN KEY ("FUNC_ID") REFERENCES "FUNCS" ("FUNC_ID") DEFERRABLE; + +-- +-- Name: public; Type: ACL; Schema: -; Owner: hiveuser +-- + +REVOKE ALL ON SCHEMA public FROM PUBLIC; +GRANT ALL ON SCHEMA public TO PUBLIC; + +-- +-- PostgreSQL database dump complete +-- + +------------------------------ +-- Transaction and lock tables +------------------------------ +CREATE TABLE TXNS ( + TXN_ID bigint PRIMARY KEY, + TXN_STATE char(1) NOT NULL, + TXN_STARTED bigint NOT NULL, + TXN_LAST_HEARTBEAT bigint NOT NULL, + TXN_USER varchar(128) NOT NULL, + TXN_HOST varchar(128) NOT NULL, + TXN_AGENT_INFO varchar(128), + TXN_META_INFO varchar(128), + TXN_HEARTBEAT_COUNT integer +); + +CREATE TABLE TXN_COMPONENTS ( + TC_TXNID bigint REFERENCES TXNS (TXN_ID), + TC_DATABASE varchar(128) NOT NULL, + TC_TABLE varchar(128), + TC_PARTITION varchar(767) DEFAULT NULL, + TC_OPERATION_TYPE char(1) NOT NULL +); + +CREATE INDEX TC_TXNID_INDEX ON TXN_COMPONENTS USING hash (TC_TXNID); + +CREATE TABLE COMPLETED_TXN_COMPONENTS ( + CTC_TXNID bigint, + CTC_DATABASE varchar(128) NOT NULL, + CTC_TABLE varchar(256), + CTC_PARTITION varchar(767) +); + +CREATE TABLE NEXT_TXN_ID ( + NTXN_NEXT bigint NOT NULL +); +INSERT INTO NEXT_TXN_ID VALUES(1); + +CREATE TABLE HIVE_LOCKS ( + HL_LOCK_EXT_ID bigint NOT NULL, + HL_LOCK_INT_ID bigint NOT NULL, + HL_TXNID bigint, + HL_DB varchar(128) NOT NULL, + HL_TABLE varchar(128), + HL_PARTITION varchar(767) DEFAULT NULL, + HL_LOCK_STATE char(1) NOT NULL, + HL_LOCK_TYPE char(1) NOT NULL, + HL_LAST_HEARTBEAT bigint NOT NULL, + HL_ACQUIRED_AT bigint, + HL_USER varchar(128) NOT NULL, + HL_HOST varchar(128) NOT NULL, + HL_HEARTBEAT_COUNT integer, + HL_AGENT_INFO varchar(128), + HL_BLOCKEDBY_EXT_ID bigint, + HL_BLOCKEDBY_INT_ID bigint, + PRIMARY KEY(HL_LOCK_EXT_ID, HL_LOCK_INT_ID) +); + +CREATE INDEX HL_TXNID_INDEX ON HIVE_LOCKS USING hash (HL_TXNID); + +CREATE TABLE NEXT_LOCK_ID ( + NL_NEXT bigint NOT NULL +); +INSERT INTO NEXT_LOCK_ID VALUES(1); + +CREATE TABLE COMPACTION_QUEUE ( + CQ_ID bigint PRIMARY KEY, + CQ_DATABASE varchar(128) NOT NULL, + CQ_TABLE varchar(128) NOT NULL, + CQ_PARTITION varchar(767), + CQ_STATE char(1) NOT NULL, + CQ_TYPE char(1) NOT NULL, + CQ_TBLPROPERTIES varchar(2048), + CQ_WORKER_ID varchar(128), + CQ_START bigint, + CQ_RUN_AS varchar(128), + CQ_HIGHEST_TXN_ID bigint, + CQ_META_INFO bytea, + CQ_HADOOP_JOB_ID varchar(32) +); + +CREATE TABLE NEXT_COMPACTION_QUEUE_ID ( + NCQ_NEXT bigint NOT NULL +); +INSERT INTO NEXT_COMPACTION_QUEUE_ID VALUES(1); + +CREATE TABLE COMPLETED_COMPACTIONS ( + CC_ID bigint PRIMARY KEY, + CC_DATABASE varchar(128) NOT NULL, + CC_TABLE varchar(128) NOT NULL, + CC_PARTITION varchar(767), + CC_STATE char(1) NOT NULL, + CC_TYPE char(1) NOT NULL, + CC_TBLPROPERTIES varchar(2048), + CC_WORKER_ID varchar(128), + CC_START bigint, + CC_END bigint, + CC_RUN_AS varchar(128), + CC_HIGHEST_TXN_ID bigint, + CC_META_INFO bytea, + CC_HADOOP_JOB_ID varchar(32) +); + +CREATE TABLE AUX_TABLE ( + MT_KEY1 varchar(128) NOT NULL, + MT_KEY2 bigint NOT NULL, + MT_COMMENT varchar(255), + PRIMARY KEY(MT_KEY1, MT_KEY2) +); + +CREATE TABLE WRITE_SET ( + WS_DATABASE varchar(128) NOT NULL, + WS_TABLE varchar(128) NOT NULL, + WS_PARTITION varchar(767), + WS_TXNID bigint NOT NULL, + WS_COMMIT_ID bigint NOT NULL, + WS_OPERATION_TYPE char(1) NOT NULL +); + +-- ----------------------------------------------------------------- +-- Record schema version. Should be the last step in the init script +-- ----------------------------------------------------------------- +INSERT INTO "VERSION" ("VER_ID", "SCHEMA_VERSION", "VERSION_COMMENT") VALUES (1, '2.3.0', 'Hive release version 2.3.0'); diff --git a/api/utils/zeppelinAPI.py b/api/utils/zeppelinAPI.py index a597f374..41e2f089 100644 --- a/api/utils/zeppelinAPI.py +++ b/api/utils/zeppelinAPI.py @@ -25,6 +25,7 @@ class ZeppelinAPI: def __init__(self, zeppelinServerId: str = None): if(zeppelinServerId): self.ZEPPELIN_ADDR = "http://" + zeppelinServerId + ":" + settings.ZEPPELIN_PORT + logger.info(f"{self.ZEPPELIN_ADDR} {zeppelinServerId}") def setZeppelinAddress(self, host: str, port: str): self.ZEPPELIN_ADDR = "http://"+ host + ":" + port diff --git a/api/workflows/models.py b/api/workflows/models.py index 0de6b3f5..75ac4c08 100644 --- a/api/workflows/models.py +++ b/api/workflows/models.py @@ -1,5 +1,6 @@ from django.db import models from django_celery_beat.models import PeriodicTask +from workspace.models import Workspace STATUS_SUCCESS = "SUCCESS" STATUS_ERROR = "ERROR" @@ -14,6 +15,7 @@ class Workflow(models.Model): periodictask = models.ForeignKey(PeriodicTask, on_delete=models.CASCADE, null=True) triggerWorkflow = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, db_index=True) triggerWorkflowStatus = models.CharField(max_length=50, default=STATUS_SUCCESS) + workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE,null=True, default=None) class WorkflowRunLogs(models.Model): workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, db_index=True) diff --git a/api/workflows/services/workflowServices.py b/api/workflows/services/workflowServices.py index b3db7b6f..7b33c10e 100644 --- a/api/workflows/services/workflowServices.py +++ b/api/workflows/services/workflowServices.py @@ -18,13 +18,13 @@ class WorkflowServices: """ @staticmethod - def getWorkflows(offset: int = 0, limit: int = 25, sortColumn : str = None, sortOrder : str = None): + def getWorkflows(workspaceId: int, offset: int = 0, limit: int = 25, sortColumn : str = None, sortOrder : str = None): """ Service to fetch and serialize Workflows :param offset: Offset for fetching NotebookJob objects """ res = ApiResponse(message="Error retrieving workflows") - workflows = Workflow.objects.order_by("-id") + workflows = Workflow.objects.filter(workspace_id=workspaceId).order_by("-id") total = workflows.count() if(sortColumn): @@ -64,6 +64,7 @@ def createWorkflow( triggerWorkflowId: int, triggerWorkflowStatus: str, notebookIds: List[int], + workspaceId:int ): """ Creates workflow @@ -80,7 +81,8 @@ def createWorkflow( name=name, periodictask=periodictask, triggerWorkflow_id=triggerWorkflowId, - triggerWorkflowStatus=triggerWorkflowStatus + triggerWorkflowStatus=triggerWorkflowStatus, + workspace_id=workspaceId ) if scheduleId: periodictask = PeriodicTask.objects.create( diff --git a/api/workflows/taskUtils.py b/api/workflows/taskUtils.py index 2d619ea4..15d425aa 100644 --- a/api/workflows/taskUtils.py +++ b/api/workflows/taskUtils.py @@ -12,7 +12,6 @@ STATUS_RUNNING, STATUS_ABORTED ) -from utils.zeppelinAPI import Zeppelin from genie.tasks import runNotebookJob as runNotebookJobTask from genie.models import NOTEBOOK_STATUS_QUEUED, NotebookRunLogs, NOTEBOOK_STATUS_RUNNING, NOTEBOOK_STATUS_SUCCESS @@ -53,7 +52,7 @@ def runWorkflow(workflowId: int, taskId: str, workflowRunLogsId: int = None): return workflowRunLogs.status @staticmethod - def __runNotebookJobsFromList(notebookIds: List[int], workflowRunLogsId: int): + def __runNotebookJobsFromList(notebookIds: List[int], workflowRunLogsId: int, workspaceId: int = 0): """ Runs notebook jobs for all notebookIds """ @@ -62,7 +61,7 @@ def __runNotebookJobsFromList(notebookIds: List[int], workflowRunLogsId: int): notebookRunLogs = NotebookRunLogs.objects.create( notebookId=notebookId, status=NOTEBOOK_STATUS_QUEUED, runType="Workflow", workflowRunLogs_id=workflowRunLogsId ) - response = runNotebookJobTask.delay(notebookId=notebookId, notebookRunLogsId=notebookRunLogs.id) + response = runNotebookJobTask.delay(notebookId=notebookId, notebookRunLogsId=notebookRunLogs.id, workspaceId=workspaceId) notebookRunLogs.taskId = response.id notebookRunLogs.save() notebookRunLogsIds.append(notebookRunLogs.id) diff --git a/api/workflows/tests/test_views.py b/api/workflows/tests/test_views.py index 965d13d9..805289bd 100644 --- a/api/workflows/tests/test_views.py +++ b/api/workflows/tests/test_views.py @@ -18,7 +18,8 @@ def test_workflows(client, populate_seed_data, mocker): # Create workflow test - path = reverse('workflowsPost') + path = reverse('workflowsPost', kwargs={"workspaceId": 1}) + mixer.blend("workspace.workspace", id=1, name="test") data = {'name': 'test', 'notebookIds': [], 'scheduleId': None, @@ -30,7 +31,7 @@ def test_workflows(client, populate_seed_data, mocker): workflowId = response.data['data'] # Update workflow test - path = reverse('workflowsPost') + path = reverse('workflowsPost', kwargs={"workspaceId": 1}) data = {'id': workflowId, 'name': 'testWorkflow', 'notebookIds': ['2G5CNGNAJ'], @@ -42,7 +43,7 @@ def test_workflows(client, populate_seed_data, mocker): assert response.data['success'] # Get workflows test - path = reverse('workflows', kwargs={"offset": 0}) + path = reverse('workflows', kwargs={"workspaceId": 1}) response = client.get(path) exceptedWorkflow = {'id': workflowId, 'lastRun': None, @@ -67,7 +68,7 @@ def test_workflows(client, populate_seed_data, mocker): assert response.data['data'] # Assign trigger workflow test - path = reverse('workflowsPost') + path = reverse('workflowsPost', kwargs={"workspaceId": 1}) data = {'name': 'triggerWorkflow', 'notebookIds': [], 'scheduleId': None, @@ -90,7 +91,7 @@ def test_workflows(client, populate_seed_data, mocker): assert response.data['data'] == 1 # Getting workflows test - path = reverse('workflows', kwargs={"offset": 0}) + path = reverse('workflows', kwargs={"workspaceId": 1}) response = client.get(path) expectedWorkflows = [{'id': _workflowId, 'lastRun': None, @@ -125,7 +126,7 @@ def test_workflows(client, populate_seed_data, mocker): runNotebookJobPatch.stop() # Get workflow test - path = reverse('workflows', kwargs={"offset": 0}) + path = reverse('workflows', kwargs={"workspaceId": 1}) response = client.get(path) expectedLastRunKeys = set(["endTimestamp", "startTimestamp", "status", "workflowRunId"]) assert response.status_code == 200 @@ -134,7 +135,7 @@ def test_workflows(client, populate_seed_data, mocker): assert set(response.data['data']['workflows'][1]['lastRun'].keys()) == expectedLastRunKeys # Sorting on workflows test - path = reverse('workflowsPost') + path = reverse('workflowsPost', kwargs={"workspaceId": 1}) data = {'name': 'sortTest', 'notebookIds': [], 'scheduleId': None, @@ -145,23 +146,23 @@ def test_workflows(client, populate_seed_data, mocker): assert response.data['data'] # Sort on name test - path = reverse("workflows", kwargs={"offset": 0}) + "?sortColumn=name&sortOrder=descend" + path = reverse("workflows", kwargs={"workspaceId": 1}) + "?sortColumn=name&sortOrder=descend" response = client.get(path) names = [ x['name'] for x in response.data['data']['workflows'] ] assert names == sorted(names, reverse=True) - path = reverse("workflows", kwargs={"offset": 0}) + "?sortColumn=name&sortOrder=ascend" + path = reverse("workflows", kwargs={"workspaceId": 1}) + "?sortColumn=name&sortOrder=ascend" response = client.get(path) names = [ x['name'] for x in response.data['data']['workflows'] ] assert names == sorted(names) # Sort on trigger workflow name test - path = reverse("workflows", kwargs={"offset": 0}) + "?sortColumn=triggerWorkflow&sortOrder=descend" + path = reverse("workflows", kwargs={"workspaceId": 1}) + "?sortColumn=triggerWorkflow&sortOrder=descend" response = client.get(path) triggerWorkflows = [ x['triggerWorkflow']['name'] for x in response.data['data']['workflows'] if x['triggerWorkflow'] ] assert triggerWorkflows == sorted(triggerWorkflows, reverse=True) - path = reverse("workflows", kwargs={"offset": 0}) + "?sortColumn=triggerWorkflow&sortOrder=ascend" + path = reverse("workflows", kwargs={"workspaceId": 1}) + "?sortColumn=triggerWorkflow&sortOrder=ascend" response = client.get(path) triggerWorkflows = [ x['triggerWorkflow']['name'] for x in response.data['data']['workflows'] if x['triggerWorkflow'] ] assert triggerWorkflows == sorted(triggerWorkflows) diff --git a/api/workflows/urls.py b/api/workflows/urls.py index 35c1a203..f218138b 100644 --- a/api/workflows/urls.py +++ b/api/workflows/urls.py @@ -2,9 +2,9 @@ from . import views urlpatterns = [ - path("workflows/", views.Workflows.as_view(), name="workflows"), + path("workflows/", views.Workflows.as_view(), name="workflows"), path("workflow/", views.Workflow.as_view(), name="workflow"), - path("workflows", views.Workflows.as_view(), name="workflowsPost"), + path("workflows/", views.Workflows.as_view(), name="workflowsPost"), path("workflowRuns//", views.WorkflowRun.as_view(), name="workflowRuns"), path("workflowRuns/", views.WorkflowRunLog.as_view(), name="workflowRunLogs"), path("runWorkflow/", views.RunWorkflow.as_view(), name="runWorkflow"), diff --git a/api/workflows/views.py b/api/workflows/views.py index c8d2f6a1..d2256508 100644 --- a/api/workflows/views.py +++ b/api/workflows/views.py @@ -8,16 +8,16 @@ class Workflows(APIView): """ Class to get and post workflows """ - def get(self, request, offset: int): + def get(self, request, workspaceId: int): """Gets all workflows""" - limit= int(request.GET.get("limit", 25)) sortColumn = request.GET.get("sortColumn", "") sortOrder = request.GET.get("sortOrder", '') - res = WorkflowServices.getWorkflows(offset, limit, sortColumn, sortOrder) + offset = int(request.GET.get("offset", 0)) + res = WorkflowServices.getWorkflows(workspaceId, offset, limit, sortColumn, sortOrder) return Response(res.json()) - def post(self, request): + def post(self, request, workspaceId: int): data = request.data name = data.get("name", "") scheduleId = data.get("scheduleId", "") @@ -28,7 +28,7 @@ def post(self, request): if 'id' in data and data['id']: res = WorkflowServices.updateWorkflow(data['id'], name, scheduleId, triggerWorkflowId, triggerWorkflowStatus, notebookIds) else: - res = WorkflowServices.createWorkflow(name, scheduleId, triggerWorkflowId, triggerWorkflowStatus, notebookIds) + res = WorkflowServices.createWorkflow(name, scheduleId, triggerWorkflowId, triggerWorkflowStatus, notebookIds, workspaceId) return Response(res.json()) diff --git a/api/workspace/__init__.py b/api/workspace/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/api/workspace/admin.py b/api/workspace/admin.py new file mode 100755 index 00000000..5dcb352e --- /dev/null +++ b/api/workspace/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from workspace.models import Workspace, WorkspaceConfig, EnvironmentVariable, KubernetesTemplate + +admin.site.register(Workspace) +admin.site.register(WorkspaceConfig) +admin.site.register(EnvironmentVariable) +admin.site.register(KubernetesTemplate) diff --git a/api/workspace/apps.py b/api/workspace/apps.py new file mode 100755 index 00000000..7be3c801 --- /dev/null +++ b/api/workspace/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class WorkspaceConfig(AppConfig): + name = 'workspace' diff --git a/api/workspace/migrations/0001_initial.py b/api/workspace/migrations/0001_initial.py new file mode 100755 index 00000000..ba4552c5 --- /dev/null +++ b/api/workspace/migrations/0001_initial.py @@ -0,0 +1,60 @@ +# Generated by Django 3.2.4 on 2021-07-24 16:03 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Workspace', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(default='')), + ], + ), + migrations.CreateModel( + name='WorkspaceConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('storage', models.CharField(blank=True, max_length=100, null=True)), + ('s3AccessKey', models.TextField(blank=True, null=True)), + ('s3SecretKey', models.TextField(blank=True, null=True)), + ('googleKey', models.TextField(blank=True, null=True)), + ('azureAccount', models.TextField(blank=True, null=True)), + ('azureKey', models.TextField(blank=True, null=True)), + ('inactivityTimeout', models.IntegerField(default=600)), + ('zeppelinServerImage', models.CharField(blank=True, max_length=200, null=True)), + ('zeppelinInterpreterImage', models.CharField(blank=True, max_length=200, null=True)), + ('sparkImage', models.CharField(blank=True, max_length=200, null=True)), + ('acidProvider', models.CharField(blank=True, max_length=100, null=True)), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workspace.workspace')), + ], + ), + migrations.CreateModel( + name='KubernetesTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('zeppelinServerTemplate', models.TextField(blank=True, null=True)), + ('zeppelinJobServerTemplate', models.TextField(blank=True, null=True)), + ('interpreterTemplate', models.TextField(blank=True, null=True)), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workspace.workspace')), + ], + ), + migrations.CreateModel( + name='EnvironmentVariable', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.TextField(blank=True, null=True)), + ('value', models.TextField(blank=True, null=True)), + ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='workspace.workspace')), + ], + ), + ] diff --git a/api/workspace/migrations/0002_workspaceconfig_warehouselocation.py b/api/workspace/migrations/0002_workspaceconfig_warehouselocation.py new file mode 100755 index 00000000..2322d01f --- /dev/null +++ b/api/workspace/migrations/0002_workspaceconfig_warehouselocation.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.4 on 2021-07-29 12:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('workspace', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='workspaceconfig', + name='warehouseLocation', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/api/workspace/migrations/__init__.py b/api/workspace/migrations/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/api/workspace/models.py b/api/workspace/models.py new file mode 100755 index 00000000..fbbbb026 --- /dev/null +++ b/api/workspace/models.py @@ -0,0 +1,32 @@ +from django.db import models + +class Workspace(models.Model): + name = models.CharField(max_length=200) + description = models.TextField(default="") + +class WorkspaceConfig(models.Model): + workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE) + storage = models.CharField(max_length=100, blank=True, null=True) # GS/S3/AZFS/PV + warehouseLocation = models.TextField(null=True, blank=True) + s3AccessKey = models.TextField(null=True, blank=True) + s3SecretKey = models.TextField(null=True, blank=True) + googleKey = models.TextField(null=True, blank=True) + azureAccount = models.TextField(null=True, blank=True) + azureKey = models.TextField(null=True, blank=True) + inactivityTimeout = models.IntegerField(default=600) # Inactivity timeout in seconds, default 600 - 10 mins. + zeppelinServerImage = models.CharField(max_length=200, blank=True, null=True) + zeppelinInterpreterImage = models.CharField(max_length=200, blank=True, null=True) + sparkImage = models.CharField(max_length=200, blank=True, null=True) + acidProvider = models.CharField(max_length=100, blank=True, null=True) # Delta/Iceberg/None + + +class EnvironmentVariable(models.Model): + workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE) + name = models.TextField(null=True,blank=True) + value = models.TextField(null=True, blank=True) + +class KubernetesTemplate(models.Model): + workspace = models.ForeignKey(Workspace, on_delete=models.CASCADE) + zeppelinServerTemplate = models.TextField(null=True, blank=True) + zeppelinJobServerTemplate = models.TextField(null=True, blank=True) + interpreterTemplate = models.TextField(null=True, blank=True) \ No newline at end of file diff --git a/api/workspace/serializers.py b/api/workspace/serializers.py new file mode 100755 index 00000000..60ce1c09 --- /dev/null +++ b/api/workspace/serializers.py @@ -0,0 +1,16 @@ +from rest_framework.fields import SerializerMethodField +from rest_framework import serializers +from workspace.models import Workspace + +class WorkspaceSerializer(serializers.ModelSerializer): + """ + Serializer for the model NotebookJob + """ + workspaceConfig = SerializerMethodField() + + def get_workspaceConfig(self, obj): + return obj.workspaceconfig_set.values('acidProvider', 'storage', 'warehouseLocation', 'sparkImage', 'zeppelinInterpreterImage', 'zeppelinServerImage')[0] + + class Meta: + model = Workspace + fields = ["id", "name", "description", "workspaceConfig"] diff --git a/api/workspace/services/__init__.py b/api/workspace/services/__init__.py new file mode 100755 index 00000000..3da4579f --- /dev/null +++ b/api/workspace/services/__init__.py @@ -0,0 +1 @@ +from .workspace import WorkspaceService \ No newline at end of file diff --git a/api/workspace/services/templates/conf/interpreter.json b/api/workspace/services/templates/conf/interpreter.json new file mode 100755 index 00000000..2c5475ee --- /dev/null +++ b/api/workspace/services/templates/conf/interpreter.json @@ -0,0 +1,822 @@ +{ + "interpreterSettings": { + "angular": { + "id": "angular", + "name": "angular", + "group": "angular", + "properties": {}, + "status": "READY", + "interpreterGroup": [ + { + "name": "angular", + "class": "org.apache.zeppelin.angular.AngularInterpreter", + "defaultInterpreter": false, + "editor": { + "editOnDblClick": true, + "completionSupport": false + } + }, + { + "name": "ng", + "class": "org.apache.zeppelin.angular.AngularInterpreter", + "defaultInterpreter": false, + "editor": { + "editOnDblClick": true, + "completionSupport": false + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + }, + "python": { + "id": "python", + "name": "python", + "group": "python", + "properties": { + "zeppelin.python": { + "name": "zeppelin.python", + "value": "python", + "type": "string", + "description": "Python binary executable path. It is set to python by default.(assume python is in your $PATH)" + }, + "zeppelin.python.maxResult": { + "name": "zeppelin.python.maxResult", + "value": "1000", + "type": "number", + "description": "Max number of dataframe rows to display." + }, + "zeppelin.python.useIPython": { + "name": "zeppelin.python.useIPython", + "value": true, + "type": "checkbox", + "description": "Whether use IPython when it is available in `%python`" + }, + "zeppelin.ipython.launch.timeout": { + "name": "zeppelin.ipython.launch.timeout", + "value": "30000", + "type": "number", + "description": "Time out for ipython launch" + }, + "zeppelin.ipython.grpc.message_size": { + "name": "zeppelin.ipython.grpc.message_size", + "value": "33554432", + "type": "number", + "description": "grpc message size, default is 32M" + } + }, + "status": "READY", + "interpreterGroup": [ + { + "name": "python", + "class": "org.apache.zeppelin.python.PythonInterpreter", + "defaultInterpreter": true, + "editor": { + "language": "python", + "editOnDblClick": false, + "completionSupport": true + } + }, + { + "name": "ipython", + "class": "org.apache.zeppelin.python.IPythonInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "python", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": true + } + }, + { + "name": "sql", + "class": "org.apache.zeppelin.python.PythonInterpreterPandasSql", + "defaultInterpreter": false, + "editor": { + "language": "sql", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": false + } + }, + { + "name": "conda", + "class": "org.apache.zeppelin.python.PythonCondaInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "sh", + "editOnDblClick": false, + "completionSupport": false + } + }, + { + "name": "docker", + "class": "org.apache.zeppelin.python.PythonDockerInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "sh", + "editOnDblClick": false, + "completionSupport": false + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + }, + "jupyter": { + "id": "jupyter", + "name": "jupyter", + "group": "jupyter", + "properties": {}, + "status": "READY", + "interpreterGroup": [ + { + "name": "jupyter", + "class": "org.apache.zeppelin.jupyter.JupyterInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "text", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": true + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + }, + "sh": { + "id": "sh", + "name": "sh", + "group": "sh", + "properties": { + "shell.command.timeout.millisecs": { + "name": "shell.command.timeout.millisecs", + "value": "60000", + "type": "number", + "description": "Shell command time out in millisecs. Default \u003d 60000" + }, + "shell.command.timeout.check.interval": { + "name": "shell.command.timeout.check.interval", + "value": "60000", + "type": "number", + "description": "Shell command output check interval in millisecs. Default \u003d 10000" + }, + "shell.working.directory.user.home": { + "name": "shell.working.directory.user.home", + "value": false, + "type": "checkbox", + "description": "If this set to true, the shell\u0027s working directory will be set to user home" + }, + "zeppelin.shell.auth.type": { + "name": "zeppelin.shell.auth.type", + "value": "", + "type": "string", + "description": "If auth type is needed, Example: KERBEROS" + }, + "zeppelin.shell.keytab.location": { + "name": "zeppelin.shell.keytab.location", + "value": "", + "type": "string", + "description": "Kerberos keytab location" + }, + "zeppelin.shell.principal": { + "name": "zeppelin.shell.principal", + "value": "", + "type": "string", + "description": "Kerberos principal" + }, + "zeppelin.shell.interpolation": { + "name": "zeppelin.shell.interpolation", + "value": false, + "type": "checkbox", + "description": "Enable ZeppelinContext variable interpolation into paragraph text" + }, + "zeppelin.terminal.ip.mapping": { + "name": "zeppelin.terminal.ip.mapping", + "value": "", + "type": "string", + "description": "Internal and external IP mapping of zeppelin server" + } + }, + "status": "READY", + "interpreterGroup": [ + { + "name": "sh", + "class": "org.apache.zeppelin.shell.ShellInterpreter", + "defaultInterpreter": true, + "editor": { + "language": "sh", + "editOnDblClick": false, + "completionSupport": false + } + }, + { + "name": "terminal", + "class": "org.apache.zeppelin.shell.TerminalInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "sh", + "editOnDblClick": false, + "completionSupport": false + }, + "config": { + "checkEmpty": false + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + }, + "spark": { + "id": "spark", + "name": "spark", + "group": "spark", + "properties": { + "SPARK_HOME": { + "name": "SPARK_HOME", + "value": "/spark", + "type": "string", + "description": "Location of spark distribution" + }, + "spark.master": { + "name": "spark.master", + "value": "k8s://https://kubernetes.default.svc", + "type": "string", + "description": "Spark master uri. local | yarn-client | yarn-cluster | spark master address of standalone mode, ex) spark://master_host:7077" + }, + "spark.submit.deployMode": { + "name": "spark.submit.deployMode", + "value": "", + "type": "string", + "description": "The deploy mode of Spark driver program, either \"client\" or \"cluster\", Which means to launch driver program locally (\"client\") or remotely (\"cluster\") on one of the nodes inside the cluster." + }, + "spark.app.name": { + "name": "spark.app.name", + "value": "", + "type": "string", + "description": "The name of spark application." + }, + "spark.driver.cores": { + "name": "spark.driver.cores", + "value": "1", + "type": "number", + "description": "Number of cores to use for the driver process, only in cluster mode." + }, + "spark.driver.memory": { + "name": "spark.driver.memory", + "value": "2g", + "type": "string", + "description": "Amount of memory to use for the driver process, i.e. where SparkContext is initialized, in the same format as JVM memory strings with a size unit suffix (\"k\", \"m\", \"g\" or \"t\") (e.g. 512m, 2g)." + }, + "spark.executor.cores": { + "name": "spark.executor.cores", + "value": "1", + "type": "number", + "description": "The number of cores to use on each executor" + }, + "spark.executor.memory": { + "name": "spark.executor.memory", + "value": "4g", + "type": "string", + "description": "Executor memory per worker instance. ex) 512m, 32g" + }, + "spark.executor.instances": { + "name": "spark.executor.instances", + "value": "1", + "type": "number", + "description": "The number of executors for static allocation." + }, + "spark.files": { + "name": "spark.files", + "value": "", + "type": "string", + "description": "Comma-separated list of files to be placed in the working directory of each executor. Globs are allowed." + }, + "spark.jars": { + "name": "spark.jars", + "value": "", + "type": "string", + "description": "Comma-separated list of jars to include on the driver and executor classpaths. Globs are allowed." + }, + "spark.jars.packages": { + "name": "spark.jars.packages", + "value": "com.google.guava:guava:30.1.1-jre", + "type": "string", + "description": "Comma-separated list of Maven coordinates of jars to include on the driver and executor classpaths. The coordinates should be groupId:artifactId:version. If spark.jars.ivySettings is given artifacts will be resolved according to the configuration in the file, otherwise artifacts will be searched for in the local maven repo, then maven central and finally any additional remote repositories given by the command-line option --repositories." + }, + "zeppelin.spark.useHiveContext": { + "name": "zeppelin.spark.useHiveContext", + "value": true, + "type": "checkbox", + "description": "Use HiveContext instead of SQLContext if it is true. Enable hive for SparkSession." + }, + "zeppelin.spark.printREPLOutput": { + "name": "zeppelin.spark.printREPLOutput", + "value": true, + "type": "checkbox", + "description": "Print REPL output" + }, + "zeppelin.spark.maxResult": { + "name": "zeppelin.spark.maxResult", + "value": "1000", + "type": "number", + "description": "Max number of result to display." + }, + "zeppelin.spark.enableSupportedVersionCheck": { + "name": "zeppelin.spark.enableSupportedVersionCheck", + "value": true, + "type": "checkbox", + "description": "Whether checking supported spark version. Developer only setting, not for production use" + }, + "zeppelin.spark.uiWebUrl": { + "name": "zeppelin.spark.uiWebUrl", + "value": "", + "type": "string", + "description": "Override Spark UI default URL. In Kubernetes mode, value can be Jinja template string with 3 template variables \u0027PORT\u0027, \u0027SERVICE_NAME\u0027 and \u0027SERVICE_DOMAIN\u0027. (ex: http://{{PORT}}-{{SERVICE_NAME}}.{{SERVICE_DOMAIN}})" + }, + "zeppelin.spark.ui.hidden": { + "name": "zeppelin.spark.ui.hidden", + "value": false, + "type": "checkbox", + "description": "Whether hide spark ui in zeppelin ui" + }, + "spark.webui.yarn.useProxy": { + "name": "spark.webui.yarn.useProxy", + "value": false, + "type": "checkbox", + "description": "whether use yarn proxy url as spark weburl, e.g. http://localhost:8088/proxy/application_1583396598068_0004" + }, + "zeppelin.spark.scala.color": { + "name": "zeppelin.spark.scala.color", + "value": true, + "type": "checkbox", + "description": "Whether enable color output of spark scala interpreter" + }, + "zeppelin.spark.deprecatedMsg.show": { + "name": "zeppelin.spark.deprecatedMsg.show", + "value": true, + "type": "checkbox", + "description": "Whether show the spark deprecated message, spark 2.2 and before are deprecated. Zeppelin will display warning message by default" + }, + "zeppelin.spark.concurrentSQL": { + "name": "zeppelin.spark.concurrentSQL", + "value": true, + "type": "checkbox", + "description": "Execute multiple SQL concurrently if set true." + }, + "zeppelin.spark.concurrentSQL.max": { + "name": "zeppelin.spark.concurrentSQL.max", + "value": "10", + "type": "number", + "description": "Max number of SQL concurrently executed" + }, + "zeppelin.spark.sql.stacktrace": { + "name": "zeppelin.spark.sql.stacktrace", + "value": true, + "type": "checkbox", + "description": "Show full exception stacktrace for SQL queries if set to true." + }, + "zeppelin.spark.sql.interpolation": { + "name": "zeppelin.spark.sql.interpolation", + "value": true, + "type": "checkbox", + "description": "Enable ZeppelinContext variable interpolation into spark sql" + }, + "PYSPARK_PYTHON": { + "name": "PYSPARK_PYTHON", + "value": "python", + "type": "string", + "description": "Python binary executable to use for PySpark in both driver and workers (default is python2.7 if available, otherwise python). Property `spark.pyspark.python` take precedence if it is set" + }, + "PYSPARK_DRIVER_PYTHON": { + "name": "PYSPARK_DRIVER_PYTHON", + "value": "python", + "type": "string", + "description": "Python binary executable to use for PySpark in driver only (default is `PYSPARK_PYTHON`). Property `spark.pyspark.driver.python` take precedence if it is set" + }, + "zeppelin.pyspark.useIPython": { + "name": "zeppelin.pyspark.useIPython", + "value": true, + "type": "checkbox", + "description": "Whether use IPython when it is available" + }, + "zeppelin.R.knitr": { + "name": "zeppelin.R.knitr", + "value": true, + "type": "checkbox", + "description": "Whether use knitr or not" + }, + "zeppelin.R.cmd": { + "name": "zeppelin.R.cmd", + "value": "R", + "type": "string", + "description": "R binary executable path" + }, + "zeppelin.R.image.width": { + "name": "zeppelin.R.image.width", + "value": "100%", + "type": "number", + "description": "Image width of R plotting" + }, + "zeppelin.R.render.options": { + "name": "zeppelin.R.render.options", + "value": "out.format \u003d \u0027html\u0027, comment \u003d NA, echo \u003d FALSE, results \u003d \u0027asis\u0027, message \u003d F, warning \u003d F, fig.retina \u003d 2", + "type": "textarea", + "description": "" + }, + "zeppelin.kotlin.shortenTypes": { + "name": "zeppelin.kotlin.shortenTypes", + "value": true, + "type": "checkbox", + "description": "Show short types instead of full, e.g. List\u003cString\u003e or kotlin.collections.List\u003ckotlin.String\u003e" + }, + "spark.sql.catalog.spark_catalog.type":{ + "name": "spark.sql.catalog.spark_catalog.type", + "value": "hive", + "type": "textarea" + }, + "spark.sql.catalogImplementation":{ + "name": "spark.sql.catalogImplementation", + "value": "hive", + "type": "textarea" + }, + "spark.hadoop.javax.jdo.option.ConnectionURL":{ + "name": "spark.hadoop.javax.jdo.option.ConnectionURL", + "value": "_HOST:_PORT/_DBNAME", + "type": "textarea" + }, + "spark.hadoop.javax.jdo.option.ConnectionUserName":{ + "name": "spark.hadoop.javax.jdo.option.ConnectionUserName", + "value": "_USERNAME", + "type": "textarea" + }, + "spark.hadoop.javax.jdo.option.ConnectionPassword":{ + "name": "spark.hadoop.javax.jdo.option.ConnectionPassword", + "value": "_PASSWORD", + "type": "textarea" + }, + "spark.hadoop.javax.jdo.option.ConnectionDriverName":{ + "name": "spark.hadoop.javax.jdo.option.ConnectionDriverName", + "value": "org.postgresql.Driver", + "type": "textarea" + }, + sparkConfigJSON + }, + "status": "READY", + "interpreterGroup": [ + { + "name": "spark", + "class": "org.apache.zeppelin.spark.SparkInterpreter", + "defaultInterpreter": true, + "editor": { + "language": "scala", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": true + } + }, + { + "name": "sql", + "class": "org.apache.zeppelin.spark.SparkSqlInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "sql", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": true + } + }, + { + "name": "pyspark", + "class": "org.apache.zeppelin.spark.PySparkInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "python", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": true + } + }, + { + "name": "ipyspark", + "class": "org.apache.zeppelin.spark.IPySparkInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "python", + "editOnDblClick": false, + "completionSupport": true, + "completionKey": "TAB" + } + }, + { + "name": "r", + "class": "org.apache.zeppelin.spark.SparkRInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "r", + "editOnDblClick": false, + "completionSupport": false, + "completionKey": "TAB" + } + }, + { + "name": "ir", + "class": "org.apache.zeppelin.spark.SparkIRInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "r", + "editOnDblClick": false, + "completionSupport": true, + "completionKey": "TAB" + } + }, + { + "name": "shiny", + "class": "org.apache.zeppelin.spark.SparkShinyInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "r", + "editOnDblClick": false, + "completionSupport": true, + "completionKey": "TAB" + } + }, + { + "name": "kotlin", + "class": "org.apache.zeppelin.spark.KotlinSparkInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "kotlin", + "editOnDblClick": false, + "completionKey": "TAB", + "completionSupport": false + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "perNote": "shared", + "perUser": "shared", + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + }, + "md": { + "id": "md", + "name": "md", + "group": "md", + "properties": { + "markdown.parser.type": { + "name": "markdown.parser.type", + "value": "flexmark", + "type": "string", + "description": "Markdown Parser Type. Available values: pegdown, markdown4j, flexmark. Default \u003d flexmark" + } + }, + "status": "READY", + "interpreterGroup": [ + { + "name": "md", + "class": "org.apache.zeppelin.markdown.Markdown", + "defaultInterpreter": false, + "editor": { + "language": "markdown", + "editOnDblClick": true, + "completionSupport": false + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + }, + "jdbc": { + "id": "jdbc", + "name": "jdbc", + "group": "jdbc", + "properties": { + "default.url": { + "name": "default.url", + "value": "jdbc:postgresql://localhost:5432/", + "type": "string", + "description": "The URL for JDBC." + }, + "default.user": { + "name": "default.user", + "value": "gpadmin", + "type": "string", + "description": "The JDBC user name" + }, + "default.password": { + "name": "default.password", + "value": "", + "type": "password", + "description": "The JDBC user password" + }, + "default.driver": { + "name": "default.driver", + "value": "org.postgresql.Driver", + "type": "string", + "description": "JDBC Driver Name" + }, + "default.completer.ttlInSeconds": { + "name": "default.completer.ttlInSeconds", + "value": "120", + "type": "number", + "description": "Time to live sql completer in seconds (-1 to update everytime, 0 to disable update)" + }, + "default.completer.schemaFilters": { + "name": "default.completer.schemaFilters", + "value": "", + "type": "textarea", + "description": "Сomma separated schema (schema \u003d catalog \u003d database) filters to get metadata for completions. Supports \u0027%\u0027 symbol is equivalent to any set of characters. (ex. prod_v_%,public%,info)" + }, + "default.precode": { + "name": "default.precode", + "value": "", + "type": "textarea", + "description": "SQL which executes while opening connection" + }, + "default.statementPrecode": { + "name": "default.statementPrecode", + "value": "", + "type": "textarea", + "description": "Runs before each run of the paragraph, in the same connection" + }, + "common.max_count": { + "name": "common.max_count", + "value": "1000", + "type": "number", + "description": "Max number of SQL result to display." + }, + "zeppelin.jdbc.auth.type": { + "name": "zeppelin.jdbc.auth.type", + "value": "", + "type": "string", + "description": "If auth type is needed, Example: KERBEROS" + }, + "zeppelin.jdbc.auth.kerberos.proxy.enable": { + "name": "zeppelin.jdbc.auth.kerberos.proxy.enable", + "value": "true", + "type": "checkbox", + "description": "When auth type is Kerberos, enable/disable Kerberos proxy with the login user to get the connection. Default value is true." + }, + "zeppelin.jdbc.concurrent.use": { + "name": "zeppelin.jdbc.concurrent.use", + "value": true, + "type": "checkbox", + "description": "Use parallel scheduler" + }, + "zeppelin.jdbc.concurrent.max_connection": { + "name": "zeppelin.jdbc.concurrent.max_connection", + "value": "10", + "type": "number", + "description": "Number of concurrent execution" + }, + "zeppelin.jdbc.keytab.location": { + "name": "zeppelin.jdbc.keytab.location", + "value": "", + "type": "string", + "description": "Kerberos keytab location" + }, + "zeppelin.jdbc.principal": { + "name": "zeppelin.jdbc.principal", + "value": "", + "type": "string", + "description": "Kerberos principal" + }, + "zeppelin.jdbc.interpolation": { + "name": "zeppelin.jdbc.interpolation", + "value": false, + "type": "checkbox", + "description": "Enable ZeppelinContext variable interpolation into paragraph text" + }, + "zeppelin.jdbc.maxConnLifetime": { + "name": "zeppelin.jdbc.maxConnLifetime", + "value": "-1", + "type": "number", + "description": "Maximum of connection lifetime in milliseconds. A value of zero or less means the connection has an infinite lifetime." + }, + "zeppelin.jdbc.maxRows": { + "name": "zeppelin.jdbc.maxRows", + "value": "1000", + "type": "number", + "description": "Maximum number of rows fetched from the query." + }, + "zeppelin.jdbc.hive.timeout.threshold": { + "name": "zeppelin.jdbc.hive.timeout.threshold", + "value": "60000", + "type": "number", + "description": "Timeout for hive job timeout" + }, + "zeppelin.jdbc.hive.monitor.query_interval": { + "name": "zeppelin.jdbc.hive.monitor.query_interval", + "value": "1000", + "type": "number", + "description": "Query interval for hive statement" + } + }, + "status": "READY", + "interpreterGroup": [ + { + "name": "sql", + "class": "org.apache.zeppelin.jdbc.JDBCInterpreter", + "defaultInterpreter": false, + "editor": { + "language": "sql", + "editOnDblClick": false, + "completionSupport": true + } + } + ], + "dependencies": [], + "option": { + "remote": true, + "port": -1, + "isExistingProcess": false, + "setPermission": false, + "owners": [], + "isUserImpersonate": false + } + } + }, + "interpreterRepositories": [ + { + "id": "central", + "type": "default", + "url": "https://repo1.maven.org/maven2/", + "host": "repo1.maven.org", + "protocol": "https", + "releasePolicy": { + "enabled": true, + "updatePolicy": "daily", + "checksumPolicy": "warn" + }, + "snapshotPolicy": { + "enabled": true, + "updatePolicy": "daily", + "checksumPolicy": "warn" + }, + "mirroredRepositories": [], + "repositoryManager": false + }, + { + "id": "local", + "type": "default", + "url": "file:///root/.m2/repository", + "host": "", + "protocol": "file", + "releasePolicy": { + "enabled": true, + "updatePolicy": "daily", + "checksumPolicy": "warn" + }, + "snapshotPolicy": { + "enabled": true, + "updatePolicy": "daily", + "checksumPolicy": "warn" + }, + "mirroredRepositories": [], + "repositoryManager": false + } + ] + } \ No newline at end of file diff --git a/api/workspace/services/templates/conf/zeppelin-env.sh b/api/workspace/services/templates/conf/zeppelin-env.sh new file mode 100755 index 00000000..368bc4b0 --- /dev/null +++ b/api/workspace/services/templates/conf/zeppelin-env.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# export JAVA_HOME= +# export USE_HADOOP= # Whether include hadoop jars into zeppelin server process. (true or false) +# export SPARK_MASTER= # Spark master url. eg. spark://master_addr:7077. Leave empty if you want to use local mode. +# export ZEPPELIN_ADDR # Bind address (default 127.0.0.1) +# export ZEPPELIN_PORT # port number to listen (default 8080) +# export ZEPPELIN_LOCAL_IP # Zeppelin's thrift server ip address, if not specified, one random IP address will be choosen. +# export ZEPPELIN_JAVA_OPTS # Additional jvm options. for example, export ZEPPELIN_JAVA_OPTS="-Dspark.executor.memory=8g -Dspark.cores.max=16" +# export ZEPPELIN_MEM # Zeppelin jvm mem options Default -Xms1024m -Xmx1024m -XX:MaxMetaspaceSize=512m +# export ZEPPELIN_INTP_MEM # zeppelin interpreter process jvm mem options. Default -Xms1024m -Xmx1024m -XX:MaxMetaspaceSize=512m +# export ZEPPELIN_INTP_JAVA_OPTS # zeppelin interpreter process jvm options. +# export ZEPPELIN_SSL_PORT # ssl port (used when ssl environment variable is set to true) +# export ZEPPELIN_JMX_ENABLE # Enable JMX feature by defining "true" +# export ZEPPELIN_JMX_PORT # Port number which JMX uses. If not set, JMX won't be enabled + +# export ZEPPELIN_LOG_DIR # Where log files are stored. PWD by default. +# export ZEPPELIN_PID_DIR # The pid files are stored. ${ZEPPELIN_HOME}/run by default. +# export ZEPPELIN_WAR_TEMPDIR # The location of jetty temporary directory. +# export ZEPPELIN_NOTEBOOK_DIR # Where notebook saved +# export ZEPPELIN_NOTEBOOK_HOMESCREEN # Id of notebook to be displayed in homescreen. ex) 2A94M5J1Z +# export ZEPPELIN_NOTEBOOK_HOMESCREEN_HIDE # hide homescreen notebook from list when this value set to "true". default "false" + +# export ZEPPELIN_NOTEBOOK_S3_BUCKET # Bucket where notebook saved +# export ZEPPELIN_NOTEBOOK_S3_ENDPOINT # Endpoint of the bucket +# export ZEPPELIN_NOTEBOOK_S3_USER # User in bucket where notebook saved. For example bucket/user/notebook/2A94M5J1Z/note.json +# export ZEPPELIN_NOTEBOOK_S3_KMS_KEY_ID # AWS KMS key ID +# export ZEPPELIN_NOTEBOOK_S3_KMS_KEY_REGION # AWS KMS key region +# export ZEPPELIN_NOTEBOOK_S3_SSE # Server-side encryption enabled for notebooks +# export ZEPPELIN_NOTEBOOK_S3_PATH_STYLE_ACCESS # Path style access for S3 bucket + +# export ZEPPELIN_NOTEBOOK_GCS_STORAGE_DIR # GCS "directory" (prefix) under which notebooks are saved. E.g. gs://example-bucket/path/to/dir +# export GOOGLE_APPLICATION_CREDENTIALS # Provide a service account key file for GCS and BigQuery API calls (overrides application default credentials) + +# export ZEPPELIN_NOTEBOOK_MONGO_URI # MongoDB connection URI used to connect to a MongoDB database server. Default "mongodb://localhost" +# export ZEPPELIN_NOTEBOOK_MONGO_DATABASE # Database name to store notebook. Default "zeppelin" +# export ZEPPELIN_NOTEBOOK_MONGO_COLLECTION # Collection name to store notebook. Default "notes" +# export ZEPPELIN_NOTEBOOK_MONGO_AUTOIMPORT # If "true" import local notes under ZEPPELIN_NOTEBOOK_DIR on startup. Default "false" + +# export ZEPPELIN_IDENT_STRING # A string representing this instance of zeppelin. $USER by default. +# export ZEPPELIN_NICENESS # The scheduling priority for daemons. Defaults to 0. +# export ZEPPELIN_INTERPRETER_LOCALREPO # Local repository for interpreter's additional dependency loading +# export ZEPPELIN_INTERPRETER_DEP_MVNREPO # Remote principal repository for interpreter's additional dependency loading +# export ZEPPELIN_HELIUM_NODE_INSTALLER_URL # Remote Node installer url for Helium dependency loader +# export ZEPPELIN_HELIUM_NPM_INSTALLER_URL # Remote Npm installer url for Helium dependency loader +# export ZEPPELIN_HELIUM_YARNPKG_INSTALLER_URL # Remote Yarn package installer url for Helium dependency loader +# export ZEPPELIN_NOTEBOOK_STORAGE # Refers to pluggable notebook storage class, can have two classes simultaneously with a sync between them (e.g. local and remote). +# export ZEPPELIN_NOTEBOOK_ONE_WAY_SYNC # If there are multiple notebook storages, should we treat the first one as the only source of truth? +# export ZEPPELIN_NOTEBOOK_PUBLIC # Make notebook public by default when created, private otherwise + +# export DOCKER_TIME_ZONE # Set to the same time zone as the zeppelin server. E.g, "America/New_York" or "Asia/Shanghai" + +#### Spark interpreter configuration #### + +## Kerberos ticket refresh setting +## +#export KINIT_FAIL_THRESHOLD # (optional) How many times should kinit retry. The default value is 5. +#export KERBEROS_REFRESH_INTERVAL # (optional) The refresh interval for Kerberos ticket. The default value is 1d. + +## Use provided spark installation ## +## defining SPARK_HOME makes Zeppelin run spark interpreter process using spark-submit +## +# export SPARK_HOME # (required) When it is defined, load it instead of Zeppelin embedded Spark libraries +# export SPARK_SUBMIT_OPTIONS # (optional) extra options to pass to spark submit. eg) "--driver-memory 512M --executor-memory 1G". +# export SPARK_APP_NAME # (optional) The name of spark application. +# export SPARK_CONF_DIR # (optional) In the zeppelin interpreter on docker mode, Need to set the local spark conf folder path + +## Use embedded spark binaries ## +## without SPARK_HOME defined, Zeppelin still able to run spark interpreter process using embedded spark binaries. +## however, it is not encouraged when you can define SPARK_HOME +## +# Options read in YARN client mode +# export HADOOP_CONF_DIR # yarn-site.xml is located in configuration directory in HADOOP_CONF_DIR. +# Pyspark (supported with Spark 1.2.1 and above) +# To configure pyspark, you need to set spark distribution's path to 'spark.home' property in Interpreter setting screen in Zeppelin GUI +# export PYSPARK_PYTHON # path to the python command. must be the same path on the driver(Zeppelin) and all workers. +# export PYTHONPATH + +## Spark interpreter options ## +## +# export ZEPPELIN_SPARK_USEHIVECONTEXT # Use HiveContext instead of SQLContext if set true. true by default. +# export ZEPPELIN_SPARK_CONCURRENTSQL # Execute multiple SQL concurrently if set true. false by default. +# export ZEPPELIN_SPARK_IMPORTIMPLICIT # Import implicits, UDF collection, and sql if set true. true by default. +# export ZEPPELIN_SPARK_MAXRESULT # Max number of Spark SQL result to display. 1000 by default. +# export ZEPPELIN_WEBSOCKET_MAX_TEXT_MESSAGE_SIZE # Size in characters of the maximum text message to be received by websocket. Defaults to 1024000 + +#### HBase interpreter configuration #### + +## To connect to HBase running on a cluster, either HBASE_HOME or HBASE_CONF_DIR must be set + +# export HBASE_HOME= # (require) Under which HBase scripts and configuration should be +# export HBASE_CONF_DIR= # (optional) Alternatively, configuration directory can be set to point to the directory that has hbase-site.xml + +#### ZeppelinHub connection configuration #### +# export ZEPPELINHUB_API_ADDRESS # Refers to the address of the ZeppelinHub service in use +# export ZEPPELINHUB_API_TOKEN # Refers to the Zeppelin instance token of the user +# export ZEPPELINHUB_USER_KEY # Optional, when using Zeppelin with authentication. + +#### Zeppelin impersonation configuration +# export ZEPPELIN_IMPERSONATE_CMD # Optional, when user want to run interpreter as end web user. eg) 'sudo -H -u ${ZEPPELIN_IMPERSONATE_USER} bash -c ' +# export ZEPPELIN_IMPERSONATE_SPARK_PROXY_USER #Optional, by default is true; can be set to false if you don't want to use --proxy-user option with Spark interpreter when impersonation enabled +export ZEPPELIN_INTERPRETER_LIFECYCLEMANAGER_CLASS=org.apache.zeppelin.interpreter.lifecycle.TimeoutLifecycleManager +export ZEPPELIN_INTERPRETER_LIFECYCLEMANAGER_TIMEOUT_CHECKINTERVAL=600000 +export ZEPPELIN_INTERPRETER_LIFECYCLEMANAGER_TIMEOUT_THRESHOLD={inactivityTimeout} +export ZEPPELIN_ALLOWED_ORIGINS=http://localhost:3000 \ No newline at end of file diff --git a/api/workspace/services/templates/zeppelinInterpreter.yaml b/api/workspace/services/templates/zeppelinInterpreter.yaml new file mode 100755 index 00000000..9ad5be1f --- /dev/null +++ b/api/workspace/services/templates/zeppelinInterpreter.yaml @@ -0,0 +1,164 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +kind: Pod +apiVersion: v1 +metadata: + namespace: {{zeppelin.k8s.namespace}} + name: {{zeppelin.k8s.interpreter.pod.name}} + labels: + app: {{zeppelin.k8s.interpreter.pod.name}} + interpreterGroupId: {{zeppelin.k8s.interpreter.group.id}} + interpreterSettingName: {{zeppelin.k8s.interpreter.setting.name}} + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +spec: + {% if zeppelin.k8s.interpreter.group.name == "spark" %} + automountServiceAccountToken: true + {% else %} + automountServiceAccountToken: false + {% endif %} + restartPolicy: Never + terminationGracePeriodSeconds: 30 + containers: + - name: {{zeppelin.k8s.interpreter.container.name}} + image: {{zeppelin.k8s.interpreter.container.image}} + args: + - "$(ZEPPELIN_HOME)/bin/interpreter.sh" + - "-d" + - "$(ZEPPELIN_HOME)/interpreter/{{zeppelin.k8s.interpreter.group.name}}" + - "-r" + - "{{zeppelin.k8s.interpreter.rpc.portRange}}" + - "-c" + - "{{zeppelin.k8s.server.rpc.service}}" + - "-p" + - "{{zeppelin.k8s.server.rpc.portRange}}" + - "-i" + - "{{zeppelin.k8s.interpreter.group.id}}" + - "-l" + - "{{zeppelin.k8s.interpreter.localRepo}}/{{zeppelin.k8s.interpreter.setting.name}}" + - "-g" + - "{{zeppelin.k8s.interpreter.setting.name}}" + env: + {% for key, value in zeppelin.k8s.envs.items() %} + - name: {{key}} + value: {{value}} + {% endfor %} + - name: ZEPPELIN_HOME + value: /zeppelin + {% if zeppelin.k8s.interpreter.cores is defined and zeppelin.k8s.interpreter.memory is defined %} + resources: + requests: + memory: "{{zeppelin.k8s.interpreter.memory}}" + cpu: "{{zeppelin.k8s.interpreter.cores}}" +{# limits.memory is not set because of a potential OOM-Killer. https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits #} + limits: + cpu: "{{zeppelin.k8s.interpreter.cores}}" + {% endif %} + {% if zeppelin.k8s.interpreter.group.name == "spark" %} + volumeMounts: + - name: spark-home + mountPath: /spark + initContainers: + - name: spark-home-init + image: {{zeppelin.k8s.spark.container.image}} + command: ["sh", "-c", "cp -r /opt/spark/* /spark/"] + volumeMounts: + - name: spark-home + mountPath: /spark + volumes: + - name: spark-home + emptyDir: {} + {% endif %} +--- +kind: Service +apiVersion: v1 +metadata: + namespace: {{zeppelin.k8s.namespace}} + name: {{zeppelin.k8s.interpreter.pod.name}} # keep Service name the same to Pod name. + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +spec: + clusterIP: None + ports: + - name: intp + port: 12321 + {% if zeppelin.k8s.interpreter.group.name == "spark" %} + - name: spark-driver + port: 22321 + - name: spark-blockmanager + port: 22322 + - name: spark-ui + port: 4040 + {% endif %} + selector: + app: {{zeppelin.k8s.interpreter.pod.name}} +{% if zeppelin.k8s.interpreter.group.name == "spark" %} +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{zeppelin.k8s.interpreter.pod.name}} + namespace: {{zeppelin.k8s.namespace}} + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +rules: +- apiGroups: [""] + resources: ["pods", "services"] + verbs: ["create", "get", "update", "list", "delete", "watch" ] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{zeppelin.k8s.interpreter.pod.name}} + {% if zeppelin.k8s.server.uid is defined %} + ownerReferences: + - apiVersion: v1 + controller: false + blockOwnerDeletion: false + kind: Pod + name: {{zeppelin.k8s.server.pod.name}} + uid: {{zeppelin.k8s.server.uid}} + {% endif %} +subjects: +- kind: ServiceAccount + name: default +roleRef: + kind: Role + name: {{zeppelin.k8s.interpreter.pod.name}} + apiGroup: rbac.authorization.k8s.io +{% endif %} \ No newline at end of file diff --git a/api/workspace/services/templates/zeppelinJobServer.yaml b/api/workspace/services/templates/zeppelinJobServer.yaml new file mode 100755 index 00000000..81995d7d --- /dev/null +++ b/api/workspace/services/templates/zeppelinJobServer.yaml @@ -0,0 +1,92 @@ +kind: Pod +apiVersion: v1 +metadata: + name: {podId} + namespace: {podNamespace} + labels: + app.kubernetes.io/name: zeppelin-server + statefulset.kubernetes.io/pod-name: {podId} +spec: + serviceAccountName: cuelake + volumes: + - name: shared-notebook + emptyDir: + - name: shared-conf + emptyDir: + - name: shared-k8s + emptyDir: + initContainers: + - name: init + image: bitnami/kubectl + command: ['sh', '-c'] + volumeMounts: + - name: shared-notebook + mountPath: /shared-notebook + - name: shared-conf + mountPath: /shared-conf + - name: shared-k8s + mountPath: /shared-k8s + args: + - >- + kubectl cp $(kubectl get pods | grep zeppelin-server | awk '{print $1}' ):/zeppelin/conf /shared-conf && + kubectl cp $(kubectl get pods | grep zeppelin-server | awk '{print $1}' ):/zeppelin/k8s /shared-k8s && + kubectl cp $(kubectl get pods | grep zeppelin-server | awk '{print $1}' ):/zeppelin/notebook /shared-notebook + containers: + - name: zeppelin-server + image: 'cuebook/zeppelin-server-lite:0.2' + command: + - sh + - '-c' + args: + - >- + sed -i + 's#zeppelin.k8s.server.rpc.service#zeppelin.k8s.server.pod.name#1' + /zeppelin/k8s/interpreter/100-interpreter-spec.yaml && + $(ZEPPELIN_HOME)/bin/zeppelin.sh + ports: + - name: http + containerPort: 8080 + protocol: TCP + - name: https + containerPort: 8443 + protocol: TCP + - name: rpc + containerPort: 12320 + protocol: TCP + envFrom: + - configMapRef: + name: zeppelin-server-conf-map + env: + - name: POD_UID + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.uid + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + volumeMounts: + - name: shared-notebook + mountPath: /zeppelin/notebook + - name: shared-conf + mountPath: /zeppelin/conf + - name: shared-k8s + mountPath: /zeppelin/k8s + lifecycle: + preStop: + exec: + command: + - sh + - '-c' + - >- + ps -ef | grep org.apache.zeppelin.server.ZeppelinServer | grep + -v grep | awk '{print $2}' | xargs kill + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullPolicy: IfNotPresent + resources: + requests: + memory: "1024Mi" + cpu: "250m" diff --git a/api/workspace/services/templates/zeppelinPVC.yaml b/api/workspace/services/templates/zeppelinPVC.yaml new file mode 100755 index 00000000..877c9eed --- /dev/null +++ b/api/workspace/services/templates/zeppelinPVC.yaml @@ -0,0 +1,12 @@ +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: zeppelin-server-{workspaceName}-pvc + namespace: {podNamespace} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi + volumeMode: Filesystem \ No newline at end of file diff --git a/api/workspace/services/templates/zeppelinServer.yaml b/api/workspace/services/templates/zeppelinServer.yaml new file mode 100755 index 00000000..4e3a2f8f --- /dev/null +++ b/api/workspace/services/templates/zeppelinServer.yaml @@ -0,0 +1,99 @@ +kind: Deployment +apiVersion: apps/v1 +metadata: + name: zeppelin-server-{workspaceName} + namespace: {podNamespace} + labels: + app: zeppelin-server-{workspaceName} +spec: + replicas: 1 + selector: + matchLabels: + app: zeppelin-server-{workspaceName} + template: + metadata: + labels: + app: zeppelin-server-{workspaceName} + spec: + serviceAccountName: cuelake + volumes: + - name: shared-notebook + emptyDir: + - name: shared-conf + emptyDir: + - name: shared-k8s + emptyDir: + initContainers: + - name: init + image: bitnami/kubectl + command: ['sh', '-c'] + volumeMounts: + - name: shared-conf + mountPath: /shared-conf + - name: shared-k8s + mountPath: /shared-k8s + args: + - >- + kubectl cp $(kubectl get pods | grep lakehouse | awk '{print $1}' ):/code/data/{workspaceName}/conf /shared-conf && + kubectl cp $(kubectl get pods | grep lakehouse | awk '{print $1}' ):/code/data/{workspaceName}/k8s /shared-k8s + containers: + - name: zeppelin-server + image: 'cuebook/zeppelin-server-lite:0.3' + command: + - sh + - '-c' + args: + - >- + $(ZEPPELIN_HOME)/bin/zeppelin.sh + livenessProbe: + exec: + command: + - ls + - /tmp + initialDelaySeconds: 5 + periodSeconds: 5 + ports: + - name: http + containerPort: 8080 + protocol: TCP + - name: https + containerPort: 8443 + protocol: TCP + - name: rpc + containerPort: 12320 + protocol: TCP + envFrom: + - configMapRef: + name: zeppelin-server-conf-map + env: + - name: POD_UID + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.uid + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + volumeMounts: + - name: shared-conf + mountPath: /zeppelin/conf + - name: shared-k8s + mountPath: /zeppelin/k8s + lifecycle: + preStop: + exec: + command: + - sh + - '-c' + - >- + ps -ef | grep org.apache.zeppelin.server.ZeppelinServer | grep + -v grep | awk '{print $2}' | xargs kill + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + imagePullPolicy: IfNotPresent + resources: + requests: + memory: "1024Mi" + cpu: "250m" diff --git a/api/workspace/services/templates/zeppelinService.yaml b/api/workspace/services/templates/zeppelinService.yaml new file mode 100755 index 00000000..673fde2c --- /dev/null +++ b/api/workspace/services/templates/zeppelinService.yaml @@ -0,0 +1,18 @@ +kind: Service +apiVersion: v1 +metadata: + name: zeppelin-server-{workspaceName} + namespace: {podNamespace} +spec: + ports: + - name: http + protocol: TCP + port: 80 + targetPort: 8080 + - name: rpc + protocol: TCP + port: 12320 + targetPort: 12320 + selector: + app: zeppelin-server-{workspaceName} + type: ClusterIP diff --git a/api/workspace/services/workspace.py b/api/workspace/services/workspace.py new file mode 100755 index 00000000..4d365c0d --- /dev/null +++ b/api/workspace/services/workspace.py @@ -0,0 +1,122 @@ +from django.db import transaction +from utils.dockerHubAPI import DockerHubAPI +from workspace.models import ( + Workspace, + WorkspaceConfig +) +from workspace.serializers import WorkspaceSerializer +from utils.apiResponse import ApiResponse +from utils.dockerHubAPI import dockerHubAPI +from utils.kubernetesAPI import Kubernetes + +class WorkspaceService: + """ + Class containing services related to NotebookJob model + """ + + @staticmethod + def getWorkspaces(): + """ + Service to fetch and serialize Workflows + :param offset: Offset for fetching NotebookJob objects + """ + res = ApiResponse(message="Error retrieving workflows") + deployments = Kubernetes.getDeployments() + runningDeployments = {} + for deployment in deployments: + if deployment.status.replicas > 0 and deployment.metadata.name.startswith("zeppelin-server-"): + runningDeployments[deployment.metadata.name[16:]] = deployment + workspaces = Workspace.objects.all() + data = WorkspaceSerializer(workspaces, many=True).data + for workspace in data: + runningDeployment = runningDeployments.get(workspace["name"], False) + if runningDeployment: + workspace['replica'] = runningDeployment.status.replicas + else: + workspace['replica'] = 0 + res.update(True, "Workspaces retrieved successfully", data) + return res + + @staticmethod + def switchWorkspaceServer(workspaceId: int): + res = ApiResponse(message="Error switching workspace") + Kubernetes.switchWorkspaceServer(workspaceId) + res.update(True, "Workspace switched successfully") + return res + + @staticmethod + def createWorkspace(name: str, description: str): + res = ApiResponse(message="Error creating workspace") + workspace = Workspace.objects.create(name=name, description=description) + WorkspaceConfig.objects.create(workspace=workspace) + res.update(True, "Workspace created successfully") + return res + + @staticmethod + def updateWorkspaceConfig(workspaceId: int, workspaceConfigDict: dict): + res = ApiResponse(message="Error updating config of workspace") + worksapceConfig = WorkspaceConfig.objects.get(workspace_id=workspaceId) + for (key, value) in workspaceConfigDict.items(): + setattr(worksapceConfig, key, value) + worksapceConfig.save() + res.update(True, message="Successfully updated workspace config") + return res + + @staticmethod + def startWorkspaceServer(workspaceId: int, isNew: bool = False): + workspaceName = Workspace.objects.get(pk=workspaceId).name + workspaceConfigDict = WorkspaceConfig.objects.get(workspace_id=workspaceId).__dict__ + res = ApiResponse(message="Error starting workspace server") + Kubernetes.addZeppelinServer(workspaceName, workspaceConfigDict, isNew) + res.update(True, message="Workspace started successfully") + return res + + @staticmethod + def stopWorkspaceServer(workspaceId: int): + workspaceName = Workspace.objects.get(pk=workspaceId).name + res = ApiResponse(message="Error stopping workspace") + Kubernetes.removeWorkspace(workspaceName) + res.update(True, "Workspace stopped successfully") + return res + + @staticmethod + def deleteWorkspace(workspaceId: int): + workspaceName = Workspace.objects.get(pk=workspaceId).name + workspace = Workspace.objects.get(pk=workspaceId) + workspace.delete() + res = ApiResponse(message="Error deleting workspace server") + Kubernetes.removeWorkspace(workspaceName) + res.update(True, message="Workspace deleted successfully") + return res + + @staticmethod + @transaction.atomic + def createAndStartWorkspaceServer(workspaceDict: dict, workspaceConfigDict: dict): + res = ApiResponse(message="Error creating workspace") + workspace = Workspace.objects.create(**workspaceDict) + WorkspaceConfig.objects.create(workspace=workspace) + worksapceConfig = WorkspaceConfig.objects.get(workspace_id=workspace.id) + for (key, value) in workspaceConfigDict.items(): + setattr(worksapceConfig, key, value) + worksapceConfig.save() + WorkspaceService.startWorkspaceServer(workspace.id, True) + res.update(True, message="Successfully created workspace config") + return res + + @staticmethod + def getDockerImages(repository: str): + res = ApiResponse(message="Error fetching docker images") + imageTags = dockerHubAPI.getImageTags(repository) + res.update(True, message="Image tags fetched successfully", data=imageTags) + return res + + @staticmethod + def getWorkspace(): + """ + Service to fetch and serialize Workflows + :param offset: Offset for fetching NotebookJob objects + """ + res = ApiResponse(message="Error retrieving workflows") + workspaces = Workspace.objects.all() + total = workspaces.count() + return res diff --git a/api/workspace/tests/__init__.py b/api/workspace/tests/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/api/workspace/urls.py b/api/workspace/urls.py new file mode 100755 index 00000000..7e5c1604 --- /dev/null +++ b/api/workspace/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path("workspaces", views.Workspaces.as_view(), name="workspaces"), + path("", views.Workspace.as_view(), name="workspace"), + path("workspaceConfig/", views.WorksapceConfig.as_view(), name="workspaceConfig"), + path("dockerimages/", views.DockerImages.as_view(), name="dockerimages"), + path("createAndStartWorkspaceServer", views.CreateAndStartWorkspaceServer.as_view(), name="createAndStartWorkspaceServer"), + path("workspaceServer/", views.WorkspaceServer.as_view(), name="workspaceServer"), + path("switchWorkspaceServer/", views.switchWorkspaceServer.as_view(), name="switchWorkspaceServer") +] \ No newline at end of file diff --git a/api/workspace/views.py b/api/workspace/views.py new file mode 100755 index 00000000..e5d3e997 --- /dev/null +++ b/api/workspace/views.py @@ -0,0 +1,58 @@ +from django.http import HttpRequest +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework.decorators import api_view +from workspace.services import WorkspaceService + +class Workspaces(APIView): + def get(self, request): + res = WorkspaceService.getWorkspaces() + return Response(res.json()) + + def post(self, request): + data = request.data + name = data.get("name", False) + description = data.get("description", False) + res = WorkspaceService.createWorkspace(name, description) + return Response(res.json()) + +class CreateAndStartWorkspaceServer(APIView): + def post(self, request): + workspaceDict = request.data.get("workspace") + workspaceConfigDict = request.data.get("workspaceConfig") + res = WorkspaceService.createAndStartWorkspaceServer(workspaceDict, workspaceConfigDict) + return Response(res.json()) + +class switchWorkspaceServer(APIView): + def post(self, request, workspaceId : int,): + res = WorkspaceService.switchWorkspaceServer(workspaceId) + return Response(res.json()) + +class WorkspaceServer(APIView): + def post(self, request, workspaceId: int): + res = WorkspaceService.startWorkspaceServer(workspaceId) + return Response(res.json()) + + def delete(self, request, workspaceId: int): + res = WorkspaceService.stopWorkspaceServer(workspaceId) + return Response(res.json()) + +class Workspace(APIView): + def get(self, request, workspaceId: int): + res = WorkspaceService.getWorkspace(workspaceId) + return Response(res.json()) + + def delete(self, request, workspaceId: int): + res = WorkspaceService.deleteWorkspace(workspaceId) + return Response(res.json()) + +class WorksapceConfig(APIView): + def put(self, request, workspaceId: int): + workspaceConfigDict = request.data + res = WorkspaceService.updateWorkspaceConfig(workspaceId, workspaceConfigDict) + return Response(res.json()) + +class DockerImages(APIView): + def get(self, request, repository: str): + res = WorkspaceService.getDockerImages(repository) + return Response(res.json()) \ No newline at end of file diff --git a/cuelake.yaml b/cuelake.yaml index 5443afbf..3de81232 100644 --- a/cuelake.yaml +++ b/cuelake.yaml @@ -90,123 +90,6 @@ data: } } --- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: zeppelin-server-main - labels: - app.kubernetes.io/name: zeppelin-server-main -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: zeppelin-server-main - strategy: - type: RollingUpdate - template: - metadata: - labels: - app.kubernetes.io/name: zeppelin-server-main - spec: - serviceAccountName: cuelake - volumes: - - name: zeppelin-server-notebook-volume - persistentVolumeClaim: - claimName: zeppelin-server-notebook-volume-pvc - - name: zeppelin-server-conf - persistentVolumeClaim: - claimName: zeppelin-server-conf-pvc - - name: nginx-conf - configMap: - name: zeppelin-server-conf - items: - - key: nginx.conf - path: nginx.conf - containers: - - name: zeppelin-server - image: cuebook/zeppelin-server-lite:0.3 - command: ["sh", "-c"] - args: - - curl https://raw.githubusercontent.com/cuebook/cuelake/main/zeppelinConf/zeppelin-env.sh -o $(ZEPPELIN_HOME)/conf/zeppelin-env.sh && curl https://raw.githubusercontent.com/cuebook/cuelake/main/zeppelinConf/zeppelin-site.xml -o $(ZEPPELIN_HOME)/conf/zeppelin-site.xml && $(ZEPPELIN_HOME)/bin/zeppelin.sh - lifecycle: - preStop: - exec: - # SIGTERM triggers a quick exit; gracefully terminate instead - command: ["sh", "-c", "ps -ef | grep org.apache.zeppelin.server.ZeppelinServer | grep -v grep | awk '{print $2}' | xargs kill"] - ports: - - name: http - containerPort: 8080 - - name: https - containerPort: 8443 - - name: rpc - containerPort: 12320 - env: - - name: POD_UID - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.uid - - name: POD_NAME - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: metadata.name - envFrom: - - configMapRef: - name: zeppelin-server-conf-map - volumeMounts: - - name: zeppelin-server-notebook-volume # configure this to persist notebook - mountPath: /zeppelin/notebook - - name: zeppelin-server-conf # configure this to persist Zeppelin configuration - mountPath: /zeppelin/conf - # - name: zeppelin-server-custom-k8s # configure this to mount customized Kubernetes spec for interpreter - # mountPath: /zeppelin/k8s - - name: zeppelin-server-gateway - image: nginx:1.14.0 - command: ["/bin/sh", "-c"] - env: - - name: SERVICE_DOMAIN - valueFrom: - configMapKeyRef: - name: zeppelin-server-conf-map - key: SERVICE_DOMAIN - args: - - cp -f /tmp/conf/nginx.conf /etc/nginx/nginx.conf; - sed -i -e "s/SERVICE_DOMAIN/$SERVICE_DOMAIN/g" /etc/nginx/nginx.conf; - sed -i -e "s/NAMESPACE/$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)/g" /etc/nginx/nginx.conf; - cat /etc/nginx/nginx.conf; - /usr/sbin/nginx - volumeMounts: - - name: nginx-conf - mountPath: /tmp/conf - lifecycle: - preStop: - exec: - # SIGTERM triggers a quick exit; gracefully terminate instead - command: ["/usr/sbin/nginx", "-s", "quit"] - - name: dnsmasq # nginx requires dns resolver for dynamic dns resolution - image: "janeczku/go-dnsmasq:release-1.0.5" - args: - - --listen - - "127.0.0.1:53" - - --default-resolver - - --append-search-domains - - --hostsfile=/etc/hosts - - --verbose ---- -kind: Service -apiVersion: v1 -metadata: - name: zeppelin-server -spec: - ports: - - name: http - port: 80 - - name: rpc # port name is referenced in the code. So it shouldn't be changed. - port: 12320 - selector: - app.kubernetes.io/name: zeppelin-server-main ---- apiVersion: v1 kind: ServiceAccount metadata: @@ -236,30 +119,6 @@ roleRef: name: cuelake-role apiGroup: rbac.authorization.k8s.io --- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: zeppelin-server-conf-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi -status: {} ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: zeppelin-server-notebook-volume-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi -status: {} ---- apiVersion: apps/v1 kind: Deployment metadata: @@ -278,6 +137,8 @@ spec: labels: app.kubernetes.io/name: lakehouse spec: + imagePullSecrets: + - name: aws-registry serviceAccountName: cuelake volumes: - name: lakehouse-db-volume @@ -285,7 +146,7 @@ spec: claimName: lakehouse-db-volume-pvc containers: - name: lakehouse - image: cuebook/lakehouse:0.3 + image: 473921064442.dkr.ecr.ap-south-1.amazonaws.com/laker resources: requests: memory: "2560Mi" @@ -364,18 +225,3 @@ spec: port: 6379 selector: app.kubernetes.io/name: redis ---- -kind: Service -apiVersion: v1 -metadata: - name: sparkui -spec: - ports: - - name: spark-ui - protocol: TCP - port: 4040 - targetPort: 4040 - selector: - interpreterSettingName: spark - type: ClusterIP - sessionAffinity: None diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100755 index 00000000..c2352ecd --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,53 @@ +version: '3.3' + +services: + cuelake-ui: + build: + context: ./ui + dockerfile: Dockerfile.dev + ports: + - "3000:3000" + stdin_open: true + network_mode: + host + volumes: + - ./ui:/app + - /app/node_modules + cuelake-api: + build: + context: ./api + dockerfile: Dockerfile.dev + environment: + - ENVIRONMENT=dev + ports: + - "8000:8000" + stdin_open: true + depends_on: + - postgres + - redis + network_mode: + host + volumes: + - ./api:/code + # Uncomment below lines for providing kubernetes access + # - /home//.kube:/.kube + # - /home//.minikube:/home//.minikube + postgres: + image: postgres:13 + ports: + - "5432:5432" + environment: + - PGDATA=/pg_data + - POSTGRES_DB=cuelake + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + volumes: + - /pg_data + network_mode: + host + redis: + ports: + - "6379:6379" + image: "redis:alpine" + network_mode: + host diff --git a/docs/settings.md b/docs/settings.md index a56f6707..93e0d546 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -113,4 +113,4 @@ Create a `pre-commit` file, make it executable `chmod +x filename`, and add: ```bash #!/bin/sh git add -A -``` +``` \ No newline at end of file diff --git a/nginx.conf b/nginx.conf index acd9dc89..334aa4f7 100644 --- a/nginx.conf +++ b/nginx.conf @@ -26,24 +26,6 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } - location /zeppelin/ws { - proxy_pass http://zeppelin-server:80/ws; - proxy_http_version 1.1; - proxy_set_header Upgrade websocket; - proxy_set_header Connection upgrade; - proxy_read_timeout 86400; - } - - location /zeppelin/ { - proxy_pass http://zeppelin-server:80/; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } - - location /sparkui/ { - proxy_pass http://sparkui:4040/; - absolute_redirect off; - } location / { root /usr/share/nginx/html; diff --git a/ui/Dockerfile.dev b/ui/Dockerfile.dev index d6598e88..b5894ca4 100644 --- a/ui/Dockerfile.dev +++ b/ui/Dockerfile.dev @@ -1,10 +1,17 @@ -# dev environment -FROM node:14-alpine -WORKDIR /app -ENV PATH /app/node_modules/.bin:$PATH -COPY ./package.json /app/package.json -RUN cat package.json -RUN npm install -RUN which npm -EXPOSE 3000 -CMD ["/usr/local/bin/npm", "start"] +# dev environment +FROM node:14-alpine +WORKDIR /app +ENV PATH /app/node_modules/.bin:$PATH +COPY ./package.json /app/package.json +RUN cat package.json +RUN npm install +RUN which npm +EXPOSE 3000 +# For testing production locally +# COPY . /app +# RUN npm run build +# RUN npm i spa-http-server -g +# RUN cd build +# CMD ["http-server", "--push-state", "-p", "3000"] +# For running code directly +CMD ["/usr/local/bin/npm", "start"] diff --git a/ui/package.json b/ui/package.json index 672b65c6..d419fad7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,61 +1,61 @@ -{ - "name": "Cuelake", - "version": "0.0.1", - "homepage": "./", - "dependencies": { - "@fortawesome/fontawesome-free": "5.14.0", - "@popperjs/core": "2.5.1", - "@tailwindcss/custom-forms": "0.2.1", - "ace-builds": "^1.4.12", - "ansi-to-react": "^6.1.5", - "antd": "^4.15.0", - "gulp": "4.0.2", - "gulp-append-prepend": "1.0.8", - "lodash": "^4.17.21", - "moment-duration-format": "^2.3.2", - "node-sass": "^4.14.1", - "react": "16.13.1", - "react-ace": "^9.4.0", - "react-dom": "16.13.1", - "react-google-maps": "9.4.5", - "react-moment": "^1.1.1", - "react-notifications-component": "^3.0.4", - "react-router": "5.2.0", - "react-router-dom": "5.2.0", - "react-scripts": "3.4.3", - "react-select": "^4.3.0", - "react-timeago": "^5.2.0", - "react-tooltip": "^4.2.15", - "reactstrap": "^8.9.0", - "tailwindcss": "1.8.10" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build && gulp licenses", - "test": "react-scripts test", - "eject": "react-scripts eject", - "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm run build:tailwind && npm start", - "build:tailwind": "tailwind build src/assets/styles/index.css -o src/assets/styles/tailwind.css" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "optionalDependencies": { - "@types/googlemaps": "3.39.13", - "@types/markerclustererplus": "2.1.33", - "@types/react": "16.9.49", - "typescript": "4.0.3" - } -} +{ + "name": "Cuelake", + "version": "0.0.1", + "homepage": ".", + "dependencies": { + "@fortawesome/fontawesome-free": "5.14.0", + "@popperjs/core": "2.5.1", + "@tailwindcss/custom-forms": "0.2.1", + "ace-builds": "^1.4.12", + "ansi-to-react": "^6.1.5", + "antd": "^4.15.0", + "gulp": "4.0.2", + "gulp-append-prepend": "1.0.8", + "lodash": "^4.17.21", + "moment-duration-format": "^2.3.2", + "node-sass": "^4.14.1", + "react": "16.13.1", + "react-ace": "^9.4.0", + "react-dom": "16.13.1", + "react-google-maps": "9.4.5", + "react-moment": "^1.1.1", + "react-notifications-component": "^3.0.4", + "react-router": "5.2.0", + "react-router-dom": "^5.2.0", + "react-scripts": "3.4.3", + "react-select": "^4.3.0", + "react-timeago": "^5.2.0", + "react-tooltip": "^4.2.15", + "reactstrap": "^8.9.0", + "tailwindcss": "1.8.10" + }, + "scripts": { + "start": "CHOKIDAR_USEPOLLING=true react-scripts start", + "build": "react-scripts build && gulp licenses", + "test": "react-scripts test", + "eject": "react-scripts eject", + "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm run build:tailwind && npm start", + "build:tailwind": "tailwind build src/assets/styles/index.css -o src/assets/styles/tailwind.css" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "optionalDependencies": { + "@types/googlemaps": "3.39.13", + "@types/markerclustererplus": "2.1.33", + "@types/react": "16.9.49", + "typescript": "4.0.3" + } +} diff --git a/ui/public/index.html b/ui/public/index.html index ecca880b..ca29a5e8 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -1,26 +1,26 @@ - - - - - - - - - - - - Lakehouse - - - - -
- - + + + + + + + + + + + + Lakehouse + + + + +
+ + diff --git a/ui/public/manifest.json b/ui/public/manifest.json index e72d2e3b..6134ca05 100644 --- a/ui/public/manifest.json +++ b/ui/public/manifest.json @@ -1,20 +1,20 @@ -{ - "short_name": "Notus React", - "name": "Notus React by Creative Tim", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "apple-icon.png", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} +{ + "short_name": "Notus React", + "name": "Notus React by Creative Tim", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "apple-icon.png", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "/", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/ui/public/robots.txt b/ui/public/robots.txt index 01b0f9a1..fa06a756 100644 --- a/ui/public/robots.txt +++ b/ui/public/robots.txt @@ -1,2 +1,2 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * +# https://www.robotstxt.org/robotstxt.html +User-agent: * diff --git a/ui/src/assets/img/ADLS.svg b/ui/src/assets/img/ADLS.svg new file mode 100755 index 00000000..29729860 --- /dev/null +++ b/ui/src/assets/img/ADLS.svg @@ -0,0 +1,20 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/ui/src/assets/img/GCS.svg b/ui/src/assets/img/GCS.svg new file mode 100755 index 00000000..d30e0030 --- /dev/null +++ b/ui/src/assets/img/GCS.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/assets/styles/tailwind.css b/ui/src/assets/styles/tailwind.css index 9f218fcf..e4c7ca97 100644 --- a/ui/src/assets/styles/tailwind.css +++ b/ui/src/assets/styles/tailwind.css @@ -1,2948 +1,2948 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ - -/* Document - ========================================================================== */ - -/** - * 1. Correct the line height in all browsers. - * 2. Prevent adjustments of font size after orientation changes in iOS. - */ - -html { - line-height: 1.15; /* 1 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/* Sections - ========================================================================== */ - -/** - * Remove the margin in all browsers. - */ - -body { - margin: 0; -} - -/** - * Render the `main` element consistently in IE. - */ - -main { - display: block; -} - -/** - * Correct the font size and margin on `h1` elements within `section` and - * `article` contexts in Chrome, Firefox, and Safari. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/* Grouping content - ========================================================================== */ - -/** - * 1. Add the correct box sizing in Firefox. - * 2. Show the overflow in Edge and IE. - */ - -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -pre { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Remove the gray background on active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * 1. Remove the bottom border in Chrome 57- - * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. - */ - -abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; /* 2 */ -} - -/** - * Add the correct font weight in Chrome, Edge, and Safari. - */ - -b, -strong { - font-weight: bolder; -} - -/** - * 1. Correct the inheritance and scaling of font size in all browsers. - * 2. Correct the odd `em` font sizing in all browsers. - */ - -code, -kbd, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ -} - -/** - * Add the correct font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` elements from affecting the line height in - * all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove the border on images inside links in IE 10. - */ - -img { - border-style: none; -} - -/* Forms - ========================================================================== */ - -/** - * 1. Change the font styles in all browsers. - * 2. Remove the margin in Firefox and Safari. - */ - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ -} - -/** - * Show the overflow in IE. - * 1. Show the overflow in Edge. - */ - -button, -input { /* 1 */ - overflow: visible; -} - -/** - * Remove the inheritance of text transform in Edge, Firefox, and IE. - * 1. Remove the inheritance of text transform in Firefox. - */ - -button, -select { /* 1 */ - text-transform: none; -} - -/** - * Correct the inability to style clickable types in iOS and Safari. - */ - -button, -[type="button"] { - -webkit-appearance: button; -} - -/** - * Remove the inner border and padding in Firefox. - */ - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -/** - * Restore the focus styles unset by the previous rule. - */ - -button:-moz-focusring, -[type="button"]:-moz-focusring { - outline: 1px dotted ButtonText; -} - -/** - * Correct the padding in Firefox. - */ - -fieldset { - padding: 0.35em 0.75em 0.625em; -} - -/** - * 1. Correct the text wrapping in Edge and IE. - * 2. Correct the color inheritance from `fieldset` elements in IE. - * 3. Remove the padding so developers are not caught out when they zero out - * `fieldset` elements in all browsers. - */ - -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} - -/** - * Add the correct vertical alignment in Chrome, Firefox, and Opera. - */ - -progress { - vertical-align: baseline; -} - -/** - * Remove the default vertical scrollbar in IE 10+. - */ - -textarea { - overflow: auto; -} - -/** - * 1. Add the correct box sizing in IE 10. - * 2. Remove the padding in IE 10. - */ - -[type="checkbox"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Correct the cursor style of increment and decrement buttons in Chrome. - */ - -[type="number"]::-webkit-inner-spin-button, -[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Correct the odd appearance in Chrome and Safari. - * 2. Correct the outline style in Safari. - */ - -/** - * Remove the inner padding in Chrome and Safari on macOS. - */ - -/** - * 1. Correct the inability to style clickable types in iOS and Safari. - * 2. Change font properties to `inherit` in Safari. - */ - -/* Interactive - ========================================================================== */ - -/* - * Add the correct display in Edge, IE 10+, and Firefox. - */ - -details { - display: block; -} - -/* - * Add the correct display in all browsers. - */ - -summary { - display: list-item; -} - -/* Misc - ========================================================================== */ - -/** - * Add the correct display in IE 10+. - */ - -template { - display: none; -} - -/** - * Add the correct display in IE 10. - */ - -[hidden] { - display: none; -} - -/** - * Manually forked from SUIT CSS Base: https://github.com/suitcss/base - * A thin layer on top of normalize.css that provides a starting point more - * suitable for web applications. - */ - -/** - * Removes the default spacing and border for appropriate elements. - */ - -blockquote, -dl, -dd, -h1, -h2, -h3, -h4, -h5, -h6, -hr, -figure, -p, -pre { - margin: 0; -} - -button { - background-color: transparent; - background-image: none; -} - -/** - * Work around a Firefox/IE bug where the transparent `button` background - * results in a loss of the default `button` focus styles. - */ - -button:focus { - outline: 1px dotted; - outline: 5px auto -webkit-focus-ring-color; -} - -fieldset { - margin: 0; - padding: 0; -} - -ol, -ul { - list-style: none; - margin: 0; - padding: 0; -} - -/** - * Tailwind custom reset styles - */ - -/** - * 1. Use the user's configured `sans` font-family (with Tailwind's default - * sans-serif font stack as a fallback) as a sane default. - * 2. Use Tailwind's default "normal" line-height so the user isn't forced - * to override it to ensure consistency even when using the default theme. - */ - -html { - font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */ - line-height: 1.5; /* 2 */ -} - -/** - * 1. Prevent padding and border from affecting element width. - * - * We used to set this in the html element and inherit from - * the parent element for everything else. This caused issues - * in shadow-dom-enhanced elements like
where the content - * is wrapped by a div with box-sizing set to `content-box`. - * - * https://github.com/mozdevs/cssremedy/issues/4 - * - * - * 2. Allow adding a border to an element by just adding a border-width. - * - * By default, the way the browser specifies that an element should have no - * border is by setting it's border-style to `none` in the user-agent - * stylesheet. - * - * In order to easily add borders to elements by just setting the `border-width` - * property, we change the default border-style for all elements to `solid`, and - * use border-width to hide them instead. This way our `border` utilities only - * need to set the `border-width` property instead of the entire `border` - * shorthand, making our border utilities much more straightforward to compose. - * - * https://github.com/tailwindcss/tailwindcss/pull/116 - */ - -*, -::before, -::after { - box-sizing: border-box; /* 1 */ - border-width: 0; /* 2 */ - border-style: solid; /* 2 */ - border-color: #e2e8f0; /* 2 */ -} - -/* - * Ensure horizontal rules are visible by default - */ - -hr { - border-top-width: 1px; -} - -/** - * Undo the `border-style: none` reset that Normalize applies to images so that - * our `border-{width}` utilities have the expected effect. - * - * The Normalize reset is unnecessary for us since we default the border-width - * to 0 on all elements. - * - * https://github.com/tailwindcss/tailwindcss/issues/362 - */ - -img { - border-style: solid; -} - -textarea { - resize: vertical; -} - -input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { - color: #a0aec0; -} - -input:-ms-input-placeholder, textarea:-ms-input-placeholder { - color: #a0aec0; -} - -input::-ms-input-placeholder, textarea::-ms-input-placeholder { - color: #a0aec0; -} - -input::placeholder, -textarea::placeholder { - color: #a0aec0; -} - -button, -[role="button"] { - cursor: pointer; -} - -table { - border-collapse: collapse; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - font-weight: inherit; -} - -/** - * Reset links to optimize for opt-in styling instead of - * opt-out. - */ - -a { - color: inherit; - text-decoration: inherit; -} - -/** - * Reset form element properties that are easy to forget to - * style explicitly so you don't inadvertently introduce - * styles that deviate from your design system. These styles - * supplement a partial reset that is already applied by - * normalize.css. - */ - -button, -input, -optgroup, -select, -textarea { - padding: 0; - line-height: inherit; - color: inherit; -} - -/** - * Use the configured 'mono' font family for elements that - * are expected to be rendered with a monospace font, falling - * back to the system monospace stack if there is no configured - * 'mono' font family. - */ - -pre, -code, -kbd, -samp { - font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; -} - -/** - * Make replaced elements `display: block` by default as that's - * the behavior you want almost all of the time. Inspired by - * CSS Remedy, with `svg` added as well. - * - * https://github.com/mozdevs/cssremedy/issues/14 - */ - -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; - vertical-align: middle; -} - -/** - * Constrain images and videos to the parent width and preserve - * their instrinsic aspect ratio. - * - * https://github.com/mozdevs/cssremedy/issues/14 - */ - -img, -video { - max-width: 100%; - height: auto; -} - -.container { - width: 100%; -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -.form-checkbox:checked { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); - border-color: transparent; - background-color: currentColor; - background-size: 100% 100%; - background-position: center; - background-repeat: no-repeat; -} - -@media not print { - .form-checkbox::-ms-check { - border-width: 1px; - color: transparent; - background: inherit; - border-color: inherit; - border-radius: inherit; - } -} - -.form-checkbox { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - -webkit-print-color-adjust: exact; - color-adjust: exact; - display: inline-block; - vertical-align: middle; - background-origin: border-box; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - flex-shrink: 0; - height: 1em; - width: 1em; - color: #4299e1; - background-color: #fff; - border-color: #e2e8f0; - border-width: 1px; - border-radius: 0.25rem; -} - -.form-checkbox:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); - border-color: #63b3ed; -} - -.bg-transparent { - background-color: transparent; -} - -.bg-black { - --bg-opacity: 1; - background-color: #000; - background-color: rgba(0, 0, 0, var(--bg-opacity)); -} - -.bg-white { - --bg-opacity: 1; - background-color: #fff; - background-color: rgba(255, 255, 255, var(--bg-opacity)); -} - -.bg-gray-100 { - --bg-opacity: 1; - background-color: #f7fafc; - background-color: rgba(247, 250, 252, var(--bg-opacity)); -} - -.bg-gray-150{ - background-color: #ebf0f147; -} - -.bg-gray-200 { - --bg-opacity: 1; - /* background-color: #edf2f7; */ - background-color: #ebf0f1; - /* background-color: rgba(237, 242, 247, var(--bg-opacity)); */ -} - -.bg-gray-300 { - --bg-opacity: 1; - background-color: #e2e8f0; - background-color: rgba(226, 232, 240, var(--bg-opacity)); -} - -.bg-gray-700 { - --bg-opacity: 1; - background-color: #4a5568; - background-color: rgba(74, 85, 104, var(--bg-opacity)); -} - -.bg-gray-800 { - --bg-opacity: 1; - background-color: #2d3748; - background-color: rgba(45, 55, 72, var(--bg-opacity)); -} - -.bg-gray-900 { - --bg-opacity: 1; - background-color: #1a202c; - background-color: rgba(26, 32, 44, var(--bg-opacity)); -} - -.bg-red-200 { - --bg-opacity: 1; - background-color: #fed7d7; - background-color: rgba(254, 215, 215, var(--bg-opacity)); -} - -.bg-red-400 { - --bg-opacity: 1; - background-color: #fc8181; - background-color: rgba(252, 129, 129, var(--bg-opacity)); -} - -.bg-red-500 { - --bg-opacity: 1; - background-color: #f56565; - background-color: rgba(245, 101, 101, var(--bg-opacity)); -} - -.bg-red-600 { - --bg-opacity: 1; - background-color: #e53e3e; - background-color: rgba(229, 62, 62, var(--bg-opacity)); -} - -.bg-red-700 { - --bg-opacity: 1; - background-color: #c53030; - background-color: rgba(197, 48, 48, var(--bg-opacity)); -} - -.bg-orange-200 { - --bg-opacity: 1; - background-color: #feebc8; - background-color: rgba(254, 235, 200, var(--bg-opacity)); -} - -.bg-orange-500 { - --bg-opacity: 1; - background-color: #ed8936; - background-color: rgba(237, 137, 54, var(--bg-opacity)); -} - -.bg-yellow-500 { - --bg-opacity: 1; - background-color: #ecc94b; - background-color: rgba(236, 201, 75, var(--bg-opacity)); -} - -.bg-green-200 { - --bg-opacity: 1; - background-color: #c6f6d5; - background-color: rgba(198, 246, 213, var(--bg-opacity)); -} - -.bg-green-400 { - --bg-opacity: 1; - background-color: #68d391; - background-color: rgba(104, 211, 145, var(--bg-opacity)); -} - -.bg-green-500 { - --bg-opacity: 1; - background-color: #48bb78; - background-color: rgba(72, 187, 120, var(--bg-opacity)); -} - -.bg-teal-200 { - --bg-opacity: 1; - background-color: #b2f5ea; - background-color: rgba(178, 245, 234, var(--bg-opacity)); -} - -.bg-teal-500 { - --bg-opacity: 1; - background-color: #38b2ac; - background-color: rgba(56, 178, 172, var(--bg-opacity)); -} - -.bg-blue-200 { - --bg-opacity: 1; - background-color: #bee3f8; - background-color: rgba(190, 227, 248, var(--bg-opacity)); -} - -.bg-blue-300 { - --bg-opacity: 1; - background-color: #90cdf4; - background-color: rgba(144, 205, 244, var(--bg-opacity)); -} - -.bg-blue-400 { - --bg-opacity: 1; - background-color: #63b3ed; - background-color: rgba(99, 179, 237, var(--bg-opacity)); -} - -.bg-blue-500 { - --bg-opacity: 1; - background-color: #4299e1; - background-color: rgba(66, 153, 225, var(--bg-opacity)); -} - -.bg-blue-600 { - --bg-opacity: 1; - background-color: #3182ce; - background-color: rgba(49, 130, 206, var(--bg-opacity)); -} - -.bg-blue-800 { - --bg-opacity: 1; - background-color: #2c5282; - background-color: rgba(44, 82, 130, var(--bg-opacity)); -} - -.bg-blue-900 { - --bg-opacity: 1; - background-color: #2a4365; - background-color: rgba(42, 67, 101, var(--bg-opacity)); -} - -.bg-indigo-500 { - --bg-opacity: 1; - background-color: #667eea; - background-color: rgba(102, 126, 234, var(--bg-opacity)); -} - -.bg-purple-200 { - --bg-opacity: 1; - background-color: #e9d8fd; - background-color: rgba(233, 216, 253, var(--bg-opacity)); -} - -.bg-purple-500 { - --bg-opacity: 1; - background-color: #9f7aea; - background-color: rgba(159, 122, 234, var(--bg-opacity)); -} - -.bg-pink-500 { - --bg-opacity: 1; - background-color: #ed64a6; - background-color: rgba(237, 100, 166, var(--bg-opacity)); -} - -.active\:bg-gray-100:active { - --bg-opacity: 1; - background-color: #f7fafc; - background-color: rgba(247, 250, 252, var(--bg-opacity)); -} - -.active\:bg-gray-700:active { - --bg-opacity: 1; - background-color: #4a5568; - background-color: rgba(74, 85, 104, var(--bg-opacity)); -} - -.active\:bg-blue-600:active { - --bg-opacity: 1; - background-color: #3182ce; - background-color: rgba(49, 130, 206, var(--bg-opacity)); -} - -.active\:bg-indigo-600:active { - --bg-opacity: 1; - background-color: #5a67d8; - background-color: rgba(90, 103, 216, var(--bg-opacity)); -} - -.bg-center { - background-position: center; -} - -.bg-no-repeat { - background-repeat: no-repeat; -} - -.bg-cover { - background-size: cover; -} - -.bg-full { - background-size: 100$; -} - -.border-collapse { - border-collapse: collapse; -} - -.border-transparent { - border-color: transparent; -} - -.border-gray-100 { - --border-opacity: 1; - border-color: #f7fafc; - border-color: rgba(247, 250, 252, var(--border-opacity)); -} - -.border-gray-200 { - --border-opacity: 1; - border-color: #edf2f7; - border-color: rgba(237, 242, 247, var(--border-opacity)); -} - -.border-gray-300 { - --border-opacity: 1; - border-color: #e2e8f0; - border-color: rgba(226, 232, 240, var(--border-opacity)); -} - -.border-gray-400 { - --border-opacity: 1; - border-color: #cbd5e0; - border-color: rgba(203, 213, 224, var(--border-opacity)); -} - -.border-gray-600 { - --border-opacity: 1; - border-color: #718096; - border-color: rgba(113, 128, 150, var(--border-opacity)); -} - -.border-gray-700 { - --border-opacity: 1; - border-color: #4a5568; - border-color: rgba(74, 85, 104, var(--border-opacity)); -} - -.border-blue-700 { - --border-opacity: 1; - border-color: #2b6cb0; - border-color: rgba(43, 108, 176, var(--border-opacity)); -} - -.rounded { - border-radius: 0.25rem; -} - -.rounded-sm { - border-radius: 0.15rem; -} - - -.rounded-lg { - border-radius: 0.5rem; -} - -.rounded-full { - border-radius: 9999px; -} - -.rounded-t { - border-top-left-radius: 0.25rem; - border-top-right-radius: 0.25rem; -} - -.rounded-t-lg { - border-top-left-radius: 0.5rem; - border-top-right-radius: 0.5rem; -} - -.border-solid { - border-style: solid; -} - -.border-none { - border-style: none; -} - -.border-0 { - border-width: 0; -} - -.border-2 { - border-width: 2px; -} - -.border { - border-width: 1px; -} - -.border-t-0 { - border-top-width: 0; -} - -.border-r-0 { - border-right-width: 0; -} - -.border-l-0 { - border-left-width: 0; -} - -.border-t { - border-top-width: 1px; -} - -.border-b { - border-bottom-width: 1px; -} - -.cursor-pointer { - cursor: pointer; -} - -.block { - display: block; -} - -.inline-block { - display: inline-block; -} - -.flex { - display: flex; -} - -.inline-flex { - display: inline-flex; -} - -.table { - display: table; -} - -.hidden { - display: none; -} - -.flex-row { - flex-direction: row; -} - -.flex-col { - flex-direction: column; -} - -.flex-wrap { - flex-wrap: wrap; -} - -.items-center { - align-items: center; -} - -.items-stretch { - align-items: stretch; -} - -.content-center { - align-content: center; -} - -.justify-end { - justify-content: flex-end; -} - -.justify-center { - justify-content: center; -} - -.justify-between { - justify-content: space-between; -} - -.flex-1 { - flex: 1 1 0%; -} - -.flex-auto { - flex: 1 1 auto; -} - -.flex-initial { - flex: 0 1 auto; -} - -.flex-grow { - flex-grow: 1; -} - -.float-left { - float: left; -} - -.font-light { - font-weight: 300; -} - -.font-normal { - font-weight: 400; -} - -.font-semibold { - font-weight: 600; -} - -.font-bold { - font-weight: 700; -} - -.h-0 { - height: 0; -} - -.h-2 { - height: 0.5rem; -} - -.h-5 { - height: 1.25rem; -} - -.h-8 { - height: 2rem; -} - -.h-10 { - height: 2.5rem; -} - -.h-12 { - height: 3rem; -} - -.h-16 { - height: 4rem; -} - -.h-20 { - height: 5rem; -} - -.h-auto { - height: auto; -} - -.h-full { - height: 100%; -} - -.h-screen { - height: 100vh; -} - -.h-95-px { - height: 95px; -} - -.h-70-px { - height: 70px; -} - -.h-350-px { - height: 350px; -} - -.h-500-px { - height: 500px; -} - -.h-600-px { - height: 600px; -} - -.text-55 { - font-size: 55rem; -} - -.text-xs { - font-size: 0.75rem; -} - -.text-sm { - font-size: 0.875rem; -} - -.text-base { - font-size: 1rem; -} - -.text-lg { - font-size: 1.125rem; -} - -.text-xl { - font-size: 1.25rem; -} - -.text-2xl { - font-size: 1.5rem; -} - -.text-3xl { - font-size: 1.875rem; -} - -.text-4xl { - font-size: 2.25rem; -} - -.text-5xl { - font-size: 3rem; -} - -.leading-none { - line-height: 1; -} - -.leading-snug { - line-height: 1.375; -} - -.leading-normal { - line-height: 1.5; -} - -.leading-relaxed { - line-height: 1.625; -} - -.list-none { - list-style-type: none; -} - -.m-2 { - margin: 0.5rem; -} - -.m-4 { - margin: 1rem; -} - -.-m-16 { - margin: -4rem; -} - -.-m-24 { - margin: -6rem; -} - -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.my-4 { - margin-top: 1rem; - margin-bottom: 1rem; -} - -.mx-4 { - margin-left: 1rem; - margin-right: 1rem; -} - -.my-6 { - margin-top: 1.5rem; - margin-bottom: 1.5rem; -} - -.mx-auto { - margin-left: auto; - margin-right: auto; -} - -.mt-0 { - margin-top: 0; -} - -.mr-0 { - margin-right: 0; -} - -.mb-0 { - margin-bottom: 0; -} - -.mt-1 { - margin-top: 0.25rem; -} - -.mr-1 { - margin-right: 0.25rem; -} - -.mb-1 { - margin-bottom: 0.25rem; -} - -.ml-1 { - margin-left: 0.25rem; -} - -.mt-2 { - margin-top: 0.5rem; -} - -.mr-2 { - margin-right: 0.5rem; -} - -.mb-2 { - margin-bottom: 0.5rem; -} - -.ml-2 { - margin-left: 0.5rem; -} - -.mt-3 { - margin-top: 0.75rem; -} - -.mr-3 { - margin-right: 0.75rem; -} - -.mb-3 { - margin-bottom: 0.75rem; -} - -.ml-3 { - margin-left: 0.75rem; -} - -.mt-4 { - margin-top: 1rem; -} - -.mr-4 { - margin-right: 1rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - -.mt-5 { - margin-top: 1.25rem; -} - -.mb-5 { - margin-bottom: 1.25rem; -} - -.mt-6 { - margin-top: 1.5rem; -} - -.mb-6 { - margin-bottom: 1.5rem; -} - -.mt-8 { - margin-top: 2rem; -} - -.mb-8 { - margin-bottom: 2rem; -} - -.mt-10 { - margin-top: 2.5rem; -} - -.mt-12 { - margin-top: 3rem; -} - -.mb-12 { - margin-bottom: 3rem; -} - -.mt-16 { - margin-top: 4rem; -} - -.mt-20 { - margin-top: 5rem; -} - -.mt-24 { - margin-top: 6rem; -} - -.mb-24 { - margin-bottom: 6rem; -} - -.mt-32 { - margin-top: 8rem; -} - -.mt-48 { - margin-top: 12rem; -} - -.mr-auto { - margin-right: auto; -} - -.ml-auto { - margin-left: auto; -} - -.-ml-4 { - margin-left: -1rem; -} - -.-mt-20 { - margin-top: -5rem; -} - -.-ml-20 { - margin-left: -5rem; -} - -.-mt-24 { - margin-top: -6rem; -} - -.-mt-32 { - margin-top: -8rem; -} - -.-mt-48 { - margin-top: -12rem; -} - -.-mt-64 { - margin-top: -16rem; -} - -.last\:mr-0:last-child { - margin-right: 0; -} - -.hover\:-mt-4:hover { - margin-top: -1rem; -} - -.max-h-860-px { - max-height: 860px; -} - -.max-w-full { - max-width: 100%; -} - -.max-w-100-px { - max-width: 100px; -} - -.max-w-120-px { - max-width: 120px; -} - -.max-w-150-px { - max-width: 150px; -} - -.max-w-180-px { - max-width: 180px; -} - -.max-w-200-px { - max-width: 200px; -} - -.max-w-210-px { - max-width: 210px; -} - -.max-w-580-px { - max-width: 580px; -} - -.min-h-screen { - min-height: 100vh; -} - -.min-h-screen-75 { - min-height: 75vh; -} - -.min-w-0 { - min-width: 0; -} - -.min-w-48 { - min-width: 12rem; -} - -.min-w-140-px { - min-width: 140px; -} - -.opacity-50 { - opacity: 0.5; -} - -.opacity-75 { - opacity: 0.75; -} - -.opacity-80 { - opacity: .8; -} - -.outline-none { - outline: 0; -} - -.focus\:outline-none:focus { - outline: 0; -} - -.overflow-hidden { - overflow: hidden; -} - -.overflow-x-auto { - overflow-x: auto; -} - -.overflow-y-auto { - overflow-y: auto; -} - -.overflow-x-hidden { - overflow-x: hidden; -} - -.p-2 { - padding: 0.5rem; -} - -.p-3 { - padding: 0.75rem; -} - -.p-4 { - padding: 1rem; -} - -.p-5 { - padding: 1.25rem; -} - -.p-8 { - padding: 2rem; -} - -.px-0 { - padding-left: 0; - padding-right: 0; -} - -.py-1 { - padding-top: 0.25rem; - padding-bottom: 0.25rem; -} - -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; -} - -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; -} - -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; -} - -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} - -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} - -.py-5 { - padding-top: 1.25rem; - padding-bottom: 1.25rem; -} - -.py-6 { - padding-top: 1.5rem; - padding-bottom: 1.5rem; -} - -.px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; -} - -.py-10 { - padding-top: 2.5rem; - padding-bottom: 2.5rem; -} - -.px-12 { - padding-left: 3rem; - padding-right: 3rem; -} - -.py-16 { - padding-top: 4rem; - padding-bottom: 4rem; -} - -.py-20 { - padding-top: 5rem; - padding-bottom: 5rem; -} - -.py-24 { - padding-top: 6rem; - padding-bottom: 6rem; -} - -.py-40 { - padding-top: 10rem; - padding-bottom: 10rem; -} - -.pt-0 { - padding-top: 0; -} - -.pb-0 { - padding-bottom: 0; -} - -.pt-1 { - padding-top: 0.25rem; -} - -.pt-2 { - padding-top: 0.5rem; -} - -.pb-2 { - padding-bottom: 0.5rem; -} - -.pl-3 { - padding-left: 0.75rem; -} - -.pr-4 { - padding-right: 1rem; -} - -.pb-4 { - padding-bottom: 1rem; -} - -.pl-4 { - padding-left: 1rem; -} - -.pt-6 { - padding-top: 1.5rem; -} - -.pb-6 { - padding-bottom: 1.5rem; -} - -.pt-8 { - padding-top: 2rem; -} - -.pl-10 { - padding-left: 2.5rem; -} - -.pt-12 { - padding-top: 3rem; -} - -.pr-12 { - padding-right: 3rem; -} - -.pt-16 { - padding-top: 4rem; -} - -.pb-16 { - padding-bottom: 4rem; -} - -.pt-20 { - padding-top: 5rem; -} - -.pb-20 { - padding-bottom: 5rem; -} - -.pt-24 { - padding-top: 6rem; -} - -.pt-32 { - padding-top: 8rem; -} - -.pb-8 { - padding-bottom: 2rem; -} - -.pb-32 { - padding-bottom: 8rem; -} - -.pb-40 { - padding-bottom: 10rem; -} - -.pt-48 { - padding-top: 12rem; -} - -.pb-48 { - padding-bottom: 12rem; -} - -.pb-64 { - padding-bottom: 16rem; -} - -.placeholder-gray-400::-webkit-input-placeholder { - --placeholder-opacity: 1; - color: #cbd5e0; - color: rgba(203, 213, 224, var(--placeholder-opacity)); -} - -.placeholder-gray-400:-ms-input-placeholder { - --placeholder-opacity: 1; - color: #cbd5e0; - color: rgba(203, 213, 224, var(--placeholder-opacity)); -} - -.placeholder-gray-400::-ms-input-placeholder { - --placeholder-opacity: 1; - color: #cbd5e0; - color: rgba(203, 213, 224, var(--placeholder-opacity)); -} - -.placeholder-gray-400::placeholder { - --placeholder-opacity: 1; - color: #cbd5e0; - color: rgba(203, 213, 224, var(--placeholder-opacity)); -} - -.pointer-events-none { - pointer-events: none; -} - -.fixed { - position: fixed; -} - -.absolute { - position: absolute; -} - -.relative { - position: relative; -} - -.top-0 { - top: 0; -} - -.right-0 { - right: 0; -} - -.bottom-0 { - bottom: 0; -} - -.left-0 { - left: 0; -} - -.top-auto { - top: auto; -} - -.bottom-auto { - bottom: auto; -} - -.left-auto { - left: auto; -} - -.-right-100 { - right: -100%; -} - -.-top-225-px { - top: -225px; -} - -.-top-160-px { - top: -160px; -} - -.-top-150-px { - top: -150px; -} - -.-top-94-px { - top: -94px; -} - -.-left-50-px { - left: -50px; -} - -.-top-29-px { - top: -29px; -} - -.-left-20-px { - left: -20px; -} - -.top-25-px { - top: 25px; -} - -.left-40-px { - left: 40px; -} - -.top-95-px { - top: 95px; -} - -.left-145-px { - left: 145px; -} - -.left-195-px { - left: 195px; -} - -.top-210-px { - top: 210px; -} - -.left-260-px { - left: 260px; -} - -.shadow { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); -} - -.shadow-md { - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} - -.shadow-xl { - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); -} - -.shadow-2xl { - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); -} - -.shadow-none { - box-shadow: none; -} - -.hover\:shadow-md:hover { - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); -} - -.hover\:shadow-lg:hover { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} - -.focus\:shadow-outline:focus { - box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); -} - -.fill-current { - fill: currentColor; -} - -.text-left { - text-align: left; -} - -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.text-black { - --text-opacity: 1; - color: #000; - color: rgba(0, 0, 0, var(--text-opacity)); -} - -.text-white { - --text-opacity: 1; - color: #fff; - color: rgba(255, 255, 255, var(--text-opacity)); -} - -.text-gray-200 { - --text-opacity: 1; - color: #edf2f7; - color: rgba(237, 242, 247, var(--text-opacity)); -} - -.text-gray-300 { - --text-opacity: 1; - color: #e2e8f0; - color: rgba(226, 232, 240, var(--text-opacity)); -} - -.text-gray-400 { - --text-opacity: 1; - color: #cbd5e0; - color: rgba(203, 213, 224, var(--text-opacity)); -} - -.text-gray-500 { - --text-opacity: 1; - color: #a0aec0; - color: rgba(160, 174, 192, var(--text-opacity)); -} - -.text-gray-600 { - --text-opacity: 1; - color: #718096; - color: rgba(113, 128, 150, var(--text-opacity)); -} - -.text-gray-700 { - --text-opacity: 1; - color: #4a5568; - color: rgba(74, 85, 104, var(--text-opacity)); -} - -.text-gray-800 { - --text-opacity: 1; - color: #2d3748; - color: rgba(45, 55, 72, var(--text-opacity)); -} - -.text-gray-900 { - --text-opacity: 1; - color: #1a202c; - color: rgba(26, 32, 44, var(--text-opacity)); -} - -.text-red-500 { - --text-opacity: 1; - color: #f56565; - color: rgba(245, 101, 101, var(--text-opacity)); -} - -.text-orange-500 { - --text-opacity: 1; - color: #ed8936; - color: rgba(237, 137, 54, var(--text-opacity)); -} - -.text-green-500 { - --text-opacity: 1; - color: #48bb78; - color: rgba(72, 187, 120, var(--text-opacity)); -} - -.text-teal-500 { - --text-opacity: 1; - color: #38b2ac; - color: rgba(56, 178, 172, var(--text-opacity)); -} - -.text-blue-300 { - --text-opacity: 1; - color: #90cdf4; - color: rgba(144, 205, 244, var(--text-opacity)); -} - -.text-blue-400 { - --text-opacity: 1; - color: #63b3ed; - color: rgba(99, 179, 237, var(--text-opacity)); -} - -.text-blue-500 { - --text-opacity: 1; - color: #4299e1; - color: rgba(66, 153, 225, var(--text-opacity)); -} - -.text-blue-600 { - --text-opacity: 1; - color: #3182ce; - color: rgba(49, 130, 206, var(--text-opacity)); -} - -.text-pink-400 { - --text-opacity: 1; - color: #f687b3; - color: rgba(246, 135, 179, var(--text-opacity)); -} - -.hover\:text-gray-400:hover { - --text-opacity: 1; - color: #cbd5e0; - color: rgba(203, 213, 224, var(--text-opacity)); -} - -.hover\:text-gray-600:hover { - --text-opacity: 1; - color: #718096; - color: rgba(113, 128, 150, var(--text-opacity)); -} - -.hover\:text-gray-800:hover { - --text-opacity: 1; - color: #2d3748; - color: rgba(45, 55, 72, var(--text-opacity)); -} - -.hover\:text-gray-900:hover { - --text-opacity: 1; - color: #1a202c; - color: rgba(26, 32, 44, var(--text-opacity)); -} - -.hover\:text-blue-600:hover { - --text-opacity: 1; - color: #3182ce; - color: rgba(49, 130, 206, var(--text-opacity)); -} - -.uppercase { - text-transform: uppercase; -} - -.no-underline { - text-decoration: none; -} - -.antialiased { - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.tracking-wide { - letter-spacing: 0.025em; -} - -.align-middle { - vertical-align: middle; -} - -.whitespace-no-wrap { - white-space: nowrap; -} - -.break-words { - overflow-wrap: break-word; -} - -.w-5 { - width: 1.25rem; -} - -.w-8 { - width: 2rem; -} - -.w-10 { - width: 2.5rem; -} - -.w-12 { - width: 3rem; -} - -.w-16 { - width: 4rem; -} - -.w-100 { - width: 25rem; -} - -.w-auto { - width: auto; -} - -.w-1\/2 { - width: 50%; -} - -.w-6\/12 { - width: 50%; -} - -.w-10\/12 { - width: 83.333333%; -} - -.w-full { - width: 100%; -} - -.z-2 { - z-index: 2; -} - -.z-3 { - z-index: 3; -} - -.z-10 { - z-index: 10; -} - -.z-40 { - z-index: 40; -} - -.z-50 { - z-index: 50; -} - -.transform { - --transform-translate-x: 0; - --transform-translate-y: 0; - --transform-rotate: 0; - --transform-skew-x: 0; - --transform-skew-y: 0; - --transform-scale-x: 1; - --transform-scale-y: 1; - transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); -} - -.transition-all { - transition-property: all; -} - -.ease-linear { - transition-timing-function: linear; -} - -.duration-150 { - transition-duration: 150ms; -} - -@-webkit-keyframes spin { - to { - transform: rotate(360deg); - } -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -@-webkit-keyframes ping { - 75%, 100% { - transform: scale(2); - opacity: 0; - } -} - -@keyframes ping { - 75%, 100% { - transform: scale(2); - opacity: 0; - } -} - -@-webkit-keyframes pulse { - 50% { - opacity: .5; - } -} - -@keyframes pulse { - 50% { - opacity: .5; - } -} - -@-webkit-keyframes bounce { - 0%, 100% { - transform: translateY(-25%); - -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1); - animation-timing-function: cubic-bezier(0.8,0,1,1); - } - - 50% { - transform: none; - -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1); - animation-timing-function: cubic-bezier(0,0,0.2,1); - } -} - -@keyframes bounce { - 0%, 100% { - transform: translateY(-25%); - -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1); - animation-timing-function: cubic-bezier(0.8,0,1,1); - } - - 50% { - transform: none; - -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1); - animation-timing-function: cubic-bezier(0,0,0.2,1); - } -} - -@media (min-width: 640px) { - .sm\:block { - display: block; - } - - .sm\:mt-0 { - margin-top: 0; - } - - .sm\:ml-1 { - margin-left: 0.25rem; - } - - .sm\:mr-2 { - margin-right: 0.5rem; - } - - .sm\:pt-0 { - padding-top: 0; - } - - .sm\:w-6\/12 { - width: 50%; - } -} - -@media (min-width: 768px) { - .md\:block { - display: block; - } - - .md\:flex { - display: flex; - } - - .md\:hidden { - display: none; - } - - .md\:flex-row { - flex-direction: row; - } - - .md\:flex-col { - flex-direction: column; - } - - .md\:flex-no-wrap { - flex-wrap: nowrap; - } - - .md\:items-stretch { - align-items: stretch; - } - - .md\:justify-start { - justify-content: flex-start; - } - - .md\:justify-end { - justify-content: flex-end; - } - - .md\:justify-between { - justify-content: space-between; - } - - .md\:mt-0 { - margin-top: 0; - } - - .md\:mt-4 { - margin-top: 1rem; - } - - .md\:mb-4 { - margin-bottom: 1rem; - } - - .md\:mt-40 { - margin-top: 10rem; - } - - .md\:ml-12 { - margin-left: 4rem; - } - - .md\:ml-48 { - margin-left: 12rem; - } - - .md\:mt-64 { - margin-top: 16rem; - } - - .md\:ml-64 { - margin-left: 16rem; - } - - .md\:min-h-full { - min-height: 100%; - } - - .md\:min-w-full { - min-width: 100%; - } - - .md\:opacity-100 { - opacity: 1; - } - - .md\:overflow-hidden { - overflow: hidden; - } - - .md\:overflow-y-auto { - overflow-y: auto; - } - - .md\:px-4 { - padding-left: 1rem; - padding-right: 1rem; - } - - .md\:px-10 { - padding-left: 2.5rem; - padding-right: 2.5rem; - } - - .md\:pt-0 { - padding-top: 0; - } - - .md\:pb-2 { - padding-bottom: 0.5rem; - } - - .md\:pr-12 { - padding-right: 3rem; - } - - .md\:pt-32 { - padding-top: 8rem; - } - - .md\:fixed { - position: fixed; - } - - .md\:relative { - position: relative; - } - - .md\:top-0 { - top: 0; - } - - .md\:bottom-0 { - bottom: 0; - } - - .md\:left-0 { - left: 0; - } - - .md\:shadow-none { - box-shadow: none; - } - - .md\:text-left { - text-align: left; - } - - .md\:w-48 { - width: 12rem; - } - - .md\:w-64 { - width: 16rem; - } - - .md\:w-4\/12 { - width: 33.333333%; - } - - .md\:w-5\/12 { - width: 41.666667%; - } - - .md\:w-6\/12 { - width: 50%; - } - - .md\:w-8\/12 { - width: 66.666667%; - } -} - -@media (min-width: 1024px) { - .lg\:bg-transparent { - background-color: transparent; - } - - .lg\:block { - display: block; - } - - .lg\:inline-block { - display: inline-block; - } - - .lg\:flex { - display: flex; - } - - .lg\:hidden { - display: none; - } - - .lg\:flex-row { - flex-direction: row; - } - - .lg\:self-center { - align-self: center; - } - - .lg\:justify-start { - justify-content: flex-start; - } - - .lg\:order-1 { - order: 1; - } - - .lg\:order-2 { - order: 2; - } - - .lg\:order-3 { - order: 3; - } - - .lg\:mb-0 { - margin-bottom: 0; - } - - .lg\:mr-1 { - margin-right: 0.25rem; - } - - .lg\:mr-4 { - margin-right: 1rem; - } - - .lg\:mt-16 { - margin-top: 4rem; - } - - .lg\:ml-auto { - margin-left: auto; - } - - .lg\:-ml-16 { - margin-left: -4rem; - } - - .lg\:-mt-64 { - margin-top: -16rem; - } - - .lg\:p-10 { - padding: 2.5rem; - } - - .lg\:py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; - } - - .lg\:px-10 { - padding-left: 2.5rem; - padding-right: 2.5rem; - } - - .lg\:pt-0 { - padding-top: 0; - } - - .lg\:pt-4 { - padding-top: 1rem; - } - - .lg\:pt-12 { - padding-top: 3rem; - } - - .lg\:pt-24 { - padding-top: 6rem; - } - - .lg\:pb-64 { - padding-bottom: 16rem; - } - - .lg\:static { - position: static; - } - - .lg\:shadow-none { - box-shadow: none; - } - - .lg\:text-left { - text-align: left; - } - - .lg\:text-right { - text-align: right; - } - - .lg\:text-white { - --text-opacity: 1; - color: #fff; - color: rgba(255, 255, 255, var(--text-opacity)); - } - - .lg\:text-gray-300 { - --text-opacity: 1; - color: #e2e8f0; - color: rgba(226, 232, 240, var(--text-opacity)); - } - - .lg\:hover\:text-gray-300:hover { - --text-opacity: 1; - color: #e2e8f0; - color: rgba(226, 232, 240, var(--text-opacity)); - } - - .lg\:w-auto { - width: auto; - } - - .lg\:w-3\/12 { - width: 25%; - } - - .lg\:w-4\/12 { - width: 33.333333%; - } - - .lg\:w-6\/12 { - width: 50%; - } - - .lg\:w-8\/12 { - width: 66.666667%; - } - - .lg\:w-9\/12 { - width: 75%; - } -} - -@media (min-width: 1280px) { - .xl\:mb-0 { - margin-bottom: 0; - } - - .xl\:w-3\/12 { - width: 25%; - } - - .xl\:w-4\/12 { - width: 33.333333%; - } - - .xl\:w-6\/12 { - width: 50%; - } - - .xl\:w-8\/12 { - width: 66.666667%; - } -} - - -.notebook-status-wrapper{ - display: flex; -} - -.notebook-status{ - width: 100%; -} - -.notebook-paragraph-progressbar-box{ - border-right: 1px solid; - cursor: pointer; -} - -.notebook-paragraph-progressbar-box:hover{ - background-color: rgba(0, 0, 0, 0.25); -} - -.notebook-paragraph-progressbar-box:last-child{ - border-right: 0; -} - -.anticon{ - vertical-align: 0.1em !important; -} - -.mh-full{ - min-height: calc(100vh - 96px); -} - -.view-connection .ant-input[disabled]{ - color: rgba(0, 0, 0, 0.75) !important; -} - -.settings-div .ant-tabs-tabpane{ - background: rgb(255, 255, 255); - margin: 0px 1px; - overflow: hidden; -} -.settings-div .ant-tabs-nav{ - margin-bottom: 0 !important; +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} + +/** + * Manually forked from SUIT CSS Base: https://github.com/suitcss/base + * A thin layer on top of normalize.css that provides a starting point more + * suitable for web applications. + */ + +/** + * Removes the default spacing and border for appropriate elements. + */ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +button { + background-color: transparent; + background-image: none; +} + +/** + * Work around a Firefox/IE bug where the transparent `button` background + * results in a loss of the default `button` focus styles. + */ + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +fieldset { + margin: 0; + padding: 0; +} + +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +/** + * Tailwind custom reset styles + */ + +/** + * 1. Use the user's configured `sans` font-family (with Tailwind's default + * sans-serif font stack as a fallback) as a sane default. + * 2. Use Tailwind's default "normal" line-height so the user isn't forced + * to override it to ensure consistency even when using the default theme. + */ + +html { + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 1 */ + line-height: 1.5; /* 2 */ +} + +/** + * 1. Prevent padding and border from affecting element width. + * + * We used to set this in the html element and inherit from + * the parent element for everything else. This caused issues + * in shadow-dom-enhanced elements like
where the content + * is wrapped by a div with box-sizing set to `content-box`. + * + * https://github.com/mozdevs/cssremedy/issues/4 + * + * + * 2. Allow adding a border to an element by just adding a border-width. + * + * By default, the way the browser specifies that an element should have no + * border is by setting it's border-style to `none` in the user-agent + * stylesheet. + * + * In order to easily add borders to elements by just setting the `border-width` + * property, we change the default border-style for all elements to `solid`, and + * use border-width to hide them instead. This way our `border` utilities only + * need to set the `border-width` property instead of the entire `border` + * shorthand, making our border utilities much more straightforward to compose. + * + * https://github.com/tailwindcss/tailwindcss/pull/116 + */ + +*, +::before, +::after { + box-sizing: border-box; /* 1 */ + border-width: 0; /* 2 */ + border-style: solid; /* 2 */ + border-color: #e2e8f0; /* 2 */ +} + +/* + * Ensure horizontal rules are visible by default + */ + +hr { + border-top-width: 1px; +} + +/** + * Undo the `border-style: none` reset that Normalize applies to images so that + * our `border-{width}` utilities have the expected effect. + * + * The Normalize reset is unnecessary for us since we default the border-width + * to 0 on all elements. + * + * https://github.com/tailwindcss/tailwindcss/issues/362 + */ + +img { + border-style: solid; +} + +textarea { + resize: vertical; +} + +input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { + color: #a0aec0; +} + +input:-ms-input-placeholder, textarea:-ms-input-placeholder { + color: #a0aec0; +} + +input::-ms-input-placeholder, textarea::-ms-input-placeholder { + color: #a0aec0; +} + +input::placeholder, +textarea::placeholder { + color: #a0aec0; +} + +button, +[role="button"] { + cursor: pointer; +} + +table { + border-collapse: collapse; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/** + * Reset links to optimize for opt-in styling instead of + * opt-out. + */ + +a { + color: inherit; + text-decoration: inherit; +} + +/** + * Reset form element properties that are easy to forget to + * style explicitly so you don't inadvertently introduce + * styles that deviate from your design system. These styles + * supplement a partial reset that is already applied by + * normalize.css. + */ + +button, +input, +optgroup, +select, +textarea { + padding: 0; + line-height: inherit; + color: inherit; +} + +/** + * Use the configured 'mono' font family for elements that + * are expected to be rendered with a monospace font, falling + * back to the system monospace stack if there is no configured + * 'mono' font family. + */ + +pre, +code, +kbd, +samp { + font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +/** + * Make replaced elements `display: block` by default as that's + * the behavior you want almost all of the time. Inspired by + * CSS Remedy, with `svg` added as well. + * + * https://github.com/mozdevs/cssremedy/issues/14 + */ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + vertical-align: middle; +} + +/** + * Constrain images and videos to the parent width and preserve + * their instrinsic aspect ratio. + * + * https://github.com/mozdevs/cssremedy/issues/14 + */ + +img, +video { + max-width: 100%; + height: auto; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +.form-checkbox:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +@media not print { + .form-checkbox::-ms-check { + border-width: 1px; + color: transparent; + background: inherit; + border-color: inherit; + border-radius: inherit; + } +} + +.form-checkbox { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + -webkit-print-color-adjust: exact; + color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1em; + width: 1em; + color: #4299e1; + background-color: #fff; + border-color: #e2e8f0; + border-width: 1px; + border-radius: 0.25rem; +} + +.form-checkbox:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); + border-color: #63b3ed; +} + +.bg-transparent { + background-color: transparent; +} + +.bg-black { + --bg-opacity: 1; + background-color: #000; + background-color: rgba(0, 0, 0, var(--bg-opacity)); +} + +.bg-white { + --bg-opacity: 1; + background-color: #fff; + background-color: rgba(255, 255, 255, var(--bg-opacity)); +} + +.bg-gray-100 { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); +} + +.bg-gray-150{ + background-color: #ebf0f147; +} + +.bg-gray-200 { + --bg-opacity: 1; + /* background-color: #edf2f7; */ + background-color: #ebf0f1; + /* background-color: rgba(237, 242, 247, var(--bg-opacity)); */ +} + +.bg-gray-300 { + --bg-opacity: 1; + background-color: #e2e8f0; + background-color: rgba(226, 232, 240, var(--bg-opacity)); +} + +.bg-gray-700 { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); +} + +.bg-gray-800 { + --bg-opacity: 1; + background-color: #2d3748; + background-color: rgba(45, 55, 72, var(--bg-opacity)); +} + +.bg-gray-900 { + --bg-opacity: 1; + background-color: #1a202c; + background-color: rgba(26, 32, 44, var(--bg-opacity)); +} + +.bg-red-200 { + --bg-opacity: 1; + background-color: #fed7d7; + background-color: rgba(254, 215, 215, var(--bg-opacity)); +} + +.bg-red-400 { + --bg-opacity: 1; + background-color: #fc8181; + background-color: rgba(252, 129, 129, var(--bg-opacity)); +} + +.bg-red-500 { + --bg-opacity: 1; + background-color: #f56565; + background-color: rgba(245, 101, 101, var(--bg-opacity)); +} + +.bg-red-600 { + --bg-opacity: 1; + background-color: #e53e3e; + background-color: rgba(229, 62, 62, var(--bg-opacity)); +} + +.bg-red-700 { + --bg-opacity: 1; + background-color: #c53030; + background-color: rgba(197, 48, 48, var(--bg-opacity)); +} + +.bg-orange-200 { + --bg-opacity: 1; + background-color: #feebc8; + background-color: rgba(254, 235, 200, var(--bg-opacity)); +} + +.bg-orange-500 { + --bg-opacity: 1; + background-color: #ed8936; + background-color: rgba(237, 137, 54, var(--bg-opacity)); +} + +.bg-yellow-500 { + --bg-opacity: 1; + background-color: #ecc94b; + background-color: rgba(236, 201, 75, var(--bg-opacity)); +} + +.bg-green-200 { + --bg-opacity: 1; + background-color: #c6f6d5; + background-color: rgba(198, 246, 213, var(--bg-opacity)); +} + +.bg-green-400 { + --bg-opacity: 1; + background-color: #68d391; + background-color: rgba(104, 211, 145, var(--bg-opacity)); +} + +.bg-green-500 { + --bg-opacity: 1; + background-color: #48bb78; + background-color: rgba(72, 187, 120, var(--bg-opacity)); +} + +.bg-teal-200 { + --bg-opacity: 1; + background-color: #b2f5ea; + background-color: rgba(178, 245, 234, var(--bg-opacity)); +} + +.bg-teal-500 { + --bg-opacity: 1; + background-color: #38b2ac; + background-color: rgba(56, 178, 172, var(--bg-opacity)); +} + +.bg-blue-200 { + --bg-opacity: 1; + background-color: #bee3f8; + background-color: rgba(190, 227, 248, var(--bg-opacity)); +} + +.bg-blue-300 { + --bg-opacity: 1; + background-color: #90cdf4; + background-color: rgba(144, 205, 244, var(--bg-opacity)); +} + +.bg-blue-400 { + --bg-opacity: 1; + background-color: #63b3ed; + background-color: rgba(99, 179, 237, var(--bg-opacity)); +} + +.bg-blue-500 { + --bg-opacity: 1; + background-color: #4299e1; + background-color: rgba(66, 153, 225, var(--bg-opacity)); +} + +.bg-blue-600 { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); +} + +.bg-blue-800 { + --bg-opacity: 1; + background-color: #2c5282; + background-color: rgba(44, 82, 130, var(--bg-opacity)); +} + +.bg-blue-900 { + --bg-opacity: 1; + background-color: #2a4365; + background-color: rgba(42, 67, 101, var(--bg-opacity)); +} + +.bg-indigo-500 { + --bg-opacity: 1; + background-color: #667eea; + background-color: rgba(102, 126, 234, var(--bg-opacity)); +} + +.bg-purple-200 { + --bg-opacity: 1; + background-color: #e9d8fd; + background-color: rgba(233, 216, 253, var(--bg-opacity)); +} + +.bg-purple-500 { + --bg-opacity: 1; + background-color: #9f7aea; + background-color: rgba(159, 122, 234, var(--bg-opacity)); +} + +.bg-pink-500 { + --bg-opacity: 1; + background-color: #ed64a6; + background-color: rgba(237, 100, 166, var(--bg-opacity)); +} + +.active\:bg-gray-100:active { + --bg-opacity: 1; + background-color: #f7fafc; + background-color: rgba(247, 250, 252, var(--bg-opacity)); +} + +.active\:bg-gray-700:active { + --bg-opacity: 1; + background-color: #4a5568; + background-color: rgba(74, 85, 104, var(--bg-opacity)); +} + +.active\:bg-blue-600:active { + --bg-opacity: 1; + background-color: #3182ce; + background-color: rgba(49, 130, 206, var(--bg-opacity)); +} + +.active\:bg-indigo-600:active { + --bg-opacity: 1; + background-color: #5a67d8; + background-color: rgba(90, 103, 216, var(--bg-opacity)); +} + +.bg-center { + background-position: center; +} + +.bg-no-repeat { + background-repeat: no-repeat; +} + +.bg-cover { + background-size: cover; +} + +.bg-full { + background-size: 100$; +} + +.border-collapse { + border-collapse: collapse; +} + +.border-transparent { + border-color: transparent; +} + +.border-gray-100 { + --border-opacity: 1; + border-color: #f7fafc; + border-color: rgba(247, 250, 252, var(--border-opacity)); +} + +.border-gray-200 { + --border-opacity: 1; + border-color: #edf2f7; + border-color: rgba(237, 242, 247, var(--border-opacity)); +} + +.border-gray-300 { + --border-opacity: 1; + border-color: #e2e8f0; + border-color: rgba(226, 232, 240, var(--border-opacity)); +} + +.border-gray-400 { + --border-opacity: 1; + border-color: #cbd5e0; + border-color: rgba(203, 213, 224, var(--border-opacity)); +} + +.border-gray-600 { + --border-opacity: 1; + border-color: #718096; + border-color: rgba(113, 128, 150, var(--border-opacity)); +} + +.border-gray-700 { + --border-opacity: 1; + border-color: #4a5568; + border-color: rgba(74, 85, 104, var(--border-opacity)); +} + +.border-blue-700 { + --border-opacity: 1; + border-color: #2b6cb0; + border-color: rgba(43, 108, 176, var(--border-opacity)); +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-sm { + border-radius: 0.15rem; +} + + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +.border-solid { + border-style: solid; +} + +.border-none { + border-style: none; +} + +.border-0 { + border-width: 0; +} + +.border-2 { + border-width: 2px; +} + +.border { + border-width: 1px; +} + +.border-t-0 { + border-top-width: 0; +} + +.border-r-0 { + border-right-width: 0; +} + +.border-l-0 { + border-left-width: 0; +} + +.border-t { + border-top-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.cursor-pointer { + cursor: pointer; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.flex { + display: flex; +} + +.inline-flex { + display: inline-flex; +} + +.table { + display: table; +} + +.hidden { + display: none; +} + +.flex-row { + flex-direction: row; +} + +.flex-col { + flex-direction: column; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.items-center { + align-items: center; +} + +.items-stretch { + align-items: stretch; +} + +.content-center { + align-content: center; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-auto { + flex: 1 1 auto; +} + +.flex-initial { + flex: 0 1 auto; +} + +.flex-grow { + flex-grow: 1; +} + +.float-left { + float: left; +} + +.font-light { + font-weight: 300; +} + +.font-normal { + font-weight: 400; +} + +.font-semibold { + font-weight: 600; +} + +.font-bold { + font-weight: 700; +} + +.h-0 { + height: 0; +} + +.h-2 { + height: 0.5rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-8 { + height: 2rem; +} + +.h-10 { + height: 2.5rem; +} + +.h-12 { + height: 3rem; +} + +.h-16 { + height: 4rem; +} + +.h-20 { + height: 5rem; +} + +.h-auto { + height: auto; +} + +.h-full { + height: 100%; +} + +.h-screen { + height: 100vh; +} + +.h-95-px { + height: 95px; +} + +.h-70-px { + height: 70px; +} + +.h-350-px { + height: 350px; +} + +.h-500-px { + height: 500px; +} + +.h-600-px { + height: 600px; +} + +.text-55 { + font-size: 55rem; +} + +.text-xs { + font-size: 0.75rem; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-base { + font-size: 1rem; +} + +.text-lg { + font-size: 1.125rem; +} + +.text-xl { + font-size: 1.25rem; +} + +.text-2xl { + font-size: 1.5rem; +} + +.text-3xl { + font-size: 1.875rem; +} + +.text-4xl { + font-size: 2.25rem; +} + +.text-5xl { + font-size: 3rem; +} + +.leading-none { + line-height: 1; +} + +.leading-snug { + line-height: 1.375; +} + +.leading-normal { + line-height: 1.5; +} + +.leading-relaxed { + line-height: 1.625; +} + +.list-none { + list-style-type: none; +} + +.m-2 { + margin: 0.5rem; +} + +.m-4 { + margin: 1rem; +} + +.-m-16 { + margin: -4rem; +} + +.-m-24 { + margin: -6rem; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mt-0 { + margin-top: 0; +} + +.mr-0 { + margin-right: 0; +} + +.mb-0 { + margin-bottom: 0; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mr-1 { + margin-right: 0.25rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mr-4 { + margin-right: 1rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + +.mb-5 { + margin-bottom: 1.25rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mt-10 { + margin-top: 2.5rem; +} + +.mt-12 { + margin-top: 3rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.mt-16 { + margin-top: 4rem; +} + +.mt-20 { + margin-top: 5rem; +} + +.mt-24 { + margin-top: 6rem; +} + +.mb-24 { + margin-bottom: 6rem; +} + +.mt-32 { + margin-top: 8rem; +} + +.mt-48 { + margin-top: 12rem; +} + +.mr-auto { + margin-right: auto; +} + +.ml-auto { + margin-left: auto; +} + +.-ml-4 { + margin-left: -1rem; +} + +.-mt-20 { + margin-top: -5rem; +} + +.-ml-20 { + margin-left: -5rem; +} + +.-mt-24 { + margin-top: -6rem; +} + +.-mt-32 { + margin-top: -8rem; +} + +.-mt-48 { + margin-top: -12rem; +} + +.-mt-64 { + margin-top: -16rem; +} + +.last\:mr-0:last-child { + margin-right: 0; +} + +.hover\:-mt-4:hover { + margin-top: -1rem; +} + +.max-h-860-px { + max-height: 860px; +} + +.max-w-full { + max-width: 100%; +} + +.max-w-100-px { + max-width: 100px; +} + +.max-w-120-px { + max-width: 120px; +} + +.max-w-150-px { + max-width: 150px; +} + +.max-w-180-px { + max-width: 180px; +} + +.max-w-200-px { + max-width: 200px; +} + +.max-w-210-px { + max-width: 210px; +} + +.max-w-580-px { + max-width: 580px; +} + +.min-h-screen { + min-height: 100vh; +} + +.min-h-screen-75 { + min-height: 75vh; +} + +.min-w-0 { + min-width: 0; +} + +.min-w-48 { + min-width: 12rem; +} + +.min-w-140-px { + min-width: 140px; +} + +.opacity-50 { + opacity: 0.5; +} + +.opacity-75 { + opacity: 0.75; +} + +.opacity-80 { + opacity: .8; +} + +.outline-none { + outline: 0; +} + +.focus\:outline-none:focus { + outline: 0; +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.overflow-x-hidden { + overflow-x: hidden; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-5 { + padding: 1.25rem; +} + +.p-8 { + padding: 2rem; +} + +.px-0 { + padding-left: 0; + padding-right: 0; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; +} + +.px-12 { + padding-left: 3rem; + padding-right: 3rem; +} + +.py-16 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.py-20 { + padding-top: 5rem; + padding-bottom: 5rem; +} + +.py-24 { + padding-top: 6rem; + padding-bottom: 6rem; +} + +.py-40 { + padding-top: 10rem; + padding-bottom: 10rem; +} + +.pt-0 { + padding-top: 0; +} + +.pb-0 { + padding-bottom: 0; +} + +.pt-1 { + padding-top: 0.25rem; +} + +.pt-2 { + padding-top: 0.5rem; +} + +.pb-2 { + padding-bottom: 0.5rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.pb-4 { + padding-bottom: 1rem; +} + +.pl-4 { + padding-left: 1rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.pb-6 { + padding-bottom: 1.5rem; +} + +.pt-8 { + padding-top: 2rem; +} + +.pl-10 { + padding-left: 2.5rem; +} + +.pt-12 { + padding-top: 3rem; +} + +.pr-12 { + padding-right: 3rem; +} + +.pt-16 { + padding-top: 4rem; +} + +.pb-16 { + padding-bottom: 4rem; +} + +.pt-20 { + padding-top: 5rem; +} + +.pb-20 { + padding-bottom: 5rem; +} + +.pt-24 { + padding-top: 6rem; +} + +.pt-32 { + padding-top: 8rem; +} + +.pb-8 { + padding-bottom: 2rem; +} + +.pb-32 { + padding-bottom: 8rem; +} + +.pb-40 { + padding-bottom: 10rem; +} + +.pt-48 { + padding-top: 12rem; +} + +.pb-48 { + padding-bottom: 12rem; +} + +.pb-64 { + padding-bottom: 16rem; +} + +.placeholder-gray-400::-webkit-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.placeholder-gray-400:-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.placeholder-gray-400::-ms-input-placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.placeholder-gray-400::placeholder { + --placeholder-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--placeholder-opacity)); +} + +.pointer-events-none { + pointer-events: none; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.top-0 { + top: 0; +} + +.right-0 { + right: 0; +} + +.bottom-0 { + bottom: 0; +} + +.left-0 { + left: 0; +} + +.top-auto { + top: auto; +} + +.bottom-auto { + bottom: auto; +} + +.left-auto { + left: auto; +} + +.-right-100 { + right: -100%; +} + +.-top-225-px { + top: -225px; +} + +.-top-160-px { + top: -160px; +} + +.-top-150-px { + top: -150px; +} + +.-top-94-px { + top: -94px; +} + +.-left-50-px { + left: -50px; +} + +.-top-29-px { + top: -29px; +} + +.-left-20-px { + left: -20px; +} + +.top-25-px { + top: 25px; +} + +.left-40-px { + left: 40px; +} + +.top-95-px { + top: 95px; +} + +.left-145-px { + left: 145px; +} + +.left-195-px { + left: 195px; +} + +.top-210-px { + top: 210px; +} + +.left-260-px { + left: 260px; +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.shadow-2xl { + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); +} + +.shadow-none { + box-shadow: none; +} + +.hover\:shadow-md:hover { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.hover\:shadow-lg:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.focus\:shadow-outline:focus { + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); +} + +.fill-current { + fill: currentColor; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.text-right { + text-align: right; +} + +.text-black { + --text-opacity: 1; + color: #000; + color: rgba(0, 0, 0, var(--text-opacity)); +} + +.text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); +} + +.text-gray-200 { + --text-opacity: 1; + color: #edf2f7; + color: rgba(237, 242, 247, var(--text-opacity)); +} + +.text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); +} + +.text-gray-400 { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); +} + +.text-gray-500 { + --text-opacity: 1; + color: #a0aec0; + color: rgba(160, 174, 192, var(--text-opacity)); +} + +.text-gray-600 { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); +} + +.text-gray-700 { + --text-opacity: 1; + color: #4a5568; + color: rgba(74, 85, 104, var(--text-opacity)); +} + +.text-gray-800 { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); +} + +.text-gray-900 { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); +} + +.text-red-500 { + --text-opacity: 1; + color: #f56565; + color: rgba(245, 101, 101, var(--text-opacity)); +} + +.text-orange-500 { + --text-opacity: 1; + color: #ed8936; + color: rgba(237, 137, 54, var(--text-opacity)); +} + +.text-green-500 { + --text-opacity: 1; + color: #48bb78; + color: rgba(72, 187, 120, var(--text-opacity)); +} + +.text-teal-500 { + --text-opacity: 1; + color: #38b2ac; + color: rgba(56, 178, 172, var(--text-opacity)); +} + +.text-blue-300 { + --text-opacity: 1; + color: #90cdf4; + color: rgba(144, 205, 244, var(--text-opacity)); +} + +.text-blue-400 { + --text-opacity: 1; + color: #63b3ed; + color: rgba(99, 179, 237, var(--text-opacity)); +} + +.text-blue-500 { + --text-opacity: 1; + color: #4299e1; + color: rgba(66, 153, 225, var(--text-opacity)); +} + +.text-blue-600 { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); +} + +.text-pink-400 { + --text-opacity: 1; + color: #f687b3; + color: rgba(246, 135, 179, var(--text-opacity)); +} + +.hover\:text-gray-400:hover { + --text-opacity: 1; + color: #cbd5e0; + color: rgba(203, 213, 224, var(--text-opacity)); +} + +.hover\:text-gray-600:hover { + --text-opacity: 1; + color: #718096; + color: rgba(113, 128, 150, var(--text-opacity)); +} + +.hover\:text-gray-800:hover { + --text-opacity: 1; + color: #2d3748; + color: rgba(45, 55, 72, var(--text-opacity)); +} + +.hover\:text-gray-900:hover { + --text-opacity: 1; + color: #1a202c; + color: rgba(26, 32, 44, var(--text-opacity)); +} + +.hover\:text-blue-600:hover { + --text-opacity: 1; + color: #3182ce; + color: rgba(49, 130, 206, var(--text-opacity)); +} + +.uppercase { + text-transform: uppercase; +} + +.no-underline { + text-decoration: none; +} + +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.tracking-wide { + letter-spacing: 0.025em; +} + +.align-middle { + vertical-align: middle; +} + +.whitespace-no-wrap { + white-space: nowrap; +} + +.break-words { + overflow-wrap: break-word; +} + +.w-5 { + width: 1.25rem; +} + +.w-8 { + width: 2rem; +} + +.w-10 { + width: 2.5rem; +} + +.w-12 { + width: 3rem; +} + +.w-16 { + width: 4rem; +} + +.w-100 { + width: 25rem; +} + +.w-auto { + width: auto; +} + +.w-1\/2 { + width: 50%; +} + +.w-6\/12 { + width: 50%; +} + +.w-10\/12 { + width: 83.333333%; +} + +.w-full { + width: 100%; +} + +.z-2 { + z-index: 2; +} + +.z-3 { + z-index: 3; +} + +.z-10 { + z-index: 10; +} + +.z-40 { + z-index: 40; +} + +.z-50 { + z-index: 50; +} + +.transform { + --transform-translate-x: 0; + --transform-translate-y: 0; + --transform-rotate: 0; + --transform-skew-x: 0; + --transform-skew-y: 0; + --transform-scale-x: 1; + --transform-scale-y: 1; + transform: translateX(var(--transform-translate-x)) translateY(var(--transform-translate-y)) rotate(var(--transform-rotate)) skewX(var(--transform-skew-x)) skewY(var(--transform-skew-y)) scaleX(var(--transform-scale-x)) scaleY(var(--transform-scale-y)); +} + +.transition-all { + transition-property: all; +} + +.ease-linear { + transition-timing-function: linear; +} + +.duration-150 { + transition-duration: 150ms; +} + +@-webkit-keyframes spin { + to { + transform: rotate(360deg); + } +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@-webkit-keyframes ping { + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} + +@keyframes ping { + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} + +@-webkit-keyframes pulse { + 50% { + opacity: .5; + } +} + +@keyframes pulse { + 50% { + opacity: .5; + } +} + +@-webkit-keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1); + animation-timing-function: cubic-bezier(0.8,0,1,1); + } + + 50% { + transform: none; + -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1); + animation-timing-function: cubic-bezier(0,0,0.2,1); + } +} + +@keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + -webkit-animation-timing-function: cubic-bezier(0.8,0,1,1); + animation-timing-function: cubic-bezier(0.8,0,1,1); + } + + 50% { + transform: none; + -webkit-animation-timing-function: cubic-bezier(0,0,0.2,1); + animation-timing-function: cubic-bezier(0,0,0.2,1); + } +} + +@media (min-width: 640px) { + .sm\:block { + display: block; + } + + .sm\:mt-0 { + margin-top: 0; + } + + .sm\:ml-1 { + margin-left: 0.25rem; + } + + .sm\:mr-2 { + margin-right: 0.5rem; + } + + .sm\:pt-0 { + padding-top: 0; + } + + .sm\:w-6\/12 { + width: 50%; + } +} + +@media (min-width: 768px) { + .md\:block { + display: block; + } + + .md\:flex { + display: flex; + } + + .md\:hidden { + display: none; + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:flex-col { + flex-direction: column; + } + + .md\:flex-no-wrap { + flex-wrap: nowrap; + } + + .md\:items-stretch { + align-items: stretch; + } + + .md\:justify-start { + justify-content: flex-start; + } + + .md\:justify-end { + justify-content: flex-end; + } + + .md\:justify-between { + justify-content: space-between; + } + + .md\:mt-0 { + margin-top: 0; + } + + .md\:mt-4 { + margin-top: 1rem; + } + + .md\:mb-4 { + margin-bottom: 1rem; + } + + .md\:mt-40 { + margin-top: 10rem; + } + + .md\:ml-12 { + margin-left: 4rem; + } + + .md\:ml-48 { + margin-left: 12rem; + } + + .md\:mt-64 { + margin-top: 16rem; + } + + .md\:ml-64 { + margin-left: 16rem; + } + + .md\:min-h-full { + min-height: 100%; + } + + .md\:min-w-full { + min-width: 100%; + } + + .md\:opacity-100 { + opacity: 1; + } + + .md\:overflow-hidden { + overflow: hidden; + } + + .md\:overflow-y-auto { + overflow-y: auto; + } + + .md\:px-4 { + padding-left: 1rem; + padding-right: 1rem; + } + + .md\:px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; + } + + .md\:pt-0 { + padding-top: 0; + } + + .md\:pb-2 { + padding-bottom: 0.5rem; + } + + .md\:pr-12 { + padding-right: 3rem; + } + + .md\:pt-32 { + padding-top: 8rem; + } + + .md\:fixed { + position: fixed; + } + + .md\:relative { + position: relative; + } + + .md\:top-0 { + top: 0; + } + + .md\:bottom-0 { + bottom: 0; + } + + .md\:left-0 { + left: 0; + } + + .md\:shadow-none { + box-shadow: none; + } + + .md\:text-left { + text-align: left; + } + + .md\:w-48 { + width: 12rem; + } + + .md\:w-64 { + width: 16rem; + } + + .md\:w-4\/12 { + width: 33.333333%; + } + + .md\:w-5\/12 { + width: 41.666667%; + } + + .md\:w-6\/12 { + width: 50%; + } + + .md\:w-8\/12 { + width: 66.666667%; + } +} + +@media (min-width: 1024px) { + .lg\:bg-transparent { + background-color: transparent; + } + + .lg\:block { + display: block; + } + + .lg\:inline-block { + display: inline-block; + } + + .lg\:flex { + display: flex; + } + + .lg\:hidden { + display: none; + } + + .lg\:flex-row { + flex-direction: row; + } + + .lg\:self-center { + align-self: center; + } + + .lg\:justify-start { + justify-content: flex-start; + } + + .lg\:order-1 { + order: 1; + } + + .lg\:order-2 { + order: 2; + } + + .lg\:order-3 { + order: 3; + } + + .lg\:mb-0 { + margin-bottom: 0; + } + + .lg\:mr-1 { + margin-right: 0.25rem; + } + + .lg\:mr-4 { + margin-right: 1rem; + } + + .lg\:mt-16 { + margin-top: 4rem; + } + + .lg\:ml-auto { + margin-left: auto; + } + + .lg\:-ml-16 { + margin-left: -4rem; + } + + .lg\:-mt-64 { + margin-top: -16rem; + } + + .lg\:p-10 { + padding: 2.5rem; + } + + .lg\:py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + } + + .lg\:px-10 { + padding-left: 2.5rem; + padding-right: 2.5rem; + } + + .lg\:pt-0 { + padding-top: 0; + } + + .lg\:pt-4 { + padding-top: 1rem; + } + + .lg\:pt-12 { + padding-top: 3rem; + } + + .lg\:pt-24 { + padding-top: 6rem; + } + + .lg\:pb-64 { + padding-bottom: 16rem; + } + + .lg\:static { + position: static; + } + + .lg\:shadow-none { + box-shadow: none; + } + + .lg\:text-left { + text-align: left; + } + + .lg\:text-right { + text-align: right; + } + + .lg\:text-white { + --text-opacity: 1; + color: #fff; + color: rgba(255, 255, 255, var(--text-opacity)); + } + + .lg\:text-gray-300 { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .lg\:hover\:text-gray-300:hover { + --text-opacity: 1; + color: #e2e8f0; + color: rgba(226, 232, 240, var(--text-opacity)); + } + + .lg\:w-auto { + width: auto; + } + + .lg\:w-3\/12 { + width: 25%; + } + + .lg\:w-4\/12 { + width: 33.333333%; + } + + .lg\:w-6\/12 { + width: 50%; + } + + .lg\:w-8\/12 { + width: 66.666667%; + } + + .lg\:w-9\/12 { + width: 75%; + } +} + +@media (min-width: 1280px) { + .xl\:mb-0 { + margin-bottom: 0; + } + + .xl\:w-3\/12 { + width: 25%; + } + + .xl\:w-4\/12 { + width: 33.333333%; + } + + .xl\:w-6\/12 { + width: 50%; + } + + .xl\:w-8\/12 { + width: 66.666667%; + } +} + + +.notebook-status-wrapper{ + display: flex; +} + +.notebook-status{ + width: 100%; +} + +.notebook-paragraph-progressbar-box{ + border-right: 1px solid; + cursor: pointer; +} + +.notebook-paragraph-progressbar-box:hover{ + background-color: rgba(0, 0, 0, 0.25); +} + +.notebook-paragraph-progressbar-box:last-child{ + border-right: 0; +} + +.anticon{ + vertical-align: 0.1em !important; +} + +.mh-full{ + min-height: calc(100vh - 96px); +} + +.view-connection .ant-input[disabled]{ + color: rgba(0, 0, 0, 0.75) !important; +} + +.settings-div .ant-tabs-tabpane{ + background: rgb(255, 255, 255); + margin: 0px 1px; + overflow: hidden; +} +.settings-div .ant-tabs-nav{ + margin-bottom: 0 !important; } \ No newline at end of file diff --git a/ui/src/components/Dashboard/addWorkspace.js b/ui/src/components/Dashboard/addWorkspace.js new file mode 100755 index 00000000..1d1b6e46 --- /dev/null +++ b/ui/src/components/Dashboard/addWorkspace.js @@ -0,0 +1,26 @@ +import React from "react"; +import { Steps, Popover } from 'antd'; + +export default function AddWorkspace() { + const customDot = (dot, { status, index }) => ( + + step {index} status: {status} + + } + > + {dot} + + ); + return ( + <> + + + + + + + + ); +} diff --git a/ui/src/components/Dashboard/dashboard.js b/ui/src/components/Dashboard/dashboard.js new file mode 100755 index 00000000..2102f8f2 --- /dev/null +++ b/ui/src/components/Dashboard/dashboard.js @@ -0,0 +1,151 @@ +import React, { useState, useEffect } from "react"; +import style from "./style.module.scss"; +import workspaceService from "services/workspace.js"; +import { + Button, + Popconfirm, +} from "antd"; +import {DeleteOutlined, StopOutlined, CheckOutlined} from '@ant-design/icons'; + +export default function Dashboard() { + const [workspaces, setWorkspaces] = useState([]); + + useEffect(() => { + if (!workspaces.length) { + fetchWorkspaces(); + } + }, []); + + const fetchWorkspaces = async () => { + const response = await workspaceService.getWorkspaces(); + setWorkspaces(response.data) + } + + function routeWorkspace(id){ + console.log("test") + } + + const StopWorkspace = async (id) => { + const response = await workspaceService.stopWorkspaceServer(id) + if(response.success){ + fetchWorkspaces(); + } + } + + const StartWorkspace = async (id) => { + const response = await workspaceService.startWorkspaceServer(id) + if(response.success){ + fetchWorkspaces(); + } + } + + + const deleteWorkspace = async (id) => { + const response = await workspaceService.deleteWorkspaceServer(id) + if(response.success){ + fetchWorkspaces(); + } + } + + const cardWidget = (workspace) => { + return
+
+
+
+

{workspace.name}

+ { + workspace.replica !== 0 + ? + : + } +
+
+

{workspace.description}

+ +
+

Warehouse Location :

+ {workspace.workspaceConfig.warehouseLocation} +
+
+ + { + + workspace.replica !== 0 + ? + + : + } + + +
+
+
+
+
+

Spark Image Version

+ {workspace.workspaceConfig.sparkImage} +
+
+

Zeppelin Interpreter Image

+ {workspace.workspaceConfig.sparkImage} +
+
+
+ } + + return ( +
+
+ +
+ +
+ { + workspaces.map((workspace, index) => { + return
+ {cardWidget(workspace)} +
+ }) + } +
+
+ ); +} \ No newline at end of file diff --git a/ui/src/components/Dashboard/style.module.scss b/ui/src/components/Dashboard/style.module.scss new file mode 100755 index 00000000..a2c2a1e8 --- /dev/null +++ b/ui/src/components/Dashboard/style.module.scss @@ -0,0 +1,172 @@ +.cardList { + display:inline-block; + width:100%; + float:right; + margin:0; + min-height: 75vh; + white-space:normal; + overflow-x: scroll; + padding-top:30px; + + &::-webkit-scrollbar { + display: none; + } + + .cardWrapper{ + padding: 10px; + width: 33.33333%; + float: left; + display: inline-block; + + .card { + padding: 20px 15px 20px; + border-radius: 5px; + box-shadow: 2px 2px 0 0 #f1f1f1; + border-bottom: #dfdfdf; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + position: relative; + background: white; + + width: 100%; + white-space: normal; + height: 200; + + + + .cardBody { + white-space: normal; + // display: flex; + // align-items: 'center'; + // justify-content: 'center'; + // flex-flow: column; + + .titleContent{ + display: flex; + align-items: center; + justify-content: space-between; + flex-flow: row; + + + .bodyTitle { + font-weight: bold; + font-size: 20px; + text-align: left; + padding-bottom: 5px; + padding-left: 10px; + } + + .storageLogo{ + background-size: contain; + background-position: 100% 100%; + background-repeat: no-repeat; + width: 20%; + height: 25px; + position: relative; + top: 5px; + left: -5px; + + } + } + + + .bodySubTitle{ + text-align: left; + font-size: 14px; + color: #666666; + } + + .storage{ + display: flex; + align-items: 'center'; + justify-content: start; + flex-flow: row; + border:1px; + border-style: solid; + border-color: #d4d4d4; + border-radius: 15px; + padding: 5px 20px; + background: #eee; + + .storageText{ + font-weight: bold; + padding-right: 5px; + margin: 0; + } + } + .buttons{ + display: flex; + align-items: 'center'; + justify-content: space-evenly; + flex-flow: row; + padding-top: 15px; + + .stopStartButton{ + + // .cardButton{ + + // } + } + + } + + + + + // .cardButton { + // position: 'relative'; + // height: 500; + // align-items: 'center'; + // justify-content: 'center'; + + // .buttonText { + // text-align: center; + // font-size: 20px; + // } + // } + } + } + .bodyFooter{ + + border-radius: 5px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-color: #fafafa; + background-color: #fafafa; + display: flex; + align-items: 'center'; + justify-content: start; + text-align: center; + font-size: 12px; + + flex-flow: row; + + .section1{ + border-right: #eee solid 1px ; + padding: 10px 20px ; + width: 50%; + .footerText{ + font-weight: bold; + margin: 0; + padding-bottom: 5px; + } + + } + .section2{ + padding: 10px 20px; + width: 50%; + .footerText{ + font-weight: bold; + margin: 0; + padding-bottom: 5px; + } + + } + + + + + + } + } +} \ No newline at end of file diff --git a/ui/src/components/Notebooks/AddNotebook.js b/ui/src/components/Notebooks/AddNotebook.js index 8a25893d..376d713a 100644 --- a/ui/src/components/Notebooks/AddNotebook.js +++ b/ui/src/components/Notebooks/AddNotebook.js @@ -1,201 +1,202 @@ -import React, { useState, useEffect } from "react"; -import { Button, Form, Input, message, Select } from "antd"; -import AceEditor from "react-ace"; -import "ace-builds/src-noconflict/mode-mysql"; -import "ace-builds/src-noconflict/theme-chrome"; - -import style from "./style.module.scss"; -import notebookService from "services/notebooks.js"; -import connectionService from "services/connection.js"; -import { Link } from "react-router-dom"; -import { LeftOutlined } from '@ant-design/icons'; -import DatasetSelector from "../DatasetSelector/DatasetSelector"; - -const { Option } = Select; - -export default function AddNotebook(props) { - const [notebookTemplates, setNotebookTemplates] = useState([]); - const [connections, setConnections] = useState([]); - const [selectedNotebookTemplate, setSelectedNotebookTemplate] = useState(''); - const [form] = Form.useForm(); - - useEffect(() => { - if (!notebookTemplates.length) { - fetchNotebookTemplates(); - } - if (!connections.length) { - fetchConnections(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchNotebookTemplates = async () => { - const response = await notebookService.getNotebookTemplates() - setNotebookTemplates(response.data) - } - - const fetchConnections = async () => { - const response = await connectionService.getConnections(); - setConnections(response.data) - } - - const handleNotebookTemplateSelect = (notebookTemplate) => { - setSelectedNotebookTemplate(notebookTemplate) - } - - const addNotebookFormSubmit = async (values) => { - values["notebookTemplateId"] = selectedNotebookTemplate.id; - const response = await notebookService.addNotebook(values) - if(response.success){ - props.onAddNotebookSuccess() - } - else{ - message.error(response.message); - } - }; - - const getConnectionOptions = (field) => { - let optionElements = []; - connections.forEach(connection => { - if(!field.filter || (field.filter && field.filter.indexOf(connection.connectionType) !== -1)){ - optionElements.push( - - ) - } - }) - return optionElements - } - - const renderInputType = (field) => { - switch(field.type) { - case 'text': - return ; - case 'connectionSelect': - return - case 'datasetSelector': - return - case 'sql': - return ; - default: - return ; - } - } - - let notebookFormElements = [] - if(selectedNotebookTemplate.id){ - selectedNotebookTemplate.formJson.fields.forEach(field => { - notebookFormElements.push( -
- - {renderInputType(field)} - -
- ) - } - ) - } - - - let addNotebookFormElement = ( -
-
-
setSelectedNotebookTemplate({})}> - - Back -
-

{selectedNotebookTemplate.name}

-
-
-
- {notebookFormElements} -
- - - -
-
- -
-
- ); - - // Code for rendering select connection type form - let selectNotebookTemplatElement = ( -
- {notebookTemplates.map((notebookTemplate, index) => ( -
- -
- ))} -
- ); - - return ( -
-
- {selectedNotebookTemplate.id ? ( -
- {addNotebookFormElement} -
- ) : ( - selectNotebookTemplatElement - )} -
-
- ); - } +import React, { useState, useEffect } from "react"; +import { Button, Form, Input, message, Select } from "antd"; +import AceEditor from "react-ace"; +import "ace-builds/src-noconflict/mode-mysql"; +import "ace-builds/src-noconflict/theme-chrome"; + +import style from "./style.module.scss"; +import notebookService from "services/notebooks.js"; +import connectionService from "services/connection.js"; +import { Link } from "react-router-dom"; +import { LeftOutlined } from '@ant-design/icons'; +import DatasetSelector from "../DatasetSelector/DatasetSelector"; + +const { Option } = Select; + +export default function AddNotebook(props) { + const [notebookTemplates, setNotebookTemplates] = useState([]); + const [connections, setConnections] = useState([]); + const [selectedNotebookTemplate, setSelectedNotebookTemplate] = useState(''); + const [form] = Form.useForm(); + + useEffect(() => { + if (!notebookTemplates.length) { + fetchNotebookTemplates(); + } + if (!connections.length) { + fetchConnections(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const fetchNotebookTemplates = async () => { + const response = await notebookService.getNotebookTemplates() + setNotebookTemplates(response.data) + } + + const fetchConnections = async () => { + const response = await connectionService.getConnections(); + setConnections(response.data) + } + + const handleNotebookTemplateSelect = (notebookTemplate) => { + setSelectedNotebookTemplate(notebookTemplate) + } + + const addNotebookFormSubmit = async (values) => { + values["notebookTemplateId"] = selectedNotebookTemplate.id; + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.addNotebook(values, workspaceId) + if(response.success){ + props.onAddNotebookSuccess() + } + else{ + message.error(response.message); + } + }; + + const getConnectionOptions = (field) => { + let optionElements = []; + connections.forEach(connection => { + if(!field.filter || (field.filter && field.filter.indexOf(connection.connectionType) !== -1)){ + optionElements.push( + + ) + } + }) + return optionElements + } + + const renderInputType = (field) => { + switch(field.type) { + case 'text': + return ; + case 'connectionSelect': + return + case 'datasetSelector': + return + case 'sql': + return ; + default: + return ; + } + } + + let notebookFormElements = [] + if(selectedNotebookTemplate.id){ + selectedNotebookTemplate.formJson.fields.forEach(field => { + notebookFormElements.push( +
+ + {renderInputType(field)} + +
+ ) + } + ) + } + + + let addNotebookFormElement = ( +
+
+
setSelectedNotebookTemplate({})}> + + Back +
+

{selectedNotebookTemplate.name}

+
+
+
+ {notebookFormElements} +
+ + + +
+
+ +
+
+ ); + + // Code for rendering select connection type form + let selectNotebookTemplatElement = ( +
+ {notebookTemplates.map((notebookTemplate, index) => ( +
+ +
+ ))} +
+ ); + + return ( +
+
+ {selectedNotebookTemplate.id ? ( +
+ {addNotebookFormElement} +
+ ) : ( + selectNotebookTemplatElement + )} +
+
+ ); + } diff --git a/ui/src/components/Notebooks/ArchivedNotebookTable.js b/ui/src/components/Notebooks/ArchivedNotebookTable.js index a19134e0..fd8f6fa6 100644 --- a/ui/src/components/Notebooks/ArchivedNotebookTable.js +++ b/ui/src/components/Notebooks/ArchivedNotebookTable.js @@ -1,124 +1,127 @@ -import React, { useState, useEffect } from "react"; -import notebookService from "services/notebooks.js"; -import style from "./style.module.scss"; -import { - Table, - Tooltip, - message, -} from "antd"; -import { RetweetOutlined, DeleteOutlined, LoadingOutlined, BackwardFilled } from '@ant-design/icons'; - -export default function NotebookTable(props) { - const [notebooks, setNotebooks] = useState([]); - const [loading, setLoading] = useState([]); - const [unarchivingNotebook, setUnarchivingNotebook] = useState(false); - - useEffect(() => { - if (!notebooks.length) { - getNotebooks(); - } - - const refreshNotebookInterval = setInterval(() => { - refreshNotebooks() - }, 3000); - - return () => { - clearInterval(refreshNotebookInterval); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const getNotebooks = async () => { - setLoading(true) - const response = await notebookService.getArchivedNotebooks(); - if(response){ - setNotebooks(response); - } - setLoading(false) - }; - - const refreshNotebooks = async () => { - const response = await notebookService.getArchivedNotebooks(); - if(response){ - setNotebooks(response); - } - }; - - const unarchiveNotebook = async (notebook) => { - setUnarchivingNotebook(notebook.id) - const response = await notebookService.unarchiveNotebook(notebook.id, notebook.path.split("/")[2]) - if(response.success){ - message.success("Notebook " + notebook.path.split("/")[2] + " removed from archive") - refreshNotebooks() - } - else{ - message.error(response.message) - setUnarchivingNotebook(false) - } - } - - const deleteNotebook = async (notebook) => { - const response = await notebookService.deleteNotebook(notebook.id) - if(response.success){ - message.success("Notebook " + notebook.name.substring(1) + " deleted successfully"); - refreshNotebooks() - } - else{ - message.error(response.message) - } - } - - const columns = [ - { - title: "Archived Notebooks", - dataIndex: "path", - key: "path", - width: "20%", - render: text => { - return ( - - {text.split("/")[2]} - - ); - } - }, - { - title: "", - dataIndex: "id", - key: "id", - width: "20%", - render: (text, notebook) => { - return ( -
- {unarchivingNotebook && unarchivingNotebook === notebook.id ? - - : - - unarchiveNotebook(notebook)} /> - - } - - deleteNotebook(notebook)} /> - -
- ); - } - } - ] - - return ( - <> - false} className={style.notebookLinkText} onClick={() => props.hideArchivedNotebooksTable() }> Notebooks - handleTableChange(event)} - /> - - ) - +import React, { useState, useEffect } from "react"; +import notebookService from "services/notebooks.js"; +import style from "./style.module.scss"; +import { + Table, + Tooltip, + message, +} from "antd"; +import { RetweetOutlined, DeleteOutlined, LoadingOutlined, BackwardFilled } from '@ant-design/icons'; + +export default function NotebookTable(props) { + const [notebooks, setNotebooks] = useState([]); + const [loading, setLoading] = useState([]); + const [unarchivingNotebook, setUnarchivingNotebook] = useState(false); + + useEffect(() => { + if (!notebooks.length) { + getNotebooks(); + } + + const refreshNotebookInterval = setInterval(() => { + refreshNotebooks() + }, 3000); + + return () => { + clearInterval(refreshNotebookInterval); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const getNotebooks = async () => { + setLoading(true) + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.getArchivedNotebooks(workspaceId); + if(response){ + setNotebooks(response); + } + setLoading(false) + }; + + const refreshNotebooks = async () => { + const response = await notebookService.getArchivedNotebooks(); + if(response){ + setNotebooks(response); + } + }; + + const unarchiveNotebook = async (notebook) => { + setUnarchivingNotebook(notebook.id) + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.unarchiveNotebook(notebook.id, notebook.path.split("/")[2], workspaceId) + if(response.success){ + message.success("Notebook " + notebook.path.split("/")[2] + " removed from archive") + refreshNotebooks() + } + else{ + message.error(response.message) + setUnarchivingNotebook(false) + } + } + + const deleteNotebook = async (notebook) => { + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.deleteNotebook(notebook.id, workspaceId) + if(response.success){ + message.success("Notebook " + notebook.name.substring(1) + " deleted successfully"); + refreshNotebooks() + } + else{ + message.error(response.message) + } + } + + const columns = [ + { + title: "Archived Notebooks", + dataIndex: "path", + key: "path", + width: "20%", + render: text => { + return ( + + {text.split("/")[2]} + + ); + } + }, + { + title: "", + dataIndex: "id", + key: "id", + width: "20%", + render: (text, notebook) => { + return ( +
+ {unarchivingNotebook && unarchivingNotebook === notebook.id ? + + : + + unarchiveNotebook(notebook)} /> + + } + + deleteNotebook(notebook)} /> + +
+ ); + } + } + ] + + return ( + <> + false} className={style.notebookLinkText} onClick={() => props.hideArchivedNotebooksTable() }> Notebooks +
handleTableChange(event)} + /> + + ) + } \ No newline at end of file diff --git a/ui/src/components/Notebooks/EditNotebook.js b/ui/src/components/Notebooks/EditNotebook.js index 585609cc..ec6d781d 100644 --- a/ui/src/components/Notebooks/EditNotebook.js +++ b/ui/src/components/Notebooks/EditNotebook.js @@ -1,180 +1,182 @@ -import React, { useState, useEffect } from "react"; -import { Button, Form, Input, message, Select, Popconfirm } from "antd"; -import AceEditor from "react-ace"; -import "ace-builds/src-noconflict/mode-mysql"; -import "ace-builds/src-noconflict/theme-chrome"; - -import style from "./style.module.scss"; -import notebookService from "services/notebooks.js"; -import connectionService from "services/connection.js"; -import { Link } from "react-router-dom"; -import DatasetSelector from "../DatasetSelector/DatasetSelector"; - -const { Option } = Select; - -export default function EditNotebook(props) { - const [connections, setConnections] = useState([]); - const [selectedNotebook, setSelectedNotebook] = useState(null); - const [form] = Form.useForm(); - - useEffect(() => { - if (!selectedNotebook) { - fetchNotebookObject(props.notebookObjId); - } - if (!connections.length) { - fetchConnections(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchNotebookObject = async (notebookObjId) => { - const response = await notebookService.getNotebookObject(notebookObjId) - setSelectedNotebook(response) - } - - const fetchConnections = async () => { - const response = await connectionService.getConnections(); - setConnections(response.data) - } - - const editNotebookFormSubmit = async (values) => { - values["notebookObjId"] = props.notebookObjId - const response = await notebookService.editNotebook(values) - if(response.success){ - props.onAddNotebookSuccess() - } - else{ - message.error(response.message); - } - }; - - const getConnectionOptions = (field) => { - let optionElements = []; - connections.forEach(connection => { - if(!field.filter || (field.filter && field.filter.indexOf(connection.connectionType) !== -1)){ - optionElements.push( - - ) - } - }) - return optionElements - } - - const renderInputType = (field, initValForDatasetSelector) => { - switch(field.type) { - case 'text': - return ; - case 'connectionSelect': - return - case 'datasetSelector': - return - case 'sql': - return ; - default: - return ; - } - } - - let notebookFormElements = [] - let defaultNotebookName = "" - if(selectedNotebook){ - defaultNotebookName = selectedNotebook.defaultPayload.name - selectedNotebook.notebookTemplate.formJson.fields.forEach(field => { - let fieldInitValue = field.type === "datasetSelector" ? undefined : selectedNotebook.defaultPayload[field.name] - let initValForDatasetSelector = field.type === "datasetSelector" ? JSON.parse(selectedNotebook.defaultPayload[field.name]).spec.ioConfig.inputSource.prefixes[0] : null - notebookFormElements.push( -
- - {renderInputType(field, initValForDatasetSelector)} - -
- ) - } - ) - } - - - let editNotebookFormElement = ( -
-
-
- {notebookFormElements} -
- - - -
-
- - - - - -
- ); - - return ( -
-
-
- {selectedNotebook ? editNotebookFormElement : null} -
-
-
- ); - } +import React, { useState, useEffect } from "react"; +import { Button, Form, Input, message, Select, Popconfirm } from "antd"; +import AceEditor from "react-ace"; +import "ace-builds/src-noconflict/mode-mysql"; +import "ace-builds/src-noconflict/theme-chrome"; + +import style from "./style.module.scss"; +import notebookService from "services/notebooks.js"; +import connectionService from "services/connection.js"; +import { Link } from "react-router-dom"; +import DatasetSelector from "../DatasetSelector/DatasetSelector"; + +const { Option } = Select; + +export default function EditNotebook(props) { + const [connections, setConnections] = useState([]); + const [selectedNotebook, setSelectedNotebook] = useState(null); + const [form] = Form.useForm(); + + useEffect(() => { + if (!selectedNotebook) { + fetchNotebookObject(props.notebookObjId); + } + if (!connections.length) { + fetchConnections(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const fetchNotebookObject = async (notebookObjId) => { + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.getNotebookObject(notebookObjId, workspaceId) + setSelectedNotebook(response) + } + + const fetchConnections = async () => { + const response = await connectionService.getConnections(); + setConnections(response.data) + } + + const editNotebookFormSubmit = async (values) => { + values["notebookObjId"] = props.notebookObjId + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.editNotebook(values, workspaceId) + if(response.success){ + props.onAddNotebookSuccess() + } + else{ + message.error(response.message); + } + }; + + const getConnectionOptions = (field) => { + let optionElements = []; + connections.forEach(connection => { + if(!field.filter || (field.filter && field.filter.indexOf(connection.connectionType) !== -1)){ + optionElements.push( + + ) + } + }) + return optionElements + } + + const renderInputType = (field, initValForDatasetSelector) => { + switch(field.type) { + case 'text': + return ; + case 'connectionSelect': + return + case 'datasetSelector': + return + case 'sql': + return ; + default: + return ; + } + } + + let notebookFormElements = [] + let defaultNotebookName = "" + if(selectedNotebook){ + defaultNotebookName = selectedNotebook.defaultPayload.name + selectedNotebook.notebookTemplate.formJson.fields.forEach(field => { + let fieldInitValue = field.type === "datasetSelector" ? undefined : selectedNotebook.defaultPayload[field.name] + let initValForDatasetSelector = field.type === "datasetSelector" ? JSON.parse(selectedNotebook.defaultPayload[field.name]).spec.ioConfig.inputSource.prefixes[0] : null + notebookFormElements.push( +
+ + {renderInputType(field, initValForDatasetSelector)} + +
+ ) + } + ) + } + + + let editNotebookFormElement = ( +
+
+
+ {notebookFormElements} +
+ + + +
+
+ + + + + +
+ ); + + return ( +
+
+
+ {selectedNotebook ? editNotebookFormElement : null} +
+
+
+ ); + } diff --git a/ui/src/components/Notebooks/NotebookTable.js b/ui/src/components/Notebooks/NotebookTable.js index 18e0ca17..ccd6055f 100644 --- a/ui/src/components/Notebooks/NotebookTable.js +++ b/ui/src/components/Notebooks/NotebookTable.js @@ -1,661 +1,667 @@ -import React, { useState, useEffect, useRef } from "react"; -import TimeAgo from 'react-timeago'; -import _ from "lodash"; -import notebookService from "services/notebooks.js"; -import style from "./style.module.scss"; -import { useHistory } from "react-router-dom"; -import { - Table, - Button, - Input, - Tooltip, - Popover, - message, - Drawer, - Popconfirm, - Menu, - Dropdown -} from "antd"; -import { MoreOutlined, PlayCircleOutlined, UnorderedListOutlined, StopOutlined, FileTextOutlined, DeleteOutlined, CopyOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons'; -import NotebookRunLogs from "./NotebookRunLogs.js" -import AddNotebook from "./AddNotebook.js" -import EditNotebook from "./EditNotebook.js" -import SelectSchedule from "components/Schedule/selectSchedule" -import { RUNNING, ABORT, SUCCESS, ERROR, PENDING } from "./constants"; -import { timehumanize } from 'services/general'; -import ArchivedNotebookTable from "components/Notebooks/ArchivedNotebookTable.js"; - -var moment = require("moment"); - -const {Search} = Input - -export default function NotebookTable() { - - const [sorter, setSorter] = useState({}); - const [limit] = useState(25); - const [searchText, setSearchText] = useState(''); - const [filter, setFilter] = useState({}); - const [notebooks, setNotebooks] = useState([]); - const [podsDriver, setpodsDriver] = useState([]); - const [loading, setLoading] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const [selectedNotebook, setSelectedNotebook] = useState(''); - const [runLogNotebook, setRunLogNotebook] = useState(''); - const [isRunLogsDrawerVisible, setIsRunLogsDrawerVisible] = useState(false); - const [isNewNotebookDrawerVisible, setIsNewNotebookDrawerVisible] = useState(false); - const [editNotebookDrawerId, setEditNotebookDrawer] = useState(null); - const [isArchivedNotebookVisible, setIsArchivedNotebookVisible] = useState(null); - const [isNotebooksRequestCompleted, setisNotebooksRequestCompleted] = useState(true); - const [isDriverStatusRequestCompleted, setisDriverStatusRequestCompleted] = useState(true); - const history = useHistory(); - const currentPageRef = useRef(currentPage); - currentPageRef.current = currentPage; - const sorterRef = useRef(sorter); - sorterRef.current = sorter; - const searchTextRef = useRef(searchText); - searchTextRef.current = searchText; - const filterRef = useRef(filter); - filterRef.current = filter; - const isNotebooksRequestCompletedRef = useRef(isNotebooksRequestCompleted); - isNotebooksRequestCompletedRef.current = isNotebooksRequestCompleted; - const isDriverStatusRequestCompletedRef = useRef(isDriverStatusRequestCompleted); - isDriverStatusRequestCompletedRef.current = isDriverStatusRequestCompleted; - - useEffect(() => { - if (!notebooks.length) { - getNotebooks(0); - refreshDriverStatus(); - } - - const refreshNotebookInterval = setInterval(() => { - refreshNotebooks() - }, 5000); - - const refreshPodStatus = setInterval(() => { - refreshDriverStatus() - }, 5000); - - return () => { - clearInterval(refreshNotebookInterval); - clearInterval(refreshPodStatus); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const getNotebooks = async (offset) => { - setLoading(true) - const response = await notebookService.getNotebooks(offset, limit, searchTextRef.current, sorterRef.current, filterRef.current); - if(response){ - setNotebooks(response); - } - setLoading(false) - if(!offset) setCurrentPage(1) - }; - - const refreshNotebooks = async (searchText = searchTextRef.current, sorter = sorterRef.current, filter = filterRef.current, currentPage = currentPageRef.current) => { - if(currentPageRef.current && isNotebooksRequestCompletedRef.current){ - setisNotebooksRequestCompleted(false); - const response = await notebookService.getNotebooks((currentPage - 1)*limit, limit, searchText, sorter, filter); - setisNotebooksRequestCompleted(true); - if(response){ - setNotebooks(response); - } - } - }; - - const refreshDriverStatus = async () => { - if(isDriverStatusRequestCompletedRef.current){ - setisDriverStatusRequestCompleted(false); - const response = await notebookService.getDriverAndExecutorStatus(); - setisDriverStatusRequestCompleted(true); - if(response){ - setpodsDriver(response) - } - } - }; - const showScheduleDropDown = (notebookId) => { - setSelectedNotebook(notebookId) - } - - - const addNotebookSchedule = async (selectedSchedule) => { - if(selectedSchedule && selectedNotebook && selectedSchedule !== -1){ - const response = await notebookService.addNotebookSchedule(selectedNotebook, selectedSchedule); - if(response.success){ - message.success(response.message) - } - else{ - message.error(response.message) - } - setSelectedNotebook(null) - getNotebooks((currentPage - 1)*limit) - } - else{ - alert('Schedule not selected') - } - } - - const handleTableChange = (event, filter, sorter) => { - setSorter({columnKey: sorter.columnKey, order: sorter.order}) - setFilter(filter) - setCurrentPage(event.current) - refreshNotebooks(searchText, sorter, filter, event.current) - } - - const navigateToNotebook = (record) => { - if(history.location.pathname.indexOf("api/redirect") !== -1) - history.push("/api/redirect/cuelake/notebook/" + record.id); - else - history.push("/notebook/" + record.id); - } - - const openRunLogs = (notebook) => { - setRunLogNotebook(notebook) - setIsRunLogsDrawerVisible(true) - } - - const closeRunLogsDrawer = () => { - setIsRunLogsDrawerVisible(false) - } - - const unassignSchedule = async (notebookId) => { - const response = await notebookService.unassignSchedule(notebookId); - if(response.success){ - refreshNotebooks() - } - else{ - message.error(response.message) - } - } - - const runNotebook = async (notebook) => { - const response = await notebookService.runNotebook(notebook.id) - if(response.success) - message.success("Notebook " + notebook.name.substring(1) + " ran successfully") - else{ - message.error(response.message) - } - } - - const stopNotebook = async (notebook) => { - const response = await notebookService.stopNotebook(notebook.id) - if(response.success) - message.success("Notebook " + notebook.name.substring(1) + " stopped successfully") - else{ - message.error(response.message) - } - } - - const cloneNotebook = async (notebook) => { - const response = await notebookService.cloneNotebook(notebook.id, notebook.name.substring(1) + " Copy") - if(response.success){ - message.success("Notebook " + notebook.name.substring(1) + " cloned successfully") - refreshNotebooks() - } - else{ - message.error(response.message) - } - } - - const archiveNotebook = async (notebook) => { - const response = await notebookService.archiveNotebook(notebook.id, notebook.name.substring(1)) - if(response.success){ - message.success("Notebook " + notebook.name.substring(1) + " moved to archive") - refreshNotebooks() - } - else{ - message.error(response.message) - } - } - - const deleteNotebook = async (notebook) => { - const response = await notebookService.deleteNotebook(notebook.id) - if(response.success){ - message.success("Notebook " + notebook.name.substring(1) + " deleted successfully"); - refreshNotebooks() - } - else{ - message.error(response.message) - } - } - - const showArchivedNotebookTable = () => { - setIsArchivedNotebookVisible(true) - } - - const hideArchivedNotebooksTable = () => { - setIsArchivedNotebookVisible(false) - } - - - const closeNewNotebookDrawer = () => { - setIsNewNotebookDrawerVisible(false) - } - - const openNewNotebookDrawer = () => { - setIsNewNotebookDrawerVisible(true) - } - - const closeEditNotebookDrawer = () => { - setEditNotebookDrawer(null) - } - - const openEditNotebookDrawer = notebookObjId => { - setEditNotebookDrawer(notebookObjId) - } - - const onAddNotebookSuccess = () => { - refreshNotebooks() - closeNewNotebookDrawer() - closeEditNotebookDrawer() - } - - - const search = value => { - setSearchText(value) - refreshNotebooks(value, sorter, filter, currentPage) - }; - - const columns = [ - { - title: "Notebook", - dataIndex: "name", - key: "name", - width: "20%", - sorter: ()=>{}, - sortOrder: sorter.columnKey === 'name' && sorter.order, - ellipsis: true, - - render: text => { - return ( - - {text.substring(1)} - - ); - } - }, - { - title: "Schedule", - dataIndex: "schedule", - key: "schedule", - width: "10%", - sorter: ()=>{}, - sortOrder: sorter.columnKey === 'schedule' && sorter.order, - ellipsis: true, - render: (schedule, notebook) => { - if(schedule && selectedNotebook !== notebook.id){ - return ( - <> -
- {schedule} - - unassignSchedule(notebook.id)}> - -
- - ) - } - else{ - return ( - <> - { - - selectedNotebook === notebook.id ? - - : - false} className={style.linkText} onClick={()=>showScheduleDropDown(notebook.id)}>Assign Schedule - } - - ); - } - } - }, - - { - title: "Workflow", - dataIndex: "assignedWorkflow", - key: "assignedWorkflow", - assign:"left", - - sorter: ()=>{}, - sortOrder: sorter.columnKey === 'assignedWorkflow' && sorter.order, - ellipsis: true, - width: "10%", - render: (text,record) => { - var listIndividuals = record.assignedWorkflow.map(e => { - return ( - - {e} - - ); - }); - return ( -
- {listIndividuals} -
- ) - } - }, - { - title: "Latest Run", - dataIndex: "lastRun", - key: "lastRun1", - width: "10%", - sorter: ()=>{}, - sortOrder: sorter.columnKey === 'lastRun1' && sorter.order, - ellipsis: true, - render: lastRun => { - let timeDiff; - if (lastRun && lastRun.startTimestamp && lastRun.endTimestamp){ - timeDiff = Math.round((new Date(lastRun.endTimestamp) - new Date(lastRun.startTimestamp))/1000) - - } - let diff; - if (timeDiff){ - diff = moment.duration(timeDiff, "second").format("h [hrs] m [min] s [sec]", { - trim: "both" - }); - } - if(diff){ - diff = timehumanize(diff.split(" ")) - } - - - let item = ( -
- {lastRun ? : null} -
- {diff} -
-
- ) - return ( - - {item} - - ); - } - }, - { - title: "Latest Run Status", - dataIndex: "lastRun", - key: "lastRun2", - width: "11%", - filters: [ - { text: 'ERROR', value: 'ERROR' }, - { text: 'RUNNING', value: 'RUNNING' }, - { text: 'QUEUED', value: 'QUEUED' }, - { text: 'ABORT', value: 'ABORT' }, - { text: 'SUCCESS', value: 'SUCCESS' } - ], - ellipsis: true, - render: (lastRun) => { - return ( - - {lastRun ? lastRun.status : ""} - - ); - } - }, - { - title: "Last Run", - dataIndex: "lastRun", - key: "lastRun", - width: "15%", - render: (lastRun, notebook) => { - let lastRunStatusElement = null - if(lastRun && lastRun.logsJSON && lastRun.logsJSON.paragraphs){ - let paragraphs = lastRun.logsJSON.paragraphs - let lastRunStatusChildElements = [] - const paragraphPercent = 100/(paragraphs.length) - paragraphs.forEach(paragraph => { - let paragraphClassName = "" - let paragraphStatus = "" - if(paragraph.hasOwnProperty('results')){ - if(paragraph["results"].hasOwnProperty("code")){ - paragraphStatus = paragraph["results"]["code"] - } - } - if(paragraphStatus === SUCCESS) paragraphClassName = "bg-green-500"; - else if(paragraph.status === ERROR) paragraphClassName = "bg-red-500"; - else if(paragraph.status === RUNNING) paragraphClassName = "bg-blue-400"; - else if(paragraph.status === PENDING) paragraphClassName = "bg-blue-300"; - else if(paragraph.status === ABORT) paragraphClassName = "bg-yellow-500"; - let content = -
- {paragraph.title ?

{paragraph.title}

: null} - {paragraph.dateStarted ?

Start Time: {paragraph.dateStarted}

: null} - {paragraph.dateFinished ?

End Time: {paragraph.dateFinished}

: null} - {paragraph.status ?

Status: {paragraph.status}

: null} - {paragraph.progress ?

Progress: {paragraph.progress}

: null} -
- lastRunStatusChildElements.push( - -
-
-
- ) - }) - lastRunStatusElement =
- {lastRunStatusChildElements} -
- } - return ( - lastRunStatusElement - ); - } - }, - { - title: "", - dataIndex: "", - key: "", - width: "10%", - render: (notebook, text) => { - const menu = ( - cloneNotebook(notebook)} > - - Clone Notebook - - - archiveNotebook(notebook)} > - {" "} - Archive Notebook - - - - deleteNotebook(notebook)} - okText="Yes" - cancelText="No" - > - - Delete Notebook - - - - openRunLogs(notebook)} > - - View Run Logs - - - ) - return ( -
- { notebook.notebookObjId ? - - openEditNotebookDrawer(notebook.notebookObjId)} /> - - : - null - } - { notebook.lastRun && (notebook.lastRun.status === RUNNING || notebook.lastRun.status === PENDING) - ? - - stopNotebook(notebook)} /> - - : - - runNotebook(notebook)} /> - - } - - navigateToNotebook(notebook)} /> - - - - - - -
- ); - } - } - ]; - - let runningDrivers = podsDriver.runningDrivers - let pendingDrivers = podsDriver.pendingDrivers - let runningExecutors = podsDriver.runningExecutors - let pendingExecutors = podsDriver.pendingExecutors - let drivers = [] - _.times(runningDrivers, (i) => { - drivers.push(); - }); - _.times(pendingDrivers, (i) => { - drivers.push(); - }); - - let executors = [] - - _.times(runningExecutors, (i) => { - executors.push(); - }); - _.times(pendingExecutors, (i) => { - executors.push(); - }); - - return ( - <> - - {/* Header */} - { - isArchivedNotebookVisible ? - - : - <> -
- - -
-
Drivers : {drivers.length > 0 ? drivers : 0 }
-
Executors : {executors.length > 0 ? executors : 0}
-
-
- -
-
- - {/* Table */} -
- - } - - - {/* Drawer Components */} - - - - } - > - { isRunLogsDrawerVisible - ? - - : - null - } - - - { isNewNotebookDrawerVisible - ? - - : - null - } - - - { editNotebookDrawerId - ? - - : - null - } - - - ); -} +import React, { useState, useEffect, useRef } from "react"; +import TimeAgo from 'react-timeago'; +import _ from "lodash"; +import notebookService from "services/notebooks.js"; +import style from "./style.module.scss"; +import { useHistory } from "react-router-dom"; +import { + Table, + Button, + Input, + Tooltip, + Popover, + message, + Drawer, + Popconfirm, + Menu, + Dropdown +} from "antd"; +import { MoreOutlined, PlayCircleOutlined, UnorderedListOutlined, StopOutlined, FileTextOutlined, DeleteOutlined, CopyOutlined, CloseOutlined, EditOutlined } from '@ant-design/icons'; +import NotebookRunLogs from "./NotebookRunLogs.js" +import AddNotebook from "./AddNotebook.js" +import EditNotebook from "./EditNotebook.js" +import SelectSchedule from "components/Schedule/selectSchedule" +import { RUNNING, ABORT, SUCCESS, ERROR, PENDING } from "./constants"; +import { timehumanize } from 'services/general'; +import ArchivedNotebookTable from "components/Notebooks/ArchivedNotebookTable.js"; + +var moment = require("moment"); + +const {Search} = Input + +export default function NotebookTable() { + + const [sorter, setSorter] = useState({}); + const [limit] = useState(25); + const [searchText, setSearchText] = useState(''); + const [filter, setFilter] = useState({}); + const [notebooks, setNotebooks] = useState([]); + const [podsDriver, setpodsDriver] = useState([]); + const [loading, setLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [selectedNotebook, setSelectedNotebook] = useState(''); + const [runLogNotebook, setRunLogNotebook] = useState(''); + const [isRunLogsDrawerVisible, setIsRunLogsDrawerVisible] = useState(false); + const [isNewNotebookDrawerVisible, setIsNewNotebookDrawerVisible] = useState(false); + const [editNotebookDrawerId, setEditNotebookDrawer] = useState(null); + const [isArchivedNotebookVisible, setIsArchivedNotebookVisible] = useState(null); + const [isNotebooksRequestCompleted, setisNotebooksRequestCompleted] = useState(true); + const [isDriverStatusRequestCompleted, setisDriverStatusRequestCompleted] = useState(true); + const history = useHistory(); + const currentPageRef = useRef(currentPage); + currentPageRef.current = currentPage; + const sorterRef = useRef(sorter); + sorterRef.current = sorter; + const searchTextRef = useRef(searchText); + searchTextRef.current = searchText; + const filterRef = useRef(filter); + filterRef.current = filter; + const isNotebooksRequestCompletedRef = useRef(isNotebooksRequestCompleted); + isNotebooksRequestCompletedRef.current = isNotebooksRequestCompleted; + const isDriverStatusRequestCompletedRef = useRef(isDriverStatusRequestCompleted); + isDriverStatusRequestCompletedRef.current = isDriverStatusRequestCompleted; + + useEffect(() => { + if (!notebooks.length) { + getNotebooks(0); + refreshDriverStatus(); + } + + const refreshNotebookInterval = setInterval(() => { + refreshNotebooks() + }, 5000); + + const refreshPodStatus = setInterval(() => { + refreshDriverStatus() + }, 5000); + + return () => { + clearInterval(refreshNotebookInterval); + clearInterval(refreshPodStatus); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const getNotebooks = async (offset) => { + setLoading(true) + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.getNotebooks(offset, limit, searchTextRef.current, sorterRef.current, filterRef.current, workspaceId); + if(response){ + setNotebooks(response); + } + setLoading(false) + if(!offset) setCurrentPage(1) + }; + + const refreshNotebooks = async (searchText = searchTextRef.current, sorter = sorterRef.current, filter = filterRef.current, currentPage = currentPageRef.current) => { + if(currentPageRef.current && isNotebooksRequestCompletedRef.current){ + setisNotebooksRequestCompleted(false); + const response = await notebookService.getNotebooks((currentPage - 1)*limit, limit, searchText, sorter, filter); + setisNotebooksRequestCompleted(true); + if(response){ + setNotebooks(response); + } + } + }; + + const refreshDriverStatus = async () => { + if(isDriverStatusRequestCompletedRef.current){ + setisDriverStatusRequestCompleted(false); + const response = await notebookService.getDriverAndExecutorStatus(); + setisDriverStatusRequestCompleted(true); + if(response){ + setpodsDriver(response) + } + } + }; + const showScheduleDropDown = (notebookId) => { + setSelectedNotebook(notebookId) + } + + + const addNotebookSchedule = async (selectedSchedule) => { + if(selectedSchedule && selectedNotebook && selectedSchedule !== -1){ + const response = await notebookService.addNotebookSchedule(selectedNotebook, selectedSchedule); + if(response.success){ + message.success(response.message) + } + else{ + message.error(response.message) + } + setSelectedNotebook(null) + getNotebooks((currentPage - 1)*limit) + } + else{ + alert('Schedule not selected') + } + } + + const handleTableChange = (event, filter, sorter) => { + setSorter({columnKey: sorter.columnKey, order: sorter.order}) + setFilter(filter) + setCurrentPage(event.current) + refreshNotebooks(searchText, sorter, filter, event.current) + } + + const navigateToNotebook = (record) => { + if(history.location.pathname.indexOf("api/redirect") !== -1) + history.push("/api/redirect/cuelake/notebook/" + record.id); + else + history.push("/notebook/" + record.id); + } + + const openRunLogs = (notebook) => { + setRunLogNotebook(notebook) + setIsRunLogsDrawerVisible(true) + } + + const closeRunLogsDrawer = () => { + setIsRunLogsDrawerVisible(false) + } + + const unassignSchedule = async (notebookId) => { + const response = await notebookService.unassignSchedule(notebookId); + if(response.success){ + refreshNotebooks() + } + else{ + message.error(response.message) + } + } + + const runNotebook = async (notebook) => { + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.runNotebook(notebook.id, workspaceId) + if(response.success) + message.success("Notebook " + notebook.name.substring(1) + " ran successfully") + else{ + message.error(response.message) + } + } + + const stopNotebook = async (notebook) => { + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.stopNotebook(notebook.id, workspaceId) + if(response.success) + message.success("Notebook " + notebook.name.substring(1) + " stopped successfully") + else{ + message.error(response.message) + } + } + + const cloneNotebook = async (notebook) => { + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.cloneNotebook(notebook.id, notebook.name.substring(1) + " Copy", workspaceId) + if(response.success){ + message.success("Notebook " + notebook.name.substring(1) + " cloned successfully") + refreshNotebooks() + } + else{ + message.error(response.message) + } + } + + const archiveNotebook = async (notebook) => { + let WorkspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.archiveNotebook(notebook.id, notebook.name.substring(1), WorkspaceId) + if(response.success){ + message.success("Notebook " + notebook.name.substring(1) + " moved to archive") + refreshNotebooks() + } + else{ + message.error(response.message) + } + } + + const deleteNotebook = async (notebook) => { + let WorkspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await notebookService.deleteNotebook(notebook.id, WorkspaceId) + if(response.success){ + message.success("Notebook " + notebook.name.substring(1) + " deleted successfully"); + refreshNotebooks() + } + else{ + message.error(response.message) + } + } + + const showArchivedNotebookTable = () => { + setIsArchivedNotebookVisible(true) + } + + const hideArchivedNotebooksTable = () => { + setIsArchivedNotebookVisible(false) + } + + + const closeNewNotebookDrawer = () => { + setIsNewNotebookDrawerVisible(false) + } + + const openNewNotebookDrawer = () => { + setIsNewNotebookDrawerVisible(true) + } + + const closeEditNotebookDrawer = () => { + setEditNotebookDrawer(null) + } + + const openEditNotebookDrawer = notebookObjId => { + setEditNotebookDrawer(notebookObjId) + } + + const onAddNotebookSuccess = () => { + refreshNotebooks() + closeNewNotebookDrawer() + closeEditNotebookDrawer() + } + + + const search = value => { + setSearchText(value) + refreshNotebooks(value, sorter, filter, currentPage) + }; + + const columns = [ + { + title: "Notebook", + dataIndex: "name", + key: "name", + width: "20%", + sorter: ()=>{}, + sortOrder: sorter.columnKey === 'name' && sorter.order, + ellipsis: true, + + render: text => { + return ( + + {text.substring(1)} + + ); + } + }, + { + title: "Schedule", + dataIndex: "schedule", + key: "schedule", + width: "10%", + sorter: ()=>{}, + sortOrder: sorter.columnKey === 'schedule' && sorter.order, + ellipsis: true, + render: (schedule, notebook) => { + if(schedule && selectedNotebook !== notebook.id){ + return ( + <> +
+ {schedule} + + unassignSchedule(notebook.id)}> + +
+ + ) + } + else{ + return ( + <> + { + + selectedNotebook === notebook.id ? + + : + false} className={style.linkText} onClick={()=>showScheduleDropDown(notebook.id)}>Assign Schedule + } + + ); + } + } + }, + + { + title: "Workflow", + dataIndex: "assignedWorkflow", + key: "assignedWorkflow", + assign:"left", + + sorter: ()=>{}, + sortOrder: sorter.columnKey === 'assignedWorkflow' && sorter.order, + ellipsis: true, + width: "10%", + render: (text,record) => { + var listIndividuals = record.assignedWorkflow.map(e => { + return ( + + {e} + + ); + }); + return ( +
+ {listIndividuals} +
+ ) + } + }, + { + title: "Latest Run", + dataIndex: "lastRun", + key: "lastRun1", + width: "10%", + sorter: ()=>{}, + sortOrder: sorter.columnKey === 'lastRun1' && sorter.order, + ellipsis: true, + render: lastRun => { + let timeDiff; + if (lastRun && lastRun.startTimestamp && lastRun.endTimestamp){ + timeDiff = Math.round((new Date(lastRun.endTimestamp) - new Date(lastRun.startTimestamp))/1000) + + } + let diff; + if (timeDiff){ + diff = moment.duration(timeDiff, "second").format("h [hrs] m [min] s [sec]", { + trim: "both" + }); + } + if(diff){ + diff = timehumanize(diff.split(" ")) + } + + + let item = ( +
+ {lastRun ? : null} +
+ {diff} +
+
+ ) + return ( + + {item} + + ); + } + }, + { + title: "Latest Run Status", + dataIndex: "lastRun", + key: "lastRun2", + width: "11%", + filters: [ + { text: 'ERROR', value: 'ERROR' }, + { text: 'RUNNING', value: 'RUNNING' }, + { text: 'QUEUED', value: 'QUEUED' }, + { text: 'ABORT', value: 'ABORT' }, + { text: 'SUCCESS', value: 'SUCCESS' } + ], + ellipsis: true, + render: (lastRun) => { + return ( + + {lastRun ? lastRun.status : ""} + + ); + } + }, + { + title: "Last Run", + dataIndex: "lastRun", + key: "lastRun", + width: "15%", + render: (lastRun, notebook) => { + let lastRunStatusElement = null + if(lastRun && lastRun.logsJSON && lastRun.logsJSON.paragraphs){ + let paragraphs = lastRun.logsJSON.paragraphs + let lastRunStatusChildElements = [] + const paragraphPercent = 100/(paragraphs.length) + paragraphs.forEach(paragraph => { + let paragraphClassName = "" + let paragraphStatus = "" + if(paragraph.hasOwnProperty('results')){ + if(paragraph["results"].hasOwnProperty("code")){ + paragraphStatus = paragraph["results"]["code"] + } + } + if(paragraphStatus === SUCCESS) paragraphClassName = "bg-green-500"; + else if(paragraph.status === ERROR) paragraphClassName = "bg-red-500"; + else if(paragraph.status === RUNNING) paragraphClassName = "bg-blue-400"; + else if(paragraph.status === PENDING) paragraphClassName = "bg-blue-300"; + else if(paragraph.status === ABORT) paragraphClassName = "bg-yellow-500"; + let content = +
+ {paragraph.title ?

{paragraph.title}

: null} + {paragraph.dateStarted ?

Start Time: {paragraph.dateStarted}

: null} + {paragraph.dateFinished ?

End Time: {paragraph.dateFinished}

: null} + {paragraph.status ?

Status: {paragraph.status}

: null} + {paragraph.progress ?

Progress: {paragraph.progress}

: null} +
+ lastRunStatusChildElements.push( + +
+
+
+ ) + }) + lastRunStatusElement =
+ {lastRunStatusChildElements} +
+ } + return ( + lastRunStatusElement + ); + } + }, + { + title: "", + dataIndex: "", + key: "", + width: "10%", + render: (notebook, text) => { + const menu = ( + cloneNotebook(notebook)} > + + Clone Notebook + + + archiveNotebook(notebook)} > + {" "} + Archive Notebook + + + + deleteNotebook(notebook)} + okText="Yes" + cancelText="No" + > + + Delete Notebook + + + + openRunLogs(notebook)} > + + View Run Logs + + + ) + return ( +
+ { notebook.notebookObjId ? + + openEditNotebookDrawer(notebook.notebookObjId)} /> + + : + null + } + { notebook.lastRun && (notebook.lastRun.status === RUNNING || notebook.lastRun.status === PENDING) + ? + + stopNotebook(notebook)} /> + + : + + runNotebook(notebook)} /> + + } + + navigateToNotebook(notebook)} /> + + + + + + +
+ ); + } + } + ]; + + let runningDrivers = podsDriver.runningDrivers + let pendingDrivers = podsDriver.pendingDrivers + let runningExecutors = podsDriver.runningExecutors + let pendingExecutors = podsDriver.pendingExecutors + let drivers = [] + _.times(runningDrivers, (i) => { + drivers.push(); + }); + _.times(pendingDrivers, (i) => { + drivers.push(); + }); + + let executors = [] + + _.times(runningExecutors, (i) => { + executors.push(); + }); + _.times(pendingExecutors, (i) => { + executors.push(); + }); + + return ( + <> + + {/* Header */} + { + isArchivedNotebookVisible ? + + : + <> +
+ + +
+
Drivers : {drivers.length > 0 ? drivers : 0 }
+
Executors : {executors.length > 0 ? executors : 0}
+
+
+ +
+
+ + {/* Table */} +
+ + } + + + {/* Drawer Components */} + + + + } + > + { isRunLogsDrawerVisible + ? + + : + null + } + + + { isNewNotebookDrawerVisible + ? + + : + null + } + + + { editNotebookDrawerId + ? + + : + null + } + + + ); +} diff --git a/ui/src/components/Sidebar/Sidebar.js b/ui/src/components/Sidebar/Sidebar.js index ae2830fd..4fe49233 100644 --- a/ui/src/components/Sidebar/Sidebar.js +++ b/ui/src/components/Sidebar/Sidebar.js @@ -1,117 +1,192 @@ -/*eslint-disable*/ -import React from "react"; -import { Link } from "react-router-dom"; -import style from "./style.module.scss"; - -export default function Sidebar(props) { - let urlPrefix = "" - if(props.isEmbedPage){ - urlPrefix = "/api/redirect/cuelake" - } - - const menuItems = [ - { - "label": "Notebooks", - "path": "/notebook", - "icon": "fa-file" - }, - { - "label": "Connections", - "path": "/connections", - "icon": "fa-database" - }, - { - "label": "Workflows", - "path": "/workflows", - "icon": "fa-tasks" - }, - { - "label": "Schedules", - "path": "/schedules", - "icon": "fa-calendar" - }, - { - "label": "Spark-UI", - "path": "/spark", - "icon": "fa-star" - }, - { - "label": "Settings", - "path": "/settings", - "icon": "fa-wrench" - } - ] - - let menuElements = [] - - menuItems.forEach(menuItem => { - menuElements.push( -
  • - - {" "} - - {menuItem.label} - - -
  • - ) - }) - - return ( - <> - - - ); -} +/*eslint-disable*/ +import React, { useState, useEffect } from "react"; +import { Select } from "antd"; +import { Link } from "react-router-dom"; +import style from "./style.module.scss"; +import workspaceService from "services/workspace.js"; + +const { Option } = Select; + +export default function Sidebar(props) { + const [workspaces, setWorkspaces] = useState(null); + const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(); + + useEffect(() => { + if (workspaces === null) { + fetchWorkspaces(); + } + }, []); + + const fetchWorkspaces = async () => { + const response = await workspaceService.getWorkspaces(); + setWorkspaces(response.data) + } + + const setSelectedWorkspace = (workspaceId) => { + localStorage.setItem('workspaceId', workspaceId); + setSelectedWorkspaceId(workspaceId) + switchWorkspaceServer(workspaceId) + } + + const switchWorkspaceServer = async (workspaceId) => { + const response = await workspaceService.switchWorkspaceServer(workspaceId); + } + + let urlPrefix = "" + if(props.isEmbedPage){ + urlPrefix = "/api/redirect/cuelake" + } + + let tempWorkspaceId = parseInt(localStorage.getItem("workspaceId")) + if(tempWorkspaceId && tempWorkspaceId !== selectedWorkspaceId){ + setSelectedWorkspaceId(tempWorkspaceId) + } + + const adminMenuItems = [ + { + "label": "Dashboard", + "path": "/dashboard", + "icon": "fa-file" + } + ] + + const menuItems = [ + { + "label": "Notebooks", + "path": "/notebook", + "icon": "fa-file" + }, + { + "label": "Connections", + "path": "/connections", + "icon": "fa-database" + }, + { + "label": "Workflows", + "path": "/workflows", + "icon": "fa-tasks" + }, + { + "label": "Schedules", + "path": "/schedules", + "icon": "fa-calendar" + }, + { + "label": "Spark-UI", + "path": "/spark", + "icon": "fa-star" + }, + { + "label": "Settings", + "path": "/settings", + "icon": "fa-wrench" + } + ] + + const generateMenuItem = (menuItem) => { + return
  • + + {" "} + + {menuItem.label} + + +
  • + } + + let menuElements = [] + menuItems.forEach(menuItem => { + menuElements.push( + generateMenuItem(menuItem) + ) + }) + + let adminMenuElements = [] + adminMenuItems.forEach(menuItem => { + adminMenuElements.push( + generateMenuItem(menuItem) + ) + }) + + let workspaceElements = [] + + workspaces && workspaces.forEach(workspace => { + workspaceElements.push( + + ) + }) + + return ( + <> + + + ); +} diff --git a/ui/src/components/Sidebar/style.module.scss b/ui/src/components/Sidebar/style.module.scss index 8aa677d0..4d15bdd5 100644 --- a/ui/src/components/Sidebar/style.module.scss +++ b/ui/src/components/Sidebar/style.module.scss @@ -1,25 +1,53 @@ -.embedNavBar{ - width: 4rem; - background: #fafafa; - - .navLink{ - display: inline-flex; - } - - .embedLabel{ - opacity: 0; - position: relative; - top: -2px; - margin-left: 5px; - white-space: nowrap; - } - - &:hover{ - width: 12rem; - transition: 200ms; - - .embedLabel{ - opacity: 1; - } - } +.embedNavBar{ + width: 4rem; + background: #fafafa; + + .navLink{ + display: inline-flex; + } + + .embedLabel{ + opacity: 0; + position: relative; + top: -2px; + margin-left: 5px; + white-space: nowrap; + } + + &:hover{ + width: 12rem; + transition: 200ms; + + .embedLabel{ + opacity: 1; + } + } +} +.element{ + display: flex; + position: relative; + justify-content: space-evenly; + flex-flow: column; + .workspaceElement{ + display: flex; + + justify-content: space-evenly; + flex-flow: row; + } +} + + +.subMenu{ + background: #f7f7f7; + padding: 10px; + border-radius: 4px; + width: calc(100% + 20px); + margin: 0 -10px; + margin-top: -10px; + + ul{ + margin-bottom: 0; + margin-top: 10px; + padding-left: 3px; + } } \ No newline at end of file diff --git a/ui/src/components/Workflows/Workflows.js b/ui/src/components/Workflows/Workflows.js index 46613e73..22c7c4ee 100644 --- a/ui/src/components/Workflows/Workflows.js +++ b/ui/src/components/Workflows/Workflows.js @@ -1,574 +1,578 @@ -import React, { useState, useEffect, useRef } from "react"; -import style from "./style.module.scss"; -import TimeAgo from 'react-timeago'; -import _ from "lodash"; -import { - Table, - Button, - Modal, - Input, - Select, - Tooltip, - Form, - message, - Drawer, - Tabs, - Menu, - Dropdown, - Popconfirm, - } from "antd"; -import { MoreOutlined, EditOutlined, PlayCircleOutlined, UnorderedListOutlined, StopOutlined, DeleteOutlined, CloseOutlined} from '@ant-design/icons'; -import { Badge } from "reactstrap"; -import WorkflowRuns from "./WorkflowRuns" -import SelectSchedule from "components/Schedule/selectSchedule" - -import workflowsService from "services/workflows"; -import notebookService from "services/notebooks"; -import { timehumanize } from 'services/general'; -import { STATUS_ALWAYS, STATUS_ERROR, STATUS_SUCCESS, STATUS_RUNNING, STATUS_RECEIVED } from "./constants" - -var moment = require("moment"); - -const { TabPane } = Tabs; -const { Option } = Select; - -export default function Workflows(props) { - - const [limit] = useState(25); - const [sortedInfo, setSortedInfo] = useState({}) - const [sortOrder, setSortOrder] = useState('') - const [sortColumn, setSortColumn] = useState(''); - const [workflows, setWorkflows] = useState([]); - const [loading, setLoading] = useState(false); - const [notebooksLight, setNotebooksLight] = useState([]) - const [totalWokflows, setTotal] = useState(''); - const [currentPage, setCurrentPage] = useState(''); - const [selectedWorkflow, setSelectedWorkflow] = useState(''); - const [selectedSchedule, setSelectedSchedule] = useState(null); - const [selectedNotebooks, setSelectedNotebooks] = useState([]); - const [isRunLogsDrawerVisible, setIsRunLogsDrawerVisible] = useState(false); - const [isEditCreateWorkflow, setIsEditCreateWorkflow] = useState(false); - const [newWorkflowName, setNewWorkflowName] = useState(''); - const [triggerWorkflow, setTriggerWorkflow] = useState(false); - const [triggerWorkflowStatus, setTriggerWorkflowStatus] = useState(STATUS_ALWAYS); - const [assignTriggerWorkflowForId, setAssignTriggerWorkflowForId] = useState(false) // stores id of parent workflow - const [assignSchedule, setAssignSchedule] = useState(false) - const [componentDidMount, setComponentDidMount] = useState(false) - const sortOrderRef = useRef(sortOrder); - sortOrderRef.current = sortOrder; - const sortColumnRef = useRef(sortColumn); - sortColumnRef.current = sortColumn; - const currentPageRef = useRef(currentPage); - currentPageRef.current = currentPage; - - const getWorkflows = async (offset) => { - setLoading(true) - const response = await workflowsService.getWorkflows(offset,limit, sortColumnRef.current, sortOrderRef.current); - if(response){ - setWorkflows(response.workflows); - setTotal(response.total) - } - setLoading(false) - if(!offset) setCurrentPage(1) - }; - - const refreshWorkflows = async (sortColumn = sortColumnRef.current, sortOrder = sortOrderRef.current, currentPage = currentPageRef.current) => { - if(currentPageRef.current){ - const response = await workflowsService.getWorkflows((currentPage - 1)*limit, limit, sortColumn, sortOrder); - if(response){ - setWorkflows(response.workflows); - setTotal(response.total) - } - }; - } - - const openRunLogs = workflow => { - setSelectedWorkflow(workflow) - setIsRunLogsDrawerVisible(true) - } - - const closeWorkflowRunsDrawer = () => { - setIsRunLogsDrawerVisible(false) - setSelectedWorkflow('') - } - - useEffect(() => { - getNotebooksLight() - getWorkflows(0) - if (!componentDidMount){ refreshWorkflows(); setComponentDidMount(true)} - const refreshWorkflowsInterval = setInterval(refreshWorkflows, 3000); - - return () => { - clearInterval(refreshWorkflowsInterval); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const handleTableChange = (event, filter, sorter) => { - setSortedInfo(sorter) - setSortColumn(sorter.columnKey) - setSortOrder(sorter.order) - setCurrentPage(event.current) - refreshWorkflows(sorter.columnKey, sorter.order, event.current) - - } - - const getNotebooksLight = async () => { - if (_.isEmpty(notebooksLight)){ - const response = await notebookService.getNotebooksLight(); - if(response){ - setNotebooksLight(response); - } - } - } - - const addWorkflow = () => { - getNotebooksLight() - setIsEditCreateWorkflow(true) - setSelectedWorkflow('') - } - - const editWorkflow = workflow => { - getNotebooksLight() - setIsEditCreateWorkflow(true) - setSelectedWorkflow(workflow) - setSelectedNotebooks(workflow.notebooks) - - if (workflow.schedule){ - setSelectedSchedule(workflow.schedule.id) - } - - setNewWorkflowName(workflow.name) - setTriggerWorkflow(workflow.triggerWorkflow) - setTriggerWorkflowStatus(workflow.triggerWorkflowStatus) - } - - const saveWorkflow = async () => { - if(!_.isEmpty(newWorkflowName)){ - const data = { - id: selectedWorkflow.id, - name: newWorkflowName, - notebookIds: selectedNotebooks, - scheduleId: selectedSchedule, - triggerWorkflowId: triggerWorkflow ? triggerWorkflow.id : null, - triggerWorkflowStatus: triggerWorkflowStatus - } - const response = await workflowsService.setWorkflows(data); - if(response){ - setIsEditCreateWorkflow(false) - settingInitialValues() - } - } - else{ - message.error('Please fill values'); - } - refreshWorkflows() - } - - const handleCancel = () => { - setIsEditCreateWorkflow(false) - settingInitialValues() - } - - const settingInitialValues = () => { - setSelectedWorkflow("") - setNewWorkflowName('') - setSelectedNotebooks([]) - setSelectedSchedule(null) - setTriggerWorkflow(false) - setTriggerWorkflowStatus(STATUS_ALWAYS) - setAssignSchedule(false) - setAssignTriggerWorkflowForId(false) - } - - const showNotebooksOfWorkflow = workflow => { - // Parent workflow removed from assign Workflow - const notebookNames = notebooksLight.filter(notebook => workflow.notebooks.find(x => x===notebook.id)).map(notebook => notebook.path.substring(1)) - return Notebooks: {notebookNames.join(", ")} - } - - const runWorkflow = async workflow => { - await workflowsService.runWorkflow(workflow.id); - refreshWorkflows() - } - - const stopWorkflow = async workflow => { - await workflowsService.stopWorkflow(workflow.lastRun.workflowRunId); - refreshWorkflows() - } - - const deleteWorkflow = async workflow => { - await workflowsService.deleteWorkflow(workflow.id); - refreshWorkflows() - } - - const columns = [ - { - title: "Workflow", - dataIndex: "name", - key: "name", - sorter: ()=>{}, - sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order, - ellipsis: true, - render: text => { - return ( - - {text} - - ); - } - }, - { - title: 'Trigger Workflow', - dataIndex: "triggerWorkflow", - key: "triggerWorkflow", - sorter: ()=>{}, - sortOrder: sortedInfo.columnKey === 'triggerWorkflow' && sortedInfo.order, - ellipsis: true, - render: (text, workflow) => { - if (workflow.triggerWorkflow){ - return ( - - {text ? text.name + " " : ""} - {text ? - {workflow.triggerWorkflowStatus} : null - } - - updateAssignedTriggerWorkflow(workflow.id)}> - - - ); - - } - else { - if (assignTriggerWorkflowForId && assignTriggerWorkflowForId === workflow.id){ - return updateAssignedTriggerWorkflow(workflow.id)} - onCancel={settingInitialValues} - okText="Save" - bodyStyle={{ paddingBottom: 80 }} - > - {selectTriggerWorkflowElement} - - } else { - return false} className={style.linkText} onClick={()=>setAssignTriggerWorkflowForId(workflow.id)}>Assign Workflow - } - } - } - }, - { - title: 'Schedule', - dataIndex: "schedule", - key: "schedule", - sorter: ()=>{}, - sortOrder: sortedInfo.columnKey === 'schedule' && sortedInfo.order, - ellipsis: true, - render: (text, workflow) => { - if (workflow.schedule){ - return ( - - {text ? text.name + " " : ""} - - updateAssignedSchedule(workflow.id, null)}> - - - ); - - } - else { - if (assignSchedule && assignSchedule === workflow.id){ - return {updateAssignedSchedule(workflow.id, value)}} /> - } else { - return false} className={style.linkText} onClick={()=>setAssignSchedule(workflow.id)}>Assign Schedule - } - } - } - }, - { - title: "Last run", - dataIndex: "lastRun", - key: "lastRunTime", - align:"left", - sorter: ()=>{}, - sortOrder: sortedInfo.columnKey === 'lastRunTime' && sortedInfo.order, - ellipsis: true, - defaultSortOrder: "descend", - render: lastRun => { - - let timeDiff; - if (lastRun && lastRun.startTimestamp && lastRun.endTimestamp){ - timeDiff = Math.round((new Date(lastRun.endTimestamp) - new Date(lastRun.startTimestamp))/1000) - - } - let diff; - if (timeDiff){ - diff = moment.duration(timeDiff, "second").format("h [hrs] m [min] s [sec]", { - trim: "both" - }); - if(diff){ - diff = timehumanize(diff.split(" ")) - } - } - let item = ( -
    - {lastRun ? : null} -
    - {diff} -
    -
    - ) - return ( -
    - {item} -
    - ); - } - }, - { - title: "Last run status", - dataIndex: "lastRun", - key: "lastRunStatus", - sortOrder: sortedInfo.columnKey === 'lastRunStatus' && sortedInfo.order, - ellipsis: true, - render: lastRun => { - return ( - - {lastRun ? lastRun.status : null} - - ); - } - }, - { - title: "", - dataIndex: "", - key: "", - // width: "10%", - render: (text, workflow) => { - const menu = ( - - deleteWorkflow(workflow)} - okText="Yes" - cancelText="No" - > - - Delete Workflow - - - - openRunLogs(workflow)} > - - View Runs - - - ) - - return ( -
    - { workflow.lastRun && (workflow.lastRun.status === STATUS_RUNNING || workflow.lastRun.status === STATUS_RECEIVED) - ? - - stopWorkflow(workflow)} /> - - : - - runWorkflow(workflow)} /> - - } - - - editWorkflow(workflow)} /> - - - - - - -
    - ); - } - } - ] - - const updateAssignedTriggerWorkflow = async (workflowId) => { - const data = { - triggerWorkflowId: triggerWorkflow ? triggerWorkflow.id : null, - triggerWorkflowStatus: triggerWorkflowStatus - } - - const response = await workflowsService.updateTriggerWorkflow(workflowId, data); - if (response){settingInitialValues() } - refreshWorkflows() - } - - const updateAssignedSchedule = async (workflowId, scheduleId) => { - const data = { - scheduleId: scheduleId - } - const response = await workflowsService.updateWorkflowSchedule(workflowId, data); - if (response){settingInitialValues() } - refreshWorkflows() - } - const workflowOptionElements = workflows.filter(workflow=>!((selectedWorkflow && workflow.id === selectedWorkflow.id) || (workflow.id === assignTriggerWorkflowForId))).map(workflow => - - ) - - const statuses = [STATUS_SUCCESS, STATUS_ERROR, STATUS_ALWAYS] - const statusOptionElements = statuses.map(status => - - ) - - const notebooksLightElement = notebooksLight && notebooksLight.map(notebook => - - ) - - const selectTriggerWorkflowElement = <> - - - - - - - - const editCreateWorkflowElement = - - - - } - > -
    - - setNewWorkflowName(event.target.value)} value={newWorkflowName} placeholder="Sample Workflow"> - - - - - - - - - Schedule - - } - key="1" - > - setSelectedSchedule(value)} schedule={selectedSchedule}/> - - - Workflow - - } - key="2" - > - {selectTriggerWorkflowElement} - - - - -
    - - return ( -
    -
    - -
    - { isEditCreateWorkflow ? editCreateWorkflowElement : null } -
    showNotebooksOfWorkflow(record), - }} - pagination={{ - current: currentPage, - pageSize: limit, - showSizeChanger: false, - total: workflows ? totalWokflows : 0 - }} - /> - - - - } - > - { isRunLogsDrawerVisible - ? - - : - null - } - - - - ) +import React, { useState, useEffect, useRef } from "react"; +import style from "./style.module.scss"; +import TimeAgo from 'react-timeago'; +import _ from "lodash"; +import { + Table, + Button, + Modal, + Input, + Select, + Tooltip, + Form, + message, + Drawer, + Tabs, + Menu, + Dropdown, + Popconfirm, + } from "antd"; +import { MoreOutlined, EditOutlined, PlayCircleOutlined, UnorderedListOutlined, StopOutlined, DeleteOutlined, CloseOutlined} from '@ant-design/icons'; +import { Badge } from "reactstrap"; +import WorkflowRuns from "./WorkflowRuns" +import SelectSchedule from "components/Schedule/selectSchedule" + +import workflowsService from "services/workflows"; +import notebookService from "services/notebooks"; +import { timehumanize } from 'services/general'; +import { STATUS_ALWAYS, STATUS_ERROR, STATUS_SUCCESS, STATUS_RUNNING, STATUS_RECEIVED } from "./constants" + +var moment = require("moment"); + +const { TabPane } = Tabs; +const { Option } = Select; + +export default function Workflows(props) { + + const [limit] = useState(25); + const [sortedInfo, setSortedInfo] = useState({}) + const [sortOrder, setSortOrder] = useState('') + const [sortColumn, setSortColumn] = useState(''); + const [workflows, setWorkflows] = useState([]); + const [loading, setLoading] = useState(false); + const [notebooksLight, setNotebooksLight] = useState([]) + const [totalWokflows, setTotal] = useState(''); + const [currentPage, setCurrentPage] = useState(''); + const [selectedWorkflow, setSelectedWorkflow] = useState(''); + const [selectedSchedule, setSelectedSchedule] = useState(null); + const [selectedNotebooks, setSelectedNotebooks] = useState([]); + const [isRunLogsDrawerVisible, setIsRunLogsDrawerVisible] = useState(false); + const [isEditCreateWorkflow, setIsEditCreateWorkflow] = useState(false); + const [newWorkflowName, setNewWorkflowName] = useState(''); + const [triggerWorkflow, setTriggerWorkflow] = useState(false); + const [triggerWorkflowStatus, setTriggerWorkflowStatus] = useState(STATUS_ALWAYS); + const [assignTriggerWorkflowForId, setAssignTriggerWorkflowForId] = useState(false) // stores id of parent workflow + const [assignSchedule, setAssignSchedule] = useState(false) + const [componentDidMount, setComponentDidMount] = useState(false) + const sortOrderRef = useRef(sortOrder); + sortOrderRef.current = sortOrder; + const sortColumnRef = useRef(sortColumn); + sortColumnRef.current = sortColumn; + const currentPageRef = useRef(currentPage); + currentPageRef.current = currentPage; + + const getWorkflows = async (offset) => { + setLoading(true) + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await workflowsService.getWorkflows(workspaceId, offset, limit, sortColumnRef.current, sortOrderRef.current); + if(response){ + setWorkflows(response.workflows); + setTotal(response.total) + } + setLoading(false) + if(!offset) setCurrentPage(1) + }; + + const refreshWorkflows = async (sortColumn = sortColumnRef.current, sortOrder = sortOrderRef.current, currentPage = currentPageRef.current) => { + if(currentPageRef.current){ + let workspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await workflowsService.getWorkflows(workspaceId, (currentPage - 1)*limit, limit, sortColumn, sortOrder); + if(response){ + setWorkflows(response.workflows); + setTotal(response.total) + } + }; + } + + const openRunLogs = workflow => { + setSelectedWorkflow(workflow) + setIsRunLogsDrawerVisible(true) + } + + const closeWorkflowRunsDrawer = () => { + setIsRunLogsDrawerVisible(false) + setSelectedWorkflow('') + } + + useEffect(() => { + getNotebooksLight() + getWorkflows(0) + if (!componentDidMount){ refreshWorkflows(); setComponentDidMount(true)} + const refreshWorkflowsInterval = setInterval(refreshWorkflows, 3000); + + return () => { + clearInterval(refreshWorkflowsInterval); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleTableChange = (event, filter, sorter) => { + setSortedInfo(sorter) + setSortColumn(sorter.columnKey) + setSortOrder(sorter.order) + setCurrentPage(event.current) + refreshWorkflows(sorter.columnKey, sorter.order, event.current) + + } + + const getNotebooksLight = async () => { + let WorkspaceId = parseInt(localStorage.getItem("workspaceId")) + if (_.isEmpty(notebooksLight)){ + const response = await notebookService.getNotebooksLight(WorkspaceId); + if(response){ + setNotebooksLight(response); + } + } + } + + const addWorkflow = () => { + getNotebooksLight() + setIsEditCreateWorkflow(true) + setSelectedWorkflow('') + } + + const editWorkflow = workflow => { + getNotebooksLight() + setIsEditCreateWorkflow(true) + setSelectedWorkflow(workflow) + setSelectedNotebooks(workflow.notebooks) + + if (workflow.schedule){ + setSelectedSchedule(workflow.schedule.id) + } + + setNewWorkflowName(workflow.name) + setTriggerWorkflow(workflow.triggerWorkflow) + setTriggerWorkflowStatus(workflow.triggerWorkflowStatus) + } + + const saveWorkflow = async () => { + if(!_.isEmpty(newWorkflowName)){ + const data = { + id: selectedWorkflow.id, + name: newWorkflowName, + notebookIds: selectedNotebooks, + scheduleId: selectedSchedule, + triggerWorkflowId: triggerWorkflow ? triggerWorkflow.id : null, + triggerWorkflowStatus: triggerWorkflowStatus + } + let WorkspaceId = parseInt(localStorage.getItem("workspaceId")) + const response = await workflowsService.setWorkflows(data, WorkspaceId); + if(response){ + setIsEditCreateWorkflow(false) + settingInitialValues() + } + } + else{ + message.error('Please fill values'); + } + refreshWorkflows() + } + + const handleCancel = () => { + setIsEditCreateWorkflow(false) + settingInitialValues() + } + + const settingInitialValues = () => { + setSelectedWorkflow("") + setNewWorkflowName('') + setSelectedNotebooks([]) + setSelectedSchedule(null) + setTriggerWorkflow(false) + setTriggerWorkflowStatus(STATUS_ALWAYS) + setAssignSchedule(false) + setAssignTriggerWorkflowForId(false) + } + + const showNotebooksOfWorkflow = workflow => { + // Parent workflow removed from assign Workflow + const notebookNames = notebooksLight.filter(notebook => workflow.notebooks.find(x => x===notebook.id)).map(notebook => notebook.path.substring(1)) + return Notebooks: {notebookNames.join(", ")} + } + + const runWorkflow = async workflow => { + await workflowsService.runWorkflow(workflow.id); + refreshWorkflows() + } + + const stopWorkflow = async workflow => { + await workflowsService.stopWorkflow(workflow.lastRun.workflowRunId); + refreshWorkflows() + } + + const deleteWorkflow = async workflow => { + await workflowsService.deleteWorkflow(workflow.id); + refreshWorkflows() + } + + const columns = [ + { + title: "Workflow", + dataIndex: "name", + key: "name", + sorter: ()=>{}, + sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order, + ellipsis: true, + render: text => { + return ( + + {text} + + ); + } + }, + { + title: 'Trigger Workflow', + dataIndex: "triggerWorkflow", + key: "triggerWorkflow", + sorter: ()=>{}, + sortOrder: sortedInfo.columnKey === 'triggerWorkflow' && sortedInfo.order, + ellipsis: true, + render: (text, workflow) => { + if (workflow.triggerWorkflow){ + return ( + + {text ? text.name + " " : ""} + {text ? + {workflow.triggerWorkflowStatus} : null + } + + updateAssignedTriggerWorkflow(workflow.id)}> + + + ); + + } + else { + if (assignTriggerWorkflowForId && assignTriggerWorkflowForId === workflow.id){ + return updateAssignedTriggerWorkflow(workflow.id)} + onCancel={settingInitialValues} + okText="Save" + bodyStyle={{ paddingBottom: 80 }} + > + {selectTriggerWorkflowElement} + + } else { + return false} className={style.linkText} onClick={()=>setAssignTriggerWorkflowForId(workflow.id)}>Assign Workflow + } + } + } + }, + { + title: 'Schedule', + dataIndex: "schedule", + key: "schedule", + sorter: ()=>{}, + sortOrder: sortedInfo.columnKey === 'schedule' && sortedInfo.order, + ellipsis: true, + render: (text, workflow) => { + if (workflow.schedule){ + return ( + + {text ? text.name + " " : ""} + + updateAssignedSchedule(workflow.id, null)}> + + + ); + + } + else { + if (assignSchedule && assignSchedule === workflow.id){ + return {updateAssignedSchedule(workflow.id, value)}} /> + } else { + return false} className={style.linkText} onClick={()=>setAssignSchedule(workflow.id)}>Assign Schedule + } + } + } + }, + { + title: "Last run", + dataIndex: "lastRun", + key: "lastRunTime", + align:"left", + sorter: ()=>{}, + sortOrder: sortedInfo.columnKey === 'lastRunTime' && sortedInfo.order, + ellipsis: true, + defaultSortOrder: "descend", + render: lastRun => { + + let timeDiff; + if (lastRun && lastRun.startTimestamp && lastRun.endTimestamp){ + timeDiff = Math.round((new Date(lastRun.endTimestamp) - new Date(lastRun.startTimestamp))/1000) + + } + let diff; + if (timeDiff){ + diff = moment.duration(timeDiff, "second").format("h [hrs] m [min] s [sec]", { + trim: "both" + }); + if(diff){ + diff = timehumanize(diff.split(" ")) + } + } + let item = ( +
    + {lastRun ? : null} +
    + {diff} +
    +
    + ) + return ( +
    + {item} +
    + ); + } + }, + { + title: "Last run status", + dataIndex: "lastRun", + key: "lastRunStatus", + sortOrder: sortedInfo.columnKey === 'lastRunStatus' && sortedInfo.order, + ellipsis: true, + render: lastRun => { + return ( + + {lastRun ? lastRun.status : null} + + ); + } + }, + { + title: "", + dataIndex: "", + key: "", + // width: "10%", + render: (text, workflow) => { + const menu = ( + + deleteWorkflow(workflow)} + okText="Yes" + cancelText="No" + > + + Delete Workflow + + + + openRunLogs(workflow)} > + + View Runs + + + ) + + return ( +
    + { workflow.lastRun && (workflow.lastRun.status === STATUS_RUNNING || workflow.lastRun.status === STATUS_RECEIVED) + ? + + stopWorkflow(workflow)} /> + + : + + runWorkflow(workflow)} /> + + } + + + editWorkflow(workflow)} /> + + + + + + +
    + ); + } + } + ] + + const updateAssignedTriggerWorkflow = async (workflowId) => { + const data = { + triggerWorkflowId: triggerWorkflow ? triggerWorkflow.id : null, + triggerWorkflowStatus: triggerWorkflowStatus + } + + const response = await workflowsService.updateTriggerWorkflow(workflowId, data); + if (response){settingInitialValues() } + refreshWorkflows() + } + + const updateAssignedSchedule = async (workflowId, scheduleId) => { + const data = { + scheduleId: scheduleId + } + const response = await workflowsService.updateWorkflowSchedule(workflowId, data); + if (response){settingInitialValues() } + refreshWorkflows() + } + const workflowOptionElements = workflows.filter(workflow=>!((selectedWorkflow && workflow.id === selectedWorkflow.id) || (workflow.id === assignTriggerWorkflowForId))).map(workflow => + + ) + + const statuses = [STATUS_SUCCESS, STATUS_ERROR, STATUS_ALWAYS] + const statusOptionElements = statuses.map(status => + + ) + + const notebooksLightElement = notebooksLight && notebooksLight.map(notebook => + + ) + + const selectTriggerWorkflowElement = <> + + + + + + + + const editCreateWorkflowElement = + + + + } + > +
    + + setNewWorkflowName(event.target.value)} value={newWorkflowName} placeholder="Sample Workflow"> + + + + + + + + + Schedule + + } + key="1" + > + setSelectedSchedule(value)} schedule={selectedSchedule}/> + + + Workflow + + } + key="2" + > + {selectTriggerWorkflowElement} + + + + +
    + + return ( +
    +
    + +
    + { isEditCreateWorkflow ? editCreateWorkflowElement : null } +
    showNotebooksOfWorkflow(record), + }} + pagination={{ + current: currentPage, + pageSize: limit, + showSizeChanger: false, + total: workflows ? totalWokflows : 0 + }} + /> + + + + } + > + { isRunLogsDrawerVisible + ? + + : + null + } + + + + ) } \ No newline at end of file diff --git a/ui/src/components/Workspaces/addWorkspace.js b/ui/src/components/Workspaces/addWorkspace.js new file mode 100755 index 00000000..33d1d36f --- /dev/null +++ b/ui/src/components/Workspaces/addWorkspace.js @@ -0,0 +1,451 @@ +import React, { useState, useEffect } from "react"; +import { Steps, Popover, Form, Input, Button, Select, message } from 'antd'; +import { DatabaseOutlined, AppstoreOutlined, CloudServerOutlined, SmileOutlined, QuestionCircleOutlined } from '@ant-design/icons'; +import style from "./style.module.scss"; +import workspaceService from "services/workspace"; +import { useHistory } from "react-router-dom"; + +const { Step } = Steps; +const { TextArea } = Input; +const { Option } = Select; + +export default function AddWorkspace() { + const [form] = Form.useForm(); + const [currentStep, setCurrentStep] = useState(0); + const [storage, setStorage] = useState("S3"); + const [sparkImages, setSparkImages] = useState([]); + const [interpreterImages, setInterpreterImages] = useState([]); + const [workspace, setWorkspace] = useState({}); + const [workspaceConfig, setWorkspaceConfig] = useState({}); + const history = useHistory(); + + const storageTypes = [ + "S3", + "GCS", + "ADLS" + ] + + const setWorksapce = (formdata) => { + setWorkspace(formdata) + setCurrentStep(1) + } + + const updateStorageConfig = (formdata) => { + let workspaceConfigData = {storage: storage, ...formdata, ...workspaceConfig} + setWorkspaceConfig(workspaceConfigData) + setCurrentStep(2) + } + + const updateServerConfig = (formdata) => { + setWorkspaceConfig({...workspaceConfig, ...formdata}) + setCurrentStep(3) + } + + const handleStorageTypeSelect = (storage) => { + setStorage(storage) + } + + const createWorkspace = () => { + const response = workspaceService.createAndStartWorkspaceServer(workspace, workspaceConfig) + if(response.success){ + window.location.href='/dashboard' + } + else{ + message.error(response.message); + } + } + + const getSparkImages = async () => { + const response = await workspaceService.getImageTags("spark") + setSparkImages(response.data) + } + + const getInterpreterImages = async () => { + const response = await workspaceService.getImageTags("zeppelin-interpreter") + setInterpreterImages(response.data) + } + + useEffect(() => { + if (!sparkImages.length) { + getSparkImages(); + } + if (!interpreterImages.length) { + getInterpreterImages(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const navigateToDashboard = () => { + history.push("/api/redirect/cuelake/dashboard"); + } + + return ( +
    +
    + + } /> + } /> + }/> + }/> + +
    +
    + { currentStep === 0 ? +
    +
    +
    + + + +
    +
    + + + + + + + + + + + + + + + +
    + + +
    + + : + null + } +
    +
    + ); +} diff --git a/ui/src/components/Workspaces/style.module.scss b/ui/src/components/Workspaces/style.module.scss new file mode 100755 index 00000000..f46a2e94 --- /dev/null +++ b/ui/src/components/Workspaces/style.module.scss @@ -0,0 +1,70 @@ +.addWorksapceForm{ + background: #fff; + padding: 20px; + margin: 0px; + height: calc(100vh - 32px); +} + +.addWorkspaceSteps{ + margin: 40px auto 70px auto; + width: 70%; +} + +.workspaceForm{ + width: 40%; + margin: 0 auto 50px auto; +} + +.storageTypes{ + display: inline-block; + width: 100%; + // margin-left: 15%; +} + +.selectedStorage, .storage{ + width: calc(33.33% - 10px); + margin-right: 10px; + margin-bottom: 10px; + height: 130px; + float: left; + border: 1px solid #fafafa; + border-radius: 5px; + text-align: center; + background-color: #fafafa; + cursor: pointer; + padding: 20px; + + &:hover{ + background-color: #eee; + } +} + +.selectedStorage{ + background-color: #ccc; + &:hover{ + background-color: #ccc; + } +} + +.storageLogo{ + background-size: contain; + background-position: 50% 50%; + background-repeat: no-repeat; + width: 50%; + margin: auto auto; + height: 70px; +} + +.buttonDiv{ + text-align: right; +} + +.formItem{ + float: left; +} + +.preview{ + input{ + margin-bottom: 10px; + } +} \ No newline at end of file diff --git a/ui/src/layouts/Admin.js b/ui/src/layouts/Admin.js index 7558082c..7c6c6cde 100644 --- a/ui/src/layouts/Admin.js +++ b/ui/src/layouts/Admin.js @@ -1,47 +1,51 @@ -import React from "react"; -import { Switch, Route, Redirect } from "react-router-dom"; -import ReactNotification from 'react-notifications-component'; - -// components - -import Sidebar from "components/Sidebar/Sidebar.js"; - -// views -import Settings from "views/admin/Settings.js"; -import Notebooks from "views/admin/Notebooks.js"; -import NotebookView from "views/admin/NotebookView.js"; -import Connections from "views/admin/Connections.js"; -import SchedulesView from "views/admin/Schedules.js"; -import WorkflowsMain from "views/admin/WorkflowsMain.js"; -import SparkUI from "views/admin/sparkUI.js"; - -// contexts -import { GlobalContextProvider } from "./GlobalContext"; - -export default function Admin() { - return ( - <> - - - -
    - {/* */} - {/* Header */} - {/* */} -
    - - - - - - - - - - -
    -
    -
    - - ); -} +import React from "react"; +import { Switch, Route, Redirect } from "react-router-dom"; +import ReactNotification from 'react-notifications-component'; + +// components + +import Sidebar from "components/Sidebar/Sidebar.js"; + +// views +import Settings from "views/admin/Settings.js"; +import Notebooks from "views/admin/Notebooks.js"; +import NotebookView from "views/admin/NotebookView.js"; +import Connections from "views/admin/Connections.js"; +import SchedulesView from "views/admin/Schedules.js"; +import WorkflowsMain from "views/admin/WorkflowsMain.js"; +import SparkUI from "views/admin/sparkUI.js"; +import Dashboard from "views/admin/Dashboard.js"; +import AddWorkspace from "views/admin/AddWorkspace.js"; + +// contexts +import { GlobalContextProvider } from "./GlobalContext"; + +export default function Admin() { + return ( + <> + + + +
    + {/* */} + {/* Header */} + {/* */} +
    + + + + + + + + + + + + +
    +
    +
    + + ); +} diff --git a/ui/src/layouts/EmbedAdmin.js b/ui/src/layouts/EmbedAdmin.js index b62949e9..0fac18f6 100644 --- a/ui/src/layouts/EmbedAdmin.js +++ b/ui/src/layouts/EmbedAdmin.js @@ -1,39 +1,41 @@ -import React from "react"; -import { Switch, Route, Redirect } from "react-router-dom"; -import ReactNotification from 'react-notifications-component'; - -// views -import Sidebar from "components/Sidebar/Sidebar.js"; -import Settings from "views/admin/Settings.js"; -import Notebooks from "views/admin/Notebooks.js"; -import NotebookView from "views/admin/NotebookView.js"; -import Connections from "views/admin/Connections.js"; -import SparkUI from "views/admin/sparkUI.js"; -import WorkflowsMain from "views/admin/WorkflowsMain.js"; -import SchedulesView from "views/admin/Schedules.js"; -import { GlobalContextProvider } from "./GlobalContext"; - -export default function Admin() { - return ( - <> - - - -
    -
    - - - - - - - - - - -
    -
    -
    - - ); -} +import React from "react"; +import { Switch, Route, Redirect } from "react-router-dom"; +import ReactNotification from 'react-notifications-component'; + +// views +import Sidebar from "components/Sidebar/Sidebar.js"; +import Settings from "views/admin/Settings.js"; +import Notebooks from "views/admin/Notebooks.js"; +import NotebookView from "views/admin/NotebookView.js"; +import Connections from "views/admin/Connections.js"; +import SparkUI from "views/admin/sparkUI.js"; +import WorkflowsMain from "views/admin/WorkflowsMain.js"; +import SchedulesView from "views/admin/Schedules.js"; +import Dashboard from "views/admin/Dashboard.js"; +import { GlobalContextProvider } from "./GlobalContext"; + +export default function Admin() { + return ( + <> + + + +
    +
    + + + + + + + + + + + +
    +
    +
    + + ); +} diff --git a/ui/src/services/notebooks.js b/ui/src/services/notebooks.js index 21ef869b..6ac92f5f 100644 --- a/ui/src/services/notebooks.js +++ b/ui/src/services/notebooks.js @@ -1,169 +1,169 @@ -import apiService from "./api"; - -class NotebookService { - async getNotebookObject(notebookObjId){ - const response = await apiService.get("genie/notebookObject/" + notebookObjId) - if(response.success === true) - return response.data - else - return null - } - - async getNotebooks(offset, limit, searchText, sorter, filter){ - const response = await apiService.get("genie/notebooks/" + offset + "?limit="+ limit + "&searchText="+ searchText+ "&sorter="+ JSON.stringify(sorter) + "&filter=" + JSON.stringify(filter)) - if(response.success === true) - return response.data - else - return null - } - - async getArchivedNotebooks(){ - const response = await apiService.get("genie/notebooks/archive") - if(response.success === true) - return response.data - else - return null - } - - async getDriverAndExecutorStatus(){ - const response = await apiService.get("genie/driverAndExecutorStatus/" ) - if(response.success === true) - return response.data - else - return null - } - - async getNotebooksLight(){ - const response = await apiService.get("genie/notebooksLight") - if(response.success === true) - return response.data - else - return null - } - - async getNotebookLogs(notebookJobId, offset){ - const response = await apiService.get("genie/notebookjob/" + notebookJobId + "?offset=" + offset) - if(response.success === true) - return response.data - else - return null - } - - async getSchedules(){ - const response = await apiService.get("genie/schedules/") - if(response.success === true) - return response.data - else - return null - } - - async deleteSchedule(scheduleId){ - const response = await apiService.delete("genie/schedules/" + scheduleId) - if(response.success === true) - return response - else - return null - } - - async getSingleSchedule(scheduleId){ - const response = await apiService.get("genie/schedules/" + scheduleId) - if(response.success === true) - return response.data - else - return null - } - - async addNotebookSchedule(notebookId, scheduleId){ - const response = await apiService.post("genie/notebookjob/", {notebookId: notebookId,scheduleId: scheduleId}) - return response - } - - async getTimezones(){ - const response = await apiService.get("genie/timezones/") - if(response.success === true) - return response.data - else - return null - } - - async addSchedule(cronTabSchedule, selectedTimezone, scheduleName){ - const response = await apiService.post("genie/schedules/", {"crontab": cronTabSchedule, "timezone": selectedTimezone, "name": scheduleName}) - return response - } - async updateSchedule(selectedScheduleId,cronTabSchedule, selectedTimezone, scheduleName){ - const response = await apiService.put("genie/schedules/", {"id":selectedScheduleId,"crontab": cronTabSchedule, "timezone": selectedTimezone, "name": scheduleName}) - return response - } - - async stopNotebook(notebookId){ - const response = await apiService.delete("genie/notebook/actions/" + notebookId) - return response - } - - async runNotebook(notebookId){ - const response = await apiService.post("genie/notebook/actions/" + notebookId) - return response - } - - async toggleNotebookSchedule(enabled, notebookId){ - const response = await apiService.put("genie/notebookjob/", {notebookId: notebookId, enabled: enabled}) - return response - } - - async getNotebookTemplates(){ - const response = await apiService.get("genie/notebookTemplates/") - return response - } - - async addNotebook(payload){ - const response = await apiService.post("genie/notebook", payload) - return response - } - - async editNotebook(payload){ - const response = await apiService.put("genie/notebookObject/" + payload.notebookObjId, payload) - return response - } - - async cloneNotebook(notebookId, newNotebookName){ - const response = await apiService.post("genie/notebook/" + notebookId, {name: newNotebookName}) - return response - } - - async archiveNotebook(notebookId, notebookName){ - const response = await apiService.get("genie/notebook/" + notebookId + "/archive/" + notebookName) - return response - } - - async unarchiveNotebook(notebookId, notebookName){ - const response = await apiService.get("genie/notebook/" + notebookId + "/unarchive/" + notebookName) - return response - } - - async deleteNotebook(notebookId){ - const response = await apiService.delete("genie/notebook/" + notebookId) - return response - } - - async unassignSchedule(notebookId){ - const response = await apiService.delete("genie/notebookjob/" + notebookId) - return response - } - - async getDatasetDetails(payload){ - const response = await apiService.post("genie/datasetDetails", payload) - return response - } - - async getMetastoreTables(){ - const response = await apiService.get("genie/metastoreTables") - return response - } - - async getMetastoreColumns(tableId){ - const response = await apiService.get("genie/metastoreColumns/" + tableId) - return response - } -} -let notebookService = new NotebookService(); -export default notebookService +import apiService from "./api"; + +class NotebookService { + async getNotebookObject(notebookObjId, workspaceId){ + const response = await apiService.get("genie/notebookObject/" + notebookObjId + "/" + workspaceId) + if(response.success === true) + return response.data + else + return null + } + + async getNotebooks(offset, limit, searchText, sorter, filter, workspaceId){ + const response = await apiService.get("genie/notebooks/" + offset + "/" + workspaceId + "?limit="+ limit + "&searchText="+ searchText+ "&sorter="+ JSON.stringify(sorter) + "&filter=" + JSON.stringify(filter)) + if(response.success === true) + return response.data + else + return null + } + + async getArchivedNotebooks(){ + const response = await apiService.get("genie/notebooks/archive") + if(response.success === true) + return response.data + else + return null + } + + async getDriverAndExecutorStatus(){ + const response = await apiService.get("genie/driverAndExecutorStatus/" ) + if(response.success === true) + return response.data + else + return null + } + + async getNotebooksLight(workspaceId){ + const response = await apiService.get("genie/notebooksLight/" + workspaceId) + if(response.success === true) + return response.data + else + return null + } + + async getNotebookLogs(notebookJobId, offset){ + const response = await apiService.get("genie/notebookjob/" + notebookJobId + "?offset=" + offset) + if(response.success === true) + return response.data + else + return null + } + + async getSchedules(){ + const response = await apiService.get("genie/schedules/") + if(response.success === true) + return response.data + else + return null + } + + async deleteSchedule(scheduleId){ + const response = await apiService.delete("genie/schedules/" + scheduleId) + if(response.success === true) + return response + else + return null + } + + async getSingleSchedule(scheduleId){ + const response = await apiService.get("genie/schedules/" + scheduleId) + if(response.success === true) + return response.data + else + return null + } + + async addNotebookSchedule(notebookId, scheduleId){ + const response = await apiService.post("genie/notebookjob/", {notebookId: notebookId,scheduleId: scheduleId}) + return response + } + + async getTimezones(){ + const response = await apiService.get("genie/timezones/") + if(response.success === true) + return response.data + else + return null + } + + async addSchedule(cronTabSchedule, selectedTimezone, scheduleName){ + const response = await apiService.post("genie/schedules/", {"crontab": cronTabSchedule, "timezone": selectedTimezone, "name": scheduleName}) + return response + } + async updateSchedule(selectedScheduleId,cronTabSchedule, selectedTimezone, scheduleName){ + const response = await apiService.put("genie/schedules/", {"id":selectedScheduleId,"crontab": cronTabSchedule, "timezone": selectedTimezone, "name": scheduleName}) + return response + } + + async stopNotebook(notebookId, workspaceId){ + const response = await apiService.delete("genie/notebook/actions/" + notebookId + "/" + workspaceId) + return response + } + + async runNotebook(notebookId, workspaceId){ + const response = await apiService.post("genie/notebook/actions/" + notebookId + "/" + workspaceId) + return response + } + + async toggleNotebookSchedule(enabled, notebookId){ + const response = await apiService.put("genie/notebookjob/", {notebookId: notebookId, enabled: enabled}) + return response + } + + async getNotebookTemplates(){ + const response = await apiService.get("genie/notebookTemplates/") + return response + } + + async addNotebook(payload, WorkspaceId){ + const response = await apiService.post("genie/notebook" + "/" + WorkspaceId, payload) + return response + } + + async editNotebook(payload, WorkspaceId){ + const response = await apiService.put("genie/notebookObject/" + payload.notebookObjId + "/" + WorkspaceId, payload) + return response + } + + async cloneNotebook(notebookId, newNotebookName, workspaceId){ + const response = await apiService.post("genie/notebook/" + notebookId + "/" + workspaceId, {name: newNotebookName}) + return response + } + + async archiveNotebook(notebookId, notebookName, WorkspaceId){ + const response = await apiService.get("genie/notebook/" + notebookId + "/archive/" + notebookName + "/" + WorkspaceId) + return response + } + + async unarchiveNotebook(notebookId, notebookName, WorkspaceId){ + const response = await apiService.get("genie/notebook/" + notebookId + "/unarchive/" + notebookName + "/" + WorkspaceId) + return response + } + + async deleteNotebook(notebookId, WorkspaceId){ + const response = await apiService.delete("genie/notebook/" + notebookId + "/" + WorkspaceId) + return response + } + + async unassignSchedule(notebookId){ + const response = await apiService.delete("genie/notebookjob/" + notebookId) + return response + } + + async getDatasetDetails(payload){ + const response = await apiService.post("genie/datasetDetails", payload) + return response + } + + async getMetastoreTables(){ + const response = await apiService.get("genie/metastoreTables") + return response + } + + async getMetastoreColumns(tableId){ + const response = await apiService.get("genie/metastoreColumns/" + tableId) + return response + } +} +let notebookService = new NotebookService(); +export default notebookService diff --git a/ui/src/services/workflows.js b/ui/src/services/workflows.js index f371c972..b521ab94 100644 --- a/ui/src/services/workflows.js +++ b/ui/src/services/workflows.js @@ -1,86 +1,86 @@ -import apiService from "./api"; -import { message } from "antd"; - -class WorkflowsService { - async getWorkflows(offset, limit, sortColumn, sortOrder){ - const response = await apiService.get("workflows/workflows/" + offset + "?limit="+ limit +"&sortColumn=" + sortColumn + "&sortOrder=" + sortOrder) - if(response.success === true) - return response.data - else - return null - } - - - async getWorkflowRuns(workflowId, offset){ - const response = await apiService.get("workflows/workflowRuns/" + workflowId +"/"+ offset) - if(response.success === true) - return response.data - else - return null - } - - async getWorkflowRunLogs(workflowRunId){ - const response = await apiService.get("workflows/workflowRunLogs/" + workflowRunId) - if(response.success === true) - return response.data - else - return null - } - - async setWorkflows(data){ - const response = await apiService.post("workflows/workflows", data) - if(response.success === true) - return response.success - else - message.error(response.message); - return null - } - - async runWorkflow(workflowId){ - const response = await apiService.get("workflows/runWorkflow/" + workflowId) - if(response.success === true) - return response.data - else - message.error(response.message); - return null - } - - async stopWorkflow(workflowId){ - const response = await apiService.get("workflows/stopWorkflow/" + workflowId) - if(response.success === true) - return response.data - else - message.error(response.message); - return null - } - - async deleteWorkflow(workflowId){ - const response = await apiService.delete("workflows/workflow/" + workflowId) - if(response.success === true) - return response.data - else - message.error(response.message); - return null - } - - async updateTriggerWorkflow(workflowId, data){ - const response = await apiService.post("workflows/updateTriggerWorkflow/" + workflowId, data) - if(response.success === true) - return response.data - else - message.error(response.message); - return null - } - - async updateWorkflowSchedule(workflowId, data){ - const response = await apiService.post("workflows/updateSchedule/" + workflowId, data) - if(response.success === true) - return response.data - else - message.error(response.message); - return null - } - -} -let workflowsService = new WorkflowsService(); -export default workflowsService +import apiService from "./api"; +import { message } from "antd"; + +class WorkflowsService { + async getWorkflows(workspaceId, offset, limit, sortColumn, sortOrder){ + const response = await apiService.get("workflows/workflows/" + workspaceId + "?offset=" + offset + "&limit="+ limit +"&sortColumn=" + sortColumn + "&sortOrder=" + sortOrder) + if(response.success === true) + return response.data + else + return null + } + + + async getWorkflowRuns(workflowId, offset){ + const response = await apiService.get("workflows/workflowRuns/" + workflowId +"/"+ offset) + if(response.success === true) + return response.data + else + return null + } + + async getWorkflowRunLogs(workflowRunId){ + const response = await apiService.get("workflows/workflowRunLogs/" + workflowRunId) + if(response.success === true) + return response.data + else + return null + } + + async setWorkflows(data, workspaceId){ + const response = await apiService.post("workflows/workflows/" + workspaceId, data) + if(response.success === true) + return response.success + else + message.error(response.message); + return null + } + + async runWorkflow(workflowId){ + const response = await apiService.get("workflows/runWorkflow/" + workflowId) + if(response.success === true) + return response.data + else + message.error(response.message); + return null + } + + async stopWorkflow(workflowId){ + const response = await apiService.get("workflows/stopWorkflow/" + workflowId) + if(response.success === true) + return response.data + else + message.error(response.message); + return null + } + + async deleteWorkflow(workflowId){ + const response = await apiService.delete("workflows/workflow/" + workflowId) + if(response.success === true) + return response.data + else + message.error(response.message); + return null + } + + async updateTriggerWorkflow(workflowId, data){ + const response = await apiService.post("workflows/updateTriggerWorkflow/" + workflowId, data) + if(response.success === true) + return response.data + else + message.error(response.message); + return null + } + + async updateWorkflowSchedule(workflowId, data){ + const response = await apiService.post("workflows/updateSchedule/" + workflowId, data) + if(response.success === true) + return response.data + else + message.error(response.message); + return null + } + +} +let workflowsService = new WorkflowsService(); +export default workflowsService diff --git a/ui/src/services/workspace.js b/ui/src/services/workspace.js new file mode 100755 index 00000000..ec470260 --- /dev/null +++ b/ui/src/services/workspace.js @@ -0,0 +1,41 @@ +import apiService from "./api"; + +class WorkspaceService { + + async getWorkspaces(){ + const response = await apiService.get("workspace/workspaces") + return response + } + + async getImageTags(repository){ + const response = await apiService.get("workspace/dockerimages/" + repository) + return response + } + + async createAndStartWorkspaceServer(workspace, workspaceConfig){ + const response = await apiService.post("workspace/createAndStartWorkspaceServer", {workspace: workspace, workspaceConfig: workspaceConfig}) + return response + } + + async stopWorkspaceServer(id){ + const response = await apiService.delete("workspace/workspaceServer/" + id) + return response + } + + async startWorkspaceServer(id){ + const response = await apiService.post("workspace/workspaceServer/" + id) + return response + } + + async switchWorkspaceServer(id){ + const response = await apiService.post("workspace/switchWorkspaceServer/" + id) + return response + } + + async deleteWorkspaceServer(id){ + const response = await apiService.delete("workspace/" + id , {id: id}) + return response + } +} +let workspaceService = new WorkspaceService(); +export default workspaceService \ No newline at end of file diff --git a/ui/src/views/admin/AddWorkspace.js b/ui/src/views/admin/AddWorkspace.js new file mode 100755 index 00000000..586360fa --- /dev/null +++ b/ui/src/views/admin/AddWorkspace.js @@ -0,0 +1,17 @@ +import React from "react"; + +// components + +import AddWorkspace from "components/Workspaces/addWorkspace.js"; + +export default function ConnectionFunction() { + return ( + <> +
    +
    + +
    +
    + + ); +} diff --git a/ui/src/views/admin/Dashboard.js b/ui/src/views/admin/Dashboard.js new file mode 100755 index 00000000..ff9667d7 --- /dev/null +++ b/ui/src/views/admin/Dashboard.js @@ -0,0 +1,17 @@ +import React from "react"; + +// components + +import Dashboard from "components/Dashboard/dashboard.js"; + +export default function DashboardFunction() { + return ( + <> +
    +
    + +
    +
    + + ); +} diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js index 6ab96919..a5b73241 100644 --- a/ui/tailwind.config.js +++ b/ui/tailwind.config.js @@ -1,97 +1,97 @@ -module.exports = { - future: { - removeDeprecatedGapUtilities: true, - purgeLayersByDefault: true, - }, - purge: { - enabled: true, - content: [ - "./public/**/*.html", - "./public/*.html", - "./src/**/*.js", - "./src/*.js", - "./src/**/*.html", - "./src/*.html", - "./public/**/*.js", - "./public/*.js", - ], - options: { - whitelist: [], - }, - }, - theme: { - extend: { - minHeight: { - "screen-75": "75vh", - }, - fontSize: { - "55": "55rem", - }, - opacity: { - "80": ".8", - }, - zIndex: { - "2": 2, - "3": 3, - }, - inset: { - "-100": "-100%", - "-225-px": "-225px", - "-160-px": "-160px", - "-150-px": "-150px", - "-94-px": "-94px", - "-50-px": "-50px", - "-29-px": "-29px", - "-20-px": "-20px", - "25-px": "25px", - "40-px": "40px", - "95-px": "95px", - "145-px": "145px", - "195-px": "195px", - "210-px": "210px", - "260-px": "260px", - }, - height: { - "95-px": "95px", - "70-px": "70px", - "350-px": "350px", - "500-px": "500px", - "600-px": "600px", - }, - maxHeight: { - "860-px": "860px", - }, - maxWidth: { - "100-px": "100px", - "120-px": "120px", - "150-px": "150px", - "180-px": "180px", - "200-px": "200px", - "210-px": "210px", - "580-px": "580px", - }, - minWidth: { - "140-px": "140px", - "48": "12rem", - }, - backgroundSize: { - full: "100$", - }, - }, - }, - variants: [ - "responsive", - "group-hover", - "focus-within", - "first", - "last", - "odd", - "even", - "hover", - "focus", - "active", - "visited", - "disabled", - ], - plugins: [require("@tailwindcss/custom-forms")], -}; +module.exports = { + future: { + removeDeprecatedGapUtilities: true, + purgeLayersByDefault: true, + }, + purge: { + enabled: true, + content: [ + "./public/**/*.html", + "./public/*.html", + "./src/**/*.js", + "./src/*.js", + "./src/**/*.html", + "./src/*.html", + "./public/**/*.js", + "./public/*.js", + ], + options: { + whitelist: [], + }, + }, + theme: { + extend: { + minHeight: { + "screen-75": "75vh", + }, + fontSize: { + "55": "55rem", + }, + opacity: { + "80": ".8", + }, + zIndex: { + "2": 2, + "3": 3, + }, + inset: { + "-100": "-100%", + "-225-px": "-225px", + "-160-px": "-160px", + "-150-px": "-150px", + "-94-px": "-94px", + "-50-px": "-50px", + "-29-px": "-29px", + "-20-px": "-20px", + "25-px": "25px", + "40-px": "40px", + "95-px": "95px", + "145-px": "145px", + "195-px": "195px", + "210-px": "210px", + "260-px": "260px", + }, + height: { + "95-px": "95px", + "70-px": "70px", + "350-px": "350px", + "500-px": "500px", + "600-px": "600px", + }, + maxHeight: { + "860-px": "860px", + }, + maxWidth: { + "100-px": "100px", + "120-px": "120px", + "150-px": "150px", + "180-px": "180px", + "200-px": "200px", + "210-px": "210px", + "580-px": "580px", + }, + minWidth: { + "140-px": "140px", + "48": "12rem", + }, + backgroundSize: { + full: "100$", + }, + }, + }, + variants: [ + "responsive", + "group-hover", + "focus-within", + "first", + "last", + "odd", + "even", + "hover", + "focus", + "active", + "visited", + "disabled", + ], + plugins: [require("@tailwindcss/custom-forms")], +};