From 9180b984782453f8e7bc9b0f3ca61a2029b2434f Mon Sep 17 00:00:00 2001 From: Gabriel Pelouze Date: Wed, 20 Sep 2023 10:10:53 +0200 Subject: [PATCH 1/2] add vlab_instances API endpoints --- vreapis/virtual_labs/admin.py | 3 ++- vreapis/virtual_labs/models.py | 5 +++++ vreapis/virtual_labs/serializers.py | 26 +++++++++++++++++++++++++- vreapis/virtual_labs/views.py | 19 ++++++++++++++++++- vreapis/vreapis/urls.py | 6 ++---- 5 files changed, 52 insertions(+), 7 deletions(-) diff --git a/vreapis/virtual_labs/admin.py b/vreapis/virtual_labs/admin.py index e40d19d..aaaf2b1 100644 --- a/vreapis/virtual_labs/admin.py +++ b/vreapis/virtual_labs/admin.py @@ -1,4 +1,5 @@ from django.contrib import admin -from virtual_labs.models import VirtualLab +from virtual_labs.models import VirtualLab, VirtualLabInstance admin.site.register(VirtualLab) +admin.site.register(VirtualLabInstance) diff --git a/vreapis/virtual_labs/models.py b/vreapis/virtual_labs/models.py index 9d0de04..a41f2c7 100644 --- a/vreapis/virtual_labs/models.py +++ b/vreapis/virtual_labs/models.py @@ -114,3 +114,8 @@ def __str__(self): class Meta: verbose_name = "KeyCloak Auth" + + +class VirtualLabInstance(models.Model): + vlab = models.ForeignKey(VirtualLab, on_delete=models.CASCADE, null=True) + username = models.CharField(max_length=100, null=True) diff --git a/vreapis/virtual_labs/serializers.py b/vreapis/virtual_labs/serializers.py index 438a1d2..0d380b5 100644 --- a/vreapis/virtual_labs/serializers.py +++ b/vreapis/virtual_labs/serializers.py @@ -1,8 +1,10 @@ from dataclasses import fields from pyexpat import model from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator from django.contrib.auth.models import User -from virtual_labs.models import VM, SDIAProvision, Topology, VLProfile, VirtualLab +from virtual_labs.models import (VM, SDIAProvision, Topology, VLProfile, + VirtualLab, VirtualLabInstance) from workflows.models import Workflow from workflows.serializers import WorkflowSerializer @@ -97,3 +99,25 @@ class Meta: 'description', 'endpoint' ) + + +class VirtualLabInstanceSerializer(serializers.ModelSerializer): + + vlab = serializers.SlugRelatedField( + slug_field='slug', + queryset=VirtualLab.objects.all() + ) + + class Meta: + model = VirtualLabInstance + fields = ( + 'vlab', + 'username', + ) + + validators = [ + UniqueTogetherValidator( + queryset=VirtualLabInstance.objects.all(), + fields=['vlab', 'username'], + ) + ] diff --git a/vreapis/virtual_labs/views.py b/vreapis/virtual_labs/views.py index 8cd94f1..1f907b1 100644 --- a/vreapis/virtual_labs/views.py +++ b/vreapis/virtual_labs/views.py @@ -1,5 +1,5 @@ from sys import stdout -from rest_framework import viewsets +from rest_framework import mixins, viewsets from rest_framework.decorators import action from . import serializers from . import models @@ -19,3 +19,20 @@ class VirtualLabViewSet(GetSerializerMixin, viewsets.ModelViewSet): } +class VirtualLabInstanceViewSet( + GetSerializerMixin, + mixins.ListModelMixin, + mixins.CreateModelMixin, + viewsets.GenericViewSet, + ): + + model = models.VirtualLabInstance + queryset = model.objects.all() + serializer_class = serializers.VirtualLabInstanceSerializer + + def get_queryset(self): + vlab_slug = self.request.query_params.get('vlab_slug', None) + if vlab_slug: + return self.model.objects.filter(vlab__slug=vlab_slug) + else: + return self.model.objects.all() diff --git a/vreapis/vreapis/urls.py b/vreapis/vreapis/urls.py index 45f73e6..16312f2 100644 --- a/vreapis/vreapis/urls.py +++ b/vreapis/vreapis/urls.py @@ -16,7 +16,7 @@ from django.contrib import admin from django.urls import path, include from rest_framework import routers -from virtual_labs.views import VirtualLabViewSet +from virtual_labs.views import VirtualLabViewSet, VirtualLabInstanceViewSet from cells.views import CellsViewSet from workflows.views import WorkflowViewSet from data_products.views import DataProductsViewSet, GeoDataProductsViewSet @@ -25,13 +25,11 @@ from vreapis.settings.base import BASE_PATH - -from vreapis.settings.base import BASE_PATH - admin.site.site_header = 'Virtual Labs Administration' router = routers.DefaultRouter() router.register(r'vlabs', VirtualLabViewSet) +router.register(r'vlab_instances', VirtualLabInstanceViewSet) router.register(r'workflows', WorkflowViewSet) router.register(r'cells', CellsViewSet) router.register(r'dataprods', DataProductsViewSet) From b8fdebcd89d121795fd0dbae5a9f6191de4810d0 Mon Sep 17 00:00:00 2001 From: Gabriel Pelouze Date: Wed, 20 Sep 2023 11:47:19 +0200 Subject: [PATCH 2/2] list instances on vlab pages --- vre-panel/components/VLabDescription.tsx | 54 +-------- vre-panel/components/VLabInstances.tsx | 140 +++++++++++++++++++++++ vre-panel/pages/vlabs/[slug].tsx | 60 ++++++++-- vre-panel/tailwind.config.js | 6 + vre-panel/types/vlab.d.ts | 7 ++ 5 files changed, 210 insertions(+), 57 deletions(-) create mode 100644 vre-panel/components/VLabInstances.tsx create mode 100644 vre-panel/types/vlab.d.ts diff --git a/vre-panel/components/VLabDescription.tsx b/vre-panel/components/VLabDescription.tsx index 07827a6..7996452 100644 --- a/vre-panel/components/VLabDescription.tsx +++ b/vre-panel/components/VLabDescription.tsx @@ -1,52 +1,11 @@ -import React, {useEffect, useState} from "react"; -import getConfig from "next/config"; -import {JWT} from "next-auth/jwt"; +import {VLab} from "../types/vlab"; type Props = { - slug: string | string[] | undefined, - isAuthenticated: boolean, - token: JWT, + vlab: VLab, + backendError: boolean, } -const VLabDescription: React.FC = ({slug, isAuthenticated, token}) => { - - const {publicRuntimeConfig} = getConfig() - - const vlabPlaceholder = { - title: "Loading ..", - slug: "", - description: "Loading ..", - endpoint: "" - } - - const [vlab, setVlab] = useState(vlabPlaceholder) - const [backendError, setBackendError] = useState(false) - - const fetchVlab = async () => { - - var requestOptions: RequestInit = { - method: "GET", - headers: { - "Authorization": "Bearer: " + token.accessToken - }, - }; - - const apiUrl = `${window.location.origin}/${publicRuntimeConfig.apiBasePath}` - const res = await fetch(`${apiUrl}/vlabs/${slug}`, requestOptions); - try { - const dat = await res.json() - setVlab(dat) - } catch (e) { - console.log(e) - setBackendError(true) - } - } - - useEffect(() => { - if (isAuthenticated) { - Promise.all([fetchVlab()]) - } - }, [isAuthenticated]); +const VLabDescription: React.FC = ({vlab, backendError}) => { return (
@@ -57,11 +16,6 @@ const VLabDescription: React.FC = ({slug, isAuthenticated, token}) => { ) : ( <>

{vlab.title}

- - -

{vlab.description}

)} diff --git a/vre-panel/components/VLabInstances.tsx b/vre-panel/components/VLabInstances.tsx new file mode 100644 index 0000000..16d1dd9 --- /dev/null +++ b/vre-panel/components/VLabInstances.tsx @@ -0,0 +1,140 @@ +import React, {useEffect, useState} from "react"; +import getConfig from "next/config"; +import {JWT} from "next-auth/jwt"; +import {VLab} from "../types/vlab"; +import {useSession} from "next-auth/react"; + +type Props = { + vlab: VLab, + slug: string | string[] | undefined, + isAuthenticated: boolean, + token: JWT, +} + +interface VLabInstance { + vlab: string, + username: string, +} + +const VLabInstances: React.FC = ({vlab, slug, isAuthenticated, token}) => { + + const {publicRuntimeConfig} = getConfig() + + const session = useSession() + + const [vlabInstances, setVlabInstances] = useState>([]) + const [backendError, setBackendError] = useState(false) + const [hideInstance, setHideInstance] = useState(false) + + const registerInstance = async () => { + if ( + hideInstance + || (session.status != "authenticated") + ) { + return + } + + const username = session.data.user.name + + const requestOptions: RequestInit = { + method: "POST", + headers: { + "Authorization": "Bearer: " + token.accessToken, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + "vlab": slug, + "username": username, + }), + } + + const apiUrl = `${window.location.origin}/${publicRuntimeConfig.apiBasePath}` + return fetch(`${apiUrl}/vlab_instances/`, requestOptions); + } + + const fetchVlabInstances = async () => { + + var requestOptions: RequestInit = { + method: "GET", + headers: { + "Authorization": "Bearer: " + token.accessToken + }, + }; + + const apiUrl = `${window.location.origin}/${publicRuntimeConfig.apiBasePath}` + const res = await fetch(`${apiUrl}/vlab_instances/?vlab_slug=${slug}`, requestOptions); + try { + const dat = await res.json() + setVlabInstances(dat) + } catch (e) { + console.log(e) + setBackendError(true) + } + } + + useEffect(() => { + if (isAuthenticated) { + Promise.all([fetchVlabInstances()]) + } + }, [isAuthenticated]); + + return ( +
+

Instances

+ {backendError || ( + <> +

+ {vlabInstances.length} instance{vlabInstances.length != 1 && "s"} +

+
    + {vlabInstances.map((vlab_instance) => { + return ( +
  • + {vlab_instance.username} +
  • + ) + })} +
+
+
+ { + setHideInstance(e.target.checked) + } + } + /> + +
+ +
+ + + )} +
+ ) +} + +export default VLabInstances diff --git a/vre-panel/pages/vlabs/[slug].tsx b/vre-panel/pages/vlabs/[slug].tsx index 82d3a66..dc3cc02 100644 --- a/vre-panel/pages/vlabs/[slug].tsx +++ b/vre-panel/pages/vlabs/[slug].tsx @@ -1,10 +1,13 @@ import {getToken} from "next-auth/jwt"; import {useRouter} from "next/router"; import VLabDescription from "../../components/VLabDescription"; -import React from "react"; +import React, {useEffect, useState} from "react"; import VLAbAssets from "../../components/VLAbAssets"; import PageLayout from "../../components/PageLayout"; import useAuth from "../auth/useAuth"; +import VLabInstances from "../../components/VLabInstances"; +import getConfig from "next/config"; +import {VLab} from "../../types/vlab"; interface VLabDetailsProps { @@ -13,20 +16,63 @@ interface VLabDetailsProps { const VLabDetails: React.FC = ({token}) => { + const {publicRuntimeConfig} = getConfig() + + const vlabPlaceholder: VLab = { + title: "Loading ..", + slug: "", + description: "Loading ..", + endpoint: "" + } + const isAuthenticated = useAuth(true); const router = useRouter(); const {slug} = router.query; + const [vlab, setVlab] = useState(vlabPlaceholder) + const [backendError, setBackendError] = useState(false) + + const fetchVlab = async () => { + + var requestOptions: RequestInit = { + method: "GET", + headers: { + "Authorization": "Bearer: " + token.accessToken + }, + }; + + const apiUrl = `${window.location.origin}/${publicRuntimeConfig.apiBasePath}` + const res = await fetch(`${apiUrl}/vlabs/${slug}`, requestOptions); + try { + const dat = await res.json() + setVlab(dat) + } catch (e) { + console.log(e) + setBackendError(true) + } + } + + useEffect(() => { + if (isAuthenticated) { + Promise.all([fetchVlab()]) + } + }, [isAuthenticated]); + + return ( -
- -
+
+ +
+ +
+ +
-
- -
+
+ +
) diff --git a/vre-panel/tailwind.config.js b/vre-panel/tailwind.config.js index c20a4ac..27f2a68 100644 --- a/vre-panel/tailwind.config.js +++ b/vre-panel/tailwind.config.js @@ -14,6 +14,12 @@ module.exports = { onSecondary: '#ffffff', secondaryContainer: '#FFBD85', onSecondaryContainer: '#000000', + tertiary: '#009138', + onTertiary: '#ffffff', + quaternary: '#F4992B', + onQuaternary: '#000000', + quinary: '#009399', + onQuinary: '#ffffff', surface: '#ffffff', onSurface: '#333333', surfaceContainer: '#f2f2f2', diff --git a/vre-panel/types/vlab.d.ts b/vre-panel/types/vlab.d.ts new file mode 100644 index 0000000..e0fce2b --- /dev/null +++ b/vre-panel/types/vlab.d.ts @@ -0,0 +1,7 @@ + +export interface VLab { + title: string, + slug: string, + description: string, + endpoint: string, +}