Merge remote-tracking branch 'upstream/master' into frontend-dockerimage-change

This commit is contained in:
Raj Babu Das 2020-11-15 09:47:53 +05:30
commit 35da8d7dde
116 changed files with 2639 additions and 1372 deletions

View File

@ -95,8 +95,6 @@ spec:
value: "cluster"
- name: SUBSCRIBER_IMAGE
value: #{subscriber}
- name: DEPLOYER_IMAGE
value: "litmuschaos/litmusportal-self-deployer:ci"
- name: ARGO_SERVER_IMAGE
value: "argoproj/argocli:v2.9.3"
- name: ARGO_WORKFLOW_CONTROLLER_IMAGE

View File

@ -42,7 +42,6 @@ jobs:
echo 'export AUTHENTICATION_SERVER_IMAGE="litmusportal-auth-server"' >> workspace/env-vars
echo 'export FRONTEND_IMAGE="litmusportal-frontend"' >> workspace/env-vars
echo 'export SUBSCRIBER_IMAGE="litmusportal-subscriber"' >> workspace/env-vars
echo 'export SELF_DEPLOYER_IMAGE="litmusportal-self-deployer"' >> workspace/env-vars
echo 'export IMGTAG="ci"' >> workspace/env-vars
cat workspace/env-vars >> $BASH_ENV
source $BASH_ENV
@ -82,19 +81,11 @@ jobs:
- run:
name: Save subscriber docker image
command: docker save -o /tmp/workspace/${SUBSCRIBER_IMAGE}.tar ${REPONAME}/${SUBSCRIBER_IMAGE}:${IMGTAG}
- run:
name: Build self-deployer docker image
command: docker build . -f build/Dockerfile -t ${REPONAME}/${SELF_DEPLOYER_IMAGE}:${IMGTAG}
working_directory: ~/project/litmus-portal/tools/self-deployer
- run:
name: Save self-deployer docker image
command: docker save -o /tmp/workspace/${SELF_DEPLOYER_IMAGE}.tar ${REPONAME}/${SELF_DEPLOYER_IMAGE}:${IMGTAG}
- persist_to_workspace:
root: /tmp/workspace
paths:
- litmusportal-server.tar
- litmusportal-subscriber.tar
- litmusportal-self-deployer.tar
- litmusportal-auth-server.tar
docker-build-frontend:
machine:
@ -134,7 +125,6 @@ jobs:
command: |
docker load -i /tmp/workspace/${GRAPHQL_SERVER_IMAGE}.tar
docker load -i /tmp/workspace/${SUBSCRIBER_IMAGE}.tar
docker load -i /tmp/workspace/${SELF_DEPLOYER_IMAGE}.tar
docker load -i /tmp/workspace/${AUTHENTICATION_SERVER_IMAGE}.tar
docker load -i /tmp/workspace/${FRONTEND_IMAGE}.tar
- run:
@ -149,10 +139,6 @@ jobs:
- run:
name: Pushing subscriber server
command: bash ./hack/push --TYPE=ci --REPONAME=${REPONAME} --IMGNAME=${SUBSCRIBER_IMAGE} --IMGTAG=${IMGTAG}
- run:
name: Pushing self deployer
command: bash ./hack/push --TYPE=ci --REPONAME=${REPONAME} --IMGNAME=${SELF_DEPLOYER_IMAGE} --IMGTAG=${IMGTAG}
release:
machine:
image: circleci/classic:201808-01
@ -169,7 +155,6 @@ jobs:
command: |
docker load -i /tmp/workspace/${GRAPHQL_SERVER_IMAGE}.tar
docker load -i /tmp/workspace/${SUBSCRIBER_IMAGE}.tar
docker load -i /tmp/workspace/${SELF_DEPLOYER_IMAGE}.tar
docker load -i /tmp/workspace/${AUTHENTICATION_SERVER_IMAGE}.tar
docker load -i /tmp/workspace/${FRONTEND_IMAGE}.tar
- run:
@ -187,9 +172,6 @@ jobs:
- run:
name: Pushing subscriber server
command: bash ./hack/push --TYPE=release --REPONAME=${REPONAME} --IMGNAME=${SUBSCRIBER_IMAGE} --IMGTAG=${IMGTAG}
- run:
name: Pushing self deployer
command: bash ./hack/push --TYPE=release --REPONAME=${REPONAME} --IMGNAME=${SELF_DEPLOYER_IMAGE} --IMGTAG=${IMGTAG}
workflows:
version: 2

View File

