From 43888cf71492d30f1f7621914a593a7b310d584b Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Tue, 2 Apr 2024 21:49:33 +0200 Subject: [PATCH 01/18] template ref crd initial --- .../api/v1alpha1/template_store_types.go | 43 +++++++++ .../api/v1alpha1/zz_generated.deepcopy.go | 58 ++++++++++++ .../bases/cyclops-ui.com_templatestores.yaml | 51 ++++++++++ .../cluster/k8sclient/templatestore.go | 10 ++ cyclops-ctrl/internal/cluster/v1alpha1/api.go | 7 ++ .../cluster/v1alpha1/templatestore.go | 44 +++++++++ cyclops-ctrl/internal/controller/templates.go | 14 +++ cyclops-ctrl/internal/handler/handler.go | 3 + cyclops-ctrl/internal/mapper/templatestore.go | 23 +++++ .../internal/models/dto/templatestore.go | 6 ++ .../src/components/pages/new_module.tsx | 94 +++++++++++++++++-- 11 files changed, 344 insertions(+), 9 deletions(-) create mode 100644 cyclops-ctrl/api/v1alpha1/template_store_types.go create mode 100644 cyclops-ctrl/config/crd/bases/cyclops-ui.com_templatestores.yaml create mode 100644 cyclops-ctrl/internal/cluster/k8sclient/templatestore.go create mode 100644 cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go create mode 100644 cyclops-ctrl/internal/mapper/templatestore.go create mode 100644 cyclops-ctrl/internal/models/dto/templatestore.go diff --git a/cyclops-ctrl/api/v1alpha1/template_store_types.go b/cyclops-ctrl/api/v1alpha1/template_store_types.go new file mode 100644 index 00000000..890e4406 --- /dev/null +++ b/cyclops-ctrl/api/v1alpha1/template_store_types.go @@ -0,0 +1,43 @@ +/* +Copyright 2023. + +Licensed 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. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +//+kubebuilder:object:root=true + +// TemplateStore holds reference to a template that can be offered as a starting point +type TemplateStore struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TemplateRef `json:"spec,omitempty"` +} + +//+kubebuilder:object:root=true + +// TemplateStoreList contains a list of TemplateStore +type TemplateStoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []TemplateStore `json:"items"` +} diff --git a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go index e25e120b..b3dc6427 100644 --- a/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go +++ b/cyclops-ctrl/api/v1alpha1/zz_generated.deepcopy.go @@ -287,3 +287,61 @@ func (in *TemplateRef) DeepCopy() *TemplateRef { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TemplateStore) DeepCopyInto(out *TemplateStore) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateStore. +func (in *TemplateStore) DeepCopy() *TemplateStore { + if in == nil { + return nil + } + out := new(TemplateStore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TemplateStore) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TemplateStoreList) DeepCopyInto(out *TemplateStoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]TemplateStore, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplateStoreList. +func (in *TemplateStoreList) DeepCopy() *TemplateStoreList { + if in == nil { + return nil + } + out := new(TemplateStoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TemplateStoreList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templatestores.yaml b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templatestores.yaml new file mode 100644 index 00000000..b2df7e5b --- /dev/null +++ b/cyclops-ctrl/config/crd/bases/cyclops-ui.com_templatestores.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + creationTimestamp: null + name: templatestores.cyclops-ui.com +spec: + group: cyclops-ui.com + names: + kind: TemplateStore + listKind: TemplateStoreList + plural: templatestores + singular: templatestore + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TemplateStore holds reference to a template that can be offered + as a starting point + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + path: + type: string + repo: + type: string + version: + type: string + required: + - path + - repo + - version + type: object + type: object + served: true + storage: true diff --git a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go new file mode 100644 index 00000000..73342ff0 --- /dev/null +++ b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go @@ -0,0 +1,10 @@ +package k8sclient + +import ( + cyclopsv1alpha1 "github.com/cyclops-ui/cycops-ctrl/api/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (k *KubernetesClient) ListTemplateStore() ([]cyclopsv1alpha1.TemplateStore, error) { + return k.moduleset.TemplateStore(cyclopsNamespace).List(metav1.ListOptions{}) +} diff --git a/cyclops-ctrl/internal/cluster/v1alpha1/api.go b/cyclops-ctrl/internal/cluster/v1alpha1/api.go index abbde5ee..6a6912c3 100644 --- a/cyclops-ctrl/internal/cluster/v1alpha1/api.go +++ b/cyclops-ctrl/internal/cluster/v1alpha1/api.go @@ -40,3 +40,10 @@ func (c *CyclopsV1Alpha1Client) TemplateAuthRules(namespace string) TemplateAuth ns: namespace, } } + +func (c *CyclopsV1Alpha1Client) TemplateStore(namespace string) TemplateStoreInterface { + return &templateStoreClient{ + restClient: c.restClient, + ns: namespace, + } +} diff --git a/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go b/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go new file mode 100644 index 00000000..6888bb94 --- /dev/null +++ b/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go @@ -0,0 +1,44 @@ +package v1alpha1 + +import ( + "context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + + cyclopsv1alpha1 "github.com/cyclops-ui/cycops-ctrl/api/v1alpha1" +) + +type TemplateStoreInterface interface { + List(opts metav1.ListOptions) ([]cyclopsv1alpha1.TemplateStore, error) + Get(name string) (*cyclopsv1alpha1.TemplateStore, error) +} + +type templateStoreClient struct { + restClient rest.Interface + ns string +} + +func (c *templateStoreClient) List(opts metav1.ListOptions) ([]cyclopsv1alpha1.TemplateStore, error) { + result := cyclopsv1alpha1.TemplateStoreList{} + err := c.restClient. + Get(). + Namespace(c.ns). + Resource("templatestores"). + Do(context.Background()). + Into(&result) + + return result.Items, err +} + +func (c *templateStoreClient) Get(name string) (*cyclopsv1alpha1.TemplateStore, error) { + result := cyclopsv1alpha1.TemplateStore{} + err := c.restClient. + Get(). + Namespace(c.ns). + Resource("templatestores"). + Name(name). + Do(context.Background()). + Into(&result) + + return &result, err +} diff --git a/cyclops-ctrl/internal/controller/templates.go b/cyclops-ctrl/internal/controller/templates.go index 01a78d0a..1c7c3bd0 100644 --- a/cyclops-ctrl/internal/controller/templates.go +++ b/cyclops-ctrl/internal/controller/templates.go @@ -178,3 +178,17 @@ func (c *Templates) GetTemplateInitialValues(ctx *gin.Context) { ctx.Data(http.StatusOK, gin.MIMEJSON, initial) } + +func (c *Templates) ListTemplatesStore(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + + store, err := c.kubernetesClient.ListTemplateStore() + if err != nil { + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error fetching templates store", err.Error())) + return + } + + storeDTO := mapper.TemplateStoreListToDTO(store) + + ctx.JSON(http.StatusOK, storeDTO) +} diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go index dd5c4181..b9e78ed8 100644 --- a/cyclops-ctrl/internal/handler/handler.go +++ b/cyclops-ctrl/internal/handler/handler.go @@ -56,6 +56,9 @@ func (h *Handler) Start() error { h.router.GET("/templates", templatesController.GetTemplate) h.router.GET("/templates/initial", templatesController.GetTemplateInitialValues) + // templates store + h.router.GET("/templates/store", templatesController.ListTemplatesStore) + // modules h.router.GET("/modules/:name", modulesController.GetModule) h.router.GET("/modules/list", modulesController.ListModules) diff --git a/cyclops-ctrl/internal/mapper/templatestore.go b/cyclops-ctrl/internal/mapper/templatestore.go new file mode 100644 index 00000000..d64b087f --- /dev/null +++ b/cyclops-ctrl/internal/mapper/templatestore.go @@ -0,0 +1,23 @@ +package mapper + +import ( + cyclopsv1alpha1 "github.com/cyclops-ui/cycops-ctrl/api/v1alpha1" + "github.com/cyclops-ui/cycops-ctrl/internal/models/dto" +) + +func TemplateStoreListToDTO(store []cyclopsv1alpha1.TemplateStore) []dto.TemplateStore { + out := make([]dto.TemplateStore, 0, len(store)) + + for _, templateStore := range store { + out = append(out, dto.TemplateStore{ + Name: templateStore.Name, + TemplateRef: dto.Template{ + URL: templateStore.Spec.URL, + Path: templateStore.Spec.Path, + Version: templateStore.Spec.Version, + }, + }) + } + + return out +} diff --git a/cyclops-ctrl/internal/models/dto/templatestore.go b/cyclops-ctrl/internal/models/dto/templatestore.go new file mode 100644 index 00000000..053073b8 --- /dev/null +++ b/cyclops-ctrl/internal/models/dto/templatestore.go @@ -0,0 +1,6 @@ +package dto + +type TemplateStore struct { + Name string `json:"name"` + TemplateRef Template `json:"ref"` +} diff --git a/cyclops-ui/src/components/pages/new_module.tsx b/cyclops-ui/src/components/pages/new_module.tsx index 729c0fac..011839dc 100644 --- a/cyclops-ui/src/components/pages/new_module.tsx +++ b/cyclops-ui/src/components/pages/new_module.tsx @@ -41,12 +41,22 @@ import "ace-builds/src-noconflict/mode-typescript"; import "ace-builds/src-noconflict/snippets/yaml"; import { numberInputValidators } from "../../utils/validators/number"; import { stringInputValidators } from "../../utils/validators/string"; +import {Option} from "antd/es/mentions"; const { Title } = Typography; const layout = { wrapperCol: { span: 16 }, }; +interface templateStoreOption { + name: string, + ref: { + repo: string, + path: string, + version: string, + } +} + const NewModule = () => { const [loading, setLoading] = useState(false); const [config, setConfig] = useState({ @@ -91,6 +101,8 @@ const NewModule = () => { const [loadingValuesFile, setLoadingValuesFile] = useState(false); const [loadingValuesModal, setLoadingValuesModal] = useState(false); + const [templateStore, setTemplateStore] = useState([]); + const history = useNavigate(); const [form] = Form.useForm(); @@ -112,6 +124,8 @@ const NewModule = () => { window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_VERSION ); } + + loadTemplateStore() }, []); useEffect(() => { @@ -391,6 +405,61 @@ const NewModule = () => { setActiveCollapses(new Map()); }; + const loadTemplateStore = async () => { + await axios + .get( + `/api/templates/store` + ) + .then((res) => { + setTemplateStore(res.data); + }) + .catch(function (error) { + setLoadingTemplate(false); + if (error.response === undefined) { + setError({ + message: String(error), + description: + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } else { + setError({ + message: error.message, + description: error.response.data, + }); + } + }); + }; + + const findTemplateStoreSelected = (name: string) => { + for (let ts of templateStore) { + if (ts.name === name) { + return ts + } + } + + return null + } + + const onTemplateStoreSelected = (v: string) => { + const ts = findTemplateStoreSelected(v) + if (ts === null) { + return + } + + setTemplate({ + repo: ts.ref.repo, + path: ts.ref.path, + version: ts.ref.version, + }); + + loadTemplate( + ts.ref.repo, + ts.ref.path, + ts.ref.version, + ); + } + const onLoadFromFile = () => { setLoadingValuesFile(true); setLoadedValues(""); @@ -1082,9 +1151,7 @@ const NewModule = () => { version: template.version, }); }} - defaultValue={ - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_REPO - } + value={template.repo} /> {" / "} { version: template.version, }); }} - defaultValue={ - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_PATH - } + value={template.path} /> {" @ "} { version: value.target.value, }); }} - defaultValue={ - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_VERSION - } + value={template.version} /> {" "} + + + Module name From 926b84008c49f71ffd599e5e778d32d120e42f01 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Wed, 3 Apr 2024 22:52:58 +0200 Subject: [PATCH 02/18] templates store ui --- cyclops-ui/src/components/layouts/Sidebar.tsx | 7 +- .../src/components/pages/TemplateStore.tsx | 79 +++++++++++++++++++ cyclops-ui/src/routes/PathConstants.tsx | 1 + cyclops-ui/src/routes/index.tsx | 2 + 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 cyclops-ui/src/components/pages/TemplateStore.tsx diff --git a/cyclops-ui/src/components/layouts/Sidebar.tsx b/cyclops-ui/src/components/layouts/Sidebar.tsx index 08abafcf..0698a666 100644 --- a/cyclops-ui/src/components/layouts/Sidebar.tsx +++ b/cyclops-ui/src/components/layouts/Sidebar.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Button, Menu, MenuProps } from "antd"; -import { AppstoreAddOutlined, HddOutlined, BugFilled } from "@ant-design/icons"; +import {AppstoreAddOutlined, HddOutlined, BugFilled, SnippetsOutlined} from "@ant-design/icons"; import { useLocation } from "react-router"; import PathConstants from "../../routes/PathConstants"; import { Link } from "react-router-dom"; @@ -19,6 +19,11 @@ const SideNav = () => { icon: , key: "nodes", }, + { + label: Templates, + icon: , + key: "templates", + }, ]; return ( diff --git a/cyclops-ui/src/components/pages/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore.tsx new file mode 100644 index 00000000..e9de8017 --- /dev/null +++ b/cyclops-ui/src/components/pages/TemplateStore.tsx @@ -0,0 +1,79 @@ +import React, { useEffect, useState } from "react"; +import {Col, Table, Typography, Alert, Row} from "antd"; +import axios from "axios"; +import Title from "antd/es/typography/Title"; + +const TemplateStore = () => { + const [templates, setTemplates] = useState([]); + const [error, setError] = useState({ + message: "", + description: "", + }); + + useEffect(() => { + axios + .get(`/api/templates/store`) + .then((res) => { + setTemplates(res.data); + }) + .catch((error) => { + if (error?.response?.data) { + setError({ + message: error.response.data.message || String(error), + description: error.response.data.description || "Check if Cyclops backend is available on: " + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } else { + setError({ + message: String(error), + description: + "Check if Cyclops backend is available on: " + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } + }); + }, []); + + return ( +
+ {error.message.length !== 0 && ( + { + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )} + + + Templates: {templates.length} + + + + + + + + +
+ +
+ ); +}; + +export default TemplateStore; diff --git a/cyclops-ui/src/routes/PathConstants.tsx b/cyclops-ui/src/routes/PathConstants.tsx index 969b93b8..e272b1ad 100644 --- a/cyclops-ui/src/routes/PathConstants.tsx +++ b/cyclops-ui/src/routes/PathConstants.tsx @@ -7,6 +7,7 @@ const PathConstants = { MODULE_ROLLBACK: "/modules/:moduleName/rollback", NODES: "/nodes", NODE_GET: "/nodes/:nodeName", + TEMPLATES: "/templates", }; export default PathConstants; diff --git a/cyclops-ui/src/routes/index.tsx b/cyclops-ui/src/routes/index.tsx index e832074b..9a9dd708 100644 --- a/cyclops-ui/src/routes/index.tsx +++ b/cyclops-ui/src/routes/index.tsx @@ -13,6 +13,7 @@ const Nodes = React.lazy(() => import("../components/pages/nodes")); const NodeDetails = React.lazy( () => import("../components/pages/node_details") ); +const Templates = React.lazy(() => import("../components/pages/TemplateStore")); const routes = [ { path: PathConstants.HOME, element: }, @@ -23,6 +24,7 @@ const routes = [ { path: PathConstants.MODULE_ROLLBACK, element: }, { path: PathConstants.NODES, element: }, { path: PathConstants.NODE_GET, element: }, + { path: PathConstants.TEMPLATES, element: }, ]; export default routes; From 82f6914b35f8853411b6615388e1a6bdb1b4f038 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Fri, 5 Apr 2024 18:15:25 +0200 Subject: [PATCH 03/18] implement template store management --- .../cluster/k8sclient/templatestore.go | 9 ++ .../cluster/v1alpha1/templatestore.go | 54 ++++++++++ cyclops-ctrl/internal/controller/templates.go | 20 ++++ cyclops-ctrl/internal/handler/handler.go | 1 + cyclops-ctrl/internal/mapper/templatestore.go | 18 ++++ cyclops-ctrl/internal/models/dto/modules.go | 6 +- .../internal/models/dto/templatestore.go | 2 +- cyclops-ui/src/components/layouts/Sidebar.tsx | 21 +++- .../src/components/layouts/styles.module.css | 3 + .../src/components/pages/TemplateStore.tsx | 100 +++++++++++++++++- 10 files changed, 225 insertions(+), 9 deletions(-) create mode 100644 cyclops-ui/src/components/layouts/styles.module.css diff --git a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go index 73342ff0..e9bbb5c1 100644 --- a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go +++ b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go @@ -8,3 +8,12 @@ import ( func (k *KubernetesClient) ListTemplateStore() ([]cyclopsv1alpha1.TemplateStore, error) { return k.moduleset.TemplateStore(cyclopsNamespace).List(metav1.ListOptions{}) } + +func (k *KubernetesClient) CreateTemplateStore(ts *cyclopsv1alpha1.TemplateStore) error { + _, err := k.moduleset.TemplateStore(cyclopsNamespace).Create(ts) + return err +} + +func (k *KubernetesClient) DeleteTemplateStore(name string) error { + return k.moduleset.TemplateStore(cyclopsNamespace).Delete(name) +} diff --git a/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go b/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go index 6888bb94..3ce7cdd3 100644 --- a/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go +++ b/cyclops-ctrl/internal/cluster/v1alpha1/templatestore.go @@ -3,7 +3,9 @@ package v1alpha1 import ( "context" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/rest" + "time" cyclopsv1alpha1 "github.com/cyclops-ui/cycops-ctrl/api/v1alpha1" ) @@ -11,6 +13,10 @@ import ( type TemplateStoreInterface interface { List(opts metav1.ListOptions) ([]cyclopsv1alpha1.TemplateStore, error) Get(name string) (*cyclopsv1alpha1.TemplateStore, error) + Create(*cyclopsv1alpha1.TemplateStore) (*cyclopsv1alpha1.TemplateStore, error) + Update(*cyclopsv1alpha1.TemplateStore) (*cyclopsv1alpha1.TemplateStore, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + Delete(name string) error } type templateStoreClient struct { @@ -42,3 +48,51 @@ func (c *templateStoreClient) Get(name string) (*cyclopsv1alpha1.TemplateStore, return &result, err } + +func (c *templateStoreClient) Create(project *cyclopsv1alpha1.TemplateStore) (*cyclopsv1alpha1.TemplateStore, error) { + result := cyclopsv1alpha1.TemplateStore{} + err := c.restClient. + Post(). + Namespace(c.ns). + Resource("templatestores"). + Body(project). + Do(context.Background()). + Into(&result) + + return &result, err +} + +func (c *templateStoreClient) Update(templateStore *cyclopsv1alpha1.TemplateStore) (project *cyclopsv1alpha1.TemplateStore, err error) { + result := &cyclopsv1alpha1.TemplateStore{} + err = c.restClient.Put(). + Namespace(c.ns). + Resource("templatestores"). + Name(templateStore.Name). + Body(templateStore). + Do(context.TODO()). + Into(result) + return +} + +func (c *templateStoreClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.restClient.Get(). + Namespace(c.ns). + Resource("templatestores"). + Timeout(timeout). + Watch(context.Background()) +} + +func (c *templateStoreClient) Delete(name string) error { + return c.restClient. + Delete(). + Namespace(c.ns). + Resource("templatestores"). + Name(name). + Do(context.Background()). + Error() +} diff --git a/cyclops-ctrl/internal/controller/templates.go b/cyclops-ctrl/internal/controller/templates.go index 1c7c3bd0..23bcabd3 100644 --- a/cyclops-ctrl/internal/controller/templates.go +++ b/cyclops-ctrl/internal/controller/templates.go @@ -192,3 +192,23 @@ func (c *Templates) ListTemplatesStore(ctx *gin.Context) { ctx.JSON(http.StatusOK, storeDTO) } + +func (c *Templates) CreateTemplatesStore(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + + var templateStore *dto.TemplateStore + if err := ctx.ShouldBind(&templateStore); err != nil { + fmt.Println("error binding request", templateStore) + ctx.JSON(http.StatusBadRequest, dto.NewError("Error binding request", err.Error())) + return + } + + k8sTemplateStore := mapper.DTOToTemplateStoreList(*templateStore) + + if err := c.kubernetesClient.CreateTemplateStore(k8sTemplateStore); err != nil { + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error creating module", err.Error())) + return + } + + ctx.Status(http.StatusCreated) +} diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go index b9e78ed8..c5041513 100644 --- a/cyclops-ctrl/internal/handler/handler.go +++ b/cyclops-ctrl/internal/handler/handler.go @@ -58,6 +58,7 @@ func (h *Handler) Start() error { // templates store h.router.GET("/templates/store", templatesController.ListTemplatesStore) + h.router.PUT("/templates/store", templatesController.CreateTemplatesStore) // modules h.router.GET("/modules/:name", modulesController.GetModule) diff --git a/cyclops-ctrl/internal/mapper/templatestore.go b/cyclops-ctrl/internal/mapper/templatestore.go index d64b087f..beaf0a69 100644 --- a/cyclops-ctrl/internal/mapper/templatestore.go +++ b/cyclops-ctrl/internal/mapper/templatestore.go @@ -3,6 +3,7 @@ package mapper import ( cyclopsv1alpha1 "github.com/cyclops-ui/cycops-ctrl/api/v1alpha1" "github.com/cyclops-ui/cycops-ctrl/internal/models/dto" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TemplateStoreListToDTO(store []cyclopsv1alpha1.TemplateStore) []dto.TemplateStore { @@ -21,3 +22,20 @@ func TemplateStoreListToDTO(store []cyclopsv1alpha1.TemplateStore) []dto.Templat return out } + +func DTOToTemplateStoreList(store dto.TemplateStore) *cyclopsv1alpha1.TemplateStore { + return &cyclopsv1alpha1.TemplateStore{ + TypeMeta: metav1.TypeMeta{ + Kind: "TemplateStore", + APIVersion: "cyclops-ui.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: store.Name, + }, + Spec: cyclopsv1alpha1.TemplateRef{ + URL: store.TemplateRef.URL, + Path: store.TemplateRef.Path, + Version: store.TemplateRef.Version, + }, + } +} diff --git a/cyclops-ctrl/internal/models/dto/modules.go b/cyclops-ctrl/internal/models/dto/modules.go index 10f738d0..f2e5a761 100644 --- a/cyclops-ctrl/internal/models/dto/modules.go +++ b/cyclops-ctrl/internal/models/dto/modules.go @@ -10,9 +10,9 @@ type Module struct { } type Template struct { - URL string `json:"repo"` - Path string `json:"path"` - Version string `json:"version"` + URL string `json:"repo" binding:"required"` + Path string `json:"path" binding:"required"` + Version string `json:"version" binding:"required"` } type TemplatesResponse struct { diff --git a/cyclops-ctrl/internal/models/dto/templatestore.go b/cyclops-ctrl/internal/models/dto/templatestore.go index 053073b8..8fb19c19 100644 --- a/cyclops-ctrl/internal/models/dto/templatestore.go +++ b/cyclops-ctrl/internal/models/dto/templatestore.go @@ -1,6 +1,6 @@ package dto type TemplateStore struct { - Name string `json:"name"` + Name string `json:"name" binding:"required"` TemplateRef Template `json:"ref"` } diff --git a/cyclops-ui/src/components/layouts/Sidebar.tsx b/cyclops-ui/src/components/layouts/Sidebar.tsx index 2b2aaf34..2195d0e6 100644 --- a/cyclops-ui/src/components/layouts/Sidebar.tsx +++ b/cyclops-ui/src/components/layouts/Sidebar.tsx @@ -1,9 +1,16 @@ import React from "react"; import { Button, Menu, MenuProps } from "antd"; -import {AppstoreAddOutlined, HddOutlined, BugFilled, SnippetsOutlined} from "@ant-design/icons"; +import { + AppstoreAddOutlined, + HddOutlined, + BugFilled, + SnippetsOutlined, + GithubFilled +} from "@ant-design/icons"; import { useLocation } from "react-router"; import PathConstants from "../../routes/PathConstants"; import { Link } from "react-router-dom"; +import styles from "./styles.module.css" const SideNav = () => { const location = useLocation().pathname.split("/")[1]; @@ -26,6 +33,14 @@ const SideNav = () => { }, ]; + const tagChangelogLink = (tag: string) => { + if (tag === "v0.0.0") { + return "https://github.com/cyclops-ui/cyclops/releases" + } + + return "https://github.com/cyclops-ui/cyclops/releases/tag/" + tag + } + return (
{ margin: "25px", marginTop: "0", }}> - {window.__RUNTIME_CONFIG__.REACT_APP_VERSION} + + {window.__RUNTIME_CONFIG__.REACT_APP_VERSION} +
); diff --git a/cyclops-ui/src/components/layouts/styles.module.css b/cyclops-ui/src/components/layouts/styles.module.css new file mode 100644 index 00000000..6c306ed9 --- /dev/null +++ b/cyclops-ui/src/components/layouts/styles.module.css @@ -0,0 +1,3 @@ +.taglink { + color: #FFF; +} diff --git a/cyclops-ui/src/components/pages/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore.tsx index e9de8017..afa179c8 100644 --- a/cyclops-ui/src/components/pages/TemplateStore.tsx +++ b/cyclops-ui/src/components/pages/TemplateStore.tsx @@ -1,16 +1,20 @@ import React, { useEffect, useState } from "react"; -import {Col, Table, Typography, Alert, Row} from "antd"; +import {Col, Table, Typography, Alert, Row, Button, Tabs, Modal, Form, Input} from "antd"; import axios from "axios"; import Title from "antd/es/typography/Title"; const TemplateStore = () => { const [templates, setTemplates] = useState([]); - const [error, setError] = useState({ + const [newTemplateModal, setNewTemplateModal] = useState(false) + const [confirmLoading, setConfirmLoading] = useState(false); + const [error, setError] = useState({ message: "", description: "", }); - useEffect(() => { + const [form] = Form.useForm(); + + useEffect(() => { axios .get(`/api/templates/store`) .then((res) => { @@ -32,6 +36,41 @@ const TemplateStore = () => { }); }, []); + const handleOK = () => { + form.submit(); + } + + const handleSubmit = (values: any) => { + setConfirmLoading(true); + + axios + .put(`/api/templates/store`, values) + .then((res) => { + setNewTemplateModal(false); + setConfirmLoading(true); + window.location.href = "/templates"; + }) + .catch((error) => { + if (error.response === undefined) { + setError({ + message: String(error), + description: + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } else { + setError({ + message: error.message, + description: error.response.data, + }); + } + }); + } + + const handleCancelModal = () => { + setNewTemplateModal(false); + } + return (
{error.message.length !== 0 && ( @@ -53,6 +92,15 @@ const TemplateStore = () => { Templates: {templates.length} + + + @@ -72,6 +120,52 @@ const TemplateStore = () => {
+ +
+ + + + + + + + + + + + + + + +
+
); }; From e7bd630e17dfabf0a6799db1eee8956199400cb5 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sat, 6 Apr 2024 17:20:10 +0200 Subject: [PATCH 04/18] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20implement=20templ?= =?UTF-8?q?ate=20store=20deletion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cyclops-ctrl/internal/controller/templates.go | 13 +++ cyclops-ctrl/internal/handler/handler.go | 1 + .../src/components/layouts/AppLayout.tsx | 60 ++++++++------ .../src/components/layouts/styles.module.css | 4 + .../{ => TemplateStore}/TemplateStore.tsx | 81 +++++++++++++++++-- .../pages/TemplateStore/styles.module.css | 9 +++ cyclops-ui/src/routes/index.tsx | 2 +- 7 files changed, 136 insertions(+), 34 deletions(-) rename cyclops-ui/src/components/pages/{ => TemplateStore}/TemplateStore.tsx (64%) create mode 100644 cyclops-ui/src/components/pages/TemplateStore/styles.module.css diff --git a/cyclops-ctrl/internal/controller/templates.go b/cyclops-ctrl/internal/controller/templates.go index 23bcabd3..0df9c7e4 100644 --- a/cyclops-ctrl/internal/controller/templates.go +++ b/cyclops-ctrl/internal/controller/templates.go @@ -212,3 +212,16 @@ func (c *Templates) CreateTemplatesStore(ctx *gin.Context) { ctx.Status(http.StatusCreated) } + +func (c *Templates) DeleteTemplatesStore(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + + templateRefName := ctx.Param("name") + + if err := c.kubernetesClient.DeleteTemplateStore(templateRefName); err != nil { + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error deleting module", err.Error())) + return + } + + ctx.Status(http.StatusOK) +} diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go index c5041513..e4f35a1d 100644 --- a/cyclops-ctrl/internal/handler/handler.go +++ b/cyclops-ctrl/internal/handler/handler.go @@ -59,6 +59,7 @@ func (h *Handler) Start() error { // templates store h.router.GET("/templates/store", templatesController.ListTemplatesStore) h.router.PUT("/templates/store", templatesController.CreateTemplatesStore) + h.router.DELETE("/templates/store/:name", templatesController.DeleteTemplatesStore) // modules h.router.GET("/modules/:name", modulesController.GetModule) diff --git a/cyclops-ui/src/components/layouts/AppLayout.tsx b/cyclops-ui/src/components/layouts/AppLayout.tsx index a437edba..064d9d2a 100644 --- a/cyclops-ui/src/components/layouts/AppLayout.tsx +++ b/cyclops-ui/src/components/layouts/AppLayout.tsx @@ -3,43 +3,51 @@ import SideNav from "./Sidebar"; import { Suspense } from "react"; import Sider from "antd/es/layout/Sider"; import { Content, Header } from "antd/es/layout/layout"; -import { Layout } from "antd"; +import {ConfigProvider, Layout} from "antd"; export default function AppLayout() { return ( - - - - -
- - Loading...} + + + +
+ - - - - + Loading...} + > + + + + + ); } diff --git a/cyclops-ui/src/components/layouts/styles.module.css b/cyclops-ui/src/components/layouts/styles.module.css index 6c306ed9..72e3d990 100644 --- a/cyclops-ui/src/components/layouts/styles.module.css +++ b/cyclops-ui/src/components/layouts/styles.module.css @@ -1,3 +1,7 @@ .taglink { color: #FFF; } + +.taglink:hover { + color: #fe8801; +} diff --git a/cyclops-ui/src/components/pages/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx similarity index 64% rename from cyclops-ui/src/components/pages/TemplateStore.tsx rename to cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx index afa179c8..28d44df4 100644 --- a/cyclops-ui/src/components/pages/TemplateStore.tsx +++ b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx @@ -1,13 +1,17 @@ import React, { useEffect, useState } from "react"; -import {Col, Table, Typography, Alert, Row, Button, Tabs, Modal, Form, Input} from "antd"; +import {Col, Table, Typography, Alert, Row, Button, Tabs, Modal, Form, Input, Divider} from "antd"; import axios from "axios"; import Title from "antd/es/typography/Title"; +import {DeleteOutlined} from "@ant-design/icons"; +import styles from "./styles.module.css" const TemplateStore = () => { const [templates, setTemplates] = useState([]); + const [confirmDelete, setConfirmDelete] = useState("") + const [confirmDeleteInput, setConfirmDeleteInput] = useState("") const [newTemplateModal, setNewTemplateModal] = useState(false) - const [confirmLoading, setConfirmLoading] = useState(false); - const [error, setError] = useState({ + const [confirmLoading, setConfirmLoading] = useState(false); + const [error, setError] = useState({ message: "", description: "", }); @@ -47,7 +51,32 @@ const TemplateStore = () => { .put(`/api/templates/store`, values) .then((res) => { setNewTemplateModal(false); - setConfirmLoading(true); + setConfirmLoading(false); + window.location.href = "/templates"; + }) + .catch((error) => { + if (error.response === undefined) { + setError({ + message: String(error), + description: + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } else { + setError({ + message: error.message, + description: error.response.data, + }); + } + }); + } + + const deleteTemplateRef = () => { + axios + .delete(`/api/templates/store/` + confirmDelete) + .then((res) => { + setNewTemplateModal(false); + setConfirmLoading(false); window.location.href = "/templates"; }) .catch((error) => { @@ -109,7 +138,7 @@ const TemplateStore = () => { { }} /> + ( + <> + + + )} + /> { labelCol={{span: 6}} > + + { + 0} + onCancel={handleCancelModal} + width={"40%"} + footer={ + + } + > + + In order to delete this template ref, type the template ref name in the box below + { + setConfirmDeleteInput(e.target.value) + }} /> + ); }; diff --git a/cyclops-ui/src/components/pages/TemplateStore/styles.module.css b/cyclops-ui/src/components/pages/TemplateStore/styles.module.css new file mode 100644 index 00000000..0dde2cc7 --- /dev/null +++ b/cyclops-ui/src/components/pages/TemplateStore/styles.module.css @@ -0,0 +1,9 @@ +.deletetemplate { + color: red; + transform: scale(1.2); + transition: transform .2s; +} + +.deletetemplate:hover { + transform: scale(1.4); +} diff --git a/cyclops-ui/src/routes/index.tsx b/cyclops-ui/src/routes/index.tsx index 0159d85b..fb91c95f 100644 --- a/cyclops-ui/src/routes/index.tsx +++ b/cyclops-ui/src/routes/index.tsx @@ -11,7 +11,7 @@ const EditModule = React.lazy(() => import("../components/pages/EditModule")); const ModuleHistory = React.lazy(() => import("../components/pages/History")); const Nodes = React.lazy(() => import("../components/pages/Nodes")); const NodeDetails = React.lazy(() => import("../components/pages/NodeDetails")); -const Templates = React.lazy(() => import("../components/pages/TemplateStore")); +const Templates = React.lazy(() => import("../components/pages/TemplateStore/TemplateStore")); const routes = [ { path: PathConstants.HOME, element: }, From f53ff0fa37b9738393c1224fef8d9fea51551cfc Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sat, 6 Apr 2024 17:30:10 +0200 Subject: [PATCH 05/18] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F=20remove=20template?= =?UTF-8?q?=20ref=20input?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cyclops-ui/src/components/pages/NewModule.tsx | 77 ++++--------------- .../pages/TemplateStore/TemplateStore.tsx | 12 ++- 2 files changed, 24 insertions(+), 65 deletions(-) diff --git a/cyclops-ui/src/components/pages/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule.tsx index ef0bc9ef..57603b0e 100644 --- a/cyclops-ui/src/components/pages/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule.tsx @@ -1150,71 +1150,20 @@ const NewModule = () => { Module template - { - setTemplate({ - repo: value.target.value, - path: template.path, - version: template.version, - }); - }} - value={template.repo} - /> - {" / "} - { - setTemplate({ - repo: template.repo, - path: value.target.value, - version: template.version, - }); - }} - value={template.path} - /> - {" @ "} - { - setTemplate({ - repo: template.repo, - path: template.path, - version: value.target.value, - }); - }} - value={template.version} - /> - {" "} - - + + + Module name diff --git a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx index 28d44df4..3d680a9f 100644 --- a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx +++ b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx @@ -146,7 +146,17 @@ const TemplateStore = () => { return value; }} /> - + {''} + } + return value; + }} + /> ( From e9d172de0448b36655692ee11eebed9e16765fad Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sat, 6 Apr 2024 17:35:18 +0200 Subject: [PATCH 06/18] =?UTF-8?q?=F0=9F=94=A5=20template=20store=20CRD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/cyclops-install.yaml | 81 ++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index 09fa5a22..b6d5ec00 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -260,6 +260,57 @@ spec: subresources: status: {} --- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.3 + creationTimestamp: null + name: templatestores.cyclops-ui.com +spec: + group: cyclops-ui.com + names: + kind: TemplateStore + listKind: TemplateStoreList + plural: templatestores + singular: templatestore + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: TemplateStore holds reference to a template that can be offered + as a starting point + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + properties: + path: + type: string + repo: + type: string + version: + type: string + required: + - path + - repo + - version + type: object + type: object + served: true + storage: true +--- apiVersion: v1 kind: Namespace metadata: @@ -413,3 +464,33 @@ spec: targetPort: 8080 selector: app: cyclops-ctrl +--- +apiVersion: cyclops-ui.com/v1alpha1 +kind: TemplateStore +metadata: + name: demo-template + namespace: cyclops +spec: + path: demo + repo: https://github.com/cyclops-ui/templates + version: main +--- +apiVersion: cyclops-ui.com/v1alpha1 +kind: TemplateStore +metadata: + name: demo-template-test + namespace: cyclops +spec: + path: test + repo: https://github.com/cyclops-ui/templates + version: "" +--- +apiVersion: cyclops-ui.com/v1alpha1 +kind: TemplateStore +metadata: + name: demo-template-with-deps + namespace: cyclops +spec: + path: demo + repo: https://github.com/cyclops-ui/templates + version: chart-deps From 3b1c6c7feaf24ce4e2642e26f8bf2fb09518907f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Apr 2024 15:41:28 +0000 Subject: [PATCH 07/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20cyclops=20t?= =?UTF-8?q?o=20v0.3.0-rc.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/cyclops-install.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index b6d5ec00..856e5edc 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -384,7 +384,7 @@ spec: spec: containers: - name: cyclops-ui - image: cyclopsui/cyclops-ui:v0.3.0-rc.1 + image: cyclopsui/cyclops-ui:v0.3.0-rc.2 ports: - containerPort: 80 env: @@ -430,7 +430,7 @@ spec: serviceAccountName: cyclops-ctrl containers: - name: cyclops-ctrl - image: cyclopsui/cyclops-ctrl:v0.3.0-rc.1 + image: cyclopsui/cyclops-ctrl:v0.3.0-rc.2 ports: - containerPort: 8080 env: From 39a4fddac4f11e05a6d5a738e273a01c7fe5c1d1 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sat, 6 Apr 2024 17:42:14 +0200 Subject: [PATCH 08/18] =?UTF-8?q?=F0=9F=94=A5=20template=20store=20name=20?= =?UTF-8?q?rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/TemplateStore/TemplateStore.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx index 3d680a9f..4439ed11 100644 --- a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx +++ b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx @@ -190,7 +190,22 @@ const TemplateStore = () => { style={{paddingTop: "20px"}} label="Name" name={"name"} - rules={[{ required: true, message: 'Template ref is required' }]} + rules={[ + { + required: true, + message: "Template ref name is required", + }, + { + max: 63, + message: + "Template ref name must contain no more than 63 characters", + }, + { + pattern: /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, // only alphanumeric characters and hyphens, cannot start or end with a hyphen and the alpha characters can only be lowercase + message: + "Template ref name must follow the Kubernetes naming convention", + }, + ]} > From 83b9a53239ade1368e3a809db9b4bd867c53b34d Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sat, 6 Apr 2024 18:33:54 +0200 Subject: [PATCH 09/18] =?UTF-8?q?=F0=9F=94=A5=20refactor=20demo=20template?= =?UTF-8?q?=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/cyclops-install.yaml | 30 ------------------------------ install/demo-templates.yaml | 29 +++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 30 deletions(-) create mode 100644 install/demo-templates.yaml diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index 856e5edc..08064988 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -464,33 +464,3 @@ spec: targetPort: 8080 selector: app: cyclops-ctrl ---- -apiVersion: cyclops-ui.com/v1alpha1 -kind: TemplateStore -metadata: - name: demo-template - namespace: cyclops -spec: - path: demo - repo: https://github.com/cyclops-ui/templates - version: main ---- -apiVersion: cyclops-ui.com/v1alpha1 -kind: TemplateStore -metadata: - name: demo-template-test - namespace: cyclops -spec: - path: test - repo: https://github.com/cyclops-ui/templates - version: "" ---- -apiVersion: cyclops-ui.com/v1alpha1 -kind: TemplateStore -metadata: - name: demo-template-with-deps - namespace: cyclops -spec: - path: demo - repo: https://github.com/cyclops-ui/templates - version: chart-deps diff --git a/install/demo-templates.yaml b/install/demo-templates.yaml new file mode 100644 index 00000000..5b4994b6 --- /dev/null +++ b/install/demo-templates.yaml @@ -0,0 +1,29 @@ +apiVersion: cyclops-ui.com/v1alpha1 +kind: TemplateStore +metadata: + name: demo-template + namespace: cyclops +spec: + path: demo + repo: https://github.com/cyclops-ui/templates + version: main +--- +apiVersion: cyclops-ui.com/v1alpha1 +kind: TemplateStore +metadata: + name: demo-template-test + namespace: cyclops +spec: + path: test + repo: https://github.com/cyclops-ui/templates + version: "" +--- +apiVersion: cyclops-ui.com/v1alpha1 +kind: TemplateStore +metadata: + name: demo-template-with-deps + namespace: cyclops +spec: + path: demo + repo: https://github.com/cyclops-ui/templates + version: chart-deps \ No newline at end of file From b805e947a565510963e37947de4b0e3e011e134a Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 7 Apr 2024 12:37:49 +0200 Subject: [PATCH 10/18] =?UTF-8?q?=F0=9F=94=A5=20make=20template=20store=20?= =?UTF-8?q?version=20optional?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cyclops-ctrl/internal/models/dto/modules.go | 2 +- .../pages/TemplateStore/TemplateStore.tsx | 53 ++++++++++++++++--- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/cyclops-ctrl/internal/models/dto/modules.go b/cyclops-ctrl/internal/models/dto/modules.go index f2e5a761..f8fb38de 100644 --- a/cyclops-ctrl/internal/models/dto/modules.go +++ b/cyclops-ctrl/internal/models/dto/modules.go @@ -12,7 +12,7 @@ type Module struct { type Template struct { URL string `json:"repo" binding:"required"` Path string `json:"path" binding:"required"` - Version string `json:"version" binding:"required"` + Version string `json:"version"` } type TemplatesResponse struct { diff --git a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx index 4439ed11..5ba3a3e4 100644 --- a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx +++ b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx @@ -55,17 +55,21 @@ const TemplateStore = () => { window.location.href = "/templates"; }) .catch((error) => { - if (error.response === undefined) { + setConfirmLoading(false); + if (error?.response?.data) { setError({ - message: String(error), + message: error.response.data.message || String(error), description: + error.response.data.description || "Check if Cyclops backend is available on: " + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, }); } else { setError({ - message: error.message, - description: error.response.data, + message: String(error), + description: + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, }); } }); @@ -80,17 +84,20 @@ const TemplateStore = () => { window.location.href = "/templates"; }) .catch((error) => { - if (error.response === undefined) { + if (error?.response?.data) { setError({ - message: String(error), + message: error.response.data.message || String(error), description: + error.response.data.description || "Check if Cyclops backend is available on: " + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, }); } else { setError({ - message: error.message, - description: error.response.data, + message: String(error), + description: + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, }); } }); @@ -180,6 +187,21 @@ const TemplateStore = () => { confirmLoading={confirmLoading} width={"60%"} > + {error.message.length !== 0 && ( + { + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )}
{ } > + {error.message.length !== 0 && ( + { + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )} In order to delete this template ref, type the template ref name in the box below { From ef835cf4645252f6fb842eaace2a4810dd67fbfa Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Sun, 7 Apr 2024 19:08:23 +0200 Subject: [PATCH 11/18] =?UTF-8?q?=F0=9F=9A=A6=20remove=20default=20templat?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cyclops-ui/env.js | 3 --- cyclops-ui/public/env-config.js | 3 --- cyclops-ui/src/components/pages/NewModule.tsx | 17 ----------------- 3 files changed, 23 deletions(-) diff --git a/cyclops-ui/env.js b/cyclops-ui/env.js index 02c3067a..2704d675 100644 --- a/cyclops-ui/env.js +++ b/cyclops-ui/env.js @@ -2,8 +2,5 @@ window.__RUNTIME_CONFIG__ = { NODE_ENV: '${NODE_ENV}', REACT_APP_CYCLOPS_CTRL_HOST: '${REACT_APP_CYCLOPS_CTRL_HOST}', - REACT_APP_DEFAULT_TEMPLATE_REPO: '${REACT_APP_DEFAULT_TEMPLATE_REPO}', - REACT_APP_DEFAULT_TEMPLATE_PATH: '${REACT_APP_DEFAULT_TEMPLATE_PATH}', - REACT_APP_DEFAULT_TEMPLATE_VERSION: '${REACT_APP_DEFAULT_TEMPLATE_VERSION}', REACT_APP_VERSION: '${REACT_APP_VERSION}' }; diff --git a/cyclops-ui/public/env-config.js b/cyclops-ui/public/env-config.js index 5d670dad..0b4ff970 100644 --- a/cyclops-ui/public/env-config.js +++ b/cyclops-ui/public/env-config.js @@ -1,8 +1,5 @@ window.__RUNTIME_CONFIG__ = { "NODE_ENV": "development", "REACT_APP_CYCLOPS_CTRL_HOST": "http://localhost:8888", - "REACT_APP_DEFAULT_TEMPLATE_REPO": "https://github.com/cyclops-ui/templates", - "REACT_APP_DEFAULT_TEMPLATE_PATH": "test", - "REACT_APP_DEFAULT_TEMPLATE_VERSION": "", "REACT_APP_VERSION": "v0.0.0" }; diff --git a/cyclops-ui/src/components/pages/NewModule.tsx b/cyclops-ui/src/components/pages/NewModule.tsx index 57603b0e..985b54ed 100644 --- a/cyclops-ui/src/components/pages/NewModule.tsx +++ b/cyclops-ui/src/components/pages/NewModule.tsx @@ -108,23 +108,6 @@ const NewModule = () => { const [form] = Form.useForm(); useEffect(() => { - if ( - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_REPO && - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_REPO.length > 0 - ) { - setTemplate({ - repo: window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_REPO, - path: window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_PATH, - version: window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_VERSION, - }); - - loadTemplate( - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_REPO, - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_PATH, - window.__RUNTIME_CONFIG__.REACT_APP_DEFAULT_TEMPLATE_VERSION - ); - } - loadTemplateStore() }, []); From 09db8534ca088aba731b7414331c49419771ab68 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 7 Apr 2024 17:14:00 +0000 Subject: [PATCH 12/18] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20update=20cyclops=20t?= =?UTF-8?q?o=20v0.3.0-rc.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/cyclops-install.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/cyclops-install.yaml b/install/cyclops-install.yaml index 08064988..a91e60eb 100644 --- a/install/cyclops-install.yaml +++ b/install/cyclops-install.yaml @@ -384,7 +384,7 @@ spec: spec: containers: - name: cyclops-ui - image: cyclopsui/cyclops-ui:v0.3.0-rc.2 + image: cyclopsui/cyclops-ui:v0.3.0-rc.3 ports: - containerPort: 80 env: @@ -430,7 +430,7 @@ spec: serviceAccountName: cyclops-ctrl containers: - name: cyclops-ctrl - image: cyclopsui/cyclops-ctrl:v0.3.0-rc.2 + image: cyclopsui/cyclops-ctrl:v0.3.0-rc.3 ports: - containerPort: 8080 env: From 43776f02c58a24d4dbdc486cdaa26a46b54d1d39 Mon Sep 17 00:00:00 2001 From: petar-cvit Date: Mon, 8 Apr 2024 17:12:43 +0200 Subject: [PATCH 13/18] =?UTF-8?q?=F0=9F=9A=A6=20edit=20template=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cluster/k8sclient/templatestore.go | 12 + cyclops-ctrl/internal/controller/templates.go | 24 +- cyclops-ctrl/internal/handler/handler.go | 1 + cyclops-ctrl/internal/mapper/templatestore.go | 2 +- .../pages/TemplateStore/TemplateStore.tsx | 136 +++- .../pages/TemplateStore/styles.module.css | 10 + prom.yaml | 706 ++++++++++++++++++ 7 files changed, 886 insertions(+), 5 deletions(-) create mode 100644 prom.yaml diff --git a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go index e9bbb5c1..711a0fa2 100644 --- a/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go +++ b/cyclops-ctrl/internal/cluster/k8sclient/templatestore.go @@ -14,6 +14,18 @@ func (k *KubernetesClient) CreateTemplateStore(ts *cyclopsv1alpha1.TemplateStore return err } +func (k *KubernetesClient) UpdateTemplateStore(ts *cyclopsv1alpha1.TemplateStore) error { + curr, err := k.moduleset.TemplateStore(cyclopsNamespace).Get(ts.Name) + if err != nil { + return err + } + + ts.SetResourceVersion(curr.GetResourceVersion()) + + _, err = k.moduleset.TemplateStore(cyclopsNamespace).Update(ts) + return err +} + func (k *KubernetesClient) DeleteTemplateStore(name string) error { return k.moduleset.TemplateStore(cyclopsNamespace).Delete(name) } diff --git a/cyclops-ctrl/internal/controller/templates.go b/cyclops-ctrl/internal/controller/templates.go index 0df9c7e4..ff3de240 100644 --- a/cyclops-ctrl/internal/controller/templates.go +++ b/cyclops-ctrl/internal/controller/templates.go @@ -203,7 +203,7 @@ func (c *Templates) CreateTemplatesStore(ctx *gin.Context) { return } - k8sTemplateStore := mapper.DTOToTemplateStoreList(*templateStore) + k8sTemplateStore := mapper.DTOToTemplateStore(*templateStore) if err := c.kubernetesClient.CreateTemplateStore(k8sTemplateStore); err != nil { ctx.JSON(http.StatusInternalServerError, dto.NewError("Error creating module", err.Error())) @@ -213,6 +213,28 @@ func (c *Templates) CreateTemplatesStore(ctx *gin.Context) { ctx.Status(http.StatusCreated) } +func (c *Templates) EditTemplatesStore(ctx *gin.Context) { + ctx.Header("Access-Control-Allow-Origin", "*") + + var templateStore *dto.TemplateStore + if err := ctx.ShouldBind(&templateStore); err != nil { + fmt.Println("error binding request", templateStore) + ctx.JSON(http.StatusBadRequest, dto.NewError("Error binding request", err.Error())) + return + } + + templateStore.Name = ctx.Param("name") + + k8sTemplateStore := mapper.DTOToTemplateStore(*templateStore) + + if err := c.kubernetesClient.UpdateTemplateStore(k8sTemplateStore); err != nil { + ctx.JSON(http.StatusInternalServerError, dto.NewError("Error creating module", err.Error())) + return + } + + ctx.Status(http.StatusCreated) +} + func (c *Templates) DeleteTemplatesStore(ctx *gin.Context) { ctx.Header("Access-Control-Allow-Origin", "*") diff --git a/cyclops-ctrl/internal/handler/handler.go b/cyclops-ctrl/internal/handler/handler.go index e4f35a1d..b25cc1bf 100644 --- a/cyclops-ctrl/internal/handler/handler.go +++ b/cyclops-ctrl/internal/handler/handler.go @@ -59,6 +59,7 @@ func (h *Handler) Start() error { // templates store h.router.GET("/templates/store", templatesController.ListTemplatesStore) h.router.PUT("/templates/store", templatesController.CreateTemplatesStore) + h.router.POST("/templates/store/:name", templatesController.EditTemplatesStore) h.router.DELETE("/templates/store/:name", templatesController.DeleteTemplatesStore) // modules diff --git a/cyclops-ctrl/internal/mapper/templatestore.go b/cyclops-ctrl/internal/mapper/templatestore.go index beaf0a69..13a9b5ec 100644 --- a/cyclops-ctrl/internal/mapper/templatestore.go +++ b/cyclops-ctrl/internal/mapper/templatestore.go @@ -23,7 +23,7 @@ func TemplateStoreListToDTO(store []cyclopsv1alpha1.TemplateStore) []dto.Templat return out } -func DTOToTemplateStoreList(store dto.TemplateStore) *cyclopsv1alpha1.TemplateStore { +func DTOToTemplateStore(store dto.TemplateStore) *cyclopsv1alpha1.TemplateStore { return &cyclopsv1alpha1.TemplateStore{ TypeMeta: metav1.TypeMeta{ Kind: "TemplateStore", diff --git a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx index 5ba3a3e4..94b44652 100644 --- a/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx +++ b/cyclops-ui/src/components/pages/TemplateStore/TemplateStore.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import {Col, Table, Typography, Alert, Row, Button, Tabs, Modal, Form, Input, Divider} from "antd"; import axios from "axios"; import Title from "antd/es/typography/Title"; -import {DeleteOutlined} from "@ant-design/icons"; +import {DeleteOutlined, EditOutlined} from "@ant-design/icons"; import styles from "./styles.module.css" const TemplateStore = () => { @@ -11,6 +11,13 @@ const TemplateStore = () => { const [confirmDeleteInput, setConfirmDeleteInput] = useState("") const [newTemplateModal, setNewTemplateModal] = useState(false) const [confirmLoading, setConfirmLoading] = useState(false); + const [editModal, setEditModal] = useState({ + on: false, + name: "", + repo: "", + path: "", + version: "", + }) const [error, setError] = useState({ message: "", description: "", @@ -75,6 +82,39 @@ const TemplateStore = () => { }); } + const handleUpdateSubmit = (values: any) => { + setConfirmLoading(true); + + values.name = editModal.name; + + axios + .post(`/api/templates/store/` + editModal.name, values) + .then((res) => { + setNewTemplateModal(false); + setConfirmLoading(false); + window.location.href = "/templates"; + }) + .catch((error) => { + setConfirmLoading(false); + if (error?.response?.data) { + setError({ + message: error.response.data.message || String(error), + description: + error.response.data.description || + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } else { + setError({ + message: String(error), + description: + "Check if Cyclops backend is available on: " + + window.__RUNTIME_CONFIG__.REACT_APP_CYCLOPS_CTRL_HOST, + }); + } + }); + } + const deleteTemplateRef = () => { axios .delete(`/api/templates/store/` + confirmDelete) @@ -107,6 +147,20 @@ const TemplateStore = () => { setNewTemplateModal(false); } + const handleCancelEditModal = () => { + setEditModal({ + on: false, + name: "", + repo: "", + path: "", + version: "", + }); + } + + const handleCancelDeleteModal = () => { + setConfirmDelete(""); + } + return (
{error.message.length !== 0 && ( @@ -164,6 +218,25 @@ const TemplateStore = () => { return value; }} /> + ( + <> + + + )} + /> ( @@ -259,9 +332,66 @@ const TemplateStore = () => { Edit template ref {editModal.name}
} + open={editModal.on} + onOk={handleOK} + onCancel={handleCancelEditModal} + confirmLoading={confirmLoading} + width={"60%"} + > + {error.message.length !== 0 && ( + { + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )} +
+ + + + + + + + + + + +
+ + 0} - onCancel={handleCancelModal} + onCancel={handleCancelDeleteModal} width={"40%"} footer={ - + + {""}; + } + return value; + }} + /> + ( + <> + + + )} /> {''} - } - return value; - }} + width="5%" + render={(template) => ( + <> + + + )} /> - ( - <> - - - )} - /> - ( - <> - - - )} - />
- {error.message.length !== 0 && ( - { - setError({ - message: "", - description: "", - }); - }} - style={{ marginBottom: "20px" }} - /> - )} -
{ + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )} + + - - - + + - + - - - + + + - - - + + + - - - - + + + +
- Edit template ref {editModal.name}} - open={editModal.on} - onOk={handleOK} - onCancel={handleCancelEditModal} - confirmLoading={confirmLoading} - width={"60%"} + + Edit template ref{" "} + {editModal} + + } + open={editModal.length > 0} + onOk={handleOKEdit} + onCancel={handleCancelEditModal} + confirmLoading={confirmLoading} + width={"60%"} + > + {error.message.length !== 0 && ( + { + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )} +
- {error.message.length !== 0 && ( - { - setError({ - message: "", - description: "", - }); - }} - style={{ marginBottom: "20px" }} - /> - )} - - - - + + + - - - + + + - - - - -
- 0} - onCancel={handleCancelDeleteModal} - width={"40%"} - footer={ - - } - > - {error.message.length !== 0 && ( - { - setError({ - message: "", - description: "", - }); - }} - style={{ marginBottom: "20px" }} - /> - )} - - In order to delete this template ref, type the template ref name in the box below - { - setConfirmDeleteInput(e.target.value) - }} /> - + + + + +
+ 0} + onCancel={handleCancelDeleteModal} + width={"40%"} + footer={ + + } + > + {error.message.length !== 0 && ( + { + setError({ + message: "", + description: "", + }); + }} + style={{ marginBottom: "20px" }} + /> + )} + + In order to delete this template ref, type the template ref name in the + box below + { + setConfirmDeleteInput(e.target.value); + }} + /> + ); };