@ -1,21 +1,23 @@
This is the list of organizations and users that publicly shared details of how they are using LitmusChaos for running chaos experiments.
Please send PRs to add or remove organizations/users.
| Organization | Applications/Workloads | Success Story |
| :--- | :--- | :---|
|[Zebrium](https://www.zebrium.com?utm_source=github&utm_campaign=litmuschaos_repo)|[Zebrium K8s Demo](https://github.com/zebrium/zebrium-kubernetes-demo)|[our story](https://github.com/litmuschaos/litmus/blob/master/adopters/zebrium.md)|
|[MayaData](https://mayadata.io)|[Director Online](https://director.mayadata.io/)|[our story](https://github.com/litmuschaos/litmus/tree/master/adopters/MayaData_DirectorOnline.md)|
|[OpenEBS](https://openebs.io/)|[openebs-ci](https://openebs.ci/)|[our story](https://github.com/litmuschaos/litmus/tree/master/adopters/openebs.md)|
|[Wipro](https://www.wipro.com/en-IN/infrastructure/wipros-appanywhere/?utm_source=github&utm_campaign=litmuschaos_repo)|[Wipro AppAnywhere](https://www.wipro.com/en-IN/infrastructure/wipros-appanywhere/?utm_source=github&utm_campaign=litmuschaos_repo)|[our story](https://github.com/litmuschaos/litmus/tree/master/adopters/AppAnywhere.md)|
|[Intuit](https://www.intuit.com?utm_source=github&utm_campaign=litmuschaos_repo)|[Argo-Litmus Demo](https://youtu.be/Uwqop-s99LA?t=720)|[our story](https://github.com/litmuschaos/litmus/tree/master/adopters/intuit.md)|
|[Okteto](https://okteto.com)|[Okteto-Litmus Demo](https://okteto.com/blog/chaos-engineering-with-litmus/)| [our story](adopters/okteto.md)|
|[WeScale](https://www.wescale.fr)|[Chaos Engineering](https://blog.wescale.fr/2020/03/19/le-guide-de-chaos-engineering-partie-2/)|[our story](https://github.com/litmuschaos/litmus/blob/master/adopters/wescale.md)|
|[NetApp](https://www.netapp.com)|[Chaos Engineering](https://www.netapp.com/us/index.aspx)|[our story](https://github.com/litmuschaos/litmus/blob/master/adopters/netapp.md)|
| Organization | Usecase | Details |
| :--- | :--- | :--- |
|[Zebrium](https://www.zebrium.com?utm_source=github&utm_campaign=litmuschaos_repo)|[Zebrium K8s Chaos Project](https://github.com/zebrium/zebrium-kubernetes-demo)|[Our Story](adopters/organizations/zebrium.md)|
|[MayaData](https://mayadata.io)|[Director Online](https://director.mayadata.io/)|[Our Story](adopters/organizations/mayadata.md)|
|[OpenEBS](https://openebs.io/)|[Openebs-CI](https://openebs.ci/)|[Our Story](adopters/organizations/openebs.md)|
|[Wipro](https://www.wipro.com/en-IN/infrastructure/wipros-appanywhere/?utm_source=github&utm_campaign=litmuschaos_repo)|[Wipro AppAnywhere](https://www.wipro.com/en-IN/infrastructure/wipros-appanywhere/?utm_source=github&utm_campaign=litmuschaos_repo)|[Our Story](adopters/organizations/wipro.md)|
|[Intuit](https://www.intuit.com?utm_source=github&utm_campaign=litmuschaos_repo)|[Argo Based Chaos Workflows](https://youtu.be/Uwqop-s99LA?t=720)|[Our Story](adopters/organizations/intuit.md)|
|[Okteto](https://okteto.com)|[Okteto-Litmus Integration](https://okteto.com/blog/chaos-engineering-with-litmus/)| [Our Story](adopters/organizations/okteto.md)|
|[WeScale](https://www.wescale.fr)|[Chaos Engineering](https://blog.wescale.fr/2020/03/19/le-guide-de-chaos-engineering-partie-2/)|[Our Story](adopters/organizations/wescale.md)|
|[NetApp](https://www.netapp.com)|[Chaos Engineering](https://www.netapp.com/us/index.aspx)|[Our Story](adopters/organizations/netapp.md)|
|[Keptn](https://keptn.sh)|[Chaos Engineering integration in CD](https://www.youtube.com/watch?v=aa5SzQmv4EQ)|To Be Added|
| User | Applications/Workloads | Success Story |
| :--- | :--- | :--- |
| [Laura Henning](https://github.com/LaumiH) | reasearch on how to do chaos engineering in minikube demo clusters like [these](https://github.com/LaumiH/k8sstuff) | [my story](https://github.com/litmuschaos/litmus/tree/master/adopters/Laura_Henning_Research_Project.md) |
| [Johnny Jacob](https://github.com/johnnyjacob) | Testing deployment designs for resiliency | Coming Soon! |
| [Jayesh Kumar Tank](https://github.com/k8s-dev) | Create Cloud Native Validation Suite on [Demo Application](https://github.com/k8s-dev/microservices-demo)| [my story](https://github.com/litmuschaos/litmus/tree/master/adopters/Jayesh_Kumar_CloudNative_Validation.md)|
| [Bhaumik Shah](https://github.com/Bhaumik1802) | Use LitmusChaos for Kafka Resiliency on Dev/Staging| [my story](https://github.com/litmuschaos/litmus/tree/master/adopters/Bhaumik_Shah_Kafka_Chaos.md)|
| [Jayadeep KM](https://github.com/kmjayadeep) | Ensure reliability of microservices| [my story](https://github.com/litmuschaos/litmus/tree/master/adopters/jayadeep_microservices.md)|
| User | Usecase | Details |
| :--- | :--- | :--- |
| [Laura Henning](https://github.com/LaumiH)|Reasearch on how to do chaos engineering in minikube clusters like [these](https://github.com/LaumiH/k8sstuff)|[My Story](adopters/users/Laura_Henning.md)
| [Johnny Jacob](https://github.com/johnnyjacob)|Testing deployment designs for resiliency|Coming Soon!|
| [Jayesh Kumar Tank](https://github.com/k8s-dev)|Create Cloud Native Validation Suite on [Microservices Application](https://github.com/k8s-dev/microservices-demo)|[My Story](adopters/users/Jayesh_Kumar_Tank.md)|
| [Bhaumik Shah](https://github.com/Bhaumik1802)|Use LitmusChaos for Kafka Resiliency on Dev/Staging|[My Story](adopters/users/Bhaumik_Shah.md)|
| [Jayadeep KM](https://github.com/kmjayadeep)|Ensure reliability of microservices|[My Story](adopters/users/Jayadeep_KM.md)|
| [Shantanu Deshpande](https://github.com/ishantanu)|Chaos Engineering Practice as SRE|[My Story](adopters/users/Shantanu_Deshpande.md)|

View File

@ -41,6 +41,7 @@ This document captures only the high level roadmap items. For the detailed backl
### Backlog
- Add pre-defined chaos workflows for the [podtato-head](https://github.com/cncf/podtato-head) model app from CNCF Ap-Delivery SIG
- Pre-defined chaos workflows to inject chaos during application benchmark runs
- Support for cloudevents compliant chaos events
- Increased chaos metrics via prometheus chaos exporter

View File

@ -0,0 +1,4 @@
I was looking for a cloud-native way of introducing chaos and after going through the details
and other options, Litmus was the best fit. Usage of Litmus is still in preliminary stages.
A limited set of chaos experiments are being used for testing resiliency. This will change in the
future, with more experiments planned to be adopted.

View File

@ -70,15 +70,6 @@ backend-services-checks:
&& exit 1; \
fi
@echo "------------------"
@echo "--> Check litmus-portal self-deployer [go mod tidy]"
@echo "------------------"
@tidyRes=$$(cd tools/self-deployer && go mod tidy); \
if [ -n "$${tidyRes}" ]; then \
echo "go mod tidy checking failed!" && echo "$${tidyRes}" \
&& echo "Please ensure you are using $$($(GO) version) for formatting code." \
&& exit 1; \
fi
@echo "------------------"
@echo "--> Check litmus-portal workflow-agent [go mod tidy]"
@echo "------------------"
@tidyRes=$$(cd cluster-agents/workflow-agent && go mod tidy); \

View File

@ -1,10 +1,12 @@
package store
import (
"errors"
"time"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
log "github.com/golang/glog"
"github.com/litmuschaos/litmus/litmus-portal/authentication/pkg/models"
"github.com/litmuschaos/litmus/litmus-portal/authentication/pkg/types"
@ -31,6 +33,20 @@ func NewUserStore(cfg *Config, ucfgs ...*UserConfig) (*UserStore, error) {
return nil, err
}
if types.DBUser != "" && types.DBPassword != "" {
cred := mgo.Credential{
Username: types.DBUser,
Password: types.DBPassword,
}
err = session.Login(&cred)
if err != nil {
log.Errorln("Database connection failed", err)
return nil, err
}
} else {
return nil, errors.New("Some DB configs are not present")
}
return NewUserStoreWithSession(session, cfg.DB, ucfgs...)
}

View File

@ -11,6 +11,8 @@ var (
DefaultUserName string = os.Getenv("ADMIN_USERNAME")
DefaultUserPassword string = os.Getenv("ADMIN_PASSWORD")
DefaultDBServerURL string = os.Getenv("DB_SERVER")
DBUser string = os.Getenv("DB_USER")
DBPassword string = os.Getenv("DB_PASSWORD")
DefaultAuthDB string = "auth"
DefaultLocalAuthCollection string = "usercredentials"
PasswordEncryptionCost int = 15

View File

@ -98,6 +98,19 @@ func applyRequest(requestType string, obj *unstructured.Unstructured) (*unstruct
log.Println("Resource successfully created")
return response, nil
} else if requestType == "update" {
getObj, err := dr.Get(obj.GetName(), metav1.GetOptions{})
if errors.IsNotFound(err) {
// This doesnt ever happen even if it is already deleted or not found
log.Printf("%v not found", obj.GetName())
return nil, nil
}
if err != nil {
return nil, err
}
obj.SetResourceVersion(getObj.GetResourceVersion())
response, err := dr.Update(obj, metav1.UpdateOptions{})
if err != nil {
return nil, err

View File

@ -14,6 +14,8 @@ data:
AgentNamespace: litmus
DataBaseServer: "mongodb://mongo-service:27017"
JWTSecret: "litmus-portal@123"
DB_USER: "admin"
DB_PASSWORD: "1234"
---
apiVersion: apps/v1
kind: Deployment
@ -61,38 +63,6 @@ spec:
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: self-deployer-admin-account
namespace: litmus
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: deployer-admin
namespace: litmus
labels:
name: deployer-admin
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: deployer-admin-rb
namespace: litmus
subjects:
- kind: ServiceAccount
name: self-deployer-admin-account
namespace: litmus
roleRef:
kind: ClusterRole
name: deployer-admin
apiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: litmus-server-account
namespace: litmus
@ -106,24 +76,11 @@ metadata:
name: litmus-server
rules:
- apiGroups:
- ""
- "*"
resources:
- pods
- pods/exec
- services
- nodes
- "*"
verbs:
- create
- patch
- update
- get
- list
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- create
- "*"
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
@ -186,12 +143,20 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: DB_USER
valueFrom:
configMapKeyRef:
name: litmus-portal-admin-config
key: DB_USER
- name: DB_PASSWORD
valueFrom:
configMapKeyRef:
name: litmus-portal-admin-config
key: DB_PASSWORD
- name: PORTAL_SCOPE
value: "cluster"
- name: SUBSCRIBER_IMAGE
value: "litmuschaos/litmusportal-subscriber:ci"
- name: DEPLOYER_IMAGE
value: "litmuschaos/litmusportal-self-deployer:ci"
- name: ARGO_SERVER_IMAGE
value: "argoproj/argocli:v2.9.3"
- name: ARGO_WORKFLOW_CONTROLLER_IMAGE
@ -218,6 +183,16 @@ spec:
configMapKeyRef:
name: litmus-portal-admin-config
key: JWTSecret
- name: DB_USER
valueFrom:
configMapKeyRef:
name: litmus-portal-admin-config
key: DB_USER
- name: DB_PASSWORD
valueFrom:
configMapKeyRef:
name: litmus-portal-admin-config
key: DB_PASSWORD
- name: ADMIN_USERNAME
value: "admin"
- name: ADMIN_PASSWORD
@ -270,6 +245,17 @@ spec:
volumeMounts:
- name: mongo-persistent-storage
mountPath: /data/db
env:
- name: MONGO_INITDB_ROOT_USERNAME
valueFrom:
configMapKeyRef:
name: litmus-portal-admin-config
key: DB_USER
- name: MONGO_INITDB_ROOT_PASSWORD
valueFrom:
configMapKeyRef:
name: litmus-portal-admin-config
key: DB_PASSWORD
volumes:
- name: mongo-persistent-storage
persistentVolumeClaim:

View File

@ -0,0 +1,6 @@
<svg width="65" height="82" viewBox="0 0 65 82" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M65 0H0V75.85C0 79.2467 2.72826 82 6.09375 82H58.9062C62.2717 82 65 79.2467 65 75.85V0Z" fill="#858CDD"/>
<path d="M10 10H20V33H10V10Z" fill="#5B44BA"/>
<path d="M10 31H20V72H10V31Z" fill="white"/>
<path d="M25 62H55V72H25V62Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 395 B

View File

@ -0,0 +1,11 @@
<svg width="46" height="47" viewBox="0 0 46 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="0.605469" width="45.7" height="45.7" rx="22.85" fill="#858CDD"/>
<g clip-path="url(#clip0)">
<path d="M19.7294 33.4202C19.475 33.6761 19.1278 33.8189 18.7671 33.8189C18.4065 33.8189 18.0593 33.6761 17.8048 33.4202L10.1197 25.7337C9.3221 24.9362 9.3221 23.6429 10.1197 22.8468L11.082 21.8843C11.8798 21.0867 13.1715 21.0867 13.9691 21.8843L18.7671 26.6826L31.7321 13.7173C32.53 12.9198 33.823 12.9198 34.6193 13.7173L35.5816 14.6799C36.3792 15.4774 36.3792 16.7704 35.5816 17.5668L19.7294 33.4202Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="26.6583" height="26.6583" fill="white" transform="translate(9.52148 10.1289)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 772 B

View File

@ -0,0 +1,5 @@
<svg width="78" height="64" viewBox="0 0 78 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1138 14.3014C21.0269 7.11955 28.3237 0.326172 39.2092 0.326172C50.9208 0.326172 60.9024 8.29471 63.0908 21.1899C66.181 21.6524 69.4164 22.8359 72.0804 24.8148C75.3916 27.2745 77.8695 31.0223 77.8695 36.0266C77.8695 40.8787 75.8267 44.7389 72.5009 47.3264C69.2407 49.8627 64.9018 51.0679 60.3516 51.0679H48.8743C47.5398 51.0679 46.458 49.9861 46.458 48.6516C46.458 47.3172 47.5398 46.2353 48.8743 46.2353H60.3516C64.1073 46.2353 67.3192 45.2349 69.5334 43.5122C71.682 41.8407 73.037 39.3883 73.037 36.0266C73.037 32.8171 71.513 30.4132 69.1988 28.6942C66.825 26.9309 63.6788 25.9557 60.8361 25.8149C59.6459 25.7559 58.6763 24.838 58.5525 23.6527C57.3351 12.0049 48.9635 5.15872 39.2092 5.15872C29.9984 5.15872 24.1094 11.235 22.1799 17.2529C21.8848 18.1733 21.0697 18.8292 20.1075 18.9206C11.7811 19.7116 5.38137 24.8377 5.38137 32.5834C5.38137 40.3703 12.11 46.2353 21.0871 46.2353H29.5441C30.8786 46.2353 31.9604 47.3172 31.9604 48.6516C31.9604 49.9861 30.8786 51.0679 29.5441 51.0679H21.0871C10.1301 51.0679 0.548828 43.6797 0.548828 32.5834C0.548828 22.0304 8.90384 15.7256 18.1138 14.3014Z" fill="#858CDD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M37.4998 20.364C38.4434 19.4203 39.9733 19.4203 40.9169 20.364L50.582 30.029C51.5256 30.9727 51.5256 32.5026 50.582 33.4462C49.6384 34.3898 48.1085 34.3898 47.1648 33.4462L39.2083 25.4896L31.2518 33.4462C30.3082 34.3898 28.7783 34.3898 27.8347 33.4462C26.891 32.5026 26.891 30.9727 27.8347 30.029L37.4998 20.364Z" fill="#858CDD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.2092 22.0723C40.5437 22.0723 41.6255 23.1541 41.6255 24.4885V60.796C41.6255 62.1305 40.5437 63.2123 39.2092 63.2123C37.8748 63.2123 36.793 62.1305 36.793 60.796V24.4885C36.793 23.1541 37.8748 22.0723 39.2092 22.0723Z" fill="#858CDD"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -30,6 +30,7 @@ welcomeModal:
sidebar:
title: Litmus
version: 'Version: 1.10'
header:
notificationDropdown:
@ -435,6 +436,7 @@ createWorkflow:
schedule: Schedule
schedulingNow: Scheduling now
adjustedWeights: Adjusted Weights
clustername: Cluster Name
workflowCluster:
activeCluster: Choose an active cluster
none: None
@ -459,6 +461,15 @@ targets:
head4: 4.Click on the button
head5: Connect the target
conformation: Kuberenetes Agent is confirming, Please wait...
clusterDetails: Cluster Details
modalDelete:
head1: Are you sure to
head2: remove the current Agent?
head3: It will be removed forever
delete: Delete
yes: Yes
no: No
connectTargets:
title: A new Target,
@ -574,13 +585,21 @@ myhub:
customWorkflow:
createWorkflow:
create: Creating a new chaos workflow
createDesc: Add new experiments from your ChaosHubs defined at MyHubs section
createDesc: Add new experiments from your ChaosHubs defined at MyHub section
workflowInfo: Workflow information
workflowName: Workflow Name
workflowDesc: Description
firstChaos: First Chaos Experiment
chooseNamespace: Choose Namespace
firstChaos: Choose the hub
selectHub: Select the hub
public: Public Hub
configure: Construct your workflow
construct: Using experiments from MyHub
upload: Upload your
yaml: YAML
uploadFile: Upload File
uploadSuccess: Successfully uploaded
drag: Drag & Drop here
chooseExp: Choose the experiment
loadingExp: Loading Experiments, please wait...
selectExp: Select the experiment
@ -604,6 +623,8 @@ customWorkflow:
expName: Experiment name
description: Description
sequence: Sequence
sequenceFirstExp: This is your first experiment
sequenceNotFirstExp: This Experiment will execute after
appInfo: Specify the appinfo for the experiment
envText: We have created a chaos engine specification for this experiment. You can tune the following variables for the chaos engine.
customEnvText: To change the variables in chaos experiment specific to this workflow, You can tune it here.

View File

@ -56,6 +56,12 @@ const Header: React.FC = () => {
const memberList: Member[] = project.members;
memberList.forEach((member) => {
if (member.role === 'Owner') {
user.updateUserDetails({
selectedProjectOwner: member.user_name,
});
}
if (member.user_name === data?.getUser.username) {
user.updateUserDetails({
selectedProjectID,

View File

@ -10,11 +10,13 @@ import useStyles from './styles';
interface PredifinedWorkflowsProps {
workflows: preDefinedWorkflowData[];
callbackOnSelectWorkflow: (index: number) => void;
isCustomWorkflowVisible: boolean;
}
const PredifinedWorkflows: React.FC<PredifinedWorkflowsProps> = ({
workflows,
callbackOnSelectWorkflow,
isCustomWorkflowVisible,
}) => {
const workflowAction = useActions(WorkflowActions);
const classes = useStyles();
@ -38,19 +40,22 @@ const PredifinedWorkflows: React.FC<PredifinedWorkflowsProps> = ({
/>
</div>
))}
<CustomWorkflowCard
handleClick={() => {
workflowAction.setWorkflowDetails({
name: `custom-chaos-workflow-${Math.round(
new Date().getTime() / 1000
)}`,
description: 'Custom Chaos Workflow',
isCustomWorkflow: true,
customWorkflows: [],
});
history.push('/create-workflow/custom');
}}
/>
{isCustomWorkflowVisible ? (
<CustomWorkflowCard
handleClick={() => {
workflowAction.setWorkflowDetails({
name: `custom-chaos-workflow-${Math.round(
new Date().getTime() / 1000
)}`,
description: 'Custom Chaos Workflow',
isCustomWorkflow: true,
namespace: 'litmus',
customWorkflows: [],
});
history.push('/create-workflow/custom');
}}
/>
) : null}
</div>
);
};

View File

@ -141,6 +141,16 @@ const SideBar: React.FC = () => {
</CustomisedListItem>
)}
</List>
<div className={classes.versionDiv}>
<img
src="/icons/litmusPurple.svg"
alt="litmus logo"
className={classes.versionlogo}
/>
<Typography className={classes.versionText}>
{t('sidebar.version')}
</Typography>
</div>
</Drawer>
);
};

View File

@ -67,6 +67,22 @@ const useStyles = makeStyles((theme: Theme) => ({
marginTop: theme.spacing(8),
height: '16.68rem',
},
versionlogo: {
width: '1.25rem',
height: '2.185rem',
},
versionText: {
margin: 'auto',
marginLeft: theme.spacing(1.25),
fontSize: '0.75rem',
},
versionDiv: {
display: 'flex',
flexDirection: 'row',
marginTop: 'auto',
marginBottom: theme.spacing(2),
marginLeft: theme.spacing(4),
},
}));
export default useStyles;

View File

@ -1,14 +1,21 @@
import { Typography } from '@material-ui/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from '@apollo/client';
import { useSelector } from 'react-redux';
import { history } from '../../../redux/configureStore';
import ButtonOutline from '../../Button/ButtonOutline';
// import BrowseWorkflow from '../TargetHome/BrowseWorkflow';
import useStyles from './styles';
import Scaffold from '../../../containers/layouts/Scaffold';
import TargetCopy from '../TargetCopy';
import { Cluster } from '../../../models/graphql/clusterData';
import { Cluster, DeleteCluster } from '../../../models/graphql/clusterData';
import { LocationState } from '../../../models/routerModel';
import { DELETE_CLUSTER } from '../../../graphql';
import Unimodal from '../../../containers/layouts/Unimodal';
import ButtonFilled from '../../Button/ButtonFilled';
import BackButton from '../../Button/BackButton';
import { RootState } from '../../../redux/reducers';
interface ClusterProps {
data: Cluster;
@ -21,19 +28,25 @@ const ClusterInfo: React.FC<ClusterVarsProps> = ({ location }) => {
const { data } = location.state;
const classes = useStyles();
const link: string = data.token;
const handleClick = () => {
const [deleteCluster] = useMutation<DeleteCluster>(DELETE_CLUSTER);
const [open, setOpen] = React.useState(false);
const handleDelete = () => {
deleteCluster({ variables: { cluster_id: data.cluster_id } });
history.push('/targets');
};
const userRole = useSelector((state: RootState) => state.userData.userRole);
const { t } = useTranslation();
return (
<Scaffold>
<section className="Header section">
<div className={classes.backBotton}>
<ButtonOutline isDisabled={false} handleClick={handleClick}>
<BackButton isDisabled={false}>
<div>{t('workflowCluster.header.formControl.back')}</div>
</ButtonOutline>
</BackButton>
<div className={classes.header}>
<Typography variant="h4">
{t('workflowCluster.header.formControl.clusterInfo')}
@ -46,32 +59,47 @@ const ClusterInfo: React.FC<ClusterVarsProps> = ({ location }) => {
<div className={classes.detailsDiv}>
{/* name */}
<div className={classes.firstCol}>
<div className={classes.status}>
<div className={classes.checkCluster}>
<Typography variant="h6">
<strong>Cluster Details</strong>
</Typography>
<div className={classes.linkBox}>
<div className={classes.status}>
<div>
<Typography variant="h6">
<strong>{t('targets.newTarget.clusterDetails')}</strong>
</Typography>
</div>
<div>
{data.is_active ? (
<Typography
className={`${classes.check} ${classes.active}`}
>
{t('workflowCluster.header.formControl.menu1')}
</Typography>
) : data.is_cluster_confirmed === false ? (
<Typography
className={`${classes.check} ${classes.pending}`}
>
{t('workflowCluster.header.formControl.menu6')}
</Typography>
) : (
<Typography
className={`${classes.check} ${classes.notactive}`}
>
{t('workflowCluster.header.formControl.menu2')}
</Typography>
)}
</div>
</div>
<div>
{data.is_active ? (
<Typography
className={`${classes.check} ${classes.active}`}
>
{t('workflowCluster.header.formControl.menu1')}
</Typography>
) : data.is_cluster_confirmed === false ? (
<Typography
className={`${classes.check} ${classes.pending}`}
>
{t('workflowCluster.header.formControl.menu6')}
</Typography>
) : (
<Typography
className={`${classes.check} ${classes.notactive}`}
>
{t('workflowCluster.header.formControl.menu2')}
</Typography>
)}
<div className={classes.buttonBox}>
<ButtonOutline
isDisabled={userRole === 'Viewer'}
handleClick={() => {
setOpen(true);
}}
>
<div className={classes.status}>
<img src="/icons/bin-red.svg" alt="Delete" />
<div> {t('targets.modalDelete.delete')} </div>
</div>
</ButtonOutline>
</div>
</div>
</div>
@ -97,18 +125,58 @@ const ClusterInfo: React.FC<ClusterVarsProps> = ({ location }) => {
<Typography>{t('targets.newTarget.head1')}</Typography>
<Typography>{t('targets.newTarget.head2')}</Typography>
<Typography>{t('targets.newTarget.head3')}</Typography>
{/*
<Typography>
{t('targets.newTarget.head4')}{' '}
<strong>{t('targets.newTarget.head5')}</strong>
</Typography>
*/}
</div>
<div className={classes.rightMargin}>
{link && <TargetCopy yamlLink={link} />}
</div>
</div>
</div>
<div>
{open ? (
<div>
<Unimodal
open={open}
handleClose={() => {
setOpen(false);
}}
hasCloseBtn
>
<div className={classes.body}>
<img src="/icons/bin-red-delete.svg" alt="Delete" />
<div className={classes.text}>
<Typography className={classes.typo} align="center">
{t('targets.modalDelete.head1')} <br />
<strong> {t('targets.modalDelete.head2')}</strong>
</Typography>
</div>
<div className={classes.textSecond}>
<Typography className={classes.typoSub} align="center">
{t('targets.modalDelete.head3')}
</Typography>
</div>
<div className={classes.buttonGroup}>
<ButtonOutline
isDisabled={false}
handleClick={() => {
setOpen(false);
}}
>
<> {t('targets.modalDelete.no')}</>
</ButtonOutline>
<ButtonFilled
isDisabled={false}
isPrimary
handleClick={handleDelete}
>
<>{t('targets.modalDelete.yes')}</>
</ButtonFilled>
</div>
</div>
</Unimodal>
</div>
) : null}
</div>
</div>
</section>
</Scaffold>

View File

@ -36,9 +36,7 @@ const useStyles = makeStyles((theme) => ({
paddingTop: theme.spacing(5),
marginLeft: theme.spacing(2),
},
checkCluster: {
marginRight: theme.spacing(2),
},
version: {
marginTop: theme.spacing(2),
},
@ -48,9 +46,20 @@ const useStyles = makeStyles((theme) => ({
marginLeft: theme.spacing(5),
marginTop: theme.spacing(4),
},
linkBox: {
backgroundColor: theme.palette.common.white,
paddingRight: theme.spacing(9),
display: 'flex',
flexDirection: 'row',
width: '100%',
wordWrap: 'break-word',
justifyContent: 'space-between',
},
status: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
gap: '1rem',
},
expDiv: {
display: 'flex',
@ -68,6 +77,10 @@ const useStyles = makeStyles((theme) => ({
rightMargin: {
marginRight: theme.spacing(8),
},
buttonBox: {
display: 'flex',
paddingLeft: theme.spacing(4),
},
connectdevice: {
fontSize: '1rem',
lineHeight: '175%',
@ -102,6 +115,39 @@ const useStyles = makeStyles((theme) => ({
background: theme.palette.customColors.menuOption.pending,
color: theme.palette.warning.main,
},
body: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
marginTop: theme.spacing(7.5),
},
// styles for text
text: {
width: '31.93rem',
height: '5.875rem',
marginTop: theme.spacing(3.75),
marginBottom: theme.spacing(3.75),
},
typo: {
fontSize: '2rem',
},
textSecond: {
width: '29.06rem',
height: '1.6875rem',
marginTop: theme.spacing(1.875),
marginBottom: theme.spacing(3.75),
},
typoSub: {
fontSize: '1rem',
},
// for yes or no buttons
buttonGroup: {
display: 'flex',
gap: '1rem',
marginTop: theme.spacing(2.5),
justifyContent: 'space-between',
},
}));
export default useStyles;

View File

@ -18,6 +18,7 @@ import { RootState } from '../../../redux/reducers';
import Loader from '../../Loader';
import ButtonFilled from '../../Button/ButtonFilled';
import Unimodal from '../../../containers/layouts/Unimodal';
import BackButton from '../../Button/BackButton';
const ConnectTarget = () => {
const classes = useStyles();
@ -26,10 +27,6 @@ const ConnectTarget = () => {
const [id, setID] = React.useState('');
const [modal, setModal] = React.useState(false);
const handleClick = () => {
history.push('/targets');
};
const selectedProjectID = useSelector(
(state: RootState) => state.userData.selectedProjectID
);
@ -71,6 +68,10 @@ const ConnectTarget = () => {
project_id: selectedProjectID,
cluster_type: 'external',
agent_scope: 'cluster',
agent_namespace: 'litmus',
serviceaccount: '',
agent_sa_exists: false,
agent_ns_exists: false,
};
createClusterReg({
variables: { ClusterInput: createClusterInput },
@ -87,13 +88,9 @@ const ConnectTarget = () => {
<Scaffold>
<section className="Header section">
<div className={classes.backBotton}>
<ButtonOutline
isDisabled={false}
handleClick={handleClick}
data-cy="backSelect"
>
<BackButton isDisabled={false}>
<Typography>Back</Typography>
</ButtonOutline>
</BackButton>
<div className={classes.header}>
<Typography variant="h4">
{t('targets.connectHome.connectText')}
@ -140,7 +137,9 @@ const ConnectTarget = () => {
<div>
<Unimodal
open={modal}
handleClose={handleClick}
handleClose={() => {
history.push('/targets');
}}
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
hasCloseBtn

View File

@ -23,7 +23,12 @@ const Welcomemodal: React.FC<WelcomemodalProps> = ({ handleIsOpen }) => {
);
return (
<Unimodal open handleClose={handleClose} hasCloseBtn={false}>
<Unimodal
open
handleClose={handleClose}
hasCloseBtn={false}
disableBackdropClick
>
{body}
</Unimodal>
);

View File

@ -8,7 +8,6 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import YAML from 'yaml';
import workflowsList from '../PredifinedWorkflows/data';
import Unimodal from '../../containers/layouts/Unimodal';
import { CREATE_WORKFLOW } from '../../graphql';
import {
@ -35,6 +34,7 @@ import ButtonOutline from '../Button/ButtonOutline';
import QontoConnector from './quontoConnector';
import useStyles from './styles';
import useQontoStepIconStyles from './useQontoStepIconStyles';
import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate';
function getSteps(): string[] {
return [
@ -122,7 +122,6 @@ const CustomStepper = () => {
(state: RootState) => state.workflowData
);
const {
id,
yaml,
weights,
description,
@ -148,68 +147,50 @@ const CustomStepper = () => {
const workflow = useActions(WorkflowActions);
const [invalidYaml, setinValidYaml] = React.useState(false);
const steps = getSteps();
const scheduleOnce = workflowOnce;
const scheduleMore = cronWorkflow;
function EditYaml() {
const oldParsedYaml = YAML.parse(yaml);
let NewLink: string = ' ';
let NewYaml: string = ' ';
const NewLink: string = ' ';
if (
oldParsedYaml.kind === 'Workflow' &&
scheduleType.scheduleOnce !== 'now'
) {
NewLink = workflowsList[parseInt(id, 10)].chaosWkfCRDLink_Recur as string;
fetch(NewLink)
.then((data) => {
data.text().then((yamlText) => {
const oldParsedYaml = YAML.parse(yaml);
const newParsedYaml = YAML.parse(yamlText);
delete newParsedYaml.spec.workflowSpec;
newParsedYaml.spec.schedule = cronSyntax;
delete newParsedYaml.metadata.generateName;
newParsedYaml.metadata.name = workflowData.name;
newParsedYaml.spec.workflowSpec = oldParsedYaml.spec;
const tz = {
timezone:
Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
};
Object.entries(tz).forEach(([key, value]) => {
newParsedYaml.spec[key] = value;
});
NewYaml = YAML.stringify(newParsedYaml);
workflow.setWorkflowDetails({
link: NewLink,
yaml: NewYaml,
});
});
})
.catch((err) => {
console.error(`Unable to fetch the yaml text${err}`);
});
const oldParsedYaml = YAML.parse(yaml);
const newParsedYaml = YAML.parse(scheduleMore);
delete newParsedYaml.spec.workflowSpec;
newParsedYaml.spec.schedule = cronSyntax;
delete newParsedYaml.metadata.generateName;
newParsedYaml.metadata.name = workflowData.name;
newParsedYaml.spec.workflowSpec = oldParsedYaml.spec;
const timeZone = {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
};
Object.entries(timeZone).forEach(([key, value]) => {
newParsedYaml.spec[key] = value;
});
const NewYaml = YAML.stringify(newParsedYaml);
workflow.setWorkflowDetails({
link: NewLink,
yaml: NewYaml,
});
}
if (
oldParsedYaml.kind === 'CronWorkflow' &&
scheduleType.scheduleOnce === 'now'
) {
NewLink = workflowsList[parseInt(id, 10)].chaosWkfCRDLink as string;
fetch(NewLink)
.then((data) => {
data.text().then((yamlText) => {
const oldParsedYaml = YAML.parse(yaml);
const newParsedYaml = YAML.parse(yamlText);
delete newParsedYaml.spec;
delete newParsedYaml.metadata.generateName;
newParsedYaml.metadata.name = workflowData.name;
newParsedYaml.spec = oldParsedYaml.spec.workflowSpec;
NewYaml = YAML.stringify(newParsedYaml);
workflow.setWorkflowDetails({
link: NewLink,
yaml: NewYaml,
});
});
})
.catch((err) => {
console.error(`Unable to fetch the yaml text${err}`);
});
const oldParsedYaml = YAML.parse(yaml);
const newParsedYaml = YAML.parse(scheduleOnce);
delete newParsedYaml.spec;
delete newParsedYaml.metadata.generateName;
newParsedYaml.metadata.name = workflowData.name;
newParsedYaml.spec = oldParsedYaml.spec.workflowSpec;
const NewYaml = YAML.stringify(newParsedYaml);
workflow.setWorkflowDetails({
link: NewLink,
yaml: NewYaml,
});
}
if (
oldParsedYaml.kind === 'CronWorkflow' &&
@ -219,13 +200,13 @@ const CustomStepper = () => {
newParsedYaml.spec.schedule = cronSyntax;
delete newParsedYaml.metadata.generateName;
newParsedYaml.metadata.name = workflowData.name;
const tz = {
const timeZone = {
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
};
Object.entries(tz).forEach(([key, value]) => {
Object.entries(timeZone).forEach(([key, value]) => {
newParsedYaml.spec[key] = value;
});
NewYaml = YAML.stringify(newParsedYaml);
const NewYaml = YAML.stringify(newParsedYaml);
workflow.setWorkflowDetails({
link: NewLink,
yaml: NewYaml,

View File

@ -4,7 +4,6 @@ import { Redirect, Route, Router, Switch } from 'react-router-dom';
import Loader from '../../components/Loader';
import useActions from '../../redux/actions';
import * as AnalyticsActions from '../../redux/actions/analytics';
import * as MyHubActions from '../../redux/actions/myhub';
import { history } from '../../redux/configureStore';
import { RootState } from '../../redux/reducers';
import withTheme from '../../theme';
@ -131,16 +130,13 @@ const Routes: React.FC<RoutesProps> = ({ isOwner, isProjectAvailable }) => {
function App() {
const classes = useStyles();
const analyticsAction = useActions(AnalyticsActions);
const publicHubAction = useActions(MyHubActions);
const userData = useSelector((state: RootState) => state.userData);
const token = getToken();
useEffect(() => {
if (token !== '') {
analyticsAction.loadCommunityAnalytics();
publicHubAction.getAllPublicCharts();
}
}, [token]);
return (
<Suspense fallback={<Loader />}>
<Router history={history}>

View File

@ -76,24 +76,30 @@ export const USER_CLUSTER_REG = gql`
`;
export const ADD_MY_HUB = gql`
mutation addMyHub($MyHubDetails: CreateMyHub!, $Username: String!) {
addMyHub(myhubInput: $MyHubDetails, username: $Username) {
username
my_hub {
HubName
RepoURL
RepoBranch
}
mutation addMyHub($MyHubDetails: CreateMyHub!, $projectID: String!) {
addMyHub(myhubInput: $MyHubDetails, projectID: $projectID) {
HubName
RepoURL
RepoBranch
}
}
`;
export const SYNC_REPO = gql`
mutation syncHub($data: ChartsInput!) {
syncHub(syncHubInput: $data) {
mutation syncHub($projectID: String!, $HubName: String!) {
syncHub(projectID: $projectID, HubName: $HubName) {
id
RepoURL
RepoBranch
IsAvailable
TotalExp
HubName
}
}
`;
export const DELETE_CLUSTER = gql`
mutation deleteCluster($cluster_id: String!) {
deleteClusterReg(cluster_id: $cluster_id)
}
`;

View File

@ -35,6 +35,7 @@ export const SCHEDULE_DETAILS = gql`
cluster_id
cluster_type
cluster_name
isRemoved
}
}
`;
@ -86,12 +87,6 @@ export const GET_USER = gql`
name
id
}
my_hub {
id
HubName
RepoURL
RepoBranch
}
company_name
updated_at
created_at
@ -121,6 +116,11 @@ export const GET_CLUSTER = gql`
no_of_schedules
no_of_workflows
token
agent_namespace
serviceaccount
agent_scope
agent_ns_exists
agent_sa_exists
}
}
`;
@ -137,8 +137,8 @@ export const ALL_USERS = gql`
`;
export const GET_CHARTS_DATA = gql`
query getCharts($data: ChartsInput!) {
getCharts(chartsInput: $data) {
query getCharts($HubName: String!, $projectID: String!) {
getCharts(HubName: $HubName, projectID: $projectID) {
ApiVersion
Kind
Metadata {
@ -242,7 +242,7 @@ export const GET_EXPERIMENT_DATA = gql`
export const GET_HUB_STATUS = gql`
query getHubStatus($data: String!) {
getHubStatus(username: $data) {
getHubStatus(projectID: $data) {
id
HubName
RepoBranch
@ -252,3 +252,15 @@ export const GET_HUB_STATUS = gql`
}
}
`;
export const GET_ENGINE_YAML = gql`
query getEngineData($experimentInput: ExperimentInput!) {
getYAMLData(experimentInput: $experimentInput)
}
`;
export const GET_EXPERIMENT_YAML = gql`
query getExperimentData($experimentInput: ExperimentInput!) {
getYAMLData(experimentInput: $experimentInput)
}
`;

View File

@ -2,7 +2,7 @@ export interface Cluster {
cluster_id: string;
project_id: string;
cluster_name: string;
description: String;
description: string;
platform_name: string;
access_key: string;
is_registered: boolean;
@ -14,6 +14,11 @@ export interface Cluster {
no_of_workflows: number;
no_of_schedules: number;
token: string;
agent_namespace: string;
serviceaccount: string;
agent_scope: string;
agent_ns_exists: boolean;
agent_sa_exists: boolean;
}
export interface Clusters {
@ -27,7 +32,11 @@ export interface CreateClusterInput {
platform_name: string;
project_id: string;
cluster_type: string;
agent_namespace: string;
serviceaccount: string;
agent_scope: string;
agent_ns_exists: boolean;
agent_sa_exists: boolean;
};
}
@ -44,3 +53,7 @@ export interface CreateClusterInputResponse {
export interface ClusterVars {
project_id: string;
}
export interface DeleteCluster {
cluster_id: string;
}

View File

@ -18,6 +18,7 @@ export interface ScheduleWorkflow {
cluster_name: string;
cluster_type: string;
regularity?: string;
isRemoved: boolean;
}
export interface Schedules {

View File

@ -27,7 +27,6 @@ export interface UserDetails {
is_email_verified: string;
state: string;
role: string;
my_hub: MyHubDetail[];
}
export interface MyHubDetail {

View File

@ -69,10 +69,6 @@ export interface Charts {
getCharts: Chart[];
}
export interface PublicHubData {
charts: Chart[];
}
export interface ExperimentDetail {
getHubExperiment: Chart;
}
@ -92,7 +88,6 @@ export interface HubStatus {
export enum MyHubActions {
SET_MYHUB = 'SET_MYHUBS',
LOAD_PUBLIC_CHARTS = 'LOAD_PUBLIC_CHARTS',
}
interface MyHubActionType<T, P> {
@ -100,6 +95,7 @@ interface MyHubActionType<T, P> {
payload: P;
}
export type MyHubAction =
| MyHubActionType<typeof MyHubActions.SET_MYHUB, HubDetails>
| MyHubActionType<typeof MyHubActions.LOAD_PUBLIC_CHARTS, Chart[]>;
export type MyHubAction = MyHubActionType<
typeof MyHubActions.SET_MYHUB,
HubDetails
>;

View File

@ -1,6 +1,7 @@
export interface UpdateUser {
selectedProjectID: string;
selectedProjectName: string;
selectedProjectOwner: string;
userRole: string;
loader: boolean;
}

View File

@ -21,6 +21,7 @@ export interface customWorkflow {
hubName?: string;
repoUrl?: string;
repoBranch?: string;
description: string;
yamlLink?: string;
yaml?: string;
index?: number;
@ -34,6 +35,8 @@ export interface WorkflowData {
description: string;
weights: experimentMap[];
isCustomWorkflow: boolean;
namespace: string;
clustername: string;
clusterid: string;
cronSyntax: string;
scheduleType: scheduleType;

View File

@ -12,34 +12,27 @@ function getStepContent(
): React.ReactNode {
switch (stepIndex) {
case 0:
return <CreateWorkflow gotoStep={(page: number) => gotoStep(page)} />;
return <CreateWorkflow gotoStep={gotoStep} />;
case 1:
return <TuneCustomWorkflow gotoStep={(page: number) => gotoStep(page)} />;
return <TuneCustomWorkflow gotoStep={gotoStep} />;
case 2:
return (
<ScheduleCustomWorkflow gotoStep={(page: number) => gotoStep(page)} />
);
return <ScheduleCustomWorkflow gotoStep={gotoStep} />;
case 3:
return <ExperimentEditor gotoStep={(page: number) => gotoStep(page)} />;
return <ExperimentEditor gotoStep={gotoStep} />;
default:
return <CreateWorkflow gotoStep={(page: number) => gotoStep(page)} />;
return <CreateWorkflow gotoStep={gotoStep} />;
}
}
const CreateCustomWorkflow = () => {
const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0);
function gotoStep({ page }: { page: number }) {
const gotoStep = (page: number) => {
setActiveStep(page);
}
};
return (
<Scaffold>
<div className={classes.root}>
{getStepContent(activeStep, (page: number) => gotoStep({ page }))}
</div>
<div className={classes.root}>{getStepContent(activeStep, gotoStep)}</div>
</Scaffold>
);
};
export default CreateCustomWorkflow;

View File

@ -115,6 +115,7 @@ const HomePage: React.FC = () => {
selectedProjectID: isOwnerOfProject.id,
userRole: 'Owner',
selectedProjectName: isOwnerOfProject.name,
selectedProjectOwner: userData.username,
});
user.updateUserDetails({ loader: false });
// Flush data to persistor immediately

View File

@ -28,7 +28,7 @@ const MyHub = () => {
// Get MyHubs with Status
const { data, loading, refetch } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: userData.username },
variables: { data: userData.selectedProjectID },
fetchPolicy: 'cache-and-network',
});
@ -104,7 +104,11 @@ const MyHub = () => {
{hub.IsAvailable ? 'Connected' : 'Error'}
</Typography>
<img
src="/icons/my-hub-charts.svg"
src={`/icons/${
hub.HubName === 'Chaos Hub'
? 'myhub-litmus.svg'
: 'my-hub-charts.svg'
}`}
alt="add-hub"
/>
<Typography
@ -144,12 +148,8 @@ const MyHub = () => {
onClick={() => {
syncRepo({
variables: {
data: {
HubName: hub.HubName,
UserName: userData.username,
RepoURL: hub.RepoURL,
RepoBranch: hub.RepoBranch,
},
HubName: hub.HubName,
projectID: userData.selectedProjectID,
},
});
setKey(hub.id);
@ -165,22 +165,24 @@ const MyHub = () => {
</Paper>
);
})}
<Card
elevation={3}
className={classes.cardDiv}
onClick={() => {
history.push({ pathname: '/myhub/connect' });
}}
>
<CardActionArea>
<CardContent className={classes.cardContent}>
<img src="/icons/add-hub.svg" alt="add-hub" />
<Typography variant="h6" align="center">
{t('myhub.mainPage.connectNewHub')}
</Typography>
</CardContent>
</CardActionArea>
</Card>
{userData.userRole !== 'Viewer' ? (
<Card
elevation={3}
className={classes.cardDiv}
onClick={() => {
history.push({ pathname: '/myhub/connect' });
}}
>
<CardActionArea>
<CardContent className={classes.cardContent}>
<img src="/icons/add-hub.svg" alt="add-hub" />
<Typography variant="h6" align="center">
{t('myhub.mainPage.connectNewHub')}
</Typography>
</CardContent>
</CardActionArea>
</Card>
) : null}
</div>
</div>
</div>

View File

@ -11,21 +11,3 @@ export function setHubDetails(selectedHub: HubDetails): MyHubAction {
payload: selectedHub,
};
}
export const getAllPublicCharts = () => (dispatch: Function) => {
fetch('https://hub.litmuschaos.io/api/charts/master')
.then((response) => response.json())
.then((data) => {
dispatch({
type: MyHubActions.LOAD_PUBLIC_CHARTS,
payload: data,
});
})
.catch((error) => {
console.error('Cant load data', error);
dispatch({
type: MyHubActions.LOAD_PUBLIC_CHARTS,
payload: [],
});
});
};

View File

@ -12,8 +12,7 @@ import * as templateReducer from './template';
import * as userReducer from './user';
import * as workflowReducer from './workflow';
import * as hubDetails from './myhub';
import * as publicHubDetails from './publicHub';
import { HubDetails, PublicHubData } from '../../models/redux/myhub';
import { HubDetails } from '../../models/redux/myhub';
export interface RootState {
communityData: AnalyticsData;
@ -23,7 +22,6 @@ export interface RootState {
tabNumber: TabState;
selectTemplate: TemplateData;
hubDetails: HubDetails;
publicHubDetails: PublicHubData;
}
export default () =>
@ -35,5 +33,4 @@ export default () =>
...tabsReducer,
...templateReducer,
...hubDetails,
...publicHubDetails,
});

View File

@ -1,22 +0,0 @@
import {
Chart,
MyHubAction,
MyHubActions,
PublicHubData,
} from '../../models/redux/myhub';
import createReducer from './createReducer';
const initialState: PublicHubData = {
charts: [],
};
export const publicHubDetails = createReducer<PublicHubData>(initialState, {
[MyHubActions.LOAD_PUBLIC_CHARTS](state: PublicHubData, action: MyHubAction) {
return {
...state,
charts: action.payload as Chart[],
};
},
});
export default publicHubDetails;

View File

@ -17,6 +17,7 @@ const initialState: UserData = {
name: '',
email: '',
loader: false,
selectedProjectOwner: '',
};
export const userData = createReducer<UserData>(initialState, {

View File

@ -14,6 +14,7 @@ const initialState: WorkflowData = {
description: '',
weights: [],
isCustomWorkflow: false,
namespace: 'litmus',
clusterid: '',
cronSyntax: '',
scheduleType: {
@ -35,9 +36,11 @@ const initialState: WorkflowData = {
yamlLink: '',
yaml: '',
index: -1,
description: '',
},
customWorkflows: [],
stepperActiveStep: 1,
clustername: '',
};
export const workflowData = createReducer<WorkflowData>(initialState, {

View File

@ -0,0 +1,4 @@
export const workflowOnce =
'{\r\n "apiVersion": "argoproj.io/v1alpha1",\r\n "kind": "Workflow",\r\n "metadata": {\r\n "generateName": "argowf-chaos-node-cpu-hog-",\r\n "namespace": "litmus"\r\n },\r\n "spec": null\r\n}';
export const cronWorkflow =
'{\r\n "apiVersion": "argoproj.io/v1alpha1",\r\n "kind": "CronWorkflow",\r\n "metadata": {\r\n "name": "argo-chaos-pod-memory-cron-wf",\r\n "namespace": "litmus"\r\n },\r\n "spec": {\r\n "schedule": "0 * * * *",\r\n "concurrencyPolicy": "Forbid",\r\n "startingDeadlineSeconds": 0,\r\n "workflowSpec": null\r\n }\r\n}';

View File

@ -3,27 +3,37 @@
/* eslint-disable no-console */
import { useQuery } from '@apollo/client';
import {
IconButton,
MuiThemeProvider,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TablePagination,
TableRow,
Paper,
IconButton,
Typography,
} from '@material-ui/core';
import useTheme from '@material-ui/core/styles/useTheme';
import ExpandMoreTwoToneIcon from '@material-ui/icons/ExpandMoreTwoTone';
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
import * as _ from 'lodash';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import useTheme from '@material-ui/core/styles/useTheme';
import ExpandMoreTwoToneIcon from '@material-ui/icons/ExpandMoreTwoTone';
import * as _ from 'lodash';
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
import Loader from '../../../../components/Loader';
import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/quries';
import {
ExecutionData,
WeightageMap,
Workflow,
WorkflowList,
WorkflowListDataVars,
} from '../../../../models/graphql/workflowListData';
import { RootState } from '../../../../redux/reducers';
import {
customThemeAnalyticsTable,
customThemeAnalyticsTableCompareMode,
@ -34,21 +44,11 @@ import {
sortNumAsc,
sortNumDesc,
} from '../../../../utils/sort';
import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/quries';
import { RootState } from '../../../../redux/reducers';
import Loader from '../../../../components/Loader';
import ResilienceScoreComparisonPlot from '../WorkflowComparisonPlot/index';
import useStyles from './styles';
import TableData from './TableData';
import TableHeader from './TableHeader';
import TableToolBar from './TableToolbar';
import {
WeightageMap,
ExecutionData,
Workflow,
WorkflowList,
WorkflowListDataVars,
} from '../../../../models/graphql/workflowListData';
interface RangeType {
startDate: string;
@ -466,7 +466,7 @@ const WorkflowComparisonTable = () => {
const doc = new jsPDF('p', 'mm', 'a4'); // A4 size page of PDF
const position = -45;
doc.setFontSize(10);
doc.text('Litmus Portal Report Version: 1.9', 10, 10);
doc.text('Litmus Portal Report Version: 1.10', 10, 10);
doc.text('Time of Generation:', 10, 15);
doc.text(new Date().toString(), 42, 15);
doc.text(

View File

@ -2,16 +2,22 @@ import { TableCell, Typography, IconButton } from '@material-ui/core';
import React from 'react';
import moment from 'moment';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import useStyles from './styles';
import { Cluster } from '../../../models/graphql/clusterData';
import { history } from '../../../redux/configureStore';
import timeDifferenceForDate from '../../../utils/datesModifier';
import Unimodal from '../../../containers/layouts/Unimodal';
import ButtonFilled from '../../../components/Button/ButtonFilled';
import ButtonOutline from '../../../components/Button/ButtonOutline';
import { RootState } from '../../../redux/reducers';
interface TableDataProps {
data: Cluster;
deleteRow: (clid: string) => void;
}
const TableData: React.FC<TableDataProps> = ({ data }) => {
const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
const classes = useStyles();
const { t } = useTranslation();
@ -22,6 +28,19 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
if (date) return resDate;
return 'Date not available';
};
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(true);
};
const userRole = useSelector((state: RootState) => state.userData.userRole);
const handleClose = () => {
deleteRow(data.cluster_id);
setOpen(false);
};
return (
<>
<TableCell className={classes.tableDataStatus}>
@ -62,6 +81,64 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
<Typography>{data.no_of_workflows}</Typography>
</TableCell>
<TableCell>{timeDifferenceForDate(data.updated_at)}</TableCell>
<TableCell>
<div className={classes.deleteCluster}>
<div>
<IconButton onClick={handleClick}>
<img alt="delete" src="./icons/bin-red.svg" />
</IconButton>
</div>
<div>
<Typography>{t('targets.modalDelete.delete')}</Typography>
</div>
</div>
<div>
{open ? (
<div>
<Unimodal
open={open}
handleClose={() => {
setOpen(false);
}}
hasCloseBtn
>
<div className={classes.body}>
<img src="/icons/bin-red-delete.svg" alt="Delete" />
<div className={classes.text}>
<Typography className={classes.typo} align="center">
{t('targets.modalDelete.head1')} <br />
<strong> {t('targets.modalDelete.head2')}</strong>
</Typography>
</div>
<div className={classes.textSecond}>
<Typography className={classes.typoSub} align="center">
{t('targets.modalDelete.head3')}
</Typography>
</div>
<div className={classes.buttonGroup}>
<ButtonOutline
isDisabled={false}
handleClick={() => {
setOpen(false);
}}
>
<> {t('targets.modalDelete.no')}</>
</ButtonOutline>
<ButtonFilled
isDisabled={userRole === 'Viewer'}
isPrimary
handleClick={handleClose}
>
<>{t('targets.modalDelete.yes')}</>
</ButtonFilled>
</div>
</div>
</Unimodal>
</div>
) : null}
</div>
</TableCell>
</>
);
};

View File

@ -13,11 +13,11 @@ import {
import moment from 'moment';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useQuery } from '@apollo/client';
import { useMutation, useQuery } from '@apollo/client';
import { useTranslation } from 'react-i18next';
import HeaderSection from './HeaderSection';
import useStyles from './styles';
import { GET_CLUSTER } from '../../../graphql';
import { GET_CLUSTER, DELETE_CLUSTER } from '../../../graphql';
import Loader from '../../../components/Loader';
import { RootState } from '../../../redux/reducers';
import TableData from './TableData';
@ -31,6 +31,7 @@ import {
Cluster,
ClusterVars,
Clusters,
DeleteCluster,
} from '../../../models/graphql/clusterData';
interface FilterOptions {
@ -79,6 +80,9 @@ const BrowseCluster = () => {
}
);
// Apollo mutation to delete the selected Target Cluster
const [deleteCluster] = useMutation<DeleteCluster>(DELETE_CLUSTER);
const [dateRange, setDateRange] = React.useState<DateData>({
dateValue: 'Select a period',
fromDate: new Date(0).toString(),
@ -205,6 +209,12 @@ const BrowseCluster = () => {
};
const { t } = useTranslation();
const deleteRow = (clid: string) => {
deleteCluster({
variables: { cluster_id: clid },
});
};
return (
<div>
<section className="Heading section">
@ -309,6 +319,15 @@ const BrowseCluster = () => {
</Typography>
</div>
</TableCell>
{/* Delete Cluster */}
<TableCell className={classes.headData}>
<div className={classes.tableCell}>
<Typography>
{t('workflowCluster.header.formControl.delete')}
</Typography>
</div>
</TableCell>
</TableRow>
</TableHead>
@ -316,13 +335,13 @@ const BrowseCluster = () => {
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={6}>
<TableCell colSpan={7}>
<Loader />
</TableCell>
</TableRow>
) : error ? (
<TableRow>
<TableCell data-cy="browseClusterError" colSpan={6}>
<TableCell data-cy="browseClusterError" colSpan={7}>
<Typography align="center">
{t('workflowCluster.header.formControl.fetchingError')}
</Typography>
@ -341,7 +360,7 @@ const BrowseCluster = () => {
key={data.cluster_id}
className={classes.dataRow}
>
<TableData data={data} />
<TableData data={data} deleteRow={deleteRow} />
</TableRow>
))
) : (

View File

@ -213,10 +213,9 @@ const useStyles = makeStyles((theme) => ({
// for yes or no buttons
buttonGroup: {
display: 'flex',
width: '10.75rem',
height: '2.75rem',
marginTop: theme.spacing(2.5),
justifyContent: 'space-between',
gap: '1rem',
},
// delete user
delDiv: {

View File

@ -13,6 +13,8 @@ import MoreVertIcon from '@material-ui/icons/MoreVert';
import moment from 'moment';
import React from 'react';
import cronstrue from 'cronstrue';
import YAML from 'yaml';
import GetAppIcon from '@material-ui/icons/GetApp';
import { ScheduleWorkflow } from '../../../models/graphql/scheduleData';
import useStyles from './styles';
import ExperimentPoints from './ExperimentPoints';
@ -48,6 +50,21 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
setAnchorEl(null);
};
// Function to download the manifest
const downloadYAML = (manifest: string, name: string) => {
const parsedYAML = YAML.parse(manifest);
const doc = new YAML.Document();
doc.contents = parsedYAML;
const element = document.createElement('a');
const file = new Blob([YAML.stringify(doc)], {
type: 'text/yaml',
});
element.href = URL.createObjectURL(file);
element.download = `${name}.yaml`;
document.body.appendChild(element);
element.click();
};
// Function to convert UNIX time in format of DD MMM YYY
const formatDate = (date: string) => {
const updated = new Date(parseInt(date, 10) * 1000).toString();
@ -150,6 +167,22 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
open={open}
onClose={handleClose}
>
<MenuItem
value="Download"
onClick={() =>
downloadYAML(data.workflow_manifest, data.workflow_name)
}
>
<div className={classes.expDiv}>
<GetAppIcon className={classes.downloadBtn} />
<Typography
data-cy="downloadManifest"
className={classes.downloadText}
>
Download Manifest
</Typography>
</div>
</MenuItem>
<MenuItem
value="Analysis"
onClick={() => deleteRow(data.workflow_id)}

View File

@ -152,7 +152,15 @@ const useStyles = makeStyles((theme) => ({
btnText: {
paddingLeft: theme.spacing(1.625),
},
downloadText: {
paddingLeft: theme.spacing(1.2),
},
downloadBtn: {
marginTop: theme.spacing(0.375),
marginLeft: theme.spacing(-0.375),
width: '1.2rem',
height: '1.2rem',
},
// Experiment Weights PopOver Property
weightDiv: {
width: '15.1875rem',

View File

@ -15,7 +15,7 @@ const Templates = () => {
const testWeights: number[] = [];
// Setting initial selected template ID to 0
template.selectTemplate({ selectedTemplateID: -1, isDisable: true });
template.selectTemplate({ selectedTemplateID: 0, isDisable: true });
const selectWorkflow = (index: number) => {
// Updating template ID to the selected one
@ -67,6 +67,7 @@ const Templates = () => {
selectWorkflow(index);
}}
workflows={workflowData}
isCustomWorkflowVisible={false}
/>
</div>
</div>

View File

@ -157,6 +157,7 @@ const ChooseWorkflow: React.FC = () => {
selectWorkflow(index);
}}
workflows={workflowsList}
isCustomWorkflowVisible
/>
<div className={classes.paddedTop}>
<ButtonFilled

View File

@ -5,10 +5,10 @@ import useStyles from './styles';
interface BackButtonProps {
isDisabled: boolean;
gotoStep: (page: number) => void;
onClick: () => void;
}
const BackButton: React.FC<BackButtonProps> = ({ isDisabled, gotoStep }) => {
const BackButton: React.FC<BackButtonProps> = ({ isDisabled, onClick }) => {
const classes = useStyles();
const { t } = useTranslation();
return (
@ -16,7 +16,7 @@ const BackButton: React.FC<BackButtonProps> = ({ isDisabled, gotoStep }) => {
size="medium"
className={classes.btn}
disabled={isDisabled}
onClick={() => gotoStep(1)}
onClick={onClick}
>
<img src="/icons/back.svg" alt="back" />
<Typography className={classes.text}>

View File

@ -1,6 +1,8 @@
import { useLazyQuery, useQuery } from '@apollo/client';
import {
Button,
FormControl,
FormControlLabel,
IconButton,
InputAdornment,
InputLabel,
@ -8,29 +10,37 @@ import {
MenuList,
OutlinedInput,
Paper,
Radio,
RadioGroup,
Select,
Typography,
ClickAwayListener,
} from '@material-ui/core';
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
import BackButton from '../../../../components/Button/BackButton';
import YAML from 'yaml';
import ButtonFilled from '../../../../components/Button/ButtonFilled';
import InputField from '../../../../components/InputField';
import Loader from '../../../../components/Loader';
import { GET_CHARTS_DATA, GET_HUB_STATUS } from '../../../../graphql';
import { MyHubDetail } from '../../../../models/graphql/user';
import { Chart, Charts, HubStatus } from '../../../../models/redux/myhub';
import { Charts, HubStatus } from '../../../../models/redux/myhub';
import * as WorkflowActions from '../../../../redux/actions/workflow';
import useActions from '../../../../redux/actions';
import { RootState } from '../../../../redux/reducers';
import useStyles, { CustomTextField, MenuProps } from './styles';
import WorkflowDetails from '../../../../pages/WorkflowDetails';
import { GET_EXPERIMENT_YAML } from '../../../../graphql/quries';
import BackButton from '../BackButton';
import * as TemplateSelectionActions from '../../../../redux/actions/template';
import { history } from '../../../../redux/configureStore';
interface WorkflowDetails {
workflow_name: string;
workflow_desc: string;
namespace: string;
}
interface ChartName {
@ -43,33 +53,61 @@ interface VerifyCommitProps {
}
const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
const userData = useSelector((state: RootState) => state.userData);
const hubData = useSelector((state: RootState) => state.publicHubDetails);
const workflowDetails = useSelector((state: RootState) => state.workflowData);
const workflowAction = useActions(WorkflowActions);
const { selectedProjectID } = useSelector(
(state: RootState) => state.userData
);
const [workflowData, setWorkflowData] = useState<WorkflowDetails>({
workflow_name: workflowDetails.name,
workflow_desc: workflowDetails.description,
namespace: workflowDetails.namespace,
});
const { t } = useTranslation();
const classes = useStyles();
const [allExperiment, setAllExperiment] = useState<ChartName[]>([]);
const [selectedHub, setSelectedHub] = useState('Public Hub');
const [allExperiments, setAllExperiments] = useState<ChartName[]>([]);
const [selectedHub, setSelectedHub] = useState('');
const [selectedExp, setSelectedExp] = useState(
t('customWorkflow.createWorkflow.selectAnExp') as string
);
const allExp: ChartName[] = [];
const [availableHubs, setAvailableHubs] = useState<MyHubDetail[]>([]);
const template = useActions(TemplateSelectionActions);
const [constructYAML, setConstructYAML] = useState('construct');
const [selectedHubDetails, setSelectedHubDetails] = useState<MyHubDetail>();
// Get all MyHubs with status
const { data } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: userData.username },
fetchPolicy: 'cache-and-network',
const [getExperimentYaml] = useLazyQuery(GET_EXPERIMENT_YAML, {
variables: {
experimentInput: {
ProjectID: selectedProjectID,
HubName: selectedHub,
ChartName: selectedExp.split('/')[0],
ExperimentName: selectedExp.split('/')[1],
FileType: 'experiment',
},
},
onCompleted: (data) => {
const parsedYaml = YAML.parse(data.getYAMLData);
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
description: parsedYaml.description.message,
},
});
gotoStep(1);
},
});
// Graphql query to get charts
const [getCharts, { loading: chartsLoading }] = useLazyQuery<Charts>(
GET_CHARTS_DATA,
{
onCompleted: (data) => {
const allExp: ChartName[] = [];
data.getCharts.forEach((data) => {
return data.Spec.Experiments?.forEach((experiment) => {
allExp.push({
@ -78,11 +116,29 @@ const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
});
});
});
setAllExperiment(allExp);
setAllExperiments([...allExp]);
},
fetchPolicy: 'cache-and-network',
}
);
// Get all MyHubs with status
const { data } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: selectedProjectID },
fetchPolicy: 'cache-and-network',
onCompleted: (data) => {
setSelectedHub(data.getHubStatus[0].HubName);
setAvailableHubs([...data.getHubStatus]);
getCharts({
variables: {
projectID: selectedProjectID,
HubName: data.getHubStatus[0].HubName,
},
});
setSelectedHubDetails(data.getHubStatus[0]);
},
});
// Function to get charts of a particular hub
const findChart = (hubname: string) => {
const myHubData = data?.getHubStatus.filter((myHub) => {
@ -90,68 +146,77 @@ const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
})[0];
getCharts({
variables: {
data: {
UserName: userData.username,
RepoURL: myHubData?.RepoURL,
RepoBranch: myHubData?.RepoBranch,
HubName: hubname,
},
projectID: selectedProjectID,
HubName: hubname,
},
});
setSelectedHubDetails(myHubData);
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
hubName: hubname,
repoUrl: myHubData?.RepoURL,
repoBranch: myHubData?.RepoBranch,
},
});
};
useEffect(() => {
if (selectedHub === 'Public Hub') {
setSelectedHub('Public Hub');
const ChartsData = hubData.charts;
ChartsData.forEach((data: Chart) => {
if (data.Spec.Experiments) {
data.Spec.Experiments.forEach((experiment) => {
allExp.push({
ChaosName: data.Metadata.Name,
ExperimentName: experiment,
});
});
}
});
setAllExperiment([...allExp]);
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
hubName: 'Public Hub',
repoUrl: 'https://github.com/litmuschaos/chaos-charts',
repoBranch: 'master',
},
});
} else {
setAllExperiment([]);
}
}, [selectedHub]);
const availableHubs: MyHubDetail[] = data ? data.getHubStatus : [];
const [open, setOpen] = useState(false);
const filteredExperiment = allExperiment.filter((exp) => {
const filteredExperiment = allExperiments.filter((exp) => {
const name = `${exp.ChaosName}/${exp.ExperimentName}`;
if (selectedExp === 'Select an experiment') {
return true;
}
return name.includes(selectedExp);
});
const [uploadedYAML, setUploadedYAML] = useState('');
const [fileName, setFileName] = useState<string | null>('');
const handleDrag = (e: React.DragEvent<HTMLDivElement>) => {
Array.from(e.dataTransfer.files)
.filter(
(file) =>
file.name.split('.')[1] === 'yaml' ||
file.name.split('.')[1] === 'yml'
)
.forEach(async (file) => {
const readFile = await file.text();
setUploadedYAML(readFile);
setFileName(file.name);
const parsedYaml = YAML.parse(readFile);
workflowAction.setWorkflowDetails({
...workflowDetails,
yaml: YAML.stringify(parsedYaml),
});
});
};
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const readFile = e.target.files && e.target.files[0];
setFileName(readFile && readFile.name);
const extension = readFile?.name.substring(
readFile.name.lastIndexOf('.') + 1
);
if ((extension === 'yaml' || extension === 'yml') && readFile) {
readFile.text().then((response) => {
setUploadedYAML(response);
const parsedYaml = YAML.parse(response);
workflowAction.setWorkflowDetails({
...workflowDetails,
yaml: YAML.stringify(parsedYaml),
});
});
} else {
workflowAction.setWorkflowDetails({
...workflowDetails,
yaml: '',
});
}
};
return (
<div className={classes.root}>
<div className={classes.headerDiv}>
<BackButton isDisabled={false} />
<BackButton
isDisabled={false}
onClick={() => {
workflowAction.setWorkflowDetails({
isCustomWorkflow: false,
});
window.history.back();
}}
/>
<Typography variant="h3" className={classes.headerText} gutterBottom>
{t('customWorkflow.createWorkflow.create')}
</Typography>
@ -173,12 +238,13 @@ const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
styles={{
width: '100%',
}}
data-cy="inputWorkflow"
data-cy="inputWorkflowName"
validationError={false}
handleChange={(e) => {
setWorkflowData({
workflow_name: e.target.value,
workflow_desc: workflowData.workflow_desc,
namespace: workflowData.namespace,
});
}}
value={workflowData.workflow_name}
@ -190,7 +256,7 @@ const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
</Typography>
<CustomTextField
label="Description"
data-cy="inputWorkflow"
data-cy="inputWorkflowDesc"
InputProps={{
disableUnderline: true,
classes: {
@ -201,6 +267,7 @@ const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
setWorkflowData({
workflow_name: workflowData.workflow_name,
workflow_desc: e.target.value,
namespace: workflowData.namespace,
});
}}
value={workflowData.workflow_desc}
@ -209,152 +276,285 @@ const CreateWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
/>
</div>
<hr />
<div className={classes.inputDiv}>
<Typography variant="h6" className={classes.titleText}>
{t('customWorkflow.createWorkflow.firstChaos')}
</Typography>
<FormControl
variant="outlined"
className={classes.formControl}
color="secondary"
focused
<Typography variant="h5" className={classes.configureYAML}>
<strong>{t('customWorkflow.createWorkflow.configure')}</strong>
</Typography>
<FormControl component="fieldset">
<RadioGroup
aria-label="gender"
name="gender1"
value={constructYAML}
onChange={(event) => {
setConstructYAML(event.target.value);
if (event.target.value === 'construct') {
setUploadedYAML('');
}
}}
>
<InputLabel className={classes.selectText}>
{t('customWorkflow.createWorkflow.selectHub')}
</InputLabel>
<Select
value={selectedHub}
onChange={(e) => {
setSelectedHub(e.target.value as string);
if (e.target.value !== 'Public Hub') {
findChart(e.target.value as string);
}
}}
label="Cluster Status"
MenuProps={MenuProps}
className={classes.selectText}
>
<MenuItem value="Public Hub">
{t('customWorkflow.createWorkflow.public')}
</MenuItem>
{availableHubs.map((hubs) => (
<MenuItem key={hubs.HubName} value={hubs.HubName}>
{hubs.HubName}
</MenuItem>
))}
</Select>
</FormControl>
</div>
<div className={classes.inputDiv}>
<Typography variant="h6" className={classes.titleText}>
{t('customWorkflow.createWorkflow.chooseExp')}
</Typography>
{chartsLoading ? (
<div className={classes.chooseExpDiv}>
<Loader />
<Typography variant="body2">
{t('customWorkflow.createWorkflow.loadingExp')}
</Typography>
</div>
) : (
<FormControl
variant="outlined"
color="secondary"
focused
component="button"
className={classes.formControlExp}
>
<InputLabel className={classes.selectText1}>
{t('customWorkflow.createWorkflow.selectExp')}
</InputLabel>
<OutlinedInput
value={selectedExp}
onChange={(e) => {
setSelectedExp(e.target.value);
setOpen(true);
}}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => {
setOpen(!open);
<FormControlLabel
value="construct"
control={<Radio />}
label={
<Typography className={classes.radioText}>
{t('customWorkflow.createWorkflow.construct')}
</Typography>
}
/>
{constructYAML === 'construct' ? (
<>
<div className={classes.inputDiv}>
<Typography variant="h6" className={classes.titleText}>
{t('customWorkflow.createWorkflow.firstChaos')}
</Typography>
<FormControl
variant="outlined"
className={classes.formControl}
color="secondary"
focused
>
<InputLabel className={classes.selectText}>
{t('customWorkflow.createWorkflow.selectHub')}
</InputLabel>
<Select
value={selectedHub}
onChange={(e) => {
setSelectedHub(e.target.value as string);
findChart(e.target.value as string);
}}
edge="end"
label="Cluster Status"
MenuProps={MenuProps}
className={classes.selectText}
>
<ArrowDropDownIcon />
</IconButton>
</InputAdornment>
}
className={classes.inputExpDiv}
labelWidth={150}
/>
{open ? (
<Paper elevation={3}>
<MenuList className={classes.expMenu}>
{filteredExperiment.length > 0 ? (
filteredExperiment.map((exp) => (
<MenuItem
key={`${exp.ChaosName}/${exp.ExperimentName}`}
value={`${exp.ChaosName}/${exp.ExperimentName}`}
onClick={() => {
setSelectedExp(
`${exp.ChaosName}/${exp.ExperimentName}`
);
setOpen(false);
if (selectedHub === 'Public Hub') {
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
experiment_name: `${exp.ChaosName}/${exp.ExperimentName}`,
yamlLink: `${workflowDetails.customWorkflow.repoUrl}/raw/${workflowDetails.customWorkflow.repoBranch}/charts/${exp.ChaosName}/${exp.ExperimentName}/engine.yaml`,
},
});
} else {
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
experiment_name: `${exp.ChaosName}/${exp.ExperimentName}`,
yamlLink: `${selectedHubDetails?.RepoURL}/raw/${selectedHubDetails?.RepoBranch}/charts/${exp.ChaosName}/${exp.ExperimentName}/engine.yaml`,
},
});
}
}}
>
{exp.ExperimentName}
{availableHubs.map((hubs) => (
<MenuItem key={hubs.HubName} value={hubs.HubName}>
{hubs.HubName}
</MenuItem>
))
) : (
<MenuItem value="Select an experiment">
{t('customWorkflow.createWorkflow.noExp')}
</MenuItem>
)}
</MenuList>
</Paper>
) : null}
</FormControl>
)}
</div>
))}
</Select>
</FormControl>
</div>
<div className={classes.inputDiv}>
<Typography variant="h6" className={classes.titleText}>
{t('customWorkflow.createWorkflow.chooseExp')}
</Typography>
{chartsLoading ? (
<div className={classes.chooseExpDiv}>
<Loader />
<Typography variant="body2">
{t('customWorkflow.createWorkflow.loadingExp')}
</Typography>
</div>
) : (
<FormControl
variant="outlined"
color="secondary"
focused
component="button"
className={classes.formControlExp}
>
<InputLabel className={classes.selectText1}>
{t('customWorkflow.createWorkflow.selectExp')}
</InputLabel>
<OutlinedInput
value={selectedExp}
onChange={(e) => {
setSelectedExp(e.target.value);
setOpen(true);
}}
endAdornment={
<InputAdornment position="end">
<IconButton
onClick={() => {
setOpen(!open);
}}
edge="end"
>
<ArrowDropDownIcon />
</IconButton>
</InputAdornment>
}
className={classes.inputExpDiv}
labelWidth={150}
/>
{open ? (
<ClickAwayListener onClickAway={() => setOpen(!open)}>
<Paper elevation={3}>
<MenuList className={classes.expMenu}>
{filteredExperiment.length > 0 ? (
filteredExperiment.map((exp) => (
<MenuItem
key={`${exp.ChaosName}/${exp.ExperimentName}`}
value={`${exp.ChaosName}/${exp.ExperimentName}`}
onClick={() => {
setSelectedExp(
`${exp.ChaosName}/${exp.ExperimentName}`
);
setOpen(false);
if (selectedHub === 'Public Hub') {
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
experiment_name: `${exp.ChaosName}/${exp.ExperimentName}`,
yamlLink: `${workflowDetails.customWorkflow.repoUrl}/raw/${workflowDetails.customWorkflow.repoBranch}/charts/${exp.ChaosName}/${exp.ExperimentName}/engine.yaml`,
},
});
} else {
workflowAction.setWorkflowDetails({
customWorkflow: {
...workflowDetails.customWorkflow,
experiment_name: `${exp.ChaosName}/${exp.ExperimentName}`,
yamlLink: `${selectedHubDetails?.RepoURL}/raw/${selectedHubDetails?.RepoBranch}/charts/${exp.ChaosName}/${exp.ExperimentName}/engine.yaml`,
},
});
}
}}
>
{exp.ExperimentName}
</MenuItem>
))
) : (
<MenuItem value="Select an experiment">
{t('customWorkflow.createWorkflow.noExp')}
</MenuItem>
)}
</MenuList>
</Paper>
</ClickAwayListener>
) : null}
</FormControl>
)}
</div>
<div className={classes.inputDiv}>
<Typography variant="h6" className={classes.titleText}>
{t('customWorkflow.createWorkflow.chooseNamespace')}
</Typography>
<FormControl
variant="outlined"
color="secondary"
focused
component="button"
className={classes.formControlExp}
>
<InputLabel className={classes.selectText1}>
{t('customWorkflow.createWorkflow.chooseNamespace')}
</InputLabel>
<OutlinedInput
value={workflowData.namespace}
onChange={(e) => {
setWorkflowData({
workflow_name: workflowData.workflow_name,
workflow_desc: workflowData.workflow_desc,
namespace: e.target.value,
});
}}
className={classes.inputExpDiv}
labelWidth={130}
/>
</FormControl>
</div>
</>
) : null}
<FormControlLabel
value="upload"
control={<Radio />}
label={
<Typography className={classes.radioText}>
{t('customWorkflow.createWorkflow.upload')}{' '}
<strong>{t('customWorkflow.createWorkflow.yaml')}</strong>
</Typography>
}
/>
{constructYAML === 'upload' ? (
<Paper
elevation={3}
component="div"
onDragOver={(e) => {
e.preventDefault();
}}
onDrop={(e) => {
e.preventDefault();
handleDrag(e);
}}
className={classes.uploadYAMLDiv}
>
{uploadedYAML === '' ? (
<div className={classes.uploadYAMLText}>
<img src="/icons/upload-yaml.svg" alt="upload yaml" />
<Typography variant="h5">
{t('customWorkflow.createWorkflow.drag')}
</Typography>
<Typography>or</Typography>
<input
accept=".yaml"
style={{ display: 'none' }}
id="contained-button-file"
type="file"
onChange={(e) => {
handleFileUpload(e);
}}
/>
<label htmlFor="contained-button-file">
<Button
variant="outlined"
className={classes.uploadBtn}
component="span"
>
{t('customWorkflow.createWorkflow.uploadFile')}
</Button>
</label>
</div>
) : (
<div className={classes.uploadSuccessDiv}>
<img
src="/icons/upload-success.svg"
alt="checkmark"
className={classes.uploadSuccessImg}
/>
<Typography className={classes.uploadSuccessText}>
{t('customWorkflow.createWorkflow.uploadSuccess')}{' '}
{fileName}
</Typography>
</div>
)}
</Paper>
) : null}
</RadioGroup>
</FormControl>
</div>
</div>
<div className={classes.nextButtonDiv}>
<ButtonFilled
handleClick={() => {
if (constructYAML === 'upload' && uploadedYAML !== '') {
history.push('/create-workflow');
template.selectTemplate({ isDisable: false });
}
workflowAction.setWorkflowDetails({
name: workflowData.workflow_name,
description: workflowData.workflow_desc,
namespace: workflowData.namespace,
customWorkflow: {
...workflowDetails.customWorkflow,
hubName: selectedHub,
repoUrl: selectedHubDetails?.RepoURL,
repoBranch: selectedHubDetails?.RepoBranch,
yaml: '',
index: -1,
},
});
gotoStep(1);
getExperimentYaml();
}}
isPrimary
isDisabled={
selectedExp === 'Select an experiment' ||
filteredExperiment.length !== 1
constructYAML === 'construct' &&
(selectedExp === 'Select an experiment' ||
filteredExperiment.length !== 1 ||
workflowData.namespace.trim() === '')
? true
: !!(constructYAML === 'upload' && uploadedYAML === '')
}
>
<div>

View File

@ -65,15 +65,72 @@ const useStyles = makeStyles((theme) => ({
height: '2.5rem',
minWidth: '9rem',
backgroundColor: theme.palette.common.white,
outline: 'none',
},
inputExpDiv: {
height: '2.5rem',
maxWidth: '15.625rem',
backgroundColor: theme.palette.common.white,
},
expMenu: {
minWidth: '15.625rem',
maxHeight: '9.375rem',
overflow: 'auto',
zIndex: 2,
backgroundColor: theme.palette.common.white,
},
configureYAML: {
marginTop: theme.spacing(2.5),
marginBottom: theme.spacing(2.5),
},
radioText: {
fontSize: '1rem',
},
uploadYAMLDiv: {
width: '39.375rem',
height: '7.5rem',
backgroundColor: theme.palette.common.white,
border: `1px dashed ${theme.palette.common.black}`,
margin: 'auto',
marginTop: theme.spacing(5),
borderRadius: theme.spacing(1.25),
},
uploadYAMLText: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
width: '31.25rem',
margin: 'auto',
paddingTop: theme.spacing(3.125),
},
uploadBtn: {
textTransform: 'none',
width: '8.5rem',
fontSize: '0.9rem',
height: '2.8125rem',
border: `2px solid ${theme.palette.secondary.main}`,
borderRadius: theme.spacing(0.5),
},
uploadSuccessDiv: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
maxWidth: '31.25rem',
margin: '0 auto',
paddingTop: theme.spacing(4.375),
},
uploadSuccessImg: {
width: '3.125rem',
height: '3.125rem',
verticalAlign: 'middle',
paddingBottom: theme.spacing(1),
},
uploadSuccessText: {
display: 'inline-block',
fontSize: '1rem',
marginBottom: theme.spacing(1.25),
marginLeft: theme.spacing(2.5),
},
}));
export default useStyles;

View File

@ -3,7 +3,6 @@ import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import YAML from 'yaml';
import { useTranslation } from 'react-i18next';
import BackButton from '../BackButton';
import ButtonFilled from '../../../../components/Button/ButtonFilled';
import ButtonOutline from '../../../../components/Button/ButtonOutline';
import { customWorkflow } from '../../../../models/redux/workflow';
@ -56,6 +55,7 @@ const ScheduleCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
// Edit experiment operation
const editExperiment = (index: number) => {
workflowAction.setWorkflowDetails({
...workflowDetails,
customWorkflow: {
...workflowDetails.customWorkflows[index],
index,
@ -94,7 +94,7 @@ const ScheduleCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
parameters: [
{
name: 'adminModeNamespace',
value: 'litmus',
value: `${workflowDetails.namespace}`,
},
],
},
@ -217,7 +217,6 @@ const ScheduleCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
return (
<div className={classes.root}>
<div className={classes.headerDiv}>
<BackButton isDisabled={false} gotoStep={() => gotoStep(1)} />
<Typography variant="h3" className={classes.headerText} gutterBottom>
{t('customWorkflow.scheduleWorkflow.scheduleHeader')}
</Typography>

View File

@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
import YAML from 'yaml';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { useLazyQuery } from '@apollo/client';
import BackButton from '../BackButton';
import ButtonFilled from '../../../../components/Button/ButtonFilled';
import InputField from '../../../../components/InputField';
@ -11,6 +12,7 @@ import { RootState } from '../../../../redux/reducers';
import useActions from '../../../../redux/actions';
import * as WorkflowActions from '../../../../redux/actions/workflow';
import Loader from '../../../../components/Loader';
import { GET_ENGINE_YAML } from '../../../../graphql/quries';
interface EnvValues {
name: string;
@ -32,17 +34,37 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
const [overrideEnvs, setOverrideEnvs] = useState<EnvValues[]>([
{ name: '', value: '' },
]);
const { customWorkflow, customWorkflows } = useSelector(
(state: RootState) => state.workflowData
);
const [appInfo, setAppInfo] = useState<AppInfo>({
appns: 'kube-system',
applabel: 'k8s-app=kube-proxy',
appkind: 'daemonset',
});
const { t } = useTranslation();
const workflowDetails = useSelector((state: RootState) => state.workflowData);
const workflowAction = useActions(WorkflowActions);
const [expDesc, setExpDesc] = useState('');
const [loadingEnv, setLoadingEnv] = useState(true);
const [env, setEnv] = useState<EnvValues[]>([]);
const [yaml, setYaml] = useState<string>('');
const [loadingEnv, setLoadingEnv] = useState(true);
const { t } = useTranslation();
const userData = useSelector((state: RootState) => state.userData);
const [getEngineYaml] = useLazyQuery(GET_ENGINE_YAML, {
onCompleted: (data) => {
const parsedYaml = YAML.parse(data.getYAMLData);
setEnv([...parsedYaml.spec.experiments[0].spec.components.env]);
setAppInfo({
appns: parsedYaml.spec.appinfo.appns,
applabel: parsedYaml.spec.appinfo.applabel,
appkind: parsedYaml.spec.appinfo.appkind,
});
setYaml(YAML.stringify(parsedYaml));
setLoadingEnv(false);
},
});
const workflowAction = useActions(WorkflowActions);
const changeKey = (
index: number,
event: React.ChangeEvent<HTMLInputElement>
@ -60,7 +82,6 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
const AddEnvPair = () => {
setOverrideEnvs([...overrideEnvs, { name: '', value: '' }]);
};
const [env, setEnv] = useState<EnvValues[]>([]);
const changeOriginalEnvValue = (
index: number,
@ -69,87 +90,54 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
env[index].value = event.target.value;
setEnv([...env]);
};
// Function to fetch workflow description
const getWorkflowDesc = () => {
fetch(
`${workflowDetails.customWorkflow.repoUrl}/raw/${workflowDetails.customWorkflow.repoBranch}/charts/${workflowDetails.customWorkflow.experiment_name}/experiment.yaml`
)
.then((data) => {
data.text().then((yamlText) => {
const parsedYaml = YAML.parse(yamlText);
setExpDesc(parsedYaml.description.message);
});
})
.catch((error) => {
console.error('Error', error);
});
if (expDesc) {
return expDesc;
}
return '';
};
// UseEffect to fetch the env variables
useEffect(() => {
if (workflowDetails.customWorkflow.yaml === '') {
fetch(workflowDetails.customWorkflow.yamlLink as string)
.then((data) => {
data.text().then((yamlText) => {
const parsedYaml = YAML.parse(yamlText);
parsedYaml.metadata.name =
workflowDetails.customWorkflow.experiment_name;
setEnv([...parsedYaml.spec.experiments[0].spec.components.env]);
setAppInfo({
appns: parsedYaml.spec.appinfo.appns,
applabel: parsedYaml.spec.appinfo.applabel,
appkind: parsedYaml.spec.appinfo.appkind,
});
setYaml(YAML.stringify(parsedYaml));
setLoadingEnv(false);
});
})
.catch((error) => {
console.error('Error', error);
});
if (customWorkflow.yaml === '') {
getEngineYaml({
variables: {
experimentInput: {
ProjectID: userData.selectedProjectID,
HubName: customWorkflow.hubName,
ChartName: customWorkflow.experiment_name.split('/')[0],
ExperimentName: customWorkflow.experiment_name.split('/')[1],
FileType: 'engine',
},
},
});
} else {
const parsedYaml = YAML.parse(
workflowDetails.customWorkflow.yaml as string
);
const parsedYaml = YAML.parse(customWorkflow.yaml as string);
setAppInfo({
appns: parsedYaml.spec.appinfo.appns,
applabel: parsedYaml.spec.appinfo.applabel,
appkind: parsedYaml.spec.appinfo.appkind,
});
setEnv([...parsedYaml.spec.experiments[0].spec.components.env]);
setYaml(workflowDetails.customWorkflow.yaml as string);
setYaml(customWorkflow.yaml as string);
setLoadingEnv(false);
}
}, []);
// Function to generate sequence of experiemnt
const experimentSequence = () => {
const elemPos = workflowDetails.customWorkflows
const elemPos = customWorkflows
.map((exp) => {
return exp.experiment_name;
})
.indexOf(workflowDetails.customWorkflow.experiment_name);
.indexOf(customWorkflow.experiment_name);
if (
(workflowDetails.customWorkflow.index === -1 &&
workflowDetails.customWorkflows.length === 0) ||
(customWorkflow.index === -1 && customWorkflows.length === 0) ||
elemPos === 0
) {
return 'This is your first experiment';
return t('customWorkflow.tuneExperiment.sequenceFirstExp');
}
if (workflowDetails.customWorkflow.index === -1) {
return `This experiment will execute after
${
workflowDetails.customWorkflows[
workflowDetails.customWorkflows.length - 1
].experiment_name
}`;
if (customWorkflow.index === -1) {
return `${t('customWorkflow.tuneExperiment.sequenceNotFirstExp')} ${
customWorkflows[customWorkflows.length - 1].experiment_name
}`;
}
return `This experiment will execute after
${
workflowDetails.customWorkflows[elemPos - 1].experiment_name
}`;
return `${t('customWorkflow.tuneExperiment.sequenceNotFirstExp')} ${
customWorkflows[elemPos - 1].experiment_name
}`;
};
// Function to handle the change in env variables
const handleEnvModification = () => {
@ -176,33 +164,30 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
parsedYaml.spec.appinfo.appns = appInfo.appns;
parsedYaml.spec.appinfo.applabel = appInfo.applabel;
parsedYaml.spec.appinfo.appkind = appInfo.appkind;
parsedYaml.metadata.name = workflowDetails.customWorkflow.experiment_name?.split(
'/'
)[1];
parsedYaml.metadata.name = customWorkflow.experiment_name?.split('/')[1];
parsedYaml.metadata.namespace =
'{{workflow.parameters.adminModeNamespace}}';
parsedYaml.spec.chaosServiceAccount = 'litmus-admin';
const YamlString = YAML.stringify(parsedYaml);
const experimentArray = workflowDetails.customWorkflows;
const experimentArray = customWorkflows;
if (workflowDetails.customWorkflow.index === -1) {
if (customWorkflow.index === -1) {
workflowAction.setWorkflowDetails({
customWorkflows: [
...workflowDetails.customWorkflows,
...customWorkflows,
{
experiment_name: workflowDetails.customWorkflow.experiment_name,
hubName: workflowDetails.customWorkflow.hubName,
repoUrl: workflowDetails.customWorkflow.repoUrl,
repoBranch: workflowDetails.customWorkflow.repoBranch,
yamlLink: workflowDetails.customWorkflow.yamlLink,
experiment_name: customWorkflow.experiment_name,
hubName: customWorkflow.hubName,
repoUrl: customWorkflow.repoUrl,
repoBranch: customWorkflow.repoBranch,
yamlLink: customWorkflow.yamlLink,
yaml: YamlString,
description: customWorkflow.description,
},
],
});
} else {
experimentArray[
workflowDetails.customWorkflow.index as number
].yaml = YamlString;
experimentArray[customWorkflow.index as number].yaml = YamlString;
workflowAction.setWorkflowDetails({
customWorkflows: [...experimentArray],
});
@ -212,7 +197,9 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
return (
<div className={classes.root}>
<div className={classes.headerDiv}>
<BackButton isDisabled={false} gotoStep={() => gotoStep(0)} />
{customWorkflow.index === -1 ? (
<BackButton isDisabled={false} onClick={() => gotoStep(0)} />
) : null}
<Typography variant="h3" className={classes.headerText} gutterBottom>
{t('customWorkflow.tuneExperiment.headerText')}
</Typography>
@ -230,7 +217,7 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
{t('customWorkflow.tuneExperiment.expName')}:
</Typography>
<Typography className={classes.mainDetail}>
{workflowDetails.customWorkflow.experiment_name}
{customWorkflow.experiment_name}
</Typography>
</div>
<div className={classes.inputDiv}>
@ -238,7 +225,7 @@ const TuneCustomWorkflow: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
{t('customWorkflow.tuneExperiment.description')}:
</Typography>
<Typography className={classes.mainDetail}>
{getWorkflowDesc()}
{customWorkflow.description}
</Typography>
</div>
<hr className={classes.horizontalLineHeader} />

View File

@ -24,7 +24,7 @@ const ExperimentEditor: React.FC<ExperimentEditorProps> = ({ gotoStep }) => {
return (
<div className={classes.root}>
<div className={classes.tuneDiv}>
<BackButton isDisabled={false} gotoStep={() => gotoStep(2)} />
<BackButton isDisabled={false} onClick={() => gotoStep(2)} />
<Typography className={classes.headerText} variant="h4">
{customWorkflow.experiment_name}
</Typography>

View File

@ -43,6 +43,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
description,
weights,
cronSyntax,
clustername,
} = workflowData;
const [open, setOpen] = React.useState(false);
@ -92,7 +93,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
annotations: editorValidations.annotations,
};
if (stateObject.annotations.length > 0) {
setYamlStatus('Error in CRD Yaml.');
setYamlStatus('Error in CRHeloD Yaml.');
} else {
setYamlStatus('Your code is fine. You can move on !');
}
@ -141,6 +142,18 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
/>
</div>
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
{t('createWorkflow.verifyCommit.summary.clustername')}:
</Typography>
</div>
<Typography className={classes.clusterName}>
{clustername}
</Typography>
</div>
<div className={classes.summaryDiv}>
<div className={classes.innerSumDiv}>
<Typography className={classes.col1}>
@ -254,22 +267,6 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
</div>
</div>
<Divider />
{/*
<div>
<Typography className={classes.config}>
The configuration details of this workflow will be committed to:{' '}
<span>
<Link
href="https://github.com/abcorn-org/reputeops/sandbox-repute.yaml"
onClick={preventDefault}
className={classes.link}
>
https://github.com/abcorn-org/reputeops/sandbox-repute.yaml
</Link>
</span>
</Typography>
</div>
*/}
</div>
<Unimodal

View File

@ -72,6 +72,11 @@ const useStyles = makeStyles((theme: Theme) => ({
display: 'flex',
flexDirection: 'row',
},
clusterName: {
fontSize: '1rem',
marginLeft: theme.spacing(5),
paddingTop: theme.spacing(0.5),
},
editButton1: {
marginLeft: theme.spacing(1),
},

View File

@ -46,20 +46,25 @@ const WorkflowCluster: React.FC<WorkflowClusterProps> = ({ gotoStep }) => {
const selectedProjectID = useSelector(
(state: RootState) => state.userData.selectedProjectID
);
const [clusterData, setclusterData] = useState<Cluster[]>([]);
const [name, setName] = React.useState('');
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
const str: string = event.target.value as string;
let clusterName;
clusterData.forEach((cluster) => {
if (str === cluster.cluster_id) {
clusterName = cluster.cluster_name;
}
});
workflow.setWorkflowDetails({
clusterid: str,
project_id: selectedProjectID,
clustername: clusterName,
});
setName(str);
setTarget(false);
};
const [clusterData, setclusterData] = useState<Cluster[]>([]);
const [getCluster] = useLazyQuery(GET_CLUSTER, {
onCompleted: (data) => {
const clusters: Cluster[] = [];

View File

@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react';
import Paper from '@material-ui/core/Paper';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import { Avatar } from '@material-ui/core';
import CheckCircleSharpIcon from '@material-ui/icons/CheckCircleSharp';
import Box from '@material-ui/core/Box';
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import CancelSharpIcon from '@material-ui/icons/CancelSharp';
import CheckCircleSharpIcon from '@material-ui/icons/CheckCircleSharp';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import useStyles from './styles';
@ -48,7 +48,7 @@ const PassedVsFailed: React.FC<PassedVsFailedProps> = ({ passed, failed }) => {
<Box width={`${passedValue}%`} className={classes.passedBox}>
{/* Render an empty div if props is not
passed */}
{passedValue === 0 ? (
{passedValue < 11 ? (
<div />
) : (
<Avatar className={classes.passedIcon}>
@ -59,7 +59,7 @@ const PassedVsFailed: React.FC<PassedVsFailedProps> = ({ passed, failed }) => {
<Box width={`${failedValue}%`} className={classes.failedBox}>
{/* Render an empty div if props is not
passed */}
{failedValue === 0 ? (
{failedValue < 11 ? (
<div />
) : (
<Avatar className={classes.failedIcon}>

View File

@ -1,7 +1,4 @@
/* eslint-disable max-len */
import React, { useEffect } from 'react';
import Plotly from 'plotly.js';
import createPlotlyComponent from 'react-plotly.js/factory';
import {
FormControl,
IconButton,
@ -10,14 +7,17 @@ import {
Select,
Tooltip,
} from '@material-ui/core';
import AssessmentOutlinedIcon from '@material-ui/icons/AssessmentOutlined';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@material-ui/core/styles';
import useStyles from './style';
import Score from './Score';
import { history } from '../../../redux/configureStore';
import AssessmentOutlinedIcon from '@material-ui/icons/AssessmentOutlined';
import Plotly from 'plotly.js';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import createPlotlyComponent from 'react-plotly.js/factory';
import useActions from '../../../redux/actions';
import * as TabActions from '../../../redux/actions/tabs';
import { history } from '../../../redux/configureStore';
import Score from './Score';
import useStyles from './style';
const Plot = createPlotlyComponent(Plotly);
@ -80,10 +80,10 @@ const ResilienceScoreComparisonPlot: React.FC<ResilienceScoreComparisonPlotProps
dataY = yData.Monthly;
}
const colors = [
palette.error.dark,
palette.primary.dark,
palette.warning.main,
palette.secondary.main,
palette.warning.main,
palette.primary.dark,
palette.error.dark,
];
const lineSize = [3, 3, 3, 3];
const data = [];

View File

@ -2,30 +2,30 @@
/* eslint-disable no-loop-func */
/* eslint-disable max-len */
/* eslint-disable no-console */
import React, { useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useQuery } from '@apollo/client';
import moment from 'moment';
import * as _ from 'lodash';
import { Paper, Typography } from '@material-ui/core';
import * as _ from 'lodash';
import moment from 'moment';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import useStyles from './style';
import { useSelector } from 'react-redux';
import Loader from '../../../components/Loader';
import QuickActionCard from '../../../components/QuickActionCard';
import { WORKFLOW_LIST_DETAILS } from '../../../graphql';
import { ExecutionData } from '../../../models/graphql/workflowData';
import { RootState } from '../../../redux/reducers';
import PassedVsFailed from '../PassedVsFailed';
import TotalWorkflows from '../TotalWorkflows';
import AverageResilienceScore from '../AverageResilienceScore';
import QuickActionCard from '../../../components/QuickActionCard';
import { Message } from '../../../models/header';
import ResilienceScoreComparisonPlot from '../ResilienceScoreComparisonPlot';
import RecentActivity from '../RecentActivity';
import Loader from '../../../components/Loader';
import {
WeightageMap,
WorkflowList,
WorkflowListDataVars,
} from '../../../models/graphql/workflowListData';
import { Message } from '../../../models/header';
import { RootState } from '../../../redux/reducers';
import AverageResilienceScore from '../AverageResilienceScore';
import PassedVsFailed from '../PassedVsFailed';
import RecentActivity from '../RecentActivity';
import ResilienceScoreComparisonPlot from '../ResilienceScoreComparisonPlot';
import TotalWorkflows from '../TotalWorkflows';
import useStyles from './style';
interface DataPresentCallBackType {
(dataPresent: boolean): void;
@ -93,7 +93,6 @@ const ReturningHome: React.FC<ReturningHomeProps> = ({
WORKFLOW_LIST_DETAILS,
{
variables: { projectID: userData.selectedProjectID, workflowIDs: [] },
fetchPolicy: 'cache-and-network',
}
);

View File

@ -34,8 +34,8 @@ const MyHub = () => {
const userData = useSelector((state: RootState) => state.userData);
// Get all MyHubs with status
const { data: userDetails } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: userData.username },
const { data: hubDetails } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: userData.selectedProjectID },
fetchPolicy: 'cache-and-network',
});
@ -43,7 +43,7 @@ const MyHub = () => {
const paramData: URLParams = useParams();
// Filter the selected MyHub
const UserHub = userDetails?.getHubStatus.filter((myHub) => {
const UserHub = hubDetails?.getHubStatus.filter((myHub) => {
return paramData.hubname === myHub.HubName;
})[0];
@ -55,12 +55,8 @@ const MyHub = () => {
// Query to get charts of selected MyHub
const { data, loading } = useQuery<Charts>(GET_CHARTS_DATA, {
variables: {
data: {
UserName: userData.username,
RepoURL: UserHub?.RepoURL,
RepoBranch: UserHub?.RepoBranch,
HubName: paramData.hubname,
},
HubName: paramData.hubname,
projectID: userData.selectedProjectID,
},
fetchPolicy: 'cache-and-network',
});

View File

@ -60,7 +60,7 @@ const MyHub = () => {
RepoURL: gitHub.GitURL,
RepoBranch: gitHub.GitBranch,
},
Username: userData.username,
projectID: userData.selectedProjectID,
},
});
setCloningRepo(true);

View File

@ -28,8 +28,8 @@ const MyHub = () => {
const userData = useSelector((state: RootState) => state.userData);
// Get all MyHubs with status
const { data: userDetails } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: userData.username },
const { data: hubDetails } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: userData.selectedProjectID },
fetchPolicy: 'cache-and-network',
});
@ -37,7 +37,7 @@ const MyHub = () => {
const paramData: URLParams = useParams();
// Filter the selected MyHub
const UserHub = userDetails?.getHubStatus.filter((myHub) => {
const UserHub = hubDetails?.getHubStatus.filter((myHub) => {
return paramData.hubname === myHub.HubName;
})[0];
@ -46,9 +46,7 @@ const MyHub = () => {
variables: {
data: {
HubName: paramData.hubname,
UserName: userData.username,
RepoURL: UserHub?.RepoURL,
RepoBranch: UserHub?.RepoBranch,
ProjectID: userData.selectedProjectID,
ChartName: paramData.chart,
ExperimentName: paramData.experiment,
},

View File

@ -15,6 +15,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/rs/cors v1.6.0
github.com/sirupsen/logrus v1.4.2
github.com/tidwall/gjson v1.6.0
github.com/tidwall/sjson v1.1.1
github.com/vektah/gqlparser/v2 v2.0.1
go.mongodb.org/mongo-driver v1.3.5
@ -25,7 +26,6 @@ require (
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/src-d/go-git.v4 v4.13.1
gopkg.in/yaml.v2 v2.3.0
k8s.io/api v0.18.6
k8s.io/apimachinery v0.18.6
k8s.io/client-go v0.18.6
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 // indirect

View File

@ -50,6 +50,7 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
@ -162,8 +163,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/litmuschaos/litmus v0.0.0-20201030044325-64ebcdc8ffcb h1:3DGMJMWqy8Yeyb7jkfTFE7MHMJ+2hDj9ov8vO8GtLjg=
github.com/litmuschaos/litmus v0.0.0-20201103140214-d61f522c8b8f h1:G7nRXRpmMmkkhIvEysz0i4UTZZwflRdoyWlJaGTmEuQ=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
@ -189,8 +188,10 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -286,7 +287,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -316,7 +316,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -357,11 +356,11 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
@ -371,6 +370,7 @@ gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOA
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
@ -394,6 +394,7 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,9 @@ import (
)
type ActionPayload struct {
RequestType *string `json:"request_type"`
K8sManifest *string `json:"k8s_manifest"`
Namespace *string `json:"namespace"`
RequestType string `json:"request_type"`
K8sManifest string `json:"k8s_manifest"`
Namespace string `json:"namespace"`
ExternalData *string `json:"external_data"`
}
@ -25,6 +25,7 @@ type Annotation struct {
}
type ChaosWorkFlowInput struct {
WorkflowID *string `json:"workflow_id"`
WorkflowManifest string `json:"workflow_manifest"`
CronSyntax string `json:"cronSyntax"`
WorkflowName string `json:"workflow_name"`
@ -56,9 +57,9 @@ type Charts struct {
Charts []*Chart `json:"Charts"`
}
type ChartsInput struct {
type CloningInput struct {
HubName string `json:"HubName"`
UserName string `json:"UserName"`
ProjectID string `json:"ProjectID"`
RepoBranch string `json:"RepoBranch"`
RepoURL string `json:"RepoURL"`
}
@ -150,12 +151,11 @@ type CreateUserInput struct {
}
type ExperimentInput struct {
UserName string `json:"UserName"`
RepoURL string `json:"RepoURL"`
RepoBranch string `json:"RepoBranch"`
ChartName string `json:"ChartName"`
ExperimentName string `json:"ExperimentName"`
HubName string `json:"HubName"`
ProjectID string `json:"ProjectID"`
ChartName string `json:"ChartName"`
ExperimentName string `json:"ExperimentName"`
HubName string `json:"HubName"`
FileType *string `json:"FileType"`
}
type Experiments struct {
@ -197,11 +197,13 @@ type Metadata struct {
}
type MyHub struct {
ID string `json:"id"`
RepoURL string `json:"RepoURL"`
RepoBranch string `json:"RepoBranch"`
IsConfirmed bool `json:"IsConfirmed"`
HubName string `json:"HubName"`
ID string `json:"id"`
RepoURL string `json:"RepoURL"`
RepoBranch string `json:"RepoBranch"`
ProjectID string `json:"ProjectID"`
HubName string `json:"HubName"`
CreatedAt string `json:"CreatedAt"`
UpdatedAt string `json:"UpdatedAt"`
}
type MyHubStatus struct {
@ -273,6 +275,7 @@ type ScheduledWorkflows struct {
ProjectID string `json:"project_id"`
ClusterID string `json:"cluster_id"`
ClusterType string `json:"cluster_type"`
IsRemoved bool `json:"isRemoved"`
}
type Spec struct {
@ -302,7 +305,6 @@ type User struct {
Username string `json:"username"`
Email *string `json:"email"`
IsEmailVerified *bool `json:"is_email_verified"`
MyHub []*MyHub `json:"my_hub"`
CompanyName *string `json:"company_name"`
Name *string `json:"name"`
Projects []*Project `json:"projects"`
@ -332,6 +334,7 @@ type Workflow struct {
ProjectID string `json:"project_id"`
ClusterID string `json:"cluster_id"`
ClusterType string `json:"cluster_type"`
IsRemoved bool `json:"isRemoved"`
WorkflowRuns []*WorkflowRuns `json:"workflow_runs"`
}

View File

@ -2,8 +2,10 @@ type MyHub {
id: ID!
RepoURL: String!
RepoBranch: String!
IsConfirmed: Boolean!
ProjectID: String!
HubName: String!
CreatedAt: String!
UpdatedAt: String!
}
type Charts {
@ -90,17 +92,16 @@ input CreateMyHub {
}
input ExperimentInput {
UserName: String!
RepoURL: String!
RepoBranch: String!
ProjectID: String!
ChartName: String!
ExperimentName: String!
HubName: String!
FileType: String
}
input ChartsInput {
input CloningInput {
HubName: String!
UserName: String!
ProjectID: String!
RepoBranch: String!
RepoURL: String!
}

View File

@ -49,9 +49,9 @@ type ClusterEvent {
}
type ActionPayload {
request_type: String
k8s_manifest: String
namespace: String
request_type: String!
k8s_manifest: String!
namespace: String!
external_data: String
}
@ -94,6 +94,7 @@ type weightages {
}
input ChaosWorkFlowInput {
workflow_id: String
workflow_manifest: String!
cronSyntax: String!
workflow_name: String!
@ -174,6 +175,7 @@ type ScheduledWorkflows {
project_id: ID!
cluster_id: ID!
cluster_type: String!
isRemoved: Boolean!
}
type Workflow {
@ -190,6 +192,7 @@ type Workflow {
project_id: ID!
cluster_id: ID!
cluster_type: String!
isRemoved: Boolean!
workflow_runs: [WorkflowRuns]
}
@ -222,11 +225,13 @@ type Query {
ListWorkflow(project_id: String!, workflow_ids: [ID]): [Workflow]! @authorized
getCharts(chartsInput: ChartsInput!): [Chart!]! @authorized
getCharts(HubName: String!, projectID: String!): [Chart!]! @authorized
getHubExperiment(experimentInput: ExperimentInput!): Chart! @authorized
getHubStatus(username: String!): [MyHubStatus]! @authorized
getHubStatus(projectID: String!): [MyHubStatus]! @authorized
getYAMLData(experimentInput: ExperimentInput!): String!
}
type Mutation {
@ -261,9 +266,14 @@ type Mutation {
podLog(log: PodLog!): String!
addMyHub(myhubInput: CreateMyHub!, username: String!): User! @authorized
addMyHub(myhubInput: CreateMyHub!, projectID: String!): MyHub! @authorized
syncHub(syncHubInput: ChartsInput!): [MyHubStatus!]! @authorized
syncHub(projectID: String!, HubName: String!): [MyHubStatus!]! @authorized
updateChaosWorkflow(input: ChaosWorkFlowInput): ChaosWorkFlowResponse!
@authorized
deleteClusterReg(cluster_id: String!): String! @authorized
}
type Subscription {

View File

@ -42,7 +42,7 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, user model.UpdateUser
}
func (r *mutationResolver) DeleteChaosWorkflow(ctx context.Context, workflowid string) (bool, error) {
return database.DeleteChaosWorkflow(workflowid)
return mutations.DeleteWorkflow(workflowid, *store)
}
func (r *mutationResolver) SendInvitation(ctx context.Context, member model.MemberInput) (*model.Member, error) {
@ -77,12 +77,20 @@ func (r *mutationResolver) PodLog(ctx context.Context, log model.PodLog) (string
return mutations.LogsHandler(log, *store)
}
func (r *mutationResolver) AddMyHub(ctx context.Context, myhubInput model.CreateMyHub, username string) (*model.User, error) {
return myhub.AddMyHub(ctx, myhubInput, username)
func (r *mutationResolver) AddMyHub(ctx context.Context, myhubInput model.CreateMyHub, projectID string) (*model.MyHub, error) {
return myhub.AddMyHub(ctx, myhubInput, projectID)
}
func (r *mutationResolver) SyncHub(ctx context.Context, syncHubInput model.ChartsInput) ([]*model.MyHubStatus, error) {
return myhub.SyncHub(ctx, syncHubInput)
func (r *mutationResolver) SyncHub(ctx context.Context, projectID string, hubName string) ([]*model.MyHubStatus, error) {
return myhub.SyncHub(ctx, projectID, hubName)
}
func (r *mutationResolver) UpdateChaosWorkflow(ctx context.Context, input *model.ChaosWorkFlowInput) (*model.ChaosWorkFlowResponse, error) {
return mutations.UpdateWorkflow(input, *store)
}
func (r *mutationResolver) DeleteClusterReg(ctx context.Context, clusterID string) (string, error) {
return mutations.DeleteCluster(clusterID, *store)
}
func (r *queryResolver) GetWorkFlowRuns(ctx context.Context, projectID string) ([]*model.WorkflowRun, error) {
@ -117,16 +125,20 @@ func (r *queryResolver) ListWorkflow(ctx context.Context, projectID string, work
}
}
func (r *queryResolver) GetCharts(ctx context.Context, chartsInput model.ChartsInput) ([]*model.Chart, error) {
return myhub.GetCharts(ctx, chartsInput)
func (r *queryResolver) GetCharts(ctx context.Context, hubName string, projectID string) ([]*model.Chart, error) {
return myhub.GetCharts(ctx, hubName, projectID)
}
func (r *queryResolver) GetHubExperiment(ctx context.Context, experimentInput model.ExperimentInput) (*model.Chart, error) {
return myhub.GetExperiment(ctx, experimentInput)
}
func (r *queryResolver) GetHubStatus(ctx context.Context, username string) ([]*model.MyHubStatus, error) {
return myhub.HubStatus(ctx, username)
func (r *queryResolver) GetHubStatus(ctx context.Context, projectID string) ([]*model.MyHubStatus, error) {
return myhub.HubStatus(ctx, projectID)
}
func (r *queryResolver) GetYAMLData(ctx context.Context, experimentInput model.ExperimentInput) (string, error) {
return myhub.GetYAMLData(ctx, experimentInput)
}
func (r *subscriptionResolver) ClusterEventListener(ctx context.Context, projectID string) (<-chan *model.ClusterEvent, error) {

View File

@ -3,7 +3,6 @@ type User {
username: String!
email: String
is_email_verified: Boolean
my_hub: [MyHub!]!
company_name: String
name: String
projects: [Project!]!

Binary file not shown.

View File

@ -1033,7 +1033,6 @@ metadata:
name: argo-chaos
namespace: #{AGENT-NAMESPACE}
---
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
@ -1074,6 +1073,7 @@ rules:
verbs:
- get
- create
- delete
- apiGroups:
- litmuschaos.io
resources:
@ -1097,6 +1097,8 @@ rules:
- get
- list
- watch
- delete
- update
- apiGroups:
- ""
resources:
@ -1105,6 +1107,12 @@ rules:
- get
- list
- watch
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- delete
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding

View File

@ -401,6 +401,7 @@ rules:
verbs:
- get
- create
- delete
- apiGroups:
- litmuschaos.io
resources:
@ -422,6 +423,8 @@ rules:
- get
- list
- watch
- delete
- update
- apiGroups:
- ""
resources:
@ -430,6 +433,12 @@ rules:
- get
- list
- watch
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- delete
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding

View File

@ -47,6 +47,7 @@ type Cluster struct {
CreatedAt string `bson:"created_at"`
ClusterType string `bson:"cluster_type"`
Token string `bson:"token"`
IsRemoved bool `bson:"is_removed"`
}
type ChaosWorkFlowInput struct {
@ -62,6 +63,7 @@ type ChaosWorkFlowInput struct {
ProjectID string `bson:"project_id"`
ClusterID string `bson:"cluster_id"`
WorkflowRuns []*WorkflowRun `bson:"workflow_runs"`
IsRemoved bool `bson:"isRemoved"`
}
type WorkflowRun struct {
@ -79,11 +81,22 @@ type WeightagesInput struct {
//init initializes database connection
func init() {
dbServer := os.Getenv("DB_SERVER")
if dbServer == "" {
log.Fatal("Environment Variable DB_SERVER is not present")
var (
dbServer = os.Getenv("DB_SERVER")
username = os.Getenv("DB_USER")
pwd = os.Getenv("DB_PASSWORD")
)
if dbServer == "" || username == "" || pwd == "" {
log.Fatal("DB configuration failed")
}
clientOptions := options.Client().ApplyURI(dbServer)
credential := options.Credential{
Username: username,
Password: pwd,
}
clientOptions := options.Client().ApplyURI(dbServer).SetAuth(credential)
client, err := mongo.Connect(backgroundContext, clientOptions)
if err != nil {
log.Fatal(err)

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"log"
"time"
"go.mongodb.org/mongo-driver/bson"
@ -132,9 +131,9 @@ func GetClusterWithProjectID(project_id string, cluster_type *string) ([]*Cluste
var query bson.M
if cluster_type == nil {
query = bson.M{"project_id": project_id}
query = bson.M{"project_id": project_id, "is_removed": false}
} else {
query = bson.M{"project_id": project_id, "cluster_type": cluster_type}
query = bson.M{"project_id": project_id, "cluster_type": cluster_type, "is_removed": false}
}
fmt.Print(query)
@ -164,16 +163,13 @@ func InsertChaosWorkflow(chaosWorkflow ChaosWorkFlowInput) error {
return nil
}
func DeleteChaosWorkflow(workflowid string) (bool, error) {
func UpdateChaosWorkflow(query bson.D, update bson.D) error {
ctx, _ := context.WithTimeout(backgroundContext, 10*time.Second)
res, err := workflowCollection.DeleteOne(ctx, bson.M{"workflow_id": workflowid})
_, err := workflowCollection.UpdateOne(ctx, query, update)
if err != nil {
return false, err
} else if res.DeletedCount == 0 {
return false, nil
return err
}
log.Println("Successfully delete %v", workflowid)
return true, nil
return nil
}

View File

@ -0,0 +1,62 @@
package operations
import (
"context"
"log"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/database/mongodb"
dbSchema "github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/database/mongodb/schema"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
var myhubCollection *mongo.Collection
func init() {
myhubCollection = mongodb.Database.Collection("myhub")
}
//CreateMyHub ...
func CreateMyHub(ctx context.Context, myhub *dbSchema.MyHub) error {
_, err := myhubCollection.InsertOne(ctx, myhub)
if err != nil {
log.Print("Error creating MyHub: ", err)
return err
}
return nil
}
//GetMyHubByProjectID ...
func GetMyHubByProjectID(ctx context.Context, projectID string) ([]dbSchema.MyHub, error) {
query := bson.M{"project_id": projectID}
cursor, err := myhubCollection.Find(ctx, query)
if err != nil {
log.Print("ERROR GETTING USERS : ", err)
return []dbSchema.MyHub{}, err
}
var myhubs []dbSchema.MyHub
err = cursor.All(ctx, &myhubs)
if err != nil {
log.Print("Error deserializing myhubs in myhub object : ", err)
return []dbSchema.MyHub{}, err
}
return myhubs, nil
}
//GetHubs ...
func GetHubs(ctx context.Context) ([]dbSchema.MyHub, error) {
// ctx, _ := context.WithTimeout(backgroundContext, 10*time.Second)
query := bson.D{{}}
cursor, err := myhubCollection.Find(ctx, query)
if err != nil {
log.Print("ERROR GETTING MYHUBS : ", err)
return []dbSchema.MyHub{}, err
}
var MyHubs []dbSchema.MyHub
err = cursor.All(ctx, &MyHubs)
if err != nil {
log.Print("Error deserializing myhubs in the myhub object : ", err)
return []dbSchema.MyHub{}, err
}
return MyHubs, nil
}

View File

@ -89,16 +89,3 @@ func UpdateUser(ctx context.Context, user *dbSchema.User) error {
return nil
}
//AddNewMyHub ...
func AddNewMyHub(ctx context.Context, username string, myHub *dbSchema.MyHub) error {
query := bson.M{"username": username}
change := bson.M{"$push": bson.M{"myhub": myHub}}
_, err := userCollection.UpdateOne(ctx, query, change)
if err != nil {
log.Print("Error adding hub")
return err
}
return nil
}

View File

@ -0,0 +1,28 @@
package schema
import "github.com/litmuschaos/litmus/litmus-portal/graphql-server/graph/model"
//MyHub ...
type MyHub struct {
ID string `bson:"myhub_id"`
ProjectID string `bson:"project_id"`
RepoURL string `bson:"repo_url"`
RepoBranch string `bson:"repo_branch"`
HubName string `bson:"hub_name"`
CreatedAt string `bson:"created_at"`
UpdatedAt string `bson:"updated_at"`
}
//GetOutputMyHub ...
func (myhub *MyHub) GetOutputMyHub() *model.MyHub {
return &model.MyHub{
ID: myhub.ID,
ProjectID: myhub.ProjectID,
RepoURL: myhub.RepoURL,
RepoBranch: myhub.RepoBranch,
HubName: myhub.HubName,
CreatedAt: myhub.CreatedAt,
UpdatedAt: myhub.UpdatedAt,
}
}

View File

@ -4,18 +4,17 @@ import "github.com/litmuschaos/litmus/litmus-portal/graphql-server/graph/model"
//User ...
type User struct {
ID string `bson:"_id"`
Username string `bson:"username"`
Email *string `bson:"email"`
IsEmailVerified *bool `bson:"is_email_verified"`
MyHub []*MyHub `bson:"myhub"`
CompanyName *string `bson:"company_name"`
Name *string `bson:"name"`
Role *string `bson:"role"`
State *string `bson:"state"`
CreatedAt string `bson:"created_at"`
UpdatedAt string `bson:"updated_at"`
RemovedAt string `bson:"removed_at"`
ID string `bson:"_id"`
Username string `bson:"username"`
Email *string `bson:"email"`
IsEmailVerified *bool `bson:"is_email_verified"`
CompanyName *string `bson:"company_name"`
Name *string `bson:"name"`
Role *string `bson:"role"`
State *string `bson:"state"`
CreatedAt string `bson:"created_at"`
UpdatedAt string `bson:"updated_at"`
RemovedAt string `bson:"removed_at"`
}
//GetOutputUser ...
@ -26,7 +25,6 @@ func (user User) GetOutputUser() *model.User {
Username: user.Username,
Email: user.Email,
IsEmailVerified: user.IsEmailVerified,
MyHub: user.GetOuputMyHubs(),
CompanyName: user.CompanyName,
Name: user.Name,
Role: user.Role,
@ -37,36 +35,3 @@ func (user User) GetOutputUser() *model.User {
}
}
//MyHub ...
type MyHub struct {
ID string `bson:"myhub_id"`
RepoURL string `bson:"repo_url"`
RepoBranch string `bson:"repo_branch"`
IsConfirmed bool `bson:"is_confirmed"`
HubName string `bson:"hub_name"`
}
//GetOuputMyHubs ...
func (user *User) GetOuputMyHubs() []*model.MyHub {
outputMyHub := []*model.MyHub{}
for _, myhub := range user.MyHub {
outputMyHub = append(outputMyHub, myhub.GetOutputMyHub())
}
return outputMyHub
}
//GetOutputMyHub ...
func (myhub *MyHub) GetOutputMyHub() *model.MyHub {
return &model.MyHub{
ID: myhub.ID,
RepoURL: myhub.RepoURL,
RepoBranch: myhub.RepoBranch,
IsConfirmed: myhub.IsConfirmed,
HubName: myhub.HubName,
}
}

View File

@ -1,17 +1,15 @@
package file_handlers
import (
"log"
"net/http"
"os"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/k8s"
"github.com/gorilla/mux"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/cluster"
database "github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/database/mongodb"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/k8s"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/types"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/utils"
"log"
"net/http"
"os"
)
var subscriberConfiguration = &types.SubscriberConfigurationVars{
@ -28,36 +26,45 @@ var subscriberConfiguration = &types.SubscriberConfigurationVars{
//FileHandler dynamically generates the manifest file and sends it as a response
func FileHandler(w http.ResponseWriter, r *http.Request) {
var (
vars = mux.Vars(r)
token = vars["key"]
vars = mux.Vars(r)
token = vars["key"]
)
response, statusCode, err := GetManifest(token)
if err != nil {
log.Print("error", err)
utils.WriteHeaders(&w, statusCode)
}
utils.WriteHeaders(&w, statusCode)
w.Write(response)
}
func GetManifest(token string) ([]byte, int, error) {
var (
portalEndpoint string
)
id, err := cluster.ClusterValidateJWT(token)
if err != nil {
log.Print("ERROR", err)
utils.WriteHeaders(&w, 404)
return
return nil, 404, err
}
reqCluster, err := database.GetCluster(id)
if err != nil {
log.Print("ERROR", err)
utils.WriteHeaders(&w, 500)
return
return nil, 500, err
}
if os.Getenv("PORTAL_SCOPE") == "cluster" {
portalEndpoint, err = k8s.GetPortalEndpoint()
if err != nil {
log.Print(err)
return nil, 500, err
}
} else if os.Getenv("PORTAL_SCOPE") == "namespace" {
portalEndpoint = os.Getenv("PORTAL_ENDPOINT")
}
subscriberConfiguration.GQLServerURI = portalEndpoint + "/query"
if !reqCluster.IsRegistered {
var respData []byte
@ -68,16 +75,13 @@ func FileHandler(w http.ResponseWriter, r *http.Request) {
} else {
log.Print("ERROR- AGENT SCOPE NOT SELECTED!")
}
if err != nil {
log.Print("ERROR", err)
utils.WriteHeaders(&w, 500)
return
return nil, 500, err
}
utils.WriteHeaders(&w, 200)
w.Write(respData)
return
return respData, 200, nil
} else {
return []byte("Cluster is already registered"), 409, nil
}
utils.WriteHeaders(&w, 404)
}

View File

@ -3,10 +3,13 @@ package mutations
import (
"encoding/json"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/graphql"
"github.com/jinzhu/copier"
"github.com/google/uuid"
@ -17,6 +20,7 @@ import (
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/graphql/subscriptions"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/utils"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"go.mongodb.org/mongo-driver/bson"
)
@ -46,6 +50,7 @@ func ClusterRegister(input model.ClusterInput) (*model.ClusterRegResponse, error
CreatedAt: strconv.FormatInt(time.Now().Unix(), 10),
UpdatedAt: strconv.FormatInt(time.Now().Unix(), 10),
Token: token,
IsRemoved: false,
}
err = database.InsertCluster(newCluster)
@ -191,6 +196,8 @@ func CreateChaosWorkflow(input *model.ChaosWorkFlowInput, r store.StateData) (*m
newWorkflowManifest, _ = sjson.Set(input.WorkflowManifest, "spec.workflowMetadata.labels.workflow_id", workflow_id)
}
isRemoved := false
newChaosWorkflow := database.ChaosWorkFlowInput{
WorkflowID: workflow_id,
WorkflowManifest: newWorkflowManifest,
@ -204,6 +211,7 @@ func CreateChaosWorkflow(input *model.ChaosWorkFlowInput, r store.StateData) (*m
CreatedAt: strconv.FormatInt(time.Now().Unix(), 10),
UpdatedAt: strconv.FormatInt(time.Now().Unix(), 10),
WorkflowRuns: []*database.WorkflowRun{},
IsRemoved: isRemoved,
}
err = database.InsertChaosWorkflow(newChaosWorkflow)
@ -211,7 +219,19 @@ func CreateChaosWorkflow(input *model.ChaosWorkFlowInput, r store.StateData) (*m
return nil, err
}
subscriptions.SendWorkflowRequest(&newChaosWorkflow, r)
workflowNamespace := gjson.Get(newWorkflowManifest, "metadata.namespace").String()
if workflowNamespace == "" {
workflowNamespace = os.Getenv("AGENT_NAMESPACE")
}
subscriptions.SendRequestToSubscriber(graphql.SubscriberRequests{
K8sManifest: newWorkflowManifest,
RequestType: "create",
ProjectID: input.ProjectID,
ClusterID: input.ClusterID,
Namespace: workflowNamespace,
}, r)
return &model.ChaosWorkFlowResponse{
WorkflowID: workflow_id,
@ -221,3 +241,135 @@ func CreateChaosWorkflow(input *model.ChaosWorkFlowInput, r store.StateData) (*m
IsCustomWorkflow: input.IsCustomWorkflow,
}, nil
}
func DeleteCluster(cluster_id string, r store.StateData) (string, error) {
time := strconv.FormatInt(time.Now().Unix(), 10)
query := bson.D{{"cluster_id", cluster_id}}
update := bson.D{{"$set", bson.D{{"is_removed", true}, {"updated_at", time}}}}
err := database.UpdateCluster(query, update)
if err != nil {
return "", err
}
cluster, err := database.GetCluster(cluster_id)
if err != nil {
return "", nil
}
requests := []string{
`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "subscriber",
"namespace": ` + *cluster.AgentNamespace + `
}
}`,
`{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "litmus-portal-config",
"namespace": ` + *cluster.AgentNamespace + `
}
}`,
}
for _, request := range requests {
subscriptions.SendRequestToSubscriber(graphql.SubscriberRequests{
K8sManifest: request,
RequestType: "delete",
ProjectID: cluster.ProjectID,
ClusterID: cluster_id,
Namespace: *cluster.AgentNamespace,
}, r)
}
return "Successfully deleted cluster", nil
}
func UpdateWorkflow(workflow *model.ChaosWorkFlowInput, r store.StateData) (*model.ChaosWorkFlowResponse, error) {
var newWeightages []*database.WeightagesInput
copier.Copy(&newWeightages, &workflow.Weightages)
var workflowManifest map[string]interface{}
err := json.Unmarshal([]byte(workflow.WorkflowManifest), &workflowManifest)
if err != nil {
return nil, err
}
newWorkflowManifest, err := sjson.Set(workflow.WorkflowManifest, "metadata.labels.workflow_id", workflow.WorkflowID)
if err != nil {
return nil, err
}
if strings.ToLower(workflowManifest["kind"].(string)) == "cronworkflow" {
newWorkflowManifest, _ = sjson.Set(workflow.WorkflowManifest, "spec.workflowMetadata.labels.workflow_id", workflow.WorkflowID)
}
query := bson.D{{"workflow_id", workflow.WorkflowID}}
update := bson.D{{"$set", bson.D{{"workflow_manifest", newWorkflowManifest}, {"cronSyntax", workflow.CronSyntax}, {"Workflow_name", workflow.WorkflowName}, {"Workflow_description", workflow.WorkflowDescription}, {"isCustomWorkflow", workflow.IsCustomWorkflow}, {"Weightages", newWeightages}, {"updated_at", strconv.FormatInt(time.Now().Unix(), 10)}}}}
err = database.UpdateChaosWorkflow(query, update)
if err != nil {
return nil, err
}
workflowNamespace := gjson.Get(workflow.WorkflowManifest, "metadata.namespace").String()
if workflowNamespace == "" {
workflowNamespace = os.Getenv("AGENT_NAMESPACE")
}
subscriptions.SendRequestToSubscriber(graphql.SubscriberRequests{
K8sManifest: newWorkflowManifest,
RequestType: "update",
ProjectID: workflow.ProjectID,
ClusterID: workflow.ClusterID,
Namespace: workflowNamespace,
}, r)
return &model.ChaosWorkFlowResponse{
WorkflowID: *workflow.WorkflowID,
CronSyntax: workflow.CronSyntax,
WorkflowName: workflow.WorkflowName,
WorkflowDescription: workflow.WorkflowDescription,
IsCustomWorkflow: workflow.IsCustomWorkflow,
}, nil
}
func DeleteWorkflow(workflow_id string, r store.StateData) (bool, error) {
workflows, err := database.GetWorkflowsByIDs([]*string{&workflow_id})
if err != nil {
return false, err
}
query := bson.D{{"workflow_id", workflow_id}}
update := bson.D{{"$set", bson.D{{"isRemoved", true}}}}
err = database.UpdateChaosWorkflow(query, update)
if err != nil {
return false, err
}
for _, workflow := range workflows {
workflowNamespace := gjson.Get(workflow.WorkflowManifest, "metadata.namespace").String()
if workflowNamespace == "" {
workflowNamespace = os.Getenv("AGENT_NAMESPACE")
}
subscriptions.SendRequestToSubscriber(graphql.SubscriberRequests{
K8sManifest: workflow.WorkflowManifest,
RequestType: "delete",
ProjectID: workflow.ProjectID,
ClusterID: workflow.ClusterID,
Namespace: workflowNamespace,
}, r)
}
return true, nil
}

View File

@ -55,25 +55,28 @@ func QueryWorkflows(project_id string) ([]*model.ScheduledWorkflows, error) {
if err != nil {
return nil, err
}
var Weightages []*model.Weightages
copier.Copy(&Weightages, &workflow.Weightages)
if workflow.IsRemoved == false {
var Weightages []*model.Weightages
copier.Copy(&Weightages, &workflow.Weightages)
newChaosWorkflows := model.ScheduledWorkflows{
WorkflowID: workflow.WorkflowID,
WorkflowManifest: workflow.WorkflowManifest,
WorkflowName: workflow.WorkflowName,
CronSyntax: workflow.CronSyntax,
WorkflowDescription: workflow.WorkflowDescription,
Weightages: Weightages,
IsCustomWorkflow: workflow.IsCustomWorkflow,
UpdatedAt: workflow.UpdatedAt,
CreatedAt: workflow.CreatedAt,
ProjectID: workflow.ProjectID,
ClusterName: cluster.ClusterName,
ClusterID: cluster.ClusterID,
ClusterType: cluster.ClusterType,
newChaosWorkflows := model.ScheduledWorkflows{
WorkflowID: workflow.WorkflowID,
WorkflowManifest: workflow.WorkflowManifest,
WorkflowName: workflow.WorkflowName,
CronSyntax: workflow.CronSyntax,
WorkflowDescription: workflow.WorkflowDescription,
Weightages: Weightages,
IsCustomWorkflow: workflow.IsCustomWorkflow,
UpdatedAt: workflow.UpdatedAt,
CreatedAt: workflow.CreatedAt,
ProjectID: workflow.ProjectID,
IsRemoved: workflow.IsRemoved,
ClusterName: cluster.ClusterName,
ClusterID: cluster.ClusterID,
ClusterType: cluster.ClusterType,
}
result = append(result, &newChaosWorkflows)
}
result = append(result, &newChaosWorkflows)
}
return result, nil
@ -110,6 +113,7 @@ func QueryListWorkflow(project_id string) ([]*model.Workflow, error) {
UpdatedAt: workflow.UpdatedAt,
CreatedAt: workflow.CreatedAt,
ProjectID: workflow.ProjectID,
IsRemoved: workflow.IsRemoved,
ClusterName: cluster.ClusterName,
ClusterID: cluster.ClusterID,
ClusterType: cluster.ClusterType,
@ -174,7 +178,7 @@ func GetLogs(reqID string, pod model.PodLogRequest, r store.StateData) {
payload := model.ClusterAction{
ProjectID: reqID,
Action: &model.ActionPayload{
RequestType: &reqType,
RequestType: reqType,
ExternalData: &externalData,
},
}

View File

@ -1,12 +1,12 @@
package subscriptions
import (
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/graphql"
"os"
"github.com/google/uuid"
"github.com/litmuschaos/litmus/litmus-portal/graphql-server/graph/model"
store "github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/data-store"
database "github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/database/mongodb"
)
//SendClusterEvent sends events from the clusters to the appropriate users listening for the events
@ -38,9 +38,7 @@ func SendWorkflowEvent(wfRun model.WorkflowRun, r store.StateData) {
r.Mutex.Unlock()
}
func SendWorkflowRequest(wfRequest *database.ChaosWorkFlowInput, r store.StateData) {
namespace := os.Getenv("AGENT_NAMESPACE")
func SendRequestToSubscriber(subscriberRequest graphql.SubscriberRequests, r store.StateData) {
if os.Getenv("AGENT_SCOPE") == "cluster" {
/*
namespace = Obtain from WorkflowManifest or
@ -48,19 +46,18 @@ func SendWorkflowRequest(wfRequest *database.ChaosWorkFlowInput, r store.StateDa
for CreateChaosWorkflow mutation to be passed to this function.
*/
}
requesttype := "create"
newAction := &model.ClusterAction{
ProjectID: wfRequest.ProjectID,
ProjectID: subscriberRequest.ProjectID,
Action: &model.ActionPayload{
K8sManifest: &wfRequest.WorkflowManifest,
Namespace: &namespace,
RequestType: &requesttype,
K8sManifest: subscriberRequest.K8sManifest,
Namespace: subscriberRequest.Namespace,
RequestType: subscriberRequest.RequestType,
},
}
r.Mutex.Lock()
if observer, ok := r.ConnectedCluster[wfRequest.ClusterID]; ok {
if observer, ok := r.ConnectedCluster[subscriberRequest.ClusterID]; ok {
observer <- newAction
}

View File

@ -0,0 +1,10 @@
package graphql
type SubscriberRequests struct {
RequestType string `json:"request_type"`
K8sManifest string `json:"k8s_manifest"`
ExternalData *string `json:"external_data"`
ProjectID string `json:"project_id"`
ClusterID string `json:"cluster_id"`
Namespace string `json:"namespace"`
}

View File

@ -1,6 +1,8 @@
package k8s
import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
@ -25,3 +27,26 @@ func GetGenericK8sClient() (*kubernetes.Clientset, error) {
return kubernetes.NewForConfig(config)
}
//This function returns dynamic client and discovery client
func GetDynamicAndDiscoveryClient() (discovery.DiscoveryInterface, dynamic.Interface, error) {
// returns a config object which uses the service account kubernetes gives to pods
config, err := GetKubeConfig()
if err != nil {
return nil, nil, err
}
// NewDiscoveryClientForConfig creates a new DiscoveryClient for the given config
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
if err != nil {
return nil, nil, err
}
// NewForConfig creates a new dynamic client or returns an error.
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, nil, err
}
return discoveryClient, dynamicClient, nil
}

View File

@ -2,94 +2,74 @@ package k8s
import (
"context"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"log"
"os"
"strconv"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
memory "k8s.io/client-go/discovery/cached"
)
func CreateDeployment(namespace, token string) (*appsv1.Deployment, error) {
deployerImage := os.Getenv("DEPLOYER_IMAGE")
subscriberSC := os.Getenv("AGENT_SCOPE")
selfDeployerSvcAccount := "self-deployer-namespace-account"
if subscriberSC == "cluster" {
selfDeployerSvcAccount = "self-deployer-admin-account"
}
cfg, err := GetKubeConfig()
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
panic(err)
}
deploymentsClient := clientset.AppsV1().Deployments(namespace)
var (
decUnstructured = yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
dr dynamic.ResourceInterface
AgentNamespace = os.Getenv("AGENT_NAMESPACE")
)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "self-deployer",
Labels: map[string]string{"component": "self-deployer"},
Namespace: namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: func(i int32) *int32 { return &i }(1),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"component": "self-deployer",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"component": "self-deployer",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "deployer",
Image: deployerImage,
Resources: apiv1.ResourceRequirements{
Limits: apiv1.ResourceList{
"cpu": resource.MustParse("100m"),
"memory": resource.MustParse("128Mi"),
},
},
ImagePullPolicy: "Always",
Env: []apiv1.EnvVar{
{
Name: "SERVER",
Value: "http://litmusportal-server-service:9002",
},
{
Name: "TOKEN",
Value: token,
},
{
Name: "NAMESPACE",
ValueFrom: &apiv1.EnvVarSource{
FieldRef: &apiv1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
},
},
},
ServiceAccountName: selfDeployerSvcAccount,
},
},
},
}
// Create Deployment
log.Println("Creating deployment...")
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
// This function handles cluster operations
func ClusterResource(manifest string, namespace string) (*unstructured.Unstructured, error) {
// Getting dynamic and discovery client
discoveryClient, dynamicClient, err := GetDynamicAndDiscoveryClient()
if err != nil {
return nil, err
}
return result, nil
// Create a mapper using dynamic client
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(discoveryClient))
// Decode YAML manifest into unstructured.Unstructured
obj := &unstructured.Unstructured{}
_, gvk, err := decUnstructured.Decode([]byte(manifest), nil, obj)
if err != nil {
return nil, err
}
// Find GVR
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return nil, err
}
// Obtain REST interface for the GVR
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
// namespaced resources should specify the namespace
dr = dynamicClient.Resource(mapping.Resource).Namespace(namespace)
} else {
// for cluster-wide resources
dr = dynamicClient.Resource(mapping.Resource)
}
response, err := dr.Create(context.TODO(), obj, metav1.CreateOptions{})
if errors.IsAlreadyExists(err) {
// This doesnt ever happen even if it does already exist
log.Print("Already exists")
return nil, nil
}
if err != nil {
return nil, err
}
log.Println("Resource successfully created")
return response, nil
}
func GetPortalEndpoint() (string, error) {

Some files were not shown because too many files have changed in this diff Show